This commit is contained in:
sguenter 2023-12-27 19:01:12 +01:00
parent da535c5de2
commit c86abe5ff7

View File

@ -1,24 +1,21 @@
import { HotkeyDefinitionType, specialKeys } from './HotkeyDefinitionType'; import { HotkeyDefinitionType, specialKeys } from './HotkeyDefinitionType';
import { HotkeyListener } from './HotkeyListener'; import { HotkeyListener } from './HotkeyListener';
import { JsonHelper, ObjectHelper } from '@ainias42/js-helper'; import { JsonHelper, ObjectHelper, Override } from '@ainias42/js-helper';
import { HotkeyEntry } from './HotkeyEntry'; import { HotkeyEntry } from './HotkeyEntry';
import { SubKeyType } from './SubKeyType'; import { SubKeyType } from './SubKeyType';
import { HotkeyPressedMap } from './HotkeyPressedMap'; import { HotkeyPressedMap } from './HotkeyPressedMap';
import { HotkeyConfigWithOptionals } from './HotkeyConfigWithOptionals'; import { HotkeyConfigWithOptionals } from './HotkeyConfigWithOptionals';
export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<string>>> { export class HotKeyManager<HotkeyConfig extends Record<string, HotkeyEntry<string>>> {
private keyPressedMap = new Map<string, boolean>(); private keyPressedMap = new Map<string, boolean>();
private hotkeys: HotkeyConfig; private hotKeys: HotkeyConfig;
private hotkeysPressedMap: HotkeyPressedMap<HotkeyConfig>; private hotKeysPressedMap: HotkeyPressedMap<HotkeyConfig>;
private enabled = true; private enabled = true;
private ignoreFormElements = true; private readonly ignoreFormElements: boolean = true;
constructor(hotkeys: HotkeyConfigWithOptionals<HotkeyConfig>, ignoreFormElements?: boolean) { constructor(hotkeys: HotkeyConfigWithOptionals<HotkeyConfig>, ignoreFormElements?: boolean) {
this.hotkeys = ObjectHelper.entries(hotkeys).reduce((acc, [key, value]) => { this.hotKeys = {} as HotkeyConfig;
acc[key] = { subKeys: {}, callbacks: [], ...value } as unknown as HotkeyConfig[typeof key]; this.addHotKeys(hotkeys);
return acc;
}, {} as HotkeyConfig);
this.hotkeysPressedMap = this.generateHotkeyPressedMap();
if (ignoreFormElements !== undefined) { if (ignoreFormElements !== undefined) {
this.ignoreFormElements = ignoreFormElements; this.ignoreFormElements = ignoreFormElements;
@ -29,7 +26,8 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
return ( return (
element instanceof HTMLInputElement || element instanceof HTMLInputElement ||
element instanceof HTMLSelectElement || element instanceof HTMLSelectElement ||
element instanceof HTMLTextAreaElement element instanceof HTMLTextAreaElement ||
(element instanceof HTMLElement && element.contentEditable === 'true')
); );
} }
@ -38,7 +36,7 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
} }
private static checkAffections(keyDefinitions: HotkeyDefinitionType[], key: string): boolean { private static checkAffections(keyDefinitions: HotkeyDefinitionType[], key: string): boolean {
return keyDefinitions.some((keyDefinition) => HotkeyManager.checkAffection(keyDefinition, key)); return keyDefinitions.some((keyDefinition) => HotKeyManager.checkAffection(keyDefinition, key));
} }
private static checkAffection(keyDefinition: HotkeyDefinitionType, key: string): boolean { private static checkAffection(keyDefinition: HotkeyDefinitionType, key: string): boolean {
@ -48,11 +46,28 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
); );
} }
addHotKeys<NewHotkeyConfig extends Record<string, HotkeyEntry<string>>>(
hotkeys: HotkeyConfigWithOptionals<NewHotkeyConfig>,
) {
type NewConfig = Override<HotkeyConfig, NewHotkeyConfig>;
this.hotKeys = {
...this.hotKeys,
...ObjectHelper.entries(hotkeys).reduce((acc, [key, value]) => {
acc[key] = { subKeys: {}, callbacks: [], ...value } as unknown as NewConfig[typeof key];
return acc;
}, {} as NewConfig),
};
this.hotKeysPressedMap = this.generateHotkeyPressedMap();
return this as unknown as HotKeyManager<NewConfig>;
}
addListener<HotkeyName extends keyof HotkeyConfig>( addListener<HotkeyName extends keyof HotkeyConfig>(
hotkeyName: HotkeyName, hotkeyName: HotkeyName,
callback: HotkeyListener<SubKeyType<HotkeyConfig, HotkeyName>>, callback: HotkeyListener<SubKeyType<HotkeyConfig, HotkeyName>>,
) { ) {
const { callbacks } = this.hotkeys[hotkeyName]; const { callbacks } = this.hotKeys[hotkeyName];
callbacks.push(callback); callbacks.push(callback);
return () => { return () => {
const index = callbacks.indexOf(callback); const index = callbacks.indexOf(callback);
@ -71,15 +86,15 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
} }
addHotKeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) { addHotKeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) {
this.hotkeys[hotkey].keys.push(definition); this.hotKeys[hotkey].keys.push(definition);
} }
removeHotkeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) { removeHotKeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) {
this.hotkeys[hotkey].keys = this.hotkeys[hotkey].keys.filter((key) => !HotkeyManager.isEqual(key, definition)); this.hotKeys[hotkey].keys = this.hotKeys[hotkey].keys.filter((key) => !HotKeyManager.isEqual(key, definition));
} }
setHotkeyDefinitions(hotkey: keyof HotkeyConfig, definitions: HotkeyDefinitionType[]) { setHotKeyDefinitions(hotkey: keyof HotkeyConfig, definitions: HotkeyDefinitionType[]) {
this.hotkeys[hotkey].keys = definitions; this.hotKeys[hotkey].keys = definitions;
} }
addSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>( addSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>(
@ -87,7 +102,7 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
subKey: SubKeyType<HotkeyConfig, HotkeyName>, subKey: SubKeyType<HotkeyConfig, HotkeyName>,
definition: HotkeyDefinitionType, definition: HotkeyDefinitionType,
) { ) {
this.hotkeys[hotkey].subKeys[subKey as string].push(definition); this.hotKeys[hotkey].subKeys[subKey as string].push(definition);
} }
removeSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>( removeSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>(
@ -95,8 +110,8 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
subKey: SubKeyType<HotkeyConfig, HotkeyName>, subKey: SubKeyType<HotkeyConfig, HotkeyName>,
definition: HotkeyDefinitionType, definition: HotkeyDefinitionType,
) { ) {
const { subKeys } = this.hotkeys[hotkey]; const { subKeys } = this.hotKeys[hotkey];
subKeys[subKey as string] = subKeys[subKey as string].filter((key) => !HotkeyManager.isEqual(key, definition)); subKeys[subKey as string] = subKeys[subKey as string].filter((key) => !HotKeyManager.isEqual(key, definition));
} }
setSubKeyDefinitions<HotkeyName extends keyof HotkeyConfig>( setSubKeyDefinitions<HotkeyName extends keyof HotkeyConfig>(
@ -104,16 +119,16 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
subKey: SubKeyType<HotkeyConfig, HotkeyName>, subKey: SubKeyType<HotkeyConfig, HotkeyName>,
definitions: HotkeyDefinitionType[], definitions: HotkeyDefinitionType[],
) { ) {
this.hotkeys[hotkey].subKeys[subKey as string] = definitions; this.hotKeys[hotkey].subKeys[subKey as string] = definitions;
} }
getConfig() { getConfig() {
return this.hotkeys; return this.hotKeys;
} }
addKeyListeners() { addKeyListeners() {
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
if (this.ignoreFormElements && HotkeyManager.isFormElement(e.target)) { if (this.ignoreFormElements && HotKeyManager.isFormElement(e.target)) {
return; return;
} }
@ -134,16 +149,16 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
}); });
} }
getHotkeysPressedMap() { getHotKeysPressedMap() {
return this.hotkeysPressedMap; return this.hotKeysPressedMap;
} }
isHotkeyPressed(hotkey: keyof HotkeyConfig) { isHotKeyPressed(hotkey: keyof HotkeyConfig) {
return this.hotkeysPressedMap[hotkey].isPressed; return this.hotKeysPressedMap[hotkey].isPressed;
} }
isSubKeyPressed<Hotkey extends keyof HotkeyConfig>(hotkey: Hotkey, subkey: SubKeyType<HotkeyConfig, Hotkey>) { isSubKeyPressed<Hotkey extends keyof HotkeyConfig>(hotkey: Hotkey, subkey: SubKeyType<HotkeyConfig, Hotkey>) {
return this.hotkeysPressedMap[hotkey].isPressed && this.hotkeysPressedMap[hotkey].subKeys[subkey]; return this.hotKeysPressedMap[hotkey].isPressed && this.hotKeysPressedMap[hotkey].subKeys[subkey];
} }
private checkHotkeyDefinitions(keyDefinitions: HotkeyDefinitionType[]): boolean { private checkHotkeyDefinitions(keyDefinitions: HotkeyDefinitionType[]): boolean {
@ -165,7 +180,7 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
} }
private generateHotkeyPressedMap() { private generateHotkeyPressedMap() {
return ObjectHelper.entries(this.hotkeys).reduce((acc, [key, value]) => { return ObjectHelper.entries(this.hotKeys).reduce((acc, [key, value]) => {
const isPressed = this.checkHotkeyDefinitions(value.keys); const isPressed = this.checkHotkeyDefinitions(value.keys);
acc[key] = { acc[key] = {
isPressed, isPressed,
@ -183,8 +198,8 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
} }
private triggerListenerFor(key: keyof HotkeyConfig, event: KeyboardEvent) { private triggerListenerFor(key: keyof HotkeyConfig, event: KeyboardEvent) {
const pressedEntry = this.hotkeysPressedMap[key]; const pressedEntry = this.hotKeysPressedMap[key];
this.hotkeys[key].callbacks.forEach((callback) => this.hotKeys[key].callbacks.forEach((callback) =>
callback({ event, subKeys: pressedEntry.subKeys, isPressed: pressedEntry.isPressed }), callback({ event, subKeys: pressedEntry.subKeys, isPressed: pressedEntry.isPressed }),
); );
} }
@ -192,9 +207,9 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
private checkHotkeys(event: KeyboardEvent) { private checkHotkeys(event: KeyboardEvent) {
const hotkeysToTrigger: (keyof HotkeyConfig)[] = []; const hotkeysToTrigger: (keyof HotkeyConfig)[] = [];
const oldMap = this.hotkeysPressedMap; const oldMap = this.hotKeysPressedMap;
this.hotkeysPressedMap = this.generateHotkeyPressedMap(); this.hotKeysPressedMap = this.generateHotkeyPressedMap();
const hotkeysToCheckAffection = ObjectHelper.entries(this.hotkeysPressedMap).filter(([key, value]) => { const hotkeysToCheckAffection = ObjectHelper.entries(this.hotKeysPressedMap).filter(([key, value]) => {
const oldValue = oldMap[key]; const oldValue = oldMap[key];
if ( if (
oldValue.isPressed !== value.isPressed || oldValue.isPressed !== value.isPressed ||
@ -209,12 +224,12 @@ export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<strin
// Check for repeated pressed. Nothing changed, but key is pressed again // Check for repeated pressed. Nothing changed, but key is pressed again
const eventKey = event.key.toLowerCase(); const eventKey = event.key.toLowerCase();
hotkeysToCheckAffection.forEach(([key]) => { hotkeysToCheckAffection.forEach(([key]) => {
const keyDefinition = this.hotkeys[key]; const keyDefinition = this.hotKeys[key];
if (HotkeyManager.checkAffections(keyDefinition.keys, eventKey)) { if (HotKeyManager.checkAffections(keyDefinition.keys, eventKey)) {
hotkeysToTrigger.push(key); hotkeysToTrigger.push(key);
} else { } else {
ObjectHelper.values(keyDefinition.subKeys).forEach((subKeyDefinitions) => { ObjectHelper.values(keyDefinition.subKeys).forEach((subKeyDefinitions) => {
if (HotkeyManager.checkAffections(subKeyDefinitions, eventKey)) { if (HotKeyManager.checkAffections(subKeyDefinitions, eventKey)) {
hotkeysToTrigger.push(key); hotkeysToTrigger.push(key);
} }
}); });