diff --git a/web/src/lib/managers/AssetCacheManager.svelte.ts b/web/src/lib/managers/AssetCacheManager.svelte.ts new file mode 100644 index 0000000000..298682b396 --- /dev/null +++ b/web/src/lib/managers/AssetCacheManager.svelte.ts @@ -0,0 +1,52 @@ +import { getAssetInfo, getAssetOcr, type AssetOcrResponseDto, type AssetResponseDto } from '@immich/sdk'; + +class AsyncCache { + #cache = new Map(); + + async getOrFetch( + params: K, + fetcher: (params: K) => Promise, + keySerializer: (params: K) => string = (params) => JSON.stringify(params), + ): Promise { + const cacheKey = keySerializer(params); + + const cached = this.#cache.get(cacheKey); + if (cached) { + return cached; + } + + const value = await fetcher(params); + if (value) { + this.#cache.set(cacheKey, value); + } + + return value; + } + + clear() { + this.#cache.clear(); + } +} + +class AssetCacheManager { + #assetCache = new AsyncCache(); + #ocrCache = new AsyncCache(); + + async getAsset(assetIdentifier: { key?: string; slug?: string; id: string }) { + return this.#assetCache.getOrFetch(assetIdentifier, getAssetInfo); + } + + async getAssetOcr(id: string) { + return this.#ocrCache.getOrFetch({ id }, getAssetOcr, (params) => params.id); + } + + clearAssetCache() { + this.#assetCache.clear(); + } + + clearOcrCache() { + this.#ocrCache.clear(); + } +} + +export const assetCacheManager = new AssetCacheManager(); diff --git a/web/src/lib/utils/navigation.ts b/web/src/lib/utils/navigation.ts index c3fe051f12..6d000d0368 100644 --- a/web/src/lib/utils/navigation.ts +++ b/web/src/lib/utils/navigation.ts @@ -1,8 +1,8 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; +import type { RouteId } from '$app/types'; import { AppRoute } from '$lib/constants'; -import { getAssetInfo } from '@immich/sdk'; -import type { NavigationTarget } from '@sveltejs/kit'; +import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte'; import { get } from 'svelte/store'; export type AssetGridRouteSearchParams = { @@ -20,11 +20,12 @@ export const isAlbumsRoute = (route?: string | null) => !!route?.startsWith('/(u export const isPeopleRoute = (route?: string | null) => !!route?.startsWith('/(user)/people/[personId]'); export const isLockedFolderRoute = (route?: string | null) => !!route?.startsWith('/(user)/locked'); -export const isAssetViewerRoute = (target?: NavigationTarget | null) => - !!(target?.route.id?.endsWith('/[[assetId=id]]') && 'assetId' in (target?.params || {})); +export const isAssetViewerRoute = ( + target?: { route?: { id?: RouteId | null }; params?: Record | null } | null, +) => !!(target?.route?.id?.endsWith('/[[assetId=id]]') && 'assetId' in (target?.params || {})); export function getAssetInfoFromParam({ assetId, slug, key }: { assetId?: string; key?: string; slug?: string }) { - return assetId ? getAssetInfo({ id: assetId, slug, key }) : undefined; + return assetId ? assetCacheManager.getAsset({ id: assetId, slug, key }) : undefined; } function currentUrlWithoutAsset() {