From e6790d9a6afc5f0b3d94dddbc3ca627c9618c353 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 18 Jun 2024 08:32:17 -0600 Subject: [PATCH] Add ability to select all on desktop (#12044) * Add ability to select all review items * Refactor keybaord listener --- web/src/components/card/ExportCard.tsx | 10 ++++-- web/src/components/player/VideoControls.tsx | 34 +++++++++------------ web/src/hooks/use-keyboard-listener.tsx | 12 ++++++-- web/src/pages/Logs.tsx | 4 +-- web/src/views/events/EventView.tsx | 27 ++++++++++++++++ web/src/views/live/LiveCameraView.tsx | 6 ++-- 6 files changed, 64 insertions(+), 29 deletions(-) diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index ce21113e7..95357956a 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -49,8 +49,14 @@ export default function ExportCard({ useKeyboardListener( editName != undefined ? ["Enter"] : [], - (_, down, repeat) => { - if (down && !repeat && editName && editName.update.length > 0) { + (key, modifiers) => { + if ( + key == "Enter" && + modifiers.down && + !modifiers.repeat && + editName && + editName.update.length > 0 + ) { onRename(exportedRecording.id, editName.update); setEditName(undefined); } diff --git a/web/src/components/player/VideoControls.tsx b/web/src/components/player/VideoControls.tsx index 8e86645e8..5adebdc7c 100644 --- a/web/src/components/player/VideoControls.tsx +++ b/web/src/components/player/VideoControls.tsx @@ -16,7 +16,9 @@ import { MdVolumeOff, MdVolumeUp, } from "react-icons/md"; -import useKeyboardListener from "@/hooks/use-keyboard-listener"; +import useKeyboardListener, { + KeyModifiers, +} from "@/hooks/use-keyboard-listener"; import { VolumeSlider } from "../ui/slider"; import FrigatePlusIcon from "../icons/FrigatePlusIcon"; import { @@ -137,42 +139,36 @@ export default function VideoControls({ }, [volume, muted]); const onKeyboardShortcut = useCallback( - (key: string, down: boolean, repeat: boolean) => { + (key: string, modifiers: KeyModifiers) => { + if (!modifiers.down) { + return; + } + switch (key) { case "ArrowDown": - if (down) { - onSeek(-1); - } + onSeek(-1); break; case "ArrowLeft": - if (down) { - onSeek(-10); - } + onSeek(-10); break; case "ArrowRight": - if (down) { - onSeek(10); - } + onSeek(10); break; case "ArrowUp": - if (down) { - onSeek(1); - } + onSeek(1); break; case "f": - if (toggleFullscreen && down && !repeat) { + if (toggleFullscreen && !modifiers.repeat) { toggleFullscreen(); } break; case "m": - if (setMuted && down && !repeat && video) { + if (setMuted && !modifiers.repeat && video) { setMuted(!muted); } break; case " ": - if (down) { - onPlayPause(!isPlaying); - } + onPlayPause(!isPlaying); break; } }, diff --git a/web/src/hooks/use-keyboard-listener.tsx b/web/src/hooks/use-keyboard-listener.tsx index b3143db0a..b2694f74c 100644 --- a/web/src/hooks/use-keyboard-listener.tsx +++ b/web/src/hooks/use-keyboard-listener.tsx @@ -1,8 +1,14 @@ import { useCallback, useEffect } from "react"; +export type KeyModifiers = { + down: boolean; + repeat: boolean; + ctrl: boolean; +}; + export default function useKeyboardListener( keys: string[], - listener: (key: string, down: boolean, repeat: boolean) => void, + listener: (key: string, modifiers: KeyModifiers) => void, ) { const keyDownListener = useCallback( (e: KeyboardEvent) => { @@ -12,7 +18,7 @@ export default function useKeyboardListener( if (keys.includes(e.key)) { e.preventDefault(); - listener(e.key, true, e.repeat); + listener(e.key, { down: true, repeat: e.repeat, ctrl: e.ctrlKey }); } }, [keys, listener], @@ -26,7 +32,7 @@ export default function useKeyboardListener( if (keys.includes(e.key)) { e.preventDefault(); - listener(e.key, false, false); + listener(e.key, { down: false, repeat: false, ctrl: false }); } }, [keys, listener], diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index e01c897ef..0691cb635 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -231,8 +231,8 @@ function Logs() { useKeyboardListener( ["PageDown", "PageUp", "ArrowDown", "ArrowUp"], - (key, down, _) => { - if (!down) { + (key, modifiers) => { + if (!modifiers.down) { return; } diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index dfcf0344d..ec92c00ad 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -51,6 +51,7 @@ import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { FilterList, LAST_24_HOURS_KEY } from "@/types/filter"; import { GiSoundWaves } from "react-icons/gi"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; type EventViewProps = { reviewItems?: SegmentedReviewData; @@ -158,6 +159,17 @@ export default function EventView({ }, [selectedReviews, setSelectedReviews, onOpenRecording, markItemAsReviewed], ); + const onSelectAllReviews = useCallback(() => { + if (!currentReviewItems || currentReviewItems.length == 0) { + return; + } + + if (selectedReviews.length < currentReviewItems.length) { + setSelectedReviews(currentReviewItems.map((seg) => seg.id)); + } else { + setSelectedReviews([]); + } + }, [currentReviewItems, selectedReviews]); const exportReview = useCallback( (id: string) => { @@ -376,6 +388,7 @@ export default function EventView({ markItemAsReviewed={markItemAsReviewed} markAllItemsAsReviewed={markAllItemsAsReviewed} onSelectReview={onSelectReview} + onSelectAllReviews={onSelectAllReviews} pullLatestData={pullLatestData} /> )} @@ -417,6 +430,7 @@ type DetectionReviewProps = { markItemAsReviewed: (review: ReviewSegment) => void; markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void; onSelectReview: (review: ReviewSegment, ctrl: boolean) => void; + onSelectAllReviews: () => void; pullLatestData: () => void; }; function DetectionReview({ @@ -434,6 +448,7 @@ function DetectionReview({ markItemAsReviewed, markAllItemsAsReviewed, onSelectReview, + onSelectAllReviews, pullLatestData, }: DetectionReviewProps) { const reviewTimelineRef = useRef(null); @@ -580,6 +595,18 @@ function DetectionReview({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [startTime]); + // keyboard + + useKeyboardListener(["a"], (key, modifiers) => { + if (modifiers.repeat || !modifiers.down) { + return; + } + + if (key == "a" && modifiers.ctrl) { + onSelectAllReviews(); + } + }); + return ( <>
{ - if (repeat) { + (key, modifiers) => { + if (modifiers.repeat) { return; } - if (!down) { + if (!modifiers.down) { sendPtz("STOP"); return; }