keybindings.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
  2. import { HoppAction, invokeAction } from "./actions"
  3. import { isAppleDevice } from "./platformutils"
  4. import { isDOMElement, isTypableElement } from "./utils/dom"
  5. /**
  6. * This variable keeps track whether keybindings are being accepted
  7. * true -> Keybindings are checked
  8. * false -> Key presses are ignored (Keybindings are not checked)
  9. */
  10. let keybindingsEnabled = true
  11. /**
  12. * Alt is also regarded as macOS OPTION (⌥) key
  13. * Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
  14. */
  15. type ModifierKeys = "ctrl" | "alt" | "ctrl-shift" | "alt-shift"
  16. /* eslint-disable prettier/prettier */
  17. // prettier-ignore
  18. type Key =
  19. | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
  20. | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
  21. | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3"
  22. | "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left"
  23. | "right" | "/" | "?"
  24. /* eslint-enable */
  25. type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}`
  26. // Singular keybindings (these will be disabled when an input-ish area has been focused)
  27. type SingleCharacterShortcutKey = `${Key}`
  28. type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
  29. export const bindings: {
  30. // eslint-disable-next-line no-unused-vars
  31. [_ in ShortcutKey]?: HoppAction
  32. } = {
  33. "ctrl-g": "request.send-cancel",
  34. "ctrl-i": "request.reset",
  35. "ctrl-u": "request.copy-link",
  36. "ctrl-s": "request.save",
  37. "ctrl-shift-s": "request.save-as",
  38. "alt-up": "request.method.next",
  39. "alt-down": "request.method.prev",
  40. "alt-g": "request.method.get",
  41. "alt-h": "request.method.head",
  42. "alt-p": "request.method.post",
  43. "alt-u": "request.method.put",
  44. "alt-x": "request.method.delete",
  45. "ctrl-k": "flyouts.keybinds.toggle",
  46. "/": "modals.search.toggle",
  47. "?": "modals.support.toggle",
  48. "ctrl-m": "modals.share.toggle",
  49. "alt-r": "navigation.jump.rest",
  50. "alt-q": "navigation.jump.graphql",
  51. "alt-w": "navigation.jump.realtime",
  52. "alt-d": "navigation.jump.documentation",
  53. "alt-s": "navigation.jump.settings",
  54. "ctrl-left": "navigation.jump.back",
  55. "ctrl-right": "navigation.jump.forward",
  56. }
  57. /**
  58. * A composable that hooks to the caller component's
  59. * lifecycle and hooks to the keyboard events to fire
  60. * the appropriate actions based on keybindings
  61. */
  62. export function hookKeybindingsListener() {
  63. onMounted(() => {
  64. document.addEventListener("keydown", handleKeyDown)
  65. })
  66. onBeforeUnmount(() => {
  67. document.removeEventListener("keydown", handleKeyDown)
  68. })
  69. }
  70. function handleKeyDown(ev: KeyboardEvent) {
  71. // Do not check keybinds if the mode is disabled
  72. if (!keybindingsEnabled) return
  73. const binding = generateKeybindingString(ev)
  74. if (!binding) return
  75. const boundAction = bindings[binding]
  76. if (!boundAction) return
  77. ev.preventDefault()
  78. invokeAction(boundAction)
  79. }
  80. function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
  81. // All our keybinds need to have one modifier pressed atleast
  82. const modifierKey = getActiveModifier(ev)
  83. const target = ev.target
  84. if (!modifierKey && !(isDOMElement(target) && isTypableElement(target))) {
  85. // Check if we are having singulars instead
  86. const key = getPressedKey(ev)
  87. if (!key) return null
  88. else return `${key}` as ShortcutKey
  89. }
  90. const key = getPressedKey(ev)
  91. if (!key) return null
  92. return `${modifierKey}-${key}` as ShortcutKey
  93. }
  94. function getPressedKey(ev: KeyboardEvent): Key | null {
  95. const val = ev.key.toLowerCase()
  96. // Check arrow keys
  97. if (val === "arrowup") return "up"
  98. else if (val === "arrowdown") return "down"
  99. else if (val === "arrowleft") return "left"
  100. else if (val === "arrowright") return "right"
  101. // Check letter keys
  102. if (val.length === 1 && val.toUpperCase() !== val.toLowerCase())
  103. return val as Key
  104. // Check if number keys
  105. if (val.length === 1 && !isNaN(val as any)) return val as Key
  106. // Check if question mark
  107. if (val === "?") return "?"
  108. // Check if question mark
  109. if (val === "/") return "/"
  110. // If no other cases match, this is not a valid key
  111. return null
  112. }
  113. function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null {
  114. const isShiftKey = ev.shiftKey
  115. // We only allow one modifier key to be pressed (for now)
  116. // Control key (+ Command) gets priority and if Alt is also pressed, it is ignored
  117. if (isAppleDevice() && ev.metaKey) return isShiftKey ? "ctrl-shift" : "ctrl"
  118. else if (!isAppleDevice() && ev.ctrlKey)
  119. return isShiftKey ? "ctrl-shift" : "ctrl"
  120. // Test for Alt key
  121. if (ev.altKey) return isShiftKey ? "alt-shift" : "alt"
  122. return null
  123. }
  124. /**
  125. * This composable allows for the UI component to be disabled if the component in question is mounted
  126. */
  127. export function useKeybindingDisabler() {
  128. // TODO: Move to a lock based system that keeps the bindings disabled until all locks are lifted
  129. const disableKeybindings = () => {
  130. keybindingsEnabled = false
  131. }
  132. const enableKeybindings = () => {
  133. keybindingsEnabled = true
  134. }
  135. return {
  136. disableKeybindings,
  137. enableKeybindings,
  138. }
  139. }