forked from Github/frigate
Timeline fixes and export handles (#10522)
* select an export range from timeline * height tweak
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import useDraggableHandler from "@/hooks/use-handle-dragging";
|
||||
import useDraggableElement from "@/hooks/use-draggable-element";
|
||||
import {
|
||||
useEffect,
|
||||
useCallback,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
RefObject,
|
||||
} from "react";
|
||||
import EventSegment from "./EventSegment";
|
||||
import { useEventUtils } from "@/hooks/use-event-utils";
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
import { ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||
import ReviewTimeline from "./ReviewTimeline";
|
||||
|
||||
@@ -23,6 +23,11 @@ export type EventReviewTimelineProps = {
|
||||
showMinimap?: boolean;
|
||||
minimapStartTime?: number;
|
||||
minimapEndTime?: number;
|
||||
showExportHandles?: boolean;
|
||||
exportStartTime?: number;
|
||||
exportEndTime?: number;
|
||||
setExportStartTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
events: ReviewSegment[];
|
||||
severityType: ReviewSeverity;
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
@@ -40,47 +45,113 @@ export function EventReviewTimeline({
|
||||
showMinimap = false,
|
||||
minimapStartTime,
|
||||
minimapEndTime,
|
||||
showExportHandles = false,
|
||||
exportStartTime,
|
||||
exportEndTime,
|
||||
setExportStartTime,
|
||||
setExportEndTime,
|
||||
events,
|
||||
severityType,
|
||||
contentRef,
|
||||
onHandlebarDraggingChange,
|
||||
}: EventReviewTimelineProps) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const handlebarRef = useRef<HTMLDivElement>(null);
|
||||
const [exportStartPosition, setExportStartPosition] = useState(0);
|
||||
const [exportEndPosition, setExportEndPosition] = useState(0);
|
||||
|
||||
const timelineRef = 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 timelineDuration = useMemo(
|
||||
() => timelineStart - timelineEnd,
|
||||
[timelineEnd, timelineStart],
|
||||
);
|
||||
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
|
||||
events,
|
||||
segmentDuration,
|
||||
);
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } =
|
||||
useTimelineUtils(segmentDuration);
|
||||
|
||||
const timelineStartAligned = useMemo(
|
||||
() => alignStartDateToTimeline(timelineStart),
|
||||
[timelineStart, alignStartDateToTimeline],
|
||||
);
|
||||
|
||||
const { handleMouseDown, handleMouseUp, handleMouseMove } =
|
||||
useDraggableHandler({
|
||||
contentRef,
|
||||
timelineRef,
|
||||
handlebarRef,
|
||||
alignStartDateToTimeline,
|
||||
alignEndDateToTimeline,
|
||||
segmentDuration,
|
||||
showHandlebar,
|
||||
handlebarTime,
|
||||
setHandlebarTime,
|
||||
timelineDuration,
|
||||
timelineStartAligned,
|
||||
isDragging,
|
||||
setIsDragging,
|
||||
handlebarTimeRef,
|
||||
});
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
draggableElementRef: exportEndRef,
|
||||
segmentDuration,
|
||||
showDraggableElement: showExportHandles,
|
||||
draggableElementTime: exportEndTime,
|
||||
draggableElementEarliestTime: paddedExportStartTime,
|
||||
setDraggableElementTime: setExportEndTime,
|
||||
timelineDuration,
|
||||
timelineStartAligned,
|
||||
isDragging,
|
||||
setIsDragging,
|
||||
draggableElementTimeRef: exportEndTimeRef,
|
||||
setDraggableElementPosition: setExportEndPosition,
|
||||
});
|
||||
|
||||
// Generate segments for the timeline
|
||||
const generateSegments = useCallback(() => {
|
||||
@@ -145,12 +216,26 @@ export function EventReviewTimeline({
|
||||
timelineRef={timelineRef}
|
||||
handlebarRef={handlebarRef}
|
||||
handlebarTimeRef={handlebarTimeRef}
|
||||
handleMouseMove={handleMouseMove}
|
||||
handleMouseUp={handleMouseUp}
|
||||
handleMouseDown={handleMouseDown}
|
||||
handlebarMouseMove={handlebarMouseMove}
|
||||
handlebarMouseUp={handlebarMouseUp}
|
||||
handlebarMouseDown={handlebarMouseDown}
|
||||
segmentDuration={segmentDuration}
|
||||
timelineDuration={timelineDuration}
|
||||
showHandlebar={showHandlebar}
|
||||
isDragging={isDragging}
|
||||
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}
|
||||
>
|
||||
{segments}
|
||||
</ReviewTimeline>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useApiHost } from "@/api";
|
||||
import { useEventUtils } from "@/hooks/use-event-utils";
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils";
|
||||
import { ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||
import React, {
|
||||
@@ -53,10 +53,8 @@ export function EventSegment({
|
||||
getEventThumbnail,
|
||||
} = useEventSegmentUtils(segmentDuration, events, severityType);
|
||||
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
|
||||
events,
|
||||
segmentDuration,
|
||||
);
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } =
|
||||
useTimelineUtils(segmentDuration);
|
||||
|
||||
const severity = useMemo(
|
||||
() => getSeverity(segmentTime, displaySeverityType),
|
||||
@@ -155,7 +153,7 @@ export function EventSegment({
|
||||
: ""
|
||||
} ${
|
||||
isFirstSegmentInMinimap || isLastSegmentInMinimap
|
||||
? "relative h-2 border-b-2 border-gray-500"
|
||||
? "relative h-2 border-b-2 border-neutral-600"
|
||||
: ""
|
||||
}`;
|
||||
|
||||
@@ -236,7 +234,7 @@ export function EventSegment({
|
||||
key={`${segmentKey}_${index}_primary_data`}
|
||||
className={`w-full h-2 bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`}
|
||||
onClick={segmentClick}
|
||||
onTouchStart={(event) =>
|
||||
onTouchEnd={(event) =>
|
||||
handleTouchStart(event, segmentClick)
|
||||
}
|
||||
></div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import useDraggableHandler from "@/hooks/use-handle-dragging";
|
||||
import useDraggableElement from "@/hooks/use-draggable-element";
|
||||
import {
|
||||
useEffect,
|
||||
useCallback,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
RefObject,
|
||||
} from "react";
|
||||
import MotionSegment from "./MotionSegment";
|
||||
import { useEventUtils } from "@/hooks/use-event-utils";
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||
import ReviewTimeline from "./ReviewTimeline";
|
||||
|
||||
@@ -23,6 +23,11 @@ export type MotionReviewTimelineProps = {
|
||||
showMinimap?: boolean;
|
||||
minimapStartTime?: number;
|
||||
minimapEndTime?: number;
|
||||
showExportHandles?: boolean;
|
||||
exportStartTime?: number;
|
||||
exportEndTime?: number;
|
||||
setExportStartTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
events: ReviewSegment[];
|
||||
motion_events: MotionData[];
|
||||
severityType: ReviewSeverity;
|
||||
@@ -41,47 +46,113 @@ export function MotionReviewTimeline({
|
||||
showMinimap = false,
|
||||
minimapStartTime,
|
||||
minimapEndTime,
|
||||
showExportHandles = false,
|
||||
exportStartTime,
|
||||
exportEndTime,
|
||||
setExportStartTime,
|
||||
setExportEndTime,
|
||||
events,
|
||||
motion_events,
|
||||
contentRef,
|
||||
onHandlebarDraggingChange,
|
||||
}: MotionReviewTimelineProps) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const handlebarRef = useRef<HTMLDivElement>(null);
|
||||
const [exportStartPosition, setExportStartPosition] = useState(0);
|
||||
const [exportEndPosition, setExportEndPosition] = useState(0);
|
||||
|
||||
const timelineRef = 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 timelineDuration = useMemo(
|
||||
() => timelineStart - timelineEnd + 4 * segmentDuration,
|
||||
[timelineEnd, timelineStart, segmentDuration],
|
||||
);
|
||||
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
|
||||
events,
|
||||
segmentDuration,
|
||||
);
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } =
|
||||
useTimelineUtils(segmentDuration);
|
||||
|
||||
const timelineStartAligned = useMemo(
|
||||
() => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration,
|
||||
[timelineStart, alignStartDateToTimeline, segmentDuration],
|
||||
);
|
||||
|
||||
const { handleMouseDown, handleMouseUp, handleMouseMove } =
|
||||
useDraggableHandler({
|
||||
contentRef,
|
||||
timelineRef,
|
||||
handlebarRef,
|
||||
alignStartDateToTimeline,
|
||||
alignEndDateToTimeline,
|
||||
segmentDuration,
|
||||
showHandlebar,
|
||||
handlebarTime,
|
||||
setHandlebarTime,
|
||||
timelineDuration,
|
||||
timelineStartAligned,
|
||||
isDragging,
|
||||
setIsDragging,
|
||||
handlebarTimeRef,
|
||||
});
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
draggableElementRef: exportEndRef,
|
||||
segmentDuration,
|
||||
showDraggableElement: showExportHandles,
|
||||
draggableElementTime: exportEndTime,
|
||||
draggableElementEarliestTime: paddedExportStartTime,
|
||||
setDraggableElementTime: setExportEndTime,
|
||||
timelineDuration,
|
||||
timelineStartAligned,
|
||||
isDragging,
|
||||
setIsDragging,
|
||||
draggableElementTimeRef: exportEndTimeRef,
|
||||
setDraggableElementPosition: setExportEndPosition,
|
||||
});
|
||||
|
||||
// Generate segments for the timeline
|
||||
const generateSegments = useCallback(() => {
|
||||
@@ -147,12 +218,26 @@ export function MotionReviewTimeline({
|
||||
timelineRef={timelineRef}
|
||||
handlebarRef={handlebarRef}
|
||||
handlebarTimeRef={handlebarTimeRef}
|
||||
handleMouseMove={handleMouseMove}
|
||||
handleMouseUp={handleMouseUp}
|
||||
handleMouseDown={handleMouseDown}
|
||||
handlebarMouseMove={handlebarMouseMove}
|
||||
handlebarMouseUp={handlebarMouseUp}
|
||||
handlebarMouseDown={handlebarMouseDown}
|
||||
segmentDuration={segmentDuration}
|
||||
timelineDuration={timelineDuration}
|
||||
showHandlebar={showHandlebar}
|
||||
isDragging={isDragging}
|
||||
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}
|
||||
>
|
||||
{segments}
|
||||
</ReviewTimeline>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEventUtils } from "@/hooks/use-event-utils";
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils";
|
||||
import { MotionData, ReviewSegment } from "@/types/review";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
@@ -42,10 +42,8 @@ export function MotionSegment({
|
||||
const { getMotionSegmentValue, interpolateMotionAudioData } =
|
||||
useMotionSegmentUtils(segmentDuration, motion_events);
|
||||
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
|
||||
events,
|
||||
segmentDuration,
|
||||
);
|
||||
const { alignStartDateToTimeline, alignEndDateToTimeline } =
|
||||
useTimelineUtils(segmentDuration);
|
||||
|
||||
const { handleTouchStart } = useTapUtils();
|
||||
|
||||
@@ -180,7 +178,7 @@ export function MotionSegment({
|
||||
key={segmentKey}
|
||||
className={segmentClasses}
|
||||
onClick={segmentClick}
|
||||
onTouchStart={(event) => handleTouchStart(event, segmentClick)}
|
||||
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
||||
>
|
||||
<MinimapBounds
|
||||
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
|
||||
|
||||
@@ -1,28 +1,75 @@
|
||||
import { ReactNode, RefObject } from "react";
|
||||
import { DraggableElement } from "@/types/draggable-element";
|
||||
import {
|
||||
ReactNode,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { isIOS, isMobile } from "react-device-detect";
|
||||
|
||||
export type ReviewTimelineProps = {
|
||||
timelineRef: RefObject<HTMLDivElement>;
|
||||
handlebarRef: RefObject<HTMLDivElement>;
|
||||
handlebarTimeRef: RefObject<HTMLDivElement>;
|
||||
handleMouseMove: (
|
||||
handlebarMouseMove: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
handleMouseUp: (
|
||||
handlebarMouseUp: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
handleMouseDown: (
|
||||
handlebarMouseDown: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
segmentDuration: number;
|
||||
timelineDuration: number;
|
||||
showHandlebar: boolean;
|
||||
showExportHandles: boolean;
|
||||
exportStartRef: RefObject<HTMLDivElement>;
|
||||
exportStartTimeRef: RefObject<HTMLDivElement>;
|
||||
exportEndRef: RefObject<HTMLDivElement>;
|
||||
exportEndTimeRef: RefObject<HTMLDivElement>;
|
||||
exportStartMouseMove: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
exportStartMouseUp: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
exportStartMouseDown: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
exportEndMouseMove: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
exportEndMouseUp: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
exportEndMouseDown: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
isDragging: boolean;
|
||||
exportStartPosition?: number;
|
||||
exportEndPosition?: number;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@@ -30,14 +77,156 @@ export function ReviewTimeline({
|
||||
timelineRef,
|
||||
handlebarRef,
|
||||
handlebarTimeRef,
|
||||
handleMouseMove,
|
||||
handleMouseUp,
|
||||
handleMouseDown,
|
||||
handlebarMouseMove,
|
||||
handlebarMouseUp,
|
||||
handlebarMouseDown,
|
||||
segmentDuration,
|
||||
timelineDuration,
|
||||
showHandlebar = false,
|
||||
showExportHandles = false,
|
||||
exportStartRef,
|
||||
exportStartTimeRef,
|
||||
exportEndRef,
|
||||
exportEndTimeRef,
|
||||
exportStartMouseMove,
|
||||
exportStartMouseUp,
|
||||
exportStartMouseDown,
|
||||
exportEndMouseMove,
|
||||
exportEndMouseUp,
|
||||
exportEndMouseDown,
|
||||
isDragging,
|
||||
exportStartPosition,
|
||||
exportEndPosition,
|
||||
children,
|
||||
}: ReviewTimelineProps) {
|
||||
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] =
|
||||
useState<DraggableElement>();
|
||||
|
||||
const handleHandlebar = useCallback(
|
||||
(
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => {
|
||||
setDraggableElementType("handlebar");
|
||||
handlebarMouseDown(e);
|
||||
},
|
||||
[handlebarMouseDown],
|
||||
);
|
||||
|
||||
const handleExportStart = useCallback(
|
||||
(
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => {
|
||||
setDraggableElementType("export_start");
|
||||
exportStartMouseDown(e);
|
||||
},
|
||||
[exportStartMouseDown],
|
||||
);
|
||||
|
||||
const handleExportEnd = useCallback(
|
||||
(
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => {
|
||||
setDraggableElementType("export_end");
|
||||
exportEndMouseDown(e);
|
||||
},
|
||||
[exportEndMouseDown],
|
||||
);
|
||||
|
||||
const handleMouseMove = useCallback(
|
||||
(
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => {
|
||||
switch (draggableElementType) {
|
||||
case "export_start":
|
||||
exportStartMouseMove(e);
|
||||
break;
|
||||
case "export_end":
|
||||
exportEndMouseMove(e);
|
||||
break;
|
||||
case "handlebar":
|
||||
handlebarMouseMove(e);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
[
|
||||
draggableElementType,
|
||||
exportStartMouseMove,
|
||||
exportEndMouseMove,
|
||||
handlebarMouseMove,
|
||||
],
|
||||
);
|
||||
|
||||
const handleMouseUp = useCallback(
|
||||
(
|
||||
e:
|
||||
| React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
| React.TouchEvent<HTMLDivElement>,
|
||||
) => {
|
||||
switch (draggableElementType) {
|
||||
case "export_start":
|
||||
exportStartMouseUp(e);
|
||||
break;
|
||||
case "export_end":
|
||||
exportEndMouseUp(e);
|
||||
break;
|
||||
case "handlebar":
|
||||
handlebarMouseUp(e);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
[
|
||||
draggableElementType,
|
||||
exportStartMouseUp,
|
||||
exportEndMouseUp,
|
||||
handlebarMouseUp,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
exportSectionRef.current &&
|
||||
segmentHeight &&
|
||||
exportStartPosition &&
|
||||
exportEndPosition
|
||||
) {
|
||||
exportSectionRef.current.style.top = `${exportEndPosition + segmentHeight}px`;
|
||||
exportSectionRef.current.style.height = `${exportStartPosition - exportEndPosition + segmentHeight / 2}px`;
|
||||
}
|
||||
}, [
|
||||
showExportHandles,
|
||||
segmentHeight,
|
||||
timelineRef,
|
||||
exportStartPosition,
|
||||
exportEndPosition,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={timelineRef}
|
||||
@@ -46,7 +235,9 @@ export function ReviewTimeline({
|
||||
onMouseUp={handleMouseUp}
|
||||
onTouchEnd={handleMouseUp}
|
||||
className={`relative h-full overflow-y-auto no-scrollbar bg-secondary ${
|
||||
isDragging && showHandlebar ? "cursor-grabbing" : "cursor-auto"
|
||||
isDragging && (showHandlebar || showExportHandles)
|
||||
? "cursor-grabbing"
|
||||
: "cursor-auto"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col relative">
|
||||
@@ -62,8 +253,8 @@ export function ReviewTimeline({
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
onMouseDown={handleMouseDown}
|
||||
onTouchStart={handleMouseDown}
|
||||
onMouseDown={handleHandlebar}
|
||||
onTouchStart={handleHandlebar}
|
||||
>
|
||||
<div
|
||||
className={`relative w-full ${
|
||||
@@ -73,20 +264,90 @@ export function ReviewTimeline({
|
||||
<div
|
||||
className={`bg-destructive rounded-full mx-auto ${
|
||||
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
|
||||
} h-5 ${isDragging && isMobile ? "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`}
|
||||
} 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`}
|
||||
>
|
||||
<div
|
||||
ref={handlebarTimeRef}
|
||||
className={`text-white ${isDragging && isMobile ? "text-lg" : "text-[8px] md:text-xs"} z-10`}
|
||||
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "handlebar" ? "text-lg" : "text-[8px] md:text-xs"} z-10`}
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
className={`absolute h-1 w-full bg-destructive ${isDragging && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
|
||||
className={`absolute h-1 w-full bg-destructive ${isDragging && isMobile && draggableElementType == "handlebar" ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showExportHandles && (
|
||||
<>
|
||||
<div
|
||||
className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
|
||||
role="scrollbar"
|
||||
ref={exportEndRef}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
onMouseDown={handleExportEnd}
|
||||
onTouchStart={handleExportEnd}
|
||||
>
|
||||
<div
|
||||
className={`relative mt-[6.5px] w-full ${
|
||||
isDragging ? "cursor-grabbing" : "cursor-grab"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`bg-selected -mt-4 mx-auto ${
|
||||
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
|
||||
} 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`}
|
||||
>
|
||||
<div
|
||||
ref={exportEndTimeRef}
|
||||
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_end" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
className={`absolute h-1 w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_end" ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={exportSectionRef}
|
||||
className="bg-selected/50 absolute w-full"
|
||||
></div>
|
||||
<div
|
||||
className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
|
||||
role="scrollbar"
|
||||
ref={exportStartRef}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
onMouseDown={handleExportStart}
|
||||
onTouchStart={handleExportStart}
|
||||
>
|
||||
<div
|
||||
className={`relative -mt-[6.5px] w-full ${
|
||||
isDragging ? "cursor-grabbing" : "cursor-grab"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute h-1 w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_start" ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
|
||||
></div>
|
||||
<div
|
||||
className={`bg-selected mt-4 mx-auto ${
|
||||
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
|
||||
} 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`}
|
||||
>
|
||||
<div
|
||||
ref={exportStartTimeRef}
|
||||
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_start" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function MinimapBounds({
|
||||
<>
|
||||
{isFirstSegmentInMinimap && (
|
||||
<div
|
||||
className="absolute inset-0 -bottom-7 w-full flex items-center justify-center text-primary-foreground font-medium z-20 text-center text-[10px] scroll-mt-8"
|
||||
className="absolute inset-0 -bottom-7 w-full flex items-center justify-center text-primary-foreground font-medium z-20 text-center text-[10px] scroll-mt-8 pointer-events-none"
|
||||
ref={firstMinimapSegmentRef}
|
||||
>
|
||||
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
|
||||
@@ -44,7 +44,7 @@ export function MinimapBounds({
|
||||
)}
|
||||
|
||||
{isLastSegmentInMinimap && (
|
||||
<div className="absolute inset-0 -top-3 w-full flex items-center justify-center text-primary-foreground font-medium z-20 text-center text-[10px]">
|
||||
<div className="absolute inset-0 -top-3 w-full flex items-center justify-center text-primary-foreground font-medium z-20 text-center text-[10px] pointer-events-none">
|
||||
{new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
@@ -61,14 +61,14 @@ export function Tick({ timestamp, timestampSpread }: TickSegmentProps) {
|
||||
<div className="absolute">
|
||||
<div className="flex items-end content-end w-[12px] h-2">
|
||||
<div
|
||||
className={`h-0.5 ${
|
||||
className={`pointer-events-none h-0.5 ${
|
||||
timestamp.getMinutes() % timestampSpread === 0 &&
|
||||
timestamp.getSeconds() === 0
|
||||
? "w-[12px] bg-neutral-600 dark:bg-neutral-500"
|
||||
: timestamp.getMinutes() % (timestampSpread == 15 ? 5 : 1) ===
|
||||
0 && timestamp.getSeconds() === 0
|
||||
? "w-[8px] bg-neutral-500 dark:bg-neutral-600" // Minor tick mark
|
||||
: "w-[5px] bg-neutral-400 dark:bg-neutral-700"
|
||||
? "w-[8px] bg-neutral-500" // Minor tick mark
|
||||
: "w-[5px] bg-neutral-400 dark:bg-neutral-600"
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@ export function Timestamp({
|
||||
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
||||
<div
|
||||
key={`${segmentKey}_timestamp`}
|
||||
className="text-[8px] text-neutral-600 dark:text-neutral-500"
|
||||
className="pointer-events-none text-[8px] text-neutral-600 dark:text-neutral-500"
|
||||
>
|
||||
{timestamp.getMinutes() % timestampSpread === 0 &&
|
||||
timestamp.getSeconds() === 0 &&
|
||||
|
||||
Reference in New Issue
Block a user