mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +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 { 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
|
<div className="relative w-full" style={{ height: `${height}%` }}>
|
||||||
key={segmentKey}
|
<div className="absolute inset-0 flex h-full cursor-pointer justify-end">
|
||||||
className="relative w-full"
|
<div
|
||||||
style={{ height: segmentHeight }}
|
className={cn(
|
||||||
>
|
"w-[10px]",
|
||||||
{severity.map((severityValue: number, index: number) => (
|
severityColors[severity],
|
||||||
<React.Fragment key={index}>
|
height < 0.5 && "min-h-[0.5px]",
|
||||||
{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>
|
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
></div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
{
|
||||||
timelineDuration: reviewTimelineDuration,
|
segmentDuration,
|
||||||
timelineRef: reviewTimelineRef,
|
timelineDuration: reviewTimelineDuration,
|
||||||
});
|
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) {
|
const sortedEvents = filteredEvents.sort(
|
||||||
return Array.from({ length: segmentCount }, (_, index) => {
|
(a, b) => a.start_time - b.start_time,
|
||||||
const segmentTime = timelineStartAligned - index * segmentDuration;
|
);
|
||||||
|
|
||||||
return (
|
const consolidated: ConsolidatedSegmentData[] = [];
|
||||||
<SummarySegment
|
|
||||||
key={segmentTime}
|
let currentTime = alignEndDateToTimeline(timelineEnd);
|
||||||
events={events}
|
const timelineStartAligned = alignStartDateToTimeline(timelineStart);
|
||||||
segmentDuration={segmentDuration}
|
|
||||||
segmentTime={segmentTime}
|
sortedEvents.forEach((event) => {
|
||||||
segmentHeight={segmentHeight}
|
const alignedStartTime = Math.max(
|
||||||
severityType={severityType}
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
|
||||||
segmentDuration,
|
|
||||||
timelineStartAligned,
|
|
||||||
events,
|
|
||||||
reviewTimelineDuration,
|
|
||||||
segmentHeight,
|
|
||||||
severityType,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const segments = useMemo(
|
return consolidated.length > 0
|
||||||
() => generateSegments(),
|
? consolidated
|
||||||
// we know that these deps are correct
|
: [
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
{
|
||||||
[
|
startTime: alignEndDateToTimeline(timelineEnd),
|
||||||
segmentDuration,
|
endTime: timelineStartAligned,
|
||||||
segmentHeight,
|
severity: "empty" as const,
|
||||||
timelineStartAligned,
|
reviewed: false,
|
||||||
events,
|
},
|
||||||
reviewTimelineDuration,
|
];
|
||||||
segmentHeight,
|
}, [
|
||||||
generateSegments,
|
events,
|
||||||
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}
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user