From 991541826020f166b19633db0dbf572ef97a75b3 Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 30 Jul 2026 10:19:13 -0600 Subject: [PATCH 1/8] Add Slideshow Settings for Animate switch and Pan / Zoom Strength --- i18n/en.json | 3 +++ .../lib/modals/SlideshowSettingsModal.svelte | 19 +++++++++++++++++++ web/src/lib/stores/slideshow.store.ts | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/i18n/en.json b/i18n/en.json index 1dbd50e294..c555706ba8 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2022,6 +2022,9 @@ "skip_to_folders": "Skip to folders", "skip_to_tags": "Skip to tags", "slideshow": "Slideshow", + "slideshow_animate": "Animate slideshow", + "slideshow_animate_pan_strength": "Pan strength percentage", + "slideshow_animate_zoom_strength": "Zoom strength percentage", "slideshow_body": "Sit back and watch your photos play in a full-screen slideshow.", "slideshow_metadata_overlay_mode": "Overlay content", "slideshow_metadata_overlay_mode_description_only": "Description only", diff --git a/web/src/lib/modals/SlideshowSettingsModal.svelte b/web/src/lib/modals/SlideshowSettingsModal.svelte index 6540f63124..f355bdda3b 100644 --- a/web/src/lib/modals/SlideshowSettingsModal.svelte +++ b/web/src/lib/modals/SlideshowSettingsModal.svelte @@ -22,6 +22,9 @@ const { slideshowDelay, showProgressBar, + slideshowAnimate, + slideshowAnimatePanStrength, + slideshowAnimateZoomStrength, slideshowNavigation, slideshowLook, slideshowTransition, @@ -41,6 +44,9 @@ // Temporary variables to hold the settings - marked as reactive with $state() but initialized with store values let tempSlideshowDelay = $state($slideshowDelay); let tempShowProgressBar = $state($showProgressBar); + let tempSlideshowAnimate = $state($slideshowAnimate); + let tempSlideshowAnimatePanStrength = $state($slideshowAnimatePanStrength); + let tempSlideshowAnimateZoomStrength = $state($slideshowAnimateZoomStrength); let tempSlideshowNavigation = $state($slideshowNavigation); let tempSlideshowLook = $state($slideshowLook); let tempSlideshowTransition = $state($slideshowTransition); @@ -84,6 +90,9 @@ const onSubmit = () => { $slideshowDelay = tempSlideshowDelay; $showProgressBar = tempShowProgressBar; + $slideshowAnimate = tempSlideshowAnimate; + $slideshowAnimatePanStrength = tempSlideshowAnimatePanStrength; + $slideshowAnimateZoomStrength = tempSlideshowAnimateZoomStrength; $slideshowNavigation = tempSlideshowNavigation; $slideshowLook = tempSlideshowLook; $slideshowTransition = tempSlideshowTransition; @@ -151,5 +160,15 @@ {$t('admin.slideshow_duration_description')} + + + + + + + + + + diff --git a/web/src/lib/stores/slideshow.store.ts b/web/src/lib/stores/slideshow.store.ts index 17ba266ee1..e6417b53ee 100644 --- a/web/src/lib/stores/slideshow.store.ts +++ b/web/src/lib/stores/slideshow.store.ts @@ -42,6 +42,9 @@ function createSlideshowStore() { const slideshowState = writable(SlideshowState.None); const showProgressBar = persisted('slideshow-show-progressbar', true); + const slideshowAnimate = persisted('slideshow-animate', false); + const slideshowAnimatePanStrength = persisted('slideshow-animate-pan-strength', 50, {}); + const slideshowAnimateZoomStrength = persisted('slideshow-animate-zoom-strength', 50, {}); const slideshowDelay = persisted('slideshow-delay', 5, {}); const slideshowTransition = persisted('slideshow-transition', true); const slideshowAutoplay = persisted('slideshow-autoplay', true, {}); @@ -89,6 +92,9 @@ function createSlideshowStore() { slideshowRepeat, slideshowShowMetadataOverlay, slideshowMetadataOverlayMode, + slideshowAnimate, + slideshowAnimatePanStrength, + slideshowAnimateZoomStrength, }; } From 211d7cbd8a2cbdc724a5b0a0e869c4f16fec93c2 Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Fri, 31 Jul 2026 12:16:11 -0600 Subject: [PATCH 2/8] WIP: Add basic zoom feature to slideshow. Iin PhotoViewer onImageReady function call assetViewerManager animatedZoom with random scale values. Disable AdaptiveImage effect in show, which called for loading original every animation frame. --- web/src/lib/components/AdaptiveImage.svelte | 15 ++++---- .../asset-viewer/PhotoViewer.svelte | 37 ++++++++++++++++++- .../lib/utils/adaptive-image-loader.svelte.ts | 4 +- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index 89b10b2d77..26516bd13b 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -57,7 +57,7 @@ import Thumbhash from '$lib/components/Thumbhash.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { getAssetUrls } from '$lib/utils'; - import { AdaptiveImageLoader, type QualityList } from '$lib/utils/adaptive-image-loader.svelte'; + import { AdaptiveImageLoader, type ImageQuality, type QualityList } from '$lib/utils/adaptive-image-loader.svelte'; import { scaleToCover, scaleToFit, type Size } from '$lib/utils/container-utils'; import { getAltText } from '$lib/utils/thumbnail-util'; import { toTimelineAsset } from '$lib/utils/timeline-util'; @@ -71,7 +71,7 @@ objectFit?: 'contain' | 'cover'; container: Size; onUrlChange?: (url: string) => void; - onImageReady?: () => void; + onImageReady?: (quality: ImageQuality) => void; onError?: () => void; ref?: HTMLDivElement; imgRef?: HTMLImageElement; @@ -200,11 +200,12 @@ assetViewerManager.imageLoaderStatus = status; }); - $effect(() => { - if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { - untrack(() => void adaptiveImageLoader.trigger('original')); - } - }); + // $effect(() => { + // if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { + // //console.log('effect trigger original'); + // //untrack(() => void adaptiveImageLoader.trigger('original')); + // } + // }); let thumbnailElement = $state(); let previewElement = $state(); diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index c0af733f3f..b3868992d4 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -23,6 +23,7 @@ import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures'; import { t } from 'svelte-i18n'; import type { AssetCursor } from './AssetViewer.svelte'; + import { type ImageQuality } from '$lib/utils/adaptive-image-loader.svelte'; type Props = { cursor: AssetCursor; @@ -35,7 +36,14 @@ let { cursor, element = $bindable(), sharedLink, onReady, onError, onSwipe }: Props = $props(); - const { slideshowState, slideshowLook } = slideshowStore; + const { + slideshowState, + slideshowLook, + slideshowAnimate, + slideshowDelay, + //slideshowAnimatePanStrength, + slideshowAnimateZoomStrength, + } = slideshowStore; const asset = $derived(cursor.current); let visibleImageReady: boolean = $state(false); @@ -198,6 +206,12 @@ return result; }); + + function getRandomInt(min: number, max: number): number { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max + 1 - min)) + min; + } @@ -227,8 +241,27 @@ {container} objectFit={$slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.Cover ? 'cover' : 'contain'} {onUrlChange} - onImageReady={() => { + onImageReady={(quality: ImageQuality) => { visibleImageReady = true; + + console.log('onImageReady: ' + quality); + if (quality === 'thumbnail' && $slideshowState === SlideshowState.PlaySlideshow && $slideshowAnimate) { + let randomDirection = getRandomInt(0, 2); + let randomScale = (getRandomInt(50, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; + let duration = $slideshowDelay * 1000 - 600; + console.group('asset: ' + asset.id); + console.log('Direction: ' + randomDirection); + console.log('Scale: ' + randomScale); + console.groupEnd(); + if (randomDirection == 1) { + assetViewerManager.animatedZoom(randomScale, duration); + } else if (randomDirection == 2) { + assetViewerManager.animatedZoom(randomScale, 20); + setTimeout(() => { + assetViewerManager.animatedZoom(1, duration - 40); + }, 40); + } + } onReady?.(); }} onError={() => { diff --git a/web/src/lib/utils/adaptive-image-loader.svelte.ts b/web/src/lib/utils/adaptive-image-loader.svelte.ts index c82d17ec98..c8b7201056 100644 --- a/web/src/lib/utils/adaptive-image-loader.svelte.ts +++ b/web/src/lib/utils/adaptive-image-loader.svelte.ts @@ -14,7 +14,7 @@ export type ImageLoaderStatus = { type ImageLoaderCallbacks = { onUrlChange?: (url: string) => void; - onImageReady?: () => void; + onImageReady?: (quality: ImageQuality) => void; onError?: () => void; }; @@ -99,7 +99,7 @@ export class AdaptiveImageLoader { this.highestLoadedQualityIndex = index; this.status.quality[quality] = 'success'; this.callbacks?.onUrlChange?.(this.qualityConfigs[quality].url); - this.callbacks?.onImageReady?.(); + this.callbacks?.onImageReady?.(quality); config.onAfterLoad?.(this); } From e128fb204a3e0177bca81a75a13dda04cfd3021f Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 13:24:12 -0600 Subject: [PATCH 3/8] Remove console.log statements --- web/src/lib/components/AdaptiveImage.svelte | 1 - web/src/lib/components/asset-viewer/PhotoViewer.svelte | 5 ----- 2 files changed, 6 deletions(-) diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index 26516bd13b..e46f433c5a 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -202,7 +202,6 @@ // $effect(() => { // if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { - // //console.log('effect trigger original'); // //untrack(() => void adaptiveImageLoader.trigger('original')); // } // }); diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index b3868992d4..11144d8f95 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -244,15 +244,10 @@ onImageReady={(quality: ImageQuality) => { visibleImageReady = true; - console.log('onImageReady: ' + quality); if (quality === 'thumbnail' && $slideshowState === SlideshowState.PlaySlideshow && $slideshowAnimate) { let randomDirection = getRandomInt(0, 2); let randomScale = (getRandomInt(50, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; let duration = $slideshowDelay * 1000 - 600; - console.group('asset: ' + asset.id); - console.log('Direction: ' + randomDirection); - console.log('Scale: ' + randomScale); - console.groupEnd(); if (randomDirection == 1) { assetViewerManager.animatedZoom(randomScale, duration); } else if (randomDirection == 2) { From 2232e9798aabdccfcb02f2d0d90836402c429901 Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 13:19:12 -0600 Subject: [PATCH 4/8] Remove slideshowAnimatePanStrength option from code. --- i18n/en.json | 1 - web/src/lib/components/asset-viewer/PhotoViewer.svelte | 10 ++-------- web/src/lib/modals/SlideshowSettingsModal.svelte | 6 ------ web/src/lib/stores/slideshow.store.ts | 2 -- 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index c555706ba8..7710bd1686 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2023,7 +2023,6 @@ "skip_to_tags": "Skip to tags", "slideshow": "Slideshow", "slideshow_animate": "Animate slideshow", - "slideshow_animate_pan_strength": "Pan strength percentage", "slideshow_animate_zoom_strength": "Zoom strength percentage", "slideshow_body": "Sit back and watch your photos play in a full-screen slideshow.", "slideshow_metadata_overlay_mode": "Overlay content", diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index 11144d8f95..f09572b00a 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -36,14 +36,8 @@ let { cursor, element = $bindable(), sharedLink, onReady, onError, onSwipe }: Props = $props(); - const { - slideshowState, - slideshowLook, - slideshowAnimate, - slideshowDelay, - //slideshowAnimatePanStrength, - slideshowAnimateZoomStrength, - } = slideshowStore; + const { slideshowState, slideshowLook, slideshowAnimate, slideshowDelay, slideshowAnimateZoomStrength } = + slideshowStore; const asset = $derived(cursor.current); let visibleImageReady: boolean = $state(false); diff --git a/web/src/lib/modals/SlideshowSettingsModal.svelte b/web/src/lib/modals/SlideshowSettingsModal.svelte index f355bdda3b..79612a2211 100644 --- a/web/src/lib/modals/SlideshowSettingsModal.svelte +++ b/web/src/lib/modals/SlideshowSettingsModal.svelte @@ -23,7 +23,6 @@ slideshowDelay, showProgressBar, slideshowAnimate, - slideshowAnimatePanStrength, slideshowAnimateZoomStrength, slideshowNavigation, slideshowLook, @@ -45,7 +44,6 @@ let tempSlideshowDelay = $state($slideshowDelay); let tempShowProgressBar = $state($showProgressBar); let tempSlideshowAnimate = $state($slideshowAnimate); - let tempSlideshowAnimatePanStrength = $state($slideshowAnimatePanStrength); let tempSlideshowAnimateZoomStrength = $state($slideshowAnimateZoomStrength); let tempSlideshowNavigation = $state($slideshowNavigation); let tempSlideshowLook = $state($slideshowLook); @@ -91,7 +89,6 @@ $slideshowDelay = tempSlideshowDelay; $showProgressBar = tempShowProgressBar; $slideshowAnimate = tempSlideshowAnimate; - $slideshowAnimatePanStrength = tempSlideshowAnimatePanStrength; $slideshowAnimateZoomStrength = tempSlideshowAnimateZoomStrength; $slideshowNavigation = tempSlideshowNavigation; $slideshowLook = tempSlideshowLook; @@ -164,9 +161,6 @@ - - - diff --git a/web/src/lib/stores/slideshow.store.ts b/web/src/lib/stores/slideshow.store.ts index e6417b53ee..61352f3be3 100644 --- a/web/src/lib/stores/slideshow.store.ts +++ b/web/src/lib/stores/slideshow.store.ts @@ -43,7 +43,6 @@ function createSlideshowStore() { const showProgressBar = persisted('slideshow-show-progressbar', true); const slideshowAnimate = persisted('slideshow-animate', false); - const slideshowAnimatePanStrength = persisted('slideshow-animate-pan-strength', 50, {}); const slideshowAnimateZoomStrength = persisted('slideshow-animate-zoom-strength', 50, {}); const slideshowDelay = persisted('slideshow-delay', 5, {}); const slideshowTransition = persisted('slideshow-transition', true); @@ -93,7 +92,6 @@ function createSlideshowStore() { slideshowShowMetadataOverlay, slideshowMetadataOverlayMode, slideshowAnimate, - slideshowAnimatePanStrength, slideshowAnimateZoomStrength, }; } From 5e08497de65013ae44c072bd0cbe92d34da50f1b Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 13:36:59 -0600 Subject: [PATCH 5/8] Re-enable AdaptiveImage effect triggering original download --- web/src/lib/components/AdaptiveImage.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index e46f433c5a..a78b75b662 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -200,11 +200,11 @@ assetViewerManager.imageLoaderStatus = status; }); - // $effect(() => { - // if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { - // //untrack(() => void adaptiveImageLoader.trigger('original')); - // } - // }); + $effect(() => { + if (assetViewerManager.zoom > 1 && status.quality.original !== 'success') { + untrack(() => void adaptiveImageLoader.trigger('original')); + } + }); let thumbnailElement = $state(); let previewElement = $state(); From 4bc9a20500e12a1d84b2c369a94f0a6f0686c5bd Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 13:43:54 -0600 Subject: [PATCH 6/8] Extract new function in PhotoViewer, animateSlideshow from onImageReady code. --- .../asset-viewer/PhotoViewer.svelte | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index f09572b00a..086b253da1 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -115,6 +115,20 @@ const onPlaySlideshow = () => ($slideshowState = SlideshowState.PlaySlideshow); + const animateSlideshow = () => { + let randomDirection = getRandomInt(0, 2); + let randomScale = (getRandomInt(50, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; + let duration = $slideshowDelay * 1000 - 600; + if (randomDirection == 1) { + assetViewerManager.animatedZoom(randomScale, duration); + } else if (randomDirection == 2) { + assetViewerManager.animatedZoom(randomScale, 20); + setTimeout(() => { + assetViewerManager.animatedZoom(1, duration - 40); + }, 40); + } + }; + // TODO move to action + command palette const onCopyShortcut = (event: KeyboardEvent) => { // eslint-disable-next-line unicorn/no-unnecessary-global-this @@ -239,17 +253,7 @@ visibleImageReady = true; if (quality === 'thumbnail' && $slideshowState === SlideshowState.PlaySlideshow && $slideshowAnimate) { - let randomDirection = getRandomInt(0, 2); - let randomScale = (getRandomInt(50, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; - let duration = $slideshowDelay * 1000 - 600; - if (randomDirection == 1) { - assetViewerManager.animatedZoom(randomScale, duration); - } else if (randomDirection == 2) { - assetViewerManager.animatedZoom(randomScale, 20); - setTimeout(() => { - assetViewerManager.animatedZoom(1, duration - 40); - }, 40); - } + animateSlideshow(); } onReady?.(); }} From c4628c91124a5dc2cccc049676c9ed0d8dd507ab Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 14:20:10 -0600 Subject: [PATCH 7/8] Update animateSlideshow to animate 10% no motion, 20% zoom in from 1x, 70% random between 2 scales. Random duration between 50% - 100% of animation duration. --- .../asset-viewer/PhotoViewer.svelte | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index 086b253da1..fcbd9e4d74 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -116,15 +116,20 @@ const onPlaySlideshow = () => ($slideshowState = SlideshowState.PlaySlideshow); const animateSlideshow = () => { - let randomDirection = getRandomInt(0, 2); - let randomScale = (getRandomInt(50, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; + let randomDirection = getRandomInt(0, 9); + if (randomDirection == 0) { + return; + } + let randomScale1 = (getRandomInt(0, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; let duration = $slideshowDelay * 1000 - 600; - if (randomDirection == 1) { - assetViewerManager.animatedZoom(randomScale, duration); - } else if (randomDirection == 2) { - assetViewerManager.animatedZoom(randomScale, 20); + let randomDuration = Math.round(duration * (getRandomInt(50, 100) / 100)); + let randomScale2 = (getRandomInt(0, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; + if (randomDirection <= 2) { + assetViewerManager.animatedZoom(randomScale1, randomDuration); + } else if (randomDirection > 2) { + assetViewerManager.animatedZoom(randomScale1, 20); setTimeout(() => { - assetViewerManager.animatedZoom(1, duration - 40); + assetViewerManager.animatedZoom(randomScale2, randomDuration - 40); }, 40); } }; From 62d7d966be6ffa7eb658f1740cd3a2be3386c4ce Mon Sep 17 00:00:00 2001 From: Hartley Raabis Date: Thu, 6 Aug 2026 15:04:11 -0600 Subject: [PATCH 8/8] Add easeInOut parameter to animatedZoom. Apply quadInOut easing to zoom for PhotoViewer animate slideshow. --- web/src/lib/components/asset-viewer/PhotoViewer.svelte | 4 ++-- web/src/lib/managers/asset-viewer-manager.svelte.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index fcbd9e4d74..3379e7dfbc 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -125,11 +125,11 @@ let randomDuration = Math.round(duration * (getRandomInt(50, 100) / 100)); let randomScale2 = (getRandomInt(0, 200) / 100) * ($slideshowAnimateZoomStrength / 100) + 1; if (randomDirection <= 2) { - assetViewerManager.animatedZoom(randomScale1, randomDuration); + assetViewerManager.animatedZoom(randomScale1, randomDuration, true); } else if (randomDirection > 2) { assetViewerManager.animatedZoom(randomScale1, 20); setTimeout(() => { - assetViewerManager.animatedZoom(randomScale2, randomDuration - 40); + assetViewerManager.animatedZoom(randomScale2, randomDuration - 40, true); }, 40); } }; diff --git a/web/src/lib/managers/asset-viewer-manager.svelte.ts b/web/src/lib/managers/asset-viewer-manager.svelte.ts index 94a6d00700..326ae7ed6b 100644 --- a/web/src/lib/managers/asset-viewer-manager.svelte.ts +++ b/web/src/lib/managers/asset-viewer-manager.svelte.ts @@ -1,6 +1,6 @@ import { getAssetInfo, type AssetResponseDto } from '@immich/sdk'; import type { ZoomImageWheelState } from '@zoom-image/core'; -import { cubicOut } from 'svelte/easing'; +import { cubicOut, quadInOut } from 'svelte/easing'; import { authManager } from '$lib/managers/auth-manager.svelte'; import type { ImageLoaderStatus } from '$lib/utils/adaptive-image-loader.svelte'; import { canCopyImageToClipboard } from '$lib/utils/asset-utils'; @@ -142,7 +142,7 @@ class AssetViewerManager extends BaseEventManager { this.#animationFrameId = null; } - animatedZoom(targetZoom: number, duration = 300) { + animatedZoom(targetZoom: number, duration = 300, easeInOut = false) { this.cancelZoomAnimation(); const startZoom = this.#zoomState.currentZoom; @@ -151,7 +151,7 @@ class AssetViewerManager extends BaseEventManager { const frame = (currentTime: number) => { const elapsed = currentTime - startTime; const linearProgress = Math.min(elapsed / duration, 1); - const easedProgress = cubicOut(linearProgress); + const easedProgress = easeInOut ? quadInOut(linearProgress) : cubicOut(linearProgress); const interpolatedZoom = startZoom + (targetZoom - startZoom) * easedProgress; this.zoomState = { ...this.#zoomState, currentZoom: interpolatedZoom };