From 84ea8e480e33bbcff12e51be7bb9237096dd14a3 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 6 May 2022 17:56:07 +0100 Subject: [PATCH] Refactoring and optimizations for `useHotkeys` --- src/components/left/LeftColumn.tsx | 8 +-- src/components/left/main/ChatFolders.tsx | 2 +- src/components/left/main/ChatList.tsx | 17 +++---- src/components/middle/HeaderActions.tsx | 6 +-- .../middle/hooks/useCopySelectedMessages.ts | 2 +- src/hooks/useHotkeys.ts | 51 +++++++++++-------- src/hooks/useNativeCopySelectedMessages.ts | 2 +- src/util/parseHotkey.ts | 13 ----- 8 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 7aabaed89..eef8c7a43 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -282,10 +282,10 @@ const LeftColumn: FC = ({ openChat({ id: currentUserId }); }, [currentUserId, openChat]); - useHotkeys([ - ['mod+shift+F', handleHotkeySearch], - ['mod+shift+S', handleHotkeySavedMessages], - ]); + useHotkeys({ + 'mod+shift+F': handleHotkeySearch, + 'mod+shift+S': handleHotkeySavedMessages, + }); useEffect(() => { clearTwoFaError(); diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index 3bb620c31..344bd862f 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -163,7 +163,7 @@ const ChatFolders: FC = ({ return () => { document.removeEventListener('keydown', handleKeyDown, true); }; - }); + }, [currentUserId, folderTabs, openChat, setActiveChatFolder]); const { shouldRender: shouldRenderPlaceholder, transitionClassNames, diff --git a/src/components/left/main/ChatList.tsx b/src/components/left/main/ChatList.tsx index f67f54066..9e89b333b 100644 --- a/src/components/left/main/ChatList.tsx +++ b/src/components/left/main/ChatList.tsx @@ -19,7 +19,7 @@ import usePrevious from '../../../hooks/usePrevious'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager'; import { useChatAnimationType } from './hooks'; -import { HotkeyItem, useHotkeys } from '../../../hooks/useHotkeys'; +import { useHotkeys } from '../../../hooks/useHotkeys'; import InfiniteScroll from '../../ui/InfiniteScroll'; import Loading from '../../ui/Loading'; @@ -76,19 +76,16 @@ const ChatList: FC = ({ const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE); // Support + to navigate between chats - const hotkeys: HotkeyItem[] = []; - if (isActive && orderedIds?.length) { - hotkeys.push(['alt+ArrowUp', (e: KeyboardEvent) => { + useHotkeys(isActive && orderedIds?.length ? { + 'alt+ArrowUp': (e: KeyboardEvent) => { e.preventDefault(); openNextChat({ targetIndexDelta: -1, orderedIds }); - }]); - hotkeys.push(['alt+ArrowDown', (e: KeyboardEvent) => { + }, + 'alt+ArrowDown': (e: KeyboardEvent) => { e.preventDefault(); openNextChat({ targetIndexDelta: 1, orderedIds }); - }]); - } - - useHotkeys(hotkeys); + }, + } : undefined); // Support + to navigate between chats useEffect(() => { diff --git a/src/components/middle/HeaderActions.tsx b/src/components/middle/HeaderActions.tsx index b1bd40f53..b645a28ba 100644 --- a/src/components/middle/HeaderActions.tsx +++ b/src/components/middle/HeaderActions.tsx @@ -151,9 +151,9 @@ const HeaderActions: FC = ({ handleSearchClick(); }, [canSearch, handleSearchClick]); - useHotkeys([ - ['meta+F', handleHotkeySearchClick], - ]); + useHotkeys({ + 'meta+F': handleHotkeySearchClick, + }); const lang = useLang(); diff --git a/src/components/middle/hooks/useCopySelectedMessages.ts b/src/components/middle/hooks/useCopySelectedMessages.ts index cafc14e74..983bfb083 100644 --- a/src/components/middle/hooks/useCopySelectedMessages.ts +++ b/src/components/middle/hooks/useCopySelectedMessages.ts @@ -10,7 +10,7 @@ const useCopySelectedMessages = (isActive: boolean, copySelectedMessages: NoneTo copySelectedMessages(); } - useHotkeys([['meta+C', handleCopy]]); + useHotkeys({ 'meta+C': handleCopy }); }; export default useCopySelectedMessages; diff --git a/src/hooks/useHotkeys.ts b/src/hooks/useHotkeys.ts index f3dd53617..427e6898c 100644 --- a/src/hooks/useHotkeys.ts +++ b/src/hooks/useHotkeys.ts @@ -1,31 +1,40 @@ -// Original source from Mantine -// https://github.com/mantinedev/mantine/blob/master/src/mantine-hooks/src/use-hotkeys/ - import { useEffect } from '../lib/teact/teact'; -import { getHotkeyHandler, getHotkeyMatcher } from '../util/parseHotkey'; +import { getHotkeyMatcher } from '../util/parseHotkey'; +import { createCallbackManager } from '../util/callbacks'; -export { getHotkeyHandler }; +const IGNORE_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT']); -export type HotkeyItem = [string, (event: KeyboardEvent) => void]; +const handlers = createCallbackManager(); +document.documentElement.addEventListener('keydown', handlers.runCallbacks); -function shouldFireEvent(event: KeyboardEvent) { - if (event.target instanceof HTMLElement) { - return !['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName); - } - return true; -} - -export function useHotkeys(hotkeys: HotkeyItem[]) { +export function useHotkeys(hotkeys?: Record void>) { useEffect(() => { - const keydownListener = (event: KeyboardEvent) => { - hotkeys.forEach(([hotkey, handler]) => { - if (getHotkeyMatcher(hotkey)(event) && shouldFireEvent(event)) { - handler(event); + if (!hotkeys) { + return undefined; + } + + const entries = Object.entries(hotkeys); + + function handleKeyDown(e: KeyboardEvent) { + if (!shouldFireEvent(e)) { + return; + } + + entries.forEach(([hotkey, handler]) => { + if (getHotkeyMatcher(hotkey)(e)) { + handler(e); } }); - }; + } - document.documentElement.addEventListener('keydown', keydownListener); - return () => document.documentElement.removeEventListener('keydown', keydownListener); + return handlers.addCallback(handleKeyDown); }, [hotkeys]); } + +function shouldFireEvent(e: KeyboardEvent) { + if (e.target instanceof HTMLElement) { + return !IGNORE_TAGS.has(e.target.tagName); + } + + return true; +} diff --git a/src/hooks/useNativeCopySelectedMessages.ts b/src/hooks/useNativeCopySelectedMessages.ts index 5ad87beee..f90d27425 100644 --- a/src/hooks/useNativeCopySelectedMessages.ts +++ b/src/hooks/useNativeCopySelectedMessages.ts @@ -11,7 +11,7 @@ const useNativeCopySelectedMessages = (copyMessagesByIds: ({ messageIds }: { mes } } - useHotkeys([['meta+C', handleCopy]]); + useHotkeys({ 'meta+C': handleCopy }); }; export default useNativeCopySelectedMessages; diff --git a/src/util/parseHotkey.ts b/src/util/parseHotkey.ts index 0dcee34ce..d90d5561b 100644 --- a/src/util/parseHotkey.ts +++ b/src/util/parseHotkey.ts @@ -13,8 +13,6 @@ export type Hotkey = KeyboardModifiers & { key?: string; }; -type HotkeyItem = [string, (event: React.KeyboardEvent) => void]; - type CheckHotkeyMatch = (event: KeyboardEvent) => boolean; export function parseHotkey(hotkey: string): Hotkey { @@ -77,14 +75,3 @@ function isExactHotkey(hotkey: Hotkey, event: KeyboardEvent): boolean { export function getHotkeyMatcher(hotkey: string): CheckHotkeyMatch { return (event) => isExactHotkey(parseHotkey(hotkey), event); } - -export function getHotkeyHandler(hotkeys: HotkeyItem[]) { - return (event: React.KeyboardEvent) => { - hotkeys.forEach(([hotkey, handler]) => { - if (getHotkeyMatcher(hotkey)(event.nativeEvent)) { - event.preventDefault(); - handler(event); - } - }); - }; -}