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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 106 additions and 159 deletions

View file

@ -7,6 +7,13 @@ import DeleteAction from './delete-action.svelte';
let asset: AssetResponseDto; let asset: AssetResponseDto;
describe('DeleteAction component', () => { 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', () => { describe('given an asset which is not trashed yet', () => {
beforeEach(() => { beforeEach(() => {
asset = assetFactory.build({ isTrashed: false }); asset = assetFactory.build({ isTrashed: false });

View file

@ -1,15 +1,14 @@
<script lang="ts"> <script lang="ts">
import { shortcuts } from '$lib/actions/shortcut'; import { shortcuts } from '$lib/actions/shortcut';
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.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 { showDeleteModal } from '$lib/stores/preferences.store';
import { deleteAssets as deleteAssetsUtil, type OnUndoDelete } from '$lib/utils/actions'; import { deleteAssets as deleteAssetsUtil, type OnUndoDelete } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
import { deleteAssets, type AssetResponseDto } from '@immich/sdk'; 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 { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { OnAction, PreAction } from './action'; import type { OnAction, PreAction } from './action';
@ -23,24 +22,32 @@
let { asset, onAction, preAction, onUndoDelete = undefined }: Props = $props(); 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) => { const trashOrDelete = async (forceRequest?: boolean) => {
if (force || !featureFlagsManager.value.trash) { const timelineAsset = toTimelineAsset(asset);
const force = forceDefault || forceRequest;
if (force) {
if ($showDeleteModal) { if ($showDeleteModal) {
showConfirmModal = true; const confirmed = await modalManager.show(AssetDeleteConfirmModal, { size: 1 });
return; 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; return;
} }
await trashAsset();
return;
};
const trashAsset = async () => {
const timelineAsset = toTimelineAsset(asset);
preAction({ type: AssetAction.TRASH, asset: timelineAsset }); preAction({ type: AssetAction.TRASH, asset: timelineAsset });
await deleteAssetsUtil( await deleteAssetsUtil(
false, false,
@ -49,24 +56,11 @@
onUndoDelete, 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> </script>
<svelte:document <svelte:document
use:shortcuts={[ use:shortcuts={[
{ shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete(asset.isTrashed) }, { shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete() },
{ shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) }, { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) },
]} ]}
/> />
@ -75,13 +69,7 @@
color="secondary" color="secondary"
shape="round" shape="round"
variant="ghost" variant="ghost"
icon={asset.isTrashed ? mdiDeleteForeverOutline : mdiDeleteOutline} icon={forceDefault ? mdiDeleteForeverOutline : mdiDeleteOutline}
aria-label={asset.isTrashed ? $t('permanently_delete') : $t('delete')} aria-label={forceDefault ? $t('permanently_delete') : $t('delete')}
onclick={() => trashOrDelete(asset.isTrashed)} onclick={() => trashOrDelete()}
/> />
{#if showConfirmModal}
<Portal target="body">
<DeleteAssetDialog size={1} onCancel={() => (showConfirmModal = false)} onConfirm={deleteAsset} />
</Portal>
{/if}

View file

@ -32,8 +32,14 @@ describe('AssetViewerNavBar component', () => {
vi.fn(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })), vi.fn(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })),
); );
vi.mock(import('$lib/managers/feature-flags-manager.svelte'), () => { vi.mock(import('$lib/managers/feature-flags-manager.svelte'), () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any return {
return { featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: { smartSearch: true } } as any }; featureFlagsManager: {
init: vi.fn(),
loadFeatureFlags: vi.fn(),
value: { trash: true, smartSearch: true },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
};
}); });
}); });

View file

@ -7,6 +7,7 @@
import Portal from '$lib/elements/Portal.svelte'; import Portal from '$lib/elements/Portal.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; 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 ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -23,9 +24,8 @@
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import DeleteAssetDialog from '../../photos-page/delete-asset-dialog.svelte';
interface Props { type Props = {
initialAssetId?: string; initialAssetId?: string;
assets: AssetResponseDto[]; assets: AssetResponseDto[];
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
@ -34,7 +34,6 @@
viewport: Viewport; viewport: Viewport;
onIntersected?: (() => void) | undefined; onIntersected?: (() => void) | undefined;
showAssetName?: boolean; showAssetName?: boolean;
isShowDeleteConfirmation?: boolean;
onPrevious?: (() => Promise<{ id: string } | undefined>) | undefined; onPrevious?: (() => Promise<{ id: string } | undefined>) | undefined;
onNext?: (() => Promise<{ id: string } | undefined>) | undefined; onNext?: (() => Promise<{ id: string } | undefined>) | undefined;
onRandom?: (() => Promise<{ id: string } | undefined>) | undefined; onRandom?: (() => Promise<{ id: string } | undefined>) | undefined;
@ -42,7 +41,7 @@
pageHeaderOffset?: number; pageHeaderOffset?: number;
slidingWindowOffset?: number; slidingWindowOffset?: number;
arrowNavigation?: boolean; arrowNavigation?: boolean;
} };
let { let {
initialAssetId = undefined, initialAssetId = undefined,
@ -53,7 +52,6 @@
viewport, viewport,
onIntersected = undefined, onIntersected = undefined,
showAssetName = false, showAssetName = false,
isShowDeleteConfirmation = $bindable(false),
onPrevious = undefined, onPrevious = undefined,
onNext = undefined, onNext = undefined,
onRandom = undefined, onRandom = undefined,
@ -209,30 +207,27 @@
const onDelete = () => { const onDelete = () => {
const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed); const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed);
if ($showDeleteModal && (!isTrashEnabled || hasTrashedAsset)) {
isShowDeleteConfirmation = true;
return;
}
handlePromiseError(trashOrDelete(hasTrashedAsset)); handlePromiseError(trashOrDelete(hasTrashedAsset));
}; };
const onForceDelete = () => {
if ($showDeleteModal) {
isShowDeleteConfirmation = true;
return;
}
handlePromiseError(trashOrDelete(true));
};
const trashOrDelete = async (force: boolean = false) => { 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( await deleteAssets(
!(isTrashEnabled && !force), forceOrNoTrash,
(assetIds) => (assets = assets.filter((asset) => !assetIds.includes(asset.id))), (assetIds) => (assets = assets.filter((asset) => !assetIds.includes(asset.id))),
assetInteraction.selectedAssets, selectedAssets,
onReload, onReload,
); );
assetInteraction.clearMultiselect(); assetInteraction.clearMultiselect();
}; };
@ -285,7 +280,7 @@
shortcuts.push( shortcuts.push(
{ shortcut: { key: 'Escape' }, onShortcut: deselectAllAssets }, { shortcut: { key: 'Escape' }, onShortcut: deselectAllAssets },
{ shortcut: { key: 'Delete' }, onShortcut: onDelete }, { 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: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() },
{ shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive }, { shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive },
); );
@ -405,8 +400,6 @@
} }
}; };
let isTrashEnabled = $derived(featureFlagsManager.value.trash);
$effect(() => { $effect(() => {
if (!lastAssetMouseEvent) { if (!lastAssetMouseEvent) {
assetInteraction.clearAssetSelectionCandidates(); assetInteraction.clearAssetSelectionCandidates();
@ -440,14 +433,6 @@
onscroll={() => updateSlidingWindow()} onscroll={() => updateSlidingWindow()}
/> />
{#if isShowDeleteConfirmation}
<DeleteAssetDialog
size={assetInteraction.selectedAssets.length}
onCancel={() => (isShowDeleteConfirmation = false)}
onConfirm={() => handlePromiseError(trashOrDelete(true))}
/>
{/if}
{#if assets.length > 0} {#if assets.length > 0}
<div <div
style:position="relative" style:position="relative"

View file

@ -46,7 +46,6 @@
album?: AlbumResponseDto; album?: AlbumResponseDto;
albumUsers?: UserResponseDto[]; albumUsers?: UserResponseDto[];
person?: PersonResponseDto; person?: PersonResponseDto;
isShowDeleteConfirmation?: boolean;
onSelect?: (asset: TimelineAsset) => void; onSelect?: (asset: TimelineAsset) => void;
onEscape?: () => void; onEscape?: () => void;
children?: Snippet; children?: Snippet;
@ -79,7 +78,6 @@
album, album,
albumUsers = [], albumUsers = [],
person, person,
isShowDeleteConfirmation = $bindable(false),
onSelect = () => {}, onSelect = () => {},
onEscape = () => {}, onEscape = () => {},
children, children,
@ -600,7 +598,6 @@
scrollToAsset={(asset) => scrollToAsset(asset) ?? false} scrollToAsset={(asset) => scrollToAsset(asset) ?? false}
{timelineManager} {timelineManager}
{assetInteraction} {assetInteraction}
bind:isShowDeleteConfirmation
{onEscape} {onEscape}
/> />

View file

@ -1,55 +1,48 @@
<script lang="ts"> <script lang="ts">
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte'; import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.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 { type OnDelete, type OnUndoDelete, deleteAssets } from '$lib/utils/actions'; import { type OnDelete, type OnUndoDelete, deleteAssets } from '$lib/utils/actions';
import { IconButton } from '@immich/ui'; import { IconButton, modalManager } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiDeleteOutline, mdiTimerSand } from '@mdi/js'; import { mdiDeleteForeverOutline, mdiDeleteOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
interface Props { type Props = {
onAssetDelete: OnDelete; onAssetDelete: OnDelete;
onUndoDelete?: OnUndoDelete | undefined; onUndoDelete?: OnUndoDelete | undefined;
menuItem?: boolean; menuItem?: boolean;
force?: boolean; force?: boolean;
} };
let { let { onAssetDelete, onUndoDelete = undefined, menuItem = false, force: forceRequested }: Props = $props();
onAssetDelete,
onUndoDelete = undefined, const force = $derived(forceRequested || !featureFlagsManager.value.trash);
menuItem = false, let label = $derived(force ? $t('permanently_delete') : $t('delete'));
force = !featureFlagsManager.value.trash, let loading = $state(false);
}: Props = $props();
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
let isShowConfirmation = $state(false); const onAction = async () => {
let loading = $state(false); const assets = getOwnedAssets();
let label = $derived(force ? $t('permanently_delete') : $t('delete')); if (force && $showDeleteModal) {
const confirmed = await modalManager.show(AssetDeleteConfirmModal, { size: assets.length });
const handleTrash = async () => { if (!confirmed) {
if (force) { return;
isShowConfirmation = true; }
return;
} }
await handleDelete();
};
const handleDelete = async () => {
loading = true; loading = true;
const assets = [...getOwnedAssets()];
await deleteAssets(force, onAssetDelete, assets, onUndoDelete); await deleteAssets(force, onAssetDelete, assets, onUndoDelete);
clearSelect(); clearSelect();
isShowConfirmation = false;
loading = false; loading = false;
}; };
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text={label} icon={mdiDeleteOutline} onClick={handleTrash} /> <MenuOption text={label} icon={mdiDeleteOutline} onClick={onAction} />
{:else if loading} {:else if loading}
<IconButton <IconButton
shape="round" shape="round"
@ -66,14 +59,6 @@
variant="ghost" variant="ghost"
aria-label={label} aria-label={label}
icon={mdiDeleteForeverOutline} icon={mdiDeleteForeverOutline}
onclick={handleTrash} onclick={onAction}
/>
{/if}
{#if isShowConfirmation}
<DeleteAssetDialog
size={getOwnedAssets().length}
onConfirm={handleDelete}
onCancel={() => (isShowConfirmation = false)}
/> />
{/if} {/if}

View file

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut'; import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { import {
setFocusToAsset as setFocusAssetInit, setFocusToAsset as setFocusAssetInit,
setFocusTo as setFocusToInit, setFocusTo as setFocusToInit,
@ -10,6 +9,7 @@
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte'; import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
@ -22,53 +22,42 @@
import { AssetVisibility } from '@immich/sdk'; import { AssetVisibility } from '@immich/sdk';
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
interface Props { type Props = {
timelineManager: TimelineManager; timelineManager: TimelineManager;
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
isShowDeleteConfirmation: boolean;
onEscape?: () => void; onEscape?: () => void;
scrollToAsset: (asset: TimelineAsset) => boolean; scrollToAsset: (asset: TimelineAsset) => boolean;
} };
let { let { timelineManager = $bindable(), assetInteraction, onEscape, scrollToAsset }: Props = $props();
timelineManager = $bindable(),
assetInteraction,
isShowDeleteConfirmation = $bindable(false),
onEscape,
scrollToAsset,
}: Props = $props();
const { isViewing: showAssetViewer } = assetViewingStore; const { isViewing: showAssetViewer } = assetViewingStore;
const trashOrDelete = async (force: boolean = false) => { const trashOrDelete = async (forceRequested?: boolean) => {
isShowDeleteConfirmation = false; const force = forceRequested || !featureFlagsManager.value.trash;
const selectedAssets = assetInteraction.selectedAssets;
if ($showDeleteModal && force) {
const confirmed = await modalManager.show(AssetDeleteConfirmModal, { size: selectedAssets.length });
if (!confirmed) {
return;
}
}
await deleteAssets( await deleteAssets(
!(isTrashEnabled && !force), force,
(assetIds) => timelineManager.removeAssets(assetIds), (assetIds) => timelineManager.removeAssets(assetIds),
assetInteraction.selectedAssets, selectedAssets,
!isTrashEnabled || force ? undefined : (assets) => timelineManager.upsertAssets(assets), force ? undefined : (assets) => timelineManager.upsertAssets(assets),
); );
assetInteraction.clearMultiselect(); assetInteraction.clearMultiselect();
}; };
const onDelete = () => { const onDelete = () => {
const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed); const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed);
if ($showDeleteModal && (!isTrashEnabled || hasTrashedAsset)) {
isShowDeleteConfirmation = true;
return;
}
handlePromiseError(trashOrDelete(hasTrashedAsset)); handlePromiseError(trashOrDelete(hasTrashedAsset));
}; };
const onForceDelete = () => {
if ($showDeleteModal) {
isShowDeleteConfirmation = true;
return;
}
handlePromiseError(trashOrDelete(true));
};
const onStackAssets = async () => { const onStackAssets = async () => {
const result = await stackAssets(assetInteraction.selectedAssets); const result = await stackAssets(assetInteraction.selectedAssets);
@ -118,9 +107,7 @@
} }
}; };
const isTrashEnabled = $derived(featureFlagsManager.value.trash);
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0); const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
const idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id));
let isShortcutModalOpen = false; let isShortcutModalOpen = false;
const handleOpenShortcutModal = async () => { const handleOpenShortcutModal = async () => {
@ -176,7 +163,7 @@
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {
shortcuts.push( shortcuts.push(
{ shortcut: { key: 'Delete' }, onShortcut: onDelete }, { 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: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() },
{ shortcut: { key: 's' }, onShortcut: () => onStackAssets() }, { shortcut: { key: 's' }, onShortcut: () => onStackAssets() },
{ shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive }, { shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive },
@ -189,11 +176,3 @@
</script> </script>
<svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} onselectstart={onSelectStart} use:shortcuts={shortcutList} /> <svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} onselectstart={onSelectStart} use:shortcuts={shortcutList} />
{#if isShowDeleteConfirmation}
<DeleteAssetDialog
size={idsSelectedAssets.length}
onCancel={() => (isShowDeleteConfirmation = false)}
onConfirm={() => handlePromiseError(trashOrDelete(true))}
/>
{/if}

View file

@ -5,21 +5,21 @@
import { mdiDeleteForeverOutline } from '@mdi/js'; import { mdiDeleteForeverOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
interface Props { type Props = {
size: number; size: number;
onConfirm: () => void; onClose: (confirmed?: boolean) => void;
onCancel: () => void; };
}
let { size, onConfirm, onCancel }: Props = $props(); let { size, onClose: onCloseParent }: Props = $props();
let checked = $state(false); let checked = $state(false);
const handleConfirm = () => { const onClose = (confirmed: boolean) => {
if (checked) { if (confirmed && checked) {
$showDeleteModal = false; $showDeleteModal = false;
} }
onConfirm();
onCloseParent(confirmed);
}; };
</script> </script>
@ -27,7 +27,7 @@
title={$t('permanently_delete_assets_count', { values: { count: size } })} title={$t('permanently_delete_assets_count', { values: { count: size } })}
confirmText={$t('delete')} confirmText={$t('delete')}
icon={mdiDeleteForeverOutline} icon={mdiDeleteForeverOutline}
onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())} {onClose}
> >
{#snippet promptSnippet()} {#snippet promptSnippet()}
<p> <p>