2024-03-18 23:58:54 +03:00
|
|
|
import useDraggableElement from "@/hooks/use-draggable-element";
|
2024-03-04 19:42:51 +03:00
|
|
|
import {
|
|
|
|
|
useEffect,
|
|
|
|
|
useCallback,
|
|
|
|
|
useMemo,
|
|
|
|
|
useRef,
|
|
|
|
|
useState,
|
|
|
|
|
RefObject,
|
|
|
|
|
} from "react";
|
|
|
|
|
import MotionSegment from "./MotionSegment";
|
2024-03-18 23:58:54 +03:00
|
|
|
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
2024-03-05 22:55:44 +03:00
|
|
|
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
2024-03-04 19:42:51 +03:00
|
|
|
import ReviewTimeline from "./ReviewTimeline";
|
2024-03-21 17:00:04 +03:00
|
|
|
import { isDesktop } from "react-device-detect";
|
2024-03-04 19:42:51 +03:00
|
|
|
|
|
|
|
|
export type MotionReviewTimelineProps = {
|
|
|
|
|
segmentDuration: number;
|
|
|
|
|
timestampSpread: number;
|
|
|
|
|
timelineStart: number;
|
|
|
|
|
timelineEnd: number;
|
|
|
|
|
showHandlebar?: boolean;
|
|
|
|
|
handlebarTime?: number;
|
|
|
|
|
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
2024-03-26 19:29:07 +03:00
|
|
|
onlyInitialHandlebarScroll?: boolean;
|
2024-03-23 16:33:50 +03:00
|
|
|
motionOnly?: boolean;
|
2024-03-04 19:42:51 +03:00
|
|
|
showMinimap?: boolean;
|
|
|
|
|
minimapStartTime?: number;
|
|
|
|
|
minimapEndTime?: number;
|
2024-03-18 23:58:54 +03:00
|
|
|
showExportHandles?: boolean;
|
|
|
|
|
exportStartTime?: number;
|
|
|
|
|
exportEndTime?: number;
|
|
|
|
|
setExportStartTime?: React.Dispatch<React.SetStateAction<number>>;
|
|
|
|
|
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
2024-03-04 19:42:51 +03:00
|
|
|
events: ReviewSegment[];
|
2024-03-05 22:55:44 +03:00
|
|
|
motion_events: MotionData[];
|
2024-03-04 19:42:51 +03:00
|
|
|
severityType: ReviewSeverity;
|
|
|
|
|
contentRef: RefObject<HTMLDivElement>;
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef?: RefObject<HTMLDivElement>;
|
2024-03-04 19:42:51 +03:00
|
|
|
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function MotionReviewTimeline({
|
|
|
|
|
segmentDuration,
|
|
|
|
|
timestampSpread,
|
|
|
|
|
timelineStart,
|
|
|
|
|
timelineEnd,
|
|
|
|
|
showHandlebar = false,
|
|
|
|
|
handlebarTime,
|
|
|
|
|
setHandlebarTime,
|
2024-03-26 19:29:07 +03:00
|
|
|
onlyInitialHandlebarScroll = false,
|
2024-03-23 16:33:50 +03:00
|
|
|
motionOnly = false,
|
2024-03-04 19:42:51 +03:00
|
|
|
showMinimap = false,
|
|
|
|
|
minimapStartTime,
|
|
|
|
|
minimapEndTime,
|
2024-03-18 23:58:54 +03:00
|
|
|
showExportHandles = false,
|
|
|
|
|
exportStartTime,
|
|
|
|
|
exportEndTime,
|
|
|
|
|
setExportStartTime,
|
|
|
|
|
setExportEndTime,
|
2024-03-04 19:42:51 +03:00
|
|
|
events,
|
|
|
|
|
motion_events,
|
|
|
|
|
contentRef,
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef,
|
2024-03-04 19:42:51 +03:00
|
|
|
onHandlebarDraggingChange,
|
|
|
|
|
}: MotionReviewTimelineProps) {
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
2024-03-18 23:58:54 +03:00
|
|
|
const [exportStartPosition, setExportStartPosition] = useState(0);
|
|
|
|
|
const [exportEndPosition, setExportEndPosition] = useState(0);
|
|
|
|
|
|
2024-03-21 05:56:15 +03:00
|
|
|
const internalTimelineRef = useRef<HTMLDivElement>(null);
|
2024-03-18 23:58:54 +03:00
|
|
|
const handlebarRef = useRef<HTMLDivElement>(null);
|
2024-03-04 19:42:51 +03:00
|
|
|
const handlebarTimeRef = useRef<HTMLDivElement>(null);
|
2024-03-18 23:58:54 +03:00
|
|
|
const exportStartRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const exportStartTimeRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const exportEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const exportEndTimeRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
2024-03-04 19:42:51 +03:00
|
|
|
const timelineDuration = useMemo(
|
2024-03-12 18:23:54 +03:00
|
|
|
() => timelineStart - timelineEnd + 4 * segmentDuration,
|
|
|
|
|
[timelineEnd, timelineStart, segmentDuration],
|
2024-03-04 19:42:51 +03:00
|
|
|
);
|
|
|
|
|
|
2024-03-21 20:49:04 +03:00
|
|
|
const { alignStartDateToTimeline, alignEndDateToTimeline } = useTimelineUtils(
|
|
|
|
|
{
|
|
|
|
|
segmentDuration,
|
|
|
|
|
timelineDuration,
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-03-04 19:42:51 +03:00
|
|
|
|
2024-03-09 01:49:10 +03:00
|
|
|
const timelineStartAligned = useMemo(
|
2024-03-12 18:23:54 +03:00
|
|
|
() => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration,
|
|
|
|
|
[timelineStart, alignStartDateToTimeline, segmentDuration],
|
2024-03-09 01:49:10 +03:00
|
|
|
);
|
|
|
|
|
|
2024-03-18 23:58:54 +03:00
|
|
|
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,
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef: timelineRef || internalTimelineRef,
|
2024-03-18 23:58:54 +03:00
|
|
|
draggableElementRef: handlebarRef,
|
|
|
|
|
segmentDuration,
|
|
|
|
|
showDraggableElement: showHandlebar,
|
|
|
|
|
draggableElementTime: handlebarTime,
|
|
|
|
|
setDraggableElementTime: setHandlebarTime,
|
2024-03-26 19:29:07 +03:00
|
|
|
initialScrollIntoViewOnly: onlyInitialHandlebarScroll,
|
2024-03-18 23:58:54 +03:00
|
|
|
timelineDuration,
|
2024-03-23 16:33:50 +03:00
|
|
|
timelineCollapsed: motionOnly,
|
2024-03-18 23:58:54 +03:00
|
|
|
timelineStartAligned,
|
|
|
|
|
isDragging,
|
|
|
|
|
setIsDragging,
|
|
|
|
|
draggableElementTimeRef: handlebarTimeRef,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
handleMouseDown: exportStartMouseDown,
|
|
|
|
|
handleMouseUp: exportStartMouseUp,
|
|
|
|
|
handleMouseMove: exportStartMouseMove,
|
|
|
|
|
} = useDraggableElement({
|
|
|
|
|
contentRef,
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef: timelineRef || internalTimelineRef,
|
2024-03-18 23:58:54 +03:00
|
|
|
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,
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef: timelineRef || internalTimelineRef,
|
2024-03-18 23:58:54 +03:00
|
|
|
draggableElementRef: exportEndRef,
|
|
|
|
|
segmentDuration,
|
|
|
|
|
showDraggableElement: showExportHandles,
|
|
|
|
|
draggableElementTime: exportEndTime,
|
|
|
|
|
draggableElementEarliestTime: paddedExportStartTime,
|
|
|
|
|
setDraggableElementTime: setExportEndTime,
|
|
|
|
|
timelineDuration,
|
|
|
|
|
timelineStartAligned,
|
|
|
|
|
isDragging,
|
|
|
|
|
setIsDragging,
|
|
|
|
|
draggableElementTimeRef: exportEndTimeRef,
|
|
|
|
|
setDraggableElementPosition: setExportEndPosition,
|
|
|
|
|
});
|
2024-03-04 19:42:51 +03:00
|
|
|
|
|
|
|
|
// Generate segments for the timeline
|
|
|
|
|
const generateSegments = useCallback(() => {
|
2024-03-27 00:36:28 +03:00
|
|
|
const segmentCount = Math.ceil(timelineDuration / segmentDuration);
|
2024-03-04 19:42:51 +03:00
|
|
|
|
|
|
|
|
return Array.from({ length: segmentCount }, (_, index) => {
|
2024-03-09 01:49:10 +03:00
|
|
|
const segmentTime = timelineStartAligned - index * segmentDuration;
|
2024-03-04 19:42:51 +03:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<MotionSegment
|
|
|
|
|
key={segmentTime}
|
|
|
|
|
events={events}
|
|
|
|
|
motion_events={motion_events}
|
|
|
|
|
segmentDuration={segmentDuration}
|
|
|
|
|
segmentTime={segmentTime}
|
|
|
|
|
timestampSpread={timestampSpread}
|
2024-03-23 16:33:50 +03:00
|
|
|
motionOnly={motionOnly}
|
2024-03-04 19:42:51 +03:00
|
|
|
showMinimap={showMinimap}
|
|
|
|
|
minimapStartTime={minimapStartTime}
|
|
|
|
|
minimapEndTime={minimapEndTime}
|
2024-03-07 00:35:10 +03:00
|
|
|
setHandlebarTime={setHandlebarTime}
|
2024-03-04 19:42:51 +03:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
// we know that these deps are correct
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [
|
|
|
|
|
segmentDuration,
|
|
|
|
|
timestampSpread,
|
2024-03-09 01:49:10 +03:00
|
|
|
timelineStartAligned,
|
2024-03-04 19:42:51 +03:00
|
|
|
timelineDuration,
|
|
|
|
|
showMinimap,
|
|
|
|
|
minimapStartTime,
|
|
|
|
|
minimapEndTime,
|
|
|
|
|
events,
|
2024-03-05 01:18:27 +03:00
|
|
|
motion_events,
|
2024-03-23 16:33:50 +03:00
|
|
|
motionOnly,
|
2024-03-04 19:42:51 +03:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const segments = useMemo(
|
|
|
|
|
() => generateSegments(),
|
|
|
|
|
// we know that these deps are correct
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
[
|
|
|
|
|
segmentDuration,
|
|
|
|
|
timestampSpread,
|
2024-03-09 01:49:10 +03:00
|
|
|
timelineStartAligned,
|
2024-03-04 19:42:51 +03:00
|
|
|
timelineDuration,
|
|
|
|
|
showMinimap,
|
|
|
|
|
minimapStartTime,
|
|
|
|
|
minimapEndTime,
|
|
|
|
|
events,
|
2024-03-05 01:18:27 +03:00
|
|
|
motion_events,
|
2024-03-23 16:33:50 +03:00
|
|
|
motionOnly,
|
2024-03-04 19:42:51 +03:00
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (onHandlebarDraggingChange) {
|
|
|
|
|
onHandlebarDraggingChange(isDragging);
|
|
|
|
|
}
|
|
|
|
|
}, [isDragging, onHandlebarDraggingChange]);
|
|
|
|
|
|
2024-03-21 05:56:15 +03:00
|
|
|
const segmentsObserver = useRef<IntersectionObserver | null>(null);
|
|
|
|
|
const selectedTimelineRef = timelineRef || internalTimelineRef;
|
|
|
|
|
useEffect(() => {
|
2024-03-21 17:00:04 +03:00
|
|
|
if (selectedTimelineRef.current && segments && isDesktop) {
|
2024-03-21 05:56:15 +03:00
|
|
|
segmentsObserver.current = new IntersectionObserver(
|
|
|
|
|
(entries) => {
|
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
|
if (entry.isIntersecting) {
|
|
|
|
|
const segmentId = entry.target.getAttribute("data-segment-id");
|
|
|
|
|
|
|
|
|
|
const segmentElements =
|
|
|
|
|
internalTimelineRef.current?.querySelectorAll(
|
|
|
|
|
`[data-segment-id="${segmentId}"] .motion-segment`,
|
|
|
|
|
);
|
|
|
|
|
segmentElements?.forEach((segmentElement) => {
|
|
|
|
|
segmentElement.classList.remove("hidden");
|
|
|
|
|
segmentElement.classList.add("animate-in");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
{ threshold: 0 },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Get all segment divs and observe each one
|
|
|
|
|
const segmentDivs =
|
|
|
|
|
selectedTimelineRef.current.querySelectorAll(".segment.has-data");
|
|
|
|
|
segmentDivs.forEach((segmentDiv) => {
|
|
|
|
|
segmentsObserver.current?.observe(segmentDiv);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
segmentsObserver.current?.disconnect();
|
|
|
|
|
};
|
|
|
|
|
}, [selectedTimelineRef, segments]);
|
|
|
|
|
|
2024-03-04 19:42:51 +03:00
|
|
|
return (
|
|
|
|
|
<ReviewTimeline
|
2024-03-21 05:56:15 +03:00
|
|
|
timelineRef={timelineRef || internalTimelineRef}
|
2024-03-10 16:23:36 +03:00
|
|
|
handlebarRef={handlebarRef}
|
2024-03-04 19:42:51 +03:00
|
|
|
handlebarTimeRef={handlebarTimeRef}
|
2024-03-18 23:58:54 +03:00
|
|
|
handlebarMouseMove={handlebarMouseMove}
|
|
|
|
|
handlebarMouseUp={handlebarMouseUp}
|
|
|
|
|
handlebarMouseDown={handlebarMouseDown}
|
2024-03-04 19:42:51 +03:00
|
|
|
segmentDuration={segmentDuration}
|
2024-03-18 23:58:54 +03:00
|
|
|
timelineDuration={timelineDuration}
|
2024-03-04 19:42:51 +03:00
|
|
|
showHandlebar={showHandlebar}
|
|
|
|
|
isDragging={isDragging}
|
2024-03-18 23:58:54 +03:00
|
|
|
exportStartMouseMove={exportStartMouseMove}
|
|
|
|
|
exportStartMouseUp={exportStartMouseUp}
|
|
|
|
|
exportStartMouseDown={exportStartMouseDown}
|
|
|
|
|
exportEndMouseMove={exportEndMouseMove}
|
|
|
|
|
exportEndMouseUp={exportEndMouseUp}
|
|
|
|
|
exportEndMouseDown={exportEndMouseDown}
|
|
|
|
|
showExportHandles={showExportHandles}
|
|
|
|
|
exportStartRef={exportStartRef}
|
|
|
|
|
exportStartTimeRef={exportStartTimeRef}
|
|
|
|
|
exportEndRef={exportEndRef}
|
|
|
|
|
exportEndTimeRef={exportEndTimeRef}
|
|
|
|
|
exportStartPosition={exportStartPosition}
|
|
|
|
|
exportEndPosition={exportEndPosition}
|
2024-03-04 19:42:51 +03:00
|
|
|
>
|
|
|
|
|
{segments}
|
|
|
|
|
</ReviewTimeline>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MotionReviewTimeline;
|