diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-05-24 13:50:34 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-24 13:50:34 -0400 |
commit | 66e1185686f98f1cc4493298b5b1d4e0be7d826a (patch) | |
tree | 7d67a8bbbfd3d32235dcc5fbfe94c34e61132f3e /ext/bg/js | |
parent | c800444a0d4aa0177242da51e0f9716ebe882587 (diff) |
Settings binder (#542)
* Fix _modifySetting being async
* Return values for modifySettings's set and splice actions
* Add apiGetSettings
* Create a class which can accumulate tasks to run in bulk
* Create a class which binds input elements to customizable sources
* Create class which binds input elements to settings
* Add support for value transforms
* Remove redundant ObjectPropertyAccessor.getPathArray
* Fix not using correct types for input.min/max/step
* Fix wrong condition
* Use api object
Diffstat (limited to 'ext/bg/js')
-rw-r--r-- | ext/bg/js/backend.js | 90 | ||||
-rw-r--r-- | ext/bg/js/settings/dom-settings-binder.js | 122 |
2 files changed, 178 insertions, 34 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 8df4fd9d..90895737 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -119,7 +119,8 @@ class Backend { ['log', {async: false, contentScript: true, handler: this._onApiLog.bind(this)}], ['logIndicatorClear', {async: false, contentScript: true, handler: this._onApiLogIndicatorClear.bind(this)}], ['createActionPort', {async: false, contentScript: true, handler: this._onApiCreateActionPort.bind(this)}], - ['modifySettings', {async: true, contentScript: true, handler: this._onApiModifySettings.bind(this)}] + ['modifySettings', {async: true, contentScript: true, handler: this._onApiModifySettings.bind(this)}], + ['getSettings', {async: false, contentScript: true, handler: this._onApiGetSettings.bind(this)}] ]); this._messageHandlersWithProgress = new Map([ ['importDictionaryArchive', {async: true, contentScript: false, handler: this._onApiImportDictionaryArchive.bind(this)}], @@ -831,8 +832,8 @@ class Backend { const results = []; for (const target of targets) { try { - this._modifySetting(target); - results.push({result: true}); + const result = this._modifySetting(target); + results.push({result: utilIsolate(result)}); } catch (e) { results.push({error: errorToJson(e)}); } @@ -841,6 +842,19 @@ class Backend { return results; } + _onApiGetSettings({targets}) { + const results = []; + for (const target of targets) { + try { + const result = this._getSetting(target); + results.push({result: utilIsolate(result)}); + } catch (e) { + results.push({error: errorToJson(e)}); + } + } + return results; + } + // Command handlers _createActionListenerPort(port, sender, handlers) { @@ -1017,45 +1031,53 @@ class Backend { } } - async _modifySetting(target) { + _getSetting(target) { + const options = this._getModifySettingObject(target); + const accessor = new ObjectPropertyAccessor(options); + const {path} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + return accessor.get(ObjectPropertyAccessor.getPathArray(path)); + } + + _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; + { + const {path, value} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + const pathArray = ObjectPropertyAccessor.getPathArray(path); + accessor.set(pathArray, value); + return accessor.get(pathArray); + } case 'delete': - { - const {path} = target; - if (typeof path !== 'string') { throw new Error('Invalid path'); } - accessor.delete(ObjectPropertyAccessor.getPathArray(path)); - } - break; + { + const {path} = target; + if (typeof path !== 'string') { throw new Error('Invalid path'); } + accessor.delete(ObjectPropertyAccessor.getPathArray(path)); + return true; + } 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; + { + 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)); + return true; + } 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; + { + 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'); } + return array.splice(start, deleteCount, ...items); + } default: throw new Error(`Unknown action: ${action}`); } diff --git a/ext/bg/js/settings/dom-settings-binder.js b/ext/bg/js/settings/dom-settings-binder.js new file mode 100644 index 00000000..0441ec29 --- /dev/null +++ b/ext/bg/js/settings/dom-settings-binder.js @@ -0,0 +1,122 @@ +/* + * 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 + * DOMDataBinder + * api + * getOptionsContext + */ + +class DOMSettingsBinder { + constructor({getOptionsContext, transforms=null}) { + this._getOptionsContext = getOptionsContext; + this._defaultScope = 'profile'; + this._dataBinder = new DOMDataBinder({ + selector: '[data-setting]', + createElementMetadata: this._createElementMetadata.bind(this), + compareElementMetadata: this._compareElementMetadata.bind(this), + getValues: this._getValues.bind(this), + setValues: this._setValues.bind(this) + }); + this._transforms = new Map(transforms !== null ? transforms : []); + } + + observe(element) { + this._dataBinder.observe(element); + } + + disconnect() { + this._dataBinder.disconnect(); + } + + refresh() { + this._dataBinder.refresh(); + } + + // Private + + _createElementMetadata(element) { + return { + path: element.dataset.setting, + scope: element.dataset.scope, + transformPre: element.dataset.transformPre, + transformPost: element.dataset.transformPost + }; + } + + _compareElementMetadata(metadata1, metadata2) { + return ( + metadata1.path === metadata2.path && + metadata1.scope === metadata2.scope && + metadata1.transformPre === metadata2.transformPre && + metadata1.transformPost === metadata2.transformPost + ); + } + + async _getValues(targets) { + const settingsTargets = []; + for (const {metadata: {path, scope}} of targets) { + const target = { + path, + scope: scope || this._defaultScope + }; + if (target.scope === 'profile') { + target.optionsContext = this._getOptionsContext(); + } + settingsTargets.push(target); + } + return this._transformResults(await api.getSettings(settingsTargets), targets); + } + + async _setValues(targets) { + const settingsTargets = []; + for (const {metadata, value, element} of targets) { + const {path, scope, transformPre} = metadata; + const target = { + path, + scope: scope || this._defaultScope, + action: 'set', + value: this._transform(value, transformPre, metadata, element) + }; + if (target.scope === 'profile') { + target.optionsContext = this._getOptionsContext(); + } + settingsTargets.push(target); + } + return this._transformResults(await api.modifySettings(settingsTargets), targets); + } + + _transform(value, transform, metadata, element) { + if (typeof transform === 'string') { + const transformFunction = this._transforms.get(transform); + if (typeof transformFunction !== 'undefined') { + value = transformFunction(value, metadata, element); + } + } + return value; + } + + _transformResults(values, targets) { + return values.map((value, i) => { + const error = value.error; + if (error) { return jsonToError(error); } + const {metadata, element} = targets[i]; + const result = this._transform(value.result, metadata.transformPost, metadata, element); + return {result}; + }); + } +} |