summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline.tsx
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/components/editor/timeline.tsx
parent263f14ef2a83b0cc78189f764b0c1859fa392539 (diff)
downloadopencut-d6724e2552d14140017b194e7020252bda73751a.tar.gz
feat: init timeline zoom
Diffstat (limited to 'apps/web/src/components/editor/timeline.tsx')
-rw-r--r--apps/web/src/components/editor/timeline.tsx78
1 files changed, 56 insertions, 22 deletions
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx
index 9e68fd4d..84c228f0 100644
--- a/apps/web/src/components/editor/timeline.tsx
+++ b/apps/web/src/components/editor/timeline.tsx
@@ -56,6 +56,8 @@ import {
TIMELINE_CONSTANTS,
snapTimeToFrame,
} from "@/constants/timeline-constants";
+import { TimelineZoomControl } from "./timeline-zoom-control";
+import { useTimelineZoomActions } from "@/hooks/use-timeline-zoom-actions";
export function Timeline() {
// Timeline shows all tracks (video, audio, effects) and their elements.
@@ -69,6 +71,7 @@ export function Timeline() {
removeElementFromTrack,
removeElementFromTrackWithRipple,
getTotalDuration,
+ calculateFitToWindowZoom,
selectedElements,
clearSelectedElements,
setSelectedElements,
@@ -103,10 +106,20 @@ export function Timeline() {
downTime: 0,
});
+ // Fit to window function
+ const handleFitToWindow = useCallback(() => {
+ if (timelineRef.current) {
+ const containerWidth = timelineRef.current.clientWidth;
+ const optimalZoom = calculateFitToWindowZoom(containerWidth);
+ setZoomLevel(optimalZoom);
+ }
+ }, [calculateFitToWindowZoom]);
+
// Timeline zoom functionality
const { zoomLevel, setZoomLevel, handleWheel } = useTimelineZoom({
containerRef: timelineRef,
isInTimeline,
+ onFitToWindow: handleFitToWindow,
});
// Old marquee selection removed - using new SelectionBox component instead
@@ -141,6 +154,13 @@ export function Timeline() {
playheadRef,
});
+ // Zoom actions
+ useTimelineZoomActions({
+ zoomLevel,
+ setZoomLevel,
+ onFitToWindow: handleFitToWindow,
+ });
+
// Selection box functionality
const tracksContainerRef = useRef<HTMLDivElement>(null);
const {
@@ -836,6 +856,15 @@ export function Timeline() {
</TooltipContent>
</Tooltip>
</TooltipProvider>
+
+ {/* Timeline Zoom Control */}
+ <div className="w-px h-6 bg-border mx-2" />
+ <TimelineZoomControl
+ zoomLevel={zoomLevel}
+ onZoomChange={setZoomLevel}
+ onFitToWindow={handleFitToWindow}
+ className="mr-2"
+ />
</div>
</div>
@@ -880,21 +909,21 @@ export function Timeline() {
{/* Timeline Ruler */}
<div
- className="flex-1 relative overflow-hidden h-4"
+ className="flex-1 relative overflow-x-auto overflow-y-hidden h-4"
onWheel={handleWheel}
onMouseDown={handleSelectionMouseDown}
onClick={handleTimelineContentClick}
data-ruler-area
+ ref={rulerScrollRef}
>
- <ScrollArea className="w-full" ref={rulerScrollRef}>
- <div
- ref={rulerRef}
- className="relative h-4 select-none cursor-default"
- style={{
- width: `${dynamicTimelineWidth}px`,
- }}
- onMouseDown={handleRulerMouseDown}
- >
+ <div
+ ref={rulerRef}
+ className="relative h-4 select-none cursor-default"
+ style={{
+ width: `${dynamicTimelineWidth}px`,
+ }}
+ onMouseDown={handleRulerMouseDown}
+ >
{/* Time markers */}
{(() => {
// Calculate appropriate time interval based on zoom level
@@ -962,8 +991,7 @@ export function Timeline() {
);
}).filter(Boolean);
})()}
- </div>
- </ScrollArea>
+ </div>
</div>
</div>
@@ -976,7 +1004,10 @@ export function Timeline() {
className="w-48 flex-shrink-0 border-r border-black overflow-y-auto"
data-track-labels
>
- <ScrollArea className="w-full h-full" ref={trackLabelsScrollRef}>
+ <ScrollArea
+ ref={trackLabelsScrollRef}
+ className="w-full h-full"
+ >
<div className="flex flex-col gap-1">
{tracks.map((track) => (
<div
@@ -1001,7 +1032,7 @@ export function Timeline() {
{/* Timeline Tracks Content */}
<div
- className="flex-1 relative overflow-hidden"
+ className="flex-1 relative overflow-auto"
onWheel={handleWheel}
onMouseDown={(e) => {
handleTimelineMouseDown(e);
@@ -1010,13 +1041,16 @@ export function Timeline() {
onClick={handleTimelineContentClick}
ref={tracksContainerRef}
>
- <SelectionBox
- startPos={selectionBox?.startPos || null}
- currentPos={selectionBox?.currentPos || null}
- containerRef={tracksContainerRef}
- isActive={selectionBox?.isActive || false}
- />
- <ScrollArea className="w-full h-full" ref={tracksScrollRef}>
+ <div
+ ref={tracksScrollRef}
+ className="w-full h-full"
+ >
+ <SelectionBox
+ startPos={selectionBox?.startPos || null}
+ currentPos={selectionBox?.currentPos || null}
+ containerRef={tracksContainerRef}
+ isActive={selectionBox?.isActive || false}
+ />
<div
className="relative flex-1"
style={{
@@ -1070,7 +1104,7 @@ export function Timeline() {
</>
)}
</div>
- </ScrollArea>
+ </div>
</div>
</div>
</div>