import { useCallback, useMemo, useRef, RefObject } from "react"; import MotionSegment from "./MotionSegment"; import { useTimelineUtils } from "@/hooks/use-timeline-utils"; import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review"; import ReviewTimeline from "./ReviewTimeline"; import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils"; export type MotionReviewTimelineProps = { segmentDuration: number; timestampSpread: number; timelineStart: number; timelineEnd: number; showHandlebar?: boolean; handlebarTime?: number; setHandlebarTime?: React.Dispatch>; onlyInitialHandlebarScroll?: boolean; motionOnly?: boolean; showMinimap?: boolean; minimapStartTime?: number; minimapEndTime?: number; showExportHandles?: boolean; exportStartTime?: number; exportEndTime?: number; setExportStartTime?: React.Dispatch>; setExportEndTime?: React.Dispatch>; events: ReviewSegment[]; motion_events: MotionData[]; severityType: ReviewSeverity; contentRef: RefObject; timelineRef?: RefObject; onHandlebarDraggingChange?: (isDragging: boolean) => void; dense?: boolean; }; export function MotionReviewTimeline({ segmentDuration, timestampSpread, timelineStart, timelineEnd, showHandlebar = false, handlebarTime, setHandlebarTime, onlyInitialHandlebarScroll = false, motionOnly = false, showMinimap = false, minimapStartTime, minimapEndTime, showExportHandles = false, exportStartTime, exportEndTime, setExportStartTime, setExportEndTime, events, motion_events, contentRef, timelineRef, onHandlebarDraggingChange, dense = false, }: MotionReviewTimelineProps) { const internalTimelineRef = useRef(null); const selectedTimelineRef = timelineRef || internalTimelineRef; const timelineDuration = useMemo( () => timelineStart - timelineEnd + 4 * segmentDuration, [timelineEnd, timelineStart, segmentDuration], ); const { alignStartDateToTimeline } = useTimelineUtils({ segmentDuration, timelineDuration, }); const timelineStartAligned = useMemo( () => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration, [timelineStart, alignStartDateToTimeline, segmentDuration], ); const { getMotionSegmentValue } = useMotionSegmentUtils( segmentDuration, motion_events, ); // Generate segments for the timeline const generateSegments = useCallback(() => { const segments = []; let segmentTime = timelineStartAligned; while (segmentTime >= timelineStartAligned - timelineDuration) { const motionStart = segmentTime; const motionEnd = motionStart + segmentDuration; const firstHalfMotionValue = getMotionSegmentValue(motionStart); const secondHalfMotionValue = getMotionSegmentValue( motionStart + segmentDuration / 2, ); const segmentMotion = firstHalfMotionValue > 0 || secondHalfMotionValue > 0; const overlappingReviewItems = events.some( (item) => (item.start_time >= motionStart && item.start_time < motionEnd) || ((item.end_time ?? timelineStart) > motionStart && (item.end_time ?? timelineStart) <= motionEnd) || (item.start_time <= motionStart && (item.end_time ?? timelineStart) >= motionEnd), ); if ((!segmentMotion || overlappingReviewItems) && motionOnly) { // exclude segment if necessary when in motion only mode segmentTime -= segmentDuration; continue; } segments.push( , ); segmentTime -= segmentDuration; } return segments; // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps }, [ segmentDuration, timestampSpread, timelineStartAligned, timelineDuration, showMinimap, minimapStartTime, minimapEndTime, events, motion_events, motionOnly, ]); const segments = useMemo( () => generateSegments(), // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps [ segmentDuration, timestampSpread, timelineStartAligned, timelineDuration, showMinimap, minimapStartTime, minimapEndTime, events, motion_events, motionOnly, ], ); return ( {segments} ); } export default MotionReviewTimeline;