diff --git a/web/package-lock.json b/web/package-lock.json index e998a46a10..82942102fb 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -20,6 +20,7 @@ "@photo-sphere-viewer/settings-plugin": "^5.11.5", "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", + "async-mutex": "^0.5.0", "dom-to-image": "^2.6.0", "fabric": "^6.5.4", "geojson": "^0.5.0", @@ -3924,6 +3925,15 @@ "dev": true, "license": "MIT" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", diff --git a/web/package.json b/web/package.json index 08afe31762..2ad2a8361a 100644 --- a/web/package.json +++ b/web/package.json @@ -37,6 +37,7 @@ "@photo-sphere-viewer/settings-plugin": "^5.11.5", "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", + "async-mutex": "^0.5.0", "dom-to-image": "^2.6.0", "fabric": "^6.5.4", "geojson": "^0.5.0", diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index cf8f0fb879..2d40857f99 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -91,7 +91,7 @@ empty, }: Props = $props(); - let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget } = assetViewingStore; + let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget, mutex } = assetViewingStore; let element: HTMLElement | undefined = $state(); @@ -438,6 +438,7 @@ }; const handlePrevious = async () => { + const release = await mutex.acquire(); const laterAsset = await timelineManager.getLaterAsset($viewingAsset); if (laterAsset) { @@ -447,11 +448,14 @@ await navigate({ targetRoute: 'current', assetId: laterAsset.id }); } + release(); return !!laterAsset; }; const handleNext = async () => { + const release = await mutex.acquire(); const earlierAsset = await timelineManager.getEarlierAsset($viewingAsset); + if (earlierAsset) { const preloadAsset = await timelineManager.getEarlierAsset(earlierAsset); const asset = await getAssetInfo({ id: earlierAsset.id, key: authManager.key }); @@ -459,6 +463,7 @@ await navigate({ targetRoute: 'current', assetId: earlierAsset.id }); } + release(); return !!earlierAsset; }; diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 2cd20d9d20..997f6d20fa 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -2,12 +2,14 @@ import { authManager } from '$lib/managers/auth-manager.svelte'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { type AssetGridRouteSearchParams } from '$lib/utils/navigation'; import { getAssetInfo, type AssetResponseDto } from '@immich/sdk'; +import { Mutex } from 'async-mutex'; import { readonly, writable } from 'svelte/store'; function createAssetViewingStore() { const viewingAssetStoreState = writable(); const preloadAssets = writable([]); const viewState = writable(false); + const viewingAssetMutex = new Mutex(); const gridScrollTarget = writable(); const setAsset = (asset: AssetResponseDto, assetsToPreload: TimelineAsset[] = []) => { @@ -28,6 +30,7 @@ function createAssetViewingStore() { return { asset: readonly(viewingAssetStoreState), + mutex: viewingAssetMutex, preloadAssets: readonly(preloadAssets), isViewing: viewState, gridScrollTarget,