mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-01 06:22:44 -08:00
1004 lines
33 KiB
JavaScript
1004 lines
33 KiB
JavaScript
/*
|
|
* Copyright (C) 2023-2025 Yomitan Authors
|
|
* Copyright (C) 2020-2022 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/>.
|
|
*/
|
|
|
|
import {EventDispatcher} from '../../core/event-dispatcher.js';
|
|
import {EventListenerCollection} from '../../core/event-listener-collection.js';
|
|
import {normalizeModifier} from '../../dom/document-util.js';
|
|
import {querySelectorNotNull} from '../../dom/query-selector.js';
|
|
import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js';
|
|
|
|
/**
|
|
* @augments EventDispatcher<import('profile-conditions-ui').Events>
|
|
*/
|
|
export class ProfileConditionsUI extends EventDispatcher {
|
|
/**
|
|
* @param {import('./settings-controller.js').SettingsController} settingsController
|
|
*/
|
|
constructor(settingsController) {
|
|
super();
|
|
/** @type {import('./settings-controller.js').SettingsController} */
|
|
this._settingsController = settingsController;
|
|
/** @type {?import('environment').OperatingSystem} */
|
|
this._os = null;
|
|
/** @type {HTMLElement} */
|
|
this._conditionGroupsContainer = querySelectorNotNull(document, '#profile-condition-groups');
|
|
/** @type {HTMLElement} */
|
|
this._addConditionGroupButton = querySelectorNotNull(document, '#profile-add-condition-group');
|
|
/** @type {ProfileConditionGroupUI[]} */
|
|
this._children = [];
|
|
/** @type {EventListenerCollection} */
|
|
this._eventListeners = new EventListenerCollection();
|
|
/** @type {import('profile-conditions-ui').DescriptorType} */
|
|
this._defaultType = 'popupLevel';
|
|
/** @type {number} */
|
|
this._profileIndex = 0;
|
|
const validateInteger = this._validateInteger.bind(this);
|
|
const normalizeInteger = this._normalizeInteger.bind(this);
|
|
const validateFlags = this._validateFlags.bind(this);
|
|
const normalizeFlags = this._normalizeFlags.bind(this);
|
|
/* eslint-disable @stylistic/no-multi-spaces */
|
|
/** @type {Map<import('profile-conditions-ui').DescriptorType, import('profile-conditions-ui').Descriptor>} */
|
|
this._descriptors = new Map([
|
|
[
|
|
'popupLevel',
|
|
{
|
|
displayName: 'Popup Level',
|
|
defaultOperator: 'equal',
|
|
operators: new Map([
|
|
['equal', {displayName: '=', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
['notEqual', {displayName: '\u2260', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
['lessThan', {displayName: '<', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
['greaterThan', {displayName: '>', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
['lessThanOrEqual', {displayName: '\u2264', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
['greaterThanOrEqual', {displayName: '\u2265', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
|
|
]),
|
|
},
|
|
],
|
|
[
|
|
'url',
|
|
{
|
|
displayName: 'URL',
|
|
defaultOperator: 'matchDomain',
|
|
operators: new Map([
|
|
['matchDomain', {displayName: 'Matches Domain', type: 'string', defaultValue: 'example.com', resetDefaultOnChange: true, validate: this._validateDomains.bind(this), normalize: this._normalizeDomains.bind(this)}],
|
|
['matchRegExp', {displayName: 'Matches RegExp', type: 'string', defaultValue: 'example\\.com', resetDefaultOnChange: true, validate: this._validateRegExp.bind(this)}],
|
|
]),
|
|
},
|
|
],
|
|
[
|
|
'modifierKeys',
|
|
{
|
|
displayName: 'Modifier Keys',
|
|
defaultOperator: 'are',
|
|
operators: new Map([
|
|
['are', {displayName: 'Are', type: 'modifierKeys', defaultValue: ''}],
|
|
['areNot', {displayName: 'Are Not', type: 'modifierKeys', defaultValue: ''}],
|
|
['include', {displayName: 'Include', type: 'modifierKeys', defaultValue: ''}],
|
|
['notInclude', {displayName: 'Don\'t Include', type: 'modifierKeys', defaultValue: ''}],
|
|
]),
|
|
},
|
|
],
|
|
[
|
|
'flags',
|
|
{
|
|
displayName: 'Flags',
|
|
defaultOperator: 'are',
|
|
operators: new Map([
|
|
['are', {displayName: 'Are', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
|
|
['areNot', {displayName: 'Are Not', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
|
|
['include', {displayName: 'Include', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
|
|
['notInclude', {displayName: 'Don\'t Include', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
|
|
]),
|
|
},
|
|
],
|
|
]);
|
|
/* eslint-enable @stylistic/no-multi-spaces */
|
|
/** @type {Set<string>} */
|
|
this._validFlags = new Set([
|
|
'clipboard',
|
|
]);
|
|
}
|
|
|
|
/** @type {import('./settings-controller.js').SettingsController} */
|
|
get settingsController() {
|
|
return this._settingsController;
|
|
}
|
|
|
|
/** @type {number} */
|
|
get profileIndex() {
|
|
return this._profileIndex;
|
|
}
|
|
|
|
/** @type {?import('environment').OperatingSystem} */
|
|
get os() {
|
|
return this._os;
|
|
}
|
|
|
|
set os(value) {
|
|
this._os = value;
|
|
}
|
|
|
|
/**
|
|
* @param {number} profileIndex
|
|
*/
|
|
async prepare(profileIndex) {
|
|
const options = await this._settingsController.getOptionsFull();
|
|
const {profiles} = options;
|
|
if (profileIndex < 0 || profileIndex >= profiles.length) { return; }
|
|
const {conditionGroups} = profiles[profileIndex];
|
|
|
|
this._profileIndex = profileIndex;
|
|
|
|
for (let i = 0, ii = conditionGroups.length; i < ii; ++i) {
|
|
this._addConditionGroup(conditionGroups[i], i);
|
|
}
|
|
|
|
this._eventListeners.addEventListener(this._addConditionGroupButton, 'click', this._onAddConditionGroupButtonClick.bind(this), false);
|
|
}
|
|
|
|
/** */
|
|
cleanup() {
|
|
this._eventListeners.removeAllEventListeners();
|
|
|
|
for (const child of this._children) {
|
|
child.cleanup();
|
|
}
|
|
this._children = [];
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {HTMLElement}
|
|
*/
|
|
instantiateTemplate(name) {
|
|
return /** @type {HTMLElement} */ (this._settingsController.instantiateTemplate(name));
|
|
}
|
|
|
|
/**
|
|
* @returns {import('profile-conditions-ui').DescriptorInfo[]}
|
|
*/
|
|
getDescriptorTypes() {
|
|
const results = [];
|
|
for (const [name, {displayName}] of this._descriptors.entries()) {
|
|
results.push({name, displayName});
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @returns {import('profile-conditions-ui').OperatorInfo[]}
|
|
*/
|
|
getDescriptorOperators(type) {
|
|
const info = this._descriptors.get(type);
|
|
const results = [];
|
|
if (typeof info !== 'undefined') {
|
|
for (const [name, {displayName}] of info.operators.entries()) {
|
|
results.push({name, displayName});
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @returns {import('profile-conditions-ui').DescriptorType}
|
|
*/
|
|
getDefaultType() {
|
|
return this._defaultType;
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @returns {string}
|
|
*/
|
|
getDefaultOperator(type) {
|
|
const info = this._descriptors.get(type);
|
|
return (typeof info !== 'undefined' ? info.defaultOperator : '');
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} operator
|
|
* @returns {import('profile-conditions-ui').Operator}
|
|
*/
|
|
getOperatorDetails(type, operator) {
|
|
const info = this._getOperatorDetails(type, operator);
|
|
|
|
const {
|
|
displayName = operator,
|
|
type: type2 = 'string',
|
|
defaultValue = '',
|
|
resetDefaultOnChange = false,
|
|
validate = null,
|
|
normalize = null,
|
|
} = (typeof info === 'undefined' ? {} : info);
|
|
|
|
return {
|
|
displayName,
|
|
type: type2,
|
|
defaultValue,
|
|
resetDefaultOnChange,
|
|
validate,
|
|
normalize,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @returns {import('settings').ProfileCondition}
|
|
*/
|
|
getDefaultCondition() {
|
|
const type = this.getDefaultType();
|
|
const operator = this.getDefaultOperator(type);
|
|
const {defaultValue: value} = this.getOperatorDetails(type, operator);
|
|
return {type, operator, value};
|
|
}
|
|
|
|
/**
|
|
* @param {ProfileConditionGroupUI} child
|
|
* @returns {boolean}
|
|
*/
|
|
removeConditionGroup(child) {
|
|
const index = child.index;
|
|
if (index < 0 || index >= this._children.length) { return false; }
|
|
|
|
const child2 = this._children[index];
|
|
if (child !== child2) { return false; }
|
|
|
|
this._children.splice(index, 1);
|
|
child.cleanup();
|
|
|
|
for (let i = index, ii = this._children.length; i < ii; ++i) {
|
|
this._children[i].index = i;
|
|
}
|
|
|
|
void this.settingsController.modifyGlobalSettings([{
|
|
action: 'splice',
|
|
path: this.getPath('conditionGroups'),
|
|
start: index,
|
|
deleteCount: 1,
|
|
items: [],
|
|
}]);
|
|
|
|
this._triggerConditionGroupCountChanged(this._children.length);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {string[]}
|
|
*/
|
|
splitValue(value) {
|
|
return value.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0);
|
|
}
|
|
|
|
/**
|
|
* @param {string} property
|
|
* @returns {string}
|
|
*/
|
|
getPath(property) {
|
|
property = (typeof property === 'string' ? `.${property}` : '');
|
|
return `profiles[${this.profileIndex}]${property}`;
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLInputElement} inputNode
|
|
* @param {?HTMLButtonElement} mouseButton
|
|
* @returns {KeyboardMouseInputField}
|
|
*/
|
|
createKeyboardMouseInputField(inputNode, mouseButton) {
|
|
return new KeyboardMouseInputField(inputNode, mouseButton, this._os);
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {?import('settings').ProfileConditionType}
|
|
*/
|
|
static normalizeProfileConditionType(value) {
|
|
switch (value) {
|
|
case 'popupLevel':
|
|
case 'url':
|
|
case 'modifierKeys':
|
|
case 'flags':
|
|
return value;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Private
|
|
|
|
/** */
|
|
_onAddConditionGroupButtonClick() {
|
|
/** @type {import('settings').ProfileConditionGroup} */
|
|
const conditionGroup = {
|
|
conditions: [this.getDefaultCondition()],
|
|
};
|
|
const index = this._children.length;
|
|
|
|
this._addConditionGroup(conditionGroup, index);
|
|
|
|
void this.settingsController.modifyGlobalSettings([{
|
|
action: 'splice',
|
|
path: this.getPath('conditionGroups'),
|
|
start: index,
|
|
deleteCount: 0,
|
|
items: [conditionGroup],
|
|
}]);
|
|
|
|
this._triggerConditionGroupCountChanged(this._children.length);
|
|
}
|
|
|
|
/**
|
|
* @param {import('settings').ProfileConditionGroup} conditionGroup
|
|
* @param {number} index
|
|
* @returns {ProfileConditionGroupUI}
|
|
*/
|
|
_addConditionGroup(conditionGroup, index) {
|
|
const child = new ProfileConditionGroupUI(this, index);
|
|
child.prepare(conditionGroup);
|
|
this._children.push(child);
|
|
this._conditionGroupsContainer.appendChild(child.node);
|
|
return child;
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} operator
|
|
* @returns {import('profile-conditions-ui').OperatorInternal|undefined}
|
|
*/
|
|
_getOperatorDetails(type, operator) {
|
|
const info = this._descriptors.get(type);
|
|
return (typeof info !== 'undefined' ? info.operators.get(operator) : void 0);
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {boolean}
|
|
*/
|
|
_validateInteger(value) {
|
|
const number = Number.parseFloat(value);
|
|
return Number.isFinite(number) && Math.floor(number) === number;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {boolean}
|
|
*/
|
|
_validateDomains(value) {
|
|
return this.splitValue(value).length > 0;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {boolean}
|
|
*/
|
|
_validateRegExp(value) {
|
|
try {
|
|
// eslint-disable-next-line no-new
|
|
new RegExp(value, 'i');
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {string}
|
|
*/
|
|
_normalizeInteger(value) {
|
|
const number = Number.parseFloat(value);
|
|
return `${number}`;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {string}
|
|
*/
|
|
_normalizeDomains(value) {
|
|
return this.splitValue(value).join(', ');
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {boolean}
|
|
*/
|
|
_validateFlags(value) {
|
|
const flags = this.splitValue(value);
|
|
for (const flag of flags) {
|
|
if (!this._validFlags.has(flag)) {
|
|
return false;
|
|
}
|
|
}
|
|
return flags.length > 0;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {string}
|
|
*/
|
|
_normalizeFlags(value) {
|
|
return [...new Set(this.splitValue(value))].join(', ');
|
|
}
|
|
|
|
/**
|
|
* @param {number} count
|
|
*/
|
|
_triggerConditionGroupCountChanged(count) {
|
|
this.trigger('conditionGroupCountChanged', {count, profileIndex: this._profileIndex});
|
|
}
|
|
}
|
|
|
|
class ProfileConditionGroupUI {
|
|
/**
|
|
* @param {ProfileConditionsUI} parent
|
|
* @param {number} index
|
|
*/
|
|
constructor(parent, index) {
|
|
/** @type {ProfileConditionsUI} */
|
|
this._parent = parent;
|
|
/** @type {number} */
|
|
this._index = index;
|
|
/** @type {HTMLElement} */
|
|
this._node = /** @type {HTMLElement} */ (this._parent.instantiateTemplate('profile-condition-group'));
|
|
/** @type {HTMLElement} */
|
|
this._conditionContainer = querySelectorNotNull(this._node, '.profile-condition-list');
|
|
/** @type {HTMLElement} */
|
|
this._addConditionButton = querySelectorNotNull(this._node, '.profile-condition-add-button');
|
|
/** @type {ProfileConditionUI[]} */
|
|
this._children = [];
|
|
/** @type {EventListenerCollection} */
|
|
this._eventListeners = new EventListenerCollection();
|
|
}
|
|
|
|
/** @type {import('./settings-controller.js').SettingsController} */
|
|
get settingsController() {
|
|
return this._parent.settingsController;
|
|
}
|
|
|
|
/** @type {ProfileConditionsUI} */
|
|
get parent() {
|
|
return this._parent;
|
|
}
|
|
|
|
/** @type {number} */
|
|
get index() {
|
|
return this._index;
|
|
}
|
|
|
|
set index(value) {
|
|
this._index = value;
|
|
}
|
|
|
|
/** @type {HTMLElement} */
|
|
get node() {
|
|
return this._node;
|
|
}
|
|
|
|
/** @type {number} */
|
|
get childCount() {
|
|
return this._children.length;
|
|
}
|
|
|
|
/**
|
|
* @param {import('settings').ProfileConditionGroup} conditionGroup
|
|
*/
|
|
prepare(conditionGroup) {
|
|
const conditions = conditionGroup.conditions;
|
|
for (let i = 0, ii = conditions.length; i < ii; ++i) {
|
|
this._addCondition(conditions[i], i);
|
|
}
|
|
|
|
this._eventListeners.addEventListener(this._addConditionButton, 'click', this._onAddConditionButtonClick.bind(this), false);
|
|
}
|
|
|
|
/** */
|
|
cleanup() {
|
|
this._eventListeners.removeAllEventListeners();
|
|
|
|
for (const child of this._children) {
|
|
child.cleanup();
|
|
}
|
|
this._children = [];
|
|
|
|
if (this._node === null) { return; }
|
|
|
|
const node = this._node;
|
|
if (node.parentNode !== null) {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {ProfileConditionUI} child
|
|
* @returns {boolean}
|
|
*/
|
|
removeCondition(child) {
|
|
const index = child.index;
|
|
if (index < 0 || index >= this._children.length) { return false; }
|
|
|
|
const child2 = this._children[index];
|
|
if (child !== child2) { return false; }
|
|
|
|
this._children.splice(index, 1);
|
|
child.cleanup();
|
|
|
|
for (let i = index, ii = this._children.length; i < ii; ++i) {
|
|
this._children[i].index = i;
|
|
}
|
|
|
|
void this.settingsController.modifyGlobalSettings([{
|
|
action: 'splice',
|
|
path: this.getPath('conditions'),
|
|
start: index,
|
|
deleteCount: 1,
|
|
items: [],
|
|
}]);
|
|
|
|
if (this._children.length === 0) {
|
|
this.removeSelf();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {string} property
|
|
* @returns {string}
|
|
*/
|
|
getPath(property) {
|
|
property = (typeof property === 'string' ? `.${property}` : '');
|
|
return this._parent.getPath(`conditionGroups[${this._index}]${property}`);
|
|
}
|
|
|
|
/** */
|
|
removeSelf() {
|
|
this._parent.removeConditionGroup(this);
|
|
}
|
|
|
|
// Private
|
|
|
|
/** */
|
|
_onAddConditionButtonClick() {
|
|
const condition = this._parent.getDefaultCondition();
|
|
const index = this._children.length;
|
|
|
|
this._addCondition(condition, index);
|
|
|
|
void this.settingsController.modifyGlobalSettings([{
|
|
action: 'splice',
|
|
path: this.getPath('conditions'),
|
|
start: index,
|
|
deleteCount: 0,
|
|
items: [condition],
|
|
}]);
|
|
}
|
|
|
|
/**
|
|
* @param {import('settings').ProfileCondition} condition
|
|
* @param {number} index
|
|
* @returns {ProfileConditionUI}
|
|
*/
|
|
_addCondition(condition, index) {
|
|
const child = new ProfileConditionUI(this, index);
|
|
child.prepare(condition);
|
|
this._children.push(child);
|
|
this._conditionContainer.appendChild(child.node);
|
|
return child;
|
|
}
|
|
}
|
|
|
|
class ProfileConditionUI {
|
|
/**
|
|
* @param {ProfileConditionGroupUI} parent
|
|
* @param {number} index
|
|
*/
|
|
constructor(parent, index) {
|
|
/** @type {ProfileConditionGroupUI} */
|
|
this._parent = parent;
|
|
/** @type {number} */
|
|
this._index = index;
|
|
/** @type {HTMLElement} */
|
|
this._node = this._parent.parent.instantiateTemplate('profile-condition');
|
|
/** @type {HTMLSelectElement} */
|
|
this._typeInput = querySelectorNotNull(this._node, '.profile-condition-type');
|
|
/** @type {HTMLSelectElement} */
|
|
this._operatorInput = querySelectorNotNull(this._node, '.profile-condition-operator');
|
|
/** @type {HTMLButtonElement} */
|
|
this._mouseButton = querySelectorNotNull(this._node, '.mouse-button');
|
|
/** @type {HTMLElement} */
|
|
this._mouseButtonContainer = querySelectorNotNull(this._node, '.mouse-button-container');
|
|
/** @type {HTMLButtonElement} */
|
|
this._menuButton = querySelectorNotNull(this._node, '.profile-condition-menu-button');
|
|
/** @type {HTMLElement} */
|
|
this._typeOptionContainer = querySelectorNotNull(this._typeInput, 'optgroup');
|
|
/** @type {HTMLElement} */
|
|
this._operatorOptionContainer = querySelectorNotNull(this._operatorInput, 'optgroup');
|
|
/** @type {HTMLInputElement} */
|
|
this._valueInput = querySelectorNotNull(this._node, '.profile-condition-input');
|
|
/** @type {string} */
|
|
this._value = '';
|
|
/** @type {?KeyboardMouseInputField} */
|
|
this._kbmInputField = null;
|
|
/** @type {EventListenerCollection} */
|
|
this._eventListeners = new EventListenerCollection();
|
|
/** @type {EventListenerCollection} */
|
|
this._inputEventListeners = new EventListenerCollection();
|
|
}
|
|
|
|
/** @type {import('./settings-controller.js').SettingsController} */
|
|
get settingsController() {
|
|
return this._parent.parent.settingsController;
|
|
}
|
|
|
|
/** @type {ProfileConditionGroupUI} */
|
|
get parent() {
|
|
return this._parent;
|
|
}
|
|
|
|
/** @type {number} */
|
|
get index() {
|
|
return this._index;
|
|
}
|
|
|
|
set index(value) {
|
|
this._index = value;
|
|
}
|
|
|
|
/** @type {HTMLElement} */
|
|
get node() {
|
|
return this._node;
|
|
}
|
|
|
|
/**
|
|
* @param {import('settings').ProfileCondition} condition
|
|
*/
|
|
prepare(condition) {
|
|
const {type, operator, value} = condition;
|
|
|
|
const operatorDetails = this._getOperatorDetails(type, operator);
|
|
this._updateTypes(type);
|
|
this._updateOperators(type, operator);
|
|
this._updateValueInput(value, operatorDetails);
|
|
|
|
this._eventListeners.addEventListener(this._typeInput, 'change', this._onTypeChange.bind(this), false);
|
|
this._eventListeners.addEventListener(this._operatorInput, 'change', this._onOperatorChange.bind(this), false);
|
|
this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
|
|
this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false);
|
|
}
|
|
|
|
/** */
|
|
cleanup() {
|
|
this._eventListeners.removeAllEventListeners();
|
|
this._value = '';
|
|
|
|
if (this._node === null) { return; }
|
|
|
|
const node = this._node;
|
|
if (node.parentNode !== null) {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} property
|
|
* @returns {string}
|
|
*/
|
|
getPath(property) {
|
|
property = (typeof property === 'string' ? `.${property}` : '');
|
|
return this._parent.getPath(`conditions[${this._index}]${property}`);
|
|
}
|
|
|
|
// Private
|
|
|
|
/**
|
|
* @param {Event} e
|
|
*/
|
|
_onTypeChange(e) {
|
|
const element = /** @type {HTMLSelectElement} */ (e.currentTarget);
|
|
const type = ProfileConditionsUI.normalizeProfileConditionType(element.value);
|
|
if (type === null) { return; }
|
|
void this._setType(type);
|
|
}
|
|
|
|
/**
|
|
* @param {Event} e
|
|
*/
|
|
_onOperatorChange(e) {
|
|
const element = /** @type {HTMLSelectElement} */ (e.currentTarget);
|
|
const type = ProfileConditionsUI.normalizeProfileConditionType(this._typeInput.value);
|
|
if (type === null) { return; }
|
|
const operator = element.value;
|
|
void this._setOperator(type, operator);
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').InputData} details
|
|
* @param {Event} e
|
|
*/
|
|
_onValueInputChange({validate, normalize}, e) {
|
|
const node = /** @type {HTMLInputElement} */ (e.currentTarget);
|
|
const value = node.value;
|
|
const okay = this._validateValue(value, validate);
|
|
this._value = value;
|
|
if (okay) {
|
|
const normalizedValue = this._normalizeValue(value, normalize);
|
|
node.value = normalizedValue;
|
|
void this.settingsController.setGlobalSetting(this.getPath('value'), normalizedValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').InputData} details
|
|
* @param {import('keyboard-mouse-input-field').EventArgument<'change'>} event
|
|
*/
|
|
_onModifierInputChange({validate, normalize}, event) {
|
|
const modifiers = this._joinModifiers(event.modifiers);
|
|
const okay = this._validateValue(modifiers, validate);
|
|
this._value = modifiers;
|
|
if (okay) {
|
|
const normalizedValue = this._normalizeValue(modifiers, normalize);
|
|
void this.settingsController.setGlobalSetting(this.getPath('value'), normalizedValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('popup-menu').MenuOpenEvent} e
|
|
*/
|
|
_onMenuOpen(e) {
|
|
const bodyNode = e.detail.menu.bodyNode;
|
|
/** @type {HTMLElement} */
|
|
const deleteGroup = /** @type {HTMLElement} */ (bodyNode.querySelector('.popup-menu-item[data-menu-action="deleteGroup"]'));
|
|
if (deleteGroup !== null) {
|
|
deleteGroup.hidden = (this._parent.childCount <= 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('popup-menu').MenuCloseEvent} e
|
|
*/
|
|
_onMenuClose(e) {
|
|
switch (e.detail.action) {
|
|
case 'delete':
|
|
this._removeSelf();
|
|
break;
|
|
case 'deleteGroup':
|
|
this._parent.removeSelf();
|
|
break;
|
|
case 'resetValue':
|
|
void this._resetValue();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {import('profile-conditions-ui').DescriptorInfo[]}
|
|
*/
|
|
_getDescriptorTypes() {
|
|
return this._parent.parent.getDescriptorTypes();
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @returns {import('profile-conditions-ui').OperatorInfo[]}
|
|
*/
|
|
_getDescriptorOperators(type) {
|
|
return this._parent.parent.getDescriptorOperators(type);
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} operator
|
|
* @returns {import('profile-conditions-ui').Operator}
|
|
*/
|
|
_getOperatorDetails(type, operator) {
|
|
return this._parent.parent.getOperatorDetails(type, operator);
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
*/
|
|
_updateTypes(type) {
|
|
const types = this._getDescriptorTypes();
|
|
this._updateSelect(this._typeInput, this._typeOptionContainer, types, type);
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} operator
|
|
*/
|
|
_updateOperators(type, operator) {
|
|
const operators = this._getDescriptorOperators(type);
|
|
this._updateSelect(this._operatorInput, this._operatorOptionContainer, operators, operator);
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLSelectElement} select
|
|
* @param {HTMLElement} optionContainer
|
|
* @param {import('profile-conditions-ui').DescriptorInfo[]|import('profile-conditions-ui').OperatorInfo[]} values
|
|
* @param {string} value
|
|
*/
|
|
_updateSelect(select, optionContainer, values, value) {
|
|
optionContainer.textContent = '';
|
|
for (const {name, displayName} of values) {
|
|
const option = document.createElement('option');
|
|
option.value = name;
|
|
option.textContent = displayName;
|
|
optionContainer.appendChild(option);
|
|
}
|
|
select.value = value;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @param {import('profile-conditions-ui').Operator} operator
|
|
* @returns {boolean}
|
|
*/
|
|
_updateValueInput(value, {type, validate, normalize}) {
|
|
this._inputEventListeners.removeAllEventListeners();
|
|
if (this._kbmInputField !== null) {
|
|
this._kbmInputField.cleanup();
|
|
this._kbmInputField = null;
|
|
}
|
|
|
|
let inputType = 'text';
|
|
/** @type {?string} */
|
|
let inputValue = value;
|
|
let inputStep = null;
|
|
let showMouseButton = false;
|
|
/** @type {import('profile-conditions-ui').InputData} */
|
|
const inputData = {validate, normalize};
|
|
const node = this._valueInput;
|
|
|
|
switch (type) {
|
|
case 'integer':
|
|
inputType = 'number';
|
|
inputStep = '1';
|
|
break;
|
|
case 'modifierKeys':
|
|
case 'modifierInputs':
|
|
inputValue = null;
|
|
showMouseButton = (type === 'modifierInputs');
|
|
this._kbmInputField = this._parent.parent.createKeyboardMouseInputField(node, this._mouseButton);
|
|
this._kbmInputField.prepare(null, this._splitModifiers(value), showMouseButton, false);
|
|
break;
|
|
}
|
|
|
|
this._value = value;
|
|
delete node.dataset.invalid;
|
|
node.type = inputType;
|
|
if (inputValue !== null) {
|
|
node.value = inputValue;
|
|
}
|
|
if (typeof inputStep === 'string') {
|
|
node.step = inputStep;
|
|
} else {
|
|
node.removeAttribute('step');
|
|
}
|
|
this._mouseButtonContainer.hidden = !showMouseButton;
|
|
|
|
switch (type) {
|
|
case 'modifierKeys':
|
|
case 'modifierInputs':
|
|
if (this._kbmInputField !== null) {
|
|
this._inputEventListeners.on(this._kbmInputField, 'change', this._onModifierInputChange.bind(this, inputData));
|
|
}
|
|
break;
|
|
default: // 'integer', 'string'
|
|
this._inputEventListeners.addEventListener(node, 'change', this._onValueInputChange.bind(this, inputData), false);
|
|
break;
|
|
}
|
|
|
|
return this._validateValue(value, validate);
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @param {?import('profile-conditions-ui').ValidateFunction} validate
|
|
* @returns {boolean}
|
|
*/
|
|
_validateValue(value, validate) {
|
|
const okay = (validate === null || validate(value));
|
|
this._valueInput.dataset.invalid = `${!okay}`;
|
|
return okay;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @param {?import('profile-conditions-ui').NormalizeFunction} normalize
|
|
* @returns {value}
|
|
*/
|
|
_normalizeValue(value, normalize) {
|
|
return (normalize !== null ? normalize(value) : value);
|
|
}
|
|
|
|
/** */
|
|
_removeSelf() {
|
|
this._parent.removeCondition(this);
|
|
}
|
|
|
|
/**
|
|
* @param {string} modifiersString
|
|
* @returns {import('input').Modifier[]}
|
|
*/
|
|
_splitModifiers(modifiersString) {
|
|
/** @type {import('input').Modifier[]} */
|
|
const results = [];
|
|
for (const item of modifiersString.split(/[,;\s]+/)) {
|
|
const modifier = normalizeModifier(item.trim().toLowerCase());
|
|
if (modifier !== null) { results.push(modifier); }
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @param {import('input').Modifier[]} modifiersArray
|
|
* @returns {string}
|
|
*/
|
|
_joinModifiers(modifiersArray) {
|
|
return modifiersArray.join(', ');
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} [operator]
|
|
*/
|
|
async _setType(type, operator) {
|
|
const operators = this._getDescriptorOperators(type);
|
|
if (typeof operator === 'undefined') {
|
|
operator = operators.length > 0 ? operators[0].name : '';
|
|
}
|
|
const operatorDetails = this._getOperatorDetails(type, operator);
|
|
const {defaultValue} = operatorDetails;
|
|
this._updateSelect(this._operatorInput, this._operatorOptionContainer, operators, operator);
|
|
this._updateValueInput(defaultValue, operatorDetails);
|
|
await this.settingsController.modifyGlobalSettings([
|
|
{action: 'set', path: this.getPath('type'), value: type},
|
|
{action: 'set', path: this.getPath('operator'), value: operator},
|
|
{action: 'set', path: this.getPath('value'), value: defaultValue},
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param {import('profile-conditions-ui').DescriptorType} type
|
|
* @param {string} operator
|
|
*/
|
|
async _setOperator(type, operator) {
|
|
const operatorDetails = this._getOperatorDetails(type, operator);
|
|
/** @type {import('settings-modifications').Modification[]} */
|
|
const settingsModifications = [{action: 'set', path: this.getPath('operator'), value: operator}];
|
|
if (operatorDetails.resetDefaultOnChange) {
|
|
const {defaultValue} = operatorDetails;
|
|
const okay = this._updateValueInput(defaultValue, operatorDetails);
|
|
if (okay) {
|
|
settingsModifications.push({action: 'set', path: this.getPath('value'), value: defaultValue});
|
|
}
|
|
}
|
|
await this.settingsController.modifyGlobalSettings(settingsModifications);
|
|
}
|
|
|
|
/** */
|
|
async _resetValue() {
|
|
const type = ProfileConditionsUI.normalizeProfileConditionType(this._typeInput.value);
|
|
if (type === null) { return; }
|
|
const operator = this._operatorInput.value;
|
|
await this._setType(type, operator);
|
|
}
|
|
}
|