mirror of
https://github.com/immich-app/immich.git
synced 2025-05-30 19:54:52 -04:00
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
This commit is contained in:
parent
548298b0c7
commit
e8b4ac0522
@ -8,12 +8,14 @@ function imageLocator(page: Page) {
|
|||||||
test.describe('Photo Viewer', () => {
|
test.describe('Photo Viewer', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset: AssetMediaResponseDto;
|
let asset: AssetMediaResponseDto;
|
||||||
|
let rawAsset: AssetMediaResponseDto;
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
utils.initSdk();
|
utils.initSdk();
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup();
|
admin = await utils.adminSetup();
|
||||||
asset = await utils.createAsset(admin.accessToken);
|
asset = await utils.createAsset(admin.accessToken);
|
||||||
|
rawAsset = await utils.createAsset(admin.accessToken, { assetData: { filename: 'test.arw' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
@ -36,7 +38,7 @@ test.describe('Photo Viewer', () => {
|
|||||||
await expect(page.getByTestId('loading-spinner')).toBeVisible();
|
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 page.goto(`/photos/${asset.id}`);
|
||||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||||
const box = await imageLocator(page).boundingBox();
|
const box = await imageLocator(page).boundingBox();
|
||||||
@ -44,6 +46,17 @@ test.describe('Photo Viewer', () => {
|
|||||||
const { x, y, width, height } = box!;
|
const { x, y, width, height } = box!;
|
||||||
await page.mouse.move(x + width / 2, y + height / 2);
|
await page.mouse.move(x + width / 2, y + height / 2);
|
||||||
await page.mouse.wheel(0, -1);
|
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');
|
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('fullsize');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
cacheKey: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
@ -57,11 +57,8 @@ describe('PhotoViewer component', () => {
|
|||||||
const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' });
|
const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' });
|
||||||
render(PhotoViewer, { asset });
|
render(PhotoViewer, { asset });
|
||||||
|
|
||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
||||||
id: asset.id,
|
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash });
|
||||||
cacheKey: asset.checksum,
|
|
||||||
size: AssetMediaSize.Fullsize,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads original for shared link when download permission is true and showMetadata permission is true', () => {
|
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] });
|
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
|
||||||
render(PhotoViewer, { asset, sharedLink });
|
render(PhotoViewer, { asset, sharedLink });
|
||||||
|
|
||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
||||||
id: asset.id,
|
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash });
|
||||||
size: AssetMediaSize.Fullsize,
|
|
||||||
cacheKey: asset.checksum,
|
|
||||||
});
|
|
||||||
// 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', () => {
|
it('not loads original image when shared link download permission is false', () => {
|
||||||
@ -86,7 +78,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
cacheKey: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
@ -100,7 +92,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
cacheKey: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||||
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||||
import { canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils';
|
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
@ -68,7 +68,7 @@
|
|||||||
$boundingBoxesArray = [];
|
$boundingBoxesArray = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const preload = (targetSize: AssetMediaSize, preloadAssets?: AssetResponseDto[]) => {
|
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: AssetResponseDto[]) => {
|
||||||
for (const preloadAsset of preloadAssets || []) {
|
for (const preloadAsset of preloadAssets || []) {
|
||||||
if (preloadAsset.type === AssetTypeEnum.Image) {
|
if (preloadAsset.type === AssetTypeEnum.Image) {
|
||||||
let img = new Image();
|
let img = new Image();
|
||||||
@ -77,13 +77,14 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAssetUrl = (id: string, targetSize: AssetMediaSize, cacheKey: string | null) => {
|
const getAssetUrl = (id: string, targetSize: AssetMediaSize | 'original', cacheKey: string | null) => {
|
||||||
let finalAssetMediaSize = targetSize;
|
|
||||||
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
|
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 () => {
|
copyImage = async () => {
|
||||||
@ -136,16 +137,18 @@
|
|||||||
// 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);
|
let forceUseOriginal: boolean = $derived(asset.originalMimeType === 'image/gif' || $photoZoomState.currentZoom > 1);
|
||||||
|
|
||||||
const targetImageSize = $derived(
|
const targetImageSize = $derived.by(() => {
|
||||||
$alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded
|
if ($alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded) {
|
||||||
? AssetMediaSize.Fullsize
|
return isWebCompatibleImage(asset) ? 'original' : AssetMediaSize.Fullsize;
|
||||||
: AssetMediaSize.Preview,
|
}
|
||||||
);
|
|
||||||
|
return AssetMediaSize.Preview;
|
||||||
|
});
|
||||||
|
|
||||||
const onload = () => {
|
const onload = () => {
|
||||||
imageLoaded = true;
|
imageLoaded = true;
|
||||||
assetFileUrl = imageLoaderUrl;
|
assetFileUrl = imageLoaderUrl;
|
||||||
originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize;
|
originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize || targetImageSize === 'original';
|
||||||
};
|
};
|
||||||
|
|
||||||
const onerror = () => {
|
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 containerWidth = $state(0);
|
||||||
let containerHeight = $state(0);
|
let containerHeight = $state(0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user