mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +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,37 +44,38 @@ 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 +
|
|
||||||
timelineRef.current.offsetTop -
|
|
||||||
scrollEdgeSize && isDragging
|
scrollEdgeSize && isDragging
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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,10 +327,14 @@ function useDraggableElement({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (setDraggableElementTime) {
|
if (setDraggableElementTime) {
|
||||||
|
if (alignSetTimeToSegment) {
|
||||||
|
setDraggableElementTime(targetSegmentId);
|
||||||
|
} else {
|
||||||
setDraggableElementTime(
|
setDraggableElementTime(
|
||||||
targetSegmentId + segmentDuration * (offset / segmentHeight),
|
targetSegmentId + segmentDuration * (offset / segmentHeight),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (draggingAtTopEdge || draggingAtBottomEdge) {
|
if (draggingAtTopEdge || draggingAtBottomEdge) {
|
||||||
animationFrameId = requestAnimationFrame(handleScroll);
|
animationFrameId = requestAnimationFrame(handleScroll);
|
||||||
|
@ -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