aboutsummaryrefslogtreecommitdiff
path: root/ext/mixed/js/hotkey-handler.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/mixed/js/hotkey-handler.js')
-rw-r--r--ext/mixed/js/hotkey-handler.js146
1 files changed, 146 insertions, 0 deletions
diff --git a/ext/mixed/js/hotkey-handler.js b/ext/mixed/js/hotkey-handler.js
new file mode 100644
index 00000000..01c33f5d
--- /dev/null
+++ b/ext/mixed/js/hotkey-handler.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2021 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* global
+ * DocumentUtil
+ */
+
+/**
+ * Class which handles hotkey events and actions.
+ */
+class HotkeyHandler extends EventDispatcher {
+ /**
+ * Creates a new instance of the class.
+ * @param scope The scope required for hotkey definitions.
+ */
+ constructor(scope) {
+ super();
+ this._scope = scope;
+ this._hotkeys = new Map();
+ this._actions = new Map();
+ this._eventListeners = new EventListenerCollection();
+ }
+
+ /**
+ * Gets the scope required for the hotkey definitions.
+ */
+ get scope() {
+ return this._scope;
+ }
+
+ /**
+ * Sets the scope required for the hotkey definitions.
+ */
+ set scope(value) {
+ this._scope = value;
+ }
+
+ /**
+ * Begins listening to key press events in order to detect hotkeys.
+ */
+ prepare() {
+ this._eventListeners.addEventListener(document, 'keydown', this._onKeyDown.bind(this), false);
+ }
+
+ /**
+ * Stops listening to key press events.
+ */
+ cleanup() {
+ this._eventListeners.removeAllEventListeners();
+ }
+
+ /**
+ * Registers a set of actions that this hotkey handler supports.
+ * @param actions An array of `[name, handler]` entries, where `name` is a string and `handler` is a function.
+ */
+ registerActions(actions) {
+ for (const [name, handler] of actions) {
+ this._actions.set(name, handler);
+ }
+ }
+
+ /**
+ * Registers a set of hotkeys
+ * @param hotkeys An array of hotkey definitions of the format `{action, key, modifiers, scopes, enabled}`.
+ * * `action` - a string indicating which action to perform.
+ * * `key` - a keyboard key code indicating which key needs to be pressed.
+ * * `modifiers` - an array of keyboard modifiers which also need to be pressed. Supports: `'alt', 'ctrl', 'shift', 'meta'`.
+ * * `scopes` - an array of scopes for which the hotkey is valid. If this array does not contain `this.scope`, the hotkey will not be registered.
+ * * `enabled` - a boolean indicating whether the hotkey is currently enabled.
+ */
+ registerHotkeys(hotkeys) {
+ for (const {action, key, modifiers, scopes, enabled} of hotkeys) {
+ if (
+ enabled &&
+ action !== '' &&
+ scopes.includes(this._scope)
+ ) {
+ this._registerHotkey(key, modifiers, action);
+ }
+ }
+ }
+
+ /**
+ * Removes all registered hotkeys.
+ */
+ clearHotkeys() {
+ this._hotkeys.clear();
+ }
+
+ // Private
+
+ _onKeyDown(e) {
+ const key = e.code;
+ const handlers = this._hotkeys.get(key);
+ if (typeof handlers !== 'undefined') {
+ const eventModifiers = DocumentUtil.getActiveModifiers(e);
+ for (const {modifiers, action} of handlers) {
+ if (!this._areSame(modifiers, eventModifiers)) { continue; }
+
+ const actionHandler = this._actions.get(action);
+ if (typeof actionHandler === 'undefined') { continue; }
+
+ const result = actionHandler(e);
+ if (result !== false) {
+ e.preventDefault();
+ return true;
+ }
+ }
+ }
+ this.trigger('keydownNonHotkey', e);
+ return false;
+ }
+
+ _registerHotkey(key, modifiers, action) {
+ let handlers = this._hotkeys.get(key);
+ if (typeof handlers === 'undefined') {
+ handlers = [];
+ this._hotkeys.set(key, handlers);
+ }
+ handlers.push({modifiers: new Set(modifiers), action});
+ }
+
+ _areSame(set, array) {
+ if (set.size !== array.length) { return false; }
+ for (const value of array) {
+ if (!set.has(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}