UNPKG

alchemymvc

Version:
1,794 lines (1,483 loc) 35.8 kB
const SettingNs = Function.getNamespace('Alchemy.Setting'); const VALUE = Symbol('value'); /** * The Base Setting Definition class. * These settings are stored in some kind of database. * They differ from `alchemy.settings`: those are hard-coded. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name The name of the setting in its group * @param {Object} config The settings of this definition * @param {Group} group The parent group */ const Base = Function.inherits('Alchemy.Base', 'Alchemy.Setting', function Base(name, config, group) { // The parent group this.group = group; // The path of the setting in its group this.name = name; // The complete setting id this.setting_id = (group?.setting_id ? group.setting_id + '.' : '') + name; // The description this.description = config?.description; // Does this setting require any permission to view? this.view_permission = config?.view_permission; // Does this setting require any permission to edit? this.edit_persmission = config?.edit_persmission; }); /** * Is this a group? * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @type {boolean} */ Base.setProperty('is_group', false); /** * The Base Setting Definition class * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name The name of the setting in its group * @param {Object} config The settings of this definition * @param {Group} group The parent group */ const Definition = Function.inherits('Alchemy.Setting.Base', function Definition(name, config, group) { Definition.super.call(this, name, config, group); // The type of the definition (string, number, etc) this.type = config.type; // Allowed values (makes it an enum) this.allowed_values = config.values || config.allowed_values; // Possible validation pattern this.validation_pattern = config.validation_pattern; // The default value this.default_value = config.default; // Is the default value an object? this.default_value_needs_cloning = this.default_value && typeof this.default_value == 'object'; // Show a description? this.show_description = config.show_description; // Is this setting locked? // (Meaning: can not be edited in the frontend) this.locked = config.locked; // Does this setting require a reboot? this.requires_restart = config.requires_restart; // The intended target of this setting. // This is mostly used to differentiate between 'user' or 'visitor' settings this.target = config.target; // The action to execute this.action = config.action; // Other settings it might require (needs to be truthy) this.requires = config.requires ? Array.cast(config.requires) : undefined; // Other settings it might depend on this.depends_on = config.depends_on ? Array.cast(config.depends_on) : undefined; }); /** * unDry an object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Object} obj * @param {boolean|string} cloned * * @return {Definition} */ Definition.setStatic(function unDry(obj, cloned) { let config = obj.config; let result = new Definition(config.name, config, null); result.setting_id = config.setting_id; return result; }); /** * Get all the dependency ids * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {string[]} */ Definition.setProperty(function all_dependencies() { let result = []; if (this.depends_on) { if (Array.isArray(this.depends_on)) { result.push(...this.depends_on); } else { result.push(this.depends_on); } } if (this.requires) { let requires = Array.cast(this.requires); for (let entry of requires) { if (result.indexOf(entry) == -1) { result.push(entry); } } } return result; }); /** * Get the schema of this setting * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Definition.enforceProperty(function schema(new_value, old_value) { if (new_value == null) { new_value = alchemy.createSchema(); let type = this.type; if (type == 'primitive') { type = 'string'; } if (type == 'percentage') { type = 'integer'; } if (type == 'path') { type = 'string'; } if (type == 'duration') { type = 'string'; } if (type == 'function') { type = 'string'; } if (!type) { type = 'string'; } new_value.addField('value', type, { description: this.description, allowed_values: this.allowed_values, validation_pattern: this.validation_pattern, default: this.default_value, }); } return new_value; }); /** * Cast the given value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {Mixed} */ Definition.setMethod(function cast(value) { if (value == null) { return value; } if (this.type == 'boolean') { if (value === 'false') { value = false; } else if (value === 'true') { value = true; } else { value = !!value; } } else if (this.type == 'primitive') { let nr = Number(value); if (!isNaN(nr)) { value = nr; } } else if (this.type == 'percentage' || this.type == 'integer') { value = parseInt(value); } else if (this.type == 'number') { value = Number(value); } return value; }); /** * Create the settings with default values * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {Alchemy.Setting.Value} */ Definition.setMethod(function generateValue() { let result = new SettingValue(this); result.setDefaultValue(this.default_value); return result; }); /** * Return the json-dry representation * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Definition.setMethod(function toDry() { let config = this.toJSON(); return { value: { config, // Always convert the schema to the client-side version. // This saves us a lot of serialization headaches. schema : JSON.clone(this.schema, 'toHawkejs'), } }; }); /** * Return the json representation * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Definition.setMethod(function toJSON() { let result = { name : this.name, type : this.type, setting_id : this.setting_id, allowed_values : this.allowed_values, validation_pattern : this.validation_pattern, default_value : this.default_value, show_description : this.show_description, target : this.target, view_permission : this.view_permission, edit_persmission : this.edit_persmission, requires_restart : this.requires_restart, locked : this.locked, }; return result; }); /** * Create an enum entry * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Definition.setMethod(function toEnumEntry() { let result = this.toJSON(); result.schema = JSON.clone(this.schema, 'toHawkejs'); return result; }); /** * Get the configuration for the editor * (Including the current value) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Value} root_value * @param {Conduit} editor_context * * @return {Object} */ Definition.setMethod(function getEditorConfiguration(root_value, editor_context) { let result = this.toEnumEntry(); let value = root_value ? root_value.getPath(this.setting_id) : null; if (value) { result.current_value = value.get(); result.current_value_is_default = value.has_default_value; } if (result.show_description !== false && this.description) { result.description = this.description; } if (this.edit_persmission && editor_context && !editor_context.hasPermission(this.edit_persmission)) { result.locked = true; } return result; }); /** * Create a configuration object (for storing in the database) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Mixed} new_value * * @return {Object} */ Definition.setMethod(function createConfigurationObject(new_value) { // @TODO: make sure the value is valid let result = { value : new_value, }; return result; }); /** * Can this setting by edited by the given context? * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Conduit} permission_context * * @return {boolean} */ Definition.setMethod(function canBeEditedBy(permission_context) { // Locked settings can never be edited if (this.locked) { return false; } // If no edit permission is required, it can be edited if (!this.edit_persmission) { return true; } // If no context is given, it can't be edited if (!permission_context) { return false; } return permission_context.hasPermission(this.edit_persmission); }); /** * Execute the action (if any is linked) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Value} value_instance */ Definition.setMethod(function executeAction(value_instance) { if (!this.action) { return; } return this.action.call(this, value_instance.get(), value_instance); }); /** * The Setting Group class * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name The name of the setting in its (parent) group * @param {Object} config The settings of this definition * @param {Group} group The parent group */ const Group = Function.inherits('Alchemy.Setting.Base', function Group(name, config, group) { if (!group) { if (config && config instanceof Group) { group = config; config = null; } } Group.super.call(this, name, config, group); // All the children this.children = new Map(); // Weak references to existing values this.weak_values = new Blast.Classes.WeakValueSet(); }); /** * unDry the group * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Object} obj * @param {boolean|string} cloned * * @return {Group} */ Group.setStatic(function unDry(obj, cloned) { let config = obj.config; let result = new Group(config.name, null, null); result.setting_id = config.setting_id; let children = obj.children, child; for (child of children) { if (!child) { continue; } result.children.set(child.name, child); child.group = result; } return result; }); /** * Is this a group? * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @type {boolean} */ Group.setProperty('is_group', true); /** * Return the json-dry representation * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Group.setMethod(function toDry() { let config = { name : this.name, setting_id : this.setting_id, description : this.description, view_permission : this.view_permission, edit_persmission : this.edit_persmission, }; let children = [...this.children.values()]; return { value: { config, children, } }; }); /** * Get a child * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name * * @return {Alchemy.Setting.Group|Alchemy.Setting.Definition} */ Group.setMethod(function get(name) { let pieces = name.split('.'), current = this, piece; if (pieces[0] == current.name) { pieces.shift(); } while (pieces.length) { piece = pieces.shift(); current = current.children.get(piece); if (!current) { return; } } return current; }); /** * Return the basic record for JSON * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name * * @return {Alchemy.Setting.Group} */ Group.setMethod(function createGroup(name) { let existing = this.get(name); if (existing) { throw new Error('Cannot create setting group "' + name + '", it already exists'); } let group = new Group(name, this); this.children.set(name, group); if (this.weak_values.size) { let group_value = group.generateValue(); for (let existing of this.weak_values) { this.setDefaultValue(existing, {[name]: group_value}); } } return group; }); /** * Add a setting * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} name * @param {Object} config * * @return {Alchemy.Setting.Definition} */ Group.setMethod(function addSetting(name, config) { let existing = this.get(name); if (existing) { throw new Error('Cannot create setting "' + name + '", it already exists'); } let setting = new Definition(name, config, this); this.children.set(name, setting); return setting; }); /** * Create the settings with default values * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Group.setMethod(function generateValue() { const result = new GroupValue(this); const object = {}; let definition, key; for ([key, definition] of this.children) { object[key] = definition.generateValue(); } this.setDefaultValue(result, object); this.weak_values.add(result); return result; }); /** * Set the default value if no value has been set yet * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Value} target * @param {Mixed} value */ Group.setMethod(function setDefaultValue(target, value) { if (!value || typeof value != 'object') { return; } if (value instanceof Value) { if (!value.is_group) { throw new Error('Cannot set default value of non-group value'); } value = value[VALUE]; } let object = this.assign(target[VALUE], value, true); target[VALUE] = object; return target; }); /** * Set the values silently * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Value} target * @param {Mixed} value */ Group.setMethod(function setValueSilently(target, value) { if (!value || typeof value != 'object') { return; } if (value instanceof Value) { if (!value.is_group) { throw new Error('Cannot set default value of non-group value'); } value = value[VALUE]; } let object = this.assign(target[VALUE], value, false, false); target[VALUE] = object; return target; }); /** * Set the value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Value} target * @param {Mixed} value */ Group.setMethod(function setValue(target, value) { if (!value || typeof value != 'object') { return; } if (value instanceof Value) { if (!value.is_group) { throw new Error('Cannot set default value of non-group value'); } value = value[VALUE]; } let object = this.assign(target[VALUE], value, false, true); target[VALUE] = object; return target; }); /** * Apply the given values to the given object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Object} target * @param {Object} values * @param {boolean} default_only * @param {boolean} do_actions */ Group.setMethod(function assign(target, values, default_only, do_actions = true) { if (!Object.isPlainObject(target)) { if (target.is_group) { target = target[VALUE]; } else { throw new Error('Cannot assign values to non-object'); } } if (!values) { return target; } if (!Object.isPlainObject(values)) { if (values.is_group) { values = values[VALUE]; } else { throw new Error('Cannot assign non-object values'); } } let current_value, source_value, definition, group, key; for (key in values) { definition = this.get(key); current_value = target[key]; source_value = values[key]; if (!definition) { if (source_value && (typeof source_value == 'object' && !(source_value instanceof SettingValue))) { definition = new Group(key, this); } else { definition = new Definition(key, {}, this); } } if (definition.is_group) { group = definition; } else { group = null; } // Make sure it's correct if (group) { if (!target[key] || typeof target[key] !== 'object') { target[key] = group.generateValue(); } group.assign(target[key], values[key], default_only, do_actions); continue; } if (!current_value) { current_value = definition.generateValue(); target[key] = current_value; } // Set the value by default if (default_only) { current_value.setDefaultValue(values[key]); } else if (do_actions) { current_value.setValue(values[key]); } else { current_value.setValueSilently(values[key]); } } return target; }); /** * Create a recursive flat map * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {boolean} add_groups * * @return {Map} */ Group.setMethod(function createFlatMap(add_groups = false) { return this._addToMap(new Map(), add_groups); }); /** * Add to the given map * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {boolean} add_groups * * @return {Map} */ Group.setMethod(function _addToMap(map, add_groups = false) { let definition, key, id; for ([key, definition] of this.children) { if (definition.is_group) { if (add_groups) { // @TODO } definition._addToMap(map, add_groups); continue; } id = definition.setting_id; map.set(id, definition.toEnumEntry()); } return map; }); /** * Create the backed map for use in enums * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {Alchemy.Map.Enum} */ Group.setMethod(function createEnumMap() { return new Classes.Alchemy.Map.Enum(() => { return this.createFlatMap(); }); }); /** * Get the configuration for the editor * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Value} root_value * @param {Conduit} editor_context * * @return {Object} */ Group.setMethod(function getEditorConfiguration(root_value, editor_context) { let result = { is_root : this.group == null, name : this.name, group_id : this.setting_id, settings : [], children : [], }; let definition, key; for ([key, definition] of this.children) { if (definition.view_permission) { if (editor_context) { if (!editor_context.hasPermission(definition.view_permission)) { continue; } } } if (definition.is_group) { result.children.push(definition.getEditorConfiguration(root_value, editor_context)); continue; } result.settings.push(definition.getEditorConfiguration(root_value, editor_context)); } return result; }); /** * The base Value class * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Base} definition The definition or group */ const Value = Function.inherits('Alchemy.Setting.Base', function Value(definition) { // The definition of this setting this.definition = definition; }); /** * Mark this as an "abstract" class * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ Value.makeAbstractClass(true); /** * Get the setting_id * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @type {string} */ Value.setProperty(function setting_id() { return this.definition.setting_id; }); /** * A group value instance * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Group} group The definition of the group */ const GroupValue = Function.inherits('Alchemy.Setting.Value', function GroupValue(group) { GroupValue.super.call(this, group); this.has_default_value = false; this[VALUE] = {}; }); /** * unDry an object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Object} obj * @param {boolean|string} cloned * * @return {GroupValue} */ GroupValue.setStatic(function unDry(obj, cloned) { let result = new GroupValue(obj.group); for (let key in obj.children) { let value = obj.children[key]; result[VALUE][key] = value; } return result; }); /** * Is this a group? * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @type {boolean} */ GroupValue.setProperty(function is_group() { return true; }); /** * Return the json-dry representation * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function toDry() { let definition = this.definition, children = this[VALUE]; return { value: { group : definition, children, } }; }); /** * Get this group as a simple object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function toObject() { let result = {}, entry, key; for (key in this[VALUE]) { entry = this[VALUE][key]; if (!entry) { continue; } result[key] = entry.toObject(); } return result; }); /** * Return the simple representation of this value. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function toSimple() { return this.toObject(); }); /** * Get the configuration for the editor * (Including the current value) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {*} editor_context * * @return {Object} */ GroupValue.setMethod(function getEditorConfiguration(editor_context) { return this.definition.getEditorConfiguration(this, editor_context); }); /** * Remove an entry * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function remove(name) { this[VALUE][name] = undefined; }); /** * Get the proxy representation of this group value. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function toProxyObject() { return new MagicGroupValue(this); }); /** * Set the default value if no value has been set yet * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function setDefaultValue(value) { this.definition.setDefaultValue(this, value); }); /** * Set the value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function setValue(value) { this.definition.setValue(this, value); }); /** * Set the value silently * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function setValueSilently(value) { if (Array.isArray(value)) { let entry; for (entry of value) { if (!entry.setting_id) { continue; } this.setPathSilently(entry.setting_id, entry.configuration.value); } return; } this.definition.setValueSilently(this, value); }); /** * Inject the given group value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function injectSubGroupValue(name, value) { this[VALUE][name] = value; }); /** * Get all the setting values with executable actions in order. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {Alchemy.Setting.Value[]} sorted_values */ GroupValue.setMethod(function getSortedValues() { let result = this.getFlattenedValues(); // Try to sort the values by their dependencies, // aiming to maintain the current order as much as possible. // This is done by sorting the values by their dependencies, // and then sorting the dependencies by their dependencies. // This is done recursively. result.sortTopological('setting_id', 'all_dependencies'); return result; }); /** * Add all the values to the given array * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {Alchemy.Setting.Value[]} */ GroupValue.setMethod(function getFlattenedValues() { let result = [], entry, key; for (key in this[VALUE]) { entry = this[VALUE][key]; if (!entry) { continue; } if (entry.is_group) { result.push(...entry.getFlattenedValues()); } else { result.push(entry); } } return result; }); /** * Get the value at the given path * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * * @return {Value} */ GroupValue.setMethod(function get(path) { if (typeof path == 'string') { path = path.split('.'); } if (path && this.definition.group == null && path[0] == this.definition.name) { path.shift(); } if (!path || path.length == 0) { return this; } if (path.length == 1) { return this[VALUE][path[0]]; } let current = this, piece; while (path.length) { piece = path.shift(); current = current.get(piece); if (!current) { return null; } } return current; }); /** * Set via a path (but silently). * Any non-existing group will be created. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * @param {Mixed} raw_value * * @return {Value} */ GroupValue.setMethod(function setPathSilently(path, raw_value) { return this._setPath(true, path, raw_value); }); /** * Set via a path. * Any non-existing group will be created. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * @param {Mixed} raw_value * * @return {Value} */ GroupValue.setMethod(function setPath(path, raw_value) { return this._setPath(false, path, raw_value); }); /** * Set via a path. * Any non-existing group will be created. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {boolean} silent * @param {string|Array} path * @param {Mixed} raw_value * * @return {Value} */ GroupValue.setMethod(function _setPath(silent, path, raw_value) { if (typeof path == 'string') { path = path.split('.'); } if (this.definition.group == null && path[0] == this.definition.name) { path.shift(); } let object = Object.setPath({}, path, raw_value); if (silent) { this.setValueSilently(object); } else { this.setValue(object); } return this.getPath(path); }); /** * Force a value at the given path * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * @param {Value} */ GroupValue.setMethod(function forceValueInstanceAtPath(path, value) { if (typeof path == 'string') { path = path.split('.'); } if (this.definition.group == null && path[0] == this.definition.name) { path.shift(); } let last = path.pop(); let current = this; while (path.length && current) { let next = path.shift(); current = current.get(next); } current[VALUE][last] = value; }); /** * Convert to a datasource array * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(function toDatasourceArray() { let result = [], flattened = this.getFlattenedValues(), entry; for (entry of flattened) { if (entry.has_default_value) { continue; } result.push({ setting_id : entry.setting_id, configuration : { value : entry.get(), } }); } return result; }); /** * A value instance of a setting * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Definition} definition The definition of the setting */ const SettingValue = Function.inherits('Alchemy.Setting.Value', function SettingValue(definition) { SettingValue.super.call(this, definition); // Is this the default value? // meaning: this value does not come from any manual override // (Groups do not have default values) this.has_default_value = true; // The actual value this[VALUE] = null; // How many times the action has been executed this.action_execution_count = 0; }); /** * unDry an object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Object} obj * @param {boolean|string} cloned * * @return {SettingValue} */ SettingValue.setStatic(function unDry(obj, cloned) { let result = new this(obj.definition); result[VALUE] = obj.value; result.has_default_value = obj.has_default_value; return result; }); /** * Get all the dependency ids * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {string[]} */ SettingValue.setProperty(function all_dependencies() { return this.definition.all_dependencies; }); /** * Return the json-dry representation * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function toDry() { return { value: { definition : this.definition, value : this[VALUE], has_default_value : this.has_default_value, } }; }); /** * Get this group as a simple object * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function toObject() { return this.get(); }); /** * Return the simple representation of this value. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function toSimple() { return this.get(); }); /** * Get the current value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function get(key) { let result = this[VALUE]; if (key != null) { result = result[key]; } return result; }); /** * Set the default value if no value has been set yet * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function setDefaultValue(value) { if (!this.has_default_value) { return; } if (value != null && typeof value == 'object') { if (value instanceof Value) { value = value[VALUE]; } if (value && typeof value == 'object') { value = JSON.clone(value); } } this[VALUE] = value; }); /** * Set the value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function setValueSilently(value) { if (value instanceof Value) { value = value[VALUE]; } // Even though the default value and this new value might be the same, // it is no longer considered to be the "default" value! this.has_default_value = false; value = this.definition.cast(value); this[VALUE] = value; }); /** * Test and set the given value * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function setValue(value) { this.setValueSilently(value); return this.executeAction(); }); /** * Get via a path * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * * @return {Value} */ Value.setMethod(function getPath(path) { if (typeof path == 'string') { path = path.split('.'); } let current = this, piece; while (path.length) { piece = path.shift(); current = current.get(piece); if (!current) { return null; } } return current; }); /** * Force a value at the given path * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string|Array} path * @param {Value} */ Value.setMethod(function forceValueInstanceAtPath(path, value) { throw new Error('Unable to perform on a simple Value instance'); }); if (Blast.isBrowser) { return; } /** * Custom Janeway representation (left side) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {string} */ Value.setMethod(Symbol.for('janeway_arg_left'), function janewayClassIdentifier() { return 'A.S.' + this.constructor.name; }); /** * Custom Janeway representation (right side) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @return {String} */ Value.setMethod(Symbol.for('janeway_arg_right'), function janewayInstanceInfo() { return this.definition.setting_id; }); /** * Perform all the actions of this group and its children * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ GroupValue.setMethod(async function performAllActions() { let sorted = this.getSortedValues(); for (let value of sorted) { let result = value.executeAction(); if (result) { await result; } } }); /** * Execute the action (if any is linked) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 */ SettingValue.setMethod(function executeAction() { this.action_execution_count++; return this.definition.executeAction(this); }); /** * A magic value getter * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {Alchemy.Setting.Value} value The value group */ const MagicGroupValue = Function.inherits('Magic', 'Alchemy.Setting', function MagicGroupValue(group_value) { if (!group_value) { throw new Error('Cannot create magic group value without group value'); } this[VALUE] = group_value; }); /** * The magic getter * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} key */ MagicGroupValue.setMethod(function __get(key) { let result = this[key]; if (result != null) { return result; } result = this[VALUE].get(key); if (result == null) { return this[VALUE][key]; } if (!result) { return result; } if (result.is_group) { return result.toProxyObject(); } if (!result.get) { return result; } return result.get(); }); /** * The magic getter * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} key */ MagicGroupValue.setMethod(function __ownKeys() { return Object.keys(this[VALUE].toObject()) }); /** * The magic getter * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} key */ MagicGroupValue.setMethod(function __describe(key) { let result = Object.getOwnPropertyDescriptor(this[VALUE], key); if (result == null) { result = Object.getOwnPropertyDescriptor(this, key); } return result; });