mirror of
https://github.com/immich-app/immich.git
synced 2026-03-23 16:19:48 +03:00
add passive loading indicator to asset-viewer
This commit is contained in:
@@ -128,6 +128,10 @@
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
assetViewerManager.imageLoaderStatus = status;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') {
|
||||
untrack(() => void adaptiveImageLoader.trigger('original'));
|
||||
|
||||
46
web/src/lib/components/LoadingDots.svelte
Normal file
46
web/src/lib/components/LoadingDots.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
|
||||
interface Props {
|
||||
class?: ClassValue;
|
||||
}
|
||||
|
||||
let { class: className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="delayed inline-flex items-center gap-1 {className}">
|
||||
{#each [0, 1, 2] as i (i)}
|
||||
<span class="dot block size-1.5 rounded-full bg-white shadow-[0_0_3px_rgba(0,0,0,0.6)]" style:--delay="{i * 0.25}s"
|
||||
></span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.delayed {
|
||||
visibility: hidden;
|
||||
animation: delayed-visibility 0s linear 0.4s forwards;
|
||||
}
|
||||
|
||||
@keyframes delayed-visibility {
|
||||
to {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
animation: dot-stream 1.6s var(--delay, 0s) ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes dot-stream {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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 @@
|
||||
<ActionButton action={Close} />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 overflow-x-auto dark" data-testid="asset-viewer-navbar-actions">
|
||||
<div class="flex items-center gap-2 overflow-x-auto dark" data-testid="asset-viewer-navbar-actions">
|
||||
{#if assetViewerManager.isImageLoading}
|
||||
<Tooltip text={$t('loading')}>
|
||||
{#snippet child({ props })}
|
||||
<div {...props} role="status" aria-label={$t('loading')}>
|
||||
<LoadingDots class="me-1" />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<ActionButton action={Cast} />
|
||||
<ActionButton action={Actions.Share} />
|
||||
<ActionButton action={Actions.Offline} />
|
||||
|
||||
@@ -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<Events> {
|
||||
#zoomState = $state(createDefaultZoomState());
|
||||
|
||||
imgRef = $state<HTMLImageElement | undefined>();
|
||||
imageLoaderStatus = $state<ImageLoaderStatus | undefined>();
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user