UNPKG

@senx/discovery-widgets

Version:

Discovery Widgets Elements

610 lines (609 loc) 22.9 kB
/* * Copyright 2022-2025 SenX S.A.S. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { h, Host } from "@stencil/core"; import { DataModel } from "../../model/types"; import { Param } from "../../model/param"; import { Logger } from "../../utils/logger"; import { GTSLib } from "../../utils/gts.lib"; import { Utils } from "../../utils/utils"; import html2canvas from "html2canvas"; export class DiscoverySvgComponent { constructor() { this.options = new Param(); this.debug = false; this.unit = ''; this.execTime = 0; this.parsing = false; this.toDisplay = []; this.defOptions = new Param(); this.funqueue = []; this.refs = []; this.listenersAdded = false; } updateRes() { this.innerResult = GTSLib.getData(this.result); this.parseResult(); } optionsUpdate(newValue, oldValue) { var _a, _b; (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['optionsUpdate'], newValue, oldValue); let opts = newValue; if (!!newValue && typeof newValue === 'string') { opts = JSON.parse(newValue); } if (!Utils.deepEqual(opts, this.innerOptions)) { this.innerOptions = Utils.clone(opts); setTimeout(() => this.parseResult()); (_b = this.LOG) === null || _b === void 0 ? void 0 : _b.debug(['optionsUpdate 2'], { options: this.innerOptions, newValue, oldValue }); } } discoveryEventHandler(event) { this.funqueue.push(this.wrapFunction(this.processEvent.bind(this), this, [event])); } async resize() { const dims = Utils.getContentBounds(this.el.parentElement); this.width = dims.w; this.height = dims.h; return Promise.resolve(); } componentWillLoad() { const dims = Utils.getContentBounds(this.el.parentElement); this.width = dims.w; this.height = dims.h; this.parseResult(); this.processQueue(); } convert(data) { var _a, _b, _c; const toDisplay = []; this.refs = []; (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['convert'], data); let options = Utils.mergeDeep(this.defOptions, this.innerOptions || {}); options = Utils.mergeDeep(options || {}, data.globalParams); this.innerOptions = Utils.clone(options); if (this.innerOptions.customStyles) { this.innerStyle = Utils.clone(Object.assign(Object.assign({}, this.innerStyle), (_b = this.innerOptions.customStyles) !== null && _b !== void 0 ? _b : {})); } if (GTSLib.isArray(data.data)) { (data.data || []).forEach(img => { var _a; (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['convert'], DiscoverySvgComponent.isSVG(img)); if (DiscoverySvgComponent.isSVG(img)) { toDisplay.push(this.sanitize(img)); } }); } else if (data.data && DiscoverySvgComponent.isSVG(data.data)) { (_c = this.LOG) === null || _c === void 0 ? void 0 : _c.debug(['convert'], DiscoverySvgComponent.isSVG(data.data)); toDisplay.push(this.sanitize(data.data)); } return toDisplay; } processEvent(event) { return new Promise(resolve => { var _a; const res = Utils.parseEventData(event.detail, this.innerOptions.eventHandler, this.el.id); if (res.style) { this.innerStyle = Object.assign(Object.assign({}, this.innerStyle), res.style); } if (res.xpath) { const toDisplay = []; ((_a = this.toDisplay) !== null && _a !== void 0 ? _a : []).forEach(img => { var _a; (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['convert'], DiscoverySvgComponent.isSVG(img)); if (DiscoverySvgComponent.isSVG(img)) { toDisplay.push(this.sanitize(img, res.xpath.selector, res.xpath.value)); } }); this.toDisplay = [...toDisplay]; } resolve(true); }); } wrapFunction(fn, context, params) { return () => fn.apply(context, params); } processQueue() { // eslint-disable-next-line no-async-promise-executor void new Promise(async (resolve) => { while (this.funqueue.length > 0) await (this.funqueue.shift())(); resolve(true); }).then(() => setTimeout(() => this.processQueue(), 100)); } parseResult() { var _a, _b; this.parsing = true; this.LOG = new Logger(DiscoverySvgComponent, this.debug); if (typeof this.options === 'string') { this.innerOptions = JSON.parse(this.options); } else { this.innerOptions = this.options; } this.innerResult = GTSLib.getData(this.result); this.toDisplay = this.convert((_a = this.innerResult) !== null && _a !== void 0 ? _a : new DataModel()); (_b = this.LOG) === null || _b === void 0 ? void 0 : _b.debug(['componentWillLoad'], { type: this.type, options: this.innerOptions, toDisplay: this.toDisplay, }); this.parsing = false; this.draw.emit(); } static isSVG(data) { return typeof data === 'string' && /<svg/gi.test(data); } sanitize(svg, xpath, replacement) { var _a, _b, _c; try { const svgDoc = Utils.parseXML(svg.trim(), 'image/svg+xml'); // trim to avoid weird bugs with spaces before xml processing instruction <? ?> (forbidden) const el = svgDoc.getElementsByTagName('svg').item(0); if (xpath) { let nsXpath = xpath.split('/').filter(e => !!e).map(e => 'svg:' + e).join('/'); if (!nsXpath.startsWith('svg:svg')) { nsXpath = '//' + nsXpath; } let nsr = prefix => { if (prefix === 'svg') { return 'http://www.w3.org/2000/svg'; } else if (prefix === 'inkscape') { // allow an easy use of inkscape:label for example, in the xpath. '//*[@inkscape:label="text2"]' return 'http://www.inkscape.org/namespaces/inkscape'; } else { return null; } }; const iterator = svgDoc.evaluate(nsXpath, svgDoc, nsr, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); let elem = iterator.iterateNext(); const elemsToReplace = []; while (elem) { elemsToReplace.push(elem); elem = iterator.iterateNext(); } for (const e of elemsToReplace) { if (typeof replacement === 'string') { const parent = e.parentElement; const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); g.innerHTML = replacement.trim(); parent.replaceChild(g.firstChild, e); } else { Object.keys(replacement).forEach(k => { if ('innerHTML' !== k) { e.setAttribute(k, replacement[k].toString()); } else { e.innerHTML = replacement[k].toString(); } }); } } } if (!((_a = el.getAttribute('width')) === null || _a === void 0 ? void 0 : _a.endsWith('px')) && !!el.getAttribute('viewBox')) { const vb = el.getAttribute('viewBox').split(' '); el.setAttribute('width', vb[2] + 'px'); } if (!((_b = el.getAttribute('height')) === null || _b === void 0 ? void 0 : _b.endsWith('px')) && !!el.getAttribute('viewBox')) { const vb = el.getAttribute('viewBox').split(' '); el.setAttribute('height', vb[3] + 'px'); } if (el.getAttribute('width') && el.getAttribute('height')) { el.setAttribute('viewBox', '0 0 ' + el.getAttribute('width').replace(/[a-z]+/gi, '') + ' ' + el.getAttribute('height').replace(/[a-z]+/gi, '')); } if (el.getAttribute('preserveAspectRatio')) { el.removeAttribute('preserveAspectRatio'); } el.setAttribute('preserveAspectRatio', 'xMidYMid meet'); return new XMLSerializer().serializeToString(svgDoc); } catch (e) { (_c = this.LOG) === null || _c === void 0 ? void 0 : _c.error(['exec'], e); return svg; } } triggerEvent(evt) { this.discoveryEvent.emit(Object.assign(Object.assign({}, evt), { source: this.el.id })); } async export(type = 'png') { return type === 'svg' ? this.toDisplay : (await html2canvas(this.el)).toDataURL(); } generateStyle(innerStyle) { return Object.keys(innerStyle || {}).map(k => k + ' { ' + innerStyle[k] + ' }').join('\n'); } // noinspection JSUnusedGlobalSymbols componentDidRender() { this.addHandlers(); } render() { this.listenersAdded = false; return (h(Host, { key: '40e3b0492786284a1bce1cab203419f76e7da9ef' }, h("style", { key: 'cd6d654d19c1b321120cc030914a5226b8ba793c' }, this.generateStyle(this.innerStyle)), h("div", { key: 'bd89ed41d6ec1a3f8461e7ece94a643dbea5de91', class: "svg-wrapper", style: { width: `${this.width}px`, height: `${this.height}px` } }, this.parsing ? h("discovery-spinner", null, "Parsing data...") : this.toDisplay.length > 0 ? this.toDisplay.map(svg => h("div", { class: "svg-container", innerHTML: svg, ref: el => this.refs.push(el) })) : ''))); } addHandlers() { var _a, _b; if (!this.listenersAdded) { for (const svgWrapper of this.refs) { for (const h of (_b = (_a = this.innerOptions.svg) === null || _a === void 0 ? void 0 : _a.handlers) !== null && _b !== void 0 ? _b : []) { if (h.selector) { for (const elem of svgWrapper.querySelectorAll(h.selector)) { elem.classList.add('hoverable'); if (h.click) { elem.addEventListener('click', e => { this.triggerEvent(h.event); e.stopImmediatePropagation(); }); } if (h.hover) { elem.addEventListener('mouseover', e => { this.triggerEvent(h.event); e.stopImmediatePropagation(); }); } } } } } this.listenersAdded = true; } } static get is() { return "discovery-svg"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["discovery-svg.scss"] }; } static get styleUrls() { return { "$": ["discovery-svg.css"] }; } static get properties() { return { "result": { "type": "string", "mutable": false, "complexType": { "original": "DataModel | string", "resolved": "DataModel | string", "references": { "DataModel": { "location": "import", "path": "../../model/types", "id": "src/model/types.ts::DataModel" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "result", "reflect": false }, "type": { "type": "string", "mutable": false, "complexType": { "original": "ChartType", "resolved": "string", "references": { "ChartType": { "location": "import", "path": "../../model/types", "id": "src/model/types.ts::ChartType" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "type", "reflect": false }, "start": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "start", "reflect": false }, "options": { "type": "string", "mutable": false, "complexType": { "original": "Param | string", "resolved": "Param | string", "references": { "Param": { "location": "import", "path": "../../model/param", "id": "src/model/param.ts::Param" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "options", "reflect": false, "defaultValue": "new Param()" }, "width": { "type": "number", "mutable": true, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "width", "reflect": false }, "height": { "type": "number", "mutable": true, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "height", "reflect": false }, "debug": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "debug", "reflect": false, "defaultValue": "false" }, "unit": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "unit", "reflect": false, "defaultValue": "''" }, "url": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "url", "reflect": false }, "chartTitle": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "chart-title", "reflect": false } }; } static get states() { return { "execTime": {}, "bgColor": {}, "fontColor": {}, "parsing": {}, "toDisplay": {}, "innerStyle": {}, "innerResult": {}, "innerOptions": {} }; } static get events() { return [{ "method": "draw", "name": "draw", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "discoveryEvent", "name": "discoveryEvent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "DiscoveryEvent", "resolved": "DiscoveryEvent", "references": { "DiscoveryEvent": { "location": "import", "path": "../../model/types", "id": "src/model/types.ts::DiscoveryEvent" } } } }]; } static get methods() { return { "resize": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<void>" }, "docs": { "text": "", "tags": [] } }, "export": { "complexType": { "signature": "(type?: \"png\" | \"svg\") => Promise<string | string[]>", "parameters": [{ "name": "type", "type": "\"svg\" | \"png\"", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<string | string[]>" }, "docs": { "text": "", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "result", "methodName": "updateRes" }, { "propName": "options", "methodName": "optionsUpdate" }]; } static get listeners() { return [{ "name": "discoveryEvent", "method": "discoveryEventHandler", "target": "window", "capture": false, "passive": false }]; } } //# sourceMappingURL=discovery-svg.js.map