import useDraggableHandler from "@/hooks/use-handle-dragging"; import { useEffect, useCallback, useMemo, useRef, useState, RefObject, } from "react"; import EventSegment from "./EventSegment"; import { useEventUtils } from "@/hooks/use-event-utils"; import { ReviewSegment, ReviewSeverity } from "@/types/review"; import { TooltipProvider } from "../ui/tooltip"; export type EventReviewTimelineProps = { segmentDuration: number; timestampSpread: number; timelineStart: number; timelineEnd: number; showHandlebar?: boolean; handlebarTime?: number; setHandlebarTime?: React.Dispatch>; showMinimap?: boolean; minimapStartTime?: number; minimapEndTime?: number; events: ReviewSegment[]; severityType: ReviewSeverity; contentRef: RefObject; onHandlebarDraggingChange?: (isDragging: boolean) => void; }; export function EventReviewTimeline({ segmentDuration, timestampSpread, timelineStart, timelineEnd, showHandlebar = false, handlebarTime, setHandlebarTime, showMinimap = false, minimapStartTime, minimapEndTime, events, severityType, contentRef, onHandlebarDraggingChange, }: EventReviewTimelineProps) { const [isDragging, setIsDragging] = useState(false); const [currentTimeSegment, setCurrentTimeSegment] = useState(0); const scrollTimeRef = useRef(null); const timelineRef = useRef(null); const currentTimeRef = useRef(null); const observer = useRef(null); const timelineDuration = useMemo( () => timelineStart - timelineEnd, [timelineEnd, timelineStart] ); const { alignDateToTimeline } = useEventUtils(events, segmentDuration); const { handleMouseDown, handleMouseUp, handleMouseMove } = useDraggableHandler({ contentRef, timelineRef, scrollTimeRef, alignDateToTimeline, segmentDuration, showHandlebar, timelineDuration, timelineStart, isDragging, setIsDragging, currentTimeRef, setHandlebarTime, }); function handleResize() { // TODO: handle screen resize for mobile if (timelineRef.current && contentRef.current) { } } useEffect(() => { if (contentRef.current) { const content = contentRef.current; observer.current = new ResizeObserver(() => { handleResize(); }); observer.current.observe(content); return () => { observer.current?.unobserve(content); }; } }, []); // Generate segments for the timeline const generateSegments = useCallback(() => { const segmentCount = timelineDuration / segmentDuration; const segmentAlignedTime = alignDateToTimeline(timelineStart); return Array.from({ length: segmentCount }, (_, index) => { const segmentTime = segmentAlignedTime - index * segmentDuration; return ( ); }); }, [ segmentDuration, timestampSpread, timelineStart, timelineDuration, showMinimap, minimapStartTime, minimapEndTime, events, ]); const segments = useMemo( () => generateSegments(), [ segmentDuration, timestampSpread, timelineStart, timelineDuration, showMinimap, minimapStartTime, minimapEndTime, events, ] ); useEffect(() => { if (showHandlebar) { requestAnimationFrame(() => { if (currentTimeRef.current && currentTimeSegment) { currentTimeRef.current.textContent = new Date( currentTimeSegment * 1000 ).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", ...(segmentDuration < 60 && { second: "2-digit" }), }); } }); } }, [currentTimeSegment, showHandlebar]); useEffect(() => { if (onHandlebarDraggingChange) { onHandlebarDraggingChange(isDragging); } }, [isDragging, onHandlebarDraggingChange]); useEffect(() => { if (timelineRef.current && handlebarTime && showHandlebar) { const { scrollHeight: timelineHeight } = timelineRef.current; // Calculate the height of an individual segment const segmentHeight = timelineHeight / (timelineDuration / segmentDuration); // Calculate the segment index corresponding to the target time const alignedHandlebarTime = alignDateToTimeline(handlebarTime); const segmentIndex = Math.ceil( (timelineStart - alignedHandlebarTime) / segmentDuration ); // Calculate the top position based on the segment index const newTopPosition = Math.max(0, segmentIndex * segmentHeight); // Set the top position of the handle const thumb = scrollTimeRef.current; if (thumb) { requestAnimationFrame(() => { thumb.style.top = `${newTopPosition}px`; }); } setCurrentTimeSegment(alignedHandlebarTime); } }, []); useEffect(() => { generateSegments(); if (!currentTimeSegment && !handlebarTime) { setCurrentTimeSegment(timelineStart); } // TODO: touch events for mobile document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; }, [ currentTimeSegment, generateSegments, timelineStart, handleMouseUp, handleMouseMove, ]); return (
{segments}
{showHandlebar && (
)}
); } export default EventReviewTimeline;