import { LinkedVisualConsoleProps, AnyObject, WithModuleProps } from "../lib/types"; import { linkedVCPropsDecoder, modulePropsDecoder, decodeBase64, stringIsEmpty, parseIntOr } from "../lib"; import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item"; export type BasicChartProps = { type: ItemType.BASIC_CHART; html: string; period: number | null; value: number | null; status: string; moduleNameColor: string; } & ItemProps & WithModuleProps & LinkedVisualConsoleProps; /** * 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 basic chart props. * @throws Will throw a TypeError if some property * is missing from the raw object or have an invalid type. */ export function basicChartPropsDecoder( data: AnyObject ): BasicChartProps | never { if (stringIsEmpty(data.html) && stringIsEmpty(data.encodedHtml)) { throw new TypeError("missing html content."); } return { ...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects. type: ItemType.BASIC_CHART, html: !stringIsEmpty(data.html) ? data.html : decodeBase64(data.encodedHtml), period: parseIntOr(data.period, null), value: parseFloat(data.value), status: stringIsEmpty(data.status) ? "#B2B2B2" : data.status, moduleNameColor: stringIsEmpty(data.moduleNameColor) ? "#3f3f3f" : data.moduleNameColor, ...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects. ...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects. }; } export default class BasicChart extends Item { protected createDomElement(): HTMLElement { const element = document.createElement("div"); const header = document.createElement("div"); header.className = "basic-chart-header"; const moduleName = document.createElement("h2"); moduleName.className = "basic-chart-header-name"; moduleName.textContent = this.props.moduleName; moduleName.style.color = `${this.props.moduleNameColor}`; header.appendChild(moduleName); let value = ""; if (this.props.value !== null) { value = this.number_format(this.props.value, false, "", 2, 1000); } const moduleValue = document.createElement("h2"); moduleValue.className = "basic-chart-header-value"; moduleValue.textContent = `${value}`; moduleValue.style.color = this.props.status; header.appendChild(moduleValue); element.innerHTML = this.props.html; element.className = "basic-chart"; if ( this.props.agentDisabled === true || this.props.moduleDisabled === true ) { element.style.opacity = "0.2"; } // Remove the overview graph. const legendP = element.getElementsByTagName("p"); for (let i = 0; i < legendP.length; i++) { legendP[i].style.margin = "0px"; } // Remove the overview graph. const overviewGraphs = element.getElementsByClassName("overview_graph"); for (let i = 0; i < overviewGraphs.length; i++) { overviewGraphs[i].remove(); } // Hack to execute the JS after the HTML is added to the DOM. const scripts = element.getElementsByTagName("script"); for (let i = 0; i < scripts.length; i++) { if (scripts[i].src.length === 0) { setTimeout(() => { try { eval(scripts[i].innerHTML.trim()); } catch (ignored) {} // eslint-disable-line no-empty }, 0); } } element.innerHTML = this.props.html; element.insertBefore(header, element.firstChild); return element; } protected updateDomElement(element: HTMLElement): void { const header = document.createElement("div"); header.className = "basic-chart-header"; const moduleName = document.createElement("h2"); moduleName.className = "basic-chart-header-name"; moduleName.textContent = this.props.moduleName; moduleName.style.color = `${this.props.moduleNameColor}`; header.appendChild(moduleName); let value = ""; if (this.props.value !== null) { value = this.number_format(this.props.value, false, "", 2, 1000); } const moduleValue = document.createElement("h2"); moduleValue.className = "basic-chart-header-value"; moduleValue.textContent = `${value}`; moduleValue.style.color = this.props.status; header.appendChild(moduleValue); element.innerHTML = this.props.html; element.insertBefore(header, element.firstChild); // Remove the overview graph. const legendP = element.getElementsByTagName("p"); for (let i = 0; i < legendP.length; i++) { legendP[i].style.margin = "0px"; } // Remove the overview graph. const overviewGraphs = element.getElementsByClassName("overview_graph"); for (let i = 0; i < overviewGraphs.length; i++) { overviewGraphs[i].remove(); } // Hack to execute the JS after the HTML is added to the DOM. const scripts = element.getElementsByTagName("script"); for (let i = 0; i < scripts.length; i++) { if (scripts[i].src.length === 0) { eval(scripts[i].innerHTML.trim()); } } } protected number_format( number: number, force_integer: boolean, unit: string, short_data: number, divisor: number ) { divisor = typeof divisor !== "undefined" ? divisor : 1000; var decimals = 2; // Set maximum decimal precision to 99 in case short_data is not set. if (!short_data) { short_data = 99; } if (force_integer) { if (Math.round(number) != number) { return ""; } } else { short_data++; const aux_decimals = this.pad("1", short_data, 0); number = Math.round(number * Number.parseInt(aux_decimals)) / Number.parseInt(aux_decimals); } var shorts = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"]; var pos = 0; while (Math.abs(number) >= divisor) { // As long as the number can be divided by 1000 or 1024. pos++; number = number / divisor; } if (divisor) { number = Math.round(number * decimals) / decimals; } else { number = Math.round(number * decimals); } if (isNaN(number)) { number = 0; } return number + " " + shorts[pos] + unit; } protected pad(input: string, length: number, padding: number): string { var str = input + ""; return length <= str.length ? str : this.pad(str + padding, length, padding); } }