From d6724e2552d14140017b194e7020252bda73751a Mon Sep 17 00:00:00 2001 From: leimingsheng Date: Mon, 21 Jul 2025 19:15:52 +0800 Subject: feat: init timeline zoom --- apps/web/src/hooks/use-timeline-zoom.ts | 64 +++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) (limited to 'apps/web/src/hooks/use-timeline-zoom.ts') diff --git a/apps/web/src/hooks/use-timeline-zoom.ts b/apps/web/src/hooks/use-timeline-zoom.ts index ce32c9f0..8d41d917 100644 --- a/apps/web/src/hooks/use-timeline-zoom.ts +++ b/apps/web/src/hooks/use-timeline-zoom.ts @@ -3,6 +3,7 @@ import { useState, useCallback, useEffect, RefObject } from "react"; interface UseTimelineZoomProps { containerRef: RefObject; isInTimeline?: boolean; + onFitToWindow?: () => void; } interface UseTimelineZoomReturn { @@ -11,22 +12,81 @@ interface UseTimelineZoomReturn { handleWheel: (e: React.WheelEvent) => void; } +const ZOOM_STORAGE_KEY = 'opencut-timeline-zoom'; + export function useTimelineZoom({ containerRef, isInTimeline = false, + onFitToWindow, }: UseTimelineZoomProps): UseTimelineZoomReturn { - const [zoomLevel, setZoomLevel] = useState(1); + // Load initial zoom level from localStorage + const [zoomLevel, setZoomLevel] = useState(() => { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem(ZOOM_STORAGE_KEY); + if (stored) { + const parsed = parseFloat(stored); + if (!isNaN(parsed) && parsed >= 0.1 && parsed <= 5) { + return parsed; + } + } + } + return 1; + }); + + // Save zoom level to localStorage whenever it changes + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem(ZOOM_STORAGE_KEY, zoomLevel.toString()); + } + }, [zoomLevel]); const handleWheel = useCallback((e: React.WheelEvent) => { // Only zoom if user is using pinch gesture (ctrlKey or metaKey is true) if (e.ctrlKey || e.metaKey) { e.preventDefault(); const delta = e.deltaY > 0 ? -0.15 : 0.15; - setZoomLevel((prev) => Math.max(0.1, Math.min(10, prev + delta))); + setZoomLevel((prev) => Math.max(0.1, Math.min(5, prev + delta))); } // Otherwise, allow normal scrolling }, []); + // Keyboard shortcuts for zoom + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Only handle zoom shortcuts when in timeline or when timeline is focused + if (!isInTimeline) return; + + if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { + switch (e.key) { + case '=': + case '+': + e.preventDefault(); + setZoomLevel((prev) => Math.min(5, prev + 0.2)); + break; + case '-': + e.preventDefault(); + setZoomLevel((prev) => Math.max(0.1, prev - 0.2)); + break; + case '0': + e.preventDefault(); + setZoomLevel(1); + break; + case '9': + e.preventDefault(); + if (onFitToWindow) { + onFitToWindow(); + } + break; + } + } + }; + + if (isInTimeline) { + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + } + }, [isInTimeline, setZoomLevel]); + // Prevent browser zooming in/out when in timeline useEffect(() => { const preventZoom = (e: WheelEvent) => { -- cgit v1.3.1