feat(web): undo delete (#18729)

* feat(web): Undo asset delete

* - lints and checks
- Update English translation

* Update delete-assets.svelte

Make onUndoDelete optional in Props interface

* - Ensure undo button not available on permanent delete, or trash disabled.
- Enforce lint requirement for no-negated-condition

* Fix formatting

* fix: lint

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
xCJPECKOVERx 2025-06-04 11:46:07 -04:00 committed by GitHub
parent 8733d1e554
commit 19ff39c2b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 74 additions and 17 deletions

View file

@ -1,12 +1,13 @@
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
import type { AssetStore, TimelineAsset } from '$lib/stores/assets-store.svelte';
import type { StackResponse } from '$lib/utils/asset-utils';
import { AssetVisibility, deleteAssets as deleteBulk } from '@immich/sdk';
import { AssetVisibility, deleteAssets as deleteBulk, restoreAssets } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { get } from 'svelte/store';
import { handleError } from './handle-error';
export type OnDelete = (assetIds: string[]) => void;
export type OnUndoDelete = (assets: TimelineAsset[]) => void;
export type OnRestore = (ids: string[]) => void;
export type OnLink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void;
export type OnUnlink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void;
@ -17,9 +18,15 @@ export type OnStack = (result: StackResponse) => void;
export type OnUnstack = (assets: TimelineAsset[]) => void;
export type OnSetVisibility = (ids: string[]) => void;
export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => {
export const deleteAssets = async (
force: boolean,
onAssetDelete: OnDelete,
assets: TimelineAsset[],
onUndoDelete: OnUndoDelete | undefined = undefined,
) => {
const $t = get(t);
try {
const ids = assets.map((a) => a.id);
await deleteBulk({ assetBulkDeleteDto: { ids, force } });
onAssetDelete(ids);
@ -28,12 +35,28 @@ export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids:
? $t('assets_permanently_deleted_count', { values: { count: ids.length } })
: $t('assets_trashed_count', { values: { count: ids.length } }),
type: NotificationType.Info,
...(onUndoDelete &&
!force && {
button: { text: $t('undo'), onClick: () => undoDeleteAssets(onUndoDelete, assets) },
timeout: 5000,
}),
});
} catch (error) {
handleError(error, $t('errors.unable_to_delete_assets'));
}
};
const undoDeleteAssets = async (onUndoDelete: OnUndoDelete, assets: TimelineAsset[]) => {
const $t = get(t);
try {
const ids = assets.map((a) => a.id);
await restoreAssets({ bulkIdsDto: { ids } });
onUndoDelete?.(assets);
} catch (error) {
handleError(error, $t('errors.unable_to_restore_assets'));
}
};
/**
* Update the asset stack state in the asset store based on the provided stack response.
* This function updates the stack information so that the icon is shown for the primary asset