use virtual segments in event review timeline

This commit is contained in:
Josh Hawkins 2024-11-29 20:17:26 -06:00
parent edfe44c1e3
commit afb1712674
2 changed files with 64 additions and 73 deletions

View File

@ -1,9 +1,17 @@
import { useEffect, useCallback, useMemo, useRef, RefObject } from "react";
import EventSegment from "./EventSegment";
import React, {
useEffect,
useMemo,
useRef,
RefObject,
useCallback,
} from "react";
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
import { ReviewSegment, ReviewSeverity } from "@/types/review";
import ReviewTimeline from "./ReviewTimeline";
import scrollIntoView from "scroll-into-view-if-needed";
import {
VirtualizedEventSegments,
VirtualizedEventSegmentsRef,
} from "./VirtualizedEventSegments";
export type EventReviewTimelineProps = {
segmentDuration: number;
@ -56,6 +64,7 @@ export function EventReviewTimeline({
}: EventReviewTimelineProps) {
const internalTimelineRef = useRef<HTMLDivElement>(null);
const selectedTimelineRef = timelineRef || internalTimelineRef;
const virtualizedSegmentsRef = useRef<VirtualizedEventSegmentsRef>(null);
const timelineDuration = useMemo(
() => timelineStart - timelineEnd,
@ -73,79 +82,27 @@ export function EventReviewTimeline({
[timelineStart, alignStartDateToTimeline],
);
// Generate segments for the timeline
const generateSegments = useCallback(() => {
// Generate segment times for the timeline
const segmentTimes = useMemo(() => {
const segmentCount = Math.ceil(timelineDuration / segmentDuration);
return Array.from({ length: segmentCount }, (_, index) => {
const segmentTime = 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,
],
return Array.from(
{ length: segmentCount },
(_, index) => timelineStartAligned - index * segmentDuration,
);
}, [timelineDuration, segmentDuration, timelineStartAligned]);
useEffect(() => {
if (
selectedTimelineRef.current &&
segments &&
visibleTimestamps &&
visibleTimestamps?.length > 0 &&
!showMinimap
visibleTimestamps.length > 0 &&
!showMinimap &&
virtualizedSegmentsRef.current
) {
const alignedVisibleTimestamps = visibleTimestamps.map(
alignStartDateToTimeline,
);
const element = selectedTimelineRef.current?.querySelector(
`[data-segment-id="${Math.max(...alignedVisibleTimestamps)}"]`,
);
if (element) {
scrollIntoView(element, {
scrollMode: "if-needed",
behavior: "smooth",
});
}
scrollToSegment(Math.max(...alignedVisibleTimestamps), true);
}
// don't scroll when segments update from unreviewed -> reviewed
// we know that these deps are correct
@ -155,8 +112,18 @@ export function EventReviewTimeline({
showMinimap,
alignStartDateToTimeline,
visibleTimestamps,
segmentDuration,
]);
const scrollToSegment = useCallback(
(segmentTime: number, ifNeeded?: boolean) => {
if (virtualizedSegmentsRef.current) {
virtualizedSegmentsRef.current.scrollToSegment(segmentTime, ifNeeded);
}
},
[],
);
return (
<ReviewTimeline
timelineRef={selectedTimelineRef}
@ -174,8 +141,25 @@ export function EventReviewTimeline({
setExportStartTime={setExportStartTime}
setExportEndTime={setExportEndTime}
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>
);
}

View File

@ -30,7 +30,9 @@ export type ReviewTimelineProps = {
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
timelineCollapsed?: boolean;
dense: boolean;
children: ReactNode[];
segments: number[];
scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void;
children: ReactNode;
};
export function ReviewTimeline({
@ -51,6 +53,8 @@ export function ReviewTimeline({
setExportEndTime,
timelineCollapsed = false,
dense,
segments,
scrollToSegment,
children,
}: ReviewTimelineProps) {
const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
@ -116,7 +120,8 @@ export function ReviewTimeline({
setIsDragging: setIsDraggingHandlebar,
draggableElementTimeRef: handlebarTimeRef,
dense,
timelineSegments: children,
segments,
scrollToSegment,
});
const {
@ -140,7 +145,8 @@ export function ReviewTimeline({
draggableElementTimeRef: exportStartTimeRef,
setDraggableElementPosition: setExportStartPosition,
dense,
timelineSegments: children,
segments,
scrollToSegment,
});
const {
@ -164,7 +170,8 @@ export function ReviewTimeline({
draggableElementTimeRef: exportEndTimeRef,
setDraggableElementPosition: setExportEndPosition,
dense,
timelineSegments: children,
segments,
scrollToSegment,
});
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>
{children}
</div>
{children.length > 0 && (
{children && (
<>
{showHandlebar && (
<div