@senx/discovery-widgets
Version:
Discovery Widgets Elements
575 lines (536 loc) • 24 kB
JavaScript
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