From a4e6c43823db20ee88e8b3d101fba5c2e62c938a Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Sat, 2 Mar 2024 01:49:31 +0100 Subject: [PATCH] perf(web): asset delete (#7555) * perf(web): asset delete * update asset delete on search page * don't use arrow function in class --- .../components/photos-page/asset-grid.svelte | 6 +-- web/src/lib/stores/assets.store.ts | 40 +++++++++---------- web/src/lib/utils/actions.ts | 6 +-- .../(user)/albums/[albumId]/+page.svelte | 10 +---- web/src/routes/(user)/archive/+page.svelte | 4 +- web/src/routes/(user)/favorites/+page.svelte | 6 +-- .../(user)/people/[personId]/+page.svelte | 4 +- web/src/routes/(user)/photos/+page.svelte | 6 +-- web/src/routes/(user)/search/+page.svelte | 5 ++- web/src/routes/(user)/trash/+page.svelte | 4 +- 10 files changed, 40 insertions(+), 51 deletions(-) diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index a512adaad5..10226a5ae4 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -72,7 +72,7 @@ const trashOrDelete = async (force: boolean = false) => { isShowDeleteConfirmation = false; - await deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets); + await deleteAssets(!(isTrashEnabled && !force), (assetIds) => assetStore.removeAssets(assetIds), idsSelectedAssets); assetInteractionStore.clearMultiselect(); }; @@ -169,7 +169,7 @@ (await handleNext()) || (await handlePrevious()) || handleClose(); // delete after find the next one - assetStore.removeAsset(asset.id); + assetStore.removeAssets([asset.id]); break; } @@ -414,7 +414,7 @@ {/if}
- {#each $assetStore.buckets as bucket, bucketIndex (bucketIndex)} + {#each $assetStore.buckets as bucket (bucket.bucketDate)} assetStore.cancelBucket(bucket)} diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 9fde533936..418d530fee 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -59,14 +59,14 @@ interface DeleteAsset { value: string; } -interface TrashAsset { +interface TrashAssets { type: 'trash'; - value: string; + value: string[]; } export const photoViewer = writable(null); -type PendingChange = AddAsset | UpdateAsset | DeleteAsset | TrashAsset; +type PendingChange = AddAsset | UpdateAsset | DeleteAsset | TrashAssets; export class AssetStore { private store$ = writable(this); @@ -105,7 +105,7 @@ export class AssetStore { this.addPendingChanges({ type: 'add', value: asset }); }), websocketEvents.on('on_asset_trash', (ids) => { - this.addPendingChanges(...ids.map((id): TrashAsset => ({ type: 'trash', value: id }))); + this.addPendingChanges({ type: 'trash', value: ids }); }), websocketEvents.on('on_asset_update', (asset) => { this.addPendingChanges({ type: 'update', value: asset }); @@ -137,13 +137,13 @@ export class AssetStore { case 'trash': { if (!this.options.isTrashed) { - this.removeAsset(value); + this.removeAssets(value); } break; } case 'delete': { - this.removeAsset(value); + this.removeAssets([value]); break; } } @@ -363,7 +363,7 @@ export class AssetStore { const recalculate = asset.fileCreatedAt !== _asset.fileCreatedAt; if (recalculate) { - this.removeAsset(asset.id); + this.removeAssets([asset.id]); this.addAssetToBucket(_asset); return; } @@ -373,33 +373,29 @@ export class AssetStore { } removeAssets(ids: string[]) { - // TODO: this could probably be more efficient - for (const id of ids) { - this.removeAsset(id); - } - } + const idSet = new Set(ids); + this.assets = this.assets.filter((asset) => !idSet.has(asset.id)); - removeAsset(id: string) { - this.assets = this.assets.filter((asset) => asset.id !== id); - delete this.assetToBucket[id]; - - for (let index = 0; index < this.buckets.length; index++) { + // Iterate in reverse to allow array splicing. + for (let index = this.buckets.length - 1; index >= 0; index--) { const bucket = this.buckets[index]; - for (let index_ = 0; index_ < bucket.assets.length; index_++) { + for (let index_ = bucket.assets.length - 1; index_ >= 0; index_--) { const asset = bucket.assets[index_]; - if (asset.id !== id) { + if (!idSet.has(asset.id)) { continue; } bucket.assets.splice(index_, 1); - if (bucket.assets.length === 0) { + bucket.bucketCount = bucket.assets.length; + if (bucket.bucketCount === 0) { this.buckets.splice(index, 1); } - this.emit(true); - return; + delete this.assetToBucket[asset.id]; } } + + this.emit(false); } async getPreviousAssetId(assetId: string): Promise { diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts index 981ef65d67..b6718e63a1 100644 --- a/web/src/lib/utils/actions.ts +++ b/web/src/lib/utils/actions.ts @@ -2,7 +2,7 @@ import { notificationController, NotificationType } from '$lib/components/shared import { deleteAssets as deleteBulk } from '@immich/sdk'; import { handleError } from './handle-error'; -export type OnDelete = (assetId: string) => void; +export type OnDelete = (assetIds: string[]) => void; export type OnRestore = (ids: string[]) => void; export type OnArchive = (ids: string[], isArchived: boolean) => void; export type OnFavorite = (ids: string[], favorite: boolean) => void; @@ -11,9 +11,7 @@ export type OnStack = (ids: string[]) => void; export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => { try { await deleteBulk({ assetBulkDeleteDto: { ids, force } }); - for (const id of ids) { - onAssetDelete(id); - } + onAssetDelete(ids); notificationController.show({ message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`, diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index fca02b710e..4b87b4cd51 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -328,12 +328,6 @@ } }; - const handleRemoveAssets = (assetIds: string[]) => { - for (const assetId of assetIds) { - assetStore.removeAsset(assetId); - } - }; - const handleCloseSelectAssets = () => { viewMode = ViewMode.VIEW; timelineInteractionStore.clearMultiselect(); @@ -434,10 +428,10 @@ {/if} {#if isOwned || isAllUserOwned} - handleRemoveAssets(assetIds)} /> + assetStore.removeAssets(assetIds)} /> {/if} {#if isAllUserOwned} - assetStore.removeAsset(assetId)} /> + assetStore.removeAssets(assetIds)} /> {/if} diff --git a/web/src/routes/(user)/archive/+page.svelte b/web/src/routes/(user)/archive/+page.svelte index 09ca9d93e1..2278353547 100644 --- a/web/src/routes/(user)/archive/+page.svelte +++ b/web/src/routes/(user)/archive/+page.svelte @@ -28,14 +28,14 @@ {#if $isMultiSelectState} assetInteractionStore.clearMultiselect()}> - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> - assetStore.removeAsset(assetId)} /> + assetStore.removeAssets(assetIds)} /> assetStore.triggerUpdate()} /> diff --git a/web/src/routes/(user)/favorites/+page.svelte b/web/src/routes/(user)/favorites/+page.svelte index ec7fd55f7c..21b563a50d 100644 --- a/web/src/routes/(user)/favorites/+page.svelte +++ b/web/src/routes/(user)/favorites/+page.svelte @@ -31,17 +31,17 @@ {#if $isMultiSelectState} assetInteractionStore.clearMultiselect()}> - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> - assetStore.removeAsset(assetId)} /> + assetStore.removeAssets(assetIds)} /> - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte index dd4f2d7bd1..abc3882f4b 100644 --- a/web/src/routes/(user)/people/[personId]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/+page.svelte @@ -443,11 +443,11 @@ - $assetStore.removeAsset(assetId)} /> + $assetStore.removeAssets(assetIds)} /> assetStore.triggerUpdate()} /> - $assetStore.removeAssets(ids)} /> + $assetStore.removeAssets(assetIds)} /> diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index 6a68cfe57a..df2328aaa7 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -61,14 +61,14 @@ (handleEscapeKey = true)} - onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} + onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} /> assetStore.triggerUpdate()} /> - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> {#if $selectedAssets.size > 1} - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> {/if} diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte index 28ad6bcbd7..b6c28f4b35 100644 --- a/web/src/routes/(user)/search/+page.svelte +++ b/web/src/routes/(user)/search/+page.svelte @@ -95,8 +95,9 @@ $: isAllArchived = [...selectedAssets].every((asset) => asset.isArchived); $: isAllFavorite = [...selectedAssets].every((asset) => asset.isFavorite); - const onAssetDelete = (assetId: string) => { - searchResultAssets = searchResultAssets.filter((a: AssetResponseDto) => a.id !== assetId); + const onAssetDelete = (assetIds: string[]) => { + const assetIdSet = new Set(assetIds); + searchResultAssets = searchResultAssets.filter((a: AssetResponseDto) => !assetIdSet.has(a.id)); }; const handleSelectAll = () => { selectedAssets = new Set(searchResultAssets); diff --git a/web/src/routes/(user)/trash/+page.svelte b/web/src/routes/(user)/trash/+page.svelte index 1a695bb2fc..cba8373b86 100644 --- a/web/src/routes/(user)/trash/+page.svelte +++ b/web/src/routes/(user)/trash/+page.svelte @@ -65,8 +65,8 @@ {#if $isMultiSelectState} assetInteractionStore.clearMultiselect()}> - assetStore.removeAsset(assetId)} /> - assetStore.removeAssets(ids)} /> + assetStore.removeAssets(assetIds)} /> + assetStore.removeAssets(assetIds)} /> {/if}