mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 08:35:21 +03:00
consolidate divs in summary timeline
This commit is contained in:
parent
f09e9832a5
commit
3ecf429927
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user