From 1c767711bb553fa828596f95f8ed9e91a3e13b5d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 5 Sep 2019 19:21:50 -0400 Subject: Prevent infinite loops for corrupt options --- ext/bg/js/options.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index df95aae9..d903250e 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -305,14 +305,19 @@ function optionsVersion(options) { ]; optionsSetDefaults(options); - if (!options.hasOwnProperty('version')) { - options.version = fixups.length; + + let version = options.version; + if (typeof version !== 'number' || !Number.isFinite(version)) { + version = fixups.length; + } else { + version = Math.max(0, Math.floor(version)); } - while (options.version < fixups.length) { - fixups[options.version++](); + for (; version < fixups.length; ++version) { + fixups[version](); } + options.version = version; return options; } -- cgit v1.2.3 From ec110fa1b7299a947ea3eabc0e2c46094408c8ba Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 5 Sep 2019 20:35:04 -0400 Subject: Add some validation to options loading --- ext/bg/js/options.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index d903250e..976f8e55 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -323,10 +323,23 @@ function optionsVersion(options) { function optionsLoad() { return new Promise((resolve, reject) => { - chrome.storage.local.get(null, store => resolve(store.options)); + chrome.storage.local.get(['options'], store => { + const error = chrome.runtime.lastError; + if (error) { + reject(error); + } else { + resolve(store.options); + } + }); }).then(optionsStr => { - return optionsStr ? JSON.parse(optionsStr) : {}; - }).catch(error => { + if (typeof optionsStr === 'string') { + const options = JSON.parse(optionsStr); + if (typeof options === 'object' && options !== null && !Array.isArray(options)) { + return options; + } + } + return {}; + }).catch(() => { return {}; }).then(options => { return optionsVersion(options); @@ -334,7 +347,7 @@ function optionsLoad() { } function optionsSave(options) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { chrome.storage.local.set({options: JSON.stringify(options)}, resolve); }).then(() => { apiOptionsSet(options); -- cgit v1.2.3 From 5ddbb0373f93632109458725e0f18f1fc75ff643 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 5 Sep 2019 19:56:29 -0400 Subject: Add function to create default options --- ext/bg/js/options.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 976f8e55..be27f71f 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -183,8 +183,8 @@ function optionsFieldTemplates() { `.trim(); } -function optionsSetDefaults(options) { - const defaults = { +function optionsCreateDefaults() { + return { general: { enable: true, audioSource: 'jpod101', @@ -238,6 +238,10 @@ function optionsSetDefaults(options) { fieldTemplates: optionsFieldTemplates() } }; +} + +function optionsSetDefaults(options) { + const defaults = optionsCreateDefaults(); const combine = (target, source) => { for (const key in source) { -- cgit v1.2.3 From a74cdbff1dbfad48ae18cc101645b7a2b9ec8817 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 6 Sep 2019 18:21:20 -0400 Subject: Change update process --- ext/bg/js/options.js | 112 ++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 60 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index be27f71f..0e871567 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -17,6 +17,57 @@ */ +function optionsApplyUpdates(options, updates) { + const targetVersion = updates.length; + const currentVersion = options.version; + if (typeof currentVersion === 'number' && Number.isFinite(currentVersion)) { + for (let i = Math.max(0, Math.floor(currentVersion)); i < targetVersion; ++i) { + const update = updates[i]; + if (update !== null) { + update(options); + } + } + } + + options.version = targetVersion; + return options; +} + +const optionsVersionUpdates = [ + null, + null, + null, + null, + (options) => { + options.general.audioSource = options.general.audioPlayback ? 'jpod101' : 'disabled'; + }, + (options) => { + options.general.showGuide = false; + }, + (options) => { + options.scanning.modifier = options.scanning.requireShift ? 'shift' : 'none'; + }, + (options) => { + const fieldTemplatesDefault = profileCreateDefaultFieldTemplates(); + options.general.resultOutputMode = options.general.groupResults ? 'group' : 'split'; + options.anki.fieldTemplates = ( + (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) ? + `{{#if merge}}${fieldTemplatesDefault}{{else}}${options.anki.fieldTemplates}{{/if}}` : + fieldTemplatesDefault + ); + }, + (options) => { + if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { + options.anki.fieldTemplates = profileCreateDefaultFieldTemplates(); + } + }, + (options) => { + if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { + options.anki.fieldTemplates = profileCreateDefaultFieldTemplates(); + } + } +]; + function optionsFieldTemplates() { return ` {{#*inline "glossary-single"}} @@ -262,67 +313,8 @@ function optionsSetDefaults(options) { } function optionsVersion(options) { - const fixups = [ - () => {}, - () => {}, - () => {}, - () => {}, - () => { - if (options.general.audioPlayback) { - options.general.audioSource = 'jpod101'; - } else { - options.general.audioSource = 'disabled'; - } - }, - () => { - options.general.showGuide = false; - }, - () => { - if (options.scanning.requireShift) { - options.scanning.modifier = 'shift'; - } else { - options.scanning.modifier = 'none'; - } - }, - () => { - if (options.general.groupResults) { - options.general.resultOutputMode = 'group'; - } else { - options.general.resultOutputMode = 'split'; - } - if (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) { - options.anki.fieldTemplates = `{{#if merge}}${optionsFieldTemplates()}{{else}}${options.anki.fieldTemplates}{{/if}}`; - } else { - options.anki.fieldTemplates = optionsFieldTemplates(); - } - }, - () => { - if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { - options.anki.fieldTemplates = optionsFieldTemplates(); - } - }, - () => { - if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { - options.anki.fieldTemplates = optionsFieldTemplates(); - } - } - ]; - optionsSetDefaults(options); - - let version = options.version; - if (typeof version !== 'number' || !Number.isFinite(version)) { - version = fixups.length; - } else { - version = Math.max(0, Math.floor(version)); - } - - for (; version < fixups.length; ++version) { - fixups[version](); - } - - options.version = version; - return options; + return optionsApplyUpdates(options, optionsVersionUpdates); } function optionsLoad() { -- cgit v1.2.3 From eb98dfb1a86d42a0ecfe54d8eb978c47aa1c0f8b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 6 Sep 2019 21:23:00 -0400 Subject: Simplify logic for how option updates are propagated --- ext/bg/js/api.js | 4 ---- ext/bg/js/backend.js | 13 +++++-------- ext/bg/js/options.js | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index ff54ae81..9839aef5 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -17,10 +17,6 @@ */ -async function apiOptionsSet(options) { - utilBackend().onOptionsUpdated(options); -} - function apiOptionsGetSync() { return utilBackend().options; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index f05ae9e6..b3e737da 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -28,7 +28,7 @@ class Backend { async prepare() { await this.translator.prepare(); - await apiOptionsSet(await optionsLoad()); + this.onOptionsUpdated(await optionsLoad()); if (chrome.commands !== null && typeof chrome.commands === 'object') { chrome.commands.onCommand.addListener(this.onCommand.bind(this)); @@ -41,7 +41,8 @@ class Backend { } onOptionsUpdated(options) { - this.options = utilIsolate(options); + options = utilIsolate(options); + this.options = options; if (!options.general.enable) { this.setExtensionBadgeBackgroundColor('#555555'); @@ -53,11 +54,7 @@ class Backend { this.setExtensionBadgeText(''); } - if (options.anki.enable) { - this.anki = new AnkiConnect(options.anki.server); - } else { - this.anki = new AnkiNull(); - } + this.anki = options.anki.enable ? new AnkiConnect(options.anki.server) : new AnkiNull(); const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, tabs => { @@ -144,7 +141,7 @@ class Backend { chrome.browserAction.setBadgeBackgroundColor({color}); } } - + setExtensionBadgeText(text) { if (typeof chrome.browserAction.setBadgeText === 'function') { chrome.browserAction.setBadgeText({text}); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 0e871567..69c662e6 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -346,6 +346,6 @@ function optionsSave(options) { return new Promise((resolve) => { chrome.storage.local.set({options: JSON.stringify(options)}, resolve); }).then(() => { - apiOptionsSet(options); + utilBackend().onOptionsUpdated(options); }); } -- cgit v1.2.3 From 05ce350792fd60c1721bff4d0fb971e2bec24818 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Sep 2019 16:15:18 -0400 Subject: Use apiOptionsSave instead of optionsSave --- ext/bg/js/api.js | 9 ++++++++- ext/bg/js/backend.js | 7 +++++++ ext/bg/js/options.js | 13 +++++++++---- ext/bg/js/settings.js | 10 +++++----- 4 files changed, 29 insertions(+), 10 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 53e25348..a50353c1 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -21,6 +21,13 @@ function apiOptionsGet(optionsContext) { return utilBackend().getOptions(optionsContext); } +async function apiOptionsSave() { + const backend = utilBackend(); + const options = await backend.getFullOptions(); + await optionsSave(options); + backend.onOptionsUpdated(options); +} + async function apiTermsFind(text, optionsContext) { const options = await apiOptionsGet(optionsContext); const translator = utilBackend().translator; @@ -132,7 +139,7 @@ async function apiCommandExec(command) { const optionsContext = {depth: 0}; const options = await apiOptionsGet(optionsContext); options.general.enable = !options.general.enable; - await optionsSave(options); + await apiOptionsSave(); } }; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6dcf8e4d..1f00f788 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -150,6 +150,13 @@ class Backend { this.anki = options.anki.enable ? new AnkiConnect(options.anki.server) : new AnkiNull(); } + async getFullOptions() { + if (this.isPreparedPromise !== null) { + await this.isPreparedPromise; + } + return this.options; + } + async getOptions(optionsContext) { if (this.isPreparedPromise !== null) { await this.isPreparedPromise; diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 69c662e6..ea8f56d5 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -343,9 +343,14 @@ function optionsLoad() { } function optionsSave(options) { - return new Promise((resolve) => { - chrome.storage.local.set({options: JSON.stringify(options)}, resolve); - }).then(() => { - utilBackend().onOptionsUpdated(options); + return new Promise((resolve, reject) => { + chrome.storage.local.set({options: JSON.stringify(options)}, () => { + const error = chrome.runtime.lastError; + if (error) { + reject(error); + } else { + resolve(); + } + }); }); } diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 1c9198dd..e5786804 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -148,7 +148,7 @@ async function onFormOptionsChanged(e) { const optionsAnkiServerOld = options.anki.server; await formRead(options); - await optionsSave(options); + await apiOptionsSave(); formUpdateVisibility(options); try { @@ -385,7 +385,7 @@ async function onDictionaryPurge(e) { const options = await apiOptionsGet(optionsContext); options.dictionaries = {}; options.general.mainDictionary = ''; - await optionsSave(options); + await apiOptionsSave(); await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); @@ -435,7 +435,7 @@ async function onDictionaryImport(e) { dictionaryErrorsShow(exceptions); } - await optionsSave(options); + await apiOptionsSave(); await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); @@ -579,7 +579,7 @@ async function onAnkiModelChanged(e) { const options = await apiOptionsGet(optionsContext); await formRead(options); options.anki[tabId].fields = {}; - await optionsSave(options); + await apiOptionsSave(); ankiSpinnerShow(true); await ankiFieldsPopulate(element, options); @@ -599,7 +599,7 @@ async function onAnkiFieldTemplatesReset(e) { const fieldTemplates = optionsFieldTemplates(); options.anki.fieldTemplates = fieldTemplates; $('#field-templates').val(fieldTemplates); - await optionsSave(options); + await apiOptionsSave(); } catch (e) { ankiErrorShow(e); } -- cgit v1.2.3 From c4e6d7e3d18bc87a9e682349bd96fc35d7815bbc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Sep 2019 17:35:33 -0400 Subject: Add utility method for checking if a value is a standard object --- ext/bg/js/options.js | 2 +- ext/bg/js/util.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index ea8f56d5..d093d0b4 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -330,7 +330,7 @@ function optionsLoad() { }).then(optionsStr => { if (typeof optionsStr === 'string') { const options = JSON.parse(optionsStr); - if (typeof options === 'object' && options !== null && !Array.isArray(options)) { + if (utilIsObject(options)) { return options; } } diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 3dc7c900..79229229 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -104,3 +104,7 @@ function utilReadFile(file) { reader.readAsBinaryString(file); }); } + +function utilIsObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} -- cgit v1.2.3 From c38c7fbda1e2e7b320a9c5e564d3548197c6a236 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 11 Sep 2019 19:56:13 -0400 Subject: Replace profileCreateDefaultFieldTemplates with optionsFieldTemplates --- ext/bg/js/options.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 69c662e6..8b76d4c7 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -48,7 +48,7 @@ const optionsVersionUpdates = [ options.scanning.modifier = options.scanning.requireShift ? 'shift' : 'none'; }, (options) => { - const fieldTemplatesDefault = profileCreateDefaultFieldTemplates(); + const fieldTemplatesDefault = optionsFieldTemplates(); options.general.resultOutputMode = options.general.groupResults ? 'group' : 'split'; options.anki.fieldTemplates = ( (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) ? @@ -58,12 +58,12 @@ const optionsVersionUpdates = [ }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { - options.anki.fieldTemplates = profileCreateDefaultFieldTemplates(); + options.anki.fieldTemplates = optionsFieldTemplates(); } }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { - options.anki.fieldTemplates = profileCreateDefaultFieldTemplates(); + options.anki.fieldTemplates = optionsFieldTemplates(); } } ]; -- cgit v1.2.3 From c8171f5ec7612f0ba147d7e0887cd8c30a527827 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Sep 2019 19:50:58 -0400 Subject: Add preliminary support for profiles --- ext/bg/js/backend.js | 10 +++++- ext/bg/js/options.js | 91 +++++++++++++++++++++++++++++++++++++++++++-------- ext/bg/js/settings.js | 2 +- 3 files changed, 87 insertions(+), 16 deletions(-) (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9a300d62..3839da39 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -165,7 +165,15 @@ class Backend { } getOptionsSync(optionsContext) { - return this.options; + return this.getProfileSync(optionsContext).options; + } + + getProfileSync(optionsContext) { + const profiles = this.options.profiles; + if (typeof optionsContext.index === 'number') { + return profiles[optionsContext.index]; + } + return this.options.profiles[this.options.profileCurrent]; } setExtensionBadgeBackgroundColor(color) { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 5f04ec31..3dce5221 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -17,7 +17,11 @@ */ -function optionsApplyUpdates(options, updates) { +/* + * Generic options functions + */ + +function optionsGenericApplyUpdates(options, updates) { const targetVersion = updates.length; const currentVersion = options.version; if (typeof currentVersion === 'number' && Number.isFinite(currentVersion)) { @@ -33,7 +37,12 @@ function optionsApplyUpdates(options, updates) { return options; } -const optionsVersionUpdates = [ + +/* + * Per-profile options + */ + +const profileOptionsVersionUpdates = [ null, null, null, @@ -48,7 +57,7 @@ const optionsVersionUpdates = [ options.scanning.modifier = options.scanning.requireShift ? 'shift' : 'none'; }, (options) => { - const fieldTemplatesDefault = optionsFieldTemplates(); + const fieldTemplatesDefault = profileOptionsGetDefaultFieldTemplates(); options.general.resultOutputMode = options.general.groupResults ? 'group' : 'split'; options.anki.fieldTemplates = ( (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) ? @@ -58,17 +67,17 @@ const optionsVersionUpdates = [ }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { - options.anki.fieldTemplates = optionsFieldTemplates(); + options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); } }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { - options.anki.fieldTemplates = optionsFieldTemplates(); + options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); } } ]; -function optionsFieldTemplates() { +function profileOptionsGetDefaultFieldTemplates() { return ` {{#*inline "glossary-single"}} {{~#unless brief~}} @@ -234,7 +243,7 @@ function optionsFieldTemplates() { `.trim(); } -function optionsCreateDefaults() { +function profileOptionsCreateDefaults() { return { general: { enable: true, @@ -286,13 +295,13 @@ function optionsCreateDefaults() { screenshot: {format: 'png', quality: 92}, terms: {deck: '', model: '', fields: {}}, kanji: {deck: '', model: '', fields: {}}, - fieldTemplates: optionsFieldTemplates() + fieldTemplates: profileOptionsGetDefaultFieldTemplates() } }; } -function optionsSetDefaults(options) { - const defaults = optionsCreateDefaults(); +function profileOptionsSetDefaults(options) { + const defaults = profileOptionsCreateDefaults(); const combine = (target, source) => { for (const key in source) { @@ -312,9 +321,59 @@ function optionsSetDefaults(options) { return options; } -function optionsVersion(options) { - optionsSetDefaults(options); - return optionsApplyUpdates(options, optionsVersionUpdates); +function profileOptionsUpdateVersion(options) { + profileOptionsSetDefaults(options); + return optionsGenericApplyUpdates(options, profileOptionsVersionUpdates); +} + + +/* + * Global options + */ + +const optionsVersionUpdates = []; + +function optionsUpdateVersion(options, defaultProfileOptions) { + // Ensure profiles is an array + if (!Array.isArray(options.profiles)) { + options.profiles = []; + } + + // Remove invalid + const profiles = options.profiles; + for (let i = profiles.length - 1; i >= 0; --i) { + if (!utilIsObject(profiles[i])) { + profiles.splice(i, 1); + } + } + + // Require at least one profile + if (profiles.length === 0) { + profiles.push({ + name: 'Default', + options: defaultProfileOptions + }); + } + + // Ensure profileCurrent is valid + const profileCurrent = options.profileCurrent; + if (!( + typeof profileCurrent === 'number' && + Number.isFinite(profileCurrent) && + Math.floor(profileCurrent) === profileCurrent && + profileCurrent >= 0 && + profileCurrent < profiles.length + )) { + options.profileCurrent = 0; + } + + // Update profile options + for (const profile of profiles) { + profile.options = profileOptionsUpdateVersion(profile.options); + } + + // Generic updates + return optionsGenericApplyUpdates(options, optionsVersionUpdates); } function optionsLoad() { @@ -338,7 +397,11 @@ function optionsLoad() { }).catch(() => { return {}; }).then(options => { - return optionsVersion(options); + return ( + Array.isArray(options.profiles) ? + optionsUpdateVersion(options, {}) : + optionsUpdateVersion({}, options) + ); }); } diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 3d581ba5..88929c49 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -643,7 +643,7 @@ async function onAnkiFieldTemplatesReset(e) { e.preventDefault(); const optionsContext = getOptionsContext(); const options = await apiOptionsGet(optionsContext); - const fieldTemplates = optionsFieldTemplates(); + const fieldTemplates = profileOptionsGetDefaultFieldTemplates(); options.anki.fieldTemplates = fieldTemplates; $('#field-templates').val(fieldTemplates); await settingsSaveOptions(); -- cgit v1.2.3 From 8c4fb28a300b84ce876c35c370bd43daf2e65228 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 9 Sep 2019 20:19:49 -0400 Subject: Add support creating profile usage conditions --- ext/bg/background.html | 2 + ext/bg/js/conditions-ui.js | 317 ++++++++++++++++++++++++++++++++++++++++ ext/bg/js/conditions.js | 117 +++++++++++++++ ext/bg/js/options.js | 22 ++- ext/bg/js/profile-conditions.js | 85 +++++++++++ ext/bg/js/settings-profiles.js | 14 ++ ext/bg/settings.html | 89 ++++++++++- 7 files changed, 644 insertions(+), 2 deletions(-) create mode 100644 ext/bg/js/conditions-ui.js create mode 100644 ext/bg/js/conditions.js create mode 100644 ext/bg/js/profile-conditions.js (limited to 'ext/bg/js/options.js') diff --git a/ext/bg/background.html b/ext/bg/background.html index 90a56024..3b37db87 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -16,11 +16,13 @@ + + diff --git a/ext/bg/js/conditions-ui.js b/ext/bg/js/conditions-ui.js new file mode 100644 index 00000000..9b161c95 --- /dev/null +++ b/ext/bg/js/conditions-ui.js @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +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 conditionGroups) { + this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup)); + } + + this.addButton.on('click', () => this.onAddConditionGroup()); + } + + cleanup() { + for (const child of this.children) { + child.cleanup(); + } + + this.addButton.off('click'); + this.container.empty(); + } + + save() { + // Override + } + + 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 = { + 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 (this.conditionDescriptors.hasOwnProperty(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 (this.conditionDescriptors.hasOwnProperty(type)) { + const conditionDescriptor = this.conditionDescriptors[type]; + if (conditionDescriptor.operators.hasOwnProperty(operator)) { + const operatorDescriptor = conditionDescriptor.operators[operator]; + if (operatorDescriptor.hasOwnProperty('defaultValue')) { + return {value: operatorDescriptor.defaultValue, fromOperator: true}; + } + } + if (conditionDescriptor.hasOwnProperty('defaultValue')) { + return {value: conditionDescriptor.defaultValue, fromOperator: false}; + } + } + return {fromOperator: false}; + } +}; + +ConditionsUI.ConditionGroup = class ConditionGroup { + constructor(parent, conditionGroup) { + this.parent = parent; + this.children = []; + this.conditionGroup = conditionGroup; + this.container = $('
').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 conditionGroup.conditions) { + this.children.push(new ConditionsUI.Condition(this, condition)); + } + + this.addButton.on('click', () => this.onAddCondition()); + } + + 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(); + } + + 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.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('input'); + 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.input.on('change', () => this.onInputChanged()); + this.typeSelect.on('change', () => this.onConditionTypeChanged()); + this.operatorSelect.on('change', () => this.onConditionOperatorChanged()); + this.removeButton.on('click', () => this.onRemoveClicked()); + } + + cleanup() { + this.input.off('change'); + this.typeSelect.off('change'); + this.operatorSelect.off('change'); + this.removeButton.off('click'); + this.container.remove(); + } + + save() { + this.parent.save(); + } + + 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]; + $('
@@ -563,9 +647,12 @@ + + + -- cgit v1.2.3