refactor: library queries (#32489)

* refactor: library queries

* fix: mutation options signature
This commit is contained in:
Jason Rasmussen
2026-10-16 20:51:29 +03:00
committed by Codex
parent 2ebe288010
commit ae7cb3192e
49 changed files with 459 additions and 506 deletions
@@ -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);
+9
View File
@@ -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} />
+83 -14
View File
@@ -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);
};
+67 -1
View File
@@ -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' },
},
});
+12
View File
@@ -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' },
},
});
-3
View File
@@ -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 = () =>
+1 -7
View File
@@ -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' } },
});
+20
View File
@@ -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;
},
});
+14 -182
View File
@@ -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,
});