From bfefed4d6e943c8b6b025ec3524c98860c3ac947 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 19 Apr 2024 16:12:03 -0600 Subject: [PATCH] Dynamically scale the slider height when hovering + other UI tweaks (#11042) * Make no thumb slider height dynamic * Use existing switch component * Use existing switch component for general filter content * Show message when no reordings found for time * Don't show while scrubbing * Fix key error * Fix background color for controls on motion page --- .../components/filter/ReviewFilterGroup.tsx | 48 ++++------- .../player/PreviewThumbnailPlayer.tsx | 5 +- .../player/dynamic/DynamicVideoController.ts | 16 +++- .../player/dynamic/DynamicVideoPlayer.tsx | 17 +++- web/src/components/ui/slider.tsx | 2 +- web/src/types/playback.ts | 1 + web/src/views/events/EventView.tsx | 6 +- web/src/views/live/LiveCameraView.tsx | 85 +++++-------------- 8 files changed, 77 insertions(+), 103 deletions(-) diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index e4eafb8df..2248835a2 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -593,40 +593,26 @@ export function GeneralFilterContent({
{allLabels.map((item) => ( -
- - { - if (isChecked) { - const updatedLabels = currentLabels - ? [...currentLabels] - : []; + { + if (isChecked) { + const updatedLabels = currentLabels ? [...currentLabels] : []; - updatedLabels.push(item); + updatedLabels.push(item); + setCurrentLabels(updatedLabels); + } else { + const updatedLabels = currentLabels ? [...currentLabels] : []; + + // can not deselect the last item + if (updatedLabels.length > 1) { + updatedLabels.splice(updatedLabels.indexOf(item), 1); setCurrentLabels(updatedLabels); - } else { - const updatedLabels = currentLabels - ? [...currentLabels] - : []; - - // can not deselect the last item - if (updatedLabels.length > 1) { - updatedLabels.splice(updatedLabels.indexOf(item), 1); - setCurrentLabels(updatedLabels); - } } - }} - /> -
+ } + }} + /> ))}
diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index a7968075d..0767933b2 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -520,6 +520,7 @@ export function VideoPreview({ const onStopManualSeek = useCallback(() => { setTimeout(() => { setIgnoreClick(false); + setHoverTimeout(undefined); if (isSafari || (isFirefox && isMobile)) { setManualPlayback(true); @@ -565,7 +566,7 @@ export function VideoPreview({ {showProgress && ( void; private setFocusedItem: (timeline: Timeline) => void; private playerMode: PlayerMode = "playback"; // playback private recordings: Recording[] = []; + private timeRange: TimeRange = { after: 0, before: 0 }; private annotationOffset: number; private timeToStart: number | undefined = undefined; @@ -24,6 +26,7 @@ export class DynamicVideoController { previewController: PreviewController, annotationOffset: number, defaultMode: PlayerMode, + setNoRecording: (noRecs: boolean) => void, setFocusedItem: (timeline: Timeline) => void, ) { this.camera = camera; @@ -31,11 +34,13 @@ export class DynamicVideoController { this.previewController = previewController; this.annotationOffset = annotationOffset; this.playerMode = defaultMode; + this.setNoRecording = setNoRecording; this.setFocusedItem = setFocusedItem; } newPlayback(newPlayback: DynamicPlayback) { this.recordings = newPlayback.recordings; + this.timeRange = newPlayback.timeRange; if (this.timeToStart) { this.seekToTimestamp(this.timeToStart); @@ -52,12 +57,17 @@ export class DynamicVideoController { } seekToTimestamp(time: number, play: boolean = false) { + if (time < this.timeRange.after || time > this.timeRange.before) { + this.timeToStart = time; + return; + } + if ( this.recordings.length == 0 || time < this.recordings[0].start_time || time > this.recordings[this.recordings.length - 1].end_time ) { - this.timeToStart = time; + this.setNoRecording(true); return; } @@ -90,6 +100,8 @@ export class DynamicVideoController { } else { this.playerController.pause(); } + } else { + console.log(`seek time is 0`); } } diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index f923fbd43..15216b749 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -45,6 +45,7 @@ export default function DynamicVideoPlayer({ const playerRef = useRef(null); const [previewController, setPreviewController] = useState(null); + const [noRecording, setNoRecording] = useState(false); const controller = useMemo(() => { if (!config || !playerRef.current || !previewController) { return undefined; @@ -56,6 +57,7 @@ export default function DynamicVideoPlayer({ previewController, (config.cameras[camera]?.detect?.annotation_offset || 0) / 1000, isScrubbing ? "scrubbing" : "playback", + setNoRecording, () => {}, ); // we only want to fire once when players are ready @@ -92,9 +94,11 @@ export default function DynamicVideoPlayer({ return () => { if (loadingTimeout) { - clearTimeout(loadingTimeout) + clearTimeout(loadingTimeout); } - } + }; + // we only want trigger when scrubbing state changes + // eslint-disable-next-line react-hooks/exhaustive-deps }, [camera, isScrubbing]); const onPlayerLoaded = useCallback(() => { @@ -149,6 +153,7 @@ export default function DynamicVideoPlayer({ controller.newPlayback({ recordings: recordings ?? [], + timeRange, }); // we only want this to change when recordings update @@ -175,6 +180,7 @@ export default function DynamicVideoPlayer({ } setIsLoading(false); + setNoRecording(false); }} /> - {isLoading && ( + {isLoading && !noRecording && ( )} + {!isScrubbing && noRecording && ( +
+ No recordings found for this time +
+ )} ); } diff --git a/web/src/components/ui/slider.tsx b/web/src/components/ui/slider.tsx index 8a3e93747..1dde1df67 100644 --- a/web/src/components/ui/slider.tsx +++ b/web/src/components/ui/slider.tsx @@ -55,7 +55,7 @@ const NoThumbSlider = React.forwardRef< )} {...props} > - + diff --git a/web/src/types/playback.ts b/web/src/types/playback.ts index 6c4202304..ae45d43d7 100644 --- a/web/src/types/playback.ts +++ b/web/src/types/playback.ts @@ -4,6 +4,7 @@ import { TimeRange } from "./timeline"; export type DynamicPlayback = { recordings: Recording[]; + timeRange: TimeRange; }; export type PreviewPlayback = { diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 2540efc79..6c6bd2dec 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -591,7 +591,9 @@ function DetectionReview({ }) : Array(itemsToReview) .fill(0) - .map(() => )} + .map((_, idx) => ( + + ))} {!loading && (currentItems?.length ?? 0) > 0 && (itemsToReview ?? 0) > 0 && ( @@ -953,7 +955,7 @@ function MotionReview({ {!scrubbing && ( -
- - - sendDetect(detectState == "ON" ? "OFF" : "ON") - } - /> -
-
- - - sendRecord(recordState == "ON" ? "OFF" : "ON") - } - /> -
-
- - - sendSnapshot(snapshotState == "ON" ? "OFF" : "ON") - } - /> -
+ sendDetect(detectState == "ON" ? "OFF" : "ON")} + /> + sendRecord(recordState == "ON" ? "OFF" : "ON")} + /> + + sendSnapshot(snapshotState == "ON" ? "OFF" : "ON") + } + /> {audioDetectEnabled && ( -
- - - sendAudio(audioState == "ON" ? "OFF" : "ON") - } - /> -
+ sendAudio(audioState == "ON" ? "OFF" : "ON")} + /> )}