mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
use virtual segments in event review timeline
This commit is contained in:
parent
edfe44c1e3
commit
afb1712674
@ -1,9 +1,17 @@
|
|||||||
import { useEffect, useCallback, useMemo, useRef, RefObject } from "react";
|
import React, {
|
||||||
import EventSegment from "./EventSegment";
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
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";
|
||||||
import ReviewTimeline from "./ReviewTimeline";
|
import ReviewTimeline from "./ReviewTimeline";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import {
|
||||||
|
VirtualizedEventSegments,
|
||||||
|
VirtualizedEventSegmentsRef,
|
||||||
|
} from "./VirtualizedEventSegments";
|
||||||
|
|
||||||
export type EventReviewTimelineProps = {
|
export type EventReviewTimelineProps = {
|
||||||
segmentDuration: number;
|
segmentDuration: number;
|
||||||
@ -56,6 +64,7 @@ export function EventReviewTimeline({
|
|||||||
}: EventReviewTimelineProps) {
|
}: EventReviewTimelineProps) {
|
||||||
const internalTimelineRef = useRef<HTMLDivElement>(null);
|
const internalTimelineRef = useRef<HTMLDivElement>(null);
|
||||||
const selectedTimelineRef = timelineRef || internalTimelineRef;
|
const selectedTimelineRef = timelineRef || internalTimelineRef;
|
||||||
|
const virtualizedSegmentsRef = useRef<VirtualizedEventSegmentsRef>(null);
|
||||||
|
|
||||||
const timelineDuration = useMemo(
|
const timelineDuration = useMemo(
|
||||||
() => timelineStart - timelineEnd,
|
() => timelineStart - timelineEnd,
|
||||||
@ -73,79 +82,27 @@ export function EventReviewTimeline({
|
|||||||
[timelineStart, alignStartDateToTimeline],
|
[timelineStart, alignStartDateToTimeline],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generate segments for the timeline
|
// Generate segment times for the timeline
|
||||||
const generateSegments = useCallback(() => {
|
const segmentTimes = useMemo(() => {
|
||||||
const segmentCount = Math.ceil(timelineDuration / segmentDuration);
|
const segmentCount = Math.ceil(timelineDuration / segmentDuration);
|
||||||
|
return Array.from(
|
||||||
return Array.from({ length: segmentCount }, (_, index) => {
|
{ length: segmentCount },
|
||||||
const segmentTime = timelineStartAligned - index * segmentDuration;
|
(_, index) => timelineStartAligned - index * segmentDuration,
|
||||||
|
|
||||||
return (
|
|
||||||
<EventSegment
|
|
||||||
key={segmentTime + severityType}
|
|
||||||
events={events}
|
|
||||||
segmentDuration={segmentDuration}
|
|
||||||
segmentTime={segmentTime}
|
|
||||||
timestampSpread={timestampSpread}
|
|
||||||
showMinimap={showMinimap}
|
|
||||||
minimapStartTime={minimapStartTime}
|
|
||||||
minimapEndTime={minimapEndTime}
|
|
||||||
severityType={severityType}
|
|
||||||
contentRef={contentRef}
|
|
||||||
setHandlebarTime={setHandlebarTime}
|
|
||||||
dense={dense}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// we know that these deps are correct
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [
|
|
||||||
segmentDuration,
|
|
||||||
timestampSpread,
|
|
||||||
timelineStart,
|
|
||||||
timelineDuration,
|
|
||||||
showMinimap,
|
|
||||||
minimapStartTime,
|
|
||||||
minimapEndTime,
|
|
||||||
events,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const segments = useMemo(
|
|
||||||
() => generateSegments(),
|
|
||||||
// we know that these deps are correct
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[
|
|
||||||
segmentDuration,
|
|
||||||
timestampSpread,
|
|
||||||
timelineStart,
|
|
||||||
timelineDuration,
|
|
||||||
showMinimap,
|
|
||||||
minimapStartTime,
|
|
||||||
minimapEndTime,
|
|
||||||
events,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
}, [timelineDuration, segmentDuration, timelineStartAligned]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
selectedTimelineRef.current &&
|
|
||||||
segments &&
|
|
||||||
visibleTimestamps &&
|
visibleTimestamps &&
|
||||||
visibleTimestamps?.length > 0 &&
|
visibleTimestamps.length > 0 &&
|
||||||
!showMinimap
|
!showMinimap &&
|
||||||
|
virtualizedSegmentsRef.current
|
||||||
) {
|
) {
|
||||||
const alignedVisibleTimestamps = visibleTimestamps.map(
|
const alignedVisibleTimestamps = visibleTimestamps.map(
|
||||||
alignStartDateToTimeline,
|
alignStartDateToTimeline,
|
||||||
);
|
);
|
||||||
const element = selectedTimelineRef.current?.querySelector(
|
|
||||||
`[data-segment-id="${Math.max(...alignedVisibleTimestamps)}"]`,
|
scrollToSegment(Math.max(...alignedVisibleTimestamps), true);
|
||||||
);
|
|
||||||
if (element) {
|
|
||||||
scrollIntoView(element, {
|
|
||||||
scrollMode: "if-needed",
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// don't scroll when segments update from unreviewed -> reviewed
|
// don't scroll when segments update from unreviewed -> reviewed
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
@ -155,8 +112,18 @@ export function EventReviewTimeline({
|
|||||||
showMinimap,
|
showMinimap,
|
||||||
alignStartDateToTimeline,
|
alignStartDateToTimeline,
|
||||||
visibleTimestamps,
|
visibleTimestamps,
|
||||||
|
segmentDuration,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const scrollToSegment = useCallback(
|
||||||
|
(segmentTime: number, ifNeeded?: boolean) => {
|
||||||
|
if (virtualizedSegmentsRef.current) {
|
||||||
|
virtualizedSegmentsRef.current.scrollToSegment(segmentTime, ifNeeded);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReviewTimeline
|
<ReviewTimeline
|
||||||
timelineRef={selectedTimelineRef}
|
timelineRef={selectedTimelineRef}
|
||||||
@ -174,8 +141,25 @@ export function EventReviewTimeline({
|
|||||||
setExportStartTime={setExportStartTime}
|
setExportStartTime={setExportStartTime}
|
||||||
setExportEndTime={setExportEndTime}
|
setExportEndTime={setExportEndTime}
|
||||||
dense={dense}
|
dense={dense}
|
||||||
|
segments={segmentTimes}
|
||||||
|
scrollToSegment={scrollToSegment}
|
||||||
>
|
>
|
||||||
{segments}
|
<VirtualizedEventSegments
|
||||||
|
ref={virtualizedSegmentsRef}
|
||||||
|
timelineRef={selectedTimelineRef}
|
||||||
|
segments={segmentTimes}
|
||||||
|
events={events}
|
||||||
|
segmentDuration={segmentDuration}
|
||||||
|
timestampSpread={timestampSpread}
|
||||||
|
showMinimap={showMinimap}
|
||||||
|
minimapStartTime={minimapStartTime}
|
||||||
|
minimapEndTime={minimapEndTime}
|
||||||
|
severityType={severityType}
|
||||||
|
contentRef={contentRef}
|
||||||
|
setHandlebarTime={setHandlebarTime}
|
||||||
|
dense={dense}
|
||||||
|
alignStartDateToTimeline={alignStartDateToTimeline}
|
||||||
|
/>
|
||||||
</ReviewTimeline>
|
</ReviewTimeline>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,9 @@ export type ReviewTimelineProps = {
|
|||||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||||
timelineCollapsed?: boolean;
|
timelineCollapsed?: boolean;
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
children: ReactNode[];
|
segments: number[];
|
||||||
|
scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void;
|
||||||
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ReviewTimeline({
|
export function ReviewTimeline({
|
||||||
@ -51,6 +53,8 @@ export function ReviewTimeline({
|
|||||||
setExportEndTime,
|
setExportEndTime,
|
||||||
timelineCollapsed = false,
|
timelineCollapsed = false,
|
||||||
dense,
|
dense,
|
||||||
|
segments,
|
||||||
|
scrollToSegment,
|
||||||
children,
|
children,
|
||||||
}: ReviewTimelineProps) {
|
}: ReviewTimelineProps) {
|
||||||
const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
|
const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
|
||||||
@ -116,7 +120,8 @@ export function ReviewTimeline({
|
|||||||
setIsDragging: setIsDraggingHandlebar,
|
setIsDragging: setIsDraggingHandlebar,
|
||||||
draggableElementTimeRef: handlebarTimeRef,
|
draggableElementTimeRef: handlebarTimeRef,
|
||||||
dense,
|
dense,
|
||||||
timelineSegments: children,
|
segments,
|
||||||
|
scrollToSegment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -140,7 +145,8 @@ export function ReviewTimeline({
|
|||||||
draggableElementTimeRef: exportStartTimeRef,
|
draggableElementTimeRef: exportStartTimeRef,
|
||||||
setDraggableElementPosition: setExportStartPosition,
|
setDraggableElementPosition: setExportStartPosition,
|
||||||
dense,
|
dense,
|
||||||
timelineSegments: children,
|
segments,
|
||||||
|
scrollToSegment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -164,7 +170,8 @@ export function ReviewTimeline({
|
|||||||
draggableElementTimeRef: exportEndTimeRef,
|
draggableElementTimeRef: exportEndTimeRef,
|
||||||
setDraggableElementPosition: setExportEndPosition,
|
setDraggableElementPosition: setExportEndPosition,
|
||||||
dense,
|
dense,
|
||||||
timelineSegments: children,
|
segments,
|
||||||
|
scrollToSegment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleHandlebar = useCallback(
|
const handleHandlebar = useCallback(
|
||||||
@ -327,7 +334,7 @@ export function ReviewTimeline({
|
|||||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[30px] w-full bg-gradient-to-t from-secondary to-transparent"></div>
|
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[30px] w-full bg-gradient-to-t from-secondary to-transparent"></div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{children.length > 0 && (
|
{children && (
|
||||||
<>
|
<>
|
||||||
{showHandlebar && (
|
{showHandlebar && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user