Files
OpenCut/apps/web/src/components/editor/timeline/timeline-content.tsx
T
2025-07-17 01:59:39 +02:00

124 lines
3.9 KiB
TypeScript

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="min-w-max min-h-max" style={{ minWidth: `${dynamicTimelineWidth}px` }}>
<div className={`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 */}
<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>
);
}