2022-12-24 03:44:21 +01:00
|
|
|
import { Utils } from "../../Utils"
|
|
|
|
|
import Combine from "./Combine"
|
|
|
|
|
import BaseUIElement from "../BaseUIElement"
|
|
|
|
|
import Title from "./Title"
|
|
|
|
|
import Table from "./Table"
|
2024-07-12 03:17:15 +02:00
|
|
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
2022-12-24 03:44:21 +01:00
|
|
|
import { VariableUiElement } from "./VariableUIElement"
|
2022-12-28 00:37:48 +01:00
|
|
|
import { Translation } from "../i18n/Translation"
|
|
|
|
|
import { FixedUiElement } from "./FixedUiElement"
|
2023-01-11 01:37:44 +01:00
|
|
|
import Translations from "../i18n/Translations"
|
2024-07-12 03:17:15 +02:00
|
|
|
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
|
|
|
|
import Locale from "../i18n/Locale"
|
2022-12-24 03:44:21 +01:00
|
|
|
|
|
|
|
|
export default class Hotkeys {
|
2023-12-22 18:50:22 +01:00
|
|
|
public static readonly _docs: UIEventSource<
|
2022-12-24 03:44:21 +01:00
|
|
|
{
|
|
|
|
|
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
2022-12-28 00:37:48 +01:00
|
|
|
documentation: string | Translation
|
2023-12-22 18:50:22 +01:00
|
|
|
alsoTriggeredBy: Translation[]
|
2022-12-24 03:44:21 +01:00
|
|
|
}[]
|
2023-12-22 18:50:22 +01:00
|
|
|
> = new UIEventSource([])
|
2022-12-24 16:08:08 +01:00
|
|
|
|
2023-12-15 01:46:01 +01:00
|
|
|
/**
|
|
|
|
|
* Register a hotkey
|
|
|
|
|
* @param key
|
|
|
|
|
* @param documentation
|
|
|
|
|
* @param action the function to run. It might return 'false', indicating that it didn't do anything and gives control back to the default flow
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
2022-12-24 03:44:21 +01:00
|
|
|
public static RegisterHotkey(
|
|
|
|
|
key: (
|
|
|
|
|
| {
|
2024-07-21 10:52:51 +02:00
|
|
|
ctrl: string
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
| {
|
2024-07-21 10:52:51 +02:00
|
|
|
shift: string
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
| {
|
2024-07-21 10:52:51 +02:00
|
|
|
alt: string
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
| {
|
2024-07-21 10:52:51 +02:00
|
|
|
nomod: string
|
|
|
|
|
}
|
|
|
|
|
) & {
|
2022-12-24 03:44:21 +01:00
|
|
|
onUp?: boolean
|
|
|
|
|
},
|
2022-12-28 00:37:48 +01:00
|
|
|
documentation: string | Translation,
|
2023-12-21 17:36:43 +01:00
|
|
|
action: () => void | false,
|
2023-12-22 18:50:22 +01:00
|
|
|
alsoTriggeredBy?: Translation[]
|
2022-12-24 03:44:21 +01:00
|
|
|
) {
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-22 18:50:22 +01:00
|
|
|
this._docs.data.push({ key, documentation, alsoTriggeredBy })
|
2022-12-24 03:44:21 +01:00
|
|
|
this._docs.ping()
|
|
|
|
|
if (Utils.runningFromConsole) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (key["ctrl"] !== undefined) {
|
2024-07-21 10:52:51 +02:00
|
|
|
document.addEventListener("keydown", function (event) {
|
2022-12-24 03:44:21 +01:00
|
|
|
if (event.ctrlKey && event.key === keycode) {
|
2023-12-15 01:46:01 +01:00
|
|
|
if (action() !== false) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else if (key["shift"] !== undefined) {
|
2024-07-21 10:52:51 +02:00
|
|
|
document.addEventListener(type, function (event) {
|
2023-10-09 00:52:06 +02:00
|
|
|
if (Hotkeys.textElementSelected(event)) {
|
2022-12-24 16:08:08 +01:00
|
|
|
// A text element is selected, we don't do anything special
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
if (event.shiftKey && event.key === keycode) {
|
2023-12-15 01:46:01 +01:00
|
|
|
if (action() !== false) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else if (key["alt"] !== undefined) {
|
2024-07-21 10:52:51 +02:00
|
|
|
document.addEventListener(type, function (event) {
|
2022-12-24 03:44:21 +01:00
|
|
|
if (event.altKey && event.key === keycode) {
|
2023-12-15 01:46:01 +01:00
|
|
|
if (action() !== false) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else if (key["nomod"] !== undefined) {
|
2024-07-21 10:52:51 +02:00
|
|
|
document.addEventListener(type, function (event) {
|
2023-11-03 02:24:33 +01:00
|
|
|
if (Hotkeys.textElementSelected(event) && keycode !== "Escape") {
|
2022-12-24 16:08:08 +01:00
|
|
|
// A text element is selected, we don't do anything special
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
if (event.key === keycode) {
|
2023-12-15 01:46:01 +01:00
|
|
|
const result = action()
|
|
|
|
|
if (result !== false) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-21 10:52:51 +02:00
|
|
|
static prepareDocumentation(
|
|
|
|
|
docs: {
|
|
|
|
|
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
|
|
|
|
documentation: string | Translation
|
|
|
|
|
alsoTriggeredBy: Translation[]
|
|
|
|
|
}[]
|
|
|
|
|
) {
|
2024-07-12 03:17:15 +02:00
|
|
|
let byKey: [string, string | Translation, Translation[] | undefined][] = docs
|
|
|
|
|
.map(({ key, documentation, alsoTriggeredBy }) => {
|
2024-07-21 10:52:51 +02:00
|
|
|
const modifiers = Object.keys(key).filter((k) => k !== "nomod" && k !== "onUp")
|
|
|
|
|
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
|
2024-07-12 03:17:15 +02:00
|
|
|
if (keycode.length == 1) {
|
|
|
|
|
keycode = keycode.toUpperCase()
|
|
|
|
|
}
|
|
|
|
|
if (keycode === " ") {
|
|
|
|
|
keycode = "Spacebar"
|
2023-12-15 01:46:01 +01:00
|
|
|
}
|
2024-07-12 03:17:15 +02:00
|
|
|
modifiers.push(keycode)
|
|
|
|
|
return <[string, string | Translation, Translation[] | undefined]>[
|
|
|
|
|
modifiers.join("+"),
|
|
|
|
|
documentation,
|
2024-07-21 10:52:51 +02:00
|
|
|
alsoTriggeredBy,
|
2024-07-12 03:17:15 +02:00
|
|
|
]
|
2022-12-28 00:37:48 +01:00
|
|
|
})
|
2024-07-12 03:17:15 +02:00
|
|
|
.sort()
|
|
|
|
|
byKey = Utils.NoNull(byKey)
|
|
|
|
|
for (let i = byKey.length - 1; i > 0; i--) {
|
|
|
|
|
if (byKey[i - 1][0] === byKey[i][0]) {
|
|
|
|
|
byKey.splice(i, 1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return byKey
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-21 10:52:51 +02:00
|
|
|
static generateDocumentationFor(
|
|
|
|
|
docs: {
|
|
|
|
|
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
|
|
|
|
documentation: string | Translation
|
|
|
|
|
alsoTriggeredBy: Translation[]
|
|
|
|
|
}[],
|
|
|
|
|
language: string
|
|
|
|
|
): string {
|
2024-07-12 03:17:15 +02:00
|
|
|
const tr = Translations.t.hotkeyDocumentation
|
2024-07-21 10:52:51 +02:00
|
|
|
function t(t: Translation | string) {
|
|
|
|
|
if (typeof t === "string") {
|
2024-07-12 03:17:15 +02:00
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
return t.textFor(language)
|
|
|
|
|
}
|
2024-07-21 10:52:51 +02:00
|
|
|
const contents: string[][] = this.prepareDocumentation(docs).map(
|
|
|
|
|
([key, doc, alsoTriggeredBy]) => {
|
|
|
|
|
let keyEl: string = [key, ...(alsoTriggeredBy ?? [])]
|
|
|
|
|
.map((k) => "`" + t(k) + "`")
|
|
|
|
|
.join(" ")
|
|
|
|
|
return [keyEl, t(doc)]
|
|
|
|
|
}
|
|
|
|
|
)
|
2024-07-12 03:17:15 +02:00
|
|
|
return [
|
2024-07-21 10:52:51 +02:00
|
|
|
"# " + t(tr.title),
|
2024-07-12 03:17:15 +02:00
|
|
|
t(tr.intro),
|
2024-07-21 10:52:51 +02:00
|
|
|
MarkdownUtils.table([t(tr.key), t(tr.action)], contents),
|
|
|
|
|
].join("\n")
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
|
|
|
|
|
2024-07-21 10:52:51 +02:00
|
|
|
public static generateDocumentation(language?: string) {
|
|
|
|
|
return Hotkeys.generateDocumentationFor(
|
|
|
|
|
Hotkeys._docs.data,
|
|
|
|
|
language ?? Locale.language.data
|
|
|
|
|
)
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|
2023-12-15 01:46:01 +01:00
|
|
|
|
|
|
|
|
private static textElementSelected(event: KeyboardEvent): boolean {
|
|
|
|
|
if (event.ctrlKey || event.altKey) {
|
|
|
|
|
// This is an event with a modifier-key, lets not ignore it
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (event.key === "Escape") {
|
|
|
|
|
return false // Another not-printable character that should not be ignored
|
|
|
|
|
}
|
|
|
|
|
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
|
|
|
|
|
}
|
2022-12-24 03:44:21 +01:00
|
|
|
}
|