Add ability to select all on desktop (#12044)

* Add ability to select all review items

* Refactor keybaord listener
This commit is contained in:
Nicolas Mowen 2024-06-18 08:32:17 -06:00 committed by GitHub
parent 4bca405e29
commit e6790d9a6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 64 additions and 29 deletions

View File

@ -49,8 +49,14 @@ export default function ExportCard({
useKeyboardListener( useKeyboardListener(
editName != undefined ? ["Enter"] : [], editName != undefined ? ["Enter"] : [],
(_, down, repeat) => { (key, modifiers) => {
if (down && !repeat && editName && editName.update.length > 0) { if (
key == "Enter" &&
modifiers.down &&
!modifiers.repeat &&
editName &&
editName.update.length > 0
) {
onRename(exportedRecording.id, editName.update); onRename(exportedRecording.id, editName.update);
setEditName(undefined); setEditName(undefined);
} }

View File

@ -16,7 +16,9 @@ import {
MdVolumeOff, MdVolumeOff,
MdVolumeUp, MdVolumeUp,
} from "react-icons/md"; } 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 { VolumeSlider } from "../ui/slider";
import FrigatePlusIcon from "../icons/FrigatePlusIcon"; import FrigatePlusIcon from "../icons/FrigatePlusIcon";
import { import {
@ -137,42 +139,36 @@ export default function VideoControls({
}, [volume, muted]); }, [volume, muted]);
const onKeyboardShortcut = useCallback( const onKeyboardShortcut = useCallback(
(key: string, down: boolean, repeat: boolean) => { (key: string, modifiers: KeyModifiers) => {
if (!modifiers.down) {
return;
}
switch (key) { switch (key) {
case "ArrowDown": case "ArrowDown":
if (down) { onSeek(-1);
onSeek(-1);
}
break; break;
case "ArrowLeft": case "ArrowLeft":
if (down) { onSeek(-10);
onSeek(-10);
}
break; break;
case "ArrowRight": case "ArrowRight":
if (down) { onSeek(10);
onSeek(10);
}
break; break;
case "ArrowUp": case "ArrowUp":
if (down) { onSeek(1);
onSeek(1);
}
break; break;
case "f": case "f":
if (toggleFullscreen && down && !repeat) { if (toggleFullscreen && !modifiers.repeat) {
toggleFullscreen(); toggleFullscreen();
} }
break; break;
case "m": case "m":
if (setMuted && down && !repeat && video) { if (setMuted && !modifiers.repeat && video) {
setMuted(!muted); setMuted(!muted);
} }
break; break;
case " ": case " ":
if (down) { onPlayPause(!isPlaying);
onPlayPause(!isPlaying);
}
break; break;
} }
}, },

View File

@ -1,8 +1,14 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
export type KeyModifiers = {
down: boolean;
repeat: boolean;
ctrl: boolean;
};
export default function useKeyboardListener( export default function useKeyboardListener(
keys: string[], keys: string[],
listener: (key: string, down: boolean, repeat: boolean) => void, listener: (key: string, modifiers: KeyModifiers) => void,
) { ) {
const keyDownListener = useCallback( const keyDownListener = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
@ -12,7 +18,7 @@ export default function useKeyboardListener(
if (keys.includes(e.key)) { if (keys.includes(e.key)) {
e.preventDefault(); e.preventDefault();
listener(e.key, true, e.repeat); listener(e.key, { down: true, repeat: e.repeat, ctrl: e.ctrlKey });
} }
}, },
[keys, listener], [keys, listener],
@ -26,7 +32,7 @@ export default function useKeyboardListener(
if (keys.includes(e.key)) { if (keys.includes(e.key)) {
e.preventDefault(); e.preventDefault();
listener(e.key, false, false); listener(e.key, { down: false, repeat: false, ctrl: false });
} }
}, },
[keys, listener], [keys, listener],

View File

@ -231,8 +231,8 @@ function Logs() {
useKeyboardListener( useKeyboardListener(
["PageDown", "PageUp", "ArrowDown", "ArrowUp"], ["PageDown", "PageUp", "ArrowDown", "ArrowUp"],
(key, down, _) => { (key, modifiers) => {
if (!down) { if (!modifiers.down) {
return; return;
} }

View File

@ -51,6 +51,7 @@ import { toast } from "sonner";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { FilterList, LAST_24_HOURS_KEY } from "@/types/filter"; import { FilterList, LAST_24_HOURS_KEY } from "@/types/filter";
import { GiSoundWaves } from "react-icons/gi"; import { GiSoundWaves } from "react-icons/gi";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
type EventViewProps = { type EventViewProps = {
reviewItems?: SegmentedReviewData; reviewItems?: SegmentedReviewData;
@ -158,6 +159,17 @@ export default function EventView({
}, },
[selectedReviews, setSelectedReviews, onOpenRecording, markItemAsReviewed], [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( const exportReview = useCallback(
(id: string) => { (id: string) => {
@ -376,6 +388,7 @@ export default function EventView({
markItemAsReviewed={markItemAsReviewed} markItemAsReviewed={markItemAsReviewed}
markAllItemsAsReviewed={markAllItemsAsReviewed} markAllItemsAsReviewed={markAllItemsAsReviewed}
onSelectReview={onSelectReview} onSelectReview={onSelectReview}
onSelectAllReviews={onSelectAllReviews}
pullLatestData={pullLatestData} pullLatestData={pullLatestData}
/> />
)} )}
@ -417,6 +430,7 @@ type DetectionReviewProps = {
markItemAsReviewed: (review: ReviewSegment) => void; markItemAsReviewed: (review: ReviewSegment) => void;
markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void; markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void;
onSelectReview: (review: ReviewSegment, ctrl: boolean) => void; onSelectReview: (review: ReviewSegment, ctrl: boolean) => void;
onSelectAllReviews: () => void;
pullLatestData: () => void; pullLatestData: () => void;
}; };
function DetectionReview({ function DetectionReview({
@ -434,6 +448,7 @@ function DetectionReview({
markItemAsReviewed, markItemAsReviewed,
markAllItemsAsReviewed, markAllItemsAsReviewed,
onSelectReview, onSelectReview,
onSelectAllReviews,
pullLatestData, pullLatestData,
}: DetectionReviewProps) { }: DetectionReviewProps) {
const reviewTimelineRef = useRef<HTMLDivElement>(null); const reviewTimelineRef = useRef<HTMLDivElement>(null);
@ -580,6 +595,18 @@ function DetectionReview({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [startTime]); }, [startTime]);
// keyboard
useKeyboardListener(["a"], (key, modifiers) => {
if (modifiers.repeat || !modifiers.down) {
return;
}
if (key == "a" && modifiers.ctrl) {
onSelectAllReviews();
}
});
return ( return (
<> <>
<div <div

View File

@ -482,12 +482,12 @@ function PtzControlPanel({
useKeyboardListener( useKeyboardListener(
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "+", "-"], ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "+", "-"],
(key, down, repeat) => { (key, modifiers) => {
if (repeat) { if (modifiers.repeat) {
return; return;
} }
if (!down) { if (!modifiers.down) {
sendPtz("STOP"); sendPtz("STOP");
return; return;
} }