Files
yaak/apps/yaak-proxy/lib/hotkeys.ts
Gregory Schier 7314aedc71 Merge main into proxy branch (formatting and docs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:09:59 -07:00

63 lines
1.9 KiB
TypeScript

import type { ActionInvocation, ActionMetadata } from "@yaakapp-internal/proxy-lib";
import { rpc } from "./rpc";
type ActionBinding = {
invocation: ActionInvocation;
meta: ActionMetadata;
keys: { key: string; ctrl: boolean; shift: boolean; alt: boolean; meta: boolean };
};
/** Parse a hotkey string like "Ctrl+Shift+P" into its parts. */
function parseHotkey(hotkey: string): ActionBinding["keys"] {
const parts = hotkey.split("+").map((p) => p.trim().toLowerCase());
return {
ctrl: parts.includes("ctrl") || parts.includes("control"),
shift: parts.includes("shift"),
alt: parts.includes("alt"),
meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("command"),
key:
parts.filter(
(p) => !["ctrl", "control", "shift", "alt", "meta", "cmd", "command"].includes(p),
)[0] ?? "",
};
}
function matchesEvent(binding: ActionBinding["keys"], e: KeyboardEvent): boolean {
return (
e.ctrlKey === binding.ctrl &&
e.shiftKey === binding.shift &&
e.altKey === binding.alt &&
e.metaKey === binding.meta &&
e.key.toLowerCase() === binding.key
);
}
/** Fetch all actions from Rust and register a global keydown listener. */
export async function initHotkeys(): Promise<() => void> {
const { actions } = await rpc("list_actions", {});
const bindings: ActionBinding[] = actions
.filter(
(entry): entry is [ActionInvocation, ActionMetadata & { defaultHotkey: string }] =>
entry[1].defaultHotkey != null,
)
.map(([invocation, meta]) => ({
invocation,
meta,
keys: parseHotkey(meta.defaultHotkey),
}));
function onKeyDown(e: KeyboardEvent) {
for (const binding of bindings) {
if (matchesEvent(binding.keys, e)) {
e.preventDefault();
rpc("execute_action", binding.invocation);
return;
}
}
}
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}