diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index fc8f3346b5..92e3fad2d3 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -128,6 +128,10 @@ }; }); + $effect(() => { + assetViewerManager.imageLoaderStatus = status; + }); + $effect(() => { if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { untrack(() => void adaptiveImageLoader.trigger('original')); diff --git a/web/src/lib/components/LoadingDots.svelte b/web/src/lib/components/LoadingDots.svelte new file mode 100644 index 0000000000..3dcfcb8122 --- /dev/null +++ b/web/src/lib/components/LoadingDots.svelte @@ -0,0 +1,46 @@ + + +
+ {#each [0, 1, 2] as i (i)} + + {/each} +
+ + diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index bb52c71260..3ccadf944f 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -34,7 +34,9 @@ type PersonResponseDto, type StackResponseDto, } from '@immich/sdk'; - import { ActionButton, CommandPaletteDefaultProvider, type ActionItem } from '@immich/ui'; + import { ActionButton, CommandPaletteDefaultProvider, Tooltip, type ActionItem } from '@immich/ui'; + import LoadingDots from '$lib/components/LoadingDots.svelte'; + import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { mdiArrowLeft, mdiArrowRight, @@ -104,7 +106,16 @@ -
+
+ {#if assetViewerManager.isImageLoading} + + {#snippet child({ props })} +
+ +
+ {/snippet} +
+ {/if} diff --git a/web/src/lib/managers/asset-viewer-manager.svelte.ts b/web/src/lib/managers/asset-viewer-manager.svelte.ts index 36047d4690..c815421e2c 100644 --- a/web/src/lib/managers/asset-viewer-manager.svelte.ts +++ b/web/src/lib/managers/asset-viewer-manager.svelte.ts @@ -1,3 +1,4 @@ +import type { ImageLoaderStatus } from '$lib/utils/adaptive-image-loader.svelte'; import { canCopyImageToClipboard } from '$lib/utils/asset-utils'; import { BaseEventManager } from '$lib/utils/base-event-manager.svelte'; import { PersistedLocalStorage } from '$lib/utils/persisted'; @@ -23,10 +24,24 @@ export class AssetViewerManager extends BaseEventManager { #zoomState = $state(createDefaultZoomState()); imgRef = $state(); + imageLoaderStatus = $state(); + #isImageLoading = $derived.by(() => { + const quality = this.imageLoaderStatus?.quality; + if (!quality) { + return false; + } + const previewOrOriginalReady = quality.preview === 'success' || quality.original === 'success'; + const loadingOriginal = this.zoom > 1 && quality.original !== 'success'; + return !previewOrOriginalReady || loadingOriginal; + }); isShowActivityPanel = $state(false); isPlayingMotionPhoto = $state(false); isShowEditor = $state(false); + get isImageLoading() { + return this.#isImageLoading; + } + get isShowDetailPanel() { return isShowDetailPanel.current; }