feat: preload and cancel images with a service worker (#16893)

* feat: Service Worker to preload/cancel images and other resources

* Remove caddy configuration, localhost is secure if port-forwarded

* fix e2e tests

* Cache/return the app.html for all web entry points

* Only handle preload/cancel

* fix e2e

* fix e2e

* e2e-2

* that'll do it

* format

* fix test

* lint

* refactor common code to conditionals

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Min Idzelis 2025-04-28 10:23:05 -04:00 committed by GitHub
parent c664d99a34
commit 2fd05e8447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 108 additions and 17 deletions

View file

@ -21,6 +21,7 @@
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { cancelImageUrl, preloadImageUrl } from '$lib/utils/sw-messaging';
interface Props {
asset: AssetResponseDto;
@ -71,8 +72,7 @@
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: AssetResponseDto[]) => {
for (const preloadAsset of preloadAssets || []) {
if (preloadAsset.type === AssetTypeEnum.Image) {
let img = new Image();
img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash);
preloadImageUrl(getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash));
}
}
};
@ -168,6 +168,7 @@
return () => {
loader?.removeEventListener('load', onload);
loader?.removeEventListener('error', onerror);
cancelImageUrl(imageLoaderUrl);
};
});

View file

@ -2,9 +2,11 @@
import { thumbhash } from '$lib/actions/thumbhash';
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { TUNABLES } from '$lib/utils/tunables';
import { mdiEyeOffOutline } from '@mdi/js';
import type { ClassValue } from 'svelte/elements';
import type { ActionReturn } from 'svelte/action';
import { fade } from 'svelte/transition';
interface Props {
@ -59,11 +61,14 @@
onComplete?.(true);
};
function mount(elem: HTMLImageElement) {
function mount(elem: HTMLImageElement): ActionReturn {
if (elem.complete) {
loaded = true;
onComplete?.(false);
}
return {
destroy: () => cancelImageUrl(url),
};
}
let optionalClasses = $derived(

View file

@ -0,0 +1,8 @@
const broadcast = new BroadcastChannel('immich');
export function cancelImageUrl(url: string) {
broadcast.postMessage({ type: 'cancel', url });
}
export function preloadImageUrl(url: string) {
broadcast.postMessage({ type: 'preload', url });
}