mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-31 08:09:02 +03:00
Timeline improvements (#16429)
* virtualize event segments * use virtual segments in event review timeline * add segmentkey to props * virtualize motion segments * use virtual segments in motion review timeline * update draggable element hook to use only math * timeline zooming hook * add zooming to event review timeline * update playground * zoomable timeline on recording view * consolidate divs in summary timeline * only calculate motion data for visible motion segments * use swr loading state * fix motion only * keep handlebar centered when zooming * zoom animations * clean up * ensure motion only checks both halves of segment * prevent handlebar jump when using motion only mode
This commit is contained in:
@@ -1,9 +1,22 @@
|
||||
import { useCallback, useMemo, useRef, RefObject } from "react";
|
||||
import MotionSegment from "./MotionSegment";
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
RefObject,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||
import {
|
||||
MotionData,
|
||||
ReviewSegment,
|
||||
TimelineZoomDirection,
|
||||
} from "@/types/review";
|
||||
import ReviewTimeline from "./ReviewTimeline";
|
||||
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
||||
import {
|
||||
VirtualizedMotionSegments,
|
||||
VirtualizedMotionSegmentsRef,
|
||||
} from "./VirtualizedMotionSegments";
|
||||
|
||||
export type MotionReviewTimelineProps = {
|
||||
segmentDuration: number;
|
||||
@@ -25,11 +38,12 @@ export type MotionReviewTimelineProps = {
|
||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
events: ReviewSegment[];
|
||||
motion_events: MotionData[];
|
||||
severityType: ReviewSeverity;
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
timelineRef?: RefObject<HTMLDivElement>;
|
||||
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
||||
dense?: boolean;
|
||||
isZooming: boolean;
|
||||
zoomDirection: TimelineZoomDirection;
|
||||
};
|
||||
|
||||
export function MotionReviewTimeline({
|
||||
@@ -56,9 +70,12 @@ export function MotionReviewTimeline({
|
||||
timelineRef,
|
||||
onHandlebarDraggingChange,
|
||||
dense = false,
|
||||
isZooming,
|
||||
zoomDirection,
|
||||
}: MotionReviewTimelineProps) {
|
||||
const internalTimelineRef = useRef<HTMLDivElement>(null);
|
||||
const selectedTimelineRef = timelineRef || internalTimelineRef;
|
||||
const virtualizedSegmentsRef = useRef<VirtualizedMotionSegmentsRef>(null);
|
||||
|
||||
const timelineDuration = useMemo(
|
||||
() => timelineStart - timelineEnd + 4 * segmentDuration,
|
||||
@@ -80,90 +97,75 @@ export function MotionReviewTimeline({
|
||||
motion_events,
|
||||
);
|
||||
|
||||
// Generate segments for the timeline
|
||||
const generateSegments = useCallback(() => {
|
||||
const segmentTimes = useMemo(() => {
|
||||
const segments = [];
|
||||
let segmentTime = timelineStartAligned;
|
||||
|
||||
while (segmentTime >= timelineStartAligned - timelineDuration) {
|
||||
const motionStart = segmentTime;
|
||||
const motionEnd = motionStart + segmentDuration;
|
||||
for (let i = 0; i < Math.ceil(timelineDuration / segmentDuration); i++) {
|
||||
if (!motionOnly) {
|
||||
segments.push(segmentTime);
|
||||
} else {
|
||||
const motionStart = segmentTime;
|
||||
const motionEnd = motionStart + segmentDuration;
|
||||
const overlappingReviewItems = events.some(
|
||||
(item) =>
|
||||
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
||||
((item.end_time ?? timelineStart) > motionStart &&
|
||||
(item.end_time ?? timelineStart) <= motionEnd) ||
|
||||
(item.start_time <= motionStart &&
|
||||
(item.end_time ?? timelineStart) >= motionEnd),
|
||||
);
|
||||
const firstHalfMotionValue = getMotionSegmentValue(motionStart);
|
||||
const secondHalfMotionValue = getMotionSegmentValue(
|
||||
motionStart + segmentDuration / 2,
|
||||
);
|
||||
|
||||
const firstHalfMotionValue = getMotionSegmentValue(motionStart);
|
||||
const secondHalfMotionValue = getMotionSegmentValue(
|
||||
motionStart + segmentDuration / 2,
|
||||
);
|
||||
|
||||
const segmentMotion =
|
||||
firstHalfMotionValue > 0 || secondHalfMotionValue > 0;
|
||||
const overlappingReviewItems = events.some(
|
||||
(item) =>
|
||||
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
||||
((item.end_time ?? timelineStart) > motionStart &&
|
||||
(item.end_time ?? timelineStart) <= motionEnd) ||
|
||||
(item.start_time <= motionStart &&
|
||||
(item.end_time ?? timelineStart) >= motionEnd),
|
||||
);
|
||||
|
||||
if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
|
||||
// exclude segment if necessary when in motion only mode
|
||||
segmentTime -= segmentDuration;
|
||||
continue;
|
||||
const segmentMotion =
|
||||
firstHalfMotionValue > 0 || secondHalfMotionValue > 0;
|
||||
if (segmentMotion && !overlappingReviewItems) {
|
||||
segments.push(segmentTime);
|
||||
}
|
||||
}
|
||||
|
||||
segments.push(
|
||||
<MotionSegment
|
||||
key={segmentTime}
|
||||
events={events}
|
||||
firstHalfMotionValue={firstHalfMotionValue}
|
||||
secondHalfMotionValue={secondHalfMotionValue}
|
||||
segmentDuration={segmentDuration}
|
||||
segmentTime={segmentTime}
|
||||
timestampSpread={timestampSpread}
|
||||
motionOnly={motionOnly}
|
||||
showMinimap={showMinimap}
|
||||
minimapStartTime={minimapStartTime}
|
||||
minimapEndTime={minimapEndTime}
|
||||
setHandlebarTime={setHandlebarTime}
|
||||
dense={dense}
|
||||
/>,
|
||||
);
|
||||
segmentTime -= segmentDuration;
|
||||
}
|
||||
|
||||
return segments;
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
segmentDuration,
|
||||
timestampSpread,
|
||||
timelineStartAligned,
|
||||
segmentDuration,
|
||||
timelineDuration,
|
||||
showMinimap,
|
||||
minimapStartTime,
|
||||
minimapEndTime,
|
||||
events,
|
||||
motion_events,
|
||||
motionOnly,
|
||||
getMotionSegmentValue,
|
||||
events,
|
||||
timelineStart,
|
||||
]);
|
||||
|
||||
const segments = useMemo(
|
||||
() => generateSegments(),
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
segmentDuration,
|
||||
timestampSpread,
|
||||
timelineStartAligned,
|
||||
timelineDuration,
|
||||
showMinimap,
|
||||
minimapStartTime,
|
||||
minimapEndTime,
|
||||
events,
|
||||
motion_events,
|
||||
motionOnly,
|
||||
],
|
||||
const scrollToSegment = useCallback(
|
||||
(segmentTime: number, ifNeeded?: boolean, behavior?: ScrollBehavior) => {
|
||||
if (virtualizedSegmentsRef.current) {
|
||||
virtualizedSegmentsRef.current.scrollToSegment(
|
||||
segmentTime,
|
||||
ifNeeded,
|
||||
behavior,
|
||||
);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// keep handlebar centered when zooming
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
scrollToSegment(
|
||||
alignStartDateToTimeline(handlebarTime ?? timelineStart),
|
||||
true,
|
||||
"auto",
|
||||
);
|
||||
}, 0);
|
||||
// we only want to scroll when zooming level changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [segmentDuration]);
|
||||
|
||||
return (
|
||||
<ReviewTimeline
|
||||
timelineRef={selectedTimelineRef}
|
||||
@@ -183,8 +185,28 @@ export function MotionReviewTimeline({
|
||||
setExportEndTime={setExportEndTime}
|
||||
timelineCollapsed={motionOnly}
|
||||
dense={dense}
|
||||
segments={segmentTimes}
|
||||
scrollToSegment={scrollToSegment}
|
||||
isZooming={isZooming}
|
||||
zoomDirection={zoomDirection}
|
||||
>
|
||||
{segments}
|
||||
<VirtualizedMotionSegments
|
||||
ref={virtualizedSegmentsRef}
|
||||
timelineRef={selectedTimelineRef}
|
||||
segments={segmentTimes}
|
||||
events={events}
|
||||
motion_events={motion_events}
|
||||
segmentDuration={segmentDuration}
|
||||
timestampSpread={timestampSpread}
|
||||
showMinimap={showMinimap}
|
||||
minimapStartTime={minimapStartTime}
|
||||
minimapEndTime={minimapEndTime}
|
||||
contentRef={contentRef}
|
||||
setHandlebarTime={setHandlebarTime}
|
||||
dense={dense}
|
||||
motionOnly={motionOnly}
|
||||
getMotionSegmentValue={getMotionSegmentValue}
|
||||
/>
|
||||
</ReviewTimeline>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user