mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Add ability to select all on desktop (#12044)
* Add ability to select all review items * Refactor keybaord listener
This commit is contained in:
parent
4bca405e29
commit
e6790d9a6a
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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],
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user