130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useElementSelection } from "@/hooks/timeline/element/use-element-selection";
|
|
import { TimelineElement } from "./timeline-element";
|
|
import type { TimelineTrack } from "@/lib/timeline";
|
|
import type { TimelineElement as TimelineElementType } from "@/lib/timeline";
|
|
import type { SnapPoint } from "@/lib/timeline/snap-utils";
|
|
import { TIMELINE_LAYERS } from "./layers";
|
|
import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale";
|
|
import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll";
|
|
import type { ElementDragState } from "@/lib/timeline";
|
|
import { useEditor } from "@/hooks/use-editor";
|
|
|
|
interface TimelineTrackContentProps {
|
|
track: TimelineTrack;
|
|
zoomLevel: number;
|
|
dragState: ElementDragState;
|
|
rulerScrollRef: React.RefObject<HTMLDivElement | null>;
|
|
tracksScrollRef: React.RefObject<HTMLDivElement | null>;
|
|
lastMouseXRef: React.RefObject<number>;
|
|
onSnapPointChange?: (snapPoint: SnapPoint | null) => void;
|
|
onResizeStateChange?: (params: { isResizing: boolean }) => void;
|
|
onElementMouseDown: (params: {
|
|
event: React.MouseEvent;
|
|
element: TimelineElementType;
|
|
track: TimelineTrack;
|
|
}) => void;
|
|
onElementClick: (params: {
|
|
event: React.MouseEvent;
|
|
element: TimelineElementType;
|
|
track: TimelineTrack;
|
|
}) => void;
|
|
onTrackMouseDown?: (event: React.MouseEvent) => void;
|
|
onTrackMouseUp?: (event: React.MouseEvent) => void;
|
|
shouldIgnoreClick?: () => boolean;
|
|
targetElementId?: string | null;
|
|
}
|
|
|
|
export function TimelineTrackContent({
|
|
track,
|
|
zoomLevel,
|
|
dragState,
|
|
rulerScrollRef,
|
|
tracksScrollRef,
|
|
lastMouseXRef,
|
|
onSnapPointChange,
|
|
onResizeStateChange,
|
|
onElementMouseDown,
|
|
onElementClick,
|
|
onTrackMouseDown,
|
|
onTrackMouseUp,
|
|
shouldIgnoreClick,
|
|
targetElementId = null,
|
|
}: TimelineTrackContentProps) {
|
|
const { isElementSelected } = useElementSelection();
|
|
const duration = useEditor((e) => e.timeline.getTotalDuration());
|
|
|
|
useEdgeAutoScroll({
|
|
isActive: dragState.isDragging,
|
|
getMouseClientX: () => lastMouseXRef.current ?? 0,
|
|
rulerScrollRef,
|
|
tracksScrollRef,
|
|
contentWidth: duration * BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel,
|
|
});
|
|
|
|
return (
|
|
<div className="relative size-full">
|
|
<button
|
|
type="button"
|
|
className="absolute inset-0 m-0 size-full appearance-none border-0 bg-transparent p-0"
|
|
aria-label={`Select ${track.name} track`}
|
|
onMouseUp={(event) => {
|
|
if (shouldIgnoreClick?.()) return;
|
|
onTrackMouseUp?.(event);
|
|
}}
|
|
onMouseDown={(event) => {
|
|
event.preventDefault();
|
|
onTrackMouseDown?.(event);
|
|
}}
|
|
/>
|
|
{/* biome-ignore lint/a11y/noStaticElementInteractions: empty track area is a pointer-only seek surface */}
|
|
<div
|
|
className="relative h-full min-w-full"
|
|
style={{ zIndex: TIMELINE_LAYERS.trackContent }}
|
|
onMouseUp={(event) => {
|
|
if (event.target !== event.currentTarget) return;
|
|
if (shouldIgnoreClick?.()) return;
|
|
onTrackMouseUp?.(event);
|
|
}}
|
|
onMouseDown={(event) => {
|
|
if (event.target !== event.currentTarget) return;
|
|
event.preventDefault();
|
|
onTrackMouseDown?.(event);
|
|
}}
|
|
>
|
|
{track.elements.length === 0 ? (
|
|
<div className="text-muted-foreground border-muted/30 pointer-events-none flex size-full items-center justify-center rounded-sm border-2 border-dashed text-xs" />
|
|
) : (
|
|
track.elements.map((element) => {
|
|
const isSelected = isElementSelected({
|
|
trackId: track.id,
|
|
elementId: element.id,
|
|
});
|
|
|
|
return (
|
|
<TimelineElement
|
|
key={element.id}
|
|
element={element}
|
|
track={track}
|
|
zoomLevel={zoomLevel}
|
|
isSelected={isSelected}
|
|
onSnapPointChange={onSnapPointChange}
|
|
onResizeStateChange={onResizeStateChange}
|
|
onElementMouseDown={(event, element) =>
|
|
onElementMouseDown({ event, element, track })
|
|
}
|
|
onElementClick={(event, element) =>
|
|
onElementClick({ event, element, track })
|
|
}
|
|
dragState={dragState}
|
|
isDropTarget={element.id === targetElementId}
|
|
/>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|