Json schema profile conditions (#758)
* Add clearCache function * Add upgrade * Use schema-based profile condition validation * Update profile conditions settings controller * Remove unnecessary async * Remove old * Remove unused templates
This commit is contained in:
parent
74edf462ab
commit
f3dd2270a5
@ -38,7 +38,7 @@
|
|||||||
<script src="/bg/js/json-schema.js"></script>
|
<script src="/bg/js/json-schema.js"></script>
|
||||||
<script src="/bg/js/media-utility.js"></script>
|
<script src="/bg/js/media-utility.js"></script>
|
||||||
<script src="/bg/js/options.js"></script>
|
<script src="/bg/js/options.js"></script>
|
||||||
<script src="/bg/js/profile-conditions.js"></script>
|
<script src="/bg/js/profile-conditions2.js"></script>
|
||||||
<script src="/bg/js/request-builder.js"></script>
|
<script src="/bg/js/request-builder.js"></script>
|
||||||
<script src="/bg/js/template-renderer.js"></script>
|
<script src="/bg/js/template-renderer.js"></script>
|
||||||
<script src="/bg/js/text-source-map.js"></script>
|
<script src="/bg/js/text-source-map.js"></script>
|
||||||
|
@ -68,39 +68,39 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group
|
|||||||
content: "AND";
|
content: "AND";
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group .condition-prefix {
|
.condition-prefix {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.input-group .condition-prefix,
|
.condition-prefix,
|
||||||
.input-group .condition-group-separator-label {
|
.condition-group-separator-label {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.input-group .condition-group-separator-label {
|
.condition-group-separator-label {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.input-group .condition-type,
|
.condition-type,
|
||||||
.input-group .condition-operator {
|
.condition-operator {
|
||||||
width: auto;
|
width: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-align-last: center;
|
text-align-last: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.condition-group>.condition>*:first-child,
|
.condition-list>.condition>*:first-child,
|
||||||
.audio-source-list>.audio-source>*:first-child {
|
.audio-source-list>.audio-source>*:first-child {
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
.condition-group>.condition:nth-child(n+2)>*:first-child,
|
.condition-list>.condition:nth-child(n+2)>*:first-child,
|
||||||
.audio-source-list>.audio-source:nth-child(n+2)>*:first-child {
|
.audio-source-list>.audio-source:nth-child(n+2)>*:first-child {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
}
|
}
|
||||||
.condition-group>.condition:nth-child(n+2)>div:last-child>button,
|
.condition-list>.condition:nth-child(n+2)>div:last-child>button,
|
||||||
.audio-source-list>.audio-source:nth-child(n+2)>*:last-child>button {
|
.audio-source-list>.audio-source:nth-child(n+2)>*:last-child>button {
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
}
|
}
|
||||||
.condition-group>.condition:nth-last-child(n+2)>div:last-child>button,
|
.condition-list>.condition:nth-last-child(n+2)>div:last-child>button,
|
||||||
.audio-source-list>.audio-source:nth-last-child(n+2)>*:last-child>button {
|
.audio-source-list>.audio-source:nth-last-child(n+2)>*:last-child>button {
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group
|
|||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.condition-groups>*:last-of-type {
|
.condition-groups>.condition-group:last-child>.condition-group-separator-label {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,13 +28,11 @@
|
|||||||
* Mecab
|
* Mecab
|
||||||
* ObjectPropertyAccessor
|
* ObjectPropertyAccessor
|
||||||
* OptionsUtil
|
* OptionsUtil
|
||||||
|
* ProfileConditions
|
||||||
* RequestBuilder
|
* RequestBuilder
|
||||||
* TemplateRenderer
|
* TemplateRenderer
|
||||||
* Translator
|
* Translator
|
||||||
* conditionsTestValue
|
|
||||||
* jp
|
* jp
|
||||||
* profileConditionsDescriptor
|
|
||||||
* profileConditionsDescriptorPromise
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Backend {
|
class Backend {
|
||||||
@ -49,6 +47,8 @@ class Backend {
|
|||||||
this._options = null;
|
this._options = null;
|
||||||
this._optionsSchema = null;
|
this._optionsSchema = null;
|
||||||
this._optionsSchemaValidator = new JsonSchemaValidator();
|
this._optionsSchemaValidator = new JsonSchemaValidator();
|
||||||
|
this._profileConditionsSchemaCache = [];
|
||||||
|
this._profileConditionsUtil = new ProfileConditions();
|
||||||
this._defaultAnkiFieldTemplates = null;
|
this._defaultAnkiFieldTemplates = null;
|
||||||
this._requestBuilder = new RequestBuilder();
|
this._requestBuilder = new RequestBuilder();
|
||||||
this._audioUriBuilder = new AudioUriBuilder({
|
this._audioUriBuilder = new AudioUriBuilder({
|
||||||
@ -200,8 +200,6 @@ class Backend {
|
|||||||
}
|
}
|
||||||
await this._translator.prepare();
|
await this._translator.prepare();
|
||||||
|
|
||||||
await profileConditionsDescriptorPromise;
|
|
||||||
|
|
||||||
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
||||||
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
||||||
this._options = await OptionsUtil.load();
|
this._options = await OptionsUtil.load();
|
||||||
@ -397,6 +395,7 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onApiOptionsSave({source}) {
|
async _onApiOptionsSave({source}) {
|
||||||
|
this._clearProfileConditionsSchemaCache();
|
||||||
const options = this.getFullOptions();
|
const options = this.getFullOptions();
|
||||||
await OptionsUtil.save(options);
|
await OptionsUtil.save(options);
|
||||||
this._applyOptions(source);
|
this._applyOptions(source);
|
||||||
@ -1006,35 +1005,32 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getProfileFromContext(options, optionsContext) {
|
_getProfileFromContext(options, optionsContext) {
|
||||||
|
optionsContext = this._profileConditionsUtil.normalizeContext(optionsContext);
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
for (const profile of options.profiles) {
|
for (const profile of options.profiles) {
|
||||||
const conditionGroups = profile.conditionGroups;
|
const conditionGroups = profile.conditionGroups;
|
||||||
if (conditionGroups.length > 0 && this._testConditionGroups(conditionGroups, optionsContext)) {
|
|
||||||
|
let schema;
|
||||||
|
if (index < this._profileConditionsSchemaCache.length) {
|
||||||
|
schema = this._profileConditionsSchemaCache[index];
|
||||||
|
} else {
|
||||||
|
schema = this._profileConditionsUtil.createSchema(conditionGroups);
|
||||||
|
this._profileConditionsSchemaCache.push(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditionGroups.length > 0 && this._optionsSchemaValidator.isValid(optionsContext, schema)) {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
++index;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_testConditionGroups(conditionGroups, data) {
|
_clearProfileConditionsSchemaCache() {
|
||||||
if (conditionGroups.length === 0) { return false; }
|
this._profileConditionsSchemaCache = [];
|
||||||
|
this._optionsSchemaValidator.clearCache();
|
||||||
for (const conditionGroup of conditionGroups) {
|
|
||||||
const conditions = conditionGroup.conditions;
|
|
||||||
if (conditions.length > 0 && this._testConditions(conditions, data)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_testConditions(conditions, data) {
|
|
||||||
for (const condition of conditions) {
|
|
||||||
if (!conditionsTestValue(profileConditionsDescriptor, condition.type, condition.operator, condition.value, data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkLastError() {
|
_checkLastError() {
|
||||||
|
@ -181,6 +181,10 @@ class JsonSchemaValidator {
|
|||||||
return this._getPropertySchema(schema, property, value, null);
|
return this._getPropertySchema(schema, property, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
this._regexCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
_getPropertySchema(schema, property, value, path) {
|
_getPropertySchema(schema, property, value, path) {
|
||||||
|
@ -389,6 +389,10 @@ class OptionsUtil {
|
|||||||
{
|
{
|
||||||
async: true,
|
async: true,
|
||||||
update: this._updateVersion3.bind(this)
|
update: this._updateVersion3.bind(this)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
async: false,
|
||||||
|
update: this._updateVersion4.bind(this)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -459,4 +463,22 @@ class OptionsUtil {
|
|||||||
}
|
}
|
||||||
return fieldTemplates;
|
return fieldTemplates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static _updateVersion4(options) {
|
||||||
|
// Version 4 changes:
|
||||||
|
// Options conditions converted to string representations.
|
||||||
|
for (const {conditionGroups} of options.profiles) {
|
||||||
|
for (const {conditions} of conditionGroups) {
|
||||||
|
for (const condition of conditions) {
|
||||||
|
const value = condition.value;
|
||||||
|
condition.value = (
|
||||||
|
Array.isArray(value) ?
|
||||||
|
value.join(', ') :
|
||||||
|
`${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019-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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
* Environment
|
|
||||||
*/
|
|
||||||
|
|
||||||
let profileConditionsDescriptor = null;
|
|
||||||
|
|
||||||
const profileConditionsDescriptorPromise = (async () => {
|
|
||||||
function profileConditionTestDomain(urlDomain, domain) {
|
|
||||||
return (
|
|
||||||
urlDomain.endsWith(domain) &&
|
|
||||||
(
|
|
||||||
domain.length === urlDomain.length ||
|
|
||||||
urlDomain[urlDomain.length - domain.length - 1] === '.'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function profileConditionTestDomainList(url, domainList) {
|
|
||||||
const urlDomain = new URL(url).hostname.toLowerCase();
|
|
||||||
for (const domain of domainList) {
|
|
||||||
if (profileConditionTestDomain(urlDomain, domain)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const environment = new Environment();
|
|
||||||
await environment.prepare();
|
|
||||||
|
|
||||||
const modifiers = environment.getInfo().modifiers;
|
|
||||||
const modifierSeparator = modifiers.separator;
|
|
||||||
const modifierKeyValues = modifiers.keys.map(
|
|
||||||
({value, name}) => ({optionValue: value, name})
|
|
||||||
);
|
|
||||||
|
|
||||||
const modifierValueToName = new Map(
|
|
||||||
modifierKeyValues.map(({optionValue, name}) => [optionValue, name])
|
|
||||||
);
|
|
||||||
|
|
||||||
const modifierNameToValue = new Map(
|
|
||||||
modifierKeyValues.map(({optionValue, name}) => [name, optionValue])
|
|
||||||
);
|
|
||||||
|
|
||||||
profileConditionsDescriptor = {
|
|
||||||
popupLevel: {
|
|
||||||
name: 'Popup Level',
|
|
||||||
description: 'Use profile depending on the level of the popup.',
|
|
||||||
placeholder: 'Number',
|
|
||||||
type: 'number',
|
|
||||||
step: 1,
|
|
||||||
defaultValue: 0,
|
|
||||||
defaultOperator: 'equal',
|
|
||||||
transform: (optionValue) => parseInt(optionValue, 10),
|
|
||||||
transformReverse: (transformedOptionValue) => `${transformedOptionValue}`,
|
|
||||||
validateTransformed: (transformedOptionValue) => Number.isFinite(transformedOptionValue),
|
|
||||||
operators: {
|
|
||||||
equal: {
|
|
||||||
name: '=',
|
|
||||||
test: ({depth}, optionValue) => (depth === optionValue)
|
|
||||||
},
|
|
||||||
notEqual: {
|
|
||||||
name: '\u2260',
|
|
||||||
test: ({depth}, optionValue) => (depth !== optionValue)
|
|
||||||
},
|
|
||||||
lessThan: {
|
|
||||||
name: '<',
|
|
||||||
test: ({depth}, optionValue) => (depth < optionValue)
|
|
||||||
},
|
|
||||||
greaterThan: {
|
|
||||||
name: '>',
|
|
||||||
test: ({depth}, optionValue) => (depth > optionValue)
|
|
||||||
},
|
|
||||||
lessThanOrEqual: {
|
|
||||||
name: '\u2264',
|
|
||||||
test: ({depth}, optionValue) => (depth <= optionValue)
|
|
||||||
},
|
|
||||||
greaterThanOrEqual: {
|
|
||||||
name: '\u2265',
|
|
||||||
test: ({depth}, optionValue) => (depth >= optionValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
name: 'URL',
|
|
||||||
description: 'Use profile depending on the URL of the current website.',
|
|
||||||
defaultOperator: 'matchDomain',
|
|
||||||
operators: {
|
|
||||||
matchDomain: {
|
|
||||||
name: 'Matches Domain',
|
|
||||||
placeholder: 'Comma separated list of domains',
|
|
||||||
defaultValue: 'example.com',
|
|
||||||
transformCache: {},
|
|
||||||
transform: (optionValue) => optionValue.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0),
|
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue.join(', '),
|
|
||||||
validateTransformed: (transformedOptionValue) => (transformedOptionValue.length > 0),
|
|
||||||
test: ({url}, transformedOptionValue) => profileConditionTestDomainList(url, transformedOptionValue)
|
|
||||||
},
|
|
||||||
matchRegExp: {
|
|
||||||
name: 'Matches RegExp',
|
|
||||||
placeholder: 'Regular expression',
|
|
||||||
defaultValue: 'example\\.com',
|
|
||||||
transformCache: {},
|
|
||||||
transform: (optionValue) => new RegExp(optionValue, 'i'),
|
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue.source,
|
|
||||||
test: ({url}, transformedOptionValue) => (transformedOptionValue !== null && transformedOptionValue.test(url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifierKeys: {
|
|
||||||
name: 'Modifier Keys',
|
|
||||||
description: 'Use profile depending on the active modifier keys.',
|
|
||||||
values: modifierKeyValues,
|
|
||||||
defaultOperator: 'are',
|
|
||||||
operators: {
|
|
||||||
are: {
|
|
||||||
name: 'are',
|
|
||||||
placeholder: 'Press one or more modifier keys here',
|
|
||||||
defaultValue: [],
|
|
||||||
type: 'keyMulti',
|
|
||||||
keySeparator: modifierSeparator,
|
|
||||||
transformInput: (optionValue) => optionValue
|
|
||||||
.split(modifierSeparator)
|
|
||||||
.filter((v) => v.length > 0)
|
|
||||||
.map((v) => modifierNameToValue.get(v)),
|
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue
|
|
||||||
.map((v) => modifierValueToName.get(v))
|
|
||||||
.join(modifierSeparator),
|
|
||||||
test: ({modifierKeys}, optionValue) => areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
|
||||||
},
|
|
||||||
areNot: {
|
|
||||||
name: 'are not',
|
|
||||||
placeholder: 'Press one or more modifier keys here',
|
|
||||||
defaultValue: [],
|
|
||||||
type: 'keyMulti',
|
|
||||||
keySeparator: modifierSeparator,
|
|
||||||
transformInput: (optionValue) => optionValue
|
|
||||||
.split(modifierSeparator)
|
|
||||||
.filter((v) => v.length > 0)
|
|
||||||
.map((v) => modifierNameToValue.get(v)),
|
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue
|
|
||||||
.map((v) => modifierValueToName.get(v))
|
|
||||||
.join(modifierSeparator),
|
|
||||||
test: ({modifierKeys}, optionValue) => !areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
name: 'include',
|
|
||||||
type: 'select',
|
|
||||||
defaultValue: 'alt',
|
|
||||||
test: ({modifierKeys}, optionValue) => modifierKeys.includes(optionValue)
|
|
||||||
},
|
|
||||||
notInclude: {
|
|
||||||
name: 'don\'t include',
|
|
||||||
type: 'select',
|
|
||||||
defaultValue: 'alt',
|
|
||||||
test: ({modifierKeys}, optionValue) => !modifierKeys.includes(optionValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,449 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019-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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
* DocumentUtil
|
|
||||||
* conditionsNormalizeOptionValue
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ConditionsUI {
|
|
||||||
static instantiateTemplate(templateSelector) {
|
|
||||||
const template = document.querySelector(templateSelector);
|
|
||||||
const content = document.importNode(template.content, true);
|
|
||||||
return $(content.firstChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConditionsUI.Container = class Container {
|
|
||||||
constructor(conditionDescriptors, conditionNameDefault, conditionGroups, container, addButton) {
|
|
||||||
this.children = [];
|
|
||||||
this.conditionDescriptors = conditionDescriptors;
|
|
||||||
this.conditionNameDefault = conditionNameDefault;
|
|
||||||
this.conditionGroups = conditionGroups;
|
|
||||||
this.container = container;
|
|
||||||
this.addButton = addButton;
|
|
||||||
|
|
||||||
this.container.empty();
|
|
||||||
|
|
||||||
for (const conditionGroup of toIterable(conditionGroups)) {
|
|
||||||
this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButton.on('click', this.onAddConditionGroup.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
for (const child of this.children) {
|
|
||||||
child.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButton.off('click');
|
|
||||||
this.container.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
// Override
|
|
||||||
}
|
|
||||||
|
|
||||||
isolate(object) {
|
|
||||||
// Override
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(child) {
|
|
||||||
const index = this.children.indexOf(child);
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
child.cleanup();
|
|
||||||
this.children.splice(index, 1);
|
|
||||||
this.conditionGroups.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddConditionGroup() {
|
|
||||||
const conditionGroup = this.isolate({
|
|
||||||
conditions: [this.createDefaultCondition(this.conditionNameDefault)]
|
|
||||||
});
|
|
||||||
this.conditionGroups.push(conditionGroup);
|
|
||||||
this.save();
|
|
||||||
this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup));
|
|
||||||
}
|
|
||||||
|
|
||||||
createDefaultCondition(type) {
|
|
||||||
let operator = '';
|
|
||||||
let value = '';
|
|
||||||
if (hasOwn(this.conditionDescriptors, type)) {
|
|
||||||
const conditionDescriptor = this.conditionDescriptors[type];
|
|
||||||
operator = conditionDescriptor.defaultOperator;
|
|
||||||
({value} = this.getOperatorDefaultValue(type, operator));
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {type, operator, value};
|
|
||||||
}
|
|
||||||
|
|
||||||
getOperatorDefaultValue(type, operator) {
|
|
||||||
if (hasOwn(this.conditionDescriptors, type)) {
|
|
||||||
const conditionDescriptor = this.conditionDescriptors[type];
|
|
||||||
if (hasOwn(conditionDescriptor.operators, operator)) {
|
|
||||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
|
||||||
if (hasOwn(operatorDescriptor, 'defaultValue')) {
|
|
||||||
return {value: this.isolate(operatorDescriptor.defaultValue), fromOperator: true};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasOwn(conditionDescriptor, 'defaultValue')) {
|
|
||||||
return {value: this.isolate(conditionDescriptor.defaultValue), fromOperator: false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {fromOperator: false};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConditionsUI.ConditionGroup = class ConditionGroup {
|
|
||||||
constructor(parent, conditionGroup) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.children = [];
|
|
||||||
this.conditionGroup = conditionGroup;
|
|
||||||
this.container = $('<div>').addClass('condition-group').appendTo(parent.container);
|
|
||||||
this.options = ConditionsUI.instantiateTemplate('#condition-group-options-template').appendTo(parent.container);
|
|
||||||
this.separator = ConditionsUI.instantiateTemplate('#condition-group-separator-template').appendTo(parent.container);
|
|
||||||
this.addButton = this.options.find('.condition-add');
|
|
||||||
|
|
||||||
for (const condition of toIterable(conditionGroup.conditions)) {
|
|
||||||
this.children.push(new ConditionsUI.Condition(this, condition));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButton.on('click', this.onAddCondition.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
for (const child of this.children) {
|
|
||||||
child.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButton.off('click');
|
|
||||||
this.container.remove();
|
|
||||||
this.options.remove();
|
|
||||||
this.separator.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.parent.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
isolate(object) {
|
|
||||||
return this.parent.isolate(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(child) {
|
|
||||||
const index = this.children.indexOf(child);
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
child.cleanup();
|
|
||||||
this.children.splice(index, 1);
|
|
||||||
this.conditionGroup.conditions.splice(index, 1);
|
|
||||||
|
|
||||||
if (this.children.length === 0) {
|
|
||||||
this.parent.remove(this, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddCondition() {
|
|
||||||
const condition = this.isolate(this.parent.createDefaultCondition(this.parent.conditionNameDefault));
|
|
||||||
this.conditionGroup.conditions.push(condition);
|
|
||||||
this.children.push(new ConditionsUI.Condition(this, condition));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConditionsUI.Condition = class Condition {
|
|
||||||
constructor(parent, condition) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.condition = condition;
|
|
||||||
this.container = ConditionsUI.instantiateTemplate('#condition-template').appendTo(parent.container);
|
|
||||||
this.input = this.container.find('.condition-input');
|
|
||||||
this.inputInner = null;
|
|
||||||
this.typeSelect = this.container.find('.condition-type');
|
|
||||||
this.operatorSelect = this.container.find('.condition-operator');
|
|
||||||
this.removeButton = this.container.find('.condition-remove');
|
|
||||||
|
|
||||||
this.updateTypes();
|
|
||||||
this.updateOperators();
|
|
||||||
this.updateInput();
|
|
||||||
|
|
||||||
this.typeSelect.on('change', this.onConditionTypeChanged.bind(this));
|
|
||||||
this.operatorSelect.on('change', this.onConditionOperatorChanged.bind(this));
|
|
||||||
this.removeButton.on('click', this.onRemoveClicked.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
this.inputInner.off('change');
|
|
||||||
this.typeSelect.off('change');
|
|
||||||
this.operatorSelect.off('change');
|
|
||||||
this.removeButton.off('click');
|
|
||||||
this.container.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.parent.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
isolate(object) {
|
|
||||||
return this.parent.isolate(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTypes() {
|
|
||||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
|
||||||
const optionGroup = this.typeSelect.find('optgroup');
|
|
||||||
optionGroup.empty();
|
|
||||||
for (const type of Object.keys(conditionDescriptors)) {
|
|
||||||
const conditionDescriptor = conditionDescriptors[type];
|
|
||||||
$('<option>').val(type).text(conditionDescriptor.name).appendTo(optionGroup);
|
|
||||||
}
|
|
||||||
this.typeSelect.val(this.condition.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOperators() {
|
|
||||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
|
||||||
const optionGroup = this.operatorSelect.find('optgroup');
|
|
||||||
optionGroup.empty();
|
|
||||||
|
|
||||||
const type = this.condition.type;
|
|
||||||
if (hasOwn(conditionDescriptors, type)) {
|
|
||||||
const conditionDescriptor = conditionDescriptors[type];
|
|
||||||
const operators = conditionDescriptor.operators;
|
|
||||||
for (const operatorName of Object.keys(operators)) {
|
|
||||||
const operatorDescriptor = operators[operatorName];
|
|
||||||
$('<option>').val(operatorName).text(operatorDescriptor.name).appendTo(optionGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.operatorSelect.val(this.condition.operator);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInput() {
|
|
||||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
|
||||||
const {type, operator} = this.condition;
|
|
||||||
|
|
||||||
const objects = [];
|
|
||||||
let inputType = null;
|
|
||||||
if (hasOwn(conditionDescriptors, type)) {
|
|
||||||
const conditionDescriptor = conditionDescriptors[type];
|
|
||||||
objects.push(conditionDescriptor);
|
|
||||||
if (hasOwn(conditionDescriptor, 'type')) {
|
|
||||||
inputType = conditionDescriptor.type;
|
|
||||||
}
|
|
||||||
if (hasOwn(conditionDescriptor.operators, operator)) {
|
|
||||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
|
||||||
objects.push(operatorDescriptor);
|
|
||||||
if (hasOwn(operatorDescriptor, 'type')) {
|
|
||||||
inputType = operatorDescriptor.type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.input.empty();
|
|
||||||
if (inputType === 'select') {
|
|
||||||
this.inputInner = this.createSelectElement(objects);
|
|
||||||
} else if (inputType === 'keyMulti') {
|
|
||||||
this.inputInner = this.createInputKeyMultiElement(objects);
|
|
||||||
} else {
|
|
||||||
this.inputInner = this.createInputElement(objects);
|
|
||||||
}
|
|
||||||
this.inputInner.appendTo(this.input);
|
|
||||||
this.inputInner.on('change', this.onInputChanged.bind(this));
|
|
||||||
|
|
||||||
const {valid, value} = this.validateValue(this.condition.value);
|
|
||||||
this.inputInner.toggleClass('is-invalid', !valid);
|
|
||||||
this.inputInner.val(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
createInputElement(objects) {
|
|
||||||
const inputInner = ConditionsUI.instantiateTemplate('#condition-input-text-template');
|
|
||||||
|
|
||||||
const props = new Map([
|
|
||||||
['placeholder', ''],
|
|
||||||
['type', 'text']
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const object of objects) {
|
|
||||||
if (hasOwn(object, 'placeholder')) {
|
|
||||||
props.set('placeholder', object.placeholder);
|
|
||||||
}
|
|
||||||
if (object.type === 'number') {
|
|
||||||
props.set('type', 'number');
|
|
||||||
for (const prop of ['step', 'min', 'max']) {
|
|
||||||
if (hasOwn(object, prop)) {
|
|
||||||
props.set(prop, object[prop]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [prop, value] of props.entries()) {
|
|
||||||
inputInner.prop(prop, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputInner;
|
|
||||||
}
|
|
||||||
|
|
||||||
createInputKeyMultiElement(objects) {
|
|
||||||
const inputInner = this.createInputElement(objects);
|
|
||||||
|
|
||||||
inputInner.prop('readonly', true);
|
|
||||||
|
|
||||||
let values = [];
|
|
||||||
let keySeparator = ' + ';
|
|
||||||
for (const object of objects) {
|
|
||||||
if (hasOwn(object, 'values')) {
|
|
||||||
values = object.values;
|
|
||||||
}
|
|
||||||
if (hasOwn(object, 'keySeparator')) {
|
|
||||||
keySeparator = object.keySeparator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pressedKeyIndices = new Set();
|
|
||||||
|
|
||||||
const onKeyDown = ({originalEvent}) => {
|
|
||||||
const pressedKeyEventName = DocumentUtil.getKeyFromEvent(originalEvent);
|
|
||||||
if (pressedKeyEventName === 'Escape' || pressedKeyEventName === 'Backspace') {
|
|
||||||
pressedKeyIndices.clear();
|
|
||||||
inputInner.val('');
|
|
||||||
inputInner.change();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pressedModifiers = DocumentUtil.getActiveModifiers(originalEvent);
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/metaKey
|
|
||||||
// https://askubuntu.com/questions/567731/why-is-shift-alt-being-mapped-to-meta
|
|
||||||
// It works with mouse events on some platforms, so try to determine if metaKey is pressed
|
|
||||||
// hack; only works when Shift and Alt are not pressed
|
|
||||||
const isMetaKeyChrome = (
|
|
||||||
pressedKeyEventName === 'Meta' &&
|
|
||||||
getSetDifference(new Set(['shift', 'alt']), pressedModifiers).size !== 0
|
|
||||||
);
|
|
||||||
if (isMetaKeyChrome) {
|
|
||||||
pressedModifiers.add('meta');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const modifier of pressedModifiers) {
|
|
||||||
const foundIndex = values.findIndex(({optionValue}) => optionValue === modifier);
|
|
||||||
if (foundIndex !== -1) {
|
|
||||||
pressedKeyIndices.add(foundIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputValue = [...pressedKeyIndices].map((i) => values[i].name).join(keySeparator);
|
|
||||||
inputInner.val(inputValue);
|
|
||||||
inputInner.change();
|
|
||||||
};
|
|
||||||
|
|
||||||
inputInner.on('keydown', onKeyDown);
|
|
||||||
|
|
||||||
return inputInner;
|
|
||||||
}
|
|
||||||
|
|
||||||
createSelectElement(objects) {
|
|
||||||
const inputInner = ConditionsUI.instantiateTemplate('#condition-input-select-template');
|
|
||||||
|
|
||||||
const data = new Map([
|
|
||||||
['values', []],
|
|
||||||
['defaultValue', null]
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const object of objects) {
|
|
||||||
if (hasOwn(object, 'values')) {
|
|
||||||
data.set('values', object.values);
|
|
||||||
}
|
|
||||||
if (hasOwn(object, 'defaultValue')) {
|
|
||||||
data.set('defaultValue', this.isolate(object.defaultValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const {optionValue, name} of data.get('values')) {
|
|
||||||
const option = ConditionsUI.instantiateTemplate('#condition-input-option-template');
|
|
||||||
option.attr('value', optionValue);
|
|
||||||
option.text(name);
|
|
||||||
option.appendTo(inputInner);
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultValue = data.get('defaultValue');
|
|
||||||
if (defaultValue !== null) {
|
|
||||||
inputInner.val(this.isolate(defaultValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputInner;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateValue(value, isInput=false) {
|
|
||||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
|
||||||
let valid = true;
|
|
||||||
let inputTransformedValue = null;
|
|
||||||
try {
|
|
||||||
[value, inputTransformedValue] = conditionsNormalizeOptionValue(
|
|
||||||
conditionDescriptors,
|
|
||||||
this.condition.type,
|
|
||||||
this.condition.operator,
|
|
||||||
value,
|
|
||||||
isInput
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
return {valid, value, inputTransformedValue};
|
|
||||||
}
|
|
||||||
|
|
||||||
onInputChanged() {
|
|
||||||
const {valid, value, inputTransformedValue} = this.validateValue(this.inputInner.val(), true);
|
|
||||||
this.inputInner.toggleClass('is-invalid', !valid);
|
|
||||||
this.inputInner.val(value);
|
|
||||||
this.condition.value = inputTransformedValue !== null ? inputTransformedValue : value;
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
onConditionTypeChanged() {
|
|
||||||
const type = this.typeSelect.val();
|
|
||||||
const {operator, value} = this.parent.parent.createDefaultCondition(type);
|
|
||||||
this.condition.type = type;
|
|
||||||
this.condition.operator = operator;
|
|
||||||
this.condition.value = value;
|
|
||||||
this.save();
|
|
||||||
this.updateOperators();
|
|
||||||
this.updateInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
onConditionOperatorChanged() {
|
|
||||||
const type = this.condition.type;
|
|
||||||
const operator = this.operatorSelect.val();
|
|
||||||
const {value, fromOperator} = this.parent.parent.getOperatorDefaultValue(type, operator);
|
|
||||||
this.condition.operator = operator;
|
|
||||||
if (fromOperator) {
|
|
||||||
this.condition.value = value;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
this.updateInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemoveClicked() {
|
|
||||||
this.parent.remove(this);
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
};
|
|
686
ext/bg/js/settings/profile-conditions-ui.js
Normal file
686
ext/bg/js/settings/profile-conditions-ui.js
Normal file
@ -0,0 +1,686 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
* DocumentUtil
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ProfileConditionsUI {
|
||||||
|
constructor(settingsController) {
|
||||||
|
this._settingsController = settingsController;
|
||||||
|
this._keySeparator = '';
|
||||||
|
this._keyNames = new Map();
|
||||||
|
this._conditionGroupsContainer = null;
|
||||||
|
this._addConditionGroupButton = null;
|
||||||
|
this._children = [];
|
||||||
|
this._eventListeners = new EventListenerCollection();
|
||||||
|
this._defaultType = 'popupLevel';
|
||||||
|
this._descriptors = new Map([
|
||||||
|
[
|
||||||
|
'popupLevel',
|
||||||
|
{
|
||||||
|
displayName: 'Popup Level',
|
||||||
|
defaultOperator: 'equal',
|
||||||
|
operators: new Map([
|
||||||
|
['equal', {displayName: '=', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}],
|
||||||
|
['notEqual', {displayName: '\u2260', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}],
|
||||||
|
['lessThan', {displayName: '<', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}],
|
||||||
|
['greaterThan', {displayName: '>', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}],
|
||||||
|
['lessThanOrEqual', {displayName: '\u2264', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}],
|
||||||
|
['greaterThanOrEqual', {displayName: '\u2265', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'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: ''}]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
get settingsController() {
|
||||||
|
return this._settingsController;
|
||||||
|
}
|
||||||
|
|
||||||
|
get index() {
|
||||||
|
return this._settingsController.profileIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeyInfo(separator, keyNames) {
|
||||||
|
this._keySeparator = separator;
|
||||||
|
this._keyNames.clear();
|
||||||
|
for (const {value, name} of keyNames) {
|
||||||
|
this._keyNames.set(value, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(conditionGroups) {
|
||||||
|
this._conditionGroupsContainer = document.querySelector('#profile-condition-groups');
|
||||||
|
this._addConditionGroupButton = document.querySelector('#profile-add-condition-group');
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
this._conditionGroupsContainer = null;
|
||||||
|
this._addConditionGroupButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiateTemplate(templateSelector) {
|
||||||
|
const template = document.querySelector(templateSelector);
|
||||||
|
const content = document.importNode(template.content, true);
|
||||||
|
return content.firstChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescriptorTypes() {
|
||||||
|
const results = [];
|
||||||
|
for (const [name, {displayName}] of this._descriptors.entries()) {
|
||||||
|
results.push({name, displayName});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultType() {
|
||||||
|
return this._defaultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultOperator(type) {
|
||||||
|
const info = this._descriptors.get(type);
|
||||||
|
return (typeof info !== 'undefined' ? info.defaultOperator : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultCondition() {
|
||||||
|
const type = this.getDefaultType();
|
||||||
|
const operator = this.getDefaultOperator(type);
|
||||||
|
const {defaultValue: value} = this.getOperatorDetails(type, operator);
|
||||||
|
return {type, operator, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingsController.modifyGlobalSettings([{
|
||||||
|
action: 'splice',
|
||||||
|
path: this.getPath('conditionGroups'),
|
||||||
|
start: index,
|
||||||
|
deleteCount: 1,
|
||||||
|
items: []
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitValue(value) {
|
||||||
|
return value.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getModifierKeyStrings(modifiers) {
|
||||||
|
let value = '';
|
||||||
|
let displayValue = '';
|
||||||
|
let first = true;
|
||||||
|
for (const modifier of modifiers) {
|
||||||
|
let keyName = this._keyNames.get(modifier);
|
||||||
|
if (typeof keyName === 'undefined') { keyName = modifier; }
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
value += ', ';
|
||||||
|
displayValue += this._keySeparator;
|
||||||
|
}
|
||||||
|
value += modifier;
|
||||||
|
displayValue += keyName;
|
||||||
|
}
|
||||||
|
return {value, displayValue};
|
||||||
|
}
|
||||||
|
|
||||||
|
sortModifiers(modifiers) {
|
||||||
|
return modifiers.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPath(property) {
|
||||||
|
property = (typeof property === 'string' ? `.${property}` : '');
|
||||||
|
return `profiles[${this.index}]${property}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_onAddConditionGroupButtonClick() {
|
||||||
|
const conditionGroup = {
|
||||||
|
conditions: [this.getDefaultCondition()]
|
||||||
|
};
|
||||||
|
const index = this._children.length;
|
||||||
|
|
||||||
|
this._addConditionGroup(conditionGroup, index);
|
||||||
|
|
||||||
|
this.settingsController.modifyGlobalSettings([{
|
||||||
|
action: 'splice',
|
||||||
|
path: this.getPath('conditionGroups'),
|
||||||
|
start: index,
|
||||||
|
deleteCount: 0,
|
||||||
|
items: [conditionGroup]
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_addConditionGroup(conditionGroup, index) {
|
||||||
|
const child = new ProfileConditionGroupUI(this, index);
|
||||||
|
child.prepare(conditionGroup);
|
||||||
|
this._children.push(child);
|
||||||
|
this._conditionGroupsContainer.appendChild(child.node);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOperatorDetails(type, operator) {
|
||||||
|
const info = this._descriptors.get(type);
|
||||||
|
return (typeof info !== 'undefined' ? info.operators.get(operator) : void 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateInteger(value) {
|
||||||
|
const number = Number.parseFloat(value);
|
||||||
|
return Number.isFinite(number) && Math.floor(number) === number;
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateDomains(value) {
|
||||||
|
return this.splitValue(value).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateRegExp(value) {
|
||||||
|
try {
|
||||||
|
new RegExp(value, 'i');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalizeInteger(value) {
|
||||||
|
const number = Number.parseFloat(value);
|
||||||
|
return `${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalizeDomains(value) {
|
||||||
|
return this.splitValue(value).join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileConditionGroupUI {
|
||||||
|
constructor(parent, index) {
|
||||||
|
this._parent = parent;
|
||||||
|
this._index = index;
|
||||||
|
this._node = null;
|
||||||
|
this._conditionContainer = null;
|
||||||
|
this._addConditionButton = null;
|
||||||
|
this._children = [];
|
||||||
|
this._eventListeners = new EventListenerCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
get settingsController() {
|
||||||
|
return this._parent.settingsController;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parent() {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get index() {
|
||||||
|
return this._index;
|
||||||
|
}
|
||||||
|
|
||||||
|
set index(value) {
|
||||||
|
this._index = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get node() {
|
||||||
|
return this._node;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(conditionGroup) {
|
||||||
|
this._node = this._parent.instantiateTemplate('#condition-group-template');
|
||||||
|
this._conditionContainer = this._node.querySelector('.condition-list');
|
||||||
|
this._addConditionButton = this._node.querySelector('.condition-add');
|
||||||
|
|
||||||
|
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;
|
||||||
|
this._node = null;
|
||||||
|
this._conditionContainer = null;
|
||||||
|
this._addConditionButton = null;
|
||||||
|
|
||||||
|
if (node.parentNode !== null) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingsController.modifyGlobalSettings([{
|
||||||
|
action: 'splice',
|
||||||
|
path: this.getPath('conditions'),
|
||||||
|
start: index,
|
||||||
|
deleteCount: 1,
|
||||||
|
items: []
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (this._children.length === 0) {
|
||||||
|
this._parent.removeConditionGroup(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPath(property) {
|
||||||
|
property = (typeof property === 'string' ? `.${property}` : '');
|
||||||
|
return this._parent.getPath(`conditionGroups[${this._index}]${property}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_onAddConditionButtonClick() {
|
||||||
|
const condition = this._parent.getDefaultCondition();
|
||||||
|
const index = this._children.length;
|
||||||
|
|
||||||
|
this._addCondition(condition, index);
|
||||||
|
|
||||||
|
this.settingsController.modifyGlobalSettings([{
|
||||||
|
action: 'splice',
|
||||||
|
path: this.getPath('conditions'),
|
||||||
|
start: index,
|
||||||
|
deleteCount: 0,
|
||||||
|
items: [condition]
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 {
|
||||||
|
constructor(parent, index) {
|
||||||
|
this._parent = parent;
|
||||||
|
this._index = index;
|
||||||
|
this._node = null;
|
||||||
|
this._typeInput = null;
|
||||||
|
this._operatorInput = null;
|
||||||
|
this._valueInputContainer = null;
|
||||||
|
this._removeButton = null;
|
||||||
|
this._value = '';
|
||||||
|
this._eventListeners = new EventListenerCollection();
|
||||||
|
this._inputEventListeners = new EventListenerCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
get settingsController() {
|
||||||
|
return this._parent.parent.settingsController;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parent() {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get index() {
|
||||||
|
return this._index;
|
||||||
|
}
|
||||||
|
|
||||||
|
set index(value) {
|
||||||
|
this._index = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get node() {
|
||||||
|
return this._node;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(condition) {
|
||||||
|
const {type, operator, value} = condition;
|
||||||
|
|
||||||
|
this._node = this._parent.parent.instantiateTemplate('#condition-template');
|
||||||
|
this._typeInput = this._node.querySelector('.condition-type');
|
||||||
|
this._typeOptionContainer = this._typeInput.querySelector('optgroup');
|
||||||
|
this._operatorInput = this._node.querySelector('.condition-operator');
|
||||||
|
this._operatorOptionContainer = this._operatorInput.querySelector('optgroup');
|
||||||
|
this._valueInput = this._node.querySelector('.condition-input-inner');
|
||||||
|
this._removeButton = this._node.querySelector('.condition-remove');
|
||||||
|
|
||||||
|
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._removeButton, 'click', this._onRemoveButtonClick.bind(this), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this._eventListeners.removeAllEventListeners();
|
||||||
|
this._value = '';
|
||||||
|
|
||||||
|
if (this._node === null) { return; }
|
||||||
|
|
||||||
|
const node = this._node;
|
||||||
|
this._node = null;
|
||||||
|
this._typeInput = null;
|
||||||
|
this._operatorInput = null;
|
||||||
|
this._valueInputContainer = null;
|
||||||
|
this._removeButton = null;
|
||||||
|
|
||||||
|
if (node.parentNode !== null) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPath(property) {
|
||||||
|
property = (typeof property === 'string' ? `.${property}` : '');
|
||||||
|
return this._parent.getPath(`conditions[${this._index}]${property}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_onTypeChange(e) {
|
||||||
|
const type = e.currentTarget.value;
|
||||||
|
const operators = this._getDescriptorOperators(type);
|
||||||
|
const operator = operators.length > 0 ? operators[0].name : '';
|
||||||
|
const operatorDetails = this._getOperatorDetails(type, operator);
|
||||||
|
this._updateSelect(this._operatorInput, this._operatorOptionContainer, operators, operator);
|
||||||
|
this._updateValueInput(operatorDetails.defaultValue, operatorDetails);
|
||||||
|
this.settingsController.setGlobalSetting(this.getPath('type'), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onOperatorChange(e) {
|
||||||
|
const type = this._typeInput.value;
|
||||||
|
const operator = e.currentTarget.value;
|
||||||
|
const operatorDetails = this._getOperatorDetails(type, operator);
|
||||||
|
if (operatorDetails.resetDefaultOnChange) {
|
||||||
|
const okay = this._updateValueInput(operatorDetails.defaultValue, operatorDetails);
|
||||||
|
if (okay) {
|
||||||
|
this.settingsController.setGlobalSetting(this.getPath('operator'), operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onValueInputChange({validate, normalize}, e) {
|
||||||
|
const node = 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;
|
||||||
|
this.settingsController.setGlobalSetting(this.getPath('value'), normalizedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onModifierKeyDown({validate, normalize}, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const node = e.currentTarget;
|
||||||
|
|
||||||
|
let modifiers;
|
||||||
|
const key = DocumentUtil.getKeyFromEvent(e);
|
||||||
|
switch (key) {
|
||||||
|
case 'Escape':
|
||||||
|
case 'Backspace':
|
||||||
|
modifiers = [];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
modifiers = this._getModifiers(e);
|
||||||
|
const currentModifier = this._splitValue(this._value);
|
||||||
|
for (const modifier of currentModifier) {
|
||||||
|
modifiers.add(modifier);
|
||||||
|
}
|
||||||
|
modifiers = [...modifiers];
|
||||||
|
modifiers = this._sortModifiers(modifiers);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {value, displayValue} = this._getModifierKeyStrings(modifiers);
|
||||||
|
node.value = displayValue;
|
||||||
|
const okay = this._validateValue(value, validate);
|
||||||
|
this._value = value;
|
||||||
|
if (okay) {
|
||||||
|
const normalizedValue = this._normalizeValue(value, normalize);
|
||||||
|
node.value = normalizedValue;
|
||||||
|
this.settingsController.setGlobalSetting(this.getPath('value'), normalizedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRemoveButtonClick() {
|
||||||
|
this._parent.removeCondition(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDescriptorTypes() {
|
||||||
|
return this._parent.parent.getDescriptorTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDescriptorOperators(type) {
|
||||||
|
return this._parent.parent.getDescriptorOperators(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOperatorDetails(type, operator) {
|
||||||
|
return this._parent.parent.getOperatorDetails(type, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getModifierKeyStrings(modifiers) {
|
||||||
|
return this._parent.parent.getModifierKeyStrings(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sortModifiers(modifiers) {
|
||||||
|
return this._parent.parent.sortModifiers(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
_splitValue(value) {
|
||||||
|
return this._parent.parent.splitValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTypes(type) {
|
||||||
|
const types = this._getDescriptorTypes();
|
||||||
|
this._updateSelect(this._typeInput, this._typeOptionContainer, types, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateOperators(type, operator) {
|
||||||
|
const operators = this._getDescriptorOperators(type);
|
||||||
|
this._updateSelect(this._operatorInput, this._operatorOptionContainer, operators, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateValueInput(value, {type, validate, normalize}) {
|
||||||
|
this._inputEventListeners.removeAllEventListeners();
|
||||||
|
|
||||||
|
const inputData = {validate, normalize};
|
||||||
|
const node = this._valueInput;
|
||||||
|
node.classList.remove('is-invalid');
|
||||||
|
this._value = value;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'integer':
|
||||||
|
{
|
||||||
|
node.type = 'number';
|
||||||
|
node.step = '1';
|
||||||
|
node.value = value;
|
||||||
|
this._inputEventListeners.addEventListener(node, 'change', this._onValueInputChange.bind(this, inputData), false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'modifierKeys':
|
||||||
|
{
|
||||||
|
const modifiers = this._splitValue(value);
|
||||||
|
const {displayValue} = this._getModifierKeyStrings(modifiers);
|
||||||
|
node.type = 'text';
|
||||||
|
node.removeAttribute('step');
|
||||||
|
node.value = displayValue;
|
||||||
|
this._inputEventListeners.addEventListener(node, 'keydown', this._onModifierKeyDown.bind(this, inputData), false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: // 'string'
|
||||||
|
{
|
||||||
|
node.type = 'text';
|
||||||
|
node.removeAttribute('step');
|
||||||
|
node.value = value;
|
||||||
|
this._inputEventListeners.addEventListener(node, 'change', this._onValueInputChange.bind(this, inputData), false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._validateValue(value, validate);
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateValue(value, validate) {
|
||||||
|
const okay = (validate === null || validate(value));
|
||||||
|
this._valueInput.classList.toggle('is-invalid', !okay);
|
||||||
|
return okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalizeValue(value, normalize) {
|
||||||
|
return (normalize !== null ? normalize(value) : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getModifiers(e) {
|
||||||
|
const modifiers = DocumentUtil.getActiveModifiers(e);
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/metaKey
|
||||||
|
// https://askubuntu.com/questions/567731/why-is-shift-alt-being-mapped-to-meta
|
||||||
|
// It works with mouse events on some platforms, so try to determine if metaKey is pressed.
|
||||||
|
// This is a hack and only works when both Shift and Alt are not pressed.
|
||||||
|
if (
|
||||||
|
!modifiers.has('meta') &&
|
||||||
|
DocumentUtil.getKeyFromEvent(e) === 'Meta' &&
|
||||||
|
!(
|
||||||
|
modifiers.size === 2 &&
|
||||||
|
modifiers.has('shift') &&
|
||||||
|
modifiers.has('alt')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
modifiers.add('meta');
|
||||||
|
}
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
}
|
@ -16,17 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* global
|
/* global
|
||||||
* ConditionsUI
|
* ProfileConditionsUI
|
||||||
* conditionsClearCaches
|
* api
|
||||||
* profileConditionsDescriptor
|
|
||||||
* profileConditionsDescriptorPromise
|
|
||||||
* utilBackgroundIsolate
|
* utilBackgroundIsolate
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ProfileController {
|
class ProfileController {
|
||||||
constructor(settingsController) {
|
constructor(settingsController) {
|
||||||
this._settingsController = settingsController;
|
this._settingsController = settingsController;
|
||||||
this._conditionsContainer = null;
|
this._profileConditionsUI = new ProfileConditionsUI(settingsController);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
@ -49,8 +47,11 @@ class ProfileController {
|
|||||||
// Private
|
// Private
|
||||||
|
|
||||||
async _onOptionsChanged() {
|
async _onOptionsChanged() {
|
||||||
|
const {modifiers} = await api.getEnvironmentInfo();
|
||||||
|
this._profileConditionsUI.setKeyInfo(modifiers.separator, modifiers.keys);
|
||||||
|
|
||||||
const optionsFull = await this._settingsController.getOptionsFullMutable();
|
const optionsFull = await this._settingsController.getOptionsFullMutable();
|
||||||
await this._formWrite(optionsFull);
|
this._formWrite(optionsFull);
|
||||||
}
|
}
|
||||||
|
|
||||||
_tryGetIntegerValue(selector, min, max) {
|
_tryGetIntegerValue(selector, min, max) {
|
||||||
@ -78,7 +79,7 @@ class ProfileController {
|
|||||||
profile.name = $('#profile-name').val();
|
profile.name = $('#profile-name').val();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _formWrite(optionsFull) {
|
_formWrite(optionsFull) {
|
||||||
const currentProfileIndex = this._settingsController.profileIndex;
|
const currentProfileIndex = this._settingsController.profileIndex;
|
||||||
const profile = optionsFull.profiles[currentProfileIndex];
|
const profile = optionsFull.profiles[currentProfileIndex];
|
||||||
|
|
||||||
@ -91,23 +92,17 @@ class ProfileController {
|
|||||||
|
|
||||||
$('#profile-name').val(profile.name);
|
$('#profile-name').val(profile.name);
|
||||||
|
|
||||||
if (this._conditionsContainer !== null) {
|
this._refreshProfileConditions(optionsFull);
|
||||||
this._conditionsContainer.cleanup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await profileConditionsDescriptorPromise;
|
_refreshProfileConditions(optionsFull) {
|
||||||
this._conditionsContainer = new ConditionsUI.Container(
|
this._profileConditionsUI.cleanup();
|
||||||
profileConditionsDescriptor,
|
|
||||||
'popupLevel',
|
const profileIndex = this._settingsController.profileIndex;
|
||||||
profile.conditionGroups,
|
if (profileIndex < 0 || profileIndex >= optionsFull.profiles.length) { return; }
|
||||||
$('#profile-condition-groups'),
|
|
||||||
$('#profile-add-condition-group')
|
const {conditionGroups} = optionsFull.profiles[profileIndex];
|
||||||
);
|
this._profileConditionsUI.prepare(conditionGroups);
|
||||||
this._conditionsContainer.save = () => {
|
|
||||||
this._settingsController.save();
|
|
||||||
conditionsClearCaches(profileConditionsDescriptor);
|
|
||||||
};
|
|
||||||
this._conditionsContainer.isolate = utilBackgroundIsolate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_populateSelect(select, profiles, currentValue, ignoreIndices) {
|
_populateSelect(select, profiles, currentValue, ignoreIndices) {
|
||||||
|
@ -112,23 +112,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template id="condition-group-template"><div class="condition-group">
|
||||||
|
<div class="condition-list"></div>
|
||||||
|
<div class="condition-group-options">
|
||||||
|
<button class="btn btn-default condition-add"><span class="glyphicon glyphicon-plus"></span></button>
|
||||||
|
</div>
|
||||||
|
<div class="condition-group-separator-label">OR</div>
|
||||||
|
</div></template>
|
||||||
<template id="condition-template"><div class="input-group condition">
|
<template id="condition-template"><div class="input-group condition">
|
||||||
<div class="input-group-addon condition-prefix"></div>
|
<div class="input-group-addon condition-prefix"></div>
|
||||||
<div class="input-group-btn"><select class="form-control btn btn-default condition-type"><optgroup label="Type"></optgroup></select></div>
|
<div class="input-group-btn"><select class="form-control btn btn-default condition-type"><optgroup label="Type"></optgroup></select></div>
|
||||||
<div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div>
|
<div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div>
|
||||||
<div class="condition-line-break"></div>
|
<div class="condition-line-break"></div>
|
||||||
<div class="condition-input"></div>
|
<div class="condition-input"><input type="text" class="form-control condition-input-inner"></div>
|
||||||
<div class="input-group-btn"><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div>
|
<div class="input-group-btn"><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div>
|
||||||
</div></template>
|
</div></template>
|
||||||
<template id="condition-group-separator-template"><div class="input-group">
|
|
||||||
<div class="condition-group-separator-label">OR</div>
|
|
||||||
</div></template>
|
|
||||||
<template id="condition-group-options-template"><div class="condition-group-options">
|
|
||||||
<button class="btn btn-default condition-add"><span class="glyphicon glyphicon-plus"></span></button>
|
|
||||||
</div></template>
|
|
||||||
<template id="condition-input-text-template"><input type="text" class="form-control condition-input-inner" /></template>
|
|
||||||
<template id="condition-input-select-template"><select class="form-control condition-input-inner"></select></template>
|
|
||||||
<template id="condition-input-option-template"><option></option></template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -1143,7 +1141,6 @@
|
|||||||
<script src="/bg/js/anki-note-builder.js"></script>
|
<script src="/bg/js/anki-note-builder.js"></script>
|
||||||
<script src="/bg/js/conditions.js"></script>
|
<script src="/bg/js/conditions.js"></script>
|
||||||
<script src="/bg/js/options.js"></script>
|
<script src="/bg/js/options.js"></script>
|
||||||
<script src="/bg/js/profile-conditions.js"></script>
|
|
||||||
<script src="/bg/js/util.js"></script>
|
<script src="/bg/js/util.js"></script>
|
||||||
<script src="/mixed/js/audio-system.js"></script>
|
<script src="/mixed/js/audio-system.js"></script>
|
||||||
<script src="/mixed/js/document-util.js"></script>
|
<script src="/mixed/js/document-util.js"></script>
|
||||||
@ -1153,11 +1150,11 @@
|
|||||||
<script src="/bg/js/settings/audio.js"></script>
|
<script src="/bg/js/settings/audio.js"></script>
|
||||||
<script src="/bg/js/settings/backup.js"></script>
|
<script src="/bg/js/settings/backup.js"></script>
|
||||||
<script src="/bg/js/settings/clipboard-popups-controller.js"></script>
|
<script src="/bg/js/settings/clipboard-popups-controller.js"></script>
|
||||||
<script src="/bg/js/settings/conditions-ui.js"></script>
|
|
||||||
<script src="/bg/js/settings/dictionaries.js"></script>
|
<script src="/bg/js/settings/dictionaries.js"></script>
|
||||||
<script src="/bg/js/settings/generic-setting-controller.js"></script>
|
<script src="/bg/js/settings/generic-setting-controller.js"></script>
|
||||||
<script src="/bg/js/settings/popup-preview.js"></script>
|
<script src="/bg/js/settings/popup-preview.js"></script>
|
||||||
<script src="/bg/js/settings/profiles.js"></script>
|
<script src="/bg/js/settings/profiles.js"></script>
|
||||||
|
<script src="/bg/js/settings/profile-conditions-ui.js"></script>
|
||||||
<script src="/bg/js/settings/settings-controller.js"></script>
|
<script src="/bg/js/settings/settings-controller.js"></script>
|
||||||
<script src="/bg/js/settings/storage.js"></script>
|
<script src="/bg/js/settings/storage.js"></script>
|
||||||
<script src="/mixed/js/dictionary-data-util.js"></script>
|
<script src="/mixed/js/dictionary-data-util.js"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user