diff --git a/src/002-config/mousetrapConfig.js b/src/002-config/mousetrapConfig.js index 586c861857bc62827afcf84cf3c7983c00bc19aa..c905ac5be13f53be60f625abca0ea9cab222984f 100644 --- a/src/002-config/mousetrapConfig.js +++ b/src/002-config/mousetrapConfig.js @@ -16,12 +16,24 @@ App.UI.Hotkeys = (function() { */ const actions = {}; + /** + * Contains the default combinations for every action + * @type {object.<string, Array<string>>} + */ + const defaultCombinations = {}; + /** * References a key combination to a set of actions * @type {object.<string, Array<string>>} */ const bindings = {}; + /** + * To ensure we only record one at at time + * @type {boolean} + */ + let recording = false; + /** * @param {string} name used as key * @param {action} action @@ -31,18 +43,20 @@ App.UI.Hotkeys = (function() { for (const binding of action.combinations) { addBinding(name, binding); } + defaultCombinations[name] = [...action.combinations]; } /** - * @param {string} action + * @param {string} actionKey * @param {string} combination */ - function addBinding(action, combination) { + function addBinding(actionKey, combination) { if (bindings[combination]) { - bindings[combination].push(action); + bindings[combination].push(actionKey); } else { - bindings[combination] = [action]; - Mousetrap.bind(combination, () => { + bindings[combination] = [actionKey]; + Mousetrap.bind(combination, e => { + e.preventDefault(); for (const binding of bindings[combination]) { const action = actions[binding]; // only activate callback if we are on the right passage @@ -55,17 +69,18 @@ App.UI.Hotkeys = (function() { } /** + * @param {string} actionKey * @param {string} combination */ - function removeBinding(combination) { + function removeBinding(actionKey, combination) { if (bindings[combination]) { - const index = bindings[combination].indexOf(category); + const index = bindings[combination].indexOf(actionKey); if (index > -1) { bindings[combination].splice(index, 1); - } - if (bindings[combination].length === 0) { - delete bindings[combination]; - Mousetrap.unbind(combination); + if (bindings[combination].length === 0) { + delete bindings[combination]; + Mousetrap.unbind(combination); + } } } } @@ -78,7 +93,7 @@ App.UI.Hotkeys = (function() { if (!actions[name]) { return ""; } - const c =actions[name].combinations + const c = actions[name].combinations; if (c.length === 0) { return ""; } @@ -89,11 +104,105 @@ App.UI.Hotkeys = (function() { return `[${c[0]},${c[1]}]`; } + /** + * @returns {HTMLDivElement} + */ + function settingsMenu() { + const div = document.createElement("div"); + div.className = "hotkey-settings"; + + for (const actionsKey in actions) { + settingsRow(div, actionsKey); + } + + return div; + } + + /** + * @param {HTMLDivElement} container + * @param {string} actionKey + */ + function settingsRow(container, actionKey) { + const action = actions[actionKey]; + // get correct name + let name = actionKey; + if (action.uiName) { + if (typeof action.uiName === "string") { + name = action.uiName; + } else { + name = action.uiName(); + } + } + App.UI.DOM.appendNewElement("div", container, name, "description"); + + settingsCell(container, actionKey, 0); + settingsCell(container, actionKey, 1); + + const button = App.UI.DOM.appendNewElement("button", container, "Reset"); + if (isDefault(actionKey)) { + button.className = "inactive"; + } else { + button.onclick = () => { + action.combinations = [...defaultCombinations[name]]; + App.UI.reload(); + }; + } + } + + /** + * Checks if the combinations assigned to an action are the default ones. + * @param {string} actionKey + * @returns {boolean} + */ + function isDefault(actionKey) { + if (defaultCombinations[actionKey].length !== actions[actionKey].combinations.length) { + return false; + } + if (defaultCombinations[actionKey].length === 0) { + return true; + } + if (defaultCombinations[actionKey][0] !== actions[actionKey].combinations[0]) { + return false; + } + if (defaultCombinations[actionKey].length === 1) { + return true; + } + return defaultCombinations[actionKey][1] === actions[actionKey].combinations[1]; + } + + /** + * @param {HTMLDivElement} container + * @param {string} actionKey + * @param {number} index + */ + function settingsCell(container, actionKey, index) { + const action = actions[actionKey]; + const button = App.UI.DOM.appendNewElement("button", container, + action.combinations[index] ? action.combinations[index] : "", "combination"); + button.onclick = () => { + if (recording) { return; } + recording = true; + + $(button).empty(); + Mousetrap.record(function(sequence) { + // sequence is an array like ['ctrl+k', 'c'] + const combination = sequence.join(" "); + if (action.combinations[index]) { + removeBinding(actionKey, action.combinations[index]); + } + action.combinations[index] = combination; + addBinding(actionKey, combination); + App.UI.reload(); + recording = false; + }); + }; + } + return { add: addDefault, hotkeys: hotkeysForAction, //init: loadFromStorage, - //settings: settingsMenu, + settings: settingsMenu, }; })(); diff --git a/src/gui/options/hotkeySettings.css b/src/gui/options/hotkeySettings.css new file mode 100644 index 0000000000000000000000000000000000000000..eb695486a2583e89c2bdb81cd3013c5b18f605e5 --- /dev/null +++ b/src/gui/options/hotkeySettings.css @@ -0,0 +1,35 @@ +div.hotkey-settings { + display: grid; + grid-template-columns: max-content max-content max-content max-content; +} + +div.hotkey-settings div.description { + margin-right: 10px; + /* center text vertically */ + display: flex; + justify-content: center; + flex-direction: column; +} + +div.hotkey-settings button { + margin: 5px; + border-width: 2px; + background-color: var(--button-color); + border-color: var(--button-border-color); +} + +div.hotkey-settings button.combination { + min-width: 150px; + border-width: 0; +} + +div.hotkey-settings button.inactive, div.hotkey-settings button.inactive:hover { + background-color: var(--button-selected-color); + cursor: default; + +} + +div.hotkey-settings button:hover { + background-color: var(--button-hover-color); + border-color: var(--button-border-color); +} diff --git a/src/gui/options/hotkeySettings.tw b/src/gui/options/hotkeySettings.tw new file mode 100644 index 0000000000000000000000000000000000000000..c415b29b410c9e0c74f2f76e78e3f62ed1452606 --- /dev/null +++ b/src/gui/options/hotkeySettings.tw @@ -0,0 +1,27 @@ +:: Hotkey Settings [nobr jump-to-safe jump-from-safe] + +<<set $nextButton = "Back", $nextLink = "Main">> + +<h1>Hotkey Settings</h1> + +<p> + <ul> + <li> + On keyboards layouts other than the <a href="https://en.wikipedia.org/wiki/File:KB_United_States.svg" + target="_blank">US-QWERTY layout</a> there may be keys or combinations of keys where the recorded key is + different from the key used to listen to key events. You will have to find these keys yourself through trial + and error. + </li> + <li> + Custom hotkeys are browser specific and are not part of your save. + </li> + <li> + While we try to not overwrite browser or OS level key combinations it is possible to do so with custom + hotkeys. This also means that during recording of custom hotkeys no browser or OS level key combinations are + available. There are however keys that cannot be overwritten, the <code>Win key</code> on Windows is an + example for this. + </li> + </ul> +</p> + +<<includeDOM App.UI.Hotkeys.settings()>> diff --git a/src/gui/quicklinks.js b/src/gui/quicklinks.js index 249817e1f507c3676d0e33afc22a6f8ab6175432..1b17599fdca0746d823e01a429549f49ba150efd 100644 --- a/src/gui/quicklinks.js +++ b/src/gui/quicklinks.js @@ -77,6 +77,7 @@ App.UI.quickMenu = (function() { "Summary Options": true, "Description Options": true, "Universal Rules": true, + "Hotkey Settings": true, } }); @@ -241,6 +242,8 @@ App.UI.quickMenu = (function() { const action = { callback: () => { if (hotkeysEnabled + // we are not already on the passage + && State.passage !== passage // the passage is accessible && !(hiddenPassages[passage] && hiddenPassages[passage]())) { Engine.play(passage);