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
117
118
119
120
121
122
123
124
125
126
127
|
import { SelectionBox } from "../selection-box";
import { TimelinePlayhead } from "./timeline-playhead";
import { SnapIndicator } from "../snap-indicator";
import { TimelineRuler } from "./timeline-ruler";
import { TimelineTracksArea } from "./timeline-tracks-area";
import type { TimelineTrack } from "@/types/timeline";
import type { SnapPoint } from "@/hooks/use-timeline-snapping";
export interface TimelineContentProps {
dynamicTimelineWidth: number;
tracks: TimelineTrack[];
duration: number;
zoomLevel: number;
currentTime: number;
seek: (time: number) => void;
rulerRef: React.RefObject<HTMLDivElement>;
rulerScrollRef: React.RefObject<HTMLDivElement>;
tracksScrollRef: React.RefObject<HTMLDivElement>;
playheadRef: React.RefObject<HTMLDivElement>;
trackLabelsRef: React.RefObject<HTMLDivElement>;
timelineRef: React.RefObject<HTMLDivElement>;
handleSelectionMouseDown: (e: React.MouseEvent) => void;
handleTimelineContentClick: (e: React.MouseEvent) => void;
handleSnapPointChange: (snapPoint: SnapPoint | null) => void;
clearSelectedElements: () => void;
selectionBox: {
startPos: { x: number; y: number } | null;
currentPos: { x: number; y: number } | null;
isActive: boolean;
} | null;
tracksContainerRef: React.RefObject<HTMLDivElement>;
currentSnapPoint: SnapPoint | null;
showSnapIndicator: boolean;
}
export function TimelineContent({
dynamicTimelineWidth,
tracks,
duration,
zoomLevel,
currentTime,
seek,
rulerRef,
rulerScrollRef,
tracksScrollRef,
playheadRef,
trackLabelsRef,
timelineRef,
handleSelectionMouseDown,
handleTimelineContentClick,
handleSnapPointChange,
clearSelectedElements,
selectionBox,
tracksContainerRef,
currentSnapPoint,
showSnapIndicator,
}: TimelineContentProps) {
return (
<div
className="relative min-w-max min-h-max"
style={{ minWidth: `${dynamicTimelineWidth}px` }}
>
<div
className={`h-full grid grid-cols-[192px_1fr] ${tracks.length > 0 ? `grid-rows-[20px_repeat(${tracks.length},minmax(0,max-content))]` : "grid-rows-[20px_1fr]"}`}
>
{/* Top-Left Corner (Empty space above track labels) */}
<div className="sticky top-0 left-0 border-inset bg-card/[0.99]"></div>
{/* Top Row (Sticky Ruler Header) */}
<TimelineRuler
duration={duration}
zoomLevel={zoomLevel}
currentTime={currentTime}
seek={seek}
rulerRef={rulerRef}
rulerScrollRef={rulerScrollRef}
tracksScrollRef={tracksScrollRef}
playheadRef={playheadRef}
handleSelectionMouseDown={handleSelectionMouseDown}
handleTimelineContentClick={handleTimelineContentClick}
/>
{/* Track Rows */}
<TimelineTracksArea
tracks={tracks}
zoomLevel={zoomLevel}
handleSnapPointChange={handleSnapPointChange}
clearSelectedElements={clearSelectedElements}
/>
</div>
{/* Overlay Components - positioned absolutely relative to the timeline container */}
<SelectionBox
startPos={selectionBox?.startPos || null}
currentPos={selectionBox?.currentPos || null}
containerRef={tracksContainerRef}
isActive={selectionBox?.isActive || false}
/>
<TimelinePlayhead
currentTime={currentTime}
duration={duration}
zoomLevel={zoomLevel}
tracks={tracks}
seek={seek}
rulerRef={rulerRef}
rulerScrollRef={rulerScrollRef}
tracksScrollRef={tracksScrollRef}
trackLabelsRef={trackLabelsRef}
timelineRef={timelineRef}
playheadRef={playheadRef}
isSnappingToPlayhead={
showSnapIndicator && currentSnapPoint?.type === "playhead"
}
/>
<SnapIndicator
snapPoint={currentSnapPoint}
zoomLevel={zoomLevel}
tracks={tracks}
timelineRef={timelineRef}
trackLabelsRef={trackLabelsRef}
isVisible={showSnapIndicator}
/>
</div>
);
}
|