diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-05-06 19:32:28 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-06 19:32:28 -0400 |
commit | bb2d9501afc0e406b0dacf5675cd90985238be98 (patch) | |
tree | e6b3a32b93ea4a3485fcb0e91d95277b9c435b3c /ext/bg/js/backend.js | |
parent | 021ccb5ac3038f63d07ccc9575ee56480031a251 (diff) |
Add apiModifySettings (#501)
* Update getProfile/getProfileFromContext to store this.options in a variable
* Add useSchema parameter to options getter functions
* Add apiModifySettings
* Use apiModifySettings instead of apiOptionsSet
* Remove apiOptionsSet
* Fix incorrect deleteCount check
* Require explicit scope for options
* Throw on invalid scope
Diffstat (limited to 'ext/bg/js/backend.js')
-rw-r--r-- | ext/bg/js/backend.js | 138 |
1 files changed, 86 insertions, 52 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d454aa22..8677e04c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -26,6 +26,7 @@ * DictionaryImporter * JsonSchema * Mecab + * ObjectPropertyAccessor * Translator * conditionsTestValue * dictTermsSort @@ -84,7 +85,6 @@ class Backend { ['optionsSchemaGet', {handler: this._onApiOptionsSchemaGet.bind(this), async: false}], ['optionsGet', {handler: this._onApiOptionsGet.bind(this), async: false}], ['optionsGetFull', {handler: this._onApiOptionsGetFull.bind(this), async: false}], - ['optionsSet', {handler: this._onApiOptionsSet.bind(this), async: true}], ['optionsSave', {handler: this._onApiOptionsSave.bind(this), async: true}], ['kanjiFind', {handler: this._onApiKanjiFind.bind(this), async: true}], ['termsFind', {handler: this._onApiTermsFind.bind(this), async: true}], @@ -115,7 +115,8 @@ class Backend { ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}], ['log', {handler: this._onApiLog.bind(this), async: false}], ['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}], - ['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}] + ['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}], + ['modifySettings', {handler: this._onApiModifySettings.bind(this), async: true}] ]); this._messageHandlersWithProgress = new Map([ ['importDictionaryArchive', {handler: this._onApiImportDictionaryArchive.bind(this), async: true}], @@ -258,8 +259,9 @@ class Backend { return this.optionsSchema; } - getFullOptions() { - return this.options; + getFullOptions(useSchema=false) { + const options = this.options; + return useSchema ? JsonSchema.createProxy(options, this.optionsSchema) : options; } setFullOptions(options) { @@ -271,21 +273,22 @@ class Backend { } } - getOptions(optionsContext) { - return this.getProfile(optionsContext).options; + getOptions(optionsContext, useSchema=false) { + return this.getProfile(optionsContext, useSchema).options; } - getProfile(optionsContext) { - const profiles = this.options.profiles; + getProfile(optionsContext, useSchema=false) { + const options = this.getFullOptions(useSchema); + const profiles = options.profiles; if (typeof optionsContext.index === 'number') { return profiles[optionsContext.index]; } - const profile = this.getProfileFromContext(optionsContext); - return profile !== null ? profile : this.options.profiles[this.options.profileCurrent]; + const profile = this.getProfileFromContext(options, optionsContext); + return profile !== null ? profile : options.profiles[options.profileCurrent]; } - getProfileFromContext(optionsContext) { - for (const profile of this.options.profiles) { + getProfileFromContext(options, optionsContext) { + for (const profile of options.profiles) { const conditionGroups = profile.conditionGroups; if (conditionGroups.length > 0 && Backend.testConditionGroups(conditionGroups, optionsContext)) { return profile; @@ -413,46 +416,6 @@ class Backend { return this.getFullOptions(); } - async _onApiOptionsSet({changedOptions, optionsContext, source}) { - const options = this.getOptions(optionsContext); - - function getValuePaths(obj) { - const valuePaths = []; - const nodes = [{obj, path: []}]; - while (nodes.length > 0) { - const node = nodes.pop(); - for (const key of Object.keys(node.obj)) { - const path = node.path.concat(key); - const obj2 = node.obj[key]; - if (obj2 !== null && typeof obj2 === 'object') { - nodes.unshift({obj: obj2, path}); - } else { - valuePaths.push([obj2, path]); - } - } - } - return valuePaths; - } - - function modifyOption(path, value) { - let pivot = options; - for (const key of path.slice(0, -1)) { - if (!hasOwn(pivot, key)) { - return false; - } - pivot = pivot[key]; - } - pivot[path[path.length - 1]] = value; - return true; - } - - for (const [value, path] of getValuePaths(changedOptions)) { - modifyOption(path, value); - } - - await this._onApiOptionsSave({source}); - } - async _onApiOptionsSave({source}) { const options = this.getFullOptions(); await optionsSave(options); @@ -829,6 +792,20 @@ class Backend { await this.database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress); } + async _onApiModifySettings({targets, source}) { + const results = []; + for (const target of targets) { + try { + this._modifySetting(target); + results.push({result: true}); + } catch (e) { + results.push({error: errorToJson(e)}); + } + } + await this._onApiOptionsSave({source}); + return results; + } + // Command handlers _createActionListenerPort(port, sender, handlers) { @@ -988,6 +965,63 @@ class Backend { // Utilities + _getModifySettingObject(target) { + const scope = target.scope; + switch (scope) { + case 'profile': + if (!isObject(target.optionsContext)) { throw new Error('Invalid optionsContext'); } + return this.getOptions(target.optionsContext, true); + case 'global': + return this.getFullOptions(true); + default: + throw new Error(`Invalid scope: ${scope}`); + } + } + + async _modifySetting(target) { + const options = this._getModifySettingObject(target); + const accessor = new ObjectPropertyAccessor(options); + const action = target.action; + switch (action) { + case 'set': + { + const {path, value} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + accessor.set(ObjectPropertyAccessor.getPathArray(path), value); + } + break; + case 'delete': + { + const {path} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + accessor.delete(ObjectPropertyAccessor.getPathArray(path)); + } + break; + case 'swap': + { + const {path1, path2} = target; + if (typeof path1 !== 'string') { throw new Error('Invalid path1'); } + if (typeof path2 !== 'string') { throw new Error('Invalid path2'); } + accessor.swap(ObjectPropertyAccessor.getPathArray(path1), ObjectPropertyAccessor.getPathArray(path2)); + } + break; + case 'splice': + { + const {path, start, deleteCount, items} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + if (typeof start !== 'number' || Math.floor(start) !== start) { throw new Error('Invalid start'); } + if (typeof deleteCount !== 'number' || Math.floor(deleteCount) !== deleteCount) { throw new Error('Invalid deleteCount'); } + if (!Array.isArray(items)) { throw new Error('Invalid items'); } + const array = accessor.get(ObjectPropertyAccessor.getPathArray(path)); + if (!Array.isArray(array)) { throw new Error('Invalid target type'); } + array.splice(start, deleteCount, ...items); + } + break; + default: + throw new Error(`Unknown action: ${action}`); + } + } + _validatePrivilegedMessageSender(sender) { const url = sender.url; if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) { |