diff --git a/e2e/src/specs/web/photo-viewer.e2e-spec.ts b/e2e/src/specs/web/photo-viewer.e2e-spec.ts
index 1e723916d3..88b61278bc 100644
--- a/e2e/src/specs/web/photo-viewer.e2e-spec.ts
+++ b/e2e/src/specs/web/photo-viewer.e2e-spec.ts
@@ -31,8 +31,8 @@ test.describe('Photo Viewer', () => {
test('loads original photo when zoomed', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
- const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
- await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
+ const preview = page.getByTestId('preview').filter({ visible: true });
+ await expect(preview).toHaveAttribute('src', /.+/);
const originalResponse = page.waitForResponse((response) => response.url().includes('/original'));
@@ -49,8 +49,8 @@ test.describe('Photo Viewer', () => {
test('loads fullsize image when zoomed and original is web-incompatible', async ({ page }) => {
await page.goto(`/photos/${rawAsset.id}`);
- const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
- await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
+ const preview = page.getByTestId('preview').filter({ visible: true });
+ await expect(preview).toHaveAttribute('src', /.+/);
const fullsizeResponse = page.waitForResponse((response) => response.url().includes('fullsize'));
@@ -67,15 +67,14 @@ test.describe('Photo Viewer', () => {
test('reloads photo when checksum changes', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
- const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
- await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
- const initialSrc = await thumbnail.getAttribute('src');
+ const preview = page.getByTestId('preview').filter({ visible: true });
+ await expect(preview).toHaveAttribute('src', /.+/);
+ const initialSrc = await preview.getAttribute('src');
const websocketEvent = utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
await utils.replaceAsset(admin.accessToken, asset.id);
await websocketEvent;
- const preview = page.getByTestId('preview').filter({ visible: true });
await expect(preview).not.toHaveAttribute('src', initialSrc!);
});
});
diff --git a/e2e/src/ui/specs/asset-viewer/broken-asset.e2e-spec.ts b/e2e/src/ui/specs/asset-viewer/broken-asset.e2e-spec.ts
index b2502df6fc..de3460f893 100644
--- a/e2e/src/ui/specs/asset-viewer/broken-asset.e2e-spec.ts
+++ b/e2e/src/ui/specs/asset-viewer/broken-asset.e2e-spec.ts
@@ -73,7 +73,7 @@ test.describe('broken-asset responsiveness', () => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await page.waitForSelector('#immich-asset-viewer');
- const viewerBrokenAsset = page.locator('[data-viewer-content] [data-broken-asset]');
+ const viewerBrokenAsset = page.locator('[data-viewer-content] [data-broken-asset]').first();
await expect(viewerBrokenAsset).toBeVisible();
await expect(viewerBrokenAsset.locator('svg')).toBeVisible();
diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte
index 20dc5d4a2f..0e58c98f01 100644
--- a/web/src/lib/components/AdaptiveImage.svelte
+++ b/web/src/lib/components/AdaptiveImage.svelte
@@ -59,17 +59,15 @@
{
quality: 'thumbnail',
url: assetUrls.thumbnail,
- checkCanceled: false,
onAfterLoad: afterThumbnail,
onAfterError: afterThumbnail,
},
{
quality: 'preview',
url: assetUrls.preview,
- checkCanceled: true,
onAfterError: (loader) => loader.trigger('original'),
},
- { quality: 'original', url: assetUrls.original, checkCanceled: true },
+ { quality: 'original', url: assetUrls.original },
];
return qualityList;
};
@@ -81,7 +79,7 @@
return untrack(
() =>
- new AdaptiveImageLoader(asset.id, buildQualityList(), {
+ new AdaptiveImageLoader(buildQualityList(), {
onImageReady,
onError,
onUrlChange,
@@ -131,7 +129,7 @@
});
$effect(() => {
- if (assetViewerManager.zoom > 1 && status.quality.preview === 'success' && status.quality.original !== 'success') {
+ if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') {
untrack(() => void adaptiveImageLoader.trigger('original'));
}
});
diff --git a/web/src/lib/components/asset-viewer/PreloadManager.svelte.ts b/web/src/lib/components/asset-viewer/PreloadManager.svelte.ts
index 57935a4be8..60328abe8f 100644
--- a/web/src/lib/components/asset-viewer/PreloadManager.svelte.ts
+++ b/web/src/lib/components/asset-viewer/PreloadManager.svelte.ts
@@ -23,19 +23,17 @@ export class PreloadManager {
{
quality: 'thumbnail',
url: urls.thumbnail,
- checkCanceled: false,
onAfterLoad: afterThumbnail,
onAfterError: afterThumbnail,
},
{
quality: 'preview',
url: urls.preview,
- checkCanceled: true,
onAfterError: (loader) => loader.trigger('original'),
},
- { quality: 'original', url: urls.original, checkCanceled: true },
+ { quality: 'original', url: urls.original },
];
- const loader = new AdaptiveImageLoader(asset.id, qualityList, undefined, loadImage);
+ const loader = new AdaptiveImageLoader(qualityList, undefined, loadImage);
loader.start();
return loader;
}
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index 69b6a4f6b2..ee18d24e89 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -9,6 +9,7 @@
import { AssetAction, ProjectionType } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
+ import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
@@ -46,6 +47,7 @@
import DetailPanel from './detail-panel.svelte';
import EditorPanel from './editor/editor-panel.svelte';
import CropArea from './editor/transform-tool/crop-area.svelte';
+ import FaceEditor from './face-editor/face-editor.svelte';
import ImagePanoramaViewer from './image-panorama-viewer.svelte';
import OcrButton from './ocr-button.svelte';
import PhotoViewer from './photo-viewer.svelte';
@@ -116,6 +118,10 @@
playOriginalVideo = value;
};
+ const selectStackedAsset = async (id: string) => {
+ selectedStackAsset = await assetCacheManager.getAsset({ id });
+ };
+
const refreshStack = async () => {
if (authManager.isSharedLink || !withStacked) {
return;
@@ -128,7 +134,10 @@
}
stack = await getStack({ id: cursor.current.stack.id });
- selectedStackAsset = stack?.assets.find(({ id }) => id === stack?.primaryAssetId);
+ const primaryAsset = stack?.assets.find(({ id }) => id === stack?.primaryAssetId);
+ if (primaryAsset) {
+ await selectStackedAsset(primaryAsset.id);
+ }
};
const handleFavorite = async () => {
@@ -179,11 +188,21 @@
onClose?.(asset);
};
+ const refreshPreservingSelection = async () => {
+ const id = asset.id;
+ assetCacheManager.invalidateAsset(id);
+ if (selectedStackAsset) {
+ await selectStackedAsset(id);
+ } else {
+ const asset = await assetCacheManager.getAsset({ id });
+ assetViewingStore.setAsset(asset);
+ }
+ onAssetChange?.(asset);
+ };
+
const closeEditor = async () => {
if (editManager.hasAppliedEdits) {
- const refreshedAsset = await getAssetInfo({ id: asset.id });
- onAssetChange?.(refreshedAsset);
- assetViewingStore.setAsset(refreshedAsset);
+ await refreshPreservingSelection();
}
assetViewerManager.closeEditor();
};
@@ -454,6 +473,9 @@
navigateAsset('previous');
}
};
+
+ let containerWidth = $state(0);
+ let containerHeight = $state(0);