diff --git a/web/src/lib/components/asset-viewer/photo-viewer.spec.ts b/web/src/lib/components/asset-viewer/photo-viewer.spec.ts index e1372e37daa4be..25c0640154eb5e 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.spec.ts +++ b/web/src/lib/components/asset-viewer/photo-viewer.spec.ts @@ -49,8 +49,11 @@ describe('PhotoViewer component', () => { const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' }); render(PhotoViewer, { asset }); - expect(getAssetThumbnailUrlSpy).not.toBeCalled(); - expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum }); + expect(getAssetThumbnailUrlSpy).toBeCalledWith({ + id: asset.id, + size: AssetMediaSize.Original, + checksum: asset.checksum, + }); }); it('loads original for shared link when download permission is true and showMetadata permission is true', () => { @@ -58,8 +61,11 @@ describe('PhotoViewer component', () => { const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] }); render(PhotoViewer, { asset, sharedLink }); - expect(getAssetThumbnailUrlSpy).not.toBeCalled(); - expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum }); + expect(getAssetThumbnailUrlSpy).toBeCalledWith({ + id: asset.id, + size: AssetMediaSize.Original, + checksum: asset.checksum, + }); }); it('not loads original image when shared link download permission is false', () => { diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index e24751b3c83fdd..1f8a778a90e341 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -7,8 +7,13 @@ import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store'; import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store'; import { photoZoomState } from '$lib/stores/zoom-image.store'; - import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; - import { isWebCompatibleImage, canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils'; + import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; + import { + isRawImage, + isWebCompatibleImage, + canCopyImageToClipboard, + copyImageToClipboard, + } from '$lib/utils/asset-utils'; import { getBoundingBox } from '$lib/utils/people-utils'; import { getAltText } from '$lib/utils/thumbnail-util'; import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; @@ -66,25 +71,23 @@ $boundingBoxesArray = []; }); - const preload = (useOriginal: boolean, preloadAssets?: AssetResponseDto[]) => { + const getAssetUrl = (id: string, targetSize: AssetMediaSize, checksum: string) => { + if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) { + return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum }); + } + + return getAssetThumbnailUrl({ id, size: targetSize, checksum }); + }; + + const preload = (targetSize: AssetMediaSize, preloadAssets?: AssetResponseDto[]) => { for (const preloadAsset of preloadAssets || []) { if (preloadAsset.type === AssetTypeEnum.Image) { let img = new Image(); - img.src = getAssetUrl(preloadAsset.id, useOriginal, preloadAsset.checksum); + img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.checksum); } } }; - const getAssetUrl = (id: string, useOriginal: boolean, checksum: string) => { - if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) { - return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum }); - } - - return useOriginal - ? getAssetOriginalUrl({ id, checksum }) - : getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum }); - }; - copyImage = async () => { if (!canCopyImageToClipboard()) { return; @@ -144,21 +147,26 @@ loader?.removeEventListener('error', onerror); }; }); + let isWebCompatible = $derived(isWebCompatibleImage(asset)); + // RAW files may have corresponding extracted JPEGs + let isRaw = $derived(isRawImage(asset)); let useOriginalByDefault = $derived(isWebCompatible && $alwaysLoadOriginalFile); - // when true, will force loading of the original image + // when true, will force loading of the original image let forceUseOriginal: boolean = $derived( - asset.originalMimeType === 'image/gif' || ($photoZoomState.currentZoom > 1 && isWebCompatible), + asset.originalMimeType === 'image/gif' || ($photoZoomState.currentZoom > 1 && (isWebCompatible || isRaw)), ); - let useOriginalImage = $derived(useOriginalByDefault || forceUseOriginal); + const targetImageSize = $derived( + useOriginalByDefault || forceUseOriginal ? AssetMediaSize.Original : AssetMediaSize.Preview, + ); $effect(() => { - preload(useOriginalImage, preloadAssets); + preload(targetImageSize, preloadAssets); }); - let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.checksum)); + let imageLoaderUrl = $derived(getAssetUrl(asset.id, targetImageSize, asset.checksum)); { switch (type) { case 'IMAGE': {