From c317feaf93db2fca5003592c31c8d000d2bed57f Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:18:04 +0100 Subject: [PATCH] feat(web): force delete with shift key (#6239) * feat: force delete with shift key * fix: types import * pr feedback * fix: permanently delete assets * fix: format * fix: remove unused variable * change info title * simplify * fix: rename function name * pr feedback * simplify * pr feedback * add toggle in the user settings * fix: trash settings, input label, and wording --------- Co-authored-by: Jason Rasmussen --- .../asset-viewer/asset-viewer.svelte | 45 +++++++------- .../photos-page/actions/archive-action.svelte | 3 +- .../photos-page/actions/delete-assets.svelte | 62 +++++-------------- .../actions/favorite-action.svelte | 3 +- .../photos-page/actions/restore-assets.svelte | 3 +- .../photos-page/actions/stack-action.svelte | 3 +- .../components/photos-page/asset-grid.svelte | 43 +++++++++++-- .../asset-select-control-bar.svelte | 6 -- .../photos-page/delete-asset-dialog.svelte | 57 +++++++++++++++++ .../shared-components/show-shortcuts.svelte | 25 ++++++-- .../user-settings-page/trash-settings.svelte | 23 +++++++ .../user-settings-list.svelte | 5 ++ web/src/lib/stores/preferences.store.ts | 2 + web/src/lib/utils/actions.ts | 25 ++++++++ web/src/routes/(user)/albums/+page.svelte | 4 +- web/src/routes/(user)/trash/+page.svelte | 2 +- .../routes/(user)/user-settings/+page.svelte | 14 +++++ 17 files changed, 233 insertions(+), 92 deletions(-) create mode 100644 web/src/lib/components/photos-page/delete-asset-dialog.svelte create mode 100644 web/src/lib/components/user-settings-page/trash-settings.svelte create mode 100644 web/src/lib/utils/actions.ts diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index ffc2c5bbf2..fc298f471d 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -20,10 +20,9 @@ import VideoViewer from './video-viewer.svelte'; import PanoramaViewer from './panorama-viewer.svelte'; import { AppRoute, AssetAction, ProjectionType } from '$lib/constants'; - import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte'; - import { isShowDetail } from '$lib/stores/preferences.store'; - import { addAssetsToAlbum, downloadFile, getAssetType } from '$lib/utils/asset-utils'; + import { isShowDetail, showDeleteModal } from '$lib/stores/preferences.store'; + import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils'; import NavigationArea from './navigation-area.svelte'; import { browser } from '$app/environment'; import { handleError } from '$lib/utils/handle-error'; @@ -42,13 +41,13 @@ import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import SlideshowBar from './slideshow-bar.svelte'; import { user } from '$lib/stores/user.store'; + import DeleteAssetDialog from '../photos-page/delete-asset-dialog.svelte'; export let assetStore: AssetStore | null = null; export let asset: AssetResponseDto; export let showNavigation = true; export let sharedLink: SharedLinkResponseDto | undefined = undefined; $: isTrashEnabled = $featureFlags.trash; - export let force = false; export let withStacked = false; export let isShared = false; export let album: AlbumResponseDto | null = null; @@ -279,7 +278,7 @@ } return; case 'Delete': - trashOrDelete(); + trashOrDelete(shiftKey); return; case 'Escape': if (isShowDeleteConfirmation) { @@ -360,11 +359,19 @@ $isShowDetail = !$isShowDetail; }; - $: trashOrDelete = !(force || !isTrashEnabled) - ? trashAsset - : () => { + const trashOrDelete = (force: boolean = false) => { + if (force || !isTrashEnabled) { + if ($showDeleteModal) { isShowDeleteConfirmation = true; - }; + return; + } + deleteAsset(); + return; + } + + trashAsset(); + return; + }; const trashAsset = async () => { try { @@ -576,7 +583,7 @@ on:back={closeViewer} on:showDetail={showDetailInfoHandler} on:download={() => downloadFile(asset)} - on:delete={trashOrDelete} + on:delete={() => trashOrDelete()} on:favorite={toggleFavorite} on:addToAlbum={() => openAlbumPicker(false)} on:addToSharedAlbum={() => openAlbumPicker(true)} @@ -764,20 +771,12 @@ {/if} {#if isShowDeleteConfirmation} - (isShowDeleteConfirmation = false)} - > - -

- Are you sure you want to delete this {getAssetType(asset.type).toLowerCase()}? This will also remove it from - its album(s). -

-

You cannot undo this action!

-
-
+ on:escape={() => (isShowDeleteConfirmation = false)} + on:confirm={() => deleteAsset()} + /> {/if} {#if isShowProfileImageCrop} diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte index a72bc106ab..fc3739c4e6 100644 --- a/web/src/lib/components/photos-page/actions/archive-action.svelte +++ b/web/src/lib/components/photos-page/actions/archive-action.svelte @@ -7,8 +7,9 @@ import { handleError } from '$lib/utils/handle-error'; import { api } from '@api'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; - import { OnArchive, getAssetControlContext } from '../asset-select-control-bar.svelte'; + import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js'; + import type { OnArchive } from '$lib/utils/actions'; export let onArchive: OnArchive | undefined = undefined; diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte index 02fbdaeed3..a43f5de02b 100644 --- a/web/src/lib/components/photos-page/actions/delete-assets.svelte +++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte @@ -1,23 +1,18 @@ + + dispatch('cancel')} + on:escape={() => dispatch('cancel')} +> + +

+ Are you sure you want to permanently delete + {#if size > 1} + these {size} assets? This will also remove them from their album(s). + {:else} + this asset? This will also remove it from its album(s). + {/if} +

+

You cannot undo this action!

+ +
+ + +
+
+
diff --git a/web/src/lib/components/shared-components/show-shortcuts.svelte b/web/src/lib/components/shared-components/show-shortcuts.svelte index 2f725f1a45..ef53b17515 100644 --- a/web/src/lib/components/shared-components/show-shortcuts.svelte +++ b/web/src/lib/components/shared-components/show-shortcuts.svelte @@ -2,9 +2,21 @@ import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import { createEventDispatcher } from 'svelte'; import FullScreenModal from './full-screen-modal.svelte'; - import { mdiClose } from '@mdi/js'; + import { mdiClose, mdiInformationOutline } from '@mdi/js'; + import Icon from '../elements/icon.svelte'; - const shortcuts = { + interface Shortcuts { + general: ExplainedShortcut[]; + actions: ExplainedShortcut[]; + } + + interface ExplainedShortcut { + key: string[]; + action: string; + info?: string; + } + + const shortcuts: Shortcuts = { general: [ { key: ['←', '→'], action: 'Previous or next photo' }, { key: ['Esc'], action: 'Back, close, or deselect' }, @@ -16,7 +28,7 @@ { key: ['⇧', 'a'], action: 'Archive or unarchive photo' }, { key: ['⇧', 'd'], action: 'Download' }, { key: ['Space'], action: 'Play or pause video' }, - { key: ['Del'], action: 'Delete Asset' }, + { key: ['Del'], action: 'Trash/Delete Asset', info: 'press ⇧ to permanently delete asset' }, ], }; const dispatch = createEventDispatcher<{ @@ -71,7 +83,12 @@

{/each} -

{shortcut.action}

+
+

{shortcut.action}

+ {#if shortcut.info} + + {/if} +
{/each} diff --git a/web/src/lib/components/user-settings-page/trash-settings.svelte b/web/src/lib/components/user-settings-page/trash-settings.svelte new file mode 100644 index 0000000000..21f00178d4 --- /dev/null +++ b/web/src/lib/components/user-settings-page/trash-settings.svelte @@ -0,0 +1,23 @@ + + +
+
+
+
+
+ +
+
+
+
+
+ +
diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte index 29901177c0..3260993b7d 100644 --- a/web/src/lib/components/user-settings-page/user-settings-list.svelte +++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte @@ -15,6 +15,7 @@ import UserProfileSettings from './user-profile-settings.svelte'; import { user } from '$lib/stores/user.store'; import AppearanceSettings from './appearance-settings.svelte'; + import TrashSettings from './trash-settings.svelte'; export let keys: APIKeyResponseDto[] = []; export let devices: AuthDeviceResponseDto[] = []; @@ -70,3 +71,7 @@ + + + + diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts index 8c59b83a0a..6ce3a791d2 100644 --- a/web/src/lib/stores/preferences.store.ts +++ b/web/src/lib/stores/preferences.store.ts @@ -96,3 +96,5 @@ export const albumViewSettings = persisted('album-view-settin sortDesc: true, view: AlbumViewMode.Cover, }); + +export const showDeleteModal = persisted('delete-confirm-dialog', true, {}); diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts new file mode 100644 index 0000000000..3148143c0e --- /dev/null +++ b/web/src/lib/utils/actions.ts @@ -0,0 +1,25 @@ +import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; +import { api } from '@api'; +import { handleError } from './handle-error'; + +export type OnDelete = (assetId: string) => void; +export type OnRestore = (ids: string[]) => void; +export type OnArchive = (ids: string[], isArchived: boolean) => void; +export type OnFavorite = (ids: string[], favorite: boolean) => void; +export type OnStack = (ids: string[]) => void; + +export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => { + try { + await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } }); + for (const id of ids) { + onAssetDelete(id); + } + + notificationController.show({ + message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`, + type: NotificationType.Info, + }); + } catch (e) { + handleError(e, 'Error deleting assets'); + } +}; diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte index 0c56f690cd..55f64be50f 100644 --- a/web/src/routes/(user)/albums/+page.svelte +++ b/web/src/routes/(user)/albums/+page.svelte @@ -181,7 +181,7 @@ } } - const test = (searched: string): Sort => { + const searchSort = (searched: string): Sort => { for (const key in sortByOptions) { if (sortByOptions[key].title === searched) { return sortByOptions[key]; @@ -256,7 +256,7 @@ { return { title: option.title, diff --git a/web/src/routes/(user)/trash/+page.svelte b/web/src/routes/(user)/trash/+page.svelte index 671154ea27..87e3190e66 100644 --- a/web/src/routes/(user)/trash/+page.svelte +++ b/web/src/routes/(user)/trash/+page.svelte @@ -87,7 +87,7 @@ - +

Trashed items will be permanently deleted after {$serverConfig.trashDays} days.

diff --git a/web/src/routes/(user)/user-settings/+page.svelte b/web/src/routes/(user)/user-settings/+page.svelte index 616b22203d..359ce33d2c 100644 --- a/web/src/routes/(user)/user-settings/+page.svelte +++ b/web/src/routes/(user)/user-settings/+page.svelte @@ -1,15 +1,29 @@ + + (isShowKeyboardShortcut = !isShowKeyboardShortcut)}> + + +
+ +{#if isShowKeyboardShortcut} + (isShowKeyboardShortcut = false)} /> +{/if}