UNPKG

@senx/discovery-widgets

Version:

Discovery Widgets Elements

575 lines (536 loc) 24 kB
import { proxyCustomElement, HTMLElement, createEvent, h } from '@stencil/core/internal/client'; import { b as DataModel } from './types.js'; import { P as Param, G as GTSLib, U as Utils, L as Logger } from './utils.js'; import { a as html2canvas } from './html2canvas.js'; import { d as defineCustomElement$2 } from './discovery-pageable2.js'; import { d as defineCustomElement$1 } from './discovery-spinner2.js'; var StreamSaver = {exports: {}}; /*! streamsaver. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */ (function (module) { ((name, definition) => { module.exports = definition() ; })('streamSaver', () => { const global = typeof window === 'object' ? window : this; if (!global.HTMLElement) console.warn('streamsaver is meant to run on browsers main thread'); let mitmTransporter = null; let supportsTransferable = false; const test = fn => { try { fn(); } catch (e) {} }; const ponyfill = global.WebStreamsPolyfill || {}; const isSecureContext = global.isSecureContext; // TODO: Must come up with a real detection test (#69) let useBlobFallback = /constructor/i.test(global.HTMLElement) || !!global.safari || !!global.WebKitPoint; const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style ? 'iframe' : 'navigate'; const streamSaver = { createWriteStream, WritableStream: global.WritableStream || ponyfill.WritableStream, supported: true, version: { full: '2.0.5', major: 2, minor: 0, dot: 5 }, mitm: 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0' }; /** * create a hidden iframe and append it to the DOM (body) * * @param {string} src page to load * @return {HTMLIFrameElement} page to load */ function makeIframe (src) { if (!src) throw new Error('meh') const iframe = document.createElement('iframe'); iframe.hidden = true; iframe.src = src; iframe.loaded = false; iframe.name = 'iframe'; iframe.isIframe = true; iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args); iframe.addEventListener('load', () => { iframe.loaded = true; }, { once: true }); document.body.appendChild(iframe); return iframe } /** * create a popup that simulates the basic things * of what a iframe can do * * @param {string} src page to load * @return {object} iframe like object */ function makePopup (src) { const options = 'width=200,height=100'; const delegate = document.createDocumentFragment(); const popup = { frame: global.open(src, 'popup', options), loaded: false, isIframe: false, isPopup: true, remove () { popup.frame.close(); }, addEventListener (...args) { delegate.addEventListener(...args); }, dispatchEvent (...args) { delegate.dispatchEvent(...args); }, removeEventListener (...args) { delegate.removeEventListener(...args); }, postMessage (...args) { popup.frame.postMessage(...args); } }; const onReady = evt => { if (evt.source === popup.frame) { popup.loaded = true; global.removeEventListener('message', onReady); popup.dispatchEvent(new Event('load')); } }; global.addEventListener('message', onReady); return popup } try { // We can't look for service worker since it may still work on http new Response(new ReadableStream()); if (isSecureContext && !('serviceWorker' in navigator)) { useBlobFallback = true; } } catch (err) { useBlobFallback = true; } test(() => { // Transferable stream was first enabled in chrome v73 behind a flag const { readable } = new TransformStream(); const mc = new MessageChannel(); mc.port1.postMessage(readable, [readable]); mc.port1.close(); mc.port2.close(); supportsTransferable = true; // Freeze TransformStream object (can only work with native) Object.defineProperty(streamSaver, 'TransformStream', { configurable: false, writable: false, value: TransformStream }); }); function loadTransporter () { if (!mitmTransporter) { mitmTransporter = isSecureContext ? makeIframe(streamSaver.mitm) : makePopup(streamSaver.mitm); } } /** * @param {string} filename filename that should be used * @param {object} options [description] * @param {number} size deprecated * @return {WritableStream<Uint8Array>} */ function createWriteStream (filename, options, size) { let opts = { size: null, pathname: null, writableStrategy: undefined, readableStrategy: undefined }; let bytesWritten = 0; // by StreamSaver.js (not the service worker) let downloadUrl = null; let channel = null; let ts = null; // normalize arguments if (Number.isFinite(options)) { [ size, options ] = [ options, size ]; console.warn('[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream'); opts.size = size; opts.writableStrategy = options; } else if (options && options.highWaterMark) { console.warn('[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream'); opts.size = size; opts.writableStrategy = options; } else { opts = options || {}; } if (!useBlobFallback) { loadTransporter(); channel = new MessageChannel(); // Make filename RFC5987 compatible filename = encodeURIComponent(filename.replace(/\//g, ':')) .replace(/['()]/g, escape) .replace(/\*/g, '%2A'); const response = { transferringReadable: supportsTransferable, pathname: opts.pathname || Math.random().toString().slice(-6) + '/' + filename, headers: { 'Content-Type': 'application/octet-stream; charset=utf-8', 'Content-Disposition': "attachment; filename*=UTF-8''" + filename } }; if (opts.size) { response.headers['Content-Length'] = opts.size; } const args = [ response, '*', [ channel.port2 ] ]; if (supportsTransferable) { const transformer = downloadStrategy === 'iframe' ? undefined : { // This transformer & flush method is only used by insecure context. transform (chunk, controller) { if (!(chunk instanceof Uint8Array)) { throw new TypeError('Can only write Uint8Arrays') } bytesWritten += chunk.length; controller.enqueue(chunk); if (downloadUrl) { location.href = downloadUrl; downloadUrl = null; } }, flush () { if (downloadUrl) { location.href = downloadUrl; } } }; ts = new streamSaver.TransformStream( transformer, opts.writableStrategy, opts.readableStrategy ); const readableStream = ts.readable; channel.port1.postMessage({ readableStream }, [ readableStream ]); } channel.port1.onmessage = evt => { // Service worker sent us a link that we should open. if (evt.data.download) { // Special treatment for popup... if (downloadStrategy === 'navigate') { mitmTransporter.remove(); mitmTransporter = null; if (bytesWritten) { location.href = evt.data.download; } else { downloadUrl = evt.data.download; } } else { if (mitmTransporter.isPopup) { mitmTransporter.remove(); mitmTransporter = null; // Special case for firefox, they can keep sw alive with fetch if (downloadStrategy === 'iframe') { makeIframe(streamSaver.mitm); } } // We never remove this iframes b/c it can interrupt saving makeIframe(evt.data.download); } } else if (evt.data.abort) { chunks = []; channel.port1.postMessage('abort'); //send back so controller is aborted channel.port1.onmessage = null; channel.port1.close(); channel.port2.close(); channel = null; } }; if (mitmTransporter.loaded) { mitmTransporter.postMessage(...args); } else { mitmTransporter.addEventListener('load', () => { mitmTransporter.postMessage(...args); }, { once: true }); } } let chunks = []; return (!useBlobFallback && ts && ts.writable) || new streamSaver.WritableStream({ write (chunk) { if (!(chunk instanceof Uint8Array)) { throw new TypeError('Can only write Uint8Arrays') } if (useBlobFallback) { // Safari... The new IE6 // https://github.com/jimmywarting/StreamSaver.js/issues/69 // // even though it has everything it fails to download anything // that comes from the service worker..! chunks.push(chunk); return } // is called when a new chunk of data is ready to be written // to the underlying sink. It can return a promise to signal // success or failure of the write operation. The stream // implementation guarantees that this method will be called // only after previous writes have succeeded, and never after // close or abort is called. // TODO: Kind of important that service worker respond back when // it has been written. Otherwise we can't handle backpressure // EDIT: Transferable streams solves this... channel.port1.postMessage(chunk); bytesWritten += chunk.length; if (downloadUrl) { location.href = downloadUrl; downloadUrl = null; } }, close () { if (useBlobFallback) { const blob = new Blob(chunks, { type: 'application/octet-stream; charset=utf-8' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); } else { channel.port1.postMessage('end'); } }, abort () { chunks = []; channel.port1.postMessage('abort'); channel.port1.onmessage = null; channel.port1.close(); channel.port2.close(); channel = null; } }, opts.writableStrategy) } return streamSaver }); }(StreamSaver)); const streamSaver = StreamSaver.exports; const discoveryTabularCss = "/*!\n * Copyright 2022-2024 SenX S.A.S.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *//*!\n* Copyright 2022-2024 SenX S.A.S.\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/:host .tabular-action-button{position:relative;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:end;-ms-flex-direction:row;flex-direction:row}:host .tabular-action-button button{background-color:transparent;border:none;cursor:pointer;color:rgb(102, 102, 102);padding-top:10px}:host .tabular-action-button button:hover{color:rgb(62, 152, 197)}:host .tabular-wrapper{width:calc(100% - 20px);height:calc(100% - 50px);position:relative;overflow-y:auto;margin:0;padding:10px}:host .tabular-wrapper .tabular-wrapper-inner{position:absolute;width:calc(100% - 20px);left:10px;right:0;bottom:0;top:0}"; const DiscoveryTabularStyle0 = discoveryTabularCss; const DiscoveryTabular = /*@__PURE__*/ proxyCustomElement(class DiscoveryTabular extends HTMLElement { constructor() { super(); this.__registerHost(); this.__attachShadow(); this.draw = createEvent(this, "draw", 7); this.dataPointOver = createEvent(this, "dataPointOver", 7); this.dataPointSelected = createEvent(this, "dataPointSelected", 7); this.discoveryEvent = createEvent(this, "discoveryEvent", 7); this.options = Object.assign(Object.assign({}, new Param()), { timeMode: 'date' }); this.debug = false; this.parsing = false; this.rendering = false; this.tabularData = []; this.divider = 1000; } updateRes() { this.tabularData = this.convert(GTSLib.getData(this.result)); } async resize() { const dims = Utils.getContentBounds(this.el.parentElement); this.width = dims.w; this.height = dims.h; return Promise.resolve(); } // noinspection JSUnusedLocalSymbols async export(_type = 'png') { return (await html2canvas(this.pngWrapper, { allowTaint: true, backgroundColor: this.options.bgColor || Utils.getCSSColor(this.el, '--warp-view-tile-background', '#fff'), })).toDataURL(); } // noinspection JSUnusedGlobalSymbols componentWillLoad() { var _a, _b, _c; this.parsing = true; this.LOG = new Logger(DiscoveryTabular, this.debug); if (typeof this.options === 'string') { this.options = JSON.parse(this.options); } this.result = GTSLib.getData(this.result); this.divider = GTSLib.getDivider((_a = this.options.timeUnit) !== null && _a !== void 0 ? _a : 'us'); this.tabularData = this.convert((_b = this.result) !== null && _b !== void 0 ? _b : new DataModel()); (_c = this.LOG) === null || _c === void 0 ? void 0 : _c.debug(['componentWillLoad'], { type: this.type, options: this.options, }); const dims = Utils.getContentBounds(this.el.parentElement); this.width = dims.w; this.height = dims.h; this.el.addEventListener('mouseout', () => this.dataPointOver.emit({})); } static getHeaderParam(data, i, j, key, def) { return data.params && data.params[i] && data.params[i][key] && data.params[i][key][j] ? data.params[i][key][j] : data.globalParams && data.globalParams[key] && data.globalParams[key][j] ? data.globalParams[key][j] : def; } handleDataPointOver(event) { event.stopImmediatePropagation(); this.dataPointOver.emit(event.detail); } handleDataPointSelected(event) { event.stopImmediatePropagation(); this.dataPointSelected.emit(event.detail); } convert(data) { var _a, _b, _c; let options = Utils.mergeDeep(Object.assign(Object.assign({}, new Param()), { timeMode: 'date' }), (_a = this.options) !== null && _a !== void 0 ? _a : {}); options = Utils.mergeDeep(options !== null && options !== void 0 ? options : {}, data.globalParams); this.options = Utils.clone(options); this.params = (_b = data.params) !== null && _b !== void 0 ? _b : []; let dataGrid; if (GTSLib.isArray(data.data)) { const dataList = GTSLib.flatDeep(data.data); (_c = this.LOG) === null || _c === void 0 ? void 0 : _c.debug(['convert', 'isArray'], dataList, options); if (data.data.length > 0 && GTSLib.isGts(dataList[0])) { dataGrid = this.parseData(data, dataList); } else { dataGrid = this.parseCustomData(data, dataList); } } else { dataGrid = this.parseCustomData(data, [data.data]); } this.parsing = false; return dataGrid; } parseCustomData(dataModel, data) { var _a; const flatData = []; data.forEach(d => { var _a, _b, _c, _d; if (d !== null && d !== undefined) { const dataSet = { name: (_a = d === null || d === void 0 ? void 0 : d.title) !== null && _a !== void 0 ? _a : '', values: (_b = d === null || d === void 0 ? void 0 : d.rows) !== null && _b !== void 0 ? _b : [], headers: (_c = d === null || d === void 0 ? void 0 : d.columns) !== null && _c !== void 0 ? _c : [], isGTS: false, params: (_d = d === null || d === void 0 ? void 0 : d.params) !== null && _d !== void 0 ? _d : [], }; flatData.push(dataSet); } }); (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['parseCustomData', 'flatData'], flatData, dataModel); return flatData; } parseData(dataModel, data) { var _a, _b; const flatData = []; (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['parseData'], data); data.forEach((d, i) => { var _a, _b; const dataSet = { name: '', values: [], headers: [], isGTS: false, params: this.params, }; if (GTSLib.isGts(d)) { (_a = this.LOG) === null || _a === void 0 ? void 0 : _a.debug(['parseData', 'isGts'], d); dataSet.name = ((dataModel.params || [])[i] || { key: undefined }).key || GTSLib.serializeGtsMetadata(d); dataSet.values = d.v; // .map(v => [this.formatDate(v[0])].concat(v.slice(1, v.length))); dataSet.isGTS = true; dataSet.c = d.c; dataSet.l = d.l; dataSet.a = d.a; } else { (_b = this.LOG) === null || _b === void 0 ? void 0 : _b.debug(['parseData', 'is not a Gts'], d); dataSet.values = GTSLib.isArray(d) ? d : [d]; } dataSet.headers = [DiscoveryTabular.getHeaderParam(dataModel, i, 0, 'headers', 'Date')]; if (d.v && d.v.length > 0 && d.v[0].length > 2) { dataSet.headers.push(DiscoveryTabular.getHeaderParam(dataModel, i, 1, 'headers', 'Latitude')); } if (d.v && d.v.length > 0 && d.v[0].length > 3) { dataSet.headers.push(DiscoveryTabular.getHeaderParam(dataModel, i, 2, 'headers', 'Longitude')); } if (d.v && d.v.length > 0 && d.v[0].length > 4) { dataSet.headers.push(DiscoveryTabular.getHeaderParam(dataModel, i, 3, 'headers', 'Elevation')); } if (d.v && d.v.length > 0) { dataSet.headers.push(DiscoveryTabular.getHeaderParam(dataModel, i, d.v[0].length - 1, 'headers', 'Value')); } if (dataSet.values.length > 0) { flatData.push(dataSet); } }); (_b = this.LOG) === null || _b === void 0 ? void 0 : _b.debug(['parseData', 'flatData'], flatData, this.options); return flatData; } addCSVHeader(headers, k) { if (!headers.includes(k)) { headers.push(k); } } addPageable(elem) { this.pageables.push(elem); } async csvExport() { var _a; const headers = []; const csv = []; const tabularData = []; for (const p of this.pageables) { tabularData.push(await p.getData()); } tabularData.forEach(t => { var _a; ((_a = t.headers) !== null && _a !== void 0 ? _a : []).forEach((h) => this.addCSVHeader(headers, h)); for (const v of t.data) { csv.push(v); } }); const csvTxt = headers.join(';') + '\n' + csv.map(line => headers.map(h => { var _a; return (_a = line[h]) !== null && _a !== void 0 ? _a : ''; }).join(';')).join('\n'); const uInt8 = new TextEncoder().encode(csvTxt); const fileStream = streamSaver.createWriteStream(((_a = this.options.title) !== null && _a !== void 0 ? _a : 'export') + '.csv', { size: uInt8.byteLength, // (optional filesize) Will show progress writableStrategy: undefined, // (optional) readableStrategy: undefined, // (optional) }); const writer = fileStream.getWriter(); await writer.write(uInt8); await writer.close(); } render() { this.draw.emit(); this.pageables = []; return [ this.options.showControls ? h("div", { class: "tabular-action-button" }, h("button", { class: "tabular-export-csv", title: "CSV Export", onClick: () => void this.csvExport() }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16" }, h("path", { d: "M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" }), h("path", { d: "M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" })))) : '', h("div", { key: '30335a2af8c4ae7b6da21a94d34882dee635fa7e', class: "tabular-wrapper", ref: (el) => this.pngWrapper = el }, h("div", { key: '2bd8f7ae1bf35c841452599552cd55cd8e29b3bf', class: "tabular-wrapper-inner" }, this.parsing ? h("discovery-spinner", null, "Parsing data...") : '', this.rendering ? h("discovery-spinner", null, "Rendering data...") : '', this.tabularData.map(d => h("discovery-pageable", { data: d, onDataPointOver: event => this.handleDataPointOver(event), onDataPointSelected: event => this.handleDataPointSelected(event), divider: this.divider, options: this.options, ref: elem => this.addPageable(elem), debug: this.debug })))), ]; } get el() { return this; } static get watchers() { return { "result": ["updateRes"] }; } static get style() { return DiscoveryTabularStyle0; } }, [1, "discovery-tabular", { "result": [1025], "type": [1], "options": [1025], "width": [1026], "height": [1026], "debug": [4], "unit": [1], "parsing": [32], "rendering": [32], "tabularData": [32], "resize": [64], "export": [64] }, undefined, { "result": ["updateRes"] }]); function defineCustomElement() { if (typeof customElements === "undefined") { return; } const components = ["discovery-tabular", "discovery-pageable", "discovery-spinner"]; components.forEach(tagName => { switch (tagName) { case "discovery-tabular": if (!customElements.get(tagName)) { customElements.define(tagName, DiscoveryTabular); } break; case "discovery-pageable": if (!customElements.get(tagName)) { defineCustomElement$2(); } break; case "discovery-spinner": if (!customElements.get(tagName)) { defineCustomElement$1(); } break; } }); } export { DiscoveryTabular as D, defineCustomElement as d }; //# sourceMappingURL=discovery-tabular2.js.map