diff --git a/web/src/lib/components/asset-viewer/actions/delete-action.spec.ts b/web/src/lib/components/asset-viewer/actions/delete-action.spec.ts index 126beead9c..8ba3432464 100644 --- a/web/src/lib/components/asset-viewer/actions/delete-action.spec.ts +++ b/web/src/lib/components/asset-viewer/actions/delete-action.spec.ts @@ -7,6 +7,13 @@ import DeleteAction from './delete-action.svelte'; let asset: AssetResponseDto; describe('DeleteAction component', () => { + beforeEach(() => { + vi.mock(import('$lib/managers/feature-flags-manager.svelte'), () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: { trash: true } } as any }; + }); + }); + describe('given an asset which is not trashed yet', () => { beforeEach(() => { asset = assetFactory.build({ isTrashed: false }); diff --git a/web/src/lib/components/asset-viewer/actions/delete-action.svelte b/web/src/lib/components/asset-viewer/actions/delete-action.svelte index be9c9ccf9c..24ef7d941f 100644 --- a/web/src/lib/components/asset-viewer/actions/delete-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/delete-action.svelte @@ -1,15 +1,14 @@ trashOrDelete(asset.isTrashed) }, + { shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete() }, { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) }, ]} /> @@ -75,13 +69,7 @@ color="secondary" shape="round" variant="ghost" - icon={asset.isTrashed ? mdiDeleteForeverOutline : mdiDeleteOutline} - aria-label={asset.isTrashed ? $t('permanently_delete') : $t('delete')} - onclick={() => trashOrDelete(asset.isTrashed)} + icon={forceDefault ? mdiDeleteForeverOutline : mdiDeleteOutline} + aria-label={forceDefault ? $t('permanently_delete') : $t('delete')} + onclick={() => trashOrDelete()} /> - -{#if showConfirmModal} - - (showConfirmModal = false)} onConfirm={deleteAsset} /> - -{/if} diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts index 957c9a17d2..1c802b0dce 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts @@ -32,8 +32,14 @@ describe('AssetViewerNavBar component', () => { vi.fn(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })), ); vi.mock(import('$lib/managers/feature-flags-manager.svelte'), () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return { featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: { smartSearch: true } } as any }; + return { + featureFlagsManager: { + init: vi.fn(), + loadFeatureFlags: vi.fn(), + value: { trash: true, smartSearch: true }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }; }); }); diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index f71944d20c..4d1a802730 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -7,6 +7,7 @@ import Portal from '$lib/elements/Portal.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; + import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; @@ -23,9 +24,8 @@ import { modalManager } from '@immich/ui'; import { debounce } from 'lodash-es'; import { t } from 'svelte-i18n'; - import DeleteAssetDialog from '../../photos-page/delete-asset-dialog.svelte'; - interface Props { + type Props = { initialAssetId?: string; assets: AssetResponseDto[]; assetInteraction: AssetInteraction; @@ -34,7 +34,6 @@ viewport: Viewport; onIntersected?: (() => void) | undefined; showAssetName?: boolean; - isShowDeleteConfirmation?: boolean; onPrevious?: (() => Promise<{ id: string } | undefined>) | undefined; onNext?: (() => Promise<{ id: string } | undefined>) | undefined; onRandom?: (() => Promise<{ id: string } | undefined>) | undefined; @@ -42,7 +41,7 @@ pageHeaderOffset?: number; slidingWindowOffset?: number; arrowNavigation?: boolean; - } + }; let { initialAssetId = undefined, @@ -53,7 +52,6 @@ viewport, onIntersected = undefined, showAssetName = false, - isShowDeleteConfirmation = $bindable(false), onPrevious = undefined, onNext = undefined, onRandom = undefined, @@ -209,30 +207,27 @@ const onDelete = () => { const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed); - - if ($showDeleteModal && (!isTrashEnabled || hasTrashedAsset)) { - isShowDeleteConfirmation = true; - return; - } handlePromiseError(trashOrDelete(hasTrashedAsset)); }; - const onForceDelete = () => { - if ($showDeleteModal) { - isShowDeleteConfirmation = true; - return; - } - handlePromiseError(trashOrDelete(true)); - }; - const trashOrDelete = async (force: boolean = false) => { - isShowDeleteConfirmation = false; + const forceOrNoTrash = force || !featureFlagsManager.value.trash; + const selectedAssets = assetInteraction.selectedAssets; + + if ($showDeleteModal && forceOrNoTrash) { + const confirmed = await modalManager.show(AssetDeleteConfirmModal, { size: selectedAssets.length }); + if (!confirmed) { + return; + } + } + await deleteAssets( - !(isTrashEnabled && !force), + forceOrNoTrash, (assetIds) => (assets = assets.filter((asset) => !assetIds.includes(asset.id))), - assetInteraction.selectedAssets, + selectedAssets, onReload, ); + assetInteraction.clearMultiselect(); }; @@ -285,7 +280,7 @@ shortcuts.push( { shortcut: { key: 'Escape' }, onShortcut: deselectAllAssets }, { shortcut: { key: 'Delete' }, onShortcut: onDelete }, - { shortcut: { key: 'Delete', shift: true }, onShortcut: onForceDelete }, + { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) }, { shortcut: { key: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() }, { shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive }, ); @@ -405,8 +400,6 @@ } }; - let isTrashEnabled = $derived(featureFlagsManager.value.trash); - $effect(() => { if (!lastAssetMouseEvent) { assetInteraction.clearAssetSelectionCandidates(); @@ -440,14 +433,6 @@ onscroll={() => updateSlidingWindow()} /> -{#if isShowDeleteConfirmation} - (isShowDeleteConfirmation = false)} - onConfirm={() => handlePromiseError(trashOrDelete(true))} - /> -{/if} - {#if assets.length > 0}
void; onEscape?: () => void; children?: Snippet; @@ -79,7 +78,6 @@ album, albumUsers = [], person, - isShowDeleteConfirmation = $bindable(false), onSelect = () => {}, onEscape = () => {}, children, @@ -600,7 +598,6 @@ scrollToAsset={(asset) => scrollToAsset(asset) ?? false} {timelineManager} {assetInteraction} - bind:isShowDeleteConfirmation {onEscape} /> diff --git a/web/src/lib/components/timeline/actions/DeleteAssetsAction.svelte b/web/src/lib/components/timeline/actions/DeleteAssetsAction.svelte index 7277afe609..6f6a2f4feb 100644 --- a/web/src/lib/components/timeline/actions/DeleteAssetsAction.svelte +++ b/web/src/lib/components/timeline/actions/DeleteAssetsAction.svelte @@ -1,55 +1,48 @@ {#if menuItem} - + {:else if loading} -{/if} - -{#if isShowConfirmation} - (isShowConfirmation = false)} + onclick={onAction} /> {/if} diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index b731635355..00ac128663 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -1,7 +1,6 @@ - -{#if isShowDeleteConfirmation} - (isShowDeleteConfirmation = false)} - onConfirm={() => handlePromiseError(trashOrDelete(true))} - /> -{/if} diff --git a/web/src/lib/components/photos-page/delete-asset-dialog.svelte b/web/src/lib/modals/AssetDeleteConfirmModal.svelte similarity index 80% rename from web/src/lib/components/photos-page/delete-asset-dialog.svelte rename to web/src/lib/modals/AssetDeleteConfirmModal.svelte index d5036dbe9f..95c580d8f2 100644 --- a/web/src/lib/components/photos-page/delete-asset-dialog.svelte +++ b/web/src/lib/modals/AssetDeleteConfirmModal.svelte @@ -5,21 +5,21 @@ import { mdiDeleteForeverOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; - interface Props { + type Props = { size: number; - onConfirm: () => void; - onCancel: () => void; - } + onClose: (confirmed?: boolean) => void; + }; - let { size, onConfirm, onCancel }: Props = $props(); + let { size, onClose: onCloseParent }: Props = $props(); let checked = $state(false); - const handleConfirm = () => { - if (checked) { + const onClose = (confirmed: boolean) => { + if (confirmed && checked) { $showDeleteModal = false; } - onConfirm(); + + onCloseParent(confirmed); }; @@ -27,7 +27,7 @@ title={$t('permanently_delete_assets_count', { values: { count: size } })} confirmText={$t('delete')} icon={mdiDeleteForeverOutline} - onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())} + {onClose} > {#snippet promptSnippet()}