vercengen/components/ComponentLog.js

/**
 * Refer to <span color = "yellow">{@link ve.Component}</span> for methods or fields inherited from this Component's parent such as `.options.attributes` or `.element`.
 * 
 * ##### Constructor:
 * - `arg0_value`: {@link string} - The key of the log channel to display.
 * - `arg1_options`: {@link Object}
 *   - `.view_only=false`: {@link boolean}
 *   
 * ##### Instance:
 * - `.v`: {@link string}
 * 
 * ##### Methods:
 * - <span color=00ffff>{@link ve.Log.draw|draw}</span>()
 * - <span color=00ffff>{@link ve.Log.getChannel|getChannel}</span>() | {@link log.Channel}
 * - <span color=00ffff>{@link ve.Log.openChannel|openChannel}</span>(arg0_log_key:{@link string})
 * 
 * @augments ve.Component
 * @memberof ve.Component
 * @type {ve.Log}
 */
ve.Log = class extends ve.Component { //[WIP] - Finish Component body
	static instances = [];
	
	constructor (arg0_value, arg1_options) {
		//Convert from parameters
		let value = arg0_value;
		let options = (arg1_options) ? arg1_options : {};
			super(options);
			
		//Declare local instance variables
		this.element = document.createElement("div");
			this.element.setAttribute("component", "ve-log");
			this.element.instance = this;
			HTML.setAttributesObject(this.element, options.attributes);
		this.options = options;
		
		this.actions_bar_el = new ve.RawInterface({
			console_command: new ve.Text("", {
				attributes: { placeholder: loc("ve.registry.localisation.Log_enter_console_command") },
				name: " ",
				style: {
					display: "inline",
					'input[type="text"]': {
						maxWidth: "30rem"
					}
				}
			}),
			send_command: new ve.Button(() => {
				//Declare local instance variables
				let command_value = this.actions_bar_el.console_command.v;
				let log_obj = this.getChannel();
				
				if (!log_obj) return; //Internal guard clause if there is no valid log_obj
				
				if (command_value.length > 0) try {
					let user_command_el = document.createElement("div");
						user_command_el.setAttribute("class", "uf-log-line user-command");
						user_command_el.innerText = command_value;
						log_obj.log_el.appendChild(user_command_el);
					eval(command_value); //Evaluate user command
				} catch (e) {
					log_obj.error(e);
				}
			}, { name: loc("ve.registry.localisation.Log_send") }),
			clear_console: new ve.Button(() => {
				let local_confirm_modal = new ve.Confirm(loc("ve.registry.localisation.Log_enter_console_confirmation"), {
					special_function: () => { try { this.getChannel().clear(); } catch (e) {} }
				});
			}, { name: "Clear Console" }),
		}, {
			style: { whiteSpace: "nowrap" }
		});
		this.console_el = document.createElement("div");
			this.console_el.id = "console";
		this.draw();
		if (value) {
			this.from_binding_fire_silently = true;
			this.v = value;
			delete this.from_binding_fire_silently;
		}
		if (log.Channel.instances > 0)
			this.openChannel(log.Channel.instances[0].key);
		ve.Log.instances.push(this);
		
		//Logic loop for determining max height of console
		this.logic_loop = setInterval(() => {
			if (this.owners !== undefined)
				if (this.owners[0] instanceof ve.Window) {
					let offset_height = 400;
						try { 
							offset_height = this.owners[0].element.querySelector("#feature-body").offsetHeight; 
							offset_height = `${offset_height}px`;
						} catch (e) {
							offset_height = ve.registry.Log.default_console_height;
						}
					this.console_el.querySelector(".log-element").style.maxHeight = `calc(${offset_height} - ${this.actions_bar_el.element.offsetHeight}px - var(--padding)*2 - 5px)`; //px from resize handler
				}
		});
	}
	
	/**
	 * Returns the value of the component.
	 * - Accessor of: {@link ve.Log}
	 * 
	 * @alias v
	 * @memberof ve.Component.ve.Log
	 * @type {string}
	 */
	get v () {
		//Return statement
		return this._open_key;
	}
	
	/**
	 * Sets the present value of the Component.
	 * - Accessor of: {@link ve.Log}
	 * 
	 * @alias v
	 * @memberof ve.Component.ve.Log
	 * 
	 * @param {string} arg0_channel
	 */
	set v (arg0_channel) {
		//Convert from parameters
		let channel = arg0_channel;
		
		if (channel === undefined) return; //Internal guard clause if channel is set to undefined
		
		//Open the new channel and set it
		this.openChannel(channel);
		this.fireFromBinding();
	}
	
	/**
	 * Draws and updates the present component.
	 * - Method of: {@link ve.Log}
	 * 
	 * @alias draw
	 * @memberof ve.Component.ve.Log
	 */
	draw () {
		//Declare local instance variables
		let components_obj;
		let search_select_obj = {};
		
		//Internal guard clause if no channel is currently opened
		if (!this._open_key && log.Channel.instances.length > 0) {
			this.openChannel(log.Channel.instances[0].key);
			return;
		}
		
		//Iterate over all log.Channel.instances and append them to search_select_el as buttons
		for (let i = 0; i < log.Channel.instances.length; i++) {
			let local_log_channel = log.Channel.instances[i];
			let local_key = local_log_channel.key;
			
			let local_el = document.createElement("button");
				local_el.id = local_key;
				local_el.setAttribute("log-button", "true");
				if (this._open_key === local_key)
					local_el.classList.add("active");
				local_el.innerText = local_key;
				local_el.onclick = () => this.openChannel(local_key);
			let local_html_obj = new ve.HTML(local_el, { style: { padding: 0 } });
				local_html_obj._name = local_key;
			
			search_select_obj[local_log_channel.key] = local_html_obj;
		}
		
		if (!this.interface) {
			components_obj = {
				search_select: new ve.SearchSelect({
					...search_select_obj
				}, {
					display: "inherit",
					search_keys: ["_name"],
				}),
				console_el: new ve.HTML(this.console_el, {
					style: {
						height: "100%",
						paddingBottom: 0,
						paddingLeft: "var(--padding)",
						paddingTop: 0
					}
				}),
				type: "horizontal"
			};
			
			this.interface = new ve.FlexInterface(components_obj);
			this.interface.bind(this.element);
			
			this.components_obj = { flex_interface: this.interface };
		} else {
			this.interface.search_select.v = search_select_obj;
		}
	}
	
	/**
	 * Returns the currently opened channel.
	 * - Method of: {@link ve.Log}
	 * 
	 * @alias getChannel
	 * @memberof ve.Component.ve.Log
	 * 
	 * @returns {log.Channel}
	 */
	getChannel () {
		if (!this._open_key) return undefined; //Internal guard clause if this._open_key is not defined
		
		//Return statement
		for (let i = 0; i < log.Channel.instances.length; i++)
			if (log.Channel.instances[i].key === this._open_key)
				return log.Channel.instances[i];
	}
	
	/**
	 * Opens a log channel within the current component.
	 * - Method of: {@link ve.Log}
	 * 
	 * @alias openChannel
	 * @memberof ve.Component.ve.Log
	 * 
	 * @param {string} arg0_log_key
	 */
	openChannel (arg0_log_key) {
		//Convert from parameters
		let log_key = (arg0_log_key) ? arg0_log_key : log.Channel.instances?.[0]?.key;
		
		if (!log_key) return; //Internal guard clause if log_key is undefined
		if (!log[log_key]) return; //Internal guard clause if no log object matches this channel
		
		//Declare local instance variables
		let log_obj;
			for (let i = 0; i < log.Channel.instances.length; i++)
				if (log.Channel.instances[i].key === log_key) {
					log_obj = log.Channel.instances[i];
					break;
				}
		this._open_key = log_key;
		
		//Populate this.console_el
		this.console_el.innerHTML = "";
		this.console_el.appendChild(log_obj.log_el);
		
		if (!this.options.view_only)
			this.actions_bar_el.bind(this.console_el);
		
		//Update draw call
		this.draw();
	}
};

//Functional binding

/**
 * @returns {ve.Log}
 */
veLog = function () {
	//Return statement
	return new ve.Log(...arguments);
};