From 3d1bfc1fa2e3805131361963493d1b9b728c23c8 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:56:01 -0500 Subject: [PATCH] add icons to zoom timeline --- .../timeline/EventReviewTimeline.tsx | 10 + web/src/components/timeline/EventSegment.tsx | 2 +- .../timeline/MotionReviewTimeline.tsx | 10 + .../components/timeline/ReviewTimeline.tsx | 337 ++++++++++-------- web/src/pages/UIPlayground.tsx | 7 +- web/src/types/review.ts | 5 + web/src/views/events/EventView.tsx | 7 +- web/src/views/recording/RecordingView.tsx | 5 +- 8 files changed, 237 insertions(+), 146 deletions(-) diff --git a/web/src/components/timeline/EventReviewTimeline.tsx b/web/src/components/timeline/EventReviewTimeline.tsx index 27f318edb..89e73c21d 100644 --- a/web/src/components/timeline/EventReviewTimeline.tsx +++ b/web/src/components/timeline/EventReviewTimeline.tsx @@ -10,6 +10,7 @@ import { ReviewSegment, ReviewSeverity, TimelineZoomDirection, + ZoomLevel, } from "@/types/review"; import ReviewTimeline from "./ReviewTimeline"; import { @@ -42,6 +43,9 @@ export type EventReviewTimelineProps = { isZooming: boolean; zoomDirection: TimelineZoomDirection; dense?: boolean; + onZoomChange?: (newZoomLevel: number) => void; + possibleZoomLevels?: ZoomLevel[]; + currentZoomLevel?: number; }; export function EventReviewTimeline({ @@ -69,6 +73,9 @@ export function EventReviewTimeline({ isZooming, zoomDirection, dense = false, + onZoomChange, + possibleZoomLevels, + currentZoomLevel, }: EventReviewTimelineProps) { const internalTimelineRef = useRef(null); const selectedTimelineRef = timelineRef || internalTimelineRef; @@ -157,6 +164,9 @@ export function EventReviewTimeline({ scrollToSegment={scrollToSegment} isZooming={isZooming} zoomDirection={zoomDirection} + onZoomChange={onZoomChange} + possibleZoomLevels={possibleZoomLevels} + currentZoomLevel={currentZoomLevel} >
void; + possibleZoomLevels?: ZoomLevel[]; + currentZoomLevel?: number; }; export function MotionReviewTimeline({ @@ -77,6 +81,9 @@ export function MotionReviewTimeline({ isZooming, zoomDirection, alwaysShowMotionLine = false, + onZoomChange, + possibleZoomLevels, + currentZoomLevel, }: MotionReviewTimelineProps) { const internalTimelineRef = useRef(null); const selectedTimelineRef = timelineRef || internalTimelineRef; @@ -206,6 +213,9 @@ export function MotionReviewTimeline({ isZooming={isZooming} zoomDirection={zoomDirection} getRecordingAvailability={getRecordingAvailability} + onZoomChange={onZoomChange} + possibleZoomLevels={possibleZoomLevels} + currentZoomLevel={currentZoomLevel} > ; @@ -37,6 +39,9 @@ export type ReviewTimelineProps = { isZooming: boolean; zoomDirection: TimelineZoomDirection; getRecordingAvailability?: (time: number) => boolean | undefined; + onZoomChange?: (newZoomLevel: number) => void; + possibleZoomLevels?: ZoomLevel[]; + currentZoomLevel?: number; children: ReactNode; }; @@ -63,6 +68,9 @@ export function ReviewTimeline({ isZooming, zoomDirection, getRecordingAvailability, + onZoomChange, + possibleZoomLevels, + currentZoomLevel, children, }: ReviewTimelineProps) { const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false); @@ -78,6 +86,13 @@ export function ReviewTimeline({ const exportEndRef = useRef(null); const exportEndTimeRef = useRef(null); + // Use provided zoom levels or fallback to empty array + const zoomLevels = possibleZoomLevels ?? []; + + const currentZoomLevelIndex = + currentZoomLevel ?? + zoomLevels.findIndex((level) => level.segmentDuration === segmentDuration); + const isDragging = useMemo( () => isDraggingHandlebar || isDraggingExportStart || isDraggingExportEnd, [isDraggingHandlebar, isDraggingExportStart, isDraggingExportEnd], @@ -348,148 +363,188 @@ export function ReviewTimeline({ }, [getRecordingAvailability, handlebarTime, segmentDuration]); return ( -
-
-
-
- {children} + <> +
+
+
+
+ {children} +
+ {children && ( + <> + {showHandlebar && ( +
+
+
+
+
+
+
+
+
+ {/* TODO: determine if we should keep this tooltip */} + {false && isHandlebarInNoRecordingPeriod && ( +
+ No recordings +
+ )} +
+ )} + {showExportHandles && ( + <> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + )} + + )}
- {children && ( - <> - {showHandlebar && ( -
-
-
-
-
-
-
-
-
- {/* TODO: determine if we should keep this tooltip */} - {false && isHandlebarInNoRecordingPeriod && ( -
- No recordings -
- )} -
- )} - {showExportHandles && ( - <> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - )} - + + {onZoomChange && currentZoomLevelIndex !== -1 && ( +
+ + +
)} -
+ ); } diff --git a/web/src/pages/UIPlayground.tsx b/web/src/pages/UIPlayground.tsx index 95599d594..c078c031c 100644 --- a/web/src/pages/UIPlayground.tsx +++ b/web/src/pages/UIPlayground.tsx @@ -9,6 +9,7 @@ import { ReviewData, ReviewSegment, ReviewSeverity, + ZoomLevel, } from "@/types/review"; import { Button } from "@/components/ui/button"; import CameraActivityIndicator from "@/components/indicators/CameraActivityIndicator"; @@ -180,7 +181,7 @@ function UIPlayground() { timestampSpread: 15, }); - const possibleZoomLevels = useMemo( + const possibleZoomLevels: ZoomLevel[] = useMemo( () => [ { segmentDuration: 60, timestampSpread: 15 }, { segmentDuration: 30, timestampSpread: 5 }, @@ -414,6 +415,8 @@ function UIPlayground() { dense={isMobile} // dense will produce a smaller handlebar and only minute resolution on timestamps isZooming={isZooming} // is the timeline actively zooming? zoomDirection={zoomDirection} // is the timeline zooming in or out + onZoomChange={handleZoomChange} + possibleZoomLevels={possibleZoomLevels} /> )} {isEventsReviewTimeline && ( @@ -441,6 +444,8 @@ function UIPlayground() { dense // dense will produce a smaller handlebar and only minute resolution on timestamps isZooming={isZooming} // is the timeline actively zooming? zoomDirection={zoomDirection} // is the timeline zooming in or out + onZoomChange={handleZoomChange} + possibleZoomLevels={possibleZoomLevels} /> )}
diff --git a/web/src/types/review.ts b/web/src/types/review.ts index cd1aefff5..6c9027950 100644 --- a/web/src/types/review.ts +++ b/web/src/types/review.ts @@ -81,6 +81,11 @@ export type ConsolidatedSegmentData = { export type TimelineZoomDirection = "in" | "out" | null; +export type ZoomLevel = { + segmentDuration: number; + timestampSpread: number; +}; + export enum ThreatLevel { SUSPICIOUS = 1, DANGER = 2, diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 2737b7be0..e37393624 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -19,6 +19,7 @@ import { ReviewSeverity, ReviewSummary, SegmentedReviewData, + ZoomLevel, } from "@/types/review"; import { getChunkedTimeRange } from "@/utils/timelineUtil"; import axios from "axios"; @@ -501,7 +502,7 @@ function DetectionReview({ timestampSpread: 15, }); - const possibleZoomLevels = useMemo( + const possibleZoomLevels: ZoomLevel[] = useMemo( () => [ { segmentDuration: 60, timestampSpread: 15 }, { segmentDuration: 30, timestampSpread: 5 }, @@ -799,7 +800,7 @@ function DetectionReview({
-
+
{loading ? ( ) : ( @@ -821,6 +822,8 @@ function DetectionReview({ dense={isMobile} isZooming={isZooming} zoomDirection={zoomDirection} + onZoomChange={handleZoomChange} + possibleZoomLevels={possibleZoomLevels} /> )}
diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 2d3600288..9e1b1db68 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -22,6 +22,7 @@ import { ReviewFilter, ReviewSegment, ReviewSummary, + ZoomLevel, } from "@/types/review"; import { getChunkedTimeDay } from "@/utils/timelineUtil"; import { @@ -884,7 +885,7 @@ function Timeline({ timestampSpread: 15, }); - const possibleZoomLevels = useMemo( + const possibleZoomLevels: ZoomLevel[] = useMemo( () => [ { segmentDuration: 30, timestampSpread: 15 }, { segmentDuration: 15, timestampSpread: 5 }, @@ -1010,6 +1011,8 @@ function Timeline({ onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)} isZooming={isZooming} zoomDirection={zoomDirection} + onZoomChange={handleZoomChange} + possibleZoomLevels={possibleZoomLevels} /> ) : (