1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
import { useState, useCallback, useEffect, RefObject } from "react";
interface UseTimelineZoomProps {
containerRef: RefObject<HTMLDivElement>;
isInTimeline?: boolean;
onFitToWindow?: () => void;
}
interface UseTimelineZoomReturn {
zoomLevel: number;
setZoomLevel: (zoomLevel: number | ((prev: number) => number)) => void;
handleWheel: (e: React.WheelEvent) => void;
}
const ZOOM_STORAGE_KEY = 'opencut-timeline-zoom';
const MIN_ZOOM = 0.5; // Minimum zoom to keep element borders visible
const MAX_ZOOM = 5;
export function useTimelineZoom({
containerRef,
isInTimeline = false,
onFitToWindow,
}: UseTimelineZoomProps): UseTimelineZoomReturn {
// 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 >= MIN_ZOOM && parsed <= MAX_ZOOM) {
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(MIN_ZOOM, Math.min(MAX_ZOOM, 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(MAX_ZOOM, prev + 0.2));
break;
case '-':
e.preventDefault();
setZoomLevel((prev) => Math.max(MIN_ZOOM, 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) => {
if (
isInTimeline &&
(e.ctrlKey || e.metaKey) &&
containerRef.current?.contains(e.target as Node)
) {
e.preventDefault();
}
};
document.addEventListener("wheel", preventZoom, { passive: false });
return () => {
document.removeEventListener("wheel", preventZoom);
};
}, [isInTimeline, containerRef]);
return {
zoomLevel,
setZoomLevel,
handleWheel,
};
}
|