refactor: library queries (#32489)
* refactor: library queries * fix: mutation options signature
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createDefinedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import StorageTemplateVariablesModal from '$lib/modals/StorageTemplateVariablesModal.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
@@ -40,7 +40,7 @@
|
||||
type StorageTemplateOption = (typeof StorageTemplateOption)[keyof typeof StorageTemplateOption];
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
let configToEdit = $state(cloneDeep(config));
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
onDestroy(() => {
|
||||
if (saveOnClose) {
|
||||
void executeMutation(updateConfigMutation(), { storageTemplate: configToEdit.storageTemplate });
|
||||
void executeMutation(updateConfigMutation, { storageTemplate: configToEdit.storageTemplate });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
|
||||
const query = createQuery(() => getAboutInfoQuery());
|
||||
const query = createQuery(getAboutInfoQuery);
|
||||
const info = $derived(query.data);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigDefaultsQuery, getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import type { AdminConfigDto } from '@immich/sdk';
|
||||
import { Button, toastManager } from '@immich/ui';
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
let { disabled, keys, configToEdit = $bindable(), onBeforeSave }: Props = $props();
|
||||
|
||||
const config = createDefinedQuery(getConfigQuery());
|
||||
const defaults = createDefinedQuery(getConfigDefaultsQuery());
|
||||
const config = createPreloadedQuery(getConfigQuery);
|
||||
const defaults = createPreloadedQuery(getConfigDefaultsQuery);
|
||||
const save = createMutation(updateConfigMutation);
|
||||
|
||||
const showResetToDefault = $derived(!isEqual(pick(config.data, keys), pick(defaults.data, keys)));
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
const { serverVersion, connected } = websocketStore;
|
||||
|
||||
const aboutQuery = createQuery(() => getAboutInfoQuery());
|
||||
const aboutQuery = createQuery(getAboutInfoQuery);
|
||||
const info = $derived(aboutQuery.data);
|
||||
const historyQuery = createQuery(() => getVersionHistoryQuery());
|
||||
const historyQuery = createQuery(getVersionHistoryQuery);
|
||||
const versions = $derived(historyQuery.data ?? []);
|
||||
let isMain = $derived(info?.sourceRef === 'main' && info.repository === 'immich-app/immich');
|
||||
let version = $derived($serverVersion ? semverToName($serverVersion) : null);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createQuery(() => getStorageQuery());
|
||||
const query = createQuery(getStorageQuery);
|
||||
const serverInfo = $derived(query.data);
|
||||
|
||||
let hasQuota = $derived(authManager.user.quotaSizeInBytes !== null);
|
||||
|
||||
@@ -311,3 +311,12 @@ export enum BackupFileStatus {
|
||||
}
|
||||
|
||||
export const assetViewerFadeDuration: number = 150;
|
||||
|
||||
export const libraryTableClasses = {
|
||||
column1: 'w-4/12',
|
||||
column2: 'w-4/12',
|
||||
column3: 'w-1/12',
|
||||
column4: 'w-1/12',
|
||||
column5: 'w-1/12',
|
||||
column6: 'w-1/12 flex justify-end',
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
AssetResponseDto,
|
||||
IntegrityReport,
|
||||
JobCreateDto,
|
||||
LibraryResponseDto,
|
||||
LoginResponseDto,
|
||||
PersonResponseDto,
|
||||
QueueResponseDto,
|
||||
@@ -98,10 +97,6 @@ export type Events = {
|
||||
|
||||
JobCreate: [{ dto: JobCreateDto }];
|
||||
|
||||
LibraryCreate: [LibraryResponseDto];
|
||||
LibraryUpdate: [LibraryResponseDto];
|
||||
LibraryDelete: [{ id: string }];
|
||||
|
||||
WorkflowCreate: [WorkflowResponseDto];
|
||||
WorkflowUpdate: [WorkflowResponseDto];
|
||||
WorkflowDelete: [WorkflowResponseDto];
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { handleAddLibraryExclusionPattern } from '$lib/services/library.service';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { Field, FormModal, Input, Text } from '@immich/ui';
|
||||
import { Field, FormModal, HelperText, Input, Text } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -12,12 +13,13 @@
|
||||
|
||||
const { library, onClose }: Props = $props();
|
||||
let value = $state('');
|
||||
const exists = $derived(library.exclusionPatterns.includes(value));
|
||||
|
||||
const onSubmit = async () => {
|
||||
const success = await handleAddLibraryExclusionPattern(library, value);
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
const exclusionPatterns = [...library.exclusionPatterns, value];
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }], {
|
||||
onSuccess: onClose,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -28,9 +30,13 @@
|
||||
{onSubmit}
|
||||
submitText={$t('add')}
|
||||
size="small"
|
||||
disabled={!value || exists}
|
||||
>
|
||||
<Text size="small" class="mb-4">{$t('admin.exclusion_pattern_description')}</Text>
|
||||
<Field label={$t('pattern')}>
|
||||
<Input bind:value />
|
||||
{#if exists}
|
||||
<HelperText color="danger">{$t('errors.exclusion_pattern_already_exists')}</HelperText>
|
||||
{/if}
|
||||
</Field>
|
||||
</FormModal>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { handleEditExclusionPattern } from '$lib/services/library.service';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { Field, FormModal, Input, Text } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
@@ -15,14 +16,16 @@
|
||||
let newValue = $state(oldValue);
|
||||
|
||||
const onSubmit = async () => {
|
||||
const success = await handleEditExclusionPattern(library, oldValue, newValue);
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
const exclusionPatterns = library.exclusionPatterns.map((pattern) => (pattern === oldValue ? newValue : pattern));
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }], {
|
||||
onSuccess: onClose,
|
||||
});
|
||||
};
|
||||
|
||||
const disabled = $derived(!newValue || newValue === oldValue);
|
||||
</script>
|
||||
|
||||
<FormModal title={$t('edit_exclusion_pattern')} icon={mdiFolderSync} {onClose} {onSubmit} size="small">
|
||||
<FormModal title={$t('edit_exclusion_pattern')} icon={mdiFolderSync} {onClose} {onSubmit} size="small" {disabled}>
|
||||
<Text size="small" class="mb-4">{$t('admin.exclusion_pattern_description')}</Text>
|
||||
<Field label={$t('pattern')}>
|
||||
<Input bind:value={newValue} />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { handleAddLibraryFolder } from '$lib/services/library.service';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { Field, FormModal, Input, Text } from '@immich/ui';
|
||||
import { Field, FormModal, HelperText, Input, Text } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -12,12 +13,13 @@
|
||||
|
||||
const { library, onClose }: Props = $props();
|
||||
let value = $state('');
|
||||
const exists = $derived(library.importPaths.includes(value));
|
||||
|
||||
const onSubmit = async () => {
|
||||
const success = await handleAddLibraryFolder(library, value);
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
const importPaths = [...library.importPaths, value];
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }], {
|
||||
onSuccess: onClose,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -28,9 +30,13 @@
|
||||
{onSubmit}
|
||||
size="small"
|
||||
submitText={$t('add')}
|
||||
disabled={!value || exists}
|
||||
>
|
||||
<Text size="small" class="mb-4">{$t('admin.library_folder_description')}</Text>
|
||||
<Field label={$t('path')}>
|
||||
<Input bind:value />
|
||||
{#if exists}
|
||||
<HelperText color="danger">{$t('errors.library_folder_already_exists')}</HelperText>
|
||||
{/if}
|
||||
</Field>
|
||||
</FormModal>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { handleEditLibraryFolder } from '$lib/services/library.service';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { Field, FormModal, Input, Text } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
@@ -16,14 +17,16 @@
|
||||
let newValue = $state(oldValue);
|
||||
|
||||
const onSubmit = async () => {
|
||||
const success = await handleEditLibraryFolder(library, oldValue, newValue);
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
const importPaths = library.importPaths.map((path) => (path === oldValue ? newValue : path));
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }], {
|
||||
onSuccess: onClose,
|
||||
});
|
||||
};
|
||||
|
||||
const disabled = $derived(!newValue || newValue === oldValue);
|
||||
</script>
|
||||
|
||||
<FormModal title={$t('library_edit_folder')} icon={mdiFolderSync} {onClose} {onSubmit} size="small">
|
||||
<FormModal title={$t('library_edit_folder')} icon={mdiFolderSync} {onClose} {onSubmit} size="small" {disabled}>
|
||||
<Text size="small" class="mb-4">{$t('admin.library_folder_description')}</Text>
|
||||
<Field label={$t('path')}>
|
||||
<Input bind:value={newValue} />
|
||||
|
||||
@@ -1,24 +1,70 @@
|
||||
import { toastManager } from '@immich/ui';
|
||||
import {
|
||||
createQuery,
|
||||
MutationCache,
|
||||
mutationOptions,
|
||||
MutationObserver,
|
||||
QueryClient,
|
||||
type CreateMutationOptions,
|
||||
type DataTag,
|
||||
type DefaultError,
|
||||
type DefinedCreateQueryResult,
|
||||
type MutateOptions,
|
||||
type NonUndefinedGuard,
|
||||
type QueryKey,
|
||||
type UndefinedInitialDataOptions,
|
||||
} from '@tanstack/svelte-query';
|
||||
import { noop } from 'lodash-es';
|
||||
import type { Translations } from 'svelte-i18n';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
type ToastMeta =
|
||||
true | Translations | { color: 'primary' | 'success' | 'info' | 'warning' | 'danger'; description: Translations };
|
||||
|
||||
declare module '@tanstack/svelte-query' {
|
||||
interface Register {
|
||||
mutationMeta: {
|
||||
success?: { toast?: ToastMeta };
|
||||
error?: { toast?: Translations };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const createQueryClient = () => {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
},
|
||||
mutationCache: new MutationCache({
|
||||
onSuccess: async (_data, _variables, _onMutateResult, { meta }) => {
|
||||
const toast = meta?.success?.toast;
|
||||
if (!toast) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toast === true) {
|
||||
toastManager.primary();
|
||||
return;
|
||||
}
|
||||
|
||||
const $t = await getFormatter();
|
||||
if (typeof toast === 'string') {
|
||||
toastManager.primary($t(toast));
|
||||
return;
|
||||
}
|
||||
|
||||
toastManager[toast.color]($t(toast.description));
|
||||
},
|
||||
onError: async (error, _variables, _onMutateResult, { meta }) => {
|
||||
const $t = await getFormatter();
|
||||
handleError(error, $t(meta?.error?.toast ?? 'errors.something_went_wrong'));
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
eventManager.on({ AuthLogout: () => client.clear() });
|
||||
@@ -28,22 +74,45 @@ const createQueryClient = () => {
|
||||
|
||||
export const queryClient = createQueryClient();
|
||||
|
||||
export const createDefinedQuery = <TQueryFnData, TError = DefaultError, TQueryKey extends QueryKey = QueryKey>(
|
||||
options: UndefinedInitialDataOptions<TQueryFnData, TError, TQueryFnData, TQueryKey> & {
|
||||
export const asMutation = <TArgs extends unknown[], TData, TError = DefaultError, TOnMutateResult = unknown>(
|
||||
fn: (...args: TArgs) => Promise<TData>,
|
||||
options?: Omit<CreateMutationOptions<TData, TError, TArgs, TOnMutateResult>, 'mutationFn' | 'mutationKey'>,
|
||||
) => mutationOptions({ ...options, mutationFn: (args: TArgs) => fn(...args) });
|
||||
|
||||
export const createPreloadedQuery = <
|
||||
TQueryFnData,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
optionsFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
|
||||
queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
|
||||
},
|
||||
): DefinedCreateQueryResult<TQueryFnData, TError> => {
|
||||
const initialData = queryClient.getQueryData<TQueryFnData, TQueryKey, NonUndefinedGuard<TQueryFnData>>(
|
||||
options.queryKey,
|
||||
);
|
||||
if (initialData === undefined) {
|
||||
throw new Error(`${options.queryKey.join('/')} must be loaded first`);
|
||||
}
|
||||
): DefinedCreateQueryResult<TData, TError> =>
|
||||
createQuery(() => {
|
||||
const options = optionsFn();
|
||||
const initialData = queryClient.getQueryData<TQueryFnData, TQueryKey, NonUndefinedGuard<TQueryFnData>>(
|
||||
options.queryKey,
|
||||
);
|
||||
if (initialData === undefined) {
|
||||
throw new Error(`${options.queryKey.join('/')} must be loaded first`);
|
||||
}
|
||||
|
||||
return createQuery(() => ({ ...options, initialData }));
|
||||
};
|
||||
return { ...options, initialData };
|
||||
});
|
||||
|
||||
type MutationArgs<TData, TError, TVariables, TContext> = TVariables extends void
|
||||
? [variables?: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TContext>]
|
||||
: [variables: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TContext>];
|
||||
|
||||
export const executeMutation = <TData, TError, TVariables, TContext>(
|
||||
options: CreateMutationOptions<TData, TError, TVariables, TContext>,
|
||||
variables: TVariables,
|
||||
) => queryClient.getMutationCache().build(queryClient, options).execute(variables).catch(noop);
|
||||
options: () => CreateMutationOptions<TData, TError, TVariables, TContext>,
|
||||
...[variables, mutateOptions]: MutationArgs<TData, TError, TVariables, TContext>
|
||||
) => {
|
||||
const observer = new MutationObserver(queryClient, options());
|
||||
const unsubscribe = observer.subscribe(noop);
|
||||
return observer
|
||||
.mutate(variables as TVariables, mutateOptions)
|
||||
.catch(noop)
|
||||
.finally(unsubscribe);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
import { getLibraryStatistics } from '@immich/sdk';
|
||||
import {
|
||||
createLibrary,
|
||||
deleteLibrary,
|
||||
getAllLibraries,
|
||||
getLibraryStatistics,
|
||||
scanLibrary,
|
||||
updateLibrary,
|
||||
type LibraryResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { queryOptions } from '@tanstack/svelte-query';
|
||||
import { asMutation, queryClient } from '$lib/queries/client';
|
||||
|
||||
const findLibrary = (libraries: LibraryResponseDto[], id: string) => {
|
||||
const library = libraries.find((library) => library.id === id);
|
||||
if (!library) {
|
||||
throw new Error(`Library ${id} not found`);
|
||||
}
|
||||
return library;
|
||||
};
|
||||
|
||||
export const getAllLibrariesQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['libraries'],
|
||||
queryFn: ({ signal }) => getAllLibraries({ signal }),
|
||||
});
|
||||
|
||||
export const getLibraryQuery = (id: string) =>
|
||||
queryOptions({
|
||||
...getAllLibrariesQuery(),
|
||||
select: (libraries) => findLibrary(libraries, id),
|
||||
});
|
||||
|
||||
export const getLibraryStatisticsQuery = (id: string) =>
|
||||
queryOptions({
|
||||
queryKey: ['libraries', id, 'statistics'],
|
||||
queryFn: ({ signal }) => getLibraryStatistics({ id }, { signal }),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
|
||||
export const createLibraryMutation = () =>
|
||||
asMutation(createLibrary, {
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: getAllLibrariesQuery().queryKey, exact: true }),
|
||||
meta: { error: { toast: 'errors.unable_to_create_library' } },
|
||||
});
|
||||
|
||||
export const updateLibraryMutation = () =>
|
||||
asMutation(updateLibrary, {
|
||||
onSuccess: async (library) => {
|
||||
queryClient.setQueryData(getAllLibrariesQuery().queryKey, (libraries) =>
|
||||
libraries?.map((item) => (item.id === library.id ? library : item)),
|
||||
);
|
||||
await queryClient.invalidateQueries({ queryKey: getLibraryStatisticsQuery(library.id).queryKey });
|
||||
},
|
||||
meta: { success: { toast: 'admin.library_updated' }, error: { toast: 'errors.unable_to_update_library' } },
|
||||
});
|
||||
|
||||
export const deleteLibraryMutation = () =>
|
||||
asMutation(deleteLibrary, {
|
||||
onSuccess: (_, [{ id }]) => {
|
||||
queryClient.setQueryData(getAllLibrariesQuery().queryKey, (libraries) =>
|
||||
libraries?.filter((library) => library.id !== id),
|
||||
);
|
||||
queryClient.removeQueries({ queryKey: getLibraryStatisticsQuery(id).queryKey });
|
||||
},
|
||||
meta: { success: { toast: 'admin.library_deleted' }, error: { toast: 'errors.unable_to_remove_library' } },
|
||||
});
|
||||
|
||||
export const scanLibraryMutation = () =>
|
||||
asMutation(scanLibrary, {
|
||||
meta: {
|
||||
success: { toast: { color: 'info', description: 'admin.scanning_library' } },
|
||||
error: { toast: 'errors.unable_to_scan_library' },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { QueueCommand, QueueName, runQueueCommandLegacy } from '@immich/sdk';
|
||||
import { mutationOptions } from '@tanstack/svelte-query';
|
||||
|
||||
export const scanAllLibrariesMutation = () =>
|
||||
mutationOptions({
|
||||
mutationFn: () =>
|
||||
runQueueCommandLegacy({ name: QueueName.Library, queueCommandDto: { command: QueueCommand.Start } }),
|
||||
meta: {
|
||||
success: { toast: { color: 'info', description: 'admin.refreshing_all_libraries' } },
|
||||
error: { toast: 'errors.unable_to_scan_libraries' },
|
||||
},
|
||||
});
|
||||
@@ -5,21 +5,18 @@ export const getAboutInfoQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['server', 'about'],
|
||||
queryFn: ({ signal }) => getAboutInfo({ signal }),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
export const getVersionHistoryQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['server', 'version-history'],
|
||||
queryFn: ({ signal }) => getVersionHistory({ signal }),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
export const getStorageQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['server', 'storage'],
|
||||
queryFn: ({ signal }) => getStorage({ signal }),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
export const getServerStatisticsQuery = () =>
|
||||
|
||||
@@ -4,21 +4,18 @@ import { mutationOptions, queryOptions } from '@tanstack/svelte-query';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
export const getConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['system-config'],
|
||||
queryFn: ({ signal }) => getConfig({ signal }),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
export const getConfigDefaultsQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['system-config', 'defaults'],
|
||||
queryFn: ({ signal }) => getConfigDefaults({ signal }),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
export const updateConfigMutation = () =>
|
||||
@@ -44,8 +41,5 @@ export const updateConfigMutation = () =>
|
||||
const $t = await getFormatter();
|
||||
toastManager.primary($t('settings_saved'));
|
||||
},
|
||||
onError: async (error) => {
|
||||
const $t = await getFormatter();
|
||||
handleError(error, $t('errors.unable_to_save_settings'));
|
||||
},
|
||||
meta: { error: { toast: 'errors.unable_to_save_settings' } },
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { searchUsersAdmin } from '@immich/sdk';
|
||||
import { queryOptions } from '@tanstack/svelte-query';
|
||||
|
||||
export const searchUsersAdminQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['user-admin'],
|
||||
queryFn: ({ signal }) => searchUsersAdmin({ withDeleted: true }, { signal }),
|
||||
});
|
||||
|
||||
export const getUserAdminQuery = (id: string) =>
|
||||
queryOptions({
|
||||
...searchUsersAdminQuery(),
|
||||
select: (users) => {
|
||||
const user = users.find((user) => user.id === id);
|
||||
if (!user) {
|
||||
throw new Error(`User ${id} not found`);
|
||||
}
|
||||
return user;
|
||||
},
|
||||
});
|
||||
@@ -1,33 +1,23 @@
|
||||
import {
|
||||
createLibrary,
|
||||
deleteLibrary,
|
||||
QueueCommand,
|
||||
QueueName,
|
||||
runQueueCommandLegacy,
|
||||
scanLibrary,
|
||||
updateLibrary,
|
||||
type CreateLibraryDto,
|
||||
type LibraryResponseDto,
|
||||
type UpdateLibraryDto,
|
||||
} from '@immich/sdk';
|
||||
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { modalManager, type ActionItem } from '@immich/ui';
|
||||
import { mdiInformationOutline, mdiPencilOutline, mdiPlusBoxOutline, mdiSync, mdiTrashCanOutline } from '@mdi/js';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte';
|
||||
import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte';
|
||||
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
||||
import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { deleteLibraryMutation, scanLibraryMutation, updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import { scanAllLibrariesMutation } from '$lib/queries/queue.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
export const getLibrariesActions = ($t: MessageFormatter) => {
|
||||
const ScanAll: ActionItem = {
|
||||
title: $t('scan_all_libraries'),
|
||||
icon: mdiSync,
|
||||
onAction: () => handleScanAllLibraries(),
|
||||
onAction: () => executeMutation(scanAllLibrariesMutation),
|
||||
shortcuts: { shift: true, key: 'r' },
|
||||
};
|
||||
|
||||
@@ -79,7 +69,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse
|
||||
const Scan: ActionItem = {
|
||||
icon: mdiSync,
|
||||
title: $t('scan_library'),
|
||||
onAction: () => handleScanLibrary(library),
|
||||
onAction: () => executeMutation(scanLibraryMutation, [{ id: library.id }]),
|
||||
shortcuts: { shift: true, key: 'r' },
|
||||
};
|
||||
|
||||
@@ -122,54 +112,6 @@ export const getLibraryExclusionPatternActions = (
|
||||
return { Edit, Delete };
|
||||
};
|
||||
|
||||
const handleScanAllLibraries = async () => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
try {
|
||||
await runQueueCommandLegacy({ name: QueueName.Library, queueCommandDto: { command: QueueCommand.Start } });
|
||||
toastManager.info($t('admin.refreshing_all_libraries'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_scan_libraries'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleScanLibrary = async (library: LibraryResponseDto) => {
|
||||
const $t = await getFormatter();
|
||||
try {
|
||||
await scanLibrary({ id: library.id });
|
||||
toastManager.info($t('admin.scanning_library'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_scan_library'));
|
||||
}
|
||||
};
|
||||
|
||||
export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
try {
|
||||
const library = await createLibrary({ createLibraryDto: dto });
|
||||
eventManager.emit('LibraryCreate', library);
|
||||
toastManager.primary($t('admin.library_created', { values: { library: library.name } }));
|
||||
return library;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_create_library'));
|
||||
}
|
||||
};
|
||||
|
||||
export const handleUpdateLibrary = async (library: LibraryResponseDto, dto: UpdateLibraryDto) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: dto });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteLibrary = async (library: LibraryResponseDto) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
@@ -190,57 +132,9 @@ const handleDeleteLibrary = async (library: LibraryResponseDto) => {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteLibrary({ id: library.id });
|
||||
eventManager.emit('LibraryDelete', { id: library.id });
|
||||
toastManager.primary($t('admin.library_deleted'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_remove_library'));
|
||||
}
|
||||
};
|
||||
|
||||
export const handleAddLibraryFolder = async (library: LibraryResponseDto, folder: string) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
if (library.importPaths.includes(folder)) {
|
||||
toastManager.danger($t('errors.library_folder_already_exists'));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({
|
||||
id: library.id,
|
||||
updateLibraryDto: { importPaths: [...library.importPaths, folder] },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const handleEditLibraryFolder = async (library: LibraryResponseDto, oldValue: string, newValue: string) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
if (oldValue === newValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const importPaths = library.importPaths.map((path) => (path === oldValue ? newValue : path));
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { importPaths } });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
await executeMutation(deleteLibraryMutation, [{ id: library.id }], {
|
||||
onSuccess: () => void goto(Route.libraries()),
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: string) => {
|
||||
@@ -255,60 +149,8 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({
|
||||
id: library.id,
|
||||
updateLibraryDto: { importPaths: library.importPaths.filter((path) => path !== folder) },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
}
|
||||
};
|
||||
|
||||
export const handleAddLibraryExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
if (library.exclusionPatterns.includes(exclusionPattern)) {
|
||||
toastManager.danger($t('errors.exclusion_pattern_already_exists'));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({
|
||||
id: library.id,
|
||||
updateLibraryDto: { exclusionPatterns: [...library.exclusionPatterns, exclusionPattern] },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const handleEditExclusionPattern = async (library: LibraryResponseDto, oldValue: string, newValue: string) => {
|
||||
const $t = await getFormatter();
|
||||
|
||||
if (oldValue === newValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const exclusionPatterns = library.exclusionPatterns.map((pattern) => (pattern === oldValue ? newValue : pattern));
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { exclusionPatterns } });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
const importPaths = library.importPaths.filter((path) => path !== folder);
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }]);
|
||||
};
|
||||
|
||||
const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => {
|
||||
@@ -319,16 +161,6 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({
|
||||
id: library.id,
|
||||
updateLibraryDto: {
|
||||
exclusionPatterns: library.exclusionPatterns.filter((pattern) => pattern !== exclusionPattern),
|
||||
},
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
}
|
||||
const exclusionPatterns = library.exclusionPatterns.filter((pattern) => pattern !== exclusionPattern);
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }]);
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ const handleUploadConfig = () => {
|
||||
}
|
||||
const reader = async () => {
|
||||
const text = await file.text();
|
||||
await executeMutation(updateConfigMutation(), JSON.parse(text));
|
||||
await executeMutation(updateConfigMutation, JSON.parse(text));
|
||||
};
|
||||
reader()
|
||||
.catch((error) => console.error('Error handling JSON config upload', error))
|
||||
|
||||
@@ -1,30 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/EmptyPlaceholder.svelte';
|
||||
import { libraryTableClasses } from '$lib/constants';
|
||||
import { Route } from '$lib/route';
|
||||
import { getLibrariesActions, getLibraryActions } from '$lib/services/library.service';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
||||
import { type LibraryResponseDto } from '@immich/sdk';
|
||||
import {
|
||||
CommandPaletteDefaultProvider,
|
||||
Container,
|
||||
ContextMenuButton,
|
||||
Link,
|
||||
MenuItemType,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableHeading,
|
||||
TableRow,
|
||||
} from '@immich/ui';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getAllLibrariesQuery } from '$lib/queries/library.query';
|
||||
import { getLibrariesActions } from '$lib/services/library.service';
|
||||
import { CommandPaletteDefaultProvider, Container, Table, TableBody, TableHeader, TableHeading } from '@immich/ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { LayoutData } from './$types';
|
||||
import LibraryTableRow from './LibraryTableRow.svelte';
|
||||
|
||||
type Props = {
|
||||
children?: Snippet;
|
||||
@@ -33,35 +21,12 @@
|
||||
|
||||
let { children, data }: Props = $props();
|
||||
|
||||
let libraries = $derived([...data.libraries]);
|
||||
let owners = $derived({ ...data.owners });
|
||||
|
||||
const onLibraryCreate = async (library: LibraryResponseDto) => {
|
||||
await goto(Route.viewLibrary(library));
|
||||
};
|
||||
|
||||
const onLibraryUpdate = () => invalidate('app:libraries');
|
||||
const onLibraryDelete = () => invalidate('app:libraries');
|
||||
const query = createPreloadedQuery(getAllLibrariesQuery);
|
||||
const libraries = $derived(query.data);
|
||||
|
||||
const { Create, ScanAll } = $derived(getLibrariesActions($t));
|
||||
|
||||
const getActionsForLibrary = (library: LibraryResponseDto) => {
|
||||
const { Detail, Scan, Edit, Delete } = getLibraryActions($t, library);
|
||||
return [Detail, Scan, Edit, MenuItemType.Divider, Delete];
|
||||
};
|
||||
|
||||
const classes = {
|
||||
column1: 'w-4/12',
|
||||
column2: 'w-4/12',
|
||||
column3: 'w-1/12',
|
||||
column4: 'w-1/12',
|
||||
column5: 'w-1/12',
|
||||
column6: 'w-1/12 flex justify-end',
|
||||
};
|
||||
</script>
|
||||
|
||||
<OnEvents {onLibraryCreate} {onLibraryUpdate} {onLibraryDelete} />
|
||||
|
||||
<CommandPaletteDefaultProvider name={$t('library')} actions={[Create, ScanAll]} />
|
||||
|
||||
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[ScanAll, Create]}>
|
||||
@@ -70,61 +35,16 @@
|
||||
{#if libraries.length > 0}
|
||||
<Table striped size="small" spacing="small">
|
||||
<TableHeader>
|
||||
<TableHeading class={classes.column1}>{$t('name')}</TableHeading>
|
||||
<TableHeading class={classes.column2}>{$t('owner')}</TableHeading>
|
||||
<TableHeading class={classes.column3}>{$t('photos')}</TableHeading>
|
||||
<TableHeading class={classes.column4}>{$t('videos')}</TableHeading>
|
||||
<TableHeading class={classes.column5}>{$t('size')}</TableHeading>
|
||||
<TableHeading class={classes.column6}></TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column1}>{$t('name')}</TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column2}>{$t('owner')}</TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column3}>{$t('photos')}</TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column4}>{$t('videos')}</TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column5}>{$t('size')}</TableHeading>
|
||||
<TableHeading class={libraryTableClasses.column6}></TableHeading>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each libraries as library (library.id + library.name)}
|
||||
{@const owner = owners[library.id]}
|
||||
<TableRow>
|
||||
<TableCell class={classes.column1}>
|
||||
<Link href={Route.viewLibrary(library)}>{library.name}</Link>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column2}>
|
||||
<Link href={Route.viewUser(owner)}>{owner.name}</Link>
|
||||
</TableCell>
|
||||
{#await data.statisticsPromise}
|
||||
<TableCell class={classes.column3}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
<span class="skeleton-loader inline-block h-4 w-20"></span>
|
||||
</TableCell>
|
||||
{:then loadedStats}
|
||||
{@const stats = loadedStats[library.id]}
|
||||
<TableCell class={classes.column3}>
|
||||
{stats.photos.toLocaleString($locale)}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
{stats.videos.toLocaleString($locale)}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
{@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
|
||||
{diskUsage}
|
||||
{diskUsageUnit}
|
||||
</TableCell>
|
||||
{:catch}
|
||||
<TableCell class={classes.column3}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
<span class="skeleton-loader inline-block h-4 w-20"></span>
|
||||
</TableCell>
|
||||
{/await}
|
||||
<TableCell class={classes.column6}>
|
||||
<ContextMenuButton color="primary" aria-label={$t('open')} items={getActionsForLibrary(library)} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{#each libraries as library (library.id)}
|
||||
<LibraryTableRow {library} />
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -141,37 +61,3 @@
|
||||
</div>
|
||||
</Container>
|
||||
</AdminPageLayout>
|
||||
|
||||
<style>
|
||||
.skeleton-loader {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(156, 163, 175, 0.35);
|
||||
}
|
||||
|
||||
.skeleton-loader::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.8) 50%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-position: 200% 0;
|
||||
animation: skeleton-animation 2000ms infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-animation {
|
||||
from {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import { getAllLibraries, getLibraryStatistics, getUserAdmin, searchUsersAdmin } from '@immich/sdk';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getAllLibrariesQuery } from '$lib/queries/library.query';
|
||||
import { searchUsersAdminQuery } from '$lib/queries/user-admin.query';
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url, depends }) => {
|
||||
depends('app:libraries');
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url, { admin: true });
|
||||
const allUsers = await searchUsersAdmin({ withDeleted: false });
|
||||
await Promise.all([queryClient.query(getAllLibrariesQuery()), queryClient.query(searchUsersAdminQuery())]);
|
||||
const $t = await getFormatter();
|
||||
|
||||
const libraries = await getAllLibraries();
|
||||
const statisticsPromise = Promise.all(
|
||||
libraries.map(async ({ id }) => [id, await getLibraryStatistics({ id })] as const),
|
||||
);
|
||||
const owners = await Promise.all(
|
||||
libraries.map(async ({ id, ownerId }) => [id, await getUserAdmin({ id: ownerId })] as const),
|
||||
);
|
||||
|
||||
return {
|
||||
allUsers,
|
||||
libraries,
|
||||
statisticsPromise: statisticsPromise.then((stats) => Object.fromEntries(stats)),
|
||||
owners: Object.fromEntries(owners),
|
||||
meta: {
|
||||
title: $t('external_libraries'),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import { libraryTableClasses } from '$lib/constants';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getLibraryStatisticsQuery } from '$lib/queries/library.query';
|
||||
import { getUserAdminQuery } from '$lib/queries/user-admin.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { getLibraryActions } from '$lib/services/library.service';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { ContextMenuButton, Link, MenuItemType, TableCell, TableRow } from '@immich/ui';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
library: LibraryResponseDto;
|
||||
};
|
||||
|
||||
const { library }: Props = $props();
|
||||
|
||||
const ownerQuery = createPreloadedQuery(() => getUserAdminQuery(library.ownerId));
|
||||
const owner = $derived(ownerQuery.data);
|
||||
|
||||
const query = createQuery(() => getLibraryStatisticsQuery(library.id));
|
||||
const stats = $derived(query.data);
|
||||
|
||||
const actions = $derived.by(() => {
|
||||
const { Detail, Scan, Edit, Delete } = getLibraryActions($t, library);
|
||||
return [Detail, Scan, Edit, MenuItemType.Divider, Delete];
|
||||
});
|
||||
</script>
|
||||
|
||||
<TableRow>
|
||||
<TableCell class={libraryTableClasses.column1}>
|
||||
<Link href={Route.viewLibrary(library)}>{library.name}</Link>
|
||||
</TableCell>
|
||||
<TableCell class={libraryTableClasses.column2}>
|
||||
<Link href={Route.viewUser(owner)}>{owner.name}</Link>
|
||||
</TableCell>
|
||||
{#if stats}
|
||||
{@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
|
||||
<TableCell class={libraryTableClasses.column3}>
|
||||
{stats.photos.toLocaleString($locale)}
|
||||
</TableCell>
|
||||
<TableCell class={libraryTableClasses.column4}>
|
||||
{stats.videos.toLocaleString($locale)}
|
||||
</TableCell>
|
||||
<TableCell class={libraryTableClasses.column5}>
|
||||
{diskUsage}
|
||||
{diskUsageUnit}
|
||||
</TableCell>
|
||||
{:else}
|
||||
<TableCell class={libraryTableClasses.column3}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={libraryTableClasses.column4}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={libraryTableClasses.column5}>
|
||||
<span class="skeleton-loader inline-block h-4 w-20"></span>
|
||||
</TableCell>
|
||||
{/if}
|
||||
<TableCell class={libraryTableClasses.column6}>
|
||||
<ContextMenuButton color="primary" aria-label={$t('open')} items={actions} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<style>
|
||||
.skeleton-loader {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(156, 163, 175, 0.35);
|
||||
}
|
||||
|
||||
.skeleton-loader::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.8) 50%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-position: 200% 0;
|
||||
animation: skeleton-animation 2000ms infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-animation {
|
||||
from {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { createLibraryMutation } from '$lib/queries/library.query';
|
||||
import { searchUsersAdminQuery } from '$lib/queries/user-admin.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleCreateLibrary } from '$lib/services/library.service';
|
||||
import { Field, FormModal, HelperText, Select } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { type PageData } from './$types';
|
||||
|
||||
type Props = {
|
||||
data: PageData;
|
||||
};
|
||||
|
||||
const { data }: Props = $props();
|
||||
|
||||
let ownerId: string = $state(authManager.user.id);
|
||||
const users = $state(data.allUsers);
|
||||
const query = createPreloadedQuery(searchUsersAdminQuery);
|
||||
const users = $derived(query.data.filter((user) => !user.deletedAt));
|
||||
|
||||
const onClose = async () => {
|
||||
await goto(Route.libraries());
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const library = await handleCreateLibrary({ ownerId });
|
||||
if (library) {
|
||||
await goto(Route.viewLibrary(library), { replaceState: true });
|
||||
}
|
||||
await executeMutation(createLibraryMutation, [{ createLibraryDto: { ownerId } }], {
|
||||
onSuccess: (library) => void goto(Route.viewLibrary(library), { replaceState: true }),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import emptyFoldersUrl from '$lib/assets/empty-folders.svg';
|
||||
import AdminCard from '$lib/components/AdminCard.svelte';
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/EmptyPlaceholder.svelte';
|
||||
import TableButton from '$lib/components/TableButton.svelte';
|
||||
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getLibraryStatisticsQuery } from '$lib/queries/library.query';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getLibraryQuery, getLibraryStatisticsQuery } from '$lib/queries/library.query';
|
||||
import { Route } from '$lib/route';
|
||||
import {
|
||||
getLibraryActions,
|
||||
@@ -20,21 +18,16 @@
|
||||
import { Code, CommandPaletteDefaultProvider, Container, Heading, modalManager } from '@immich/ui';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { mdiCameraIris, mdiChartPie, mdiFilterMinusOutline, mdiFolderOutline, mdiPlayCircle } from '@mdi/js';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { LayoutData } from './$types';
|
||||
import type { LayoutProps } from './$types';
|
||||
|
||||
type Props = {
|
||||
children?: Snippet;
|
||||
data: LayoutData;
|
||||
};
|
||||
const { children, params }: LayoutProps = $props();
|
||||
|
||||
const { children, data }: Props = $props();
|
||||
const libraryQuery = createPreloadedQuery(() => getLibraryQuery(params.id));
|
||||
const library = $derived(libraryQuery.data);
|
||||
|
||||
const library = $derived(data.library);
|
||||
|
||||
const query = createQuery(() => getLibraryStatisticsQuery(library.id));
|
||||
const stats = $derived(query.data);
|
||||
const statisticsQuery = createQuery(() => getLibraryStatisticsQuery(library.id));
|
||||
const stats = $derived(statisticsQuery.data);
|
||||
|
||||
const photos = $derived(stats && { value: stats.photos });
|
||||
const videos = $derived(stats && { value: stats.videos });
|
||||
@@ -46,22 +39,9 @@
|
||||
return { value, unit };
|
||||
});
|
||||
|
||||
const onLibraryUpdate = async () => {
|
||||
await invalidate('app:library');
|
||||
await queryClient.invalidateQueries({ queryKey: getLibraryStatisticsQuery(library.id).queryKey });
|
||||
};
|
||||
|
||||
const onLibraryDelete = async ({ id }: { id: string }) => {
|
||||
if (id === library.id) {
|
||||
await goto(Route.libraries());
|
||||
}
|
||||
};
|
||||
|
||||
const { Edit, Delete, AddFolder, AddExclusionPattern, Scan } = $derived(getLibraryActions($t, library));
|
||||
</script>
|
||||
|
||||
<OnEvents {onLibraryUpdate} {onLibraryDelete} />
|
||||
|
||||
<CommandPaletteDefaultProvider name={$t('library')} actions={[Edit, Delete, AddFolder, AddExclusionPattern, Scan]} />
|
||||
|
||||
<AdminPageLayout
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import { getLibrary, type LibraryResponseDto } from '@immich/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getAllLibrariesQuery } from '$lib/queries/library.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load = (async ({ params: { id }, url, depends }) => {
|
||||
depends('app:library');
|
||||
export const load = (async ({ params: { id }, url }) => {
|
||||
await authenticate(url, { admin: true });
|
||||
|
||||
let library: LibraryResponseDto;
|
||||
|
||||
try {
|
||||
library = await getLibrary({ id });
|
||||
} catch {
|
||||
const libraries = await queryClient.query(getAllLibrariesQuery());
|
||||
if (libraries.every((library) => library.id !== id)) {
|
||||
redirect(307, Route.libraries());
|
||||
}
|
||||
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
library,
|
||||
meta: {
|
||||
title: $t('admin.library_details'),
|
||||
},
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getLibraryQuery, updateLibraryMutation } from '$lib/queries/library.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleUpdateLibrary } from '$lib/services/library.service';
|
||||
import { Field, FormModal, Input } from '@immich/ui';
|
||||
import { mdiRenameOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from '../$types';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
type Props = {
|
||||
data: PageData;
|
||||
};
|
||||
const { params }: PageProps = $props();
|
||||
|
||||
let { data }: Props = $props();
|
||||
const query = createPreloadedQuery(() => getLibraryQuery(params.id));
|
||||
const library = $derived(query.data);
|
||||
let name = $derived(library.name);
|
||||
|
||||
const library = $state(data.library);
|
||||
let name = $state(library.name);
|
||||
|
||||
const onClose = async () => {
|
||||
await goto(Route.viewLibrary(library));
|
||||
};
|
||||
const onClose = () => void goto(Route.viewLibrary(library));
|
||||
|
||||
const onSubmit = async () => {
|
||||
const success = await handleUpdateLibrary(library, { name });
|
||||
if (success) {
|
||||
await onClose();
|
||||
}
|
||||
await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { name } }], {
|
||||
onSuccess: onClose,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getSystemConfigActions } from '$lib/services/system-config.service';
|
||||
import { Alert, CommandPaletteDefaultProvider, Container } from '@immich/ui';
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
const { data }: Props = $props();
|
||||
|
||||
const config = createDefinedQuery(getConfigQuery());
|
||||
const config = createPreloadedQuery(getConfigQuery);
|
||||
|
||||
const settings: Array<{
|
||||
component: Component;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { OAuthTokenEndpointAuthMethod, unlinkAllOAuthAccountsAdmin } from '@immich/sdk';
|
||||
@@ -18,7 +18,7 @@
|
||||
import { fade } from 'svelte/transition';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingCheckboxes from './SettingCheckboxes.svelte';
|
||||
@@ -26,7 +26,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
@@ -14,7 +14,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
@@ -14,7 +14,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -15,7 +15,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -8,7 +8,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
let configToEdit = $state(cloneDeep(query.data));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
import { ReleaseChannel } from '@immich/sdk';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createDefinedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { sendTestEmailAdmin } from '@immich/sdk';
|
||||
@@ -16,7 +16,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
@@ -53,7 +53,7 @@
|
||||
);
|
||||
|
||||
if (!disabled) {
|
||||
await executeMutation(updateConfigMutation(), { notifications: configToEdit.notifications });
|
||||
await executeMutation(updateConfigMutation, { notifications: configToEdit.notifications });
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, $t('admin.notification_email_test_email_failed'));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingTextarea from './SettingTextarea.svelte';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import EmailTemplatePreviewModal from '$lib/modals/EmailTemplatePreviewModal.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
let { config = $bindable() }: Props = $props();
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
|
||||
let loadingPreview = $state(false);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -8,7 +8,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createDefinedQuery } from '$lib/queries/client';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createQuery(() => getStorageQuery());
|
||||
const query = createQuery(getStorageQuery);
|
||||
const serverInfo = $derived(query.data);
|
||||
|
||||
let success = $state(false);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
const query = createQuery(() => getStorageQuery());
|
||||
const query = createQuery(getStorageQuery);
|
||||
const serverInfo = $derived(query.data);
|
||||
|
||||
const user = $derived(data.user);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
|
||||
import { createDefinedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createDefinedQuery(getConfigQuery());
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const configToEdit = $state(cloneDeep(query.data));
|
||||
|
||||
onDestroy(() => {
|
||||
void executeMutation(updateConfigMutation(), {
|
||||
void executeMutation(updateConfigMutation, {
|
||||
map: configToEdit.map,
|
||||
newVersionCheck: configToEdit.newVersionCheck,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user