diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 7f40bf7a6d..34c1aef4fc 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -6,8 +6,11 @@ import { useActions, type ActionArray } from '$lib/actions/use-actions'; import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte'; import UserSidebar from '$lib/components/shared-components/side-bar/user-sidebar.svelte'; + import type { HeaderButtonActionItem } from '$lib/types'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; + import { Button, ContextMenuButton, HStack, isMenuItemType, type MenuItemType } from '@immich/ui'; import type { Snippet } from 'svelte'; + import { t } from 'svelte-i18n'; interface Props { hideNavbar?: boolean; @@ -16,6 +19,7 @@ description?: string | undefined; scrollbar?: boolean; use?: ActionArray; + actions?: Array; header?: Snippet; sidebar?: Snippet; buttons?: Snippet; @@ -29,6 +33,7 @@ description = undefined, scrollbar = true, use = [], + actions = [], header, sidebar, buttons, @@ -74,7 +79,31 @@

{description}

{/if} + {@render buttons?.()} + + {#if actions.length > 0} + + + + {/if} {/if} diff --git a/web/src/lib/services/trash.service.ts b/web/src/lib/services/trash.service.ts new file mode 100644 index 0000000000..d9660c2f29 --- /dev/null +++ b/web/src/lib/services/trash.service.ts @@ -0,0 +1,54 @@ +import { handleError } from '$lib/utils/handle-error'; +import { getFormatter } from '$lib/utils/i18n'; +import { emptyTrash, restoreTrash } from '@immich/sdk'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; +import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js'; +import type { MessageFormatter } from 'svelte-i18n'; + +export const getTrashActions = ($t: MessageFormatter) => { + const RestoreAll: ActionItem = { + title: $t('restore_all'), + icon: mdiHistory, + onAction: () => handleRestoreTrash(), + }; + + const Empty: ActionItem = { + title: $t('empty_trash'), + icon: mdiDeleteForeverOutline, + onAction: () => handleEmptyTrash(), + }; + + return { RestoreAll, Empty }; +}; + +export const handleEmptyTrash = async () => { + const $t = await getFormatter(); + + const confirmed = await modalManager.showDialog({ prompt: $t('empty_trash_confirmation') }); + if (!confirmed) { + return; + } + + try { + const { count } = await emptyTrash(); + toastManager.success($t('assets_permanently_deleted_count', { values: { count } })); + } catch (error) { + handleError(error, $t('errors.unable_to_empty_trash')); + } +}; + +export const handleRestoreTrash = async () => { + const $t = await getFormatter(); + + const confirmed = await modalManager.showDialog({ prompt: $t('assets_restore_confirmation') }); + if (!confirmed) { + return; + } + + try { + const { count } = await restoreTrash(); + toastManager.success($t('assets_restored_count', { values: { count } })); + } catch (error) { + handleError(error, $t('errors.unable_to_restore_trash')); + } +}; diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte index df91ef8dc4..5ffab5d90a 100644 --- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -12,18 +12,15 @@ import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { serverConfigManager } from '$lib/managers/server-config-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; + import { getTrashActions } from '$lib/services/trash.service'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { handlePromiseError } from '$lib/utils'; - import { handleError } from '$lib/utils/handle-error'; - import { emptyTrash, restoreTrash } from '@immich/sdk'; - import { Button, HStack, modalManager, Text, toastManager } from '@immich/ui'; - import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; - interface Props { + type Props = { data: PageData; - } + }; let { data }: Props = $props(); @@ -36,74 +33,23 @@ handlePromiseError(goto(AppRoute.PHOTOS)); } - const handleEmptyTrash = async () => { - const isConfirmed = await modalManager.showDialog({ prompt: $t('empty_trash_confirmation') }); - if (!isConfirmed) { - return; - } - - try { - const { count } = await emptyTrash(); - toastManager.success($t('assets_permanently_deleted_count', { values: { count } })); - } catch (error) { - handleError(error, $t('errors.unable_to_empty_trash')); - } - }; - - const handleRestoreTrash = async () => { - const isConfirmed = await modalManager.showDialog({ prompt: $t('assets_restore_confirmation') }); - if (!isConfirmed) { - return; - } - try { - const { count } = await restoreTrash(); - toastManager.success($t('assets_restored_count', { values: { count } })); - - // reset asset grid (TODO fix in asset store that it should reset when it is empty) - // note - this is still a problem, but updateOptions with the same value will not - // do anything, so need to flip it for it to reload/reinit - // await timelineManager.updateOptions({ deferInit: true, isTrashed: true }); - // await timelineManager.updateOptions({ deferInit: false, isTrashed: true }); - } catch (error) { - handleError(error, $t('errors.unable_to_restore_trash')); - } - }; - const handleEscape = () => { if (assetInteraction.selectionActive) { assetInteraction.clearMultiselect(); return; } }; + + const { Empty, RestoreAll } = $derived(getTrashActions($t)); {#if featureFlagsManager.value.trash} - - {#snippet buttons()} - - - - - {/snippet} - +

{$t('trashed_items_will_be_permanently_deleted_after', {