From c227f9893e74be22deb3eaaae5561d8689bee844 Mon Sep 17 00:00:00 2001 From: Ethan Margaillan Date: Wed, 17 Apr 2024 13:55:07 +0200 Subject: [PATCH] feat(web): un-stack from the photos page ; fix stack count (#8419) * feat(web): un-stack from the photos page ; fix stack count * move stuff outside of try-catch block * small optim --- .../asset-viewer/asset-viewer.svelte | 22 ++--- .../photos-page/actions/stack-action.svelte | 39 ++++++-- .../components/photos-page/asset-grid.svelte | 9 +- web/src/lib/utils/actions.ts | 3 +- web/src/lib/utils/asset-utils.ts | 99 +++++++++++++------ web/src/routes/(user)/photos/+page.svelte | 17 +++- 6 files changed, 129 insertions(+), 60 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 4fa7d72a2f..46c95636d0 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -12,7 +12,7 @@ import { stackAssetsStore } from '$lib/stores/stacked-asset.store'; import { user } from '$lib/stores/user.store'; import { getAssetJobMessage, getSharedLink, handlePromiseError, isSharedLink } from '$lib/utils'; - import { addAssetsToAlbum, addAssetsToNewAlbum, downloadFile } from '$lib/utils/asset-utils'; + import { addAssetsToAlbum, addAssetsToNewAlbum, downloadFile, unstackAssets } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { shortcuts } from '$lib/utils/shortcut'; import { SlideshowHistory } from '$lib/utils/slideshow-history'; @@ -28,7 +28,6 @@ getAllAlbums, runAssetJobs, updateAsset, - updateAssets, updateAlbumInfo, type ActivityResponseDto, type AlbumResponseDto, @@ -481,20 +480,15 @@ }; const handleUnstack = async () => { - try { - const ids = $stackAssetsStore.map(({ id }) => id); - await updateAssets({ assetBulkUpdateDto: { ids, removeParent: true } }); - for (const child of $stackAssetsStore) { - child.stackParentId = null; - child.stackCount = 0; - child.stack = []; - dispatch('action', { type: AssetAction.ADD, asset: child }); + const unstackedAssets = await unstackAssets($stackAssetsStore); + if (unstackedAssets) { + for (const asset of unstackedAssets) { + dispatch('action', { + type: AssetAction.ADD, + asset, + }); } - dispatch('close'); - notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 }); - } catch (error) { - handleError(error, `Unable to unstack`); } }; diff --git a/web/src/lib/components/photos-page/actions/stack-action.svelte b/web/src/lib/components/photos-page/actions/stack-action.svelte index a857da1dd3..b6a034672b 100644 --- a/web/src/lib/components/photos-page/actions/stack-action.svelte +++ b/web/src/lib/components/photos-page/actions/stack-action.svelte @@ -1,20 +1,45 @@ - +{#if unstack} + +{:else} + +{/if} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 06b5627d1b..a84b9d4d73 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -89,11 +89,10 @@ }; const onStackAssets = async () => { - if ($selectedAssets.size > 1) { - await stackAssets(Array.from($selectedAssets), (ids) => { - assetStore.removeAssets(ids); - dispatch('escape'); - }); + const ids = await stackAssets(Array.from($selectedAssets)); + if (ids) { + assetStore.removeAssets(ids); + dispatch('escape'); } }; diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts index b6718e63a1..ecfd29a8fc 100644 --- a/web/src/lib/utils/actions.ts +++ b/web/src/lib/utils/actions.ts @@ -1,5 +1,5 @@ import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; -import { deleteAssets as deleteBulk } from '@immich/sdk'; +import { deleteAssets as deleteBulk, type AssetResponseDto } from '@immich/sdk'; import { handleError } from './handle-error'; export type OnDelete = (assetIds: string[]) => void; @@ -7,6 +7,7 @@ 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 type OnUnstack = (assets: AssetResponseDto[]) => void; export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => { try { diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 72102d2634..50337fb06b 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification'; import { AppRoute } from '$lib/constants'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; +import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { BucketPosition, isSelectingAllAssets, type AssetStore } from '$lib/stores/assets.store'; import { downloadManager } from '$lib/stores/download'; import { downloadRequest, getKey } from '$lib/utils'; @@ -269,43 +270,81 @@ export const getSelectedAssets = (assets: Set, user: UserRespo return ids; }; -export async function stackAssets(assets: Array, onStack: (ds: string[]) => void) { +export const stackAssets = async (assets: AssetResponseDto[]) => { + if (assets.length < 2) { + return false; + } + + const parent = assets[0]; + const children = assets.slice(1); + const ids = children.map(({ id }) => id); + try { - const parent = assets.at(0); - if (!parent) { - return; - } + await updateAssets({ + assetBulkUpdateDto: { + ids, + stackParentId: parent.id, + }, + }); + } catch (error) { + handleError(error, 'Failed to stack assets'); + return false; + } - const children = assets.slice(1); - const ids = children.map(({ id }) => id); - - if (children.length > 0) { - await updateAssets({ assetBulkUpdateDto: { ids, stackParentId: parent.id } }); - } - - let childrenCount = parent.stackCount || 1; - for (const asset of children) { - asset.stackParentId = parent.id; - // Add grand-children's count to new parent - childrenCount += asset.stackCount || 1; + let grandChildren: AssetResponseDto[] = []; + for (const asset of children) { + asset.stackParentId = parent.id; + if (asset.stack) { + // Add grand-children to new parent + grandChildren = grandChildren.concat(asset.stack); // Reset children stack info asset.stackCount = null; asset.stack = []; } - - parent.stackCount = childrenCount; - - notificationController.show({ - message: `Stacked ${ids.length + 1} assets`, - type: NotificationType.Info, - timeout: 1500, - }); - - onStack(ids); - } catch (error) { - handleError(error, `Unable to stack`); } -} + + parent.stack ??= []; + parent.stack = parent.stack.concat(children, grandChildren); + parent.stackCount = parent.stack.length + 1; + + notificationController.show({ + message: `Stacked ${parent.stackCount} assets`, + type: NotificationType.Info, + button: { + text: 'View Stack', + onClick() { + return assetViewingStore.setAssetId(parent.id); + }, + }, + }); + + return ids; +}; + +export const unstackAssets = async (assets: AssetResponseDto[]) => { + const ids = assets.map(({ id }) => id); + try { + await updateAssets({ + assetBulkUpdateDto: { + ids, + removeParent: true, + }, + }); + } catch (error) { + handleError(error, 'Failed to un-stack assets'); + return; + } + for (const asset of assets) { + asset.stackParentId = null; + asset.stackCount = null; + asset.stack = []; + } + notificationController.show({ + type: NotificationType.Info, + message: `Un-stacked ${assets.length} assets`, + }); + return assets; +}; export const selectAllAssets = async (assetStore: AssetStore, assetInteractionStore: AssetInteractionStore) => { if (get(isSelectingAllAssets)) { diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index 00409cdc1b..f711b081d6 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -30,7 +30,14 @@ const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; - $: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite); + let isAllFavorite: boolean; + let isAssetStackSelected: boolean; + + $: { + const selection = [...$selectedAssets]; + isAllFavorite = selection.every((asset) => asset.isFavorite); + isAssetStackSelected = selection.length === 1 && !!selection[0].stack; + } const handleEscape = () => { if ($showAssetViewer) { @@ -62,8 +69,12 @@ assetStore.triggerUpdate()} /> - {#if $selectedAssets.size > 1} - assetStore.removeAssets(assetIds)} /> + {#if $selectedAssets.size > 1 || isAssetStackSelected} + assetStore.removeAssets(assetIds)} + onUnstack={(assets) => assetStore.addAssets(assets)} + /> {/if}