mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 13:15:25 +03:00
fix wrong segments when changing filters in motion only mode
This commit is contained in:
parent
7e5eb82882
commit
0a71e7fb88
@ -4,6 +4,7 @@ import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
|||||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||||
import ReviewTimeline from "./ReviewTimeline";
|
import ReviewTimeline from "./ReviewTimeline";
|
||||||
import { isDesktop } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
|
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
||||||
|
|
||||||
export type MotionReviewTimelineProps = {
|
export type MotionReviewTimelineProps = {
|
||||||
segmentDuration: number;
|
segmentDuration: number;
|
||||||
@ -75,14 +76,37 @@ export function MotionReviewTimeline({
|
|||||||
[timelineStart, alignStartDateToTimeline, segmentDuration],
|
[timelineStart, alignStartDateToTimeline, segmentDuration],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { getMotionSegmentValue } = useMotionSegmentUtils(
|
||||||
|
segmentDuration,
|
||||||
|
motion_events,
|
||||||
|
);
|
||||||
|
|
||||||
// Generate segments for the timeline
|
// Generate segments for the timeline
|
||||||
const generateSegments = useCallback(() => {
|
const generateSegments = useCallback(() => {
|
||||||
const segmentCount = Math.ceil(timelineDuration / segmentDuration);
|
const segments = [];
|
||||||
|
let segmentTime = timelineStartAligned;
|
||||||
|
|
||||||
return Array.from({ length: segmentCount }, (_, index) => {
|
while (segmentTime >= timelineStartAligned - timelineDuration) {
|
||||||
const segmentTime = timelineStartAligned - index * segmentDuration;
|
const motionStart = segmentTime;
|
||||||
|
const motionEnd = motionStart + segmentDuration;
|
||||||
|
|
||||||
return (
|
const segmentMotion =
|
||||||
|
getMotionSegmentValue(motionStart) > 0 ||
|
||||||
|
getMotionSegmentValue(motionStart + segmentDuration / 2) > 0;
|
||||||
|
const overlappingReviewItems = events.some(
|
||||||
|
(item) =>
|
||||||
|
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
||||||
|
(item.end_time > motionStart && item.end_time <= motionEnd) ||
|
||||||
|
(item.start_time <= motionStart && item.end_time >= motionEnd),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
|
||||||
|
// exclude segment if necessary when in motion only mode
|
||||||
|
segmentTime -= segmentDuration;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.push(
|
||||||
<MotionSegment
|
<MotionSegment
|
||||||
key={segmentTime}
|
key={segmentTime}
|
||||||
events={events}
|
events={events}
|
||||||
@ -96,9 +120,11 @@ export function MotionReviewTimeline({
|
|||||||
minimapEndTime={minimapEndTime}
|
minimapEndTime={minimapEndTime}
|
||||||
setHandlebarTime={setHandlebarTime}
|
setHandlebarTime={setHandlebarTime}
|
||||||
dense={dense}
|
dense={dense}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
});
|
segmentTime -= segmentDuration;
|
||||||
|
}
|
||||||
|
return segments;
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export type ReviewTimelineProps = {
|
|||||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||||
timelineCollapsed?: boolean;
|
timelineCollapsed?: boolean;
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ReviewTimeline({
|
export function ReviewTimeline({
|
||||||
@ -113,6 +113,7 @@ export function ReviewTimeline({
|
|||||||
setIsDragging: setIsDraggingHandlebar,
|
setIsDragging: setIsDraggingHandlebar,
|
||||||
draggableElementTimeRef: handlebarTimeRef,
|
draggableElementTimeRef: handlebarTimeRef,
|
||||||
dense,
|
dense,
|
||||||
|
timelineSegments: children,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -136,6 +137,7 @@ export function ReviewTimeline({
|
|||||||
draggableElementTimeRef: exportStartTimeRef,
|
draggableElementTimeRef: exportStartTimeRef,
|
||||||
setDraggableElementPosition: setExportStartPosition,
|
setDraggableElementPosition: setExportStartPosition,
|
||||||
dense,
|
dense,
|
||||||
|
timelineSegments: children,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -159,6 +161,7 @@ export function ReviewTimeline({
|
|||||||
draggableElementTimeRef: exportEndTimeRef,
|
draggableElementTimeRef: exportEndTimeRef,
|
||||||
setDraggableElementPosition: setExportEndPosition,
|
setDraggableElementPosition: setExportEndPosition,
|
||||||
dense,
|
dense,
|
||||||
|
timelineSegments: children,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleHandlebar = useCallback(
|
const handleHandlebar = useCallback(
|
||||||
@ -321,119 +324,123 @@ export function ReviewTimeline({
|
|||||||
<div className="absolute bottom-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-t from-secondary to-transparent pointer-events-none"></div>
|
<div className="absolute bottom-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-t from-secondary to-transparent pointer-events-none"></div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{showHandlebar && (
|
{children.length > 0 && (
|
||||||
<div
|
|
||||||
className={`absolute left-0 top-0 ${isDraggingHandlebar && isIOS ? "" : "z-20"} w-full`}
|
|
||||||
role="scrollbar"
|
|
||||||
ref={handlebarRef}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="flex items-center justify-center touch-none select-none"
|
|
||||||
onMouseDown={handleHandlebar}
|
|
||||||
onTouchStart={handleHandlebar}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`relative w-full ${
|
|
||||||
isDraggingHandlebar ? "cursor-grabbing" : "cursor-grab"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`bg-destructive rounded-full mx-auto ${
|
|
||||||
dense
|
|
||||||
? "w-12 md:w-20"
|
|
||||||
: segmentDuration < 60
|
|
||||||
? "w-24"
|
|
||||||
: "w-20"
|
|
||||||
} h-5 ${isDraggingHandlebar && isMobile ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={handlebarTimeRef}
|
|
||||||
className={`text-white pointer-events-none ${textSizeClasses("handlebar")} z-10`}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{showExportHandles && (
|
|
||||||
<>
|
<>
|
||||||
<div
|
{showHandlebar && (
|
||||||
className={`export-end absolute left-0 top-0 ${isDraggingExportEnd && isIOS ? "" : "z-20"} w-full`}
|
|
||||||
role="scrollbar"
|
|
||||||
ref={exportEndRef}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-center touch-none select-none"
|
className={`absolute left-0 top-0 ${isDraggingHandlebar && isIOS ? "" : "z-20"} w-full`}
|
||||||
onMouseDown={handleExportEnd}
|
role="scrollbar"
|
||||||
onTouchStart={handleExportEnd}
|
ref={handlebarRef}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`relative mt-[6.5px] w-full ${
|
className="flex items-center justify-center touch-none select-none"
|
||||||
isDraggingExportEnd ? "cursor-grabbing" : "cursor-grab"
|
onMouseDown={handleHandlebar}
|
||||||
}`}
|
onTouchStart={handleHandlebar}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`bg-selected -mt-4 mx-auto ${
|
className={`relative w-full ${
|
||||||
dense
|
isDraggingHandlebar ? "cursor-grabbing" : "cursor-grab"
|
||||||
? "w-12 md:w-20"
|
}`}
|
||||||
: segmentDuration < 60
|
|
||||||
? "w-24"
|
|
||||||
: "w-20"
|
|
||||||
} h-5 ${isDraggingExportEnd && isMobile ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={exportEndTimeRef}
|
className={`bg-destructive rounded-full mx-auto ${
|
||||||
className={`text-white pointer-events-none ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`}
|
dense
|
||||||
></div>
|
? "w-12 md:w-20"
|
||||||
</div>
|
: segmentDuration < 60
|
||||||
<div
|
? "w-24"
|
||||||
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
|
: "w-20"
|
||||||
></div>
|
} h-5 ${isDraggingHandlebar && isMobile ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
|
||||||
</div>
|
>
|
||||||
</div>
|
<div
|
||||||
</div>
|
ref={handlebarTimeRef}
|
||||||
<div
|
className={`text-white pointer-events-none ${textSizeClasses("handlebar")} z-10`}
|
||||||
ref={exportSectionRef}
|
></div>
|
||||||
className="bg-selected/50 absolute w-full"
|
</div>
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
|
|
||||||
role="scrollbar"
|
|
||||||
ref={exportStartRef}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="flex items-center justify-center touch-none select-none"
|
|
||||||
onMouseDown={handleExportStart}
|
|
||||||
onTouchStart={handleExportStart}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`relative -mt-[6.5px] w-full ${
|
|
||||||
isDragging ? "cursor-grabbing" : "cursor-grab"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className={`bg-selected mt-4 mx-auto ${
|
|
||||||
dense
|
|
||||||
? "w-12 md:w-20"
|
|
||||||
: segmentDuration < 60
|
|
||||||
? "w-24"
|
|
||||||
: "w-20"
|
|
||||||
} h-5 ${isDraggingExportStart && isMobile ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
ref={exportStartTimeRef}
|
className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
|
||||||
className={`text-white pointer-events-none ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`}
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{showExportHandles && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`export-end absolute left-0 top-0 ${isDraggingExportEnd && isIOS ? "" : "z-20"} w-full`}
|
||||||
|
role="scrollbar"
|
||||||
|
ref={exportEndRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center touch-none select-none"
|
||||||
|
onMouseDown={handleExportEnd}
|
||||||
|
onTouchStart={handleExportEnd}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`relative mt-[6.5px] w-full ${
|
||||||
|
isDraggingExportEnd ? "cursor-grabbing" : "cursor-grab"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`bg-selected -mt-4 mx-auto ${
|
||||||
|
dense
|
||||||
|
? "w-12 md:w-20"
|
||||||
|
: segmentDuration < 60
|
||||||
|
? "w-24"
|
||||||
|
: "w-20"
|
||||||
|
} h-5 ${isDraggingExportEnd && isMobile ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={exportEndTimeRef}
|
||||||
|
className={`text-white pointer-events-none ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={exportSectionRef}
|
||||||
|
className="bg-selected/50 absolute w-full"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
|
||||||
|
role="scrollbar"
|
||||||
|
ref={exportStartRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center touch-none select-none"
|
||||||
|
onMouseDown={handleExportStart}
|
||||||
|
onTouchStart={handleExportStart}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`relative -mt-[6.5px] w-full ${
|
||||||
|
isDragging ? "cursor-grabbing" : "cursor-grab"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`bg-selected mt-4 mx-auto ${
|
||||||
|
dense
|
||||||
|
? "w-12 md:w-20"
|
||||||
|
: segmentDuration < 60
|
||||||
|
? "w-24"
|
||||||
|
: "w-20"
|
||||||
|
} h-5 ${isDraggingExportStart && isMobile ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={exportStartTimeRef}
|
||||||
|
className={`text-white pointer-events-none ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import { useTimelineUtils } from "./use-timeline-utils";
|
import { useTimelineUtils } from "./use-timeline-utils";
|
||||||
@ -23,6 +23,7 @@ type DraggableElementProps = {
|
|||||||
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
|
setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
|
timelineSegments: ReactNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function useDraggableElement({
|
function useDraggableElement({
|
||||||
@ -45,6 +46,7 @@ function useDraggableElement({
|
|||||||
setIsDragging,
|
setIsDragging,
|
||||||
setDraggableElementPosition,
|
setDraggableElementPosition,
|
||||||
dense,
|
dense,
|
||||||
|
timelineSegments,
|
||||||
}: DraggableElementProps) {
|
}: DraggableElementProps) {
|
||||||
const [clientYPosition, setClientYPosition] = useState<number | null>(null);
|
const [clientYPosition, setClientYPosition] = useState<number | null>(null);
|
||||||
const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
|
const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
|
||||||
@ -213,10 +215,10 @@ function useDraggableElement({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timelineRef.current) {
|
if (timelineRef.current && timelineSegments.length) {
|
||||||
setSegments(Array.from(timelineRef.current.querySelectorAll(".segment")));
|
setSegments(Array.from(timelineRef.current.querySelectorAll(".segment")));
|
||||||
}
|
}
|
||||||
}, [timelineRef, segmentDuration, timelineDuration, timelineCollapsed]);
|
}, [timelineRef, timelineCollapsed, timelineSegments]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let animationFrameId: number | null = null;
|
let animationFrameId: number | null = null;
|
||||||
@ -426,7 +428,13 @@ function useDraggableElement({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timelineRef.current && draggableElementTime && timelineCollapsed) {
|
if (
|
||||||
|
timelineRef.current &&
|
||||||
|
draggableElementTime &&
|
||||||
|
timelineCollapsed &&
|
||||||
|
timelineSegments &&
|
||||||
|
segments
|
||||||
|
) {
|
||||||
setFullTimelineHeight(timelineRef.current.scrollHeight);
|
setFullTimelineHeight(timelineRef.current.scrollHeight);
|
||||||
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
||||||
|
|
||||||
@ -452,14 +460,30 @@ function useDraggableElement({
|
|||||||
if (setDraggableElementTime) {
|
if (setDraggableElementTime) {
|
||||||
setDraggableElementTime(searchTime);
|
setDraggableElementTime(searchTime);
|
||||||
}
|
}
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!segmentElement) {
|
||||||
|
// segment still not found, just start at the beginning of the timeline or at now()
|
||||||
|
if (segments?.length) {
|
||||||
|
const searchTime = parseInt(
|
||||||
|
segments[0].getAttribute("data-segment-id") || "0",
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
if (setDraggableElementTime) {
|
||||||
|
setDraggableElementTime(searchTime);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (setDraggableElementTime) {
|
||||||
|
setDraggableElementTime(timelineStartAligned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [timelineCollapsed]);
|
}, [timelineCollapsed, segments]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timelineRef.current && segments) {
|
if (timelineRef.current && segments) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user