import { useEffect, useRef, useMemo } from 'react' import PropTypes from 'prop-types' import loglevel from 'loglevel' import config from '../../../config' import Tooltip from './Tooltip' import KeyboardKey from './KeyboardKey' const logger = loglevel.getLogger('ApiServerContext') logger.setLevel(config.logLevel) const MODIFIER_ALIASES = { cmd: 'meta', meta: 'meta', ctrl: 'control', control: 'control', alt: 'alt', option: 'alt', shift: 'shift' } function parseShortcut(shortcut) { return shortcut .toLowerCase() .split('+') .map((part) => MODIFIER_ALIASES[part] ?? part) } function getPressedKey(event) { if ( event.key === 'Meta' || event.key === 'Control' || event.key === 'Alt' || event.key === 'Shift' ) { return event.key.toLowerCase() } if (event.code) { const code = event.code.toLowerCase() if (code.startsWith('key')) { return code.slice(3) } return code } return null } const KeyboardShortcut = ({ shortcut, children, hint, onTrigger, log = false }) => { const pressedKeysRef = useRef(new Set()) const shortcutKeys = useMemo(() => parseShortcut(shortcut), [shortcut]) useEffect(() => { const handleKeyDown = (event) => { const key = getPressedKey(event) if (key) { pressedKeysRef.current.add(key) } if (log) { logger.info('Key Down:', key, 'Pressed Keys:', [ ...pressedKeysRef.current ]) } if ( shortcutKeys.length && shortcutKeys.every((k) => pressedKeysRef.current.has(k)) ) { if (typeof onTrigger === 'function') { onTrigger(event) } event.preventDefault() } } const handleKeyUp = (event) => { const key = getPressedKey(event) if (key) { pressedKeysRef.current.delete(key) } } window.addEventListener('keydown', handleKeyDown) window.addEventListener('keyup', handleKeyUp) return () => { window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('keyup', handleKeyUp) } }, [shortcutKeys, onTrigger, log]) if (hint) { var osSpecificHint = hint.replaceAll('ALT', '⌥') return ( {osSpecificHint}} > {children} ) } return children } KeyboardShortcut.propTypes = { shortcut: PropTypes.string.isRequired, // e.g. 'cmd+shift+p' onTrigger: PropTypes.func.isRequired, children: PropTypes.element.isRequired, hint: PropTypes.string, // Optional, e.g. '⌘ ⇧ P' log: PropTypes.bool // Optional, e.g. true } export default KeyboardShortcut