@senx/discovery-widgets
Version:
Discovery Widgets Elements
610 lines (609 loc) • 22.9 kB
JavaScript
/*
* 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