refactor: library service (#24725)

This commit is contained in:
Jason Rasmussen
2025-12-19 13:20:35 -05:00
committed by GitHub
parent 1425b3da6b
commit 5b80323326
4 changed files with 70 additions and 74 deletions

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
import { handleCreateLibrary } from '$lib/services/library.service';
import { user } from '$lib/stores/user.store';
import { searchUsersAdmin } from '@immich/sdk';
import { FormModal, Text } from '@immich/ui';
import { mdiFolderSync } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
type Props = {
onClose: () => void;
};
let { onClose }: Props = $props();
let ownerId: string = $state($user.id);
let userOptions: { value: string; text: string }[] = $state([]);
onMount(async () => {
const users = await searchUsersAdmin({});
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
});
const onSubmit = async () => {
const success = await handleCreateLibrary({ ownerId });
if (success) {
onClose();
}
};
</script>
<FormModal
title={$t('create_library')}
icon={mdiFolderSync}
{onClose}
size="small"
{onSubmit}
submitText={$t('create')}
>
<SettingSelect label={$t('owner')} bind:value={ownerId} options={userOptions} name="user" />
<Text color="warning" size="small">{$t('admin.note_cannot_be_changed_later')}</Text>
</FormModal>

View File

@@ -1,46 +0,0 @@
<script lang="ts">
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
import { user } from '$lib/stores/user.store';
import { searchUsersAdmin } from '@immich/sdk';
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
import { mdiFolderSync } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
interface Props {
onClose: (ownerId?: string) => void;
}
let { onClose }: Props = $props();
let ownerId: string = $state($user.id);
let userOptions: { value: string; text: string }[] = $state([]);
onMount(async () => {
const users = await searchUsersAdmin({});
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
});
const onsubmit = (event: Event) => {
event.preventDefault();
onClose(ownerId);
};
</script>
<Modal title={$t('select_library_owner')} icon={mdiFolderSync} {onClose} size="small">
<ModalBody>
<form {onsubmit} autocomplete="off" id="select-library-owner-form">
<p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
</form>
</ModalBody>
<ModalFooter>
<HStack fullWidth>
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
<Button shape="round" type="submit" fullWidth form="select-library-owner-form">{$t('create')}</Button>
</HStack>
</ModalFooter>
</Modal>

View File

@@ -1,12 +1,12 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import LibraryCreateModal from '$lib/modals/LibraryCreateModal.svelte';
import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte'; import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte';
import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte'; import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte';
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte'; import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte'; import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte';
import LibraryRenameModal from '$lib/modals/LibraryRenameModal.svelte'; import LibraryRenameModal from '$lib/modals/LibraryRenameModal.svelte';
import LibraryUserPickerModal from '$lib/modals/LibraryUserPickerModal.svelte';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
import { import {
@@ -17,6 +17,7 @@ import {
runQueueCommandLegacy, runQueueCommandLegacy,
scanLibrary, scanLibrary,
updateLibrary, updateLibrary,
type CreateLibraryDto,
type LibraryResponseDto, type LibraryResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { modalManager, toastManager, type ActionItem } from '@immich/ui';
@@ -37,7 +38,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp
title: $t('create_library'), title: $t('create_library'),
type: $t('command'), type: $t('command'),
icon: mdiPlusBoxOutline, icon: mdiPlusBoxOutline,
onAction: () => handleCreateLibrary(), onAction: () => handleShowLibraryCreateModal(),
shortcuts: { shift: true, key: 'n' }, shortcuts: { shift: true, key: 'n' },
}; };
@@ -152,20 +153,17 @@ export const handleViewLibrary = async (library: LibraryResponseDto) => {
await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`); await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`);
}; };
export const handleCreateLibrary = async () => { export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
const $t = await getFormatter(); const $t = await getFormatter();
const ownerId = await modalManager.show(LibraryUserPickerModal, {});
if (!ownerId) {
return;
}
try { try {
const createdLibrary = await createLibrary({ createLibraryDto: { ownerId } }); const library = await createLibrary({ createLibraryDto: dto });
eventManager.emit('LibraryCreate', createdLibrary); eventManager.emit('LibraryCreate', library);
toastManager.success($t('admin.library_created', { values: { library: createdLibrary.name } })); toastManager.success($t('admin.library_created', { values: { library: library.name } }));
return true;
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_create_library')); handleError(error, $t('errors.unable_to_create_library'));
return false;
} }
}; };
@@ -359,3 +357,7 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
handleError(error, $t('errors.unable_to_update_library')); handleError(error, $t('errors.unable_to_update_library'));
} }
}; };
export const handleShowLibraryCreateModal = async () => {
await modalManager.show(LibraryCreateModal, {});
};

View File

@@ -4,18 +4,18 @@
import OnEvents from '$lib/components/OnEvents.svelte'; import OnEvents from '$lib/components/OnEvents.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { getLibrariesActions, handleCreateLibrary, handleViewLibrary } from '$lib/services/library.service'; import { getLibrariesActions, handleShowLibraryCreateModal, handleViewLibrary } from '$lib/services/library.service';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getBytesWithUnit } from '$lib/utils/byte-units'; import { getBytesWithUnit } from '$lib/utils/byte-units';
import { getLibrary, getLibraryStatistics, getUserAdmin, type LibraryResponseDto } from '@immich/sdk'; import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
import { Button, CommandPaletteContext } from '@immich/ui'; import { Button, CommandPaletteContext } from '@immich/ui';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import type { PageData } from './$types'; import type { PageData } from './$types';
interface Props { type Props = {
data: PageData; data: PageData;
} };
let { data }: Props = $props(); let { data }: Props = $props();
@@ -23,15 +23,11 @@
let statistics = $state(data.statistics); let statistics = $state(data.statistics);
let owners = $state(data.owners); let owners = $state(data.owners);
const handleLibraryAdd = async (library: LibraryResponseDto) => { const onLibraryCreate = async (library: LibraryResponseDto) => {
statistics[library.id] = await getLibraryStatistics({ id: library.id });
owners[library.id] = await getUserAdmin({ id: library.ownerId });
libraries.push(library);
await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`); await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`);
}; };
const handleLibraryUpdate = async (library: LibraryResponseDto) => { const onLibraryUpdate = async (library: LibraryResponseDto) => {
const index = libraries.findIndex(({ id }) => id === library.id); const index = libraries.findIndex(({ id }) => id === library.id);
if (index === -1) { if (index === -1) {
@@ -42,7 +38,7 @@
statistics[library.id] = await getLibraryStatistics({ id: library.id }); statistics[library.id] = await getLibraryStatistics({ id: library.id });
}; };
const handleDeleteLibrary = ({ id }: { id: string }) => { const onLibraryDelete = ({ id }: { id: string }) => {
libraries = libraries.filter((library) => library.id !== id); libraries = libraries.filter((library) => library.id !== id);
delete statistics[id]; delete statistics[id];
delete owners[id]; delete owners[id];
@@ -51,11 +47,7 @@
const { Create, ScanAll } = $derived(getLibrariesActions($t, libraries)); const { Create, ScanAll } = $derived(getLibrariesActions($t, libraries));
</script> </script>
<OnEvents <OnEvents {onLibraryCreate} {onLibraryUpdate} {onLibraryDelete} />
onLibraryCreate={handleLibraryAdd}
onLibraryUpdate={handleLibraryUpdate}
onLibraryDelete={handleDeleteLibrary}
/>
<CommandPaletteContext commands={[Create, ScanAll]} /> <CommandPaletteContext commands={[Create, ScanAll]} />
@@ -106,7 +98,11 @@
</tbody> </tbody>
</table> </table>
{:else} {:else}
<EmptyPlaceholder text={$t('no_libraries_message')} onClick={handleCreateLibrary} class="mt-10 mx-auto" /> <EmptyPlaceholder
text={$t('no_libraries_message')}
onClick={handleShowLibraryCreateModal}
class="mt-10 mx-auto"
/>
{/if} {/if}
</div> </div>
</section> </section>