From e8b4ac05226d9cc3a007c8a48e4f6d84f7ef4b8a Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Thu, 3 Apr 2025 10:01:41 -0400 Subject: [PATCH] fix(web): use original image if web compatible (#17347) * use original image if web compatible * add e2e * fix shared link handling * handle redirect in e2e * fix size not being passed to thumbnail url * test fullsize in e2e --- e2e/src/web/specs/photo-viewer.e2e-spec.ts | 15 ++++++++- .../asset-viewer/photo-viewer.spec.ts | 22 +++++-------- .../asset-viewer/photo-viewer.svelte | 31 ++++++++++--------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/e2e/src/web/specs/photo-viewer.e2e-spec.ts b/e2e/src/web/specs/photo-viewer.e2e-spec.ts index 37e691625a5..4871e7522ce 100644 --- a/e2e/src/web/specs/photo-viewer.e2e-spec.ts +++ b/e2e/src/web/specs/photo-viewer.e2e-spec.ts @@ -8,12 +8,14 @@ function imageLocator(page: Page) { test.describe('Photo Viewer', () => { let admin: LoginResponseDto; let asset: AssetMediaResponseDto; + let rawAsset: AssetMediaResponseDto; test.beforeAll(async () => { utils.initSdk(); await utils.resetDatabase(); admin = await utils.adminSetup(); asset = await utils.createAsset(admin.accessToken); + rawAsset = await utils.createAsset(admin.accessToken, { assetData: { filename: 'test.arw' } }); }); test.beforeEach(async ({ context, page }) => { @@ -36,7 +38,7 @@ test.describe('Photo Viewer', () => { await expect(page.getByTestId('loading-spinner')).toBeVisible(); }); - test('loads high resolution photo when zoomed', async ({ page }) => { + test('loads original photo when zoomed', async ({ page }) => { await page.goto(`/photos/${asset.id}`); await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail'); const box = await imageLocator(page).boundingBox(); @@ -44,6 +46,17 @@ test.describe('Photo Viewer', () => { const { x, y, width, height } = box!; await page.mouse.move(x + width / 2, y + height / 2); await page.mouse.wheel(0, -1); + await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original'); + }); + + test('loads fullsize image when zoomed and original is web-incompatible', async ({ page }) => { + await page.goto(`/photos/${rawAsset.id}`); + await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail'); + const box = await imageLocator(page).boundingBox(); + expect(box).toBeTruthy(); + const { x, y, width, height } = box!; + await page.mouse.move(x + width / 2, y + height / 2); + await page.mouse.wheel(0, -1); await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('fullsize'); }); 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 8690aae0c35..d90fb89c236 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.spec.ts +++ b/web/src/lib/components/asset-viewer/photo-viewer.spec.ts @@ -48,7 +48,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - cacheKey: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); }); @@ -57,11 +57,8 @@ describe('PhotoViewer component', () => { const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' }); render(PhotoViewer, { asset }); - expect(getAssetThumbnailUrlSpy).toBeCalledWith({ - id: asset.id, - cacheKey: asset.checksum, - size: AssetMediaSize.Fullsize, - }); + expect(getAssetThumbnailUrlSpy).not.toBeCalled(); + expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash }); }); it('loads original for shared link when download permission is true and showMetadata permission is true', () => { @@ -69,13 +66,8 @@ describe('PhotoViewer component', () => { const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] }); render(PhotoViewer, { asset, sharedLink }); - expect(getAssetThumbnailUrlSpy).toBeCalledWith({ - id: asset.id, - size: AssetMediaSize.Fullsize, - cacheKey: asset.checksum, - }); - // expect(getAssetThumbnailUrlSpy).not.toBeCalled(); - // expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash }); + expect(getAssetThumbnailUrlSpy).not.toBeCalled(); + expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash }); }); it('not loads original image when shared link download permission is false', () => { @@ -86,7 +78,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - cacheKey: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); @@ -100,7 +92,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - cacheKey: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index 1d7c49f1078..6bc67a5257e 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -6,8 +6,8 @@ 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 { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; - import { canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils'; + import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; + import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } 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'; @@ -68,7 +68,7 @@ $boundingBoxesArray = []; }); - const preload = (targetSize: AssetMediaSize, preloadAssets?: AssetResponseDto[]) => { + const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: AssetResponseDto[]) => { for (const preloadAsset of preloadAssets || []) { if (preloadAsset.type === AssetTypeEnum.Image) { let img = new Image(); @@ -77,13 +77,14 @@ } }; - const getAssetUrl = (id: string, targetSize: AssetMediaSize, cacheKey: string | null) => { - let finalAssetMediaSize = targetSize; + const getAssetUrl = (id: string, targetSize: AssetMediaSize | 'original', cacheKey: string | null) => { if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) { - finalAssetMediaSize = AssetMediaSize.Preview; + return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey }); } - return getAssetThumbnailUrl({ id, size: finalAssetMediaSize, cacheKey }); + return targetSize === 'original' + ? getAssetOriginalUrl({ id, cacheKey }) + : getAssetThumbnailUrl({ id, size: targetSize, cacheKey }); }; copyImage = async () => { @@ -136,16 +137,18 @@ // when true, will force loading of the original image let forceUseOriginal: boolean = $derived(asset.originalMimeType === 'image/gif' || $photoZoomState.currentZoom > 1); - const targetImageSize = $derived( - $alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded - ? AssetMediaSize.Fullsize - : AssetMediaSize.Preview, - ); + const targetImageSize = $derived.by(() => { + if ($alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded) { + return isWebCompatibleImage(asset) ? 'original' : AssetMediaSize.Fullsize; + } + + return AssetMediaSize.Preview; + }); const onload = () => { imageLoaded = true; assetFileUrl = imageLoaderUrl; - originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize; + originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize || targetImageSize === 'original'; }; const onerror = () => { @@ -168,7 +171,7 @@ }; }); - let imageLoaderUrl = $derived(getAssetUrl(asset.id, targetImageSize, asset.checksum)); + let imageLoaderUrl = $derived(getAssetUrl(asset.id, targetImageSize, asset.thumbhash)); let containerWidth = $state(0); let containerHeight = $state(0);