mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Timeline tweaks for mobile (#10726)
* add dense prop, combine duplicate code, fix mobile bug * put segment height in hook * playground
This commit is contained in:
		
							parent
							
								
									985b2d7b27
								
							
						
					
					
						commit
						36d5e5b45f
					
				@ -1,12 +1,4 @@
 | 
				
			|||||||
import useDraggableElement from "@/hooks/use-draggable-element";
 | 
					import { useEffect, useCallback, useMemo, useRef, RefObject } from "react";
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  useEffect,
 | 
					 | 
				
			||||||
  useCallback,
 | 
					 | 
				
			||||||
  useMemo,
 | 
					 | 
				
			||||||
  useRef,
 | 
					 | 
				
			||||||
  useState,
 | 
					 | 
				
			||||||
  RefObject,
 | 
					 | 
				
			||||||
} from "react";
 | 
					 | 
				
			||||||
import EventSegment from "./EventSegment";
 | 
					import EventSegment from "./EventSegment";
 | 
				
			||||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
 | 
					import { useTimelineUtils } from "@/hooks/use-timeline-utils";
 | 
				
			||||||
import { ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
					import { ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
				
			||||||
@ -35,6 +27,7 @@ export type EventReviewTimelineProps = {
 | 
				
			|||||||
  timelineRef?: RefObject<HTMLDivElement>;
 | 
					  timelineRef?: RefObject<HTMLDivElement>;
 | 
				
			||||||
  contentRef: RefObject<HTMLDivElement>;
 | 
					  contentRef: RefObject<HTMLDivElement>;
 | 
				
			||||||
  onHandlebarDraggingChange?: (isDragging: boolean) => void;
 | 
					  onHandlebarDraggingChange?: (isDragging: boolean) => void;
 | 
				
			||||||
 | 
					  dense?: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventReviewTimeline({
 | 
					export function EventReviewTimeline({
 | 
				
			||||||
@ -59,18 +52,9 @@ export function EventReviewTimeline({
 | 
				
			|||||||
  timelineRef,
 | 
					  timelineRef,
 | 
				
			||||||
  contentRef,
 | 
					  contentRef,
 | 
				
			||||||
  onHandlebarDraggingChange,
 | 
					  onHandlebarDraggingChange,
 | 
				
			||||||
 | 
					  dense = false,
 | 
				
			||||||
}: EventReviewTimelineProps) {
 | 
					}: EventReviewTimelineProps) {
 | 
				
			||||||
  const [isDragging, setIsDragging] = useState(false);
 | 
					 | 
				
			||||||
  const [exportStartPosition, setExportStartPosition] = useState(0);
 | 
					 | 
				
			||||||
  const [exportEndPosition, setExportEndPosition] = useState(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const internalTimelineRef = useRef<HTMLDivElement>(null);
 | 
					  const internalTimelineRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
  const handlebarRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const handlebarTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportStartRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportStartTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportEndRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportEndTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const selectedTimelineRef = timelineRef || internalTimelineRef;
 | 
					  const selectedTimelineRef = timelineRef || internalTimelineRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const timelineDuration = useMemo(
 | 
					  const timelineDuration = useMemo(
 | 
				
			||||||
@ -78,92 +62,17 @@ export function EventReviewTimeline({
 | 
				
			|||||||
    [timelineEnd, timelineStart],
 | 
					    [timelineEnd, timelineStart],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { alignStartDateToTimeline, alignEndDateToTimeline } = useTimelineUtils(
 | 
					  const { alignStartDateToTimeline } = useTimelineUtils({
 | 
				
			||||||
    {
 | 
					    segmentDuration,
 | 
				
			||||||
      segmentDuration,
 | 
					    timelineDuration,
 | 
				
			||||||
      timelineDuration,
 | 
					    timelineRef: selectedTimelineRef,
 | 
				
			||||||
      timelineRef: selectedTimelineRef,
 | 
					  });
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const timelineStartAligned = useMemo(
 | 
					  const timelineStartAligned = useMemo(
 | 
				
			||||||
    () => alignStartDateToTimeline(timelineStart),
 | 
					    () => alignStartDateToTimeline(timelineStart),
 | 
				
			||||||
    [timelineStart, alignStartDateToTimeline],
 | 
					    [timelineStart, alignStartDateToTimeline],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const paddedExportStartTime = useMemo(() => {
 | 
					 | 
				
			||||||
    if (exportStartTime) {
 | 
					 | 
				
			||||||
      return alignStartDateToTimeline(exportStartTime) + segmentDuration;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [exportStartTime, segmentDuration, alignStartDateToTimeline]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const paddedExportEndTime = useMemo(() => {
 | 
					 | 
				
			||||||
    if (exportEndTime) {
 | 
					 | 
				
			||||||
      return alignEndDateToTimeline(exportEndTime) - segmentDuration * 2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [exportEndTime, segmentDuration, alignEndDateToTimeline]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: handlebarMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: handlebarMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: handlebarMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: selectedTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: handlebarRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showHandlebar,
 | 
					 | 
				
			||||||
    draggableElementTime: handlebarTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setHandlebarTime,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: handlebarTimeRef,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: exportStartMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: exportStartMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: exportStartMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: selectedTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: exportStartRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showExportHandles,
 | 
					 | 
				
			||||||
    draggableElementTime: exportStartTime,
 | 
					 | 
				
			||||||
    draggableElementLatestTime: paddedExportEndTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setExportStartTime,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: exportStartTimeRef,
 | 
					 | 
				
			||||||
    setDraggableElementPosition: setExportStartPosition,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: exportEndMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: exportEndMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: exportEndMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: selectedTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: exportEndRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showExportHandles,
 | 
					 | 
				
			||||||
    draggableElementTime: exportEndTime,
 | 
					 | 
				
			||||||
    draggableElementEarliestTime: paddedExportStartTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setExportEndTime,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: exportEndTimeRef,
 | 
					 | 
				
			||||||
    setDraggableElementPosition: setExportEndPosition,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Generate segments for the timeline
 | 
					  // Generate segments for the timeline
 | 
				
			||||||
  const generateSegments = useCallback(() => {
 | 
					  const generateSegments = useCallback(() => {
 | 
				
			||||||
    const segmentCount = Math.ceil(timelineDuration / segmentDuration);
 | 
					    const segmentCount = Math.ceil(timelineDuration / segmentDuration);
 | 
				
			||||||
@ -184,6 +93,7 @@ export function EventReviewTimeline({
 | 
				
			|||||||
          severityType={severityType}
 | 
					          severityType={severityType}
 | 
				
			||||||
          contentRef={contentRef}
 | 
					          contentRef={contentRef}
 | 
				
			||||||
          setHandlebarTime={setHandlebarTime}
 | 
					          setHandlebarTime={setHandlebarTime}
 | 
				
			||||||
 | 
					          dense={dense}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -216,12 +126,6 @@ export function EventReviewTimeline({
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (onHandlebarDraggingChange) {
 | 
					 | 
				
			||||||
      onHandlebarDraggingChange(isDragging);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [isDragging, onHandlebarDraggingChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      selectedTimelineRef.current &&
 | 
					      selectedTimelineRef.current &&
 | 
				
			||||||
@ -254,28 +158,20 @@ export function EventReviewTimeline({
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <ReviewTimeline
 | 
					    <ReviewTimeline
 | 
				
			||||||
      timelineRef={selectedTimelineRef}
 | 
					      timelineRef={selectedTimelineRef}
 | 
				
			||||||
      handlebarRef={handlebarRef}
 | 
					      contentRef={contentRef}
 | 
				
			||||||
      handlebarTimeRef={handlebarTimeRef}
 | 
					 | 
				
			||||||
      handlebarMouseMove={handlebarMouseMove}
 | 
					 | 
				
			||||||
      handlebarMouseUp={handlebarMouseUp}
 | 
					 | 
				
			||||||
      handlebarMouseDown={handlebarMouseDown}
 | 
					 | 
				
			||||||
      segmentDuration={segmentDuration}
 | 
					      segmentDuration={segmentDuration}
 | 
				
			||||||
      timelineDuration={timelineDuration}
 | 
					      timelineDuration={timelineDuration}
 | 
				
			||||||
 | 
					      timelineStartAligned={timelineStartAligned}
 | 
				
			||||||
      showHandlebar={showHandlebar}
 | 
					      showHandlebar={showHandlebar}
 | 
				
			||||||
      isDragging={isDragging}
 | 
					      onHandlebarDraggingChange={onHandlebarDraggingChange}
 | 
				
			||||||
      exportStartMouseMove={exportStartMouseMove}
 | 
					 | 
				
			||||||
      exportStartMouseUp={exportStartMouseUp}
 | 
					 | 
				
			||||||
      exportStartMouseDown={exportStartMouseDown}
 | 
					 | 
				
			||||||
      exportEndMouseMove={exportEndMouseMove}
 | 
					 | 
				
			||||||
      exportEndMouseUp={exportEndMouseUp}
 | 
					 | 
				
			||||||
      exportEndMouseDown={exportEndMouseDown}
 | 
					 | 
				
			||||||
      showExportHandles={showExportHandles}
 | 
					      showExportHandles={showExportHandles}
 | 
				
			||||||
      exportStartRef={exportStartRef}
 | 
					      handlebarTime={handlebarTime}
 | 
				
			||||||
      exportStartTimeRef={exportStartTimeRef}
 | 
					      setHandlebarTime={setHandlebarTime}
 | 
				
			||||||
      exportEndRef={exportEndRef}
 | 
					      exportStartTime={exportStartTime}
 | 
				
			||||||
      exportEndTimeRef={exportEndTimeRef}
 | 
					      exportEndTime={exportEndTime}
 | 
				
			||||||
      exportStartPosition={exportStartPosition}
 | 
					      setExportStartTime={setExportStartTime}
 | 
				
			||||||
      exportEndPosition={exportEndPosition}
 | 
					      setExportEndTime={setExportEndTime}
 | 
				
			||||||
 | 
					      dense={dense}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      {segments}
 | 
					      {segments}
 | 
				
			||||||
    </ReviewTimeline>
 | 
					    </ReviewTimeline>
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@ type EventSegmentProps = {
 | 
				
			|||||||
  severityType: ReviewSeverity;
 | 
					  severityType: ReviewSeverity;
 | 
				
			||||||
  contentRef: RefObject<HTMLDivElement>;
 | 
					  contentRef: RefObject<HTMLDivElement>;
 | 
				
			||||||
  setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
					  setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
 | 
					  dense: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventSegment({
 | 
					export function EventSegment({
 | 
				
			||||||
@ -43,6 +44,7 @@ export function EventSegment({
 | 
				
			|||||||
  severityType,
 | 
					  severityType,
 | 
				
			||||||
  contentRef,
 | 
					  contentRef,
 | 
				
			||||||
  setHandlebarTime,
 | 
					  setHandlebarTime,
 | 
				
			||||||
 | 
					  dense,
 | 
				
			||||||
}: EventSegmentProps) {
 | 
					}: EventSegmentProps) {
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    getSeverity,
 | 
					    getSeverity,
 | 
				
			||||||
@ -212,6 +214,7 @@ export function EventSegment({
 | 
				
			|||||||
          alignedMinimapStartTime={alignedMinimapStartTime}
 | 
					          alignedMinimapStartTime={alignedMinimapStartTime}
 | 
				
			||||||
          alignedMinimapEndTime={alignedMinimapEndTime}
 | 
					          alignedMinimapEndTime={alignedMinimapEndTime}
 | 
				
			||||||
          firstMinimapSegmentRef={firstMinimapSegmentRef}
 | 
					          firstMinimapSegmentRef={firstMinimapSegmentRef}
 | 
				
			||||||
 | 
					          dense={dense}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,4 @@
 | 
				
			|||||||
import useDraggableElement from "@/hooks/use-draggable-element";
 | 
					import { useEffect, useCallback, useMemo, useRef, RefObject } from "react";
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  useEffect,
 | 
					 | 
				
			||||||
  useCallback,
 | 
					 | 
				
			||||||
  useMemo,
 | 
					 | 
				
			||||||
  useRef,
 | 
					 | 
				
			||||||
  useState,
 | 
					 | 
				
			||||||
  RefObject,
 | 
					 | 
				
			||||||
} from "react";
 | 
					 | 
				
			||||||
import MotionSegment from "./MotionSegment";
 | 
					import MotionSegment from "./MotionSegment";
 | 
				
			||||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
 | 
					import { useTimelineUtils } from "@/hooks/use-timeline-utils";
 | 
				
			||||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
					import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
				
			||||||
@ -37,6 +29,7 @@ export type MotionReviewTimelineProps = {
 | 
				
			|||||||
  contentRef: RefObject<HTMLDivElement>;
 | 
					  contentRef: RefObject<HTMLDivElement>;
 | 
				
			||||||
  timelineRef?: RefObject<HTMLDivElement>;
 | 
					  timelineRef?: RefObject<HTMLDivElement>;
 | 
				
			||||||
  onHandlebarDraggingChange?: (isDragging: boolean) => void;
 | 
					  onHandlebarDraggingChange?: (isDragging: boolean) => void;
 | 
				
			||||||
 | 
					  dense?: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function MotionReviewTimeline({
 | 
					export function MotionReviewTimeline({
 | 
				
			||||||
@ -62,111 +55,26 @@ export function MotionReviewTimeline({
 | 
				
			|||||||
  contentRef,
 | 
					  contentRef,
 | 
				
			||||||
  timelineRef,
 | 
					  timelineRef,
 | 
				
			||||||
  onHandlebarDraggingChange,
 | 
					  onHandlebarDraggingChange,
 | 
				
			||||||
 | 
					  dense = false,
 | 
				
			||||||
}: MotionReviewTimelineProps) {
 | 
					}: MotionReviewTimelineProps) {
 | 
				
			||||||
  const [isDragging, setIsDragging] = useState(false);
 | 
					 | 
				
			||||||
  const [exportStartPosition, setExportStartPosition] = useState(0);
 | 
					 | 
				
			||||||
  const [exportEndPosition, setExportEndPosition] = useState(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const internalTimelineRef = useRef<HTMLDivElement>(null);
 | 
					  const internalTimelineRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
  const handlebarRef = useRef<HTMLDivElement>(null);
 | 
					  const selectedTimelineRef = timelineRef || internalTimelineRef;
 | 
				
			||||||
  const handlebarTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportStartRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportStartTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportEndRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
  const exportEndTimeRef = useRef<HTMLDivElement>(null);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const timelineDuration = useMemo(
 | 
					  const timelineDuration = useMemo(
 | 
				
			||||||
    () => timelineStart - timelineEnd + 4 * segmentDuration,
 | 
					    () => timelineStart - timelineEnd + 4 * segmentDuration,
 | 
				
			||||||
    [timelineEnd, timelineStart, segmentDuration],
 | 
					    [timelineEnd, timelineStart, segmentDuration],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { alignStartDateToTimeline, alignEndDateToTimeline } = useTimelineUtils(
 | 
					  const { alignStartDateToTimeline } = useTimelineUtils({
 | 
				
			||||||
    {
 | 
					    segmentDuration,
 | 
				
			||||||
      segmentDuration,
 | 
					    timelineDuration,
 | 
				
			||||||
      timelineDuration,
 | 
					  });
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const timelineStartAligned = useMemo(
 | 
					  const timelineStartAligned = useMemo(
 | 
				
			||||||
    () => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration,
 | 
					    () => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration,
 | 
				
			||||||
    [timelineStart, alignStartDateToTimeline, segmentDuration],
 | 
					    [timelineStart, alignStartDateToTimeline, segmentDuration],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const paddedExportStartTime = useMemo(() => {
 | 
					 | 
				
			||||||
    if (exportStartTime) {
 | 
					 | 
				
			||||||
      return alignStartDateToTimeline(exportStartTime) + segmentDuration;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [exportStartTime, segmentDuration, alignStartDateToTimeline]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const paddedExportEndTime = useMemo(() => {
 | 
					 | 
				
			||||||
    if (exportEndTime) {
 | 
					 | 
				
			||||||
      return alignEndDateToTimeline(exportEndTime) - segmentDuration * 2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [exportEndTime, segmentDuration, alignEndDateToTimeline]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: handlebarMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: handlebarMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: handlebarMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: timelineRef || internalTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: handlebarRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showHandlebar,
 | 
					 | 
				
			||||||
    draggableElementTime: handlebarTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setHandlebarTime,
 | 
					 | 
				
			||||||
    initialScrollIntoViewOnly: onlyInitialHandlebarScroll,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineCollapsed: motionOnly,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: handlebarTimeRef,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: exportStartMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: exportStartMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: exportStartMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: timelineRef || internalTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: exportStartRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showExportHandles,
 | 
					 | 
				
			||||||
    draggableElementTime: exportStartTime,
 | 
					 | 
				
			||||||
    draggableElementLatestTime: paddedExportEndTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setExportStartTime,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: exportStartTimeRef,
 | 
					 | 
				
			||||||
    setDraggableElementPosition: setExportStartPosition,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    handleMouseDown: exportEndMouseDown,
 | 
					 | 
				
			||||||
    handleMouseUp: exportEndMouseUp,
 | 
					 | 
				
			||||||
    handleMouseMove: exportEndMouseMove,
 | 
					 | 
				
			||||||
  } = useDraggableElement({
 | 
					 | 
				
			||||||
    contentRef,
 | 
					 | 
				
			||||||
    timelineRef: timelineRef || internalTimelineRef,
 | 
					 | 
				
			||||||
    draggableElementRef: exportEndRef,
 | 
					 | 
				
			||||||
    segmentDuration,
 | 
					 | 
				
			||||||
    showDraggableElement: showExportHandles,
 | 
					 | 
				
			||||||
    draggableElementTime: exportEndTime,
 | 
					 | 
				
			||||||
    draggableElementEarliestTime: paddedExportStartTime,
 | 
					 | 
				
			||||||
    setDraggableElementTime: setExportEndTime,
 | 
					 | 
				
			||||||
    timelineDuration,
 | 
					 | 
				
			||||||
    timelineStartAligned,
 | 
					 | 
				
			||||||
    isDragging,
 | 
					 | 
				
			||||||
    setIsDragging,
 | 
					 | 
				
			||||||
    draggableElementTimeRef: exportEndTimeRef,
 | 
					 | 
				
			||||||
    setDraggableElementPosition: setExportEndPosition,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Generate segments for the timeline
 | 
					  // Generate segments for the timeline
 | 
				
			||||||
  const generateSegments = useCallback(() => {
 | 
					  const generateSegments = useCallback(() => {
 | 
				
			||||||
    const segmentCount = Math.ceil(timelineDuration / segmentDuration);
 | 
					    const segmentCount = Math.ceil(timelineDuration / segmentDuration);
 | 
				
			||||||
@ -187,6 +95,7 @@ export function MotionReviewTimeline({
 | 
				
			|||||||
          minimapStartTime={minimapStartTime}
 | 
					          minimapStartTime={minimapStartTime}
 | 
				
			||||||
          minimapEndTime={minimapEndTime}
 | 
					          minimapEndTime={minimapEndTime}
 | 
				
			||||||
          setHandlebarTime={setHandlebarTime}
 | 
					          setHandlebarTime={setHandlebarTime}
 | 
				
			||||||
 | 
					          dense={dense}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -223,14 +132,7 @@ export function MotionReviewTimeline({
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (onHandlebarDraggingChange) {
 | 
					 | 
				
			||||||
      onHandlebarDraggingChange(isDragging);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [isDragging, onHandlebarDraggingChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const segmentsObserver = useRef<IntersectionObserver | null>(null);
 | 
					  const segmentsObserver = useRef<IntersectionObserver | null>(null);
 | 
				
			||||||
  const selectedTimelineRef = timelineRef || internalTimelineRef;
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (selectedTimelineRef.current && segments && isDesktop) {
 | 
					    if (selectedTimelineRef.current && segments && isDesktop) {
 | 
				
			||||||
      segmentsObserver.current = new IntersectionObserver(
 | 
					      segmentsObserver.current = new IntersectionObserver(
 | 
				
			||||||
@ -268,29 +170,22 @@ export function MotionReviewTimeline({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <ReviewTimeline
 | 
					    <ReviewTimeline
 | 
				
			||||||
      timelineRef={timelineRef || internalTimelineRef}
 | 
					      timelineRef={selectedTimelineRef}
 | 
				
			||||||
      handlebarRef={handlebarRef}
 | 
					      contentRef={contentRef}
 | 
				
			||||||
      handlebarTimeRef={handlebarTimeRef}
 | 
					 | 
				
			||||||
      handlebarMouseMove={handlebarMouseMove}
 | 
					 | 
				
			||||||
      handlebarMouseUp={handlebarMouseUp}
 | 
					 | 
				
			||||||
      handlebarMouseDown={handlebarMouseDown}
 | 
					 | 
				
			||||||
      segmentDuration={segmentDuration}
 | 
					      segmentDuration={segmentDuration}
 | 
				
			||||||
      timelineDuration={timelineDuration}
 | 
					      timelineDuration={timelineDuration}
 | 
				
			||||||
 | 
					      timelineStartAligned={timelineStartAligned}
 | 
				
			||||||
      showHandlebar={showHandlebar}
 | 
					      showHandlebar={showHandlebar}
 | 
				
			||||||
      isDragging={isDragging}
 | 
					      onHandlebarDraggingChange={onHandlebarDraggingChange}
 | 
				
			||||||
      exportStartMouseMove={exportStartMouseMove}
 | 
					      onlyInitialHandlebarScroll={onlyInitialHandlebarScroll}
 | 
				
			||||||
      exportStartMouseUp={exportStartMouseUp}
 | 
					 | 
				
			||||||
      exportStartMouseDown={exportStartMouseDown}
 | 
					 | 
				
			||||||
      exportEndMouseMove={exportEndMouseMove}
 | 
					 | 
				
			||||||
      exportEndMouseUp={exportEndMouseUp}
 | 
					 | 
				
			||||||
      exportEndMouseDown={exportEndMouseDown}
 | 
					 | 
				
			||||||
      showExportHandles={showExportHandles}
 | 
					      showExportHandles={showExportHandles}
 | 
				
			||||||
      exportStartRef={exportStartRef}
 | 
					      handlebarTime={handlebarTime}
 | 
				
			||||||
      exportStartTimeRef={exportStartTimeRef}
 | 
					      setHandlebarTime={setHandlebarTime}
 | 
				
			||||||
      exportEndRef={exportEndRef}
 | 
					      exportStartTime={exportStartTime}
 | 
				
			||||||
      exportEndTimeRef={exportEndTimeRef}
 | 
					      exportEndTime={exportEndTime}
 | 
				
			||||||
      exportStartPosition={exportStartPosition}
 | 
					      setExportStartTime={setExportStartTime}
 | 
				
			||||||
      exportEndPosition={exportEndPosition}
 | 
					      setExportEndTime={setExportEndTime}
 | 
				
			||||||
 | 
					      dense={dense}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      {segments}
 | 
					      {segments}
 | 
				
			||||||
    </ReviewTimeline>
 | 
					    </ReviewTimeline>
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ type MotionSegmentProps = {
 | 
				
			|||||||
  minimapStartTime?: number;
 | 
					  minimapStartTime?: number;
 | 
				
			||||||
  minimapEndTime?: number;
 | 
					  minimapEndTime?: number;
 | 
				
			||||||
  setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
					  setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
 | 
					  dense: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function MotionSegment({
 | 
					export function MotionSegment({
 | 
				
			||||||
@ -32,6 +33,7 @@ export function MotionSegment({
 | 
				
			|||||||
  minimapStartTime,
 | 
					  minimapStartTime,
 | 
				
			||||||
  minimapEndTime,
 | 
					  minimapEndTime,
 | 
				
			||||||
  setHandlebarTime,
 | 
					  setHandlebarTime,
 | 
				
			||||||
 | 
					  dense,
 | 
				
			||||||
}: MotionSegmentProps) {
 | 
					}: MotionSegmentProps) {
 | 
				
			||||||
  const severityType = "all";
 | 
					  const severityType = "all";
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
@ -203,6 +205,7 @@ export function MotionSegment({
 | 
				
			|||||||
                  alignedMinimapStartTime={alignedMinimapStartTime}
 | 
					                  alignedMinimapStartTime={alignedMinimapStartTime}
 | 
				
			||||||
                  alignedMinimapEndTime={alignedMinimapEndTime}
 | 
					                  alignedMinimapEndTime={alignedMinimapEndTime}
 | 
				
			||||||
                  firstMinimapSegmentRef={firstMinimapSegmentRef}
 | 
					                  firstMinimapSegmentRef={firstMinimapSegmentRef}
 | 
				
			||||||
 | 
					                  dense={dense}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import useDraggableElement from "@/hooks/use-draggable-element";
 | 
				
			||||||
 | 
					import { useTimelineUtils } from "@/hooks/use-timeline-utils";
 | 
				
			||||||
import { DraggableElement } from "@/types/draggable-element";
 | 
					import { DraggableElement } from "@/types/draggable-element";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ReactNode,
 | 
					  ReactNode,
 | 
				
			||||||
@ -12,85 +14,150 @@ import { isIOS, isMobile } from "react-device-detect";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type ReviewTimelineProps = {
 | 
					export type ReviewTimelineProps = {
 | 
				
			||||||
  timelineRef: RefObject<HTMLDivElement>;
 | 
					  timelineRef: RefObject<HTMLDivElement>;
 | 
				
			||||||
  handlebarRef: RefObject<HTMLDivElement>;
 | 
					  contentRef: RefObject<HTMLDivElement>;
 | 
				
			||||||
  handlebarTimeRef: RefObject<HTMLDivElement>;
 | 
					 | 
				
			||||||
  handlebarMouseMove: (e: MouseEvent | TouchEvent) => void;
 | 
					 | 
				
			||||||
  handlebarMouseUp: (e: MouseEvent | TouchEvent) => void;
 | 
					 | 
				
			||||||
  handlebarMouseDown: (
 | 
					 | 
				
			||||||
    e:
 | 
					 | 
				
			||||||
      | React.MouseEvent<HTMLDivElement, MouseEvent>
 | 
					 | 
				
			||||||
      | React.TouchEvent<HTMLDivElement>,
 | 
					 | 
				
			||||||
  ) => void;
 | 
					 | 
				
			||||||
  segmentDuration: number;
 | 
					  segmentDuration: number;
 | 
				
			||||||
  timelineDuration: number;
 | 
					  timelineDuration: number;
 | 
				
			||||||
 | 
					  timelineStartAligned: number;
 | 
				
			||||||
  showHandlebar: boolean;
 | 
					  showHandlebar: boolean;
 | 
				
			||||||
  showExportHandles: boolean;
 | 
					  showExportHandles: boolean;
 | 
				
			||||||
  exportStartRef: RefObject<HTMLDivElement>;
 | 
					  handlebarTime?: number;
 | 
				
			||||||
  exportStartTimeRef: RefObject<HTMLDivElement>;
 | 
					  setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
  exportEndRef: RefObject<HTMLDivElement>;
 | 
					  onHandlebarDraggingChange?: (isDragging: boolean) => void;
 | 
				
			||||||
  exportEndTimeRef: RefObject<HTMLDivElement>;
 | 
					  onlyInitialHandlebarScroll?: boolean;
 | 
				
			||||||
  exportStartMouseMove: (e: MouseEvent | TouchEvent) => void;
 | 
					  exportStartTime?: number;
 | 
				
			||||||
  exportStartMouseUp: (e: MouseEvent | TouchEvent) => void;
 | 
					  exportEndTime?: number;
 | 
				
			||||||
  exportStartMouseDown: (
 | 
					  setExportStartTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
    e:
 | 
					  setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
      | React.MouseEvent<HTMLDivElement, MouseEvent>
 | 
					  dense: boolean;
 | 
				
			||||||
      | React.TouchEvent<HTMLDivElement>,
 | 
					 | 
				
			||||||
  ) => void;
 | 
					 | 
				
			||||||
  exportEndMouseMove: (e: MouseEvent | TouchEvent) => void;
 | 
					 | 
				
			||||||
  exportEndMouseUp: (e: MouseEvent | TouchEvent) => void;
 | 
					 | 
				
			||||||
  exportEndMouseDown: (
 | 
					 | 
				
			||||||
    e:
 | 
					 | 
				
			||||||
      | React.MouseEvent<HTMLDivElement, MouseEvent>
 | 
					 | 
				
			||||||
      | React.TouchEvent<HTMLDivElement>,
 | 
					 | 
				
			||||||
  ) => void;
 | 
					 | 
				
			||||||
  isDragging: boolean;
 | 
					 | 
				
			||||||
  exportStartPosition?: number;
 | 
					 | 
				
			||||||
  exportEndPosition?: number;
 | 
					 | 
				
			||||||
  children: ReactNode;
 | 
					  children: ReactNode;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ReviewTimeline({
 | 
					export function ReviewTimeline({
 | 
				
			||||||
  timelineRef,
 | 
					  timelineRef,
 | 
				
			||||||
  handlebarRef,
 | 
					  contentRef,
 | 
				
			||||||
  handlebarTimeRef,
 | 
					 | 
				
			||||||
  handlebarMouseMove,
 | 
					 | 
				
			||||||
  handlebarMouseUp,
 | 
					 | 
				
			||||||
  handlebarMouseDown,
 | 
					 | 
				
			||||||
  segmentDuration,
 | 
					  segmentDuration,
 | 
				
			||||||
  timelineDuration,
 | 
					  timelineDuration,
 | 
				
			||||||
 | 
					  timelineStartAligned,
 | 
				
			||||||
  showHandlebar = false,
 | 
					  showHandlebar = false,
 | 
				
			||||||
  showExportHandles = false,
 | 
					  showExportHandles = false,
 | 
				
			||||||
  exportStartRef,
 | 
					  handlebarTime,
 | 
				
			||||||
  exportStartTimeRef,
 | 
					  setHandlebarTime,
 | 
				
			||||||
  exportEndRef,
 | 
					  onHandlebarDraggingChange,
 | 
				
			||||||
  exportEndTimeRef,
 | 
					  onlyInitialHandlebarScroll = false,
 | 
				
			||||||
  exportStartMouseMove,
 | 
					  exportStartTime,
 | 
				
			||||||
  exportStartMouseUp,
 | 
					  setExportStartTime,
 | 
				
			||||||
  exportStartMouseDown,
 | 
					  exportEndTime,
 | 
				
			||||||
  exportEndMouseMove,
 | 
					  setExportEndTime,
 | 
				
			||||||
  exportEndMouseUp,
 | 
					  dense,
 | 
				
			||||||
  exportEndMouseDown,
 | 
					 | 
				
			||||||
  isDragging,
 | 
					 | 
				
			||||||
  exportStartPosition,
 | 
					 | 
				
			||||||
  exportEndPosition,
 | 
					 | 
				
			||||||
  children,
 | 
					  children,
 | 
				
			||||||
}: ReviewTimelineProps) {
 | 
					}: ReviewTimelineProps) {
 | 
				
			||||||
 | 
					  const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
 | 
				
			||||||
 | 
					  const [isDraggingExportStart, setIsDraggingExportStart] = useState(false);
 | 
				
			||||||
 | 
					  const [isDraggingExportEnd, setIsDraggingExportEnd] = useState(false);
 | 
				
			||||||
 | 
					  const [exportStartPosition, setExportStartPosition] = useState(0);
 | 
				
			||||||
 | 
					  const [exportEndPosition, setExportEndPosition] = useState(0);
 | 
				
			||||||
 | 
					  const handlebarRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const handlebarTimeRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const exportStartRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const exportStartTimeRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const exportEndRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const exportEndTimeRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isDragging = useMemo(
 | 
				
			||||||
 | 
					    () => isDraggingHandlebar || isDraggingExportStart || isDraggingExportEnd,
 | 
				
			||||||
 | 
					    [isDraggingHandlebar, isDraggingExportStart, isDraggingExportEnd],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const exportSectionRef = useRef<HTMLDivElement>(null);
 | 
					  const exportSectionRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const segmentHeight = useMemo(() => {
 | 
					 | 
				
			||||||
    if (timelineRef.current) {
 | 
					 | 
				
			||||||
      const { scrollHeight: timelineHeight } =
 | 
					 | 
				
			||||||
        timelineRef.current as HTMLDivElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return timelineHeight / (timelineDuration / segmentDuration);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // we know that these deps are correct
 | 
					 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					 | 
				
			||||||
  }, [segmentDuration, timelineDuration, timelineRef, showExportHandles]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [draggableElementType, setDraggableElementType] =
 | 
					  const [draggableElementType, setDraggableElementType] =
 | 
				
			||||||
    useState<DraggableElement>();
 | 
					    useState<DraggableElement>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { alignStartDateToTimeline, alignEndDateToTimeline, segmentHeight } =
 | 
				
			||||||
 | 
					    useTimelineUtils({
 | 
				
			||||||
 | 
					      segmentDuration,
 | 
				
			||||||
 | 
					      timelineDuration,
 | 
				
			||||||
 | 
					      timelineRef,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const paddedExportStartTime = useMemo(() => {
 | 
				
			||||||
 | 
					    if (exportStartTime) {
 | 
				
			||||||
 | 
					      return alignStartDateToTimeline(exportStartTime) + segmentDuration;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [exportStartTime, segmentDuration, alignStartDateToTimeline]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const paddedExportEndTime = useMemo(() => {
 | 
				
			||||||
 | 
					    if (exportEndTime) {
 | 
				
			||||||
 | 
					      return alignEndDateToTimeline(exportEndTime);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [exportEndTime, alignEndDateToTimeline]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    handleMouseDown: handlebarMouseDown,
 | 
				
			||||||
 | 
					    handleMouseUp: handlebarMouseUp,
 | 
				
			||||||
 | 
					    handleMouseMove: handlebarMouseMove,
 | 
				
			||||||
 | 
					  } = useDraggableElement({
 | 
				
			||||||
 | 
					    contentRef,
 | 
				
			||||||
 | 
					    timelineRef,
 | 
				
			||||||
 | 
					    draggableElementRef: handlebarRef,
 | 
				
			||||||
 | 
					    segmentDuration,
 | 
				
			||||||
 | 
					    showDraggableElement: showHandlebar,
 | 
				
			||||||
 | 
					    draggableElementTime: handlebarTime,
 | 
				
			||||||
 | 
					    setDraggableElementTime: setHandlebarTime,
 | 
				
			||||||
 | 
					    initialScrollIntoViewOnly: onlyInitialHandlebarScroll,
 | 
				
			||||||
 | 
					    timelineDuration,
 | 
				
			||||||
 | 
					    timelineStartAligned,
 | 
				
			||||||
 | 
					    isDragging: isDraggingHandlebar,
 | 
				
			||||||
 | 
					    setIsDragging: setIsDraggingHandlebar,
 | 
				
			||||||
 | 
					    draggableElementTimeRef: handlebarTimeRef,
 | 
				
			||||||
 | 
					    dense,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    handleMouseDown: exportStartMouseDown,
 | 
				
			||||||
 | 
					    handleMouseUp: exportStartMouseUp,
 | 
				
			||||||
 | 
					    handleMouseMove: exportStartMouseMove,
 | 
				
			||||||
 | 
					  } = useDraggableElement({
 | 
				
			||||||
 | 
					    contentRef,
 | 
				
			||||||
 | 
					    timelineRef,
 | 
				
			||||||
 | 
					    draggableElementRef: exportStartRef,
 | 
				
			||||||
 | 
					    segmentDuration,
 | 
				
			||||||
 | 
					    showDraggableElement: showExportHandles,
 | 
				
			||||||
 | 
					    draggableElementTime: exportStartTime,
 | 
				
			||||||
 | 
					    draggableElementLatestTime: paddedExportEndTime,
 | 
				
			||||||
 | 
					    setDraggableElementTime: setExportStartTime,
 | 
				
			||||||
 | 
					    alignSetTimeToSegment: true,
 | 
				
			||||||
 | 
					    timelineDuration,
 | 
				
			||||||
 | 
					    timelineStartAligned,
 | 
				
			||||||
 | 
					    isDragging: isDraggingExportStart,
 | 
				
			||||||
 | 
					    setIsDragging: setIsDraggingExportStart,
 | 
				
			||||||
 | 
					    draggableElementTimeRef: exportStartTimeRef,
 | 
				
			||||||
 | 
					    setDraggableElementPosition: setExportStartPosition,
 | 
				
			||||||
 | 
					    dense,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    handleMouseDown: exportEndMouseDown,
 | 
				
			||||||
 | 
					    handleMouseUp: exportEndMouseUp,
 | 
				
			||||||
 | 
					    handleMouseMove: exportEndMouseMove,
 | 
				
			||||||
 | 
					  } = useDraggableElement({
 | 
				
			||||||
 | 
					    contentRef,
 | 
				
			||||||
 | 
					    timelineRef,
 | 
				
			||||||
 | 
					    draggableElementRef: exportEndRef,
 | 
				
			||||||
 | 
					    segmentDuration,
 | 
				
			||||||
 | 
					    showDraggableElement: showExportHandles,
 | 
				
			||||||
 | 
					    draggableElementTime: exportEndTime,
 | 
				
			||||||
 | 
					    draggableElementEarliestTime: paddedExportStartTime,
 | 
				
			||||||
 | 
					    setDraggableElementTime: setExportEndTime,
 | 
				
			||||||
 | 
					    alignSetTimeToSegment: true,
 | 
				
			||||||
 | 
					    timelineDuration,
 | 
				
			||||||
 | 
					    timelineStartAligned,
 | 
				
			||||||
 | 
					    isDragging: isDraggingExportEnd,
 | 
				
			||||||
 | 
					    setIsDragging: setIsDraggingExportEnd,
 | 
				
			||||||
 | 
					    draggableElementTimeRef: exportEndTimeRef,
 | 
				
			||||||
 | 
					    setDraggableElementPosition: setExportEndPosition,
 | 
				
			||||||
 | 
					    dense,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleHandlebar = useCallback(
 | 
					  const handleHandlebar = useCallback(
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
      e:
 | 
					      e:
 | 
				
			||||||
@ -177,6 +244,19 @@ export function ReviewTimeline({
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const textSizeClasses = useCallback(
 | 
				
			||||||
 | 
					    (draggableElement: DraggableElement) => {
 | 
				
			||||||
 | 
					      if (isDragging && isMobile && draggableElementType === draggableElement) {
 | 
				
			||||||
 | 
					        return "text-lg";
 | 
				
			||||||
 | 
					      } else if (dense) {
 | 
				
			||||||
 | 
					        return "text-[8px] md:text-xs";
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return "text-xs";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [dense, isDragging, draggableElementType],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      exportSectionRef.current &&
 | 
					      exportSectionRef.current &&
 | 
				
			||||||
@ -218,6 +298,12 @@ export function ReviewTimeline({
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  }, [handleMouseMove, handleMouseUp, isDragging]);
 | 
					  }, [handleMouseMove, handleMouseUp, isDragging]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (onHandlebarDraggingChange) {
 | 
				
			||||||
 | 
					      onHandlebarDraggingChange(isDraggingHandlebar);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [isDraggingHandlebar, onHandlebarDraggingChange]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      ref={timelineRef}
 | 
					      ref={timelineRef}
 | 
				
			||||||
@ -234,7 +320,7 @@ export function ReviewTimeline({
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {showHandlebar && (
 | 
					      {showHandlebar && (
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
 | 
					          className={`absolute left-0 top-0 ${isDraggingHandlebar && isIOS ? "" : "z-20"} w-full`}
 | 
				
			||||||
          role="scrollbar"
 | 
					          role="scrollbar"
 | 
				
			||||||
          ref={handlebarRef}
 | 
					          ref={handlebarRef}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
@ -245,21 +331,25 @@ export function ReviewTimeline({
 | 
				
			|||||||
          >
 | 
					          >
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className={`relative w-full ${
 | 
					              className={`relative w-full ${
 | 
				
			||||||
                isDragging ? "cursor-grabbing" : "cursor-grab"
 | 
					                isDraggingHandlebar ? "cursor-grabbing" : "cursor-grab"
 | 
				
			||||||
              }`}
 | 
					              }`}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <div
 | 
					              <div
 | 
				
			||||||
                className={`bg-destructive rounded-full mx-auto ${
 | 
					                className={`bg-destructive rounded-full mx-auto ${
 | 
				
			||||||
                  segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
 | 
					                  dense
 | 
				
			||||||
                } h-5 ${isDragging && isMobile && draggableElementType == "handlebar" ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
 | 
					                    ? "w-12 md:w-20"
 | 
				
			||||||
 | 
					                    : segmentDuration < 60
 | 
				
			||||||
 | 
					                      ? "w-24"
 | 
				
			||||||
 | 
					                      : "w-20"
 | 
				
			||||||
 | 
					                } h-5 ${isDraggingHandlebar && isMobile ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                  ref={handlebarTimeRef}
 | 
					                  ref={handlebarTimeRef}
 | 
				
			||||||
                  className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "handlebar" ? "text-lg" : "text-[8px] md:text-xs"} z-10`}
 | 
					                  className={`text-white pointer-events-none ${textSizeClasses("handlebar")} z-10`}
 | 
				
			||||||
                ></div>
 | 
					                ></div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div
 | 
					              <div
 | 
				
			||||||
                className={`absolute h-[4px] w-full bg-destructive ${isDragging && isMobile && draggableElementType == "handlebar" ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
 | 
					                className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
 | 
				
			||||||
              ></div>
 | 
					              ></div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@ -268,7 +358,7 @@ export function ReviewTimeline({
 | 
				
			|||||||
      {showExportHandles && (
 | 
					      {showExportHandles && (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
 | 
					            className={`export-end absolute left-0 top-0 ${isDraggingExportEnd && isIOS ? "" : "z-20"} w-full`}
 | 
				
			||||||
            role="scrollbar"
 | 
					            role="scrollbar"
 | 
				
			||||||
            ref={exportEndRef}
 | 
					            ref={exportEndRef}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@ -279,21 +369,25 @@ export function ReviewTimeline({
 | 
				
			|||||||
            >
 | 
					            >
 | 
				
			||||||
              <div
 | 
					              <div
 | 
				
			||||||
                className={`relative mt-[6.5px] w-full ${
 | 
					                className={`relative mt-[6.5px] w-full ${
 | 
				
			||||||
                  isDragging ? "cursor-grabbing" : "cursor-grab"
 | 
					                  isDraggingExportEnd ? "cursor-grabbing" : "cursor-grab"
 | 
				
			||||||
                }`}
 | 
					                }`}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                  className={`bg-selected -mt-4 mx-auto ${
 | 
					                  className={`bg-selected -mt-4 mx-auto ${
 | 
				
			||||||
                    segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
 | 
					                    dense
 | 
				
			||||||
                  } h-5 ${isDragging && isMobile && draggableElementType == "export_end" ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
 | 
					                      ? "w-12 md:w-20"
 | 
				
			||||||
 | 
					                      : segmentDuration < 60
 | 
				
			||||||
 | 
					                        ? "w-24"
 | 
				
			||||||
 | 
					                        : "w-20"
 | 
				
			||||||
 | 
					                  } h-5 ${isDraggingExportEnd && isMobile ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <div
 | 
					                  <div
 | 
				
			||||||
                    ref={exportEndTimeRef}
 | 
					                    ref={exportEndTimeRef}
 | 
				
			||||||
                    className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_end" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
 | 
					                    className={`text-white pointer-events-none ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`}
 | 
				
			||||||
                  ></div>
 | 
					                  ></div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                  className={`absolute h-[4px] w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_end" ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
 | 
					                  className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
 | 
				
			||||||
                ></div>
 | 
					                ></div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -303,7 +397,7 @@ export function ReviewTimeline({
 | 
				
			|||||||
            className="bg-selected/50 absolute w-full"
 | 
					            className="bg-selected/50 absolute w-full"
 | 
				
			||||||
          ></div>
 | 
					          ></div>
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
 | 
					            className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
 | 
				
			||||||
            role="scrollbar"
 | 
					            role="scrollbar"
 | 
				
			||||||
            ref={exportStartRef}
 | 
					            ref={exportStartRef}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@ -318,16 +412,20 @@ export function ReviewTimeline({
 | 
				
			|||||||
                }`}
 | 
					                }`}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                  className={`absolute h-[4px] w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_start" ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
 | 
					                  className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
 | 
				
			||||||
                ></div>
 | 
					                ></div>
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                  className={`bg-selected mt-4 mx-auto ${
 | 
					                  className={`bg-selected mt-4 mx-auto ${
 | 
				
			||||||
                    segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
 | 
					                    dense
 | 
				
			||||||
                  } h-5 ${isDragging && isMobile && draggableElementType == "export_start" ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
 | 
					                      ? "w-12 md:w-20"
 | 
				
			||||||
 | 
					                      : segmentDuration < 60
 | 
				
			||||||
 | 
					                        ? "w-24"
 | 
				
			||||||
 | 
					                        : "w-20"
 | 
				
			||||||
 | 
					                  } h-5 ${isDraggingExportStart && isMobile ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <div
 | 
					                  <div
 | 
				
			||||||
                    ref={exportStartTimeRef}
 | 
					                    ref={exportStartTimeRef}
 | 
				
			||||||
                    className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_start" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
 | 
					                    className={`text-white pointer-events-none ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`}
 | 
				
			||||||
                  ></div>
 | 
					                  ></div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,10 @@
 | 
				
			|||||||
import { isDesktop } from "react-device-detect";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type MinimapSegmentProps = {
 | 
					type MinimapSegmentProps = {
 | 
				
			||||||
  isFirstSegmentInMinimap: boolean;
 | 
					  isFirstSegmentInMinimap: boolean;
 | 
				
			||||||
  isLastSegmentInMinimap: boolean;
 | 
					  isLastSegmentInMinimap: boolean;
 | 
				
			||||||
  alignedMinimapStartTime: number;
 | 
					  alignedMinimapStartTime: number;
 | 
				
			||||||
  alignedMinimapEndTime: number;
 | 
					  alignedMinimapEndTime: number;
 | 
				
			||||||
  firstMinimapSegmentRef: React.MutableRefObject<HTMLDivElement | null>;
 | 
					  firstMinimapSegmentRef: React.MutableRefObject<HTMLDivElement | null>;
 | 
				
			||||||
 | 
					  dense: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TickSegmentProps = {
 | 
					type TickSegmentProps = {
 | 
				
			||||||
@ -27,6 +26,7 @@ export function MinimapBounds({
 | 
				
			|||||||
  alignedMinimapStartTime,
 | 
					  alignedMinimapStartTime,
 | 
				
			||||||
  alignedMinimapEndTime,
 | 
					  alignedMinimapEndTime,
 | 
				
			||||||
  firstMinimapSegmentRef,
 | 
					  firstMinimapSegmentRef,
 | 
				
			||||||
 | 
					  dense,
 | 
				
			||||||
}: MinimapSegmentProps) {
 | 
					}: MinimapSegmentProps) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
@ -38,7 +38,7 @@ export function MinimapBounds({
 | 
				
			|||||||
          {new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
 | 
					          {new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
 | 
				
			||||||
            hour: "2-digit",
 | 
					            hour: "2-digit",
 | 
				
			||||||
            minute: "2-digit",
 | 
					            minute: "2-digit",
 | 
				
			||||||
            ...(isDesktop && { month: "short", day: "2-digit" }),
 | 
					            ...(!dense && { month: "short", day: "2-digit" }),
 | 
				
			||||||
          })}
 | 
					          })}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
@ -48,7 +48,7 @@ export function MinimapBounds({
 | 
				
			|||||||
          {new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
 | 
					          {new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
 | 
				
			||||||
            hour: "2-digit",
 | 
					            hour: "2-digit",
 | 
				
			||||||
            minute: "2-digit",
 | 
					            minute: "2-digit",
 | 
				
			||||||
            ...(isDesktop && { month: "short", day: "2-digit" }),
 | 
					            ...(!dense && { month: "short", day: "2-digit" }),
 | 
				
			||||||
          })}
 | 
					          })}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
 | 
					import { useCallback, useEffect, useMemo, useState } from "react";
 | 
				
			||||||
import { isDesktop, isMobile } from "react-device-detect";
 | 
					import { isMobile } from "react-device-detect";
 | 
				
			||||||
import scrollIntoView from "scroll-into-view-if-needed";
 | 
					import scrollIntoView from "scroll-into-view-if-needed";
 | 
				
			||||||
import { useTimelineUtils } from "./use-timeline-utils";
 | 
					import { useTimelineUtils } from "./use-timeline-utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -13,6 +13,7 @@ type DraggableElementProps = {
 | 
				
			|||||||
  draggableElementEarliestTime?: number;
 | 
					  draggableElementEarliestTime?: number;
 | 
				
			||||||
  draggableElementLatestTime?: number;
 | 
					  draggableElementLatestTime?: number;
 | 
				
			||||||
  setDraggableElementTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
					  setDraggableElementTime?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
 | 
					  alignSetTimeToSegment?: boolean;
 | 
				
			||||||
  initialScrollIntoViewOnly?: boolean;
 | 
					  initialScrollIntoViewOnly?: boolean;
 | 
				
			||||||
  draggableElementTimeRef: React.MutableRefObject<HTMLDivElement | null>;
 | 
					  draggableElementTimeRef: React.MutableRefObject<HTMLDivElement | null>;
 | 
				
			||||||
  timelineDuration: number;
 | 
					  timelineDuration: number;
 | 
				
			||||||
@ -21,6 +22,7 @@ type DraggableElementProps = {
 | 
				
			|||||||
  isDragging: boolean;
 | 
					  isDragging: boolean;
 | 
				
			||||||
  setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
 | 
					  setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
 | 
				
			||||||
  setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
 | 
					  setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
 | 
				
			||||||
 | 
					  dense: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function useDraggableElement({
 | 
					function useDraggableElement({
 | 
				
			||||||
@ -33,6 +35,7 @@ function useDraggableElement({
 | 
				
			|||||||
  draggableElementEarliestTime,
 | 
					  draggableElementEarliestTime,
 | 
				
			||||||
  draggableElementLatestTime,
 | 
					  draggableElementLatestTime,
 | 
				
			||||||
  setDraggableElementTime,
 | 
					  setDraggableElementTime,
 | 
				
			||||||
 | 
					  alignSetTimeToSegment = false,
 | 
				
			||||||
  initialScrollIntoViewOnly,
 | 
					  initialScrollIntoViewOnly,
 | 
				
			||||||
  draggableElementTimeRef,
 | 
					  draggableElementTimeRef,
 | 
				
			||||||
  timelineDuration,
 | 
					  timelineDuration,
 | 
				
			||||||
@ -41,38 +44,39 @@ function useDraggableElement({
 | 
				
			|||||||
  isDragging,
 | 
					  isDragging,
 | 
				
			||||||
  setIsDragging,
 | 
					  setIsDragging,
 | 
				
			||||||
  setDraggableElementPosition,
 | 
					  setDraggableElementPosition,
 | 
				
			||||||
 | 
					  dense,
 | 
				
			||||||
}: DraggableElementProps) {
 | 
					}: DraggableElementProps) {
 | 
				
			||||||
  const segmentHeight = 8;
 | 
					 | 
				
			||||||
  const [clientYPosition, setClientYPosition] = useState<number | null>(null);
 | 
					  const [clientYPosition, setClientYPosition] = useState<number | null>(null);
 | 
				
			||||||
  const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
 | 
					  const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
 | 
				
			||||||
  const [elementScrollIntoView, setElementScrollIntoView] = useState(true);
 | 
					  const [elementScrollIntoView, setElementScrollIntoView] = useState(true);
 | 
				
			||||||
  const [scrollEdgeSize, setScrollEdgeSize] = useState<number>();
 | 
					  const [scrollEdgeSize, setScrollEdgeSize] = useState<number>();
 | 
				
			||||||
  const [fullTimelineHeight, setFullTimelineHeight] = useState<number>();
 | 
					  const [fullTimelineHeight, setFullTimelineHeight] = useState<number>();
 | 
				
			||||||
  const [segments, setSegments] = useState<HTMLDivElement[]>([]);
 | 
					  const [segments, setSegments] = useState<HTMLDivElement[]>([]);
 | 
				
			||||||
  const { alignStartDateToTimeline, getCumulativeScrollTop } = useTimelineUtils(
 | 
					  const { alignStartDateToTimeline, getCumulativeScrollTop, segmentHeight } =
 | 
				
			||||||
    {
 | 
					    useTimelineUtils({
 | 
				
			||||||
      segmentDuration: segmentDuration,
 | 
					      segmentDuration: segmentDuration,
 | 
				
			||||||
      timelineDuration: timelineDuration,
 | 
					      timelineDuration: timelineDuration,
 | 
				
			||||||
      timelineRef,
 | 
					      timelineRef,
 | 
				
			||||||
    },
 | 
					    });
 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const draggingAtTopEdge = useMemo(() => {
 | 
					  const draggingAtTopEdge = useMemo(() => {
 | 
				
			||||||
    if (clientYPosition && timelineRef.current && scrollEdgeSize) {
 | 
					    if (clientYPosition && timelineRef.current && scrollEdgeSize) {
 | 
				
			||||||
 | 
					      const timelineRect = timelineRef.current.getBoundingClientRect();
 | 
				
			||||||
 | 
					      const timelineTopAbsolute = timelineRect.top;
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        clientYPosition - timelineRef.current.offsetTop < scrollEdgeSize &&
 | 
					        clientYPosition - timelineTopAbsolute < scrollEdgeSize && isDragging
 | 
				
			||||||
        isDragging
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [clientYPosition, timelineRef, isDragging, scrollEdgeSize]);
 | 
					  }, [clientYPosition, timelineRef, isDragging, scrollEdgeSize]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const draggingAtBottomEdge = useMemo(() => {
 | 
					  const draggingAtBottomEdge = useMemo(() => {
 | 
				
			||||||
    if (clientYPosition && timelineRef.current && scrollEdgeSize) {
 | 
					    if (clientYPosition && timelineRef.current && scrollEdgeSize) {
 | 
				
			||||||
 | 
					      const timelineRect = timelineRef.current.getBoundingClientRect();
 | 
				
			||||||
 | 
					      const timelineTopAbsolute = timelineRect.top;
 | 
				
			||||||
 | 
					      const timelineHeightAbsolute = timelineRect.height;
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        clientYPosition >
 | 
					        timelineTopAbsolute + timelineHeightAbsolute - clientYPosition <
 | 
				
			||||||
          timelineRef.current.clientHeight +
 | 
					          scrollEdgeSize && isDragging
 | 
				
			||||||
            timelineRef.current.offsetTop -
 | 
					 | 
				
			||||||
            scrollEdgeSize && isDragging
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [clientYPosition, timelineRef, isDragging, scrollEdgeSize]);
 | 
					  }, [clientYPosition, timelineRef, isDragging, scrollEdgeSize]);
 | 
				
			||||||
@ -141,7 +145,7 @@ function useDraggableElement({
 | 
				
			|||||||
    (time: number) => {
 | 
					    (time: number) => {
 | 
				
			||||||
      return ((timelineStartAligned - time) / segmentDuration) * segmentHeight;
 | 
					      return ((timelineStartAligned - time) / segmentDuration) * segmentHeight;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    [segmentDuration, timelineStartAligned],
 | 
					    [segmentDuration, timelineStartAligned, segmentHeight],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const updateDraggableElementPosition = useCallback(
 | 
					  const updateDraggableElementPosition = useCallback(
 | 
				
			||||||
@ -165,7 +169,7 @@ function useDraggableElement({
 | 
				
			|||||||
            ).toLocaleTimeString([], {
 | 
					            ).toLocaleTimeString([], {
 | 
				
			||||||
              hour: "2-digit",
 | 
					              hour: "2-digit",
 | 
				
			||||||
              minute: "2-digit",
 | 
					              minute: "2-digit",
 | 
				
			||||||
              ...(segmentDuration < 60 && isDesktop && { second: "2-digit" }),
 | 
					              ...(segmentDuration < 60 && !dense && { second: "2-digit" }),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            if (scrollTimeline) {
 | 
					            if (scrollTimeline) {
 | 
				
			||||||
              scrollIntoView(thumb, {
 | 
					              scrollIntoView(thumb, {
 | 
				
			||||||
@ -188,6 +192,7 @@ function useDraggableElement({
 | 
				
			|||||||
      draggableElementRef,
 | 
					      draggableElementRef,
 | 
				
			||||||
      setDraggableElementTime,
 | 
					      setDraggableElementTime,
 | 
				
			||||||
      setDraggableElementPosition,
 | 
					      setDraggableElementPosition,
 | 
				
			||||||
 | 
					      dense,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -322,9 +327,13 @@ function useDraggableElement({
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (setDraggableElementTime) {
 | 
					        if (setDraggableElementTime) {
 | 
				
			||||||
          setDraggableElementTime(
 | 
					          if (alignSetTimeToSegment) {
 | 
				
			||||||
            targetSegmentId + segmentDuration * (offset / segmentHeight),
 | 
					            setDraggableElementTime(targetSegmentId);
 | 
				
			||||||
          );
 | 
					          } else {
 | 
				
			||||||
 | 
					            setDraggableElementTime(
 | 
				
			||||||
 | 
					              targetSegmentId + segmentDuration * (offset / segmentHeight),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (draggingAtTopEdge || draggingAtBottomEdge) {
 | 
					        if (draggingAtTopEdge || draggingAtBottomEdge) {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,8 @@ export function useTimelineUtils({
 | 
				
			|||||||
  timelineDuration,
 | 
					  timelineDuration,
 | 
				
			||||||
  timelineRef,
 | 
					  timelineRef,
 | 
				
			||||||
}: TimelineUtilsProps) {
 | 
					}: TimelineUtilsProps) {
 | 
				
			||||||
 | 
					  const segmentHeight = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const alignEndDateToTimeline = useCallback(
 | 
					  const alignEndDateToTimeline = useCallback(
 | 
				
			||||||
    (time: number): number => {
 | 
					    (time: number): number => {
 | 
				
			||||||
      const remainder = time % segmentDuration;
 | 
					      const remainder = time % segmentDuration;
 | 
				
			||||||
@ -42,8 +44,6 @@ export function useTimelineUtils({
 | 
				
			|||||||
    if (timelineRef?.current && timelineDuration) {
 | 
					    if (timelineRef?.current && timelineDuration) {
 | 
				
			||||||
      const { clientHeight: visibleTimelineHeight } = timelineRef.current;
 | 
					      const { clientHeight: visibleTimelineHeight } = timelineRef.current;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const segmentHeight = 8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const visibleTime =
 | 
					      const visibleTime =
 | 
				
			||||||
        (visibleTimelineHeight / segmentHeight) * segmentDuration;
 | 
					        (visibleTimelineHeight / segmentHeight) * segmentDuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,5 +56,6 @@ export function useTimelineUtils({
 | 
				
			|||||||
    alignStartDateToTimeline,
 | 
					    alignStartDateToTimeline,
 | 
				
			||||||
    getCumulativeScrollTop,
 | 
					    getCumulativeScrollTop,
 | 
				
			||||||
    getVisibleTimelineDuration,
 | 
					    getVisibleTimelineDuration,
 | 
				
			||||||
 | 
					    segmentHeight,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ import { Switch } from "@/components/ui/switch";
 | 
				
			|||||||
import { Label } from "@/components/ui/label";
 | 
					import { Label } from "@/components/ui/label";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
import SummaryTimeline from "@/components/timeline/SummaryTimeline";
 | 
					import SummaryTimeline from "@/components/timeline/SummaryTimeline";
 | 
				
			||||||
 | 
					import { isMobile } from "react-device-detect";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Color data
 | 
					// Color data
 | 
				
			||||||
const colors = [
 | 
					const colors = [
 | 
				
			||||||
@ -384,6 +385,7 @@ function UIPlayground() {
 | 
				
			|||||||
                motion_events={mockMotionData}
 | 
					                motion_events={mockMotionData}
 | 
				
			||||||
                severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
 | 
					                severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
 | 
				
			||||||
                contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
 | 
					                contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
 | 
				
			||||||
 | 
					                dense={isMobile} // dense will produce a smaller handlebar and only minute resolution on timestamps
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            {isEventsReviewTimeline && (
 | 
					            {isEventsReviewTimeline && (
 | 
				
			||||||
@ -408,6 +410,7 @@ function UIPlayground() {
 | 
				
			|||||||
                severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
 | 
					                severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
 | 
				
			||||||
                contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
 | 
					                contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
 | 
				
			||||||
                timelineRef={reviewTimelineRef} // save a ref to this timeline to connect with the summary timeline
 | 
					                timelineRef={reviewTimelineRef} // save a ref to this timeline to connect with the summary timeline
 | 
				
			||||||
 | 
					                dense // dense will produce a smaller handlebar and only minute resolution on timestamps
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -580,6 +580,7 @@ function DetectionReview({
 | 
				
			|||||||
            severityType={severity}
 | 
					            severityType={severity}
 | 
				
			||||||
            contentRef={contentRef}
 | 
					            contentRef={contentRef}
 | 
				
			||||||
            timelineRef={reviewTimelineRef}
 | 
					            timelineRef={reviewTimelineRef}
 | 
				
			||||||
 | 
					            dense={isMobile}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="w-[10px]">
 | 
					        <div className="w-[10px]">
 | 
				
			||||||
@ -864,6 +865,7 @@ function MotionReview({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            setScrubbing(scrubbing);
 | 
					            setScrubbing(scrubbing);
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
 | 
					          dense={isMobile}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user