js/vercengen/engine/VercengenComponent.js

/**
 * <span color = "yellow">{@link ve.Component}</span>: Components represent inputs and displays that yield a value via `.v`. They are typically encapsulated by a <span color="yellow">{@link ve.Feature}</span>, but can be manually mounted via <span color=00ffff>{@link ve.Component.bind|bind}</span>(arg0_container_el:{@link HTMLElement}).
 * 
 * ##### Constructor:
 * - `arg0_options`: {@link Object}
 *   - All bindings accept 'this'/'global'/'window' variables. Bindings propagate recursively upwards (i.e. a userchange at a lower level will register as a userchange in an upper level).
 *   - `.binding`: {@link string} - Related event: `.onchange`. Bidirectional data binding for both `.from_binding`/`.to_binding`.
 *   - `.from_binding`: {@link string} - Related event: `.onprogramchange`. Unidirectional data binding.
 *   - `.to_binding`: {@link string} - Related event: `.onuserchange`. Unidirectional data binding.
 *   - 
 *   - `.onchange`: {@link function}(this.v, this:{@link ve.Component})
 *   - `.onprogramchange`: {@link function}(this.v, this:{@link ve.Component})
 *   - `.onuserchange`: {@link function}(this.v, this:{@link ve.Component})
 *   -
 *   - `.attributes`: {@link Object} - Any attributes to place on the mounted `this.element`.
 *     - `<attribute_key>`: {@link string}
 *   - `.limit=true`: {@link function}(this.v)|{@link undefined} | {@link boolean} - Whether to display the current Component. Immediate mode.
 *   - `.onload`: {@link function}(this:{@link ve.Component})
 *   - `.tooltip`: {@link Object}<{@link ve.Component}>|{@link string}
 *   - `.style`: {@link Object} - The CSS/Telestyle object to use for the current element. Immediate mode CSS if function is declared.
 *     - `<selector_key>`: {@link string} - CSS query selector. :nth-parent() is acceptable.
 *       - `<css_property>`: {@link function}|{@link string}
 *     - `<css_property>`: {@link function}|{@link string}
 *
 * ##### DOM:
 * - `.instance`: this:{@link ve.Component}
 *
 * ##### Instance:
 * - `.child_class=this.constructor`: {@link ve.Component} - The constructor object of the child class.
 * - `.is_vercengen_component=true`: {@link boolean} - Whether to mark this ve.Component as a Vercengen component.
 * - `.parent_el`: {@link HTMLElement} - The parent element of the current component, should it exist.
 * -
 * - `.height=1`: {@link number}
 * - `.width=1`: {@link number}
 * - `.x=0`: {@link number} - Switches to an n+1 default if only `.y` is defined.
 * - `.y=n + 1`: {@link number} - Switches to 0 if only `.x` is defined.
 * -
 * - The linter/engine guarantees the following fields:
 * - `.element`: {@link HTMLElement} - The HTMLElement that the ve.Component is mounted to.
 * - `.name`: {@link string} - The name to display for the current ve.Component.
 * - `.owner`: {@link any} - The root owner of the current ve.Component. This is typically a {@link ve.Class}, but can also be a {@link ve.Feature} or {@link global}/{@link window}.
 * - `.owners`: {@link Array}<{@link any}> - A list of relevant owners in descending orders.
 * - `.v`: {@link any} - The value stored by the component. Getter/setter.
 * 
 * ##### Methods:
 * - <span color=00ffff>{@link ve.Component.addComponent|addComponent}</span>() - Attempts to mount the current component on its parent_el.
 * - <span color=00ffff>{@link ve.Component.bind|bind}</span>(arg0_container_el:{@link HTMLElement}) - Manually mounts the current component to arg0_container_el.
 * - <span color=00ffff>{@link ve.Component.fireFromBinding|fireFromBinding}</span>() - Pseudo-setter from binding. Fires only upon program-driven changes to `.v` directly.
 * - <span color=00ffff>{@link ve.Component.fireToBinding|fireToBinding}</span>() - Pseudo-setter to binding. Fires only upon user-driven changes to `.v`.
 * - <span color=00ffff>{@link ve.Component.remove|remove}</span>() - Removes the component/element from the DOM.
 * - <span color=00ffff>{@link ve.Component.removeComponent|removeComponent}</span>() - Unmounts the current component from its parent_el.
 * - <span color=00ffff>{@link ve.Component.setOwner|setOwner}</span>(arg0_value:{@link Object}, arg1_owner_array=[]:{@link Array}<{@link Object}>) - Used by the reflection engine in {@link ve.Class} to set the owner hierarchy automatically.
 * 
 * ##### Static Methods:
 * - <span color=00ffff>{@link ve.Component.linter|linter}</span>() - Run at startup if {@link ve.debug_mode} is true. Lints all Vercengen components.
 * 
 * ##### Types:
 * Types are annotated by both their constructor function and what they return after the pipe separator (`.v`). 
 * 
 * \* indicates a recursive Object of that type.
 * - {@link ve.Component.ve.FileExplorer|veFileExplorer}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string} - The file path the File Explorer is currently navigating.
 * - {@link ve.Component.ve.Hierarchy|veHierarchy}(arg0_value:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) | {@link Object}<{@link ve.Component}> - Note. It is recommended to use {@link ve.HierarchyDatatype} as the specific {@link ve.Component} for `arg0_value`.
 *   - {@link ve.Component.ve.HierarchyDatatype|veHierarchyDatatype}({@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) | {@link Object}<{@link ve.Component}> - Represents individual items in a hierarchy.
 * 
 * - {@link ve.Component.ve.BIUF|veBIUF}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string} - Single-line rich text input. `.v` is an HTML string.
 * - {@link ve.Component.ve.Button|veButton}(arg0_value:{@link function}, arg1_options:{@link Object}) | {@link function}
 * - {@link ve.Component.ve.Checkbox|veCheckbox}(arg0_value:{@link boolean}|{@link Object}<{@link boolean}>\*, arg1_options: {@link Object}) | {@link boolean}|{@link Object}<{@link boolean}>\* - A recursive list of checkboxes, or a single toggleable input.
 * - {@link ve.Component.ve.Colour|veColour}(arg0_value:{@link Array}<{@link number}, {@link number}, {@link number}|{@link string}, arg1_options: {@link Object}>) | {@link Array}<{@link number}, {@link number}, {@link number}> - RGB colour selector.
 * - {@link ve.Component.ve.Datalist|veDatalist}(arg0_value:{@link Object}<{@link string}>, arg1_options: {@link Object})
 * - {@link ve.Component.ve.Date|veDate}(arg0_value:{@link UF.Date}, arg1_options: {@link Object}) | {@link UF.Date}
 * - {@link ve.Component.ve.DateLength|veDateLength}(arg0_value:{@link UF.Date}, arg1_options: {@link Object}) | {@link UF.Date}
 * - {@link ve.Component.ve.File|veFile}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string} - The file/folder path selected by the user.
 * - {@link ve.Component.ve.HTML|veHTML}(arg0_value:{@link function}|{@link HTMLElement}|{@link string}, arg1_options: {@link Object}) | {@link string}
 * - {@link ve.Component.ve.Interface|veInterface}(arg0_value:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) | {@link Object}<{@link ve.Component}>
 * - {@link ve.Component.ve.Map|veMap}(arg0_value:{@link maptalks.Map}, arg1_options:{@link Object}) | {@link maptalks.Map}
 * - {@link ve.Component.ve.Number|veNumber}(arg0_value:{@link number}, arg1_options:{@link Object}) | {@link number}
 * - {@link ve.Component.ve.PageMenu|vePageMenu}(arg0_value:{@link Object}, arg1_options:{@link Object}) | {@link string} - The `.page` key currently displayed.
 * - {@link ve.Component.ve.Password|vePassword}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string}
 * - {@link ve.Component.ve.Radio|veRadio}(arg0_value:{@link Object}<{@link boolean}>\* | {@link Object}<{@link boolean}>\*
 * - {@link ve.Component.ve.Range|veRange}(arg0_value:{@link number}, arg1_options: {@link Object}) | {@link number}
 * - {@link ve.Component.ve.RawInterface|veRawInterface}(arg0_components_obj:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) | {@link Object}<{@link ve.Component}>
 * - {@link ve.Component.ve.Select|veSelect}(arg0_value:{@link ve.Object}<{@link string}>, arg1_options:{@link Object}) | {@link string} - The key of the selected option.
 * - {@link ve.Component.ve.Telephone|veTelephone}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string}
 * - {@link ve.Component.ve.Text|veText}(arg0_value:{@link string}, arg1_options: {@link Object}) | {@link string}
 * - {@link ve.Component.ve.Time|veTime}(arg0_value:{hour:{@link number}, minute:{@link number}}, arg1_options:{@link Object}) | {hour:{@link number}, minute:{@link number}}
 * - {@link ve.Component.ve.Toggle|veToggle}(arg0_value:{@link boolean}, arg1_options:{@link Object}) | {@link boolean}
 * - {@link ve.Component.ve.UndoRedo|veUndoRedo}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string} - The {@link DALS.Timeline} ID that the UndoRedo component is currently navigating.
 * - {@link ve.Component.ve.URL|veURL}(arg0_value:{@link string}, arg1_options:{@link Object}) | {@link string}
 * 
 * @class
 * @memberof ve
 * @namespace ve.Component
 * @type {ve.Component}
 */
ve.Component = class {
	constructor (arg0_options) {
		//Convert from parameters
		let options = (arg0_options) ? arg0_options : {};
		
		//Declare local instance variables
		this.child_class = this.constructor;
		this.do_not_fire_to_binding = false;
		this.is_vercengen_component = true;
		
		this.height = options.height;
		this.width = options.width;
		this.x = options.x;
		this.y = options.y;
		
		//Binding handlers; setTimeout() is necessary to tick a frame until ve.Component child class's constructor populates
		setTimeout(() => {
			if (this.options === undefined)
				this.options = options; //Preferably overridden by lower components
			
			//Flow control handlers
			//.binding handler (bidirectional)
			if (this.options.binding) {
				this.from_binding = this.options.binding; //onprogramchange
				this.to_binding = this.options.binding; //onuserchange
			}
			//.from_binding handler (unidirectional, onprogramchange, variable -> this)
			if (this.options.from_binding)
				this.from_binding  = this.options.from_binding;
			//.to_binding handler (unidirectional, onuserchange, this -> variable)
			if (this.options.to_binding)
				this.to_binding = this.options.to_binding;
			
			//Event handlers
			//.onload handler
			if (this.options.onload)
				setTimeout(() => {
					this.options.onload(this);
				}, 100);
			
			//KEEP AT BOTTOM! - Feature/UI handlers
			//.limit handler
			if (this.options.limit) 
				this.limit = this.options.limit;
			
			//.tooltip handler
			if (this.options.tooltip)
				this.tooltip = new ve.Tooltip(this.options.tooltip, { element: this.element });
		});
	}
	
	//ve.Component getters/setters
	
	/**
	 * Tests the current {@link this.limit} by calling {@link this.limit_function}({@link this.v}, {@link this}). Otherwise resolves to true if no `.options.limit` is set.
	 * - Accessor of: {@link ve.Component}
	 * 
	 * @returns {boolean}
	 */
	get limit () {
		//Return statement
		return (this.limit_function) ? this.limit_function(this.v, this) : true;
	}
	
	/**
	 * Sets the present {@link this.limit} or clears it (if undefined). Calls {@link this.limit_function}({@link this.v}, {@link this}) when polled.
	 * - Accessor of: {@link ve.Component}
	 * 
	 * @param {function|undefined} arg0_function
	 */
	set limit (arg0_function) {
		//Convert from parameters
		this.limit_function = arg0_function;
		
		if (this.limit_function !== undefined) {
			this.limit_logic_loop = setInterval(() => {
				if (!this.limit) {
					this.removeComponent();
				} else {
					this.addComponent();
				}
			}, 100);
		} else {
			delete this.limit_function;
			clearInterval(this.limit_logic_loop);
			delete this.limit_logic_loop;
		}
	}
	
	/**
	 * Returns the visual name of the present {@link ve.Component},
	 * - Accessor of: {@link ve.Component}
	 * 
	 * @returns {string}
	 */
	get name () {
		//Internal guard clause for this.components_obj.name.v
		if (this.components_obj && this.components_obj.name)
			return this.components_obj.name.v;
		
		//Declare local instance variables
		let name_el = this.element.querySelector(`#name`);
		
		//Return statement
		if (name_el)
			return name_el.innerHTML;
	}
	
	/**
	 * Sets the visual name of the present {@link ve.Component}
	 * - Accessor of: {@link ve.Component}
	 * 
	 * @param {string} arg0_value
	 */
	set name (arg0_value) {
		//Convert from parameters
		let value = (arg0_value) ? arg0_value : "";
		
		//Guard catch for this.components_obj.name.v
		if (this.components_obj && this.components_obj.name) {
			this.components_obj.name.v = (value) ? value : "";
			return;
		}
		
		//Declare local instance variables
		let name_el = this.element.querySelector(`#name`);
		
		//Return statement
		if (name_el)
			name_el.innerHTML = (value) ? value : "";
	}
	
	//ve.Component directional flow functions - [WIP] - Reduce redundancy with parsing variablee_string
	
	/**
	 * Pseudo-setter from binding. Fires only upon program-driven changes to .v directly, which means that this has to be monitored manually component-side in set v(). This should always come last in set v().
	 * - Method of: {@link ve.Component}
	 */
	fireFromBinding () {
		//Convert from parameters
		let variable_string = (this.from_binding_string) ? JSON.parse(JSON.stringify(this.from_binding_string)) : undefined;
			if (this.from_binding_fire_silently) return; //Internal guard clause if this.from_binding is to fire silently
			if (variable_string === undefined) return; //Internal guard clause if variable_string is undefined
		
		//Declare local instance variables
		let initial_object = global;
		
		//Parse this to this.owner; watch mutation using getter/setter, and set this.v to new value
		if (variable_string.startsWith("this.")) {
			variable_string = variable_string.replace("this.", "");
			initial_object = this.owner;
		} else if (variable_string.startsWith("window.")) {
			variable_string = variable_string.replace("window.", "");
			initial_object = window;
		} else {
			variable_string = variable_string.replace("global.", "");
		}
		
		//Set this.from_binding in a to binding manner
		if (this.from_binding_string) {
			this.from_binding_fire_silently = true;
			Object.setValue(initial_object, variable_string, this.v);
			delete this.from_binding_fire_silently;
			
			if (typeof this.options.onchange === "function") //Fire onchange (bidirectional)
				this.options.onchange(this.v, this);
			if (typeof this.options.onprogramchange === "function") //Fire onprogramchange (unidirectional)
				this.options.onprogramchange(this.v, this);
		}
	}
	
	/**
	 * Pseudo-setter to binding. Fires only upon user-driven changes, which means that this has to be monitored manually component-side.
	 * - Method of: {@link ve.Component}
	 */
	fireToBinding () {
		//Declare local instance variables
		let initial_object = global;
		let variable_string = this.to_binding;
		
		//Internal guard clause if this.do_not_fire_to_binding is active
		if (this.do_not_fire_to_binding) return;
		this.from_binding_fire_silently = true;
		
		//Internal guard clause if this.to_binding is not defined
		if (this.to_binding) {
			if (typeof this.to_binding !== "string") {
				console.error(`ve.Component: ${this.child_class.prototype.constructor.name}: this.to_binding is an invalid string:`, this.to_binding);
				return;
			}
			
			//Parse this to this.owner; watch variable mutation using getter/setter, and set this.v to new value
			if (variable_string.startsWith("this.")) {
				variable_string = variable_string.replace("this.", "");
				initial_object = this.owner;
			} else if (variable_string.startsWith("window.")) {
				variable_string = variable_string.replace("window.", "");
				initial_object = window;
			} else {
				variable_string = variable_string.replace("global.", "");
			}
		}
		
		//Set value of to object by fetching this.v
		let local_value = this.v; //Because this is a getter, run it once
		
		//Traverse up the .owners tree and fire onchange/onuserchange
		if (this.owners) 
			for (let i = this.owners.length - 1; i >= 0; i--)
				if (this.owners[i].options) {
					let local_options = this.owners[i].options;
					
					if (typeof local_options.onchange === "function")
						local_options.onchange(this.owners[i].v, this.owners[i]);
					if (typeof local_options.onuserchange === "function") //Fire onuserchange (unidirectional)
						local_options.onuserchange(this.owners[i].v, this.owners[i]);
				}
		
		if (typeof this.options.onchange === "function") //Fire onchange (bidirectional)
			this.options.onchange(local_value, this);
		if (typeof this.options.onuserchange === "function") //Fire onuserchange (unidirectional)
			this.options.onuserchange(local_value, this);
		
		if (this.to_binding)
			Object.setValue(initial_object, variable_string, local_value);
		this.from_binding_fire_silently = false;
	}
	
	/**
	 * Internal `.from_binding` setter for handling .options.from_binding. Accepts a string literal that is then parsed to a variable reference. 
	 * 
	 * `.to_binding` counterparts are manually handled child-side.
	 * - Accessor of: {@link ve.Component}
	 * 
	 * @param {string} arg0_variable_string
	 */
	set from_binding (arg0_variable_string) {
		//Convert from parameters
		let variable_string = arg0_variable_string;
		
		//Declare local instance variables
		let initial_object = global;
		this.from_binding_string = variable_string;
		
		try {
			//Parse this to this.owner; watch variable mutation using getter/setter, and set this.v to new value
			if (variable_string.startsWith("this.")) {
				variable_string = variable_string.replace("this.", "");
				initial_object = this.owner;
			} else if (variable_string.startsWith("window.")) {
				variable_string = variable_string.replace("window.", "");
				initial_object = window;
			} else {
				variable_string = variable_string.replace("global.", "");
			}
			
			//Set init value if applicable
			let from_value = Object.getValue(initial_object, variable_string);
			if (this.options.binding && from_value === undefined)
				Object.setValue(initial_object, variable_string, this.v);
			from_value = Object.getValue(initial_object, variable_string);
			this.from_binding_fire_silently = true;
			this.v = from_value;
			delete this.from_binding_fire_silently;
			
			//Add getter/setter
			Object.addGetterSetter(initial_object, variable_string, {
				set_function: (arg0_value) => {
					//Convert from parameters
					let local_value = arg0_value;
					if (this.from_binding_fire_silently) return;
					
					//Declare local instance variables
					this.v = local_value;
					
					let is_same_value = Boolean.strictEquality(local_value, this.v);
					if (is_same_value) return;
					
					//Traverse up the .owners tree and fire onchange/onprogramchange
					if (this.owners)
						for (let i = this.owners.length - 1; i >= 0; i--)
							if (this.owners[i].options) {
								let local_options = this.owners[i].options;
								
								if (typeof local_options.onchange === "function") //Fire onchange (bidirectional)
									local_options.onchange(this.owners[i].v, this.owners[i]);
								if (typeof local_options.onprogramchange === "function") //Fire onprogramchange (unidirectional)
									local_options.onprogramchange(this.owners[i].v, this.owners[i]);
							}
					
					if (typeof this.options.onchange === "function") //Fire onchange (bidirectional)
						this.options.onchange(local_value, this);
					if (typeof this.options.onprogramchange === "function") //Fire onprogramchange (unidirectional)
						this.options.onprogramchange(local_value, this);
					this.v = local_value;
				}
			});
			let temp = from_value;
			this.from_binding_fire_silently = true;
			Object.setValue(initial_object, variable_string, temp);
			delete this.from_binding_fire_silently;
		} catch (e) {
			let error_array = [];
				error_array.push(`ve.Component: ${this.child_class.prototype.constructor.name}: this.from_binding failed.`);
				if (initial_object === undefined)
					error_array.push(`- this.updateOwner() has not been called synchronously (check constructors and/or ve.Component updates).`);
			if (this.options.binding && error_array.length <= 1) return; //Internal guard clause if this is a valid bidirectional binding
				
			console.error(`${error_array.join("\n")}\n- initial_object:`, initial_object, `variable_string:`, variable_string);
		}
	}
	
	/**
	 * Sets the root parent and ownership tree. Influences {@link this.parent_el}, {@link this.owner}, {@link this.owners}.
	 * - Method of: {@link ve.Component}
	 * 
	 * @param arg0_value
	 * @param arg1_owner_array
	 */
	setOwner (arg0_value, arg1_owner_array) {
		//Convert from parameters
		let value = arg0_value;
		let owner_array = (arg1_owner_array) ? arg1_owner_array : [this];
		
		//Declare local instance variables
		this.owner = value;	
		this.owners = [].concat(owner_array); //Mutate to avoid shallow copies
		
		//Iterate over all this.child_class_argument_names and recursively drill down owners
		if (this.components_obj) {
			owner_array.push(this);
			
			Object.iterate(this.components_obj, (local_key, local_value) => {
				local_value.setOwner(value, owner_array);
			});
		}
	}
	
	//ve.Component UI functions
	
	/**
	 * Adds the current component to {@link this.parent_el} should it exist.
	 * - Method of: {@link ve.Component}
	 */
	addComponent () {
		if (this.parent_el) try {
			if (!this.parent_el.contains(this.element))
				this.parent_el.appendChild(this.element);
		} catch (e) { console.error(e); }
	}
	
	/**
	 * Manually binds/mounts the present <span color="yellow">{@link ve.Component}</span> into the visual DOM.
	 * - Method of: {@link ve.Component}
	 * 
	 * @param {HTMLElement} arg0_container_el
	 */
	bind (arg0_container_el) {
		//Convert from parameters
		let container_el = arg0_container_el;
		
		//Set variable_key, append to container_el
		container_el.append(this.element);
	}
	
	/**
	 * Removes the component/element from the DOM.
	 * - Method of: {@link ve.Component}
	 */
	remove () {
		//Declare local instance variables
		let child_class_obj = ve[this.child_class.prototype.constructor.name];
		
		//Iterate over l instances in child_class_obj.instances if available
		if (child_class_obj.instances && this.id)
			for (let i = 0; i < child_class_obj.instances.length; i++)
				if (child_class_obj.instances[i].id === this.id) {
					child_class_obj.instances.splice(i, 1);
					break;
				}
		
		//Remove DOM element
		if (this.element)
			this.element.remove();
	}
	
	/**
	 * Removes the current component from {@link this.parent_el} should it exist.
	 * - Method of: {@link ve.Component}
	 */
	removeComponent () {
		if (this.element.parentElement) try {
			if (this.element.parentElement.contains(this.element)) {
				this.parent_el = this.element.parentElement;
				this.parent_el.removeChild(this.element);
			}
		} catch (e) { console.error(e); }
	}
	
	/**
	 * Runs over all Vercengen components that extend <span color="yellow">{@link ve.Component}</span> and lints them in addition to declaring `ve[local_key]`() as a functional binding for each.
	 * - Static method of: {@link ve.Component}
	 * 
	 * Ensures the following properties if `ve.debug_mode=true`:
	 * - get v()/set v()
	 * - Not a duplicate component
	 */
	static linter () {
		Object.iterate(global.ve, (local_key, local_value) => {
			try {
				if (Object.getPrototypeOf(local_value) === ve.Component) {
					let local_name = Object.getOwnPropertyDescriptor(local_value.prototype, "name");
					let local_v = Object.getOwnPropertyDescriptor(local_value.prototype, "v");
					
					let local_prefix = `ve.Component: ve.${local_key}`;
					
					if (!global[`ve${local_key}`]) {
						global[`ve${local_key}`] = function () {
							//Return statement
							return new ve[local_key](...arguments);
						};
					} else if (typeof global[`ve${local_key}`] !== "function") {
						console.error(`ve.${local_key} cannot have its functional binding registered, since it is already reserved elsewhere as a non-function. Use Ctrl + F to find where it has been reserved in your codebase.`);
					}
					
					if (ve.debug_mode)
						if (local_value.demo_value === undefined)
							console.warn(`${local_prefix} does not have a set static .demo_value.`);
					
					//Check if get()/set() methods exist
					if (!local_v || typeof local_v.get !== "function")
						console.error(`${local_prefix} does not have a valid get v() function.`);
					if (!local_v || typeof local_v.set !== "function")
						console.error(`${local_prefix} does not have a valid set v() function.`);
				}
			} catch (e) { console.error(e); }
		});
	}
};