1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-21 18:16:55 +02:00

feat(web): Implement keep this delete others for asset stacks (#14217)

This commit is contained in:
Braydon Davis 2024-11-19 09:54:35 -07:00 committed by GitHub
parent bcd17c2ebe
commit 1737013e66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 65 additions and 0 deletions

View File

@ -465,6 +465,7 @@
"confirm": "Confirm", "confirm": "Confirm",
"confirm_admin_password": "Confirm Admin Password", "confirm_admin_password": "Confirm Admin Password",
"confirm_delete_shared_link": "Are you sure you want to delete this shared link?", "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", "confirm_password": "Confirm password",
"contain": "Contain", "contain": "Contain",
"context": "Context", "context": "Context",
@ -514,6 +515,7 @@
"delete_key": "Delete key", "delete_key": "Delete key",
"delete_library": "Delete Library", "delete_library": "Delete Library",
"delete_link": "Delete link", "delete_link": "Delete link",
"delete_others": "Delete others",
"delete_shared_link": "Delete shared link", "delete_shared_link": "Delete shared link",
"delete_tag": "Delete tag", "delete_tag": "Delete tag",
"delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} 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_remove_product_key": "Failed to remove product key",
"failed_to_stack_assets": "Failed to stack assets", "failed_to_stack_assets": "Failed to stack assets",
"failed_to_unstack_assets": "Failed to un-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.", "import_path_already_exists": "This import path already exists.",
"incorrect_email_or_password": "Incorrect email or password", "incorrect_email_or_password": "Incorrect email or password",
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation", "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
@ -1253,8 +1256,10 @@
"unselect_all_duplicates": "Unselect all duplicates", "unselect_all_duplicates": "Unselect all duplicates",
"unstack": "Un-stack", "unstack": "Un-stack",
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", "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": "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", "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", "up_next": "Up next",
"updated_password": "Updated password", "updated_password": "Updated password",
"upload": "Upload", "upload": "Upload",

View File

@ -12,6 +12,7 @@ type ActionMap = {
[AssetAction.ADD]: { asset: AssetResponseDto }; [AssetAction.ADD]: { asset: AssetResponseDto };
[AssetAction.ADD_TO_ALBUM]: { asset: AssetResponseDto; album: AlbumResponseDto }; [AssetAction.ADD_TO_ALBUM]: { asset: AssetResponseDto; album: AlbumResponseDto };
[AssetAction.UNSTACK]: { assets: AssetResponseDto[] }; [AssetAction.UNSTACK]: { assets: AssetResponseDto[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: AssetResponseDto };
}; };
export type Action = { export type Action = {

View File

@ -0,0 +1,33 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
import { mdiPinOutline } from '@mdi/js';
import type { OnAction } from './action';
import { t } from 'svelte-i18n';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let stack: StackResponseDto;
export let asset: AssetResponseDto;
export let onAction: OnAction;
const handleKeepThisDeleteOthers = async () => {
const isConfirmed = await dialogController.show({
title: $t('keep_this_delete_others'),
prompt: $t('confirm_keep_this_delete_others'),
confirmText: $t('delete_others'),
});
if (!isConfirmed) {
return;
}
const keptAsset = await keepThisDeleteOthers(asset, stack);
if (keptAsset) {
onAction({ type: AssetAction.UNSTACK, assets: [keptAsset] });
}
};
</script>
<MenuOption icon={mdiPinOutline} onClick={handleKeepThisDeleteOthers} text={$t('keep_this_delete_others')} />

View File

@ -13,6 +13,7 @@
import ShareAction from '$lib/components/asset-viewer/actions/share-action.svelte'; import ShareAction from '$lib/components/asset-viewer/actions/share-action.svelte';
import ShowDetailAction from '$lib/components/asset-viewer/actions/show-detail-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 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 CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.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'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
@ -166,6 +167,7 @@
{#if isOwner} {#if isOwner}
{#if stack} {#if stack}
<UnstackAction {stack} {onAction} /> <UnstackAction {stack} {onAction} />
<KeepThisDeleteOthersAction {stack} {asset} {onAction} />
{/if} {/if}
{#if album} {#if album}
<SetAlbumCoverAction {asset} {album} /> <SetAlbumCoverAction {asset} {album} />

View File

@ -379,6 +379,7 @@
break; break;
} }
case AssetAction.KEEP_THIS_DELETE_OTHERS:
case AssetAction.UNSTACK: { case AssetAction.UNSTACK: {
closeViewer(); closeViewer();
} }

View File

@ -9,6 +9,7 @@ export enum AssetAction {
ADD = 'add', ADD = 'add',
ADD_TO_ALBUM = 'add-to-album', ADD_TO_ALBUM = 'add-to-album',
UNSTACK = 'unstack', UNSTACK = 'unstack',
KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others',
} }
export enum AppRoute { export enum AppRoute {

View File

@ -14,6 +14,7 @@ import { getFormatter } from '$lib/utils/i18n';
import { import {
addAssetsToAlbum as addAssets, addAssetsToAlbum as addAssets,
createStack, createStack,
deleteAssets,
deleteStacks, deleteStacks,
getAssetInfo, getAssetInfo,
getBaseUrl, getBaseUrl,
@ -27,6 +28,7 @@ import {
type AssetResponseDto, type AssetResponseDto,
type AssetTypeEnum, type AssetTypeEnum,
type DownloadInfoDto, type DownloadInfoDto,
type StackResponseDto,
type UserPreferencesResponseDto, type UserPreferencesResponseDto,
type UserResponseDto, type UserResponseDto,
} from '@immich/sdk'; } 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) => { export const selectAllAssets = async (assetStore: AssetStore, assetInteractionStore: AssetInteractionStore) => {
if (get(isSelectingAllAssets)) { if (get(isSelectingAllAssets)) {
// Selection is already ongoing // Selection is already ongoing