diff --git a/src/lib/components/common/PanzoomContainer.svelte b/src/lib/components/common/PanzoomContainer.svelte index fd386e7b35..48a5ab13eb 100644 --- a/src/lib/components/common/PanzoomContainer.svelte +++ b/src/lib/components/common/PanzoomContainer.svelte @@ -3,11 +3,11 @@ import panzoom, { type PanZoom, type PanZoomOptions } from 'panzoom'; const defaultOpts: PanZoomOptions = { - bounds: true, - boundsPadding: 0.1, - zoomSpeed: 0.085, - pinchSpeed: 2.25 -}; + bounds: true, + boundsPadding: 0.1, + zoomSpeed: 0.18, + pinchSpeed: 3.5 + }; export let className = ''; export let options: Partial = {}; diff --git a/src/lib/components/common/PptxPreview.svelte b/src/lib/components/common/PptxPreview.svelte index 6e25a6dfd3..7f80ca1517 100644 --- a/src/lib/components/common/PptxPreview.svelte +++ b/src/lib/components/common/PptxPreview.svelte @@ -19,7 +19,13 @@ let mounted = false; let initializedSlide = ''; let hideThumbs = false; + let wheelDelta = 0; + let lastWheelNavigationAt = 0; + let lastScrolledSlide = -1; + let thumbnailButtons: Array = []; const slideShortcutKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; + const wheelNavigationThreshold = 80; + const wheelNavigationCooldown = 450; $: safeSlide = Math.min(Math.max(0, currentSlide), Math.max(0, slides.length - 1)); $: selectedSlide = slides[safeSlide] ?? ''; @@ -35,7 +41,24 @@ const updateLayout = () => { hideThumbs = (rootEl?.clientWidth ?? window.innerWidth) < 720; - void tick().then(updateFitScale); + void tick().then(() => { + updateFitScale(); + scrollSelectedThumbnailIntoView(); + }); + }; + + const trackThumbnail = (node: HTMLButtonElement, index: number) => { + thumbnailButtons[index] = node; + return { + destroy: () => { + if (thumbnailButtons[index] === node) thumbnailButtons[index] = undefined; + } + }; + }; + + const scrollSelectedThumbnailIntoView = () => { + if (hideThumbs) return; + thumbnailButtons[safeSlide]?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); }; const initPanzoom = () => { @@ -47,10 +70,7 @@ boundsPadding: 0.1, zoomSpeed: 0.065, filterKey: (e?: KeyboardEvent) => !!e && slideShortcutKeys.includes(e.key), - beforeWheel: (e) => { - if (!e.ctrlKey && !e.metaKey) return true; - return false; - }, + beforeWheel: () => true, beforeMouseDown: () => { const transform = pzInstance?.getTransform(); return !!transform && Math.abs(transform.scale - 1) < 0.01; @@ -124,12 +144,23 @@ }; const handleStageWheel = (e: WheelEvent) => { - if (e.ctrlKey || e.metaKey || !pzInstance) return; - e.preventDefault(); + if (e.ctrlKey || e.metaKey || slides.length <= 1) return; + const multiplier = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? stageEl.clientHeight : 1; - pzInstance.moveBy(-e.deltaX * multiplier, -e.deltaY * multiplier, false); - zoomLevel = pzInstance.getTransform().scale; + const dominantDelta = Math.abs(e.deltaY) >= Math.abs(e.deltaX) ? e.deltaY : e.deltaX; + wheelDelta += dominantDelta * multiplier; + + const now = Date.now(); + if (Math.abs(wheelDelta) < wheelNavigationThreshold) return; + if (now - lastWheelNavigationAt < wheelNavigationCooldown) { + wheelDelta = 0; + return; + } + + lastWheelNavigationAt = now; + selectSlide(safeSlide + (wheelDelta > 0 ? 1 : -1)); + wheelDelta = 0; }; onMount(() => { @@ -148,6 +179,11 @@ }); } + $: if (mounted && safeSlide !== lastScrolledSlide) { + lastScrolledSlide = safeSlide; + void tick().then(scrollSelectedThumbnailIntoView); + } + onDestroy(() => { pzInstance?.dispose(); resizeObserver?.disconnect(); @@ -170,6 +206,7 @@ > {#each slides as slide, index}