js/vercengen/engine/VercengenFeature.js

if (!global.ve) global.ve = {};

/**
 * <span color = "yellow">{@link ve.Feature}</span>: Features in Vercengen represent special containers such as panels, windows, tooltips, or interface which encapsulate <span color = "yellow">{@link ve.Component}</span> types. 
 * 
 * Used to automatically destructure `arg0_components_obj`.
 * 
 * ##### Constructor:
 * - `arg0_components_obj`: {@link function}|{@link Object}<{@link ve.Component}>|{@link string}|{@link ve.Component}
 * - `arg1_options`: {@link Object} - Fed into this.options for localised parsing.
 * 
 * ##### DOM:
 * - `.instance`: this:{@link ve.Component}
 * 
 * ##### Instance:
 * - `.child_class=this.constructor`: {@link ve.Feature} - The constructor object of the child class.
 * - `.child_class_obj=ve[htis.child_class.prototype.constructor.name]`: {@link ve.Feature} - The actual object of the child class.
 * - `.components_obj`: {@link Array}<{@link ve.Component}>
 * - `.id`: {@link string}
 * - `.is_vercengen_feature=true`: {@link boolean} - Whether to mark this ve.Feature as a Vercengen feature.
 * - `.options`: {@link Object}
 * 
 * ##### Methods:
 * - <span color=00ffff>{@link ve.Feature.addComponents|addComponents}</span>(arg0_components_obj:{@link Object}<{@link ve.Component}>) - Appends new components to the present ve.Feature.
 * - <span color=00ffff>{@link ve.Feature.close|close}</span>() - Alias for .remove()
 * - <span color=00ffff>{@link ve.Feature.remove|remove}</span>() - Removes the current feature.
 * - <span color=00ffff>{@link ve.Feature.removeComponents|removeComponents}</span>(arg0_components_obj:{@link Object}<{@link ve.Component}>) - Removes components from the current {@link this.element} based on key names.
 * - <span color=00ffff>{@link ve.Feature.updateOwner|updateOwner}</span>() - Updates the `.owner`/`.owners` fields for all encapsulated components to ve.Feature if no {@link ve.Class} owner could be found.
 * 
 * ##### Static Fields:
 * - `.instances`: {@link Array}<{@link ve.Feature}>
 * 
 * ##### Types:
 * - {@link ve.Feature.ve.Confirm|ve.Confirm}(arg0_components_obj:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) - Confirm dialogue/modal.
 * - {@link ve.Feature.ve.ContextMenu|ve.ContextMenu}(arg0_components_obj:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) - Pop-up context menu located at the cursor with an assigned `.id` to prevent duplicates.
 * - {@link ve.Feature.ve.Modal|ve.Modal}(arg0_components_obj:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) - Overlays a modal on top of the current Vercengen overlay interface. Must be responded to before conventional windows.
 * - {@link ve.Feature.ve.Navbar|ve.Navbar}(arg0_navbar_obj:{@link Object}, arg1_options:{@link Object}) - Creates an action topbar with recursive dropdown menus and keybind settings.
 * - {@link ve.Feature.ve.PageMenuWindow|ve.PageMenuWindow}(arg0_page_obj:{@link Object}, arg1_options:{@link Object}) - Creates a page menu window, which encapsulates components in pages instead of interfaces.
 * - {@link ve.Feature.ve.Scene|ve.Scene}(arg0_components_obj:{@link Object}<{@link ve.Component}>, arg1_options:{@link Object}) - Creates a full-viewport rendering scene.
 * - {@link ve.Feature.ve.Toast|ve.Toast}(arg0_components_obj:{@link Object}<{@link ve.Component}>|{@link string}, arg1_options:{@link Object}) - Spawns a toast at the current cursor.
 * - {@link ve.Feature.ve.Tooltip|ve.Tooltip}(arg0_components_obj:{@link Object}<{@link ve.Component}>|{@link string}|{@link ve.Component}, arg1_options:{@link Object}) - Spawns a tooltip for the given `.options.element`.
 * - {@link ve.Feature.ve.Window|ve.Window}(arg0_components_obj:{@link Object}<{@link ve.Component}>|{@link string}, arg1_options:{@link Object}) - Default ve.Feature for encapsulating components in <span color = "yellow">{@link ve.Class}</span>.
 * 
 * @class
 * @memberof ve
 * @namespace ve.Feature
 * @type {ve.Feature}
 */
ve.Feature = class {
	//Declare local static variables
	
	/**
	 * @type ve.Feature[]
	 */
	static instances = [];
	
	constructor (arg0_components_obj, arg1_options) {
		//Convert from parameters
		let components_obj = arg0_components_obj;
		let options = (arg1_options) ? arg1_options : {};
		
		//Declare local instance variables
		this.child_class = this.constructor;
		this.child_class_obj = ve[this.child_class.prototype.constructor.name];
		this.id = Class.generateRandomID(ve.Feature);
		this.is_vercengen_feature = true;
		if (typeof components_obj === "function" || typeof components_obj === "string") {
			this.components_obj = {
				html: new ve.HTML(components_obj)
			};
		} else if (components_obj.is_vercengen_component) {
			this.components_obj = {
				component: components_obj
			};
		} else {
			this.components_obj = components_obj;
		}
		this.options = (options) ? options : {};
		
		//Destructure this.components_obj into available variables
		try {
			this.updateOwner();
			Object.iterate(this.components_obj, (local_key, local_value) => {
				if (!this[local_key]) this[local_key] = local_value;
			});
		} catch (e) { console.error(e); }
	}
	
	/**
	 * Adds components to the present {@link this.element}.
	 * - Method of: {@link ve.Feature}
	 * 
	 * @param {{"<component_key>": ve.Component}} arg0_components_obj
	 */
	addComponents (arg0_components_obj) {
		//Convert from parameters
		let components_obj = (arg0_components_obj) ? arg0_components_obj : {};
		
		//Iterate over all components_obj
		Object.iterate(components_obj, (local_key, local_value) => {
			try {
				//Remove any extant element that already exists
				if (this.components_obj[local_key])
					this.removeComponent(this.components_obj[local_key]);
				
				//Set and destructure new component in this.components_obj
				if (!this[local_key]) this[local_key] = local_value;
				if (this.element)
					this.element.appendChild(local_value.element);
				this.components_obj[local_key] = local_value;
				this.updateOwner();
			} catch (e) { console.error(e); }
		});
		
	}
	
	/**
	 * Alias for {@link remove|this.remove}().
	 * - Method of: {@link ve.Feature}
	 */
	close () {
		this.remove();
	}
	
	/**
	 * Removes the {@link ve.Feature} from its static `.instances` field in addition to unmounting the feature from the DOM.
	 * - Method of: {@link ve.Feature}
	 */
	remove () {
		//Iterate over all instances in ve.Window.instances
		if (this.child_class_obj && this.child_class_obj.instances && this.id)
			for (let i = 0; i < this.child_class_obj.instances.length; i++)
				if (this.child_class_obj.instances[i].id === this.id) {
					this.child_class_obj.instances.splice(i, 1);
					break;
				}
		
		//Reset any child owners if their first owner is this ve.Feature
		if (this.components_obj)
			Object.iterate(this.components_obj, (local_key, local_value) => {
				if (local_value.owner && local_value.owner.is_vercengen_feature)
					if (local_value.owner.id === this.id) {
						//Remove the .owner
						delete local_value.owner;
						
						//Splice .owners to remove the present .owner
						if (local_value.owners.length >= 1)
							local_value.owners.splice(0, 1);
					}
			});
		
		//Remove element
		if (this.element) this.element.remove();
	}
	
	/**
	 * Removes components from the present {@link this.element}.
	 * - Method of: {@link ve.Feature}
	 * 
	 * @param {{"<component_key>": ve.Component}} arg0_components_obj
	 */
	removeComponents (arg0_components_obj) {
		//Convert from parameters
		let components_obj = (arg0_components_obj) ? arg0_components_obj : {};
		
		//Iterate over all components_obj and remove them based on keys
		Object.iterate(components_obj, (local_key, local_value) => {
			try {
				if (local_value) {
					local_value.removeComponent();
					if (local_value.owner && local_value.owner.is_vercengen_feature)
						if (local_value.owner.id === this.id) {
							//Remove the .owner
							delete local_value.owner;
							
							//Splice .owners to remove the present .owner
							if (local_value.owners.length >= 1)
								local_value.owners.splice(0, 1);
						}
				}
				delete this.components_obj[local_key];
			} catch (e) { console.error(e); }
		});
	}
	
	/**
	 * Iterates over all present Vercengen components in {@link ve.Feature} and sets their owner to the current Feature if they do not already have populated `.owner`/`.owners` fields.
	 * - Method of: {@link ve.Feature}
	 */
	updateOwner () {
		//Iterate over all this.components_obj
		if (this.components_obj)
			Object.iterate(this.components_obj, (local_key, local_value) => {
				if (local_value.is_vercengen_component)
					if (!local_value.owner || local_value.owner.is_vercengen_feature)
						this.components_obj[local_key].setOwner(this, [this]);
			});
	}
};