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

View File

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

View File

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