From 7614e6435d8a0fa20f778832f798bf110bcf7bf6 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 2 Oct 2025 07:14:42 -0600 Subject: [PATCH] Send content ref to get page changes for free --- web/src/hooks/use-keyboard-listener.tsx | 60 ++++++++++++++++--- web/src/pages/Exports.tsx | 38 +----------- web/src/pages/FaceLibrary.tsx | 27 +-------- .../classification/ModelTrainingView.tsx | 27 +-------- web/src/views/events/EventView.tsx | 27 +-------- web/src/views/search/SearchView.tsx | 36 +---------- 6 files changed, 60 insertions(+), 155 deletions(-) diff --git a/web/src/hooks/use-keyboard-listener.tsx b/web/src/hooks/use-keyboard-listener.tsx index 555720e30..d887a2bd9 100644 --- a/web/src/hooks/use-keyboard-listener.tsx +++ b/web/src/hooks/use-keyboard-listener.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from "react"; +import { MutableRefObject, useCallback, useEffect, useMemo } from "react"; export type KeyModifiers = { down: boolean; @@ -9,8 +9,17 @@ export type KeyModifiers = { export default function useKeyboardListener( keys: string[], - listener: (key: string | null, modifiers: KeyModifiers) => boolean, + listener?: (key: string | null, modifiers: KeyModifiers) => boolean, + contentRef?: MutableRefObject, ) { + const pageKeys = useMemo( + () => + contentRef != undefined + ? ["ArrowDown", "ArrowUp", "PageDown", "PageUp"] + : [], + [contentRef], + ); + const keyDownListener = useCallback( (e: KeyboardEvent) => { // @ts-expect-error we know this field exists @@ -25,14 +34,44 @@ export default function useKeyboardListener( shift: e.shiftKey, }; - if (keys.includes(e.key)) { + if (contentRef && pageKeys.includes(e.key)) { + switch (e.key) { + case "ArrowDown": + contentRef.current?.scrollBy({ + top: 100, + behavior: "smooth", + }); + break; + case "ArrowUp": + contentRef.current?.scrollBy({ + top: -100, + behavior: "smooth", + }); + break; + case "PageDown": + contentRef.current?.scrollBy({ + top: contentRef.current.clientHeight / 2, + behavior: "smooth", + }); + break; + case "PageUp": + contentRef.current?.scrollBy({ + top: -contentRef.current.clientHeight / 2, + behavior: "smooth", + }); + break; + } + } else if (keys.includes(e.key) && listener) { const preventDefault = listener(e.key, modifiers); if (preventDefault) e.preventDefault(); - } else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { + } else if ( + listener && + (e.key === "Shift" || e.key === "Control" || e.key === "Meta") + ) { listener(null, modifiers); } }, - [keys, listener], + [keys, pageKeys, listener, contentRef], ); const keyUpListener = useCallback( @@ -48,10 +87,13 @@ export default function useKeyboardListener( shift: false, }; - if (keys.includes(e.key)) { - e.preventDefault(); - listener(e.key, modifiers); - } else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { + if (listener && keys.includes(e.key)) { + const preventDefault = listener(e.key, modifiers); + if (preventDefault) e.preventDefault(); + } else if ( + listener && + (e.key === "Shift" || e.key === "Control" || e.key === "Meta") + ) { listener(null, modifiers); } }, diff --git a/web/src/pages/Exports.tsx b/web/src/pages/Exports.tsx index bfaeac971..7e4e820d9 100644 --- a/web/src/pages/Exports.tsx +++ b/web/src/pages/Exports.tsx @@ -113,43 +113,7 @@ function Exports() { // Keyboard Listener const contentRef = useRef(null); - useKeyboardListener( - ["ArrowDown", "ArrowUp", "PageDown", "PageUp"], - (key, modifiers) => { - if (!modifiers.down) { - return true; - } - - switch (key) { - case "ArrowDown": - contentRef.current?.scrollBy({ - top: 100, - behavior: "smooth", - }); - break; - case "ArrowUp": - contentRef.current?.scrollBy({ - top: -100, - behavior: "smooth", - }); - break; - case "PageDown": - contentRef.current?.scrollBy({ - top: contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - break; - case "PageUp": - contentRef.current?.scrollBy({ - top: -contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - break; - } - - return true; - }, - ); + useKeyboardListener([], undefined, contentRef); return (
diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 1e42ac4b8..d48ef19c4 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -270,7 +270,7 @@ export default function FaceLibrary() { const contentRef = useRef(null); useKeyboardListener( - ["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], + ["a", "Escape"], (key, modifiers) => { if (!modifiers.down) { return true; @@ -293,34 +293,11 @@ export default function FaceLibrary() { case "Escape": setSelectedFaces([]); return true; - case "ArrowDown": - contentRef.current?.scrollBy({ - top: 100, - behavior: "smooth", - }); - return true; - case "ArrowUp": - contentRef.current?.scrollBy({ - top: -100, - behavior: "smooth", - }); - return true; - case "PageDown": - contentRef.current?.scrollBy({ - top: contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - break; - case "PageUp": - contentRef.current?.scrollBy({ - top: -contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; } return false; }, + contentRef, ); useEffect(() => { diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 6e2d1606f..7fe241496 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -235,7 +235,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { const contentRef = useRef(null); useKeyboardListener( - ["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], + ["a", "Escape"], (key, modifiers) => { if (!modifiers.down) { return true; @@ -259,34 +259,11 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { case "Escape": setSelectedImages([]); return true; - case "ArrowDown": - contentRef.current?.scrollBy({ - top: 100, - behavior: "smooth", - }); - return true; - case "ArrowUp": - contentRef.current?.scrollBy({ - top: -100, - behavior: "smooth", - }); - return true; - case "PageDown": - contentRef.current?.scrollBy({ - top: contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; - case "PageUp": - contentRef.current?.scrollBy({ - top: -contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; } return false; }, + contentRef, ); useEffect(() => { diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 174176bdc..ca77b22c7 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -651,7 +651,7 @@ function DetectionReview({ // keyboard useKeyboardListener( - ["a", "r", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], + ["a", "r", "Escape"], (key, modifiers) => { if (!modifiers.down) { return true; @@ -679,34 +679,11 @@ function DetectionReview({ case "Escape": setSelectedReviews([]); return true; - case "ArrowDown": - contentRef.current?.scrollBy({ - top: 100, - behavior: "smooth", - }); - return true; - case "ArrowUp": - contentRef.current?.scrollBy({ - top: -100, - behavior: "smooth", - }); - return true; - case "PageDown": - contentRef.current?.scrollBy({ - top: contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; - case "PageUp": - contentRef.current?.scrollBy({ - top: -contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; } return false; }, + contentRef, ); return ( diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 577fd8fd2..5cd36beab 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -359,30 +359,6 @@ export default function SearchView({ setSearchDetail(uniqueResults[newIndex]); } return true; - case "ArrowDown": - contentRef.current?.scrollBy({ - top: 100, - behavior: "smooth", - }); - return true; - case "ArrowUp": - contentRef.current?.scrollBy({ - top: -100, - behavior: "smooth", - }); - return true; - case "PageDown": - contentRef.current?.scrollBy({ - top: contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; - case "PageUp": - contentRef.current?.scrollBy({ - top: -contentRef.current.clientHeight / 2, - behavior: "smooth", - }); - return true; } return false; @@ -391,17 +367,9 @@ export default function SearchView({ ); useKeyboardListener( - [ - "a", - "Escape", - "ArrowDown", - "ArrowLeft", - "ArrowRight", - "ArrowUp", - "PageDown", - "PageUp", - ], + ["a", "Escape", "ArrowLeft", "ArrowRight"], onKeyboardShortcut, + contentRef, ); // scroll into view