Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): force delete with shift key #6239

Merged
merged 15 commits into from
Jan 17, 2024
45 changes: 22 additions & 23 deletions web/src/lib/components/asset-viewer/asset-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -279,7 +278,7 @@
}
return;
case 'Delete':
trashOrDelete();
trashOrDelete(shiftKey);
return;
case 'Escape':
if (isShowDeleteConfirmation) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)}
Expand Down Expand Up @@ -764,20 +771,12 @@
{/if}

{#if isShowDeleteConfirmation}
<ConfirmDialogue
title="Delete {getAssetType(asset.type)}"
confirmText="Delete"
on:confirm={deleteAsset}
<DeleteAssetDialog
size={1}
on:cancel={() => (isShowDeleteConfirmation = false)}
>
<svelte:fragment slot="prompt">
<p>
Are you sure you want to delete this {getAssetType(asset.type).toLowerCase()}? This will also remove it from
its album(s).
</p>
<p><b>You cannot undo this action!</b></p>
</svelte:fragment>
</ConfirmDialogue>
on:escape={() => (isShowDeleteConfirmation = false)}
on:confirm={() => deleteAsset()}
/>
{/if}

{#if isShowProfileImageCrop}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
62 changes: 14 additions & 48 deletions web/src/lib/components/photos-page/actions/delete-assets.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api } from '@api';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { createEventDispatcher } from 'svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { mdiTimerSand, mdiDeleteOutline } from '@mdi/js';
import { OnDelete, deleteAssets } from '$lib/utils/actions';
import DeleteAssetDialog from '../delete-asset-dialog.svelte';

export let onAssetDelete: OnAssetDelete;
export let onAssetDelete: OnDelete;
export let menuItem = false;
export let force = !$featureFlags.trash;

const { clearSelect, getOwnedAssets } = getAssetControlContext();
const { getOwnedAssets } = getAssetControlContext();

const dispatch = createEventDispatcher<{
escape: void;
Expand All @@ -37,28 +32,12 @@

const handleDelete = async () => {
loading = true;

try {
const ids = Array.from(getOwnedAssets())
.filter((a) => !a.isExternal)
.map((a) => a.id);
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,
});

clearSelect();
} catch (e) {
handleError(e, 'Error deleting assets');
} finally {
isShowConfirmation = false;
loading = false;
}
const ids = Array.from(getOwnedAssets())
.filter((a) => !a.isExternal)
.map((a) => a.id);
await deleteAssets(force, onAssetDelete, ids);
isShowConfirmation = false;
loading = false;
};

const escape = () => {
Expand All @@ -76,23 +55,10 @@
{/if}

{#if isShowConfirmation}
<ConfirmDialogue
title="Permanently Delete Asset{getOwnedAssets().size > 1 ? 's' : ''}"
confirmText="Delete"
<DeleteAssetDialog
size={getOwnedAssets().size}
on:confirm={handleDelete}
on:cancel={() => (isShowConfirmation = false)}
on:escape={escape}
>
<svelte:fragment slot="prompt">
<p>
Are you sure you want to permanently delete
{#if getOwnedAssets().size > 1}
these <b>{getOwnedAssets().size}</b> assets? This will also remove them from their album(s).
{:else}
this asset? This will also remove it from its album(s).
{/if}
</p>
<p><b>You cannot undo this action!</b></p>
</svelte:fragment>
</ConfirmDialogue>
/>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api } from '@api';
import { OnFavorite, getAssetControlContext } from '../asset-select-control-bar.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
import type { OnFavorite } from '$lib/utils/actions';

export let onFavorite: OnFavorite | undefined = undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import { api } from '@api';
import Icon from '$lib/components/elements/icon.svelte';
import Button from '../../elements/buttons/button.svelte';
import { OnRestore, getAssetControlContext } from '../asset-select-control-bar.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiHistory } from '@mdi/js';
import type { OnRestore } from '$lib/utils/actions';

export let onRestore: OnRestore | undefined = undefined;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { api } from '@api';
import { OnStack, getAssetControlContext } from '../asset-select-control-bar.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import type { OnStack } from '$lib/utils/actions';

export let onStack: OnStack | undefined = undefined;

Expand Down
43 changes: 39 additions & 4 deletions web/src/lib/components/photos-page/asset-grid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
import { locale } from '$lib/stores/preferences.store';
import { locale, showDeleteModal } from '$lib/stores/preferences.store';
import { isSearchEnabled } from '$lib/stores/search.store';
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
import type { AlbumResponseDto, AssetResponseDto } from '@api';
Expand All @@ -19,6 +19,8 @@
import AssetDateGroup from './asset-date-group.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
import { deleteAssets } from '$lib/utils/actions';
import DeleteAssetDialog from './delete-asset-dialog.svelte';

export let isSelectionMode = false;
export let singleSelect = false;
Expand All @@ -28,9 +30,9 @@
export let withStacked = false;
export let isShared = false;
export let album: AlbumResponseDto | null = null;
export let isShowDeleteConfirmation = false;

$: isTrashEnabled = $featureFlags.loaded && $featureFlags.trash;
export let forceDelete = false;

const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
assetInteractionStore;
Expand All @@ -42,6 +44,9 @@

$: timelineY = element?.scrollTop || 0;
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
$: idsSelectedAssets = Array.from($selectedAssets)
.filter((a) => !a.isExternal)
.map((a) => a.id);

const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
Expand All @@ -65,13 +70,22 @@
assetStore.disconnect();
});

const trashOrDelete = (force: boolean = false) => {
isShowDeleteConfirmation = false;
deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets);
assetInteractionStore.clearMultiselect();
};

const handleKeyboardPress = (event: KeyboardEvent) => {
if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
return;
}

const key = event.key;
const shiftKey = event.shiftKey;

if (!$showAssetViewer) {
switch (event.key) {
switch (key) {
case 'Escape':
dispatch('escape');
return;
Expand All @@ -85,6 +99,20 @@
event.preventDefault();
goto(AppRoute.EXPLORE);
return;
case 'Delete':
if ($isMultiSelectState) {
let force = false;
if (shiftKey || !isTrashEnabled) {
if ($showDeleteModal) {
isShowDeleteConfirmation = true;
return;
}
force = true;
}

trashOrDelete(force);
}
return;
}
}
};
Expand Down Expand Up @@ -331,6 +359,14 @@

<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} on:selectstart={onSelectStart} />

{#if isShowDeleteConfirmation}
<DeleteAssetDialog
size={idsSelectedAssets.length}
on:cancel={() => (isShowDeleteConfirmation = false)}
on:confirm={() => trashOrDelete()}
/>
{/if}

{#if showShortcuts}
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
{/if}
Expand Down Expand Up @@ -411,7 +447,6 @@
{withStacked}
{assetStore}
asset={$viewingAsset}
force={forceDelete || !isTrashEnabled}
{isShared}
{album}
on:previous={() => handlePrevious()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
<script lang="ts" context="module">
import { createContext } from '$lib/utils/context';

export type OnAssetDelete = (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 interface AssetControlContext {
// Wrap assets in a function, because context isn't reactive.
getAssets: () => Set<AssetResponseDto>; // All assets includes partners' assets
Expand Down
Loading
Loading