Jenn Mueng 2 лет назад
Родитель
Сommit
ede652b99f
2 измененных файлов с 103 добавлено и 10 удалено
  1. 81 0
      static/app/utils/useHotkeys.tsx
  2. 22 10
      static/app/views/app/index.tsx

+ 81 - 0
static/app/utils/useHotkeys.tsx

@@ -0,0 +1,81 @@
+import {useCallback, useEffect, useMemo, useRef} from 'react';
+
+import {getKeyCode} from './getKeyCode';
+
+const modifierKeys = [
+  getKeyCode('command'),
+  getKeyCode('shift'),
+  getKeyCode('alt'),
+  getKeyCode('ctrl'),
+];
+
+/**
+ * Pass in the hotkey combinations under match and the corresponding callback function to be called.
+ * Separate key names with +. For example, 'command+alt+shift+x'
+ * Alternate matchings as an array: ['command+alt+backspace', 'ctrl+alt+delete']
+ */
+export function useHotkeys(
+  hotkeys: {callback: (e: KeyboardEvent) => void; match: string[] | string}[],
+  deps: React.DependencyList
+): void {
+  const keysPressedRef = useRef<Set<number>>(new Set());
+
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const memoizedHotkeys = useMemo(() => hotkeys, deps);
+
+  const onKeyDown = useCallback(
+    (e: KeyboardEvent) => {
+      const removeKey = keyCode => {
+        keysPressedRef.current.delete(keyCode);
+      };
+
+      if (e.type === 'keydown') {
+        keysPressedRef.current.add(e.keyCode);
+
+        if (e.metaKey && !modifierKeys.includes(e.keyCode)) {
+          /*
+          If command/metaKey is held, keyup does not get called for non-modifier keys. See:
+          https://web.archive.org/web/20160304022453/http://bitspushedaround.com/on-a-few-things-you-may-not-know-about-the-hellish-command-key-and-javascript-events/
+
+          So, if the metaKey is held, we just have it remove the key after a set timeout, this allows you to hold the command key down
+          and press the other key again after the timeout removes the key.
+        */
+          setTimeout(() => {
+            removeKey(e.keyCode);
+          }, 500);
+        }
+
+        for (const hotkey of memoizedHotkeys) {
+          const matches = (
+            Array.isArray(hotkey.match) ? hotkey.match : [hotkey.match]
+          ).map(o => o.trim().split('+'));
+
+          for (const keys of matches) {
+            if (
+              keys.length === keysPressedRef.current.size &&
+              keys.every(key => keysPressedRef.current.has(getKeyCode(key)))
+            ) {
+              hotkey.callback(e);
+              break;
+            }
+          }
+        }
+      }
+
+      if (e.type === 'keyup') {
+        removeKey(e.keyCode);
+      }
+    },
+    [memoizedHotkeys]
+  );
+
+  useEffect(() => {
+    document.addEventListener('keydown', onKeyDown);
+    document.addEventListener('keyup', onKeyDown);
+
+    return () => {
+      document.removeEventListener('keydown', onKeyDown);
+      document.removeEventListener('keyup', onKeyDown);
+    };
+  }, [onKeyDown]);
+}

+ 22 - 10
static/app/views/app/index.tsx

@@ -1,5 +1,4 @@
 import {lazy, Profiler, Suspense, useCallback, useEffect, useRef} from 'react';
-import {useHotkeys} from 'react-hotkeys-hook';
 import styled from '@emotion/styled';
 
 import {
@@ -20,6 +19,7 @@ import OrganizationsStore from 'sentry/stores/organizationsStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
 import {onRenderCallback} from 'sentry/utils/performanceForSentry';
 import useApi from 'sentry/utils/useApi';
+import {useHotkeys} from 'sentry/utils/useHotkeys';
 
 import SystemAlerts from './systemAlerts';
 
@@ -38,18 +38,30 @@ function App({children}: Props) {
   const config = useLegacyStore(ConfigStore);
 
   // Command palette global-shortcut
-  useHotkeys('command+shift+p, command+k, ctrl+shift+p, ctrl+k', e => {
-    openCommandPalette();
-    e.preventDefault();
-  });
+  useHotkeys(
+    [
+      {
+        match: ['command+shift+p', 'command+k', 'ctrl+shift+p', 'ctrl+k'],
+        callback: e => {
+          openCommandPalette();
+          e.preventDefault();
+        },
+      },
+    ],
+    []
+  );
 
   // Theme toggle global shortcut
   useHotkeys(
-    'command+shift+l, ctrl+shift+l',
-    e => {
-      ConfigStore.set('theme', config.theme === 'light' ? 'dark' : 'light');
-      e.preventDefault();
-    },
+    [
+      {
+        match: ['command+shift+l', 'ctrl+shift+l'],
+        callback: e => {
+          ConfigStore.set('theme', config.theme === 'light' ? 'dark' : 'light');
+          e.preventDefault();
+        },
+      },
+    ],
     [config.theme]
   );