diff options
Diffstat (limited to 'ext/js/core/dynamic-property.js')
-rw-r--r-- | ext/js/core/dynamic-property.js | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/ext/js/core/dynamic-property.js b/ext/js/core/dynamic-property.js new file mode 100644 index 00000000..5d8b4716 --- /dev/null +++ b/ext/js/core/dynamic-property.js @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 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/>. + */ + +import {EventDispatcher} from './event-dispatcher.js'; +import {generateId} from './utilities.js'; + +/** + * Class representing a generic value with an override stack. + * Changes can be observed by listening to the 'change' event. + * @template [T=unknown] + * @augments EventDispatcher<import('dynamic-property').Events<T>> + */ +export class DynamicProperty extends EventDispatcher { + /** + * Creates a new instance with the specified value. + * @param {T} value The value to assign. + */ + constructor(value) { + super(); + /** @type {T} */ + this._value = value; + /** @type {T} */ + this._defaultValue = value; + /** @type {{value: T, priority: number, token: string}[]} */ + this._overrides = []; + } + + /** + * Gets the default value for the property, which is assigned to the + * public value property when no overrides are present. + * @type {T} + */ + get defaultValue() { + return this._defaultValue; + } + + /** + * Assigns the default value for the property. If no overrides are present + * and if the value is different than the current default value, + * the 'change' event will be triggered. + * @param {T} value The value to assign. + */ + set defaultValue(value) { + this._defaultValue = value; + if (this._overrides.length === 0) { this._updateValue(); } + } + + /** + * Gets the current value for the property, taking any overrides into account. + * @type {T} + */ + get value() { + return this._value; + } + + /** + * Gets the number of overrides added to the property. + * @type {number} + */ + get overrideCount() { + return this._overrides.length; + } + + /** + * Adds an override value with the specified priority to the override stack. + * Values with higher priority will take precedence over those with lower. + * For tie breaks, the override value added first will take precedence. + * If the newly added override has the highest priority of all overrides + * and if the override value is different from the current value, + * the 'change' event will be fired. + * @param {T} value The override value to assign. + * @param {number} [priority] The priority value to use, as a number. + * @returns {import('core').TokenString} A string token which can be passed to the clearOverride function + * to remove the override. + */ + setOverride(value, priority = 0) { + const overridesCount = this._overrides.length; + let i = 0; + for (; i < overridesCount; ++i) { + if (priority > this._overrides[i].priority) { break; } + } + const token = generateId(16); + this._overrides.splice(i, 0, {value, priority, token}); + if (i === 0) { this._updateValue(); } + return token; + } + + /** + * Removes a specific override value. If the removed override + * had the highest priority, and the new value is different from + * the previous value, the 'change' event will be fired. + * @param {import('core').TokenString} token The token for the corresponding override which is to be removed. + * @returns {boolean} `true` if an override was returned, `false` otherwise. + */ + clearOverride(token) { + for (let i = 0, ii = this._overrides.length; i < ii; ++i) { + if (this._overrides[i].token === token) { + this._overrides.splice(i, 1); + if (i === 0) { this._updateValue(); } + return true; + } + } + return false; + } + + /** + * Updates the current value using the current overrides and default value. + * If the new value differs from the previous value, the 'change' event will be fired. + */ + _updateValue() { + const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue; + if (this._value === value) { return; } + this._value = value; + this.trigger('change', {value}); + } +} |