import { Utils } from "../../Utils" import Combine from "./Combine" import BaseUIElement from "../BaseUIElement" import Title from "./Title" import Table from "./Table" import { UIEventSource } from "../../Logic/UIEventSource" import { VariableUiElement } from "./VariableUIElement" import { Translation } from "../i18n/Translation" import { FixedUiElement } from "./FixedUiElement" export default class Hotkeys { private static readonly _docs: UIEventSource< { key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean } documentation: string | Translation }[] > = new UIEventSource< { key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean } documentation: string | Translation }[] >([]) private static textElementSelected(): boolean { return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase()) } public static RegisterHotkey( key: ( | { ctrl: string } | { shift: string } | { alt: string } | { nomod: string } ) & { onUp?: boolean }, documentation: string | Translation, action: () => void ) { const type = key["onUp"] ? "keyup" : "keypress" let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] if (keycode.length == 1) { keycode = keycode.toLowerCase() if (key["shift"] !== undefined) { keycode = keycode.toUpperCase() } } this._docs.data.push({ key, documentation }) this._docs.ping() if (Utils.runningFromConsole) { return } if (key["ctrl"] !== undefined) { document.addEventListener("keydown", function (event) { if (event.ctrlKey && event.key === keycode) { action() event.preventDefault() } }) } else if (key["shift"] !== undefined) { document.addEventListener(type, function (event) { if (Hotkeys.textElementSelected()) { // A text element is selected, we don't do anything special return } if (event.shiftKey && event.key === keycode) { action() event.preventDefault() } }) } else if (key["alt"] !== undefined) { document.addEventListener(type, function (event) { if (event.altKey && event.key === keycode) { action() event.preventDefault() } }) } else if (key["nomod"] !== undefined) { document.addEventListener(type, function (event) { if (Hotkeys.textElementSelected()) { // A text element is selected, we don't do anything special return } if (event.key === keycode) { action() event.preventDefault() } }) } } static generateDocumentation(): BaseUIElement { const byKey: [string, string | Translation][] = Hotkeys._docs.data .map(({ key, documentation }) => { const modifiers = Object.keys(key).filter((k) => k !== "nomod" && k !== "onUp") const keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] modifiers.push(keycode) return <[string, string | Translation]>[modifiers.join("+"), documentation] }) .sort() return new Combine([ new Title("Hotkeys", 1), "MapComplete supports the following keys:", new Table( ["Key combination", "Action"], byKey.map(([key, doc]) => { return [new FixedUiElement(key).SetClass("code"), doc] }) ), ]) } static generateDocumentationDynamic(): BaseUIElement { return new VariableUiElement(Hotkeys._docs.map((_) => Hotkeys.generateDocumentation())) } }