// @flow import { keyboardKeys } from './keyboard-keys'; import { ALT_SYM, CTRL_SYM, isMac, META_SYM, SHIFT_SYM } from './constants'; /** * The readable definition of a hotkey. * The {@code id} the hotkey's reference id. */ export type HotKeyDefinition = { id: string, description: string, }; /** * The combination of key presses that will activate a hotkey if pressed. */ export type KeyCombination = { ctrl: boolean, alt: boolean, shift: boolean, meta: boolean, keyCode: number, }; /** * The collection of a hotkey's key combinations for each platforms. */ export type KeyBindings = { macKeys: Array, // The key combinations for both Windows and Linux. winLinuxKeys: Array, }; /** * The collection of defined hotkeys. * The registry maps a hotkey by its reference id to its key bindings. */ export type HotKeyRegistry = { [refId: string]: KeyBindings, }; function defineHotKey(id: string, description: string): HotKeyDefinition { return { id: id, description: description, }; } function keyComb( ctrl: boolean, alt: boolean, shift: boolean, meta: boolean, keyCode: number, ): KeyCombination { return { ctrl: ctrl, alt: alt, shift: shift, meta: meta, keyCode: keyCode, }; } function keyBinds( mac: KeyCombination | Array, winLinux: KeyCombination | Array, ): KeyBindings { if (!Array.isArray(mac)) { mac = [mac]; } if (!Array.isArray(winLinux)) { winLinux = [winLinux]; } return { macKeys: mac, winLinuxKeys: winLinux, }; } /** * The collection of available hotkeys' and their definitions. */ // Not using dot, because NeDB prohibits field names to contain dots. export const hotKeyRefs = { WORKSPACE_SHOW_SETTINGS: defineHotKey('workspace_showSettings', 'Show Workspace Settings'), REQUEST_SHOW_SETTINGS: defineHotKey('request_showSettings', 'Show Request Settings'), PREFERENCES_SHOW_KEYBOARD_SHORTCUTS: defineHotKey( 'preferences_showKeyboardShortcuts', 'Show Keyboard Shortcuts', ), PREFERENCES_SHOW_GENERAL: defineHotKey('preferences_showGeneral', 'Show App Preferences'), TOGGLE_MAIN_MENU: defineHotKey('toggleMainMenu', 'Toggle Main Menu'), SIDEBAR_TOGGLE: defineHotKey('sidebar_toggle', 'Toggle Sidebar'), REQUEST_QUICK_SWITCH: defineHotKey('request_quickSwitch', 'Switch Requests'), PLUGIN_RELOAD: defineHotKey('plugin_reload', 'Reload Plugins'), SHOW_AUTOCOMPLETE: defineHotKey('showAutocomplete', 'Show Autocomplete'), REQUEST_SEND: defineHotKey('request_send', 'Send Request'), REQUEST_SHOW_OPTIONS: defineHotKey('request_showOptions', 'Send Request (Options)'), ENVIRONMENT_SHOW_EDITOR: defineHotKey('environment_showEditor', 'Show Environment Editor'), ENVIRONMENT_SHOW_SWITCH_MENU: defineHotKey('environment_showSwitchMenu', 'Switch Environments'), REQUEST_TOGGLE_HTTP_METHOD_MENU: defineHotKey( 'request_toggleHttpMethodMenu', 'Change HTTP Method', ), REQUEST_TOGGLE_HISTORY: defineHotKey('request_toggleHistory', 'Show Request History'), REQUEST_FOCUS_URL: defineHotKey('request_focusUrl', 'Focus URL'), REQUEST_SHOW_GENERATE_CODE_EDITOR: defineHotKey( 'request_showGenerateCodeEditor', 'Generate Code', ), SIDEBAR_FOCUS_FILTER: defineHotKey('sidebar_focusFilter', 'Filter Sidebar'), RESPONSE_FOCUS: defineHotKey('response_focus', 'Focus Response'), SHOW_COOKIES_EDITOR: defineHotKey('showCookiesEditor', 'Edit Cookies'), REQUEST_SHOW_CREATE: defineHotKey('request_showCreate', 'Create Request'), REQUEST_QUICK_CREATE: defineHotKey('request_quickCreate', 'Create Request (Quick)'), REQUEST_SHOW_DELETE: defineHotKey('request_showDelete', 'Delete Request'), REQUEST_SHOW_CREATE_FOLDER: defineHotKey('request_showCreateFolder', 'Create Folder'), REQUEST_SHOW_DUPLICATE: defineHotKey('request_showDuplicate', 'Duplicate Request'), REQUEST_TOGGLE_PIN: defineHotKey('request_togglePin', 'Pin/Unpin Request'), CLOSE_DROPDOWN: defineHotKey('closeDropdown', 'Close Dropdown'), CLOSE_MODAL: defineHotKey('closeModal', 'Close Modal'), ENVIRONMENT_UNCOVER_VARIABLES: defineHotKey('environment_uncoverVariables', 'Uncover Variables'), }; /** * The default key bindings values of all available hotkeys. */ const defaultRegistry: HotKeyRegistry = { [hotKeyRefs.WORKSPACE_SHOW_SETTINGS.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.comma.keyCode), keyComb(true, false, true, false, keyboardKeys.comma.keyCode), ), [hotKeyRefs.REQUEST_SHOW_SETTINGS.id]: keyBinds( keyComb(false, true, true, true, keyboardKeys.comma.keyCode), keyComb(true, true, true, false, keyboardKeys.comma.keyCode), ), [hotKeyRefs.PREFERENCES_SHOW_KEYBOARD_SHORTCUTS.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.forwardslash.keyCode), keyComb(true, false, true, false, keyboardKeys.forwardslash.keyCode), ), [hotKeyRefs.PREFERENCES_SHOW_GENERAL.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.comma.keyCode), keyComb(true, false, false, false, keyboardKeys.comma.keyCode), ), [hotKeyRefs.TOGGLE_MAIN_MENU.id]: keyBinds( keyComb(false, true, false, true, keyboardKeys.comma.keyCode), keyComb(true, true, false, false, keyboardKeys.comma.keyCode), ), [hotKeyRefs.SIDEBAR_TOGGLE.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.backslash.keyCode), keyComb(true, false, false, false, keyboardKeys.backslash.keyCode), ), [hotKeyRefs.REQUEST_QUICK_SWITCH.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.p.keyCode), keyComb(true, false, false, false, keyboardKeys.p.keyCode), ), [hotKeyRefs.PLUGIN_RELOAD.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.r.keyCode), keyComb(true, false, true, false, keyboardKeys.r.keyCode), ), [hotKeyRefs.SHOW_AUTOCOMPLETE.id]: keyBinds( keyComb(true, false, false, false, keyboardKeys.space.keyCode), keyComb(true, false, false, false, keyboardKeys.space.keyCode), ), [hotKeyRefs.REQUEST_SEND.id]: keyBinds( [ keyComb(false, false, false, true, keyboardKeys.enter.keyCode), keyComb(false, false, false, true, keyboardKeys.r.keyCode), keyComb(false, false, false, false, keyboardKeys.f5.keyCode), ], [ keyComb(true, false, false, false, keyboardKeys.enter.keyCode), keyComb(true, false, false, false, keyboardKeys.r.keyCode), keyComb(false, false, false, false, keyboardKeys.f5.keyCode), ], ), [hotKeyRefs.REQUEST_SHOW_OPTIONS.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.enter.keyCode), keyComb(true, false, true, false, keyboardKeys.enter.keyCode), ), [hotKeyRefs.ENVIRONMENT_SHOW_EDITOR.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.e.keyCode), keyComb(true, false, false, false, keyboardKeys.e.keyCode), ), [hotKeyRefs.ENVIRONMENT_SHOW_SWITCH_MENU.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.e.keyCode), keyComb(true, false, true, false, keyboardKeys.e.keyCode), ), [hotKeyRefs.REQUEST_TOGGLE_HTTP_METHOD_MENU.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.l.keyCode), keyComb(true, false, true, false, keyboardKeys.l.keyCode), ), [hotKeyRefs.REQUEST_TOGGLE_HISTORY.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.h.keyCode), keyComb(true, false, true, false, keyboardKeys.h.keyCode), ), [hotKeyRefs.REQUEST_FOCUS_URL.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.l.keyCode), keyComb(true, false, false, false, keyboardKeys.l.keyCode), ), [hotKeyRefs.REQUEST_SHOW_GENERATE_CODE_EDITOR.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.g.keyCode), keyComb(true, false, true, false, keyboardKeys.g.keyCode), ), [hotKeyRefs.SIDEBAR_FOCUS_FILTER.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.f.keyCode), keyComb(true, false, true, false, keyboardKeys.f.keyCode), ), [hotKeyRefs.RESPONSE_FOCUS.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.singlequote.keyCode), keyComb(true, false, false, false, keyboardKeys.singlequote.keyCode), ), [hotKeyRefs.SHOW_COOKIES_EDITOR.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.k.keyCode), keyComb(true, false, false, false, keyboardKeys.k.keyCode), ), [hotKeyRefs.REQUEST_SHOW_CREATE.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.n.keyCode), keyComb(true, false, false, false, keyboardKeys.n.keyCode), ), [hotKeyRefs.REQUEST_QUICK_CREATE.id]: keyBinds( keyComb(false, true, false, true, keyboardKeys.n.keyCode), keyComb(true, true, false, false, keyboardKeys.n.keyCode), ), [hotKeyRefs.REQUEST_SHOW_DELETE.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.delete.keyCode), keyComb(true, false, true, false, keyboardKeys.delete.keyCode), ), [hotKeyRefs.REQUEST_SHOW_CREATE_FOLDER.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.n.keyCode), keyComb(true, false, true, false, keyboardKeys.n.keyCode), ), [hotKeyRefs.REQUEST_SHOW_DUPLICATE.id]: keyBinds( keyComb(false, false, false, true, keyboardKeys.d.keyCode), keyComb(true, false, false, false, keyboardKeys.d.keyCode), ), [hotKeyRefs.REQUEST_TOGGLE_PIN.id]: keyBinds( keyComb(false, false, true, true, keyboardKeys.p.keyCode), keyComb(true, false, true, false, keyboardKeys.p.keyCode), ), [hotKeyRefs.CLOSE_DROPDOWN.id]: keyBinds( keyComb(false, false, false, false, keyboardKeys.esc.keyCode), keyComb(false, false, false, false, keyboardKeys.esc.keyCode), ), [hotKeyRefs.CLOSE_MODAL.id]: keyBinds( keyComb(false, false, false, false, keyboardKeys.esc.keyCode), keyComb(false, false, false, false, keyboardKeys.esc.keyCode), ), [hotKeyRefs.ENVIRONMENT_UNCOVER_VARIABLES.id]: keyBinds( keyComb(false, true, true, false, keyboardKeys.u.keyCode), keyComb(false, true, true, false, keyboardKeys.u.keyCode), ), }; function copyKeyCombs(sources: Array): Array { let targets: Array = []; sources.forEach(keyComb => { targets.push(Object.assign({}, keyComb)); }); return targets; } /** * Get a new copy of key bindings with default key combinations. * @param hotKeyRefId * @returns {KeyBindings} */ export function newDefaultKeyBindings(hotKeyRefId: string): KeyBindings { const keyBindings: KeyBindings = defaultRegistry[hotKeyRefId]; return { macKeys: copyKeyCombs(keyBindings.macKeys), winLinuxKeys: copyKeyCombs(keyBindings.winLinuxKeys), }; } /** * Get a new copy of hotkey registry with default values. * @returns {HotKeyRegistry} */ export function newDefaultRegistry(): HotKeyRegistry { let newDefaults: HotKeyRegistry = {}; for (const refId in defaultRegistry) { if (!defaultRegistry.hasOwnProperty(refId)) { continue; } newDefaults[refId] = newDefaultKeyBindings(refId); } return newDefaults; } /** * Get the key combinations based on the current platform. * @param bindings * @returns {Array} */ export function getPlatformKeyCombinations(bindings: KeyBindings): Array { if (isMac()) { return bindings.macKeys; } return bindings.winLinuxKeys; } /** * Determine whether two key combinations are the same by comparing each of their keys. * @param keyComb1 * @param keyComb2 * @returns {boolean} */ export function areSameKeyCombinations( keyComb1: KeyCombination, keyComb2: KeyCombination, ): boolean { return ( keyComb1.alt === keyComb2.alt && keyComb1.shift === keyComb2.shift && keyComb1.ctrl === keyComb2.ctrl && keyComb1.meta === keyComb2.meta && keyComb1.keyCode === keyComb2.keyCode ); } /** * Checks whether the given key bindings is the same as the default one, * identified with hot key reference id. * @param hotKeyRefId refers to the default key bindings to check. * @param keyBinds to check with the default ones. * @returns {boolean} */ export function areKeyBindingsSameAsDefault(hotKeyRefId: string, keyBinds: KeyBindings): boolean { const keyCombs = getPlatformKeyCombinations(keyBinds); const defaultKeyCombs = getPlatformKeyCombinations(defaultRegistry[hotKeyRefId]); if (keyCombs.length !== defaultKeyCombs.length) { return false; } for (const keyComb of keyCombs) { const found = defaultKeyCombs.find(defKeyComb => { if (areSameKeyCombinations(keyComb, defKeyComb)) { return true; } }); if (found == null) { return false; } } return true; } /** * Gets the displayed text of a key code. * @param keyCode * @returns {string} */ export function getChar(keyCode: number): string { let char; const key = Object.keys(keyboardKeys).find(k => keyboardKeys[k].keyCode === keyCode); if (!key) { console.error('Invalid key code', keyCode); } else { char = keyboardKeys[key].label; } return char || 'unknown'; } function joinHotKeys(mustUsePlus: boolean, keys: Array): string { if (!mustUsePlus && isMac()) { return keys.join(''); } return keys.join('+'); } /** * Check whether key code is a modifier key, i.e., alt, shift, ctrl, or meta. * @param keyCode * @returns {boolean} */ export function isModifierKeyCode(keyCode: number): boolean { return ( keyCode === keyboardKeys.alt.keyCode || keyCode === keyboardKeys.shift.keyCode || keyCode === keyboardKeys.ctrl.keyCode || // Meta keys. keyCode === keyboardKeys.leftwindowkey.keyCode || keyCode === keyboardKeys.rightwindowkey.keyCode || keyCode === keyboardKeys.selectkey.keyCode ); } /** * Construct the display string of a key combination based on platform. * For example, the display of alt in Windows or Linux would be "Alt"; * while in Mac would be "⌥". * @param keyComb * @param mustUsePlus if true will join the characters with "+" for all platforms; * otherwise if the platform is Mac, the characters will be next to each other. * @returns the constructed string, if keyCode is null and the characters are joint with "+", * it will have a dangling "+" as the last character, e.g., "Alt+Ctrl+". */ export function constructKeyCombinationDisplay( keyComb: KeyCombination, mustUsePlus: boolean, ): string { const { ctrl, alt, shift, meta, keyCode } = keyComb; const chars = []; alt && chars.push(ALT_SYM); shift && chars.push(SHIFT_SYM); ctrl && chars.push(CTRL_SYM); meta && chars.push(META_SYM); if (keyCode != null && !isModifierKeyCode(keyCode)) { chars.push(getChar(keyCode)); } let joint = joinHotKeys(mustUsePlus, chars); if (mustUsePlus && isModifierKeyCode(keyCode)) { joint += '+'; } return joint; }