refactor: delete confirm modal (#25135)

This commit is contained in:
Jason Rasmussen
2026-01-08 15:59:26 -05:00
committed by GitHub
parent 6997ed83c4
commit 471fab0591
8 changed files with 106 additions and 159 deletions
@@ -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 });
@@ -1,15 +1,14 @@
<script lang="ts">
import { shortcuts } from '$lib/actions/shortcut';
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { AssetAction } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { deleteAssets as deleteAssetsUtil, type OnUndoDelete } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { deleteAssets, type AssetResponseDto } from '@immich/sdk';
import { IconButton, toastManager } from '@immich/ui';
import { IconButton, modalManager, toastManager } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction, PreAction } from './action';
@@ -23,24 +22,32 @@
let { asset, onAction, preAction, onUndoDelete = undefined }: Props = $props();
let showConfirmModal = $state(false);
const forceDefault = $derived(asset.isTrashed || !featureFlagsManager.value.trash);
const trashOrDelete = async (force = false) => {
if (force || !featureFlagsManager.value.trash) {
const trashOrDelete = async (forceRequest?: boolean) => {
const timelineAsset = toTimelineAsset(asset);
const force = forceDefault || forceRequest;
if (force) {
if ($showDeleteModal) {
showConfirmModal = true;
return;
const confirmed = await modalManager.show(AssetDeleteConfirmModal, { size: 1 });
if (!confirmed) {
return;
}
}
await deleteAsset();
try {
preAction({ type: AssetAction.DELETE, asset: timelineAsset });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
onAction({ type: AssetAction.DELETE, asset: timelineAsset });
toastManager.success($t('permanently_deleted_asset'));
} catch (error) {
handleError(error, $t('errors.unable_to_delete_asset'));
}
return;
}
await trashAsset();
return;
};
const trashAsset = async () => {
const timelineAsset = toTimelineAsset(asset);
preAction({ type: AssetAction.TRASH, asset: timelineAsset });
await deleteAssetsUtil(
false,
@@ -49,24 +56,11 @@
onUndoDelete,
);
};
const deleteAsset = async () => {
try {
preAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
onAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) });
toastManager.success($t('permanently_deleted_asset'));
} catch (error) {
handleError(error, $t('errors.unable_to_delete_asset'));
} finally {
showConfirmModal = false;
}
};
</script>
<svelte:document
use:shortcuts={[
{ shortcut: { key: 'Delete' }, onShortcut: () => 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}
<Portal target="body">
<DeleteAssetDialog size={1} onCancel={() => (showConfirmModal = false)} onConfirm={deleteAsset} />
</Portal>
{/if}
@@ -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,
};
});
});