diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 296e2af2d0..7321e29e97 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -23,7 +23,7 @@ import { handleError } from '$lib/utils/handle-error'; import { InvocationTracker } from '$lib/utils/invocationTracker'; import { SlideshowHistory } from '$lib/utils/slideshow-history'; - import { preloadImageUrl } from '$lib/utils/sw-messaging'; + import { prepareImageUrl } from '$lib/utils/sw-messaging'; import { toTimelineAsset } from '$lib/utils/timeline-util'; import { AssetJobName, @@ -135,7 +135,7 @@ untrack(() => { if (stack && stack?.assets.length > 1) { - preloadImageUrl(getAssetUrl({ asset: stack.assets[1] })); + void prepareImageUrl(getAssetUrl({ asset: stack.assets[1] })); } }); }; @@ -385,8 +385,8 @@ // eslint-disable-next-line @typescript-eslint/no-unused-expressions asset; untrack(() => handlePromiseError(refresh())); - preloadManager.preload(cursor.nextAsset); - preloadManager.preload(cursor.previousAsset); + void preloadManager.preload(cursor.nextAsset); + void preloadManager.preload(cursor.previousAsset); }); const onAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => { diff --git a/web/src/lib/managers/PreloadManager.svelte.ts b/web/src/lib/managers/PreloadManager.svelte.ts index a68c07d505..d4dadfe80b 100644 --- a/web/src/lib/managers/PreloadManager.svelte.ts +++ b/web/src/lib/managers/PreloadManager.svelte.ts @@ -1,21 +1,22 @@ import { getAssetUrl } from '$lib/utils'; -import { cancelImageUrl, preloadImageUrl } from '$lib/utils/sw-messaging'; -import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; +import { cancelImageUrl, prepareImageUrl } from '$lib/utils/sw-messaging'; +import { type AssetResponseDto } from '@immich/sdk'; class PreloadManager { - preload(asset: AssetResponseDto | undefined) { - if (globalThis.isSecureContext) { - preloadImageUrl(getAssetUrl({ asset })); + async preload(asset: AssetResponseDto | undefined) { + if (!asset) { return; } - if (!asset || asset.type !== AssetTypeEnum.Image) { - return; - } - const img = new Image(); const url = getAssetUrl({ asset }); if (!url) { return; } + + // Prepare the URL with the service worker for cancellation tracking + await prepareImageUrl(url); + + // Create an img element to trigger browser fetch (kept in memory, not added to DOM) + const img = new Image(); img.src = url; } diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index da1826a0bc..ec4d1378df 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -31,6 +31,15 @@ import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiHeadSyncOutline, md import { init, register, t } from 'svelte-i18n'; import { derived, get } from 'svelte/store'; +export const ImageKinds = { + thumbnail: true, + preview: true, + fullsize: true, + original: true, +} as const; + +export type ImageKind = keyof typeof ImageKinds; + interface DownloadRequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; url: string; @@ -195,6 +204,23 @@ const createUrl = (path: string, parameters?: Record) => { type AssetUrlOptions = { id: string; cacheKey?: string | null; edited?: boolean }; +export const getAssetUrlForKind = (asset: AssetResponseDto, kind: ImageKind) => { + switch (kind) { + case 'preview': { + return getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Preview, cacheKey: asset.thumbhash }); + } + case 'thumbnail': { + return getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash }); + } + case 'fullsize': { + return getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Fullsize, cacheKey: asset.thumbhash }); + } + case 'original': { + return getAssetOriginalUrl({ id: asset.id, cacheKey: asset.thumbhash }); + } + } +}; + export const getAssetUrl = ({ asset, sharedLink,