From bf04d9eb3947cacc44e0f28a3ffd32cb39de1657 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 8 Aug 2022 22:06:11 -0500 Subject: [PATCH] Feature - Delete asset on the web (#436) * Added selection mechanism to photos page * Added control app bar * Refactor AlbumAppBar into ControlAppBar * Added addtional micro interactions when in multi selection mode * Implemented delete selected asset and rerender --- .../components/album-page/album-viewer.svelte | 10 +- .../album-page/asset-selection.svelte | 6 +- .../album-page/thumbnail-selection.svelte | 6 +- .../control-app-bar.svelte} | 0 web/src/routes/photos/index.svelte | 142 +++++++++++++++++- 5 files changed, 147 insertions(+), 17 deletions(-) rename web/src/lib/components/{album-page/album-app-bar.svelte => shared-components/control-app-bar.svelte} (100%) diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 9d18e1ff0b..b9742f6653 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -12,7 +12,6 @@ import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; import AssetSelection from './asset-selection.svelte'; import _ from 'lodash-es'; - import AlbumAppBar from './album-app-bar.svelte'; import UserSelectionModal from './user-selection-modal.svelte'; import ShareInfoModal from './share-info-modal.svelte'; import CircleIconButton from '../shared-components/circle-icon-button.svelte'; @@ -22,6 +21,7 @@ import ContextMenu from '../shared-components/context-menu/context-menu.svelte'; import MenuOption from '../shared-components/context-menu/menu-option.svelte'; import ThumbnailSelection from './thumbnail-selection.svelte'; + import ControlAppBar from '../shared-components/control-app-bar.svelte'; export let album: AlbumResponseDto; @@ -272,7 +272,7 @@
{#if isMultiSelectionMode} - {/if} - + {/if} {#if !isMultiSelectionMode} - goto(backUrl)} backIcon={ArrowLeft}> + goto(backUrl)} backIcon={ArrowLeft}> {#if album.assets.length > 0} {/if} - + {/if}
diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte index 2b832d7d85..0c3ebfce69 100644 --- a/web/src/lib/components/album-page/asset-selection.svelte +++ b/web/src/lib/components/album-page/asset-selection.svelte @@ -8,9 +8,9 @@ import moment from 'moment'; import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; import { AssetResponseDto } from '@api'; - import AlbumAppBar from './album-app-bar.svelte'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; import { albumUploadAssetStore } from '$lib/stores/album-upload-asset'; + import ControlAppBar from '../shared-components/control-app-bar.svelte'; const dispatch = createEventDispatcher(); @@ -172,7 +172,7 @@ transition:fly={{ y: 500, duration: 100, easing: quintOut }} class="absolute top-0 left-0 w-full h-full py-[160px] bg-immich-bg z-[9999]" > - dispatch('go-back')}> + dispatch('go-back')}> {#if selectedAsset.size == 0}

Add to album

@@ -195,7 +195,7 @@ >Done
-
+
{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} diff --git a/web/src/lib/components/album-page/thumbnail-selection.svelte b/web/src/lib/components/album-page/thumbnail-selection.svelte index efa5727768..7008244539 100644 --- a/web/src/lib/components/album-page/thumbnail-selection.svelte +++ b/web/src/lib/components/album-page/thumbnail-selection.svelte @@ -3,8 +3,8 @@ import { createEventDispatcher } from 'svelte'; import { quintOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; + import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; - import AlbumAppBar from './album-app-bar.svelte'; export let album: AlbumResponseDto; @@ -24,7 +24,7 @@ transition:fly={{ y: 500, duration: 100, easing: quintOut }} class="absolute top-0 left-0 w-full h-full py-[160px] bg-immich-bg z-[9999]" > - dispatch('close')}> + dispatch('close')}>

Select album cover

@@ -37,7 +37,7 @@ >Done -
+
diff --git a/web/src/lib/components/album-page/album-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte similarity index 100% rename from web/src/lib/components/album-page/album-app-bar.svelte rename to web/src/lib/components/shared-components/control-app-bar.svelte diff --git a/web/src/routes/photos/index.svelte b/web/src/routes/photos/index.svelte index 7cd22bce77..209b4be712 100644 --- a/web/src/routes/photos/index.svelte +++ b/web/src/routes/photos/index.svelte @@ -3,6 +3,7 @@ import type { Load } from '@sveltejs/kit'; import { setAssetInfo } from '$lib/stores/assets'; + export const load: Load = async ({ fetch, session }) => { if (!browser && !session.user) { return { @@ -39,20 +40,31 @@ import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; import CheckCircle from 'svelte-material-icons/CheckCircle.svelte'; import { fly } from 'svelte/transition'; - import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets'; + import { assetsGroupByDate, flattenAssetGroupByDate, assets } from '$lib/stores/assets'; import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte'; import moment from 'moment'; import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; - import { AssetResponseDto, UserResponseDto } from '@api'; + import { api, AssetResponseDto, UserResponseDto } from '@api'; import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; + import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; + import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; + import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; + import Close from 'svelte-material-icons/Close.svelte'; import { browser } from '$app/env'; + import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; export let user: UserResponseDto; let selectedGroupThumbnail: number | null; let isMouseOverGroup: boolean; + let multiSelectedAssets = new Set(); + $: isMultiSelectionMode = multiSelectedAssets.size > 0; + + let selectedGroup: Set = new Set(); + let existingGroup: Set = new Set(); + $: if (isMouseOverGroup == false) { selectedGroupThumbnail = null; } @@ -110,6 +122,91 @@ isShowAssetViewer = false; history.pushState(null, '', `/photos`); }; + + const selectAssetHandler = (asset: AssetResponseDto, groupIndex: number) => { + let temp = new Set(multiSelectedAssets); + + if (multiSelectedAssets.has(asset)) { + temp.delete(asset); + + const tempSelectedGroup = new Set(selectedGroup); + tempSelectedGroup.delete(groupIndex); + selectedGroup = tempSelectedGroup; + } else { + temp.add(asset); + } + + multiSelectedAssets = temp; + + // Check if all assets are selected in a group to toggle the group selection's icon + if (!selectedGroup.has(groupIndex)) { + const assetsInGroup = $assetsGroupByDate[groupIndex]; + let selectedAssetsInGroupCount = 0; + + assetsInGroup.forEach((asset) => { + if (multiSelectedAssets.has(asset)) { + selectedAssetsInGroupCount++; + } + }); + + // if all assets are selected in a group, add the group to selected group + if (selectedAssetsInGroupCount == assetsInGroup.length) { + selectedGroup = selectedGroup.add(groupIndex); + } + } + }; + + const clearMultiSelectAssetAssetHandler = () => { + multiSelectedAssets = new Set(); + selectedGroup = new Set(); + existingGroup = new Set(); + }; + + const selectAssetGroupHandler = (groupIndex: number) => { + if (existingGroup.has(groupIndex)) return; + + let tempSelectedGroup = new Set(selectedGroup); + let tempSelectedAsset = new Set(multiSelectedAssets); + + if (selectedGroup.has(groupIndex)) { + tempSelectedGroup.delete(groupIndex); + tempSelectedAsset.forEach((asset) => { + if ($assetsGroupByDate[groupIndex].find((a) => a.id == asset.id)) { + tempSelectedAsset.delete(asset); + } + }); + } else { + tempSelectedGroup.add(groupIndex); + tempSelectedAsset = new Set([...multiSelectedAssets, ...$assetsGroupByDate[groupIndex]]); + } + + multiSelectedAssets = tempSelectedAsset; + selectedGroup = tempSelectedGroup; + }; + + const deleteSelectedAssetHandler = async () => { + try { + if ( + window.confirm( + `Are you sure you want to delete ${multiSelectedAssets.size} assets? This action cannot be undone.` + ) + ) { + const { data: deletedAssets } = await api.assetApi.deleteAsset({ + ids: Array.from(multiSelectedAssets).map((a) => a.id) + }); + + for (const asset of deletedAssets) { + if (asset.status == 'SUCCESS') { + $assets = $assets.filter((a) => a.id !== asset.id); + } + } + + clearMultiSelectAssetAssetHandler(); + } + } catch (e) { + console.log('Error deleteSelectedAssetHandler', e); + } + }; @@ -117,7 +214,28 @@
- openFileUploadDialog(UploadType.GENERAL)} /> + {#if isMultiSelectionMode} + + +

Selected {multiSelectedAssets.size}

+
+ + + +
+ {/if} + + {#if !isMultiSelectionMode} + openFileUploadDialog(UploadType.GENERAL)} /> + {/if}
@@ -136,13 +254,20 @@ >

- {#if selectedGroupThumbnail === groupIndex && isMouseOverGroup} + {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || isMultiSelectionMode}

selectAssetGroupHandler(groupIndex)} > - + {#if selectedGroup.has(groupIndex)} + + {:else if existingGroup.has(groupIndex)} + + {:else} + + {/if}
{/if} @@ -156,7 +281,12 @@ + isMultiSelectionMode + ? selectAssetHandler(asset, groupIndex) + : viewAssetHandler(event)} + on:select={() => selectAssetHandler(asset, groupIndex)} + selected={multiSelectedAssets.has(asset)} {groupIndex} /> {/key}