consolidate divs in summary timeline

This commit is contained in:
Josh Hawkins 2024-12-02 20:39:49 -06:00
parent f09e9832a5
commit 3ecf429927
3 changed files with 124 additions and 131 deletions

View File

@ -1,68 +1,39 @@
import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils";
import { ReviewSegment, ReviewSeverity } from "@/types/review";
import React, { useMemo } from "react";
// import useTapUtils from "@/hooks/use-tap-utils";
import { cn } from "@/lib/utils";
import { ConsolidatedSegmentData } from "@/types/review";
type SummarySegmentProps = {
events: ReviewSegment[];
segmentTime: number;
segmentDuration: number;
segmentHeight: number;
severityType: ReviewSeverity;
segmentData: ConsolidatedSegmentData;
totalDuration: number;
};
export function SummarySegment({
events,
segmentTime,
segmentDuration,
segmentHeight,
severityType,
segmentData,
totalDuration,
}: SummarySegmentProps) {
const { getSeverity, getReviewed, displaySeverityType } =
useEventSegmentUtils(segmentDuration, events, severityType);
const { startTime, endTime, severity, reviewed } = segmentData;
const severity = useMemo(
() => getSeverity(segmentTime, displaySeverityType),
[getSeverity, segmentTime, displaySeverityType],
);
const reviewed = useMemo(
() => getReviewed(segmentTime),
[getReviewed, segmentTime],
);
const segmentKey = useMemo(() => segmentTime, [segmentTime]);
const severityColors: { [key: number]: string } = {
1: reviewed
const severityColors: { [key: string]: string } = {
significant_motion: reviewed
? "bg-severity_significant_motion/50"
: "bg-severity_significant_motion",
2: reviewed ? "bg-severity_detection/50" : "bg-severity_detection",
3: reviewed ? "bg-severity_alert/50" : "bg-severity_alert",
detection: reviewed ? "bg-severity_detection/50" : "bg-severity_detection",
alert: reviewed ? "bg-severity_alert/50" : "bg-severity_alert",
empty: "bg-transparent",
};
const height = ((endTime - startTime) / totalDuration) * 100;
return (
<div className="relative w-full" style={{ height: `${height}%` }}>
<div className="absolute inset-0 flex h-full cursor-pointer justify-end">
<div
key={segmentKey}
className="relative w-full"
style={{ height: segmentHeight }}
>
{severity.map((severityValue: number, index: number) => (
<React.Fragment key={index}>
{severityValue === displaySeverityType && (
<div
className="flex cursor-pointer justify-end"
style={{ height: segmentHeight }}
>
<div
key={`${segmentKey}_${index}_secondary_data`}
style={{ height: segmentHeight }}
className={`w-[10px] ${severityColors[severityValue]}`}
className={cn(
"w-[10px]",
severityColors[severity],
height < 0.5 && "min-h-[0.5px]",
)}
></div>
</div>
)}
</React.Fragment>
))}
</div>
);
}

View File

@ -1,17 +1,20 @@
import {
RefObject,
useCallback,
useEffect,
useMemo,
import React, {
useRef,
useState,
useMemo,
useCallback,
useEffect,
} from "react";
import { SummarySegment } from "./SummarySegment";
import {
ConsolidatedSegmentData,
ReviewSegment,
ReviewSeverity,
} from "@/types/review";
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
import { ReviewSegment, ReviewSeverity } from "@/types/review";
export type SummaryTimelineProps = {
reviewTimelineRef: RefObject<HTMLDivElement>;
reviewTimelineRef: React.RefObject<HTMLDivElement>;
timelineStart: number;
timelineEnd: number;
segmentDuration: number;
@ -29,7 +32,6 @@ export function SummaryTimeline({
}: SummaryTimelineProps) {
const summaryTimelineRef = useRef<HTMLDivElement>(null);
const visibleSectionRef = useRef<HTMLDivElement>(null);
const [segmentHeight, setSegmentHeight] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const [scrollStartPosition, setScrollStartPosition] = useState<number>(0);
@ -43,61 +45,89 @@ export function SummaryTimeline({
[timelineEnd, timelineStart, segmentDuration],
);
const { alignStartDateToTimeline } = useTimelineUtils({
const { alignStartDateToTimeline, alignEndDateToTimeline } = useTimelineUtils(
{
segmentDuration,
timelineDuration: reviewTimelineDuration,
timelineRef: reviewTimelineRef,
});
const timelineStartAligned = useMemo(
() => alignStartDateToTimeline(timelineStart) + 2 * segmentDuration,
[timelineStart, alignStartDateToTimeline, segmentDuration],
},
);
// Generate segments for the timeline
const generateSegments = useCallback(() => {
const segmentCount = Math.ceil(reviewTimelineDuration / segmentDuration);
if (segmentHeight) {
return Array.from({ length: segmentCount }, (_, index) => {
const segmentTime = timelineStartAligned - index * segmentDuration;
return (
<SummarySegment
key={segmentTime}
events={events}
segmentDuration={segmentDuration}
segmentTime={segmentTime}
segmentHeight={segmentHeight}
severityType={severityType}
/>
const consolidatedSegments = useMemo(() => {
const filteredEvents = events.filter(
(event) => event.severity === severityType,
);
const sortedEvents = filteredEvents.sort(
(a, b) => a.start_time - b.start_time,
);
const consolidated: ConsolidatedSegmentData[] = [];
let currentTime = alignEndDateToTimeline(timelineEnd);
const timelineStartAligned = alignStartDateToTimeline(timelineStart);
sortedEvents.forEach((event) => {
const alignedStartTime = Math.max(
alignStartDateToTimeline(event.start_time),
currentTime,
);
const alignedEndTime = Math.min(
event.end_time
? alignEndDateToTimeline(event.end_time)
: alignedStartTime + segmentDuration,
timelineStartAligned,
);
if (alignedStartTime < alignedEndTime) {
if (alignedStartTime > currentTime) {
consolidated.push({
startTime: currentTime,
endTime: alignedStartTime,
severity: "empty",
reviewed: false,
});
}
}, [
segmentDuration,
timelineStartAligned,
events,
reviewTimelineDuration,
segmentHeight,
severityType,
]);
const segments = useMemo(
() => generateSegments(),
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[
segmentDuration,
segmentHeight,
timelineStartAligned,
consolidated.push({
startTime: alignedStartTime,
endTime: alignedEndTime,
severity: event.severity,
reviewed: event.has_been_reviewed,
});
currentTime = alignedEndTime;
}
});
if (currentTime < timelineStartAligned) {
consolidated.push({
startTime: currentTime,
endTime: timelineStartAligned,
severity: "empty",
reviewed: false,
});
}
return consolidated.length > 0
? consolidated
: [
{
startTime: alignEndDateToTimeline(timelineEnd),
endTime: timelineStartAligned,
severity: "empty" as const,
reviewed: false,
},
];
}, [
events,
reviewTimelineDuration,
segmentHeight,
generateSegments,
severityType,
],
);
timelineStart,
timelineEnd,
alignStartDateToTimeline,
alignEndDateToTimeline,
segmentDuration,
]);
const setVisibleSectionStyles = useCallback(() => {
if (
@ -137,15 +167,6 @@ export function SummaryTimeline({
observer.current = new ResizeObserver(() => {
setVisibleSectionStyles();
if (summaryTimelineRef.current) {
const { clientHeight: summaryTimelineVisibleHeight } =
summaryTimelineRef.current;
setSegmentHeight(
summaryTimelineVisibleHeight /
(reviewTimelineDuration / segmentDuration),
);
}
});
observer.current.observe(content);
@ -163,18 +184,6 @@ export function SummaryTimeline({
segmentDuration,
]);
useEffect(() => {
if (summaryTimelineRef.current) {
const { clientHeight: summaryTimelineVisibleHeight } =
summaryTimelineRef.current;
setSegmentHeight(
summaryTimelineVisibleHeight /
(reviewTimelineDuration / segmentDuration),
);
}
}, [reviewTimelineDuration, summaryTimelineRef, segmentDuration]);
const timelineClick = useCallback(
(
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
@ -344,11 +353,17 @@ export function SummaryTimeline({
>
<div
ref={summaryTimelineRef}
className="relative z-10 flex h-full flex-col"
className="relative z-10 flex h-full flex-col-reverse"
onClick={timelineClick}
onTouchEnd={timelineClick}
>
{segments}
{consolidatedSegments.map((segment, index) => (
<SummarySegment
key={`${segment.startTime}-${segment.endTime}+${index}`}
segmentData={segment}
totalDuration={timelineStart - timelineEnd}
/>
))}
</div>
<div
ref={visibleSectionRef}

View File

@ -60,3 +60,10 @@ export type MotionData = {
export const REVIEW_PADDING = 4;
export type ReviewDetailPaneType = "overview" | "details";
export type ConsolidatedSegmentData = {
startTime: number;
endTime: number;
severity: ReviewSeverity | "empty";
reviewed: boolean;
};