diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-12-06 03:53:16 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-06 03:53:16 +0000 | 
| commit | bd5bc1a5db29903bc098995cd9262c4576bf76af (patch) | |
| tree | c9214189e0214480fcf6539ad1c6327aef6cbd1c /ext/js/pages/settings/generic-setting-controller.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
| parent | 23e6fb76319c9ed7c9bcdc3efba39bc5dd38f288 (diff) | |
Merge pull request #339 from toasted-nutbread/type-annotations
Type annotations
Diffstat (limited to 'ext/js/pages/settings/generic-setting-controller.js')
| -rw-r--r-- | ext/js/pages/settings/generic-setting-controller.js | 181 | 
1 files changed, 151 insertions, 30 deletions
diff --git a/ext/js/pages/settings/generic-setting-controller.js b/ext/js/pages/settings/generic-setting-controller.js index c4104874..8666614b 100644 --- a/ext/js/pages/settings/generic-setting-controller.js +++ b/ext/js/pages/settings/generic-setting-controller.js @@ -16,14 +16,20 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {deserializeError, isObject} from '../../core.js'; +import {ExtensionError} from '../../core/extension-error.js';  import {DocumentUtil} from '../../dom/document-util.js';  import {DOMDataBinder} from '../../dom/dom-data-binder.js';  export class GenericSettingController { +    /** +     * @param {import('./settings-controller.js').SettingsController} settingsController +     */      constructor(settingsController) { +        /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; +        /** @type {import('settings-modifications').OptionsScopeType} */          this._defaultScope = 'profile'; +        /** @type {DOMDataBinder<import('generic-setting-controller').ElementMetadata>} */          this._dataBinder = new DOMDataBinder({              selector: '[data-setting]',              createElementMetadata: this._createElementMetadata.bind(this), @@ -31,7 +37,8 @@ export class GenericSettingController {              getValues: this._getValues.bind(this),              setValues: this._setValues.bind(this)          }); -        this._transforms = new Map([ +        /** @type {Map<import('generic-setting-controller').TransformType, import('generic-setting-controller').TransformFunction>} */ +        this._transforms = new Map(/** @type {[key: import('generic-setting-controller').TransformType, value: import('generic-setting-controller').TransformFunction][]} */ ([              ['setAttribute', this._setAttribute.bind(this)],              ['setVisibility', this._setVisibility.bind(this)],              ['splitTags', this._splitTags.bind(this)], @@ -40,41 +47,49 @@ export class GenericSettingController {              ['toBoolean', this._toBoolean.bind(this)],              ['toString', this._toString.bind(this)],              ['conditionalConvert', this._conditionalConvert.bind(this)] -        ]); +        ]));      } +    /** */      async prepare() {          this._dataBinder.observe(document.body);          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));      } +    /** */      async refresh() {          await this._dataBinder.refresh();      }      // Private +    /** */      _onOptionsChanged() {          this._dataBinder.refresh();      } +    /** +     * @param {Element} element +     * @returns {import('generic-setting-controller').ElementMetadata|undefined} +     */      _createElementMetadata(element) { -        const {dataset: {setting: path, scope, transform: transformRaw}} = element; -        let transforms; -        if (typeof transformRaw === 'string') { -            transforms = JSON.parse(transformRaw); -            if (!Array.isArray(transforms)) { transforms = [transforms]; } -        } else { -            transforms = []; -        } +        if (!(element instanceof HTMLElement)) { return void 0; } +        const {setting: path, scope, transform: transformRaw} = element.dataset; +        if (typeof path !== 'string') { return void 0; } +        const scope2 = this._normalizeScope(scope);          return {              path, -            scope, -            transforms, +            scope: scope2 !== null ? scope2 : this._defaultScope, +            transforms: this._getTransformDataArray(transformRaw),              transformRaw          };      } +    /** +     * @param {import('generic-setting-controller').ElementMetadata} metadata1 +     * @param {import('generic-setting-controller').ElementMetadata} metadata2 +     * @returns {boolean} +     */      _compareElementMetadata(metadata1, metadata2) {          return (              metadata1.path === metadata2.path && @@ -83,45 +98,71 @@ export class GenericSettingController {          );      } +    /** +     * @param {import('dom-data-binder').GetValuesDetails<import('generic-setting-controller').ElementMetadata>[]} targets +     * @returns {Promise<import('dom-data-binder').TaskResult[]>} +     */      async _getValues(targets) {          const defaultScope = this._defaultScope; +        /** @type {import('settings-modifications').ScopedRead[]} */          const settingsTargets = [];          for (const {metadata: {path, scope}} of targets) { +            /** @type {import('settings-modifications').ScopedRead} */              const target = {                  path, -                scope: scope || defaultScope +                scope: typeof scope === 'string' ? scope : defaultScope, +                optionsContext: null              };              settingsTargets.push(target);          }          return this._transformResults(await this._settingsController.getSettings(settingsTargets), targets);      } +    /** +     * @param {import('dom-data-binder').SetValuesDetails<import('generic-setting-controller').ElementMetadata>[]} targets +     * @returns {Promise<import('dom-data-binder').TaskResult[]>} +     */      async _setValues(targets) {          const defaultScope = this._defaultScope; +        /** @type {import('settings-modifications').ScopedModification[]} */          const settingsTargets = [];          for (const {metadata: {path, scope, transforms}, value, element} of targets) {              const transformedValue = this._applyTransforms(value, transforms, 'pre', element); +            /** @type {import('settings-modifications').ScopedModification} */              const target = {                  path, -                scope: scope || defaultScope, +                scope: typeof scope === 'string' ? scope : defaultScope,                  action: 'set', -                value: transformedValue +                value: transformedValue, +                optionsContext: null              };              settingsTargets.push(target);          }          return this._transformResults(await this._settingsController.modifySettings(settingsTargets), targets);      } +    /** +     * @param {import('settings-controller').ModifyResult[]} values +     * @param {import('dom-data-binder').GetValuesDetails<import('generic-setting-controller').ElementMetadata>[]|import('dom-data-binder').SetValuesDetails<import('generic-setting-controller').ElementMetadata>[]} targets +     * @returns {import('dom-data-binder').TaskResult[]} +     */      _transformResults(values, targets) {          return values.map((value, i) => {              const error = value.error; -            if (error) { return deserializeError(error); } +            if (error) { return {error: ExtensionError.deserialize(error)}; }              const {metadata: {transforms}, element} = targets[i];              const result = this._applyTransforms(value.result, transforms, 'post', element);              return {result};          });      } +    /** +     * @param {unknown} value +     * @param {import('generic-setting-controller').TransformData[]} transforms +     * @param {import('generic-setting-controller').TransformStep} step +     * @param {Element} element +     * @returns {unknown} +     */      _applyTransforms(value, transforms, step, element) {          for (const transform of transforms) {              const transformStep = transform.step; @@ -135,6 +176,11 @@ export class GenericSettingController {          return value;      } +    /** +     * @param {?Node} node +     * @param {number} ancestorDistance +     * @returns {?Node} +     */      _getAncestor(node, ancestorDistance) {          if (ancestorDistance < 0) {              return document.documentElement; @@ -145,6 +191,12 @@ export class GenericSettingController {          return node;      } +    /** +     * @param {?Node} node +     * @param {number|undefined} ancestorDistance +     * @param {string|undefined} selector +     * @returns {?Node} +     */      _getRelativeElement(node, ancestorDistance, selector) {          const selectorRoot = (              typeof ancestorDistance === 'number' ? @@ -154,12 +206,17 @@ export class GenericSettingController {          if (selectorRoot === null) { return null; }          return ( -            typeof selector === 'string' ? +            typeof selector === 'string' && (selectorRoot instanceof Element || selectorRoot instanceof Document) ?              selectorRoot.querySelector(selector) :              (selectorRoot === document ? document.documentElement : selectorRoot)          );      } +    /** +     * @param {import('generic-setting-controller').OperationData} operationData +     * @param {unknown} lhs +     * @returns {unknown} +     */      _evaluateSimpleOperation(operationData, lhs) {          const {op: operation, value: rhs} = operationData;          switch (operation) { @@ -167,18 +224,18 @@ export class GenericSettingController {              case '!!': return !!lhs;              case '===': return lhs === rhs;              case '!==': return lhs !== rhs; -            case '>=': return lhs >= rhs; -            case '<=': return lhs <= rhs; -            case '>': return lhs > rhs; -            case '<': return lhs < rhs; +            case '>=': return /** @type {number} */ (lhs) >= /** @type {number} */ (rhs); +            case '<=': return /** @type {number} */ (lhs) <= /** @type {number} */ (rhs); +            case '>': return /** @type {number} */ (lhs) > /** @type {number} */ (rhs); +            case '<': return /** @type {number} */ (lhs) < /** @type {number} */ (rhs);              case '&&': -                for (const operationData2 of rhs) { +                for (const operationData2 of /** @type {import('generic-setting-controller').OperationData[]} */ (rhs)) {                      const result = this._evaluateSimpleOperation(operationData2, lhs);                      if (!result) { return result; }                  }                  return true;              case '||': -                for (const operationData2 of rhs) { +                for (const operationData2 of /** @type {import('generic-setting-controller').OperationData[]} */ (rhs)) {                      const result = this._evaluateSimpleOperation(operationData2, lhs);                      if (result) { return result; }                  } @@ -188,48 +245,112 @@ export class GenericSettingController {          }      } +    /** +     * @param {string|undefined} value +     * @returns {?import('settings-modifications').OptionsScopeType} +     */ +    _normalizeScope(value) { +        switch (value) { +            case 'profile': +            case 'global': +                return value; +            default: +                return null; +        } +    } + +    /** +     * @param {string|undefined} transformRaw +     * @returns {import('generic-setting-controller').TransformData[]} +     */ +    _getTransformDataArray(transformRaw) { +        if (typeof transformRaw === 'string') { +            const transforms = JSON.parse(transformRaw); +            return Array.isArray(transforms) ? transforms : [transforms]; +        } +        return []; +    } +      // Transforms +    /** +     * @param {unknown} value +     * @param {import('generic-setting-controller').SetAttributeTransformData} data +     * @param {Element} element +     * @returns {unknown} +     */      _setAttribute(value, data, element) {          const {ancestorDistance, selector, attribute} = data;          const relativeElement = this._getRelativeElement(element, ancestorDistance, selector); -        if (relativeElement !== null) { +        if (relativeElement !== null && relativeElement instanceof Element) {              relativeElement.setAttribute(attribute, `${value}`);          }          return value;      } +    /** +     * @param {unknown} value +     * @param {import('generic-setting-controller').SetVisibilityTransformData} data +     * @param {Element} element +     * @returns {unknown} +     */      _setVisibility(value, data, element) {          const {ancestorDistance, selector, condition} = data;          const relativeElement = this._getRelativeElement(element, ancestorDistance, selector); -        if (relativeElement !== null) { +        if (relativeElement !== null && relativeElement instanceof HTMLElement) {              relativeElement.hidden = !this._evaluateSimpleOperation(condition, value);          }          return value;      } +    /** +     * @param {unknown} value +     * @returns {string[]} +     */      _splitTags(value) {          return `${value}`.split(/[,; ]+/).filter((v) => !!v);      } +    /** +     * @param {unknown} value +     * @returns {string} +     */      _joinTags(value) { -        return value.join(' '); +        return Array.isArray(value) ? value.join(' ') : '';      } +    /** +     * @param {unknown} value +     * @param {import('generic-setting-controller').ToNumberConstraintsTransformData} data +     * @returns {number} +     */      _toNumber(value, data) { -        let {constraints} = data; -        if (!isObject(constraints)) { constraints = {}; } -        return DocumentUtil.convertElementValueToNumber(value, constraints); +        /** @type {import('document-util').ToNumberConstraints} */ +        const constraints = typeof data.constraints === 'object' && data.constraints !== null ? data.constraints : {}; +        return typeof value === 'string' ? DocumentUtil.convertElementValueToNumber(value, constraints) : 0;      } +    /** +     * @param {string} value +     * @returns {boolean} +     */      _toBoolean(value) {          return (value === 'true');      } +    /** +     * @param {unknown} value +     * @returns {string} +     */      _toString(value) {          return `${value}`;      } +    /** +     * @param {unknown} value +     * @param {import('generic-setting-controller').ConditionalConvertTransformData} data +     * @returns {unknown} +     */      _conditionalConvert(value, data) {          const {cases} = data;          if (Array.isArray(cases)) {  |