From 0d00f7e1cf8a0fa1e2b1aa2732bceaae39f4e23c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Sep 2020 16:59:03 -0400 Subject: Scanning input generalization (#789) * Add inputs to options.scanning * Update CSS for mouse buttons * Update list counters * Set up HTML/CSS * Add input controller * Use new inputs * Include mouse buttons * Update how button inputs are detected * Add index/empty fields to the input details object * Update none check for scanning modifier * Remove old settings * Remove unused global --- ext/bg/js/options.js | 19 +++ ext/bg/js/settings/keyboard-mouse-input-field.js | 2 +- ext/bg/js/settings/main.js | 23 +-- ext/bg/js/settings/profile-conditions-ui.js | 2 +- ext/bg/js/settings/scan-inputs-controller.js | 178 +++++++++++++++++++++++ 5 files changed, 203 insertions(+), 21 deletions(-) create mode 100644 ext/bg/js/settings/scan-inputs-controller.js (limited to 'ext/bg/js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 398fb95c..ab32bb11 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -474,6 +474,7 @@ class OptionsUtil { // Added usePopupWindow. // Updated handlebars templates to include "clipboard-image" definition. // Added hideDelay. + // Added inputs to profileOptions.scanning. for (const {conditionGroups} of options.profiles) { for (const {conditions} of conditionGroups) { for (const condition of conditions) { @@ -489,6 +490,24 @@ class OptionsUtil { for (const {options: profileOptions} of options.profiles) { profileOptions.general.usePopupWindow = false; profileOptions.scanning.hideDelay = 0; + + const {modifier, middleMouse} = profileOptions.scanning; + const scanningInputs = []; + switch (modifier) { + case 'alt': + case 'ctrl': + case 'shift': + case 'meta': + scanningInputs.push(modifier); + break; + case 'none': + scanningInputs.push(''); + break; + } + if (middleMouse) { + scanningInputs.push('mouse2'); + } + profileOptions.scanning.inputs = scanningInputs; } await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v4.handlebars'); return options; diff --git a/ext/bg/js/settings/keyboard-mouse-input-field.js b/ext/bg/js/settings/keyboard-mouse-input-field.js index b1530c1b..4ba0d3ed 100644 --- a/ext/bg/js/settings/keyboard-mouse-input-field.js +++ b/ext/bg/js/settings/keyboard-mouse-input-field.js @@ -172,7 +172,7 @@ class KeyboardMouseInputField extends EventDispatcher { _onMouseButtonMouseDown(e) { e.preventDefault(); - this._addInputs([`mouse${e.button}`]); + this._addInputs(DocumentUtil.getActiveButtons(e)); } _onMouseButtonMouseUp(e) { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 7fa8e502..4932586b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -22,10 +22,10 @@ * ClipboardPopupsController * DictionaryController * DictionaryImportController - * DocumentUtil * GenericSettingController * PopupPreviewController * ProfileController + * ScanInputsController * SettingsBackup * SettingsController * StorageController @@ -40,23 +40,6 @@ function showExtensionInformation() { node.textContent = `${manifest.name} v${manifest.version}`; } -async function settingsPopulateModifierKeys() { - const scanModifierKeySelect = document.querySelector('#scan-modifier-key'); - scanModifierKeySelect.textContent = ''; - - const {platform: {os}} = await api.getEnvironmentInfo(); - const modifierKeys = [ - ['none', 'None'], - ...DocumentUtil.getModifierKeys(os) - ]; - for (const [value, name] of modifierKeys) { - const option = document.createElement('option'); - option.value = value; - option.textContent = name; - scanModifierKeySelect.appendChild(option); - } -} - async function setupEnvironmentInfo() { const {browser, platform} = await api.getEnvironmentInfo(); document.documentElement.dataset.browser = browser; @@ -71,7 +54,6 @@ async function setupEnvironmentInfo() { setupEnvironmentInfo(); showExtensionInformation(); - settingsPopulateModifierKeys(); const optionsFull = await api.optionsGetFull(); @@ -111,6 +93,9 @@ async function setupEnvironmentInfo() { const settingsBackup = new SettingsBackup(settingsController); settingsBackup.prepare(); + const scanInputsController = new ScanInputsController(settingsController); + scanInputsController.prepare(); + yomichan.ready(); } catch (e) { yomichan.logError(e); diff --git a/ext/bg/js/settings/profile-conditions-ui.js b/ext/bg/js/settings/profile-conditions-ui.js index d88f932b..419c35b3 100644 --- a/ext/bg/js/settings/profile-conditions-ui.js +++ b/ext/bg/js/settings/profile-conditions-ui.js @@ -441,7 +441,7 @@ class ProfileConditionUI { this._operatorOptionContainer = this._operatorInput.querySelector('optgroup'); this._valueInput = this._node.querySelector('.condition-input-inner'); this._removeButton = this._node.querySelector('.condition-remove'); - this._mouseButton = this._node.querySelector('.condition-mouse-button'); + this._mouseButton = this._node.querySelector('.mouse-button'); const operatorDetails = this._getOperatorDetails(type, operator); this._updateTypes(type); diff --git a/ext/bg/js/settings/scan-inputs-controller.js b/ext/bg/js/settings/scan-inputs-controller.js new file mode 100644 index 00000000..3b3945ff --- /dev/null +++ b/ext/bg/js/settings/scan-inputs-controller.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 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 . + */ + +/* global + * KeyboardMouseInputField + * api + */ + +class ScanInputsController { + constructor(settingsController) { + this._settingsController = settingsController; + this._os = null; + this._container = null; + this._addButton = null; + this._entries = []; + } + + async prepare() { + const {platform: {os}} = await api.getEnvironmentInfo(); + this._os = os; + + this._container = document.querySelector('#scan-input-list'); + this._addButton = document.querySelector('#scan-input-add'); + + this._addButton.addEventListener('click', this._onAddButtonClick.bind(this), false); + this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + + const options = await this._settingsController.getOptions(); + this._onOptionsChanged({options}); + } + + removeInput(index) { + if (index < 0 || index >= this._entries.length) { return false; } + const input = this._entries[index]; + input.cleanup(); + this._entries.splice(index, 1); + for (let i = index, ii = this._entries.length; i < ii; ++i) { + this._entries[i].index = i; + } + this._settingsController.modifyProfileSettings([{ + action: 'splice', + path: 'scanning.inputs', + start: index, + deleteCount: 1, + items: [] + }]); + } + + setProperty(index, property, value) { + const path = `scanning.inputs[${index}].${property}`; + this._settingsController.setProfileSetting(path, value); + } + + // Private + + _onOptionsChanged({options}) { + const {inputs} = options.scanning; + + for (let i = this._entries.length - 1; i >= 0; --i) { + this._entries[i].cleanup(); + } + this._entries.length = 0; + + for (let i = 0, ii = inputs.length; i < ii; ++i) { + const {include, exclude} = inputs[i]; + this._addOption(i, include, exclude); + } + } + + _onAddButtonClick(e) { + e.preventDefault(); + + const index = this._entries.length; + const include = ''; + const exclude = ''; + this._addOption(index, include, exclude); + this._settingsController.modifyProfileSettings([{ + action: 'splice', + path: 'scanning.inputs', + start: index, + deleteCount: 0, + items: [{include, exclude}] + }]); + } + + _addOption(index, include, exclude) { + const field = new ScanInputField(this, index, this._os); + this._entries.push(field); + field.prepare(this._container, include, exclude); + } +} + +class ScanInputField { + constructor(parent, index, os) { + this._parent = parent; + this._index = index; + this._os = os; + this._node = null; + this._includeInputField = null; + this._excludeInputField = null; + this._eventListeners = new EventListenerCollection(); + } + + get index() { + return this._index; + } + + set index(value) { + this._index = value; + } + + prepare(container, include, exclude) { + const node = this._instantiateTemplate('#scan-input-template'); + const includeInputNode = node.querySelector('.scan-input-include .scan-input-field'); + const includeMouseButton = node.querySelector('.scan-input-include .mouse-button'); + const excludeInputNode = node.querySelector('.scan-input-exclude .scan-input-field'); + const excludeMouseButton = node.querySelector('.scan-input-exclude .mouse-button'); + const removeButton = node.querySelector('.scan-input-remove'); + + this._node = node; + container.appendChild(node); + + this._includeInputField = new KeyboardMouseInputField(includeInputNode, includeMouseButton, this._os); + this._excludeInputField = new KeyboardMouseInputField(excludeInputNode, excludeMouseButton, this._os); + this._includeInputField.prepare(include, 'modifierInputs'); + this._excludeInputField.prepare(exclude, 'modifierInputs'); + + this._eventListeners.on(this._includeInputField, 'change', this._onIncludeValueChange.bind(this)); + this._eventListeners.on(this._excludeInputField, 'change', this._onExcludeValueChange.bind(this)); + this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this)); + } + + cleanup() { + this._eventListeners.removeAllEventListeners(); + if (this._includeInputField !== null) { + this._includeInputField.cleanup(); + this._includeInputField = null; + } + if (this._node !== null) { + const parent = this._node.parentNode; + if (parent !== null) { parent.removeChild(this._node); } + this._node = null; + } + } + + _onIncludeValueChange({value}) { + this._parent.setProperty(this._index, 'include', value); + } + + _onExcludeValueChange({value}) { + this._parent.setProperty(this._index, 'exclude', value); + } + + _onRemoveClick(e) { + e.preventDefault(); + this._parent.removeInput(this._index); + } + + _instantiateTemplate(templateSelector) { + const template = document.querySelector(templateSelector); + const content = document.importNode(template.content, true); + return content.firstChild; + } +} -- cgit v1.2.3