diff options
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}; +        }); +    } +} |