191 lines
5.6 KiB
TypeScript
191 lines
5.6 KiB
TypeScript
import { onBeforeUnmount, onMounted } from "vue"
|
|
import { HoppActionWithOptionalArgs, invokeAction } from "./actions"
|
|
import { isAppleDevice } from "./platformutils"
|
|
import { isDOMElement, isTypableElement } from "./utils/dom"
|
|
|
|
/**
|
|
* This variable keeps track whether keybindings are being accepted
|
|
* true -> Keybindings are checked
|
|
* false -> Key presses are ignored (Keybindings are not checked)
|
|
*/
|
|
let keybindingsEnabled = true
|
|
|
|
/**
|
|
* Alt is also regarded as macOS OPTION (⌥) key
|
|
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
|
*/
|
|
type ModifierKeys =
|
|
| "ctrl"
|
|
| "alt"
|
|
| "shift"
|
|
| "ctrl-shift"
|
|
| "alt-shift"
|
|
| "ctrl-alt"
|
|
| "ctrl-alt-shift"
|
|
|
|
/* eslint-disable prettier/prettier */
|
|
// prettier-ignore
|
|
type Key =
|
|
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
|
|
| "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
|
|
| "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3"
|
|
| "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left"
|
|
| "right" | "/" | "?" | "." | "enter"
|
|
/* eslint-enable */
|
|
|
|
type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}`
|
|
// Singular keybindings (these will be disabled when an input-ish area has been focused)
|
|
type SingleCharacterShortcutKey = `${Key}`
|
|
|
|
type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
|
|
|
|
export const bindings: {
|
|
[_ in ShortcutKey]?: HoppActionWithOptionalArgs
|
|
} = {
|
|
"ctrl-enter": "request.send-cancel",
|
|
"ctrl-i": "request.reset",
|
|
"ctrl-u": "request.share-request",
|
|
"ctrl-s": "request.save",
|
|
"ctrl-shift-s": "request.save-as",
|
|
"alt-up": "request.method.next",
|
|
"alt-down": "request.method.prev",
|
|
"alt-g": "request.method.get",
|
|
"alt-h": "request.method.head",
|
|
"alt-p": "request.method.post",
|
|
"alt-u": "request.method.put",
|
|
"alt-x": "request.method.delete",
|
|
"ctrl-k": "modals.search.toggle",
|
|
"ctrl-/": "flyouts.keybinds.toggle",
|
|
"shift-/": "modals.support.toggle",
|
|
"ctrl-m": "modals.share.toggle",
|
|
"alt-r": "navigation.jump.rest",
|
|
"alt-q": "navigation.jump.graphql",
|
|
"alt-w": "navigation.jump.realtime",
|
|
"alt-d": "navigation.jump.documentation",
|
|
"alt-s": "navigation.jump.settings",
|
|
"alt-m": "navigation.jump.profile",
|
|
"ctrl-shift-p": "response.preview.toggle",
|
|
"ctrl-j": "response.file.download",
|
|
"ctrl-.": "response.copy",
|
|
"ctrl-shift-l": "editor.format",
|
|
}
|
|
|
|
/**
|
|
* A composable that hooks to the caller component's
|
|
* lifecycle and hooks to the keyboard events to fire
|
|
* the appropriate actions based on keybindings
|
|
*/
|
|
export function hookKeybindingsListener() {
|
|
onMounted(() => {
|
|
document.addEventListener("keydown", handleKeyDown)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener("keydown", handleKeyDown)
|
|
})
|
|
}
|
|
|
|
function handleKeyDown(ev: KeyboardEvent) {
|
|
// Do not check keybinds if the mode is disabled
|
|
if (!keybindingsEnabled) return
|
|
|
|
const binding = generateKeybindingString(ev)
|
|
if (!binding) return
|
|
|
|
const boundAction = bindings[binding]
|
|
if (!boundAction) return
|
|
|
|
ev.preventDefault()
|
|
invokeAction(boundAction, undefined, "keypress")
|
|
}
|
|
|
|
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
|
const target = ev.target
|
|
|
|
// We may or may not have a modifier key
|
|
const modifierKey = getActiveModifier(ev)
|
|
|
|
// We will always have a non-modifier key
|
|
const key = getPressedKey(ev)
|
|
if (!key) return null
|
|
|
|
// All key combos backed by modifiers are valid shortcuts (whether currently typing or not)
|
|
if (modifierKey) {
|
|
// If the modifier is shift and the target is an input, we ignore
|
|
if (
|
|
modifierKey === "shift" &&
|
|
isDOMElement(target) &&
|
|
isTypableElement(target)
|
|
) {
|
|
return null
|
|
}
|
|
|
|
return `${modifierKey}-${key}`
|
|
}
|
|
|
|
// no modifier key here then we do not do anything while on input
|
|
if (isDOMElement(target) && isTypableElement(target)) return null
|
|
|
|
// single key while not input
|
|
return `${key}`
|
|
}
|
|
|
|
function getPressedKey(ev: KeyboardEvent): Key | null {
|
|
// Sometimes the property code is not available on the KeyboardEvent object
|
|
const key = (ev.key ?? "").toLowerCase()
|
|
|
|
// Check arrow keys
|
|
if (key.startsWith("arrow")) {
|
|
return key.slice(5) as Key
|
|
}
|
|
|
|
// Check letter keys
|
|
const isLetter = key.length === 1 && key >= "a" && key <= "z"
|
|
if (isLetter) return key as Key
|
|
|
|
// Check if number keys
|
|
const isDigit = key.length === 1 && key >= "0" && key <= "9"
|
|
if (isDigit) return key as Key
|
|
|
|
// Check if slash, period or enter
|
|
if (key === "/" || key === "." || key === "enter") return key
|
|
|
|
// If no other cases match, this is not a valid key
|
|
return null
|
|
}
|
|
|
|
function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null {
|
|
const modifierKeys = {
|
|
ctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey,
|
|
alt: ev.altKey,
|
|
shift: ev.shiftKey,
|
|
}
|
|
|
|
// active modifier: ctrl | alt | ctrl-alt | ctrl-shift | ctrl-alt-shift | alt-shift
|
|
// modiferKeys object's keys are sorted to match the above order
|
|
const activeModifier = Object.keys(modifierKeys)
|
|
.filter((key) => modifierKeys[key as keyof typeof modifierKeys])
|
|
.join("-")
|
|
|
|
return activeModifier === "" ? null : (activeModifier as ModifierKeys)
|
|
}
|
|
|
|
/**
|
|
* This composable allows for the UI component to be disabled if the component in question is mounted
|
|
*/
|
|
export function useKeybindingDisabler() {
|
|
// TODO: Move to a lock based system that keeps the bindings disabled until all locks are lifted
|
|
const disableKeybindings = () => {
|
|
keybindingsEnabled = false
|
|
}
|
|
|
|
const enableKeybindings = () => {
|
|
keybindingsEnabled = true
|
|
}
|
|
|
|
return {
|
|
disableKeybindings,
|
|
enableKeybindings,
|
|
}
|
|
}
|