import { Position, Size, AnyObject, WithModuleProps, ItemMeta, LinkedVisualConsoleProps, WithAgentProps } from "./lib/types"; import { sizePropsDecoder, positionPropsDecoder, parseIntOr, parseBoolean, notEmptyStringOr, replaceMacros, humanDate, humanTime, addMovementListener, debounce, addResizementListener, t } from "./lib"; import TypedEvent, { Listener, Disposable } from "./lib/TypedEvent"; import { FormContainer, InputGroup } from "./Form"; // Enum: https://www.typescriptlang.org/docs/handbook/enums.html. export const enum ItemType { STATIC_GRAPH = 0, MODULE_GRAPH = 1, SIMPLE_VALUE = 2, PERCENTILE_BAR = 3, LABEL = 4, ICON = 5, SIMPLE_VALUE_MAX = 6, SIMPLE_VALUE_MIN = 7, SIMPLE_VALUE_AVG = 8, PERCENTILE_BUBBLE = 9, SERVICE = 10, GROUP_ITEM = 11, BOX_ITEM = 12, LINE_ITEM = 13, AUTO_SLA_GRAPH = 14, CIRCULAR_PROGRESS_BAR = 15, CIRCULAR_INTERIOR_PROGRESS_BAR = 16, DONUT_GRAPH = 17, BARS_GRAPH = 18, CLOCK = 19, COLOR_CLOUD = 20, NETWORK_LINK = 21, ODOMETER = 22, BASIC_CHART = 23 } // Base item properties. This interface should be extended by the item implementations. export interface ItemProps extends Position, Size { readonly id: number; readonly type: ItemType; label: string | null; labelPosition: "up" | "right" | "down" | "left"; isLinkEnabled: boolean; link: string | null; isOnTop: boolean; parentId: number | null; aclGroupId: number | null; cacheExpiration: number | null; colorStatus: string; cellId: number | null; alertOutline: boolean; } export interface ItemClickEvent { item: VisualConsoleItem; nativeEvent: Event; } // FIXME: Fix type compatibility. export interface ItemRemoveEvent { // data: Props; item: VisualConsoleItem; } export interface ItemMovedEvent { item: VisualConsoleItem; prevPosition: Position; newPosition: Position; } export interface ItemResizedEvent { item: VisualConsoleItem; prevSize: Size; newSize: Size; } export interface ItemSelectionChangedEvent { selected: boolean; } /** * Extract a valid enum value from a raw label position value. * @param labelPosition Raw value. */ const parseLabelPosition = ( labelPosition: unknown ): ItemProps["labelPosition"] => { switch (labelPosition) { case "up": case "right": case "down": case "left": return labelPosition; default: return "down"; } }; /** * Build a valid typed object from a raw object. * This will allow us to ensure the type safety. * * @param data Raw object. * @return An object representing the item props. * @throws Will throw a TypeError if some property * is missing from the raw object or have an invalid type. */ export function itemBasePropsDecoder(data: AnyObject): ItemProps | never { if (data.id == null || isNaN(parseInt(data.id))) { throw new TypeError("invalid id."); } if (data.type == null || isNaN(parseInt(data.type))) { throw new TypeError("invalid type."); } return { id: parseInt(data.id), type: parseInt(data.type), label: notEmptyStringOr(data.label, null), labelPosition: parseLabelPosition(data.labelPosition), isLinkEnabled: parseBoolean(data.isLinkEnabled), link: notEmptyStringOr(data.link, null), isOnTop: parseBoolean(data.isOnTop), parentId: parseIntOr(data.parentId, null), aclGroupId: parseIntOr(data.aclGroupId, null), cacheExpiration: parseIntOr(data.cacheExpiration, null), colorStatus: notEmptyStringOr(data.colorStatus, "#CCC"), cellId: parseIntOr(data.cellId, null), alertOutline: parseBoolean(data.alertOutline), ...sizePropsDecoder(data), // Object spread. It will merge the properties of the two objects. ...positionPropsDecoder(data) // Object spread. It will merge the properties of the two objects. }; } //TODO: Document export function titleItem(id: number): string { let title = ""; switch (id) { case ItemType.STATIC_GRAPH: title = t("Static image"); break; case ItemType.MODULE_GRAPH: title = t("Module graph"); break; case ItemType.SIMPLE_VALUE: title = t("Simple value"); break; case ItemType.PERCENTILE_BAR: title = t("Percentile item"); break; case ItemType.LABEL: title = t("Label"); break; case ItemType.ICON: title = t("Icon"); break; case ItemType.SIMPLE_VALUE_MAX: title = t("Simple value"); break; case ItemType.SIMPLE_VALUE_MIN: title = t("Simple value"); break; case ItemType.SIMPLE_VALUE_AVG: title = t("Simple value"); break; case ItemType.PERCENTILE_BUBBLE: title = t("Percentile item"); break; case ItemType.SERVICE: title = t("Service"); break; case ItemType.GROUP_ITEM: title = t("Group"); break; case ItemType.BOX_ITEM: title = t("Box"); break; case ItemType.LINE_ITEM: title = t("Line"); break; case ItemType.AUTO_SLA_GRAPH: title = t("Event history graph"); break; case ItemType.CIRCULAR_PROGRESS_BAR: title = t("Percentile item"); break; case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR: title = t("Percentile item"); break; case ItemType.DONUT_GRAPH: title = t("Serialized pie graph"); break; case ItemType.BARS_GRAPH: title = t("Bars graph"); break; case ItemType.CLOCK: title = t("Clock"); break; case ItemType.COLOR_CLOUD: title = t("Color cloud"); break; case ItemType.NETWORK_LINK: title = t("Network link"); break; case ItemType.ODOMETER: title = t("Odometer"); break; case ItemType.BASIC_CHART: title = t("Basic chart"); break; default: title = t("Item"); break; } return title; } /** * Base class of the visual console items. Should be extended to use its capabilities. */ abstract class VisualConsoleItem { // Properties of the item. public itemProps: Props; // Metadata of the item. private _metadata: ItemMeta; // Reference to the DOM element which will contain the item. public elementRef: HTMLElement = document.createElement("div"); public labelElementRef: HTMLElement = document.createElement("div"); // Reference to the DOM element which will contain the view of the item which extends this class. protected childElementRef: HTMLElement = document.createElement("div"); // Event manager for click events. private readonly clickEventManager = new TypedEvent(); // Event manager for double click events. private readonly dblClickEventManager = new TypedEvent(); // Event manager for moved events. private readonly movedEventManager = new TypedEvent(); // Event manager for stopped movement events. private readonly movementFinishedEventManager = new TypedEvent< ItemMovedEvent >(); // Event manager for resized events. private readonly resizedEventManager = new TypedEvent(); // Event manager for resize finished events. private readonly resizeFinishedEventManager = new TypedEvent< ItemResizedEvent >(); // Event manager for remove events. private readonly removeEventManager = new TypedEvent(); // Event manager for selection change events. private readonly selectionChangedEventManager = new TypedEvent< ItemSelectionChangedEvent >(); // List of references to clean the event listeners. private readonly disposables: Disposable[] = []; // This function will only run the 2nd arg function after the time // of the first arg have passed after its last execution. public debouncedMovementSave = debounce( 500, // ms. (x: Position["x"], y: Position["y"]) => { // Update the metadata information. // Don't use the .meta property cause we don't need DOM updates. this._metadata.isBeingMoved = false; const prevPosition = { x: this.props.x, y: this.props.y }; const newPosition = { x: x, y: y }; if (!this.positionChanged(prevPosition, newPosition)) return; // Save the new position to the props. this.move(x, y); // Emit the movement event. this.movementFinishedEventManager.emit({ item: this, prevPosition: prevPosition, newPosition: newPosition }); } ); // This property will store the function // to clean the movement listener. private removeMovement: Function | null = null; /** * Start the movement funtionality. * @param element Element to move inside its container. */ private initMovementListener(element: HTMLElement): void { // Avoid line movement as 'block' force using circles. if ( this.props.type == ItemType.LINE_ITEM || this.props.type == ItemType.NETWORK_LINK ) { return; } this.removeMovement = addMovementListener( element, (x: Position["x"], y: Position["y"]) => { const prevPosition = { x: this.props.x, y: this.props.y }; const newPosition = { x, y }; this.meta = { ...this.meta, isSelected: true }; if (!this.positionChanged(prevPosition, newPosition)) return; // Update the metadata information. // Don't use the .meta property cause we don't need DOM updates. this._metadata.isBeingMoved = true; // Move the DOM element. this.moveElement(x, y); // Emit the movement event. this.movedEventManager.emit({ item: this, prevPosition: prevPosition, newPosition: newPosition }); // Run the save function. this.debouncedMovementSave(x, y); } ); } /** * Stop the movement fun */ private stopMovementListener(): void { if (this.removeMovement) { this.removeMovement(); this.removeMovement = null; } } // This function will only run the 2nd arg function after the time // of the first arg have passed after its last execution. public debouncedResizementSave = debounce( 500, // ms. (width: Size["width"], height: Size["height"]) => { // Update the metadata information. // Don't use the .meta property cause we don't need DOM updates. this._metadata.isBeingResized = false; const prevSize = { width: this.props.width, height: this.props.height }; const newSize = { width, height }; if (!this.sizeChanged(prevSize, newSize)) return; // Save the new position to the props. this.resize(width, height); // Emit the resize finished event. this.resizeFinishedEventManager.emit({ item: this, prevSize: prevSize, newSize: newSize }); } ); // This property will store the function // to clean the resizement listener. private removeResizement: Function | null = null; /** * Start the resizement funtionality. * @param element Element to move inside its container. */ protected initResizementListener(element: HTMLElement): void { if ( this.props.type == ItemType.LINE_ITEM || this.props.type == ItemType.NETWORK_LINK ) { return; } this.removeResizement = addResizementListener( element, (width: Size["width"], height: Size["height"]) => { // Update the metadata information. // Don't use the .meta property cause we don't need DOM updates. this._metadata.isBeingResized = true; // The label it's outside the item's size, so we need // to get rid of its size to get the real size of the // item's content. if (this.props.label && this.props.label.length > 0) { const { width: labelWidth, height: labelHeight } = this.labelElementRef.getBoundingClientRect(); switch (this.props.labelPosition) { case "up": case "down": height -= labelHeight; break; case "left": case "right": width -= labelWidth; break; } } const prevSize = { width: this.props.width, height: this.props.height }; const newSize = { width, height }; if (!this.sizeChanged(prevSize, newSize)) return; // Move the DOM element. this.resizeElement(width, height); // Emit the resizement event. this.resizedEventManager.emit({ item: this, prevSize, newSize }); // Run the save function. this.debouncedResizementSave(width, height); } ); } /** * Stop the resizement functionality. */ private stopResizementListener(): void { if (this.removeResizement) { this.removeResizement(); this.removeResizement = null; } } /** * To create a new element which will be inside the item box. * @return Item. */ protected abstract createDomElement(): HTMLElement; public constructor( props: Props, metadata: ItemMeta, deferInit: boolean = false ) { this.itemProps = props; this._metadata = metadata; if (!deferInit) this.init(); } /** * To create and append the DOM elements. */ protected init(): void { /* * Get a HTMLElement which represents the container box * of the Visual Console item. This element will manage * all the common things like click events, show a border * when hovered, etc. */ this.elementRef = this.createContainerDomElement(); this.labelElementRef = this.createLabelDomElement(); /* * Get a HTMLElement which represents the custom view * of the Visual Console item. This element will be * different depending on the item implementation. */ this.childElementRef = this.createDomElement(); // Insert the elements into the container. this.elementRef.appendChild(this.childElementRef); this.elementRef.appendChild(this.labelElementRef); // Resize element. this.resizeElement(this.itemProps.width, this.itemProps.height); // Set label position. this.changeLabelPosition(this.itemProps.labelPosition); } /** * To create a new box for the visual console item. * @return Item box. */ private createContainerDomElement(): HTMLElement { let box; if (this.props.isLinkEnabled) { box = document.createElement("a") as HTMLAnchorElement; if (this.props.link) { box.href = this.props.link; } else { box.className = "textDecorationNone"; } } else { box = document.createElement("div") as HTMLDivElement; box.className = "textDecorationNone"; } box.classList.add("visual-console-item"); if (this.props.isOnTop) { box.classList.add("is-on-top"); } box.style.left = `${this.props.x}px`; box.style.top = `${this.props.y}px`; if (this.props.alertOutline) { box.classList.add("is-alert-triggered"); } // Init the click listeners. box.addEventListener("dblclick", e => { if (!this.meta.isBeingMoved && !this.meta.isBeingResized) { this.unSelectItem(); this.selectItem(); this.dblClickEventManager.emit({ item: this, nativeEvent: e }); } }); box.addEventListener("click", e => { if (this.meta.editMode) { e.preventDefault(); e.stopPropagation(); } else { // Add loading click item. if (this.itemProps.isLinkEnabled && this.itemProps.link != null) { const divParent = document.createElement("div"); divParent.className = "div-visual-console-spinner"; const divSpinner = document.createElement("div"); divSpinner.className = "visual-console-spinner"; divParent.appendChild(divSpinner); let path = e.composedPath(); let containerId = "visual-console-container"; for (let index = 0; index < path.length; index++) { const element = path[index] as HTMLInputElement; if ( element.id != undefined && element.id != null && element.id != "" ) { if (element.id.includes(containerId) === true) { containerId = element.id; break; } } } const containerVC = document.getElementById(containerId); if (containerVC != null) { containerVC.classList.add("is-updating"); containerVC.appendChild(divParent); } } } if (!this.meta.isBeingMoved && !this.meta.isBeingResized) { this.clickEventManager.emit({ item: this, nativeEvent: e }); } }); // Metadata state. if (this.meta.maintenanceMode) { box.classList.add("is-maintenance"); } if (this.meta.editMode) { box.classList.add("is-editing"); } if (this.meta.isFetching) { box.classList.add("is-fetching"); } if (this.meta.isUpdating) { box.classList.add("is-updating"); } if (this.meta.isSelected) { box.classList.add("is-selected"); } return box; } /** * To create a new label for the visual console item. * @return Item label. */ protected createLabelDomElement(): HTMLElement { const element = document.createElement("div"); element.className = "visual-console-item-label"; // Add the label if it exists. const label = this.getLabelWithMacrosReplaced(); if (label.length > 0) { // Ugly table we need to use to replicate the legacy style. const table = document.createElement("table"); const row = document.createElement("tr"); const emptyRow1 = document.createElement("tr"); const emptyRow2 = document.createElement("tr"); const cell = document.createElement("td"); cell.innerHTML = label; row.appendChild(cell); table.appendChild(emptyRow1); table.appendChild(row); table.appendChild(emptyRow2); table.style.textAlign = "center"; // Change the table size depending on its position. switch (this.props.labelPosition) { case "up": case "down": if (this.props.width > 0) { table.style.width = `${this.props.width}px`; table.style.height = ""; } break; case "left": case "right": if (this.props.height > 0) { table.style.width = ""; table.style.height = `${this.props.height}px`; } break; } // element.innerHTML = this.props.label; element.appendChild(table); } return element; } /** * Return the label stored into the props with some macros replaced. */ protected getLabelWithMacrosReplaced(): string { // We assert that the props may have some needed properties. const props = this.props as Partial; return replaceMacros( [ { macro: "_date_", value: humanDate(new Date()) }, { macro: "_time_", value: humanTime(new Date()) }, { macro: "_agent_", value: props.agentAlias != null ? props.agentAlias : "" }, { macro: "_agentdescription_", value: props.agentDescription != null ? props.agentDescription : "" }, { macro: "_address_", value: props.agentAddress != null ? props.agentAddress : "" }, { macro: "_module_", value: props.moduleName != null ? props.moduleName : "" }, { macro: "_moduledescription_", value: props.moduleDescription != null ? props.moduleDescription : "" } ], this.props.label || "" ); } /** * To update the content element. * @return Item. */ protected updateDomElement(element: HTMLElement): void { element.innerHTML = this.createDomElement().innerHTML; } /** * Public accessor of the `props` property. * @return Properties. */ public get props(): Props { return { ...this.itemProps }; // Return a copy. } /** * Public setter of the `props` property. * If the new props are different enough than the * stored props, a render would be fired. * @param newProps */ public set props(newProps: Props) { this.setProps(newProps); } /** * Clasic and protected version of the setter of the `props` property. * Useful to override it from children classes. * @param newProps */ protected setProps(newProps: Props) { const prevProps = this.props; // Update the internal props. this.itemProps = newProps; // From this point, things which rely on this.props can access to the changes. // Check if we should re-render. if (this.shouldBeUpdated(prevProps, newProps)) this.render(prevProps, this._metadata); } /** * Public accessor of the `meta` property. * @return Properties. */ public get meta(): ItemMeta { return { ...this._metadata }; // Return a copy. } /** * Public setter of the `meta` property. * If the new meta are different enough than the * stored meta, a render would be fired. * @param newProps */ public set meta(newMetadata: ItemMeta) { this.setMeta(newMetadata); } /** * Classic version of the setter of the `meta` property. * Useful to override it from children classes. * @param newProps */ public setMeta(newMetadata: Partial): void { const prevMetadata = this._metadata; // Update the internal meta. this._metadata = { ...prevMetadata, ...newMetadata }; if ( typeof newMetadata.isSelected !== "undefined" && prevMetadata.isSelected !== newMetadata.isSelected ) { this.selectionChangedEventManager.emit({ selected: newMetadata.isSelected }); } // From this point, things which rely on this.props can access to the changes. // Check if we should re-render. // if (this.shouldBeUpdated(prevMetadata, newMetadata)) this.render(this.itemProps, prevMetadata); } /** * To compare the previous and the new props and returns a boolean value * in case the difference is meaningfull enough to perform DOM changes. * * Here, the only comparision is done by reference. * * Override this function to perform a different comparision depending on the item needs. * * @param prevProps * @param newProps * @return Whether the difference is meaningful enough to perform DOM changes or not. */ protected shouldBeUpdated(prevProps: Props, newProps: Props): boolean { return prevProps !== newProps; } /** * To recreate or update the HTMLElement which represents the item into the DOM. * @param prevProps If exists it will be used to only perform DOM updates instead of a full replace. */ public render( prevProps: Props | null = null, prevMeta: ItemMeta | null = null ): void { if (prevProps) { this.updateDomElement(this.childElementRef); } // Move box. if (!prevProps || this.positionChanged(prevProps, this.props)) { this.moveElement(this.props.x, this.props.y); if ( prevProps && prevProps.type != ItemType.LINE_ITEM && prevProps.type != ItemType.NETWORK_LINK ) { this.updateDomElement(this.childElementRef); } } // Resize box. if (!prevProps || this.sizeChanged(prevProps, this.props)) { this.resizeElement(this.props.width, this.props.height); if ( prevProps && prevProps.type != ItemType.LINE_ITEM && prevProps.type != ItemType.NETWORK_LINK ) { this.updateDomElement(this.childElementRef); } } // Change label. const oldLabelHtml = this.labelElementRef.innerHTML; const newLabelHtml = this.createLabelDomElement().innerHTML; if (oldLabelHtml !== newLabelHtml) { this.labelElementRef.innerHTML = newLabelHtml; } // Change label position. if (!prevProps || prevProps.labelPosition !== this.props.labelPosition) { this.changeLabelPosition(this.props.labelPosition); } //Change z-index class is-on-top if (!prevProps || prevProps.isOnTop !== this.props.isOnTop) { if (this.props.isOnTop) { this.elementRef.classList.add("is-on-top"); } else { this.elementRef.classList.remove("is-on-top"); } } // Change link. if (prevProps && prevProps.isLinkEnabled !== this.props.isLinkEnabled) { const container = this.createContainerDomElement(); // Add the children of the old element. container.innerHTML = this.elementRef.innerHTML; // Copy the attributes. const attrs = this.elementRef.attributes; for (let i = 0; i < attrs.length; i++) { if (attrs[i].nodeName !== "id") { let cloneIsNeeded = this.elementRef.getAttributeNode( attrs[i].nodeName ); if (cloneIsNeeded !== null) { container.setAttributeNode(cloneIsNeeded.cloneNode()); } } } // Replace the reference. if (this.elementRef.parentNode !== null) { this.elementRef.parentNode.replaceChild(container, this.elementRef); } // Changed the reference to the main element. It's ugly, but needed. this.elementRef = container; } if ( prevProps && this.props.isLinkEnabled && prevProps.link !== this.props.link ) { if (this.props.link !== null) { this.elementRef.setAttribute("href", this.props.link); } } // Change metadata related things. if ( !prevMeta || prevMeta.editMode !== this.meta.editMode || prevMeta.maintenanceMode !== this.meta.maintenanceMode ) { if (this.meta.editMode && this.meta.maintenanceMode === false) { this.elementRef.classList.add("is-editing"); this.elementRef.classList.remove("is-alert-triggered"); } else { this.elementRef.classList.remove("is-editing"); if (this.props.alertOutline) { this.elementRef.classList.add("is-alert-triggered"); } } } if (!prevMeta || prevMeta.isFetching !== this.meta.isFetching) { if (this.meta.isFetching) { this.elementRef.classList.add("is-fetching"); } else { this.elementRef.classList.remove("is-fetching"); } } if (!prevMeta || prevMeta.isUpdating !== this.meta.isUpdating) { if (this.meta.isUpdating) { this.elementRef.classList.add("is-updating"); const divParent = document.createElement("div"); divParent.className = "div-visual-console-spinner"; const divSpinner = document.createElement("div"); divSpinner.className = "visual-console-spinner"; divParent.appendChild(divSpinner); this.elementRef.appendChild(divParent); } else { this.elementRef.classList.remove("is-updating"); const div = this.elementRef.querySelector( ".div-visual-console-spinner" ); if (div !== null) { const parent = div.parentElement; if (parent !== null) { parent.removeChild(div); } } } this.updateDomElement(this.childElementRef); } if (!prevMeta || prevMeta.isSelected !== this.meta.isSelected) { if (this.meta.isSelected) { this.elementRef.classList.add("is-selected"); this.elementRef.setAttribute("id", "item-selected-move"); } else { this.elementRef.classList.remove("is-selected"); this.elementRef.removeAttribute("id"); } } } /** * To remove the event listeners and the elements from the DOM. */ public remove(): void { // Call the remove event. this.removeEventManager.emit({ item: this }); // Event listeners. this.disposables.forEach(disposable => { try { disposable.dispose(); } catch (ignored) {} // eslint-disable-line no-empty }); // VisualConsoleItem DOM element. this.elementRef.remove(); } /** * Compare the previous and the new position and return * a boolean value in case the position changed. * @param prevPosition * @param newPosition * @return Whether the position changed or not. */ protected positionChanged( prevPosition: Position, newPosition: Position ): boolean { return prevPosition.x !== newPosition.x || prevPosition.y !== newPosition.y; } /** * Move the label around the item content. * @param position Label position. */ protected changeLabelPosition(position: Props["labelPosition"]): void { switch (position) { case "up": this.elementRef.style.flexDirection = "column-reverse"; break; case "left": this.elementRef.style.flexDirection = "row-reverse"; break; case "right": this.elementRef.style.flexDirection = "row"; break; case "down": default: this.elementRef.style.flexDirection = "column"; break; } // Ugly table to show the label as its legacy counterpart. const tables = this.labelElementRef.getElementsByTagName("table"); const table = tables.length > 0 ? tables.item(0) : null; // Change the table size depending on its position. if (table) { switch (this.props.labelPosition) { case "up": case "down": if (this.props.width > 0) { table.style.width = `${this.props.width}px`; table.style.height = ""; } break; case "left": case "right": if (this.props.height > 0) { table.style.width = ""; table.style.height = `${this.props.height}px`; } break; } } } /** * Move the DOM container. * @param x Horizontal axis position. * @param y Vertical axis position. */ public moveElement(x: number, y: number): void { this.elementRef.style.left = `${x}px`; this.elementRef.style.top = `${y}px`; } /** * Update the position into the properties and move the DOM container. * @param x Horizontal axis position. * @param y Vertical axis position. */ public move(x: number, y: number): void { this.moveElement(x, y); this.itemProps = { ...this.props, // Object spread: http://es6-features.org/#SpreadOperator x, y }; } /** * Compare the previous and the new size and return * a boolean value in case the size changed. * @param prevSize * @param newSize * @return Whether the size changed or not. */ protected sizeChanged(prevSize: Size, newSize: Size): boolean { return ( prevSize.width !== newSize.width || prevSize.height !== newSize.height ); } /** * Resize the DOM content container. * @param width * @param height */ public resizeElement(width: number, height: number): void { // The most valuable size is the content size. if ( this.props.type != ItemType.LINE_ITEM && this.props.type != ItemType.NETWORK_LINK ) { this.childElementRef.style.width = width > 0 ? `${width}px` : ""; this.childElementRef.style.height = height > 0 ? `${height}px` : ""; } if (this.props.label && this.props.label.length > 0) { // Ugly table to show the label as its legacy counterpart. const tables = this.labelElementRef.getElementsByTagName("table"); const table = tables.length > 0 ? tables.item(0) : null; if (table) { switch (this.props.labelPosition) { case "up": case "down": table.style.width = width > 0 ? `${width}px` : ""; break; case "left": case "right": table.style.height = height > 0 ? `${height}px` : ""; break; } } } } /** * Update the size into the properties and resize the DOM container. * @param width * @param height */ public resize(width: number, height: number): void { this.resizeElement(width, height); this.itemProps = { ...this.props, // Object spread: http://es6-features.org/#SpreadOperator width, height }; } /** * To add an event handler to the click of the linked visual console elements. * @param listener Function which is going to be executed when a linked console is clicked. */ public onClick(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.clickEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the double click of the linked visual console elements. * @param listener Function which is going to be executed when a linked console is double clicked. */ public onDblClick(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.dblClickEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the movement of visual console elements. * @param listener Function which is going to be executed when a linked console is moved. */ public onMoved(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.movedEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the movement stopped of visual console elements. * @param listener Function which is going to be executed when a linked console's movement is finished. */ public onMovementFinished(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.movementFinishedEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the resizement of visual console elements. * @param listener Function which is going to be executed when a linked console is moved. */ public onResized(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.resizedEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the resizement finish of visual console elements. * @param listener Function which is going to be executed when a linked console is finished resizing. */ public onResizeFinished(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.resizeFinishedEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to the removal of the item. * @param listener Function which is going to be executed when a item is removed. */ public onRemove(listener: Listener): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.removeEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * To add an event handler to item selection. * @param listener Function which is going to be executed when a item is removed. */ public onSelectionChanged( listener: Listener ): Disposable { /* * The '.on' function returns a function which will clean the event * listener when executed. We store all the 'dispose' functions to * call them when the item should be cleared. */ const disposable = this.selectionChangedEventManager.on(listener); this.disposables.push(disposable); return disposable; } /** * Select an item. * @param itemId Item Id. * @param unique To remove the selection of other items or not. */ public selectItem(): void { this.meta = { ...this.meta, isSelected: true }; this.initMovementListener(this.elementRef); if ( this.props.type !== ItemType.LINE_ITEM && this.props.type !== ItemType.NETWORK_LINK ) { this.initResizementListener(this.elementRef); } } /** * Unselect an item. * @param itemId Item Id. */ public unSelectItem(): void { this.meta = { ...this.meta, isSelected: false }; this.stopMovementListener(); if (this.props.type !== ItemType.LINE_ITEM) { this.stopResizementListener(); } } // TODO: Document public getFormContainer(): FormContainer { return VisualConsoleItem.getFormContainer(this.props); } // TODO: Document public static getFormContainer(props: Partial): FormContainer { const title: string = props.type ? titleItem(props.type) : t("Item"); return new FormContainer(title, [], []); } } export default VisualConsoleItem;