From 34ace2fc0aa7070cc81204fdb10ed7361d433b1b Mon Sep 17 00:00:00 2001 From: Arnei Date: Mon, 22 Jan 2024 15:07:26 +0100 Subject: [PATCH 1/4] Replace Hotkey library Replaces react-hotkeys with react-hotkeys-hooks. The latter is maintained, easier in use and already used in other Opencast frontend projects. Also makes a small change to the hotkey cheat sheet, where hotkeys that can currently not be used are displayed in a lower opacity. --- app/package-lock.json | 28 +++++------ app/package.json | 2 +- app/src/components/Header.tsx | 18 +++---- app/src/components/events/Events.tsx | 16 +++---- app/src/components/events/Series.tsx | 16 +++---- .../components/shared/HotKeyCheatSheet.tsx | 44 ++++++++++------- app/src/components/shared/MainNav.tsx | 39 ++++++++------- app/src/components/shared/TableFilters.tsx | 16 +++---- app/src/configs/hotkeysConfig.ts | 47 +++++++------------ app/src/index.tsx | 5 +- 10 files changed, 115 insertions(+), 116 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 41caf91cf2..3f2fe24a0a 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -35,7 +35,7 @@ "react-chartjs-2": "^5.2.0", "react-datepicker": "^4.11.0", "react-dom": "^17.0.2", - "react-hotkeys": "^2.0.0", + "react-hotkeys-hook": "^4.4.4", "react-i18next": "^12.2.2", "react-icons": "^4.8.0", "react-redux": "^7.2.9", @@ -18781,15 +18781,13 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, - "node_modules/react-hotkeys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", - "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", - "dependencies": { - "prop-types": "^15.6.1" - }, + "node_modules/react-hotkeys-hook": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.4.tgz", + "integrity": "sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==", "peerDependencies": { - "react": ">= 0.14.0" + "react": ">=16.8.1", + "react-dom": ">=16.8.1" } }, "node_modules/react-i18next": { @@ -37184,13 +37182,11 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, - "react-hotkeys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", - "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", - "requires": { - "prop-types": "^15.6.1" - } + "react-hotkeys-hook": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.4.tgz", + "integrity": "sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==", + "requires": {} }, "react-i18next": { "version": "12.2.2", diff --git a/app/package.json b/app/package.json index 54078f614e..acf3a3801f 100644 --- a/app/package.json +++ b/app/package.json @@ -31,7 +31,7 @@ "react-chartjs-2": "^5.2.0", "react-datepicker": "^4.11.0", "react-dom": "^17.0.2", - "react-hotkeys": "^2.0.0", + "react-hotkeys-hook": "^4.4.4", "react-i18next": "^12.2.2", "react-icons": "^4.8.0", "react-redux": "^7.2.9", diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index c225aaca11..2ce9a8e706 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -7,7 +7,6 @@ import i18n from "../i18n/i18n"; import languages from "../i18n/languages"; // @ts-expect-error TS(2307): Cannot find module '../img/opencast-white.svg' or ... Remove this comment to see the full error message import opencastLogo from "../img/opencast-white.svg"; -import { GlobalHotKeys } from "react-hotkeys"; import { fetchHealthStatus } from "../thunks/healthThunks"; import { setSpecificServiceFilter } from "../thunks/tableFilterThunks"; import { loadServicesIntoTable } from "../thunks/tableThunks"; @@ -22,6 +21,7 @@ import { getCurrentLanguageInformation, hasAccess } from "../utils/utils"; import { overflowStyle } from "../utils/componentStyles"; import RegistrationModal from "./shared/RegistrationModal"; import HotKeyCheatSheet from "./shared/HotKeyCheatSheet"; +import { useHotkeys } from "react-hotkeys-hook"; // Get code, flag and name of the current language const currentLanguage = getCurrentLanguageInformation(); @@ -112,10 +112,17 @@ const Header = ({ setHotKeyCheatSheet(false); }; - const hotKeyHandlers = { - HOTKEY_CHEATSHEET: showHotKeyCheatSheet, + const toggleHotKeyCheatSheet = () => { + setHotKeyCheatSheet(!displayHotKeyCheatSheet); }; + useHotkeys( + availableHotkeys.general.HOTKEY_CHEATSHEET.sequence, + () => toggleHotKeyCheatSheet(), + { description: t(availableHotkeys.general.HOTKEY_CHEATSHEET.description) ?? undefined }, + [toggleHotKeyCheatSheet] + ); + useEffect(() => { // Function for handling clicks outside of an open dropdown menu // @ts-expect-error TS(7006): Parameter 'e' implicitly has an 'any' type. @@ -158,11 +165,6 @@ const Header = ({ return ( <> -
{/* Opencast logo in upper left corner */}
diff --git a/app/src/components/events/Events.tsx b/app/src/components/events/Events.tsx index a90dcdffe9..57821e3c8f 100644 --- a/app/src/components/events/Events.tsx +++ b/app/src/components/events/Events.tsx @@ -35,10 +35,10 @@ import Footer from "../Footer"; import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { showActions } from "../../actions/eventActions"; -import { GlobalHotKeys } from "react-hotkeys"; import { availableHotkeys } from "../../configs/hotkeysConfig"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; import { fetchAssetUploadOptions } from "../../thunks/assetsThunks"; +import { useHotkeys } from "react-hotkeys-hook"; // References for detecting a click outside of the container of the dropdown menu const containerAction = React.createRef(); @@ -193,17 +193,15 @@ const Events = ({ setEditMetadataEventsModal(false); }; - const hotKeyHandlers = { - NEW_EVENT: showNewEventModal, - }; + useHotkeys( + availableHotkeys.general.NEW_EVENT.sequence, + () => showNewEventModal(), + { description: t(availableHotkeys.general.NEW_EVENT.description) ?? undefined }, + [showNewEventModal] + ); return ( <> -
diff --git a/app/src/components/events/Series.tsx b/app/src/components/events/Series.tsx index 228992d917..d747ffe920 100644 --- a/app/src/components/events/Series.tsx +++ b/app/src/components/events/Series.tsx @@ -31,8 +31,8 @@ import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { showActions } from "../../actions/seriesActions"; import { availableHotkeys } from "../../configs/hotkeysConfig"; -import { GlobalHotKeys } from "react-hotkeys"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; +import { useHotkeys } from "react-hotkeys-hook"; // References for detecting a click outside of the container of the dropdown menu const containerAction = React.createRef(); @@ -165,17 +165,15 @@ const Series = ({ setDeleteSeriesModal(false); }; - const hotKeyHandlers = { - NEW_SERIES: showNewSeriesModal, - }; + useHotkeys( + availableHotkeys.general.NEW_SERIES.sequence, + () => showNewSeriesModal(), + { description: t(availableHotkeys.general.NEW_SERIES.description) ?? undefined }, + [showNewSeriesModal] + ); return ( <> -
diff --git a/app/src/components/shared/HotKeyCheatSheet.tsx b/app/src/components/shared/HotKeyCheatSheet.tsx index 1594b2d8f8..23110da2b8 100644 --- a/app/src/components/shared/HotKeyCheatSheet.tsx +++ b/app/src/components/shared/HotKeyCheatSheet.tsx @@ -1,19 +1,37 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { availableHotkeys } from "../../configs/hotkeysConfig"; +import { useHotkeysContext } from "react-hotkeys-hook"; +import { Hotkey } from "react-hotkeys-hook/dist/types"; /** * This component renders the hotkey cheat sheet showing all available hotkeys */ -const HotKeyCheatSheet = ({ - close -}: any) => { +const HotKeyCheatSheet: React.FC<{ + close: () => void, +}> = ({ + close +}) => { const { t } = useTranslation(); const handleClose = () => { close(); }; + const { hotkeys } = useHotkeysContext(); + + const checkHotkeys = (hotkeys: readonly Hotkey[], searchkeys: string[]) => { + for (const hotkey of hotkeys) { + if (!hotkey.keys) { continue; } + if (hotkey.keys.length !== searchkeys.length) { continue; } + if (hotkey.keys.every((element, index) => element === searchkeys[index])) { + return true; + } + } + + return false; + } + return ( <>
@@ -38,21 +56,18 @@ const HotKeyCheatSheet = ({ {/* Repeat row for each hotkey in group*/} -{/* @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message */} {Object.keys(availableHotkeys[hotkeyGroup]).map( (hotkey, key) => ( - +

{/* repeat for each key in hotkey */} -{/* @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message */} {availableHotkeys[hotkeyGroup][ hotkey -// @ts-expect-error TS(7006): Parameter 'comboKey' implicitly has an 'any' type. - ].combo.map((comboKey, key) => ( - <> - + ].sequence.map((comboKey, key) => ( + + {t( "HOTKEYS.KEYS." + @@ -62,23 +77,20 @@ const HotKeyCheatSheet = ({ {comboKey === -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message availableHotkeys[hotkeyGroup][hotkey] - .combo[ -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message + .sequence[ availableHotkeys[hotkeyGroup][hotkey] - .combo.length - 1 + .sequence.length - 1 ] ? "" : " + "} - + ))}

{t( -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message availableHotkeys[hotkeyGroup][hotkey] .description )} diff --git a/app/src/components/shared/MainNav.tsx b/app/src/components/shared/MainNav.tsx index e75a72381b..b64ee8a8ba 100644 --- a/app/src/components/shared/MainNav.tsx +++ b/app/src/components/shared/MainNav.tsx @@ -27,10 +27,10 @@ import { fetchSeries } from "../../thunks/seriesThunks"; import { fetchServers } from "../../thunks/serverThunks"; import { fetchServices } from "../../thunks/serviceThunks"; import { fetchGroups } from "../../thunks/groupThunks"; -import { GlobalHotKeys } from "react-hotkeys"; import { availableHotkeys } from "../../configs/hotkeysConfig"; import { fetchAcls } from "../../slices/aclSlice"; import { useAppDispatch } from "../../store"; +import { useHotkeys } from "react-hotkeys-hook"; /** * This component renders the main navigation that opens when the burger button is clicked @@ -224,26 +224,29 @@ const MainNav = ({ loadingThemesIntoTable(); }; - const hotkeyLoadEvents = () => { - navigate("/events/events"); - }; - - const hotkeyLoadSeries = () => { - navigate("/events/series"); - }; + useHotkeys( + availableHotkeys.general.EVENT_VIEW.sequence, + () => navigate("/events/events"), + { description: t(availableHotkeys.general.EVENT_VIEW.description) ?? undefined }, + [] + ); + + useHotkeys( + availableHotkeys.general.SERIES_VIEW.sequence, + () => navigate("/events/series"), + { description: t(availableHotkeys.general.SERIES_VIEW.description) ?? undefined }, + [] + ); + + useHotkeys( + availableHotkeys.general.MAIN_MENU.sequence, + () => toggleMenu(), + { description: t(availableHotkeys.general.MAIN_MENU.description) ?? undefined }, + [toggleMenu] + ); - const hotKeyHandlers = { - EVENT_VIEW: hotkeyLoadEvents, - SERIES_VIEW: hotkeyLoadSeries, - MAIN_MENU: toggleMenu, - }; return ( <> -
toggleMenu()}> {isOpen && (