summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks
diff options
context:
space:
mode:
authorleimingsheng <leimingsheng@58.com>2025-07-21 19:15:52 +0800
committerleimingsheng <leimingsheng@58.com>2025-07-21 19:15:52 +0800
commitd6724e2552d14140017b194e7020252bda73751a (patch)
tree0b0b400b7933e21ad911cb2366b1806ff2e35a64 /apps/web/src/hooks
parent263f14ef2a83b0cc78189f764b0c1859fa392539 (diff)
downloadopencut-d6724e2552d14140017b194e7020252bda73751a.tar.gz
feat: init timeline zoom
Diffstat (limited to 'apps/web/src/hooks')
-rw-r--r--apps/web/src/hooks/use-keyboard-shortcuts-help.ts4
-rw-r--r--apps/web/src/hooks/use-timeline-zoom-actions.ts37
-rw-r--r--apps/web/src/hooks/use-timeline-zoom.ts64
3 files changed, 103 insertions, 2 deletions
diff --git a/apps/web/src/hooks/use-keyboard-shortcuts-help.ts b/apps/web/src/hooks/use-keyboard-shortcuts-help.ts
index c7de8466..d0f177f6 100644
--- a/apps/web/src/hooks/use-keyboard-shortcuts-help.ts
+++ b/apps/web/src/hooks/use-keyboard-shortcuts-help.ts
@@ -60,6 +60,10 @@ const actionDescriptions: Record<
category: "Selection",
},
"toggle-snapping": { description: "Toggle snapping", category: "Editing" },
+ "zoom-in": { description: "Zoom in timeline", category: "View" },
+ "zoom-out": { description: "Zoom out timeline", category: "View" },
+ "zoom-reset": { description: "Reset timeline zoom", category: "View" },
+ "zoom-fit": { description: "Fit timeline to window", category: "View" },
undo: { description: "Undo", category: "History" },
redo: { description: "Redo", category: "History" },
};
diff --git a/apps/web/src/hooks/use-timeline-zoom-actions.ts b/apps/web/src/hooks/use-timeline-zoom-actions.ts
new file mode 100644
index 00000000..85003684
--- /dev/null
+++ b/apps/web/src/hooks/use-timeline-zoom-actions.ts
@@ -0,0 +1,37 @@
+"use client";
+
+import { useActionHandler } from "@/constants/actions";
+
+interface UseTimelineZoomActionsProps {
+ zoomLevel: number;
+ setZoomLevel: (zoomLevel: number | ((prev: number) => number)) => void;
+ onFitToWindow?: () => void;
+}
+
+export function useTimelineZoomActions({
+ zoomLevel,
+ setZoomLevel,
+ onFitToWindow,
+}: UseTimelineZoomActionsProps) {
+ // Zoom in action
+ useActionHandler("zoom-in", () => {
+ setZoomLevel((prev) => Math.min(5, prev + 0.2));
+ });
+
+ // Zoom out action
+ useActionHandler("zoom-out", () => {
+ setZoomLevel((prev) => Math.max(0.1, prev - 0.2));
+ });
+
+ // Reset zoom action
+ useActionHandler("zoom-reset", () => {
+ setZoomLevel(1);
+ });
+
+ // Fit to window action
+ useActionHandler("zoom-fit", () => {
+ if (onFitToWindow) {
+ onFitToWindow();
+ }
+ });
+}
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<HTMLDivElement>;
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) => {