diff --git a/i18n/en.json b/i18n/en.json index a1c79bbec4..d56595c826 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -465,6 +465,7 @@ "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", "confirm_delete_shared_link": "Are you sure you want to delete this shared link?", + "confirm_keep_this_delete_others": "All other assets in the stack will be deleted except for this asset. Are you sure you want to continue?", "confirm_password": "Confirm password", "contain": "Contain", "context": "Context", @@ -514,6 +515,7 @@ "delete_key": "Delete key", "delete_library": "Delete Library", "delete_link": "Delete link", + "delete_others": "Delete others", "delete_shared_link": "Delete shared link", "delete_tag": "Delete tag", "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", @@ -610,6 +612,7 @@ "failed_to_remove_product_key": "Failed to remove product key", "failed_to_stack_assets": "Failed to stack assets", "failed_to_unstack_assets": "Failed to un-stack assets", + "failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets", "import_path_already_exists": "This import path already exists.", "incorrect_email_or_password": "Incorrect email or password", "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation", @@ -1253,8 +1256,10 @@ "unselect_all_duplicates": "Unselect all duplicates", "unstack": "Un-stack", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", + "keep_this_delete_others": "Keep this, delete others", "untracked_files": "Untracked files", "untracked_files_decription": "These files are not tracked by the application. They can be the results of failed moves, interrupted uploads, or left behind due to a bug", + "kept_this_deleted_others": "Kept this asset and deleted {count, plural, one {# asset} other {# assets}}", "up_next": "Up next", "updated_password": "Updated password", "upload": "Upload", diff --git a/web/src/lib/components/asset-viewer/actions/action.ts b/web/src/lib/components/asset-viewer/actions/action.ts index d6136f2d18..f8cfd447f0 100644 --- a/web/src/lib/components/asset-viewer/actions/action.ts +++ b/web/src/lib/components/asset-viewer/actions/action.ts @@ -12,6 +12,7 @@ type ActionMap = { [AssetAction.ADD]: { asset: AssetResponseDto }; [AssetAction.ADD_TO_ALBUM]: { asset: AssetResponseDto; album: AlbumResponseDto }; [AssetAction.UNSTACK]: { assets: AssetResponseDto[] }; + [AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: AssetResponseDto }; }; export type Action = { diff --git a/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte new file mode 100644 index 0000000000..3d52b5f28d --- /dev/null +++ b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte @@ -0,0 +1,33 @@ + + + diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index 7972ff6c72..8bea15e2a7 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -13,6 +13,7 @@ import ShareAction from '$lib/components/asset-viewer/actions/share-action.svelte'; import ShowDetailAction from '$lib/components/asset-viewer/actions/show-detail-action.svelte'; import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte'; + import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; @@ -166,6 +167,7 @@ {#if isOwner} {#if stack} + {/if} {#if album} diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 32894ad908..5e7d1a699b 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -379,6 +379,7 @@ break; } + case AssetAction.KEEP_THIS_DELETE_OTHERS: case AssetAction.UNSTACK: { closeViewer(); } diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index f6cd8aa8ab..f47d4a8c87 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -9,6 +9,7 @@ export enum AssetAction { ADD = 'add', ADD_TO_ALBUM = 'add-to-album', UNSTACK = 'unstack', + KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others', } export enum AppRoute { diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 77aa7b1ecd..37041ecbc4 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -14,6 +14,7 @@ import { getFormatter } from '$lib/utils/i18n'; import { addAssetsToAlbum as addAssets, createStack, + deleteAssets, deleteStacks, getAssetInfo, getBaseUrl, @@ -27,6 +28,7 @@ import { type AssetResponseDto, type AssetTypeEnum, type DownloadInfoDto, + type StackResponseDto, type UserPreferencesResponseDto, type UserResponseDto, } from '@immich/sdk'; @@ -438,6 +440,26 @@ export const deleteStack = async (stackIds: string[]) => { } }; +export const keepThisDeleteOthers = async (keepAsset: AssetResponseDto, stack: StackResponseDto) => { + const $t = get(t); + + try { + const assetsToDeleteIds = stack.assets.filter((asset) => asset.id !== keepAsset.id).map((asset) => asset.id); + await deleteAssets({ assetBulkDeleteDto: { ids: assetsToDeleteIds } }); + await deleteStacks({ bulkIdsDto: { ids: [stack.id] } }); + + notificationController.show({ + type: NotificationType.Info, + message: $t('kept_this_deleted_others', { values: { count: assetsToDeleteIds.length } }), + }); + + keepAsset.stack = null; + return keepAsset; + } catch (error) { + handleError(error, $t('errors.failed_to_keep_this_delete_others')); + } +}; + export const selectAllAssets = async (assetStore: AssetStore, assetInteractionStore: AssetInteractionStore) => { if (get(isSelectingAllAssets)) { // Selection is already ongoing