@matdata/yasr 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/build/ts/src/bindingsToCsv.d.ts +2 -0
  3. package/build/ts/src/defaults.d.ts +2 -0
  4. package/build/ts/src/helpers/addCSS.d.ts +1 -0
  5. package/build/ts/src/helpers/addScript.d.ts +1 -0
  6. package/build/ts/src/helpers/index.d.ts +3 -0
  7. package/build/ts/src/helpers/sanitize.d.ts +2 -0
  8. package/build/ts/src/imgs.d.ts +4 -0
  9. package/build/ts/src/index.d.ts +130 -0
  10. package/build/ts/src/parsers/csv.d.ts +2 -0
  11. package/build/ts/src/parsers/index.d.ts +68 -0
  12. package/build/ts/src/parsers/json.d.ts +2 -0
  13. package/build/ts/src/parsers/tsv.d.ts +2 -0
  14. package/build/ts/src/parsers/turtleFamily.d.ts +4 -0
  15. package/build/ts/src/parsers/xml.d.ts +2 -0
  16. package/build/ts/src/plugins/boolean/index.d.ts +13 -0
  17. package/build/ts/src/plugins/error/index.d.ts +13 -0
  18. package/build/ts/src/plugins/index.d.ts +19 -0
  19. package/build/ts/src/plugins/response/index.d.ts +28 -0
  20. package/build/ts/src/plugins/table/index.d.ts +49 -0
  21. package/build/yasr.html +32 -0
  22. package/build/yasr.min.css +2 -0
  23. package/build/yasr.min.css.map +1 -0
  24. package/build/yasr.min.js +3 -0
  25. package/build/yasr.min.js.LICENSE.txt +34 -0
  26. package/build/yasr.min.js.map +1 -0
  27. package/package.json +56 -0
  28. package/src/bin/takeScreenshot.js +373 -0
  29. package/src/bindingsToCsv.ts +18 -0
  30. package/src/defaults.ts +28 -0
  31. package/src/helpers/addCSS.ts +7 -0
  32. package/src/helpers/addScript.ts +12 -0
  33. package/src/helpers/index.ts +3 -0
  34. package/src/helpers/sanitize.ts +11 -0
  35. package/src/imgs.ts +7 -0
  36. package/src/index.ts +688 -0
  37. package/src/jquery/extendJquery.js +1 -0
  38. package/src/jquery/tableToCsv.js +88 -0
  39. package/src/main.scss +229 -0
  40. package/src/parsers/csv.ts +30 -0
  41. package/src/parsers/index.ts +311 -0
  42. package/src/parsers/json.ts +22 -0
  43. package/src/parsers/tsv.ts +43 -0
  44. package/src/parsers/turtleFamily.ts +60 -0
  45. package/src/parsers/xml.ts +79 -0
  46. package/src/plugins/boolean/index.scss +11 -0
  47. package/src/plugins/boolean/index.ts +42 -0
  48. package/src/plugins/error/index.scss +57 -0
  49. package/src/plugins/error/index.ts +124 -0
  50. package/src/plugins/index.ts +24 -0
  51. package/src/plugins/response/index.scss +63 -0
  52. package/src/plugins/response/index.ts +170 -0
  53. package/src/plugins/table/index.scss +154 -0
  54. package/src/plugins/table/index.ts +437 -0
  55. package/src/scss/global.scss +10 -0
  56. package/src/scss/variables.scss +2 -0
package/src/index.ts ADDED
@@ -0,0 +1,688 @@
1
+ import { EventEmitter } from "events";
2
+ import { merge, filter, mapValues, uniqueId } from "lodash-es";
3
+ import getDefaults from "./defaults";
4
+ import { Plugin } from "./plugins";
5
+ import {
6
+ Storage as YStorage,
7
+ drawFontAwesomeIconAsSvg,
8
+ drawSvgStringAsElement,
9
+ removeClass,
10
+ addClass,
11
+ hasClass,
12
+ } from "@matdata/yasgui-utils";
13
+ import Parser from "./parsers";
14
+ export { default as Parser } from "./parsers";
15
+ import { addScript, addCss, sanitize } from "./helpers";
16
+ import * as faDownload from "@fortawesome/free-solid-svg-icons/faDownload";
17
+ import * as faQuestionCircle from "@fortawesome/free-solid-svg-icons/faQuestionCircle";
18
+ import * as imgs from "./imgs";
19
+ require("./main.scss");
20
+
21
+ export interface PersistentConfig {
22
+ selectedPlugin?: string;
23
+ pluginsConfig?: { [pluginName: string]: any };
24
+ }
25
+
26
+ export interface Yasr {
27
+ on(event: "change", listener: (instance: Yasr) => void): this;
28
+ emit(event: "change", instance: Yasr): boolean;
29
+ on(event: "draw", listener: (instance: Yasr, plugin: Plugin<any>) => void): this;
30
+ emit(event: "draw", instance: Yasr, plugin: Plugin<any>): boolean;
31
+ on(event: "drawn", listener: (instance: Yasr, plugin: Plugin<any>) => void): this;
32
+ emit(event: "drawn", instance: Yasr, plugin: Plugin<any>): boolean;
33
+ on(event: "toggle-help", listener: (instance: Yasr) => void): this;
34
+ emit(event: "toggle-help", instance: Yasr): boolean;
35
+ }
36
+ export class Yasr extends EventEmitter {
37
+ public results?: Parser;
38
+ public rootEl: HTMLDivElement;
39
+ public headerEl: HTMLDivElement;
40
+ public fallbackInfoEl: HTMLDivElement;
41
+ public resultsEl: HTMLDivElement;
42
+ public pluginControls!: HTMLDivElement;
43
+ public config: Config;
44
+ public storage: YStorage;
45
+ public plugins: { [name: string]: Plugin<any> } = {};
46
+ // private persistentConfig: PersistentConfig;
47
+ public helpDrawn: Boolean = false;
48
+ private drawnPlugin: string | undefined;
49
+ private selectedPlugin: string | undefined;
50
+ private fullscreenBtn: HTMLButtonElement | undefined;
51
+ private isFullscreen: boolean = false;
52
+ private loadingEl: HTMLDivElement | undefined;
53
+ private isLoading: boolean = false;
54
+
55
+ // Utils
56
+ public utils = { addScript: addScript, addCSS: addCss, sanitize: sanitize };
57
+
58
+ constructor(parent: HTMLElement, conf: Partial<Config> = {}, data?: any) {
59
+ super();
60
+ if (!parent) throw new Error("No parent passed as argument. Dont know where to draw YASR");
61
+ this.rootEl = document.createElement("div");
62
+ this.rootEl.className = "yasr";
63
+ parent.appendChild(this.rootEl);
64
+ this.config = merge({}, Yasr.defaults, conf);
65
+
66
+ //Do some post processing
67
+ this.storage = new YStorage(Yasr.storageNamespace);
68
+ this.getConfigFromStorage();
69
+ this.headerEl = document.createElement("div");
70
+ this.headerEl.className = "yasr_header";
71
+ this.rootEl.appendChild(this.headerEl);
72
+ this.fallbackInfoEl = document.createElement("div");
73
+ this.fallbackInfoEl.className = "yasr_fallback_info";
74
+ this.rootEl.appendChild(this.fallbackInfoEl);
75
+ this.resultsEl = document.createElement("div");
76
+ this.resultsEl.className = "yasr_results";
77
+ this.resultsEl.id = uniqueId("resultsId");
78
+ this.rootEl.appendChild(this.resultsEl);
79
+ this.createLoadingElement();
80
+ this.initializePlugins();
81
+ this.drawHeader();
82
+
83
+ const resp = data || this.getResponseFromStorage();
84
+ if (resp) this.setResponse(resp);
85
+ }
86
+ private getConfigFromStorage() {
87
+ const storageId = this.getStorageId(this.config.persistenceLabelConfig);
88
+ if (storageId) {
89
+ const persistentConfig: PersistentConfig | undefined = this.storage.get(storageId);
90
+ if (persistentConfig) {
91
+ this.selectedPlugin = persistentConfig.selectedPlugin;
92
+ for (const pluginName in persistentConfig.pluginsConfig) {
93
+ const pConf = persistentConfig.pluginsConfig[pluginName];
94
+ if (pConf && this.config.plugins[pluginName]) this.config.plugins[pluginName].dynamicConfig = pConf;
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Ask the [[ Config.errorRenderers | configured error renderers ]] for a
102
+ * custom rendering of [[`error`]].
103
+ *
104
+ * @param error the error for which to find a custom rendering
105
+ * @return the first custom rendering found, or `undefined` if none was found.
106
+ */
107
+ public async renderError(error: Parser.ErrorSummary): Promise<HTMLElement | undefined> {
108
+ // Chain the errorRenderers to get the first special rendering of the error
109
+ // if no special rendering is found, return undefined
110
+ let element: HTMLElement | undefined = undefined;
111
+ if (this.config.errorRenderers !== undefined) {
112
+ for (const renderer of this.config.errorRenderers) {
113
+ element = await renderer(error);
114
+ if (element !== undefined) break; // we found the first special case, so return that!
115
+ }
116
+ }
117
+ return element;
118
+ }
119
+
120
+ public getStorageId(label: string, getter?: Config["persistenceId"]): string | undefined {
121
+ const persistenceId = getter || this.config.persistenceId;
122
+ if (!persistenceId) return;
123
+ if (typeof persistenceId === "string") return persistenceId + "_" + label;
124
+ return persistenceId(this) + "_" + label;
125
+ }
126
+
127
+ public somethingDrawn() {
128
+ return !!this.resultsEl.children.length;
129
+ }
130
+ /**
131
+ * Empties fallback element
132
+ * CSS will make sure the outline is hidden when empty
133
+ */
134
+ public emptyFallbackElement() {
135
+ // This is quicker than `<node>.innerHtml = ""`
136
+ while (this.fallbackInfoEl.firstChild) this.fallbackInfoEl.removeChild(this.fallbackInfoEl.firstChild);
137
+ }
138
+ public getSelectedPluginName(): string {
139
+ return this.selectedPlugin || this.config.defaultPlugin;
140
+ }
141
+ public getSelectedPlugin() {
142
+ if (this.plugins[this.getSelectedPluginName()]) {
143
+ return this.plugins[this.getSelectedPluginName()];
144
+ }
145
+ console.warn(`Tried using plugin ${this.getSelectedPluginName()}, but seems this plugin isnt registered in yasr.`);
146
+ }
147
+ /**
148
+ * Update selectors, based on whether they can actuall draw something, and which plugin is currently selected
149
+ */
150
+ private updatePluginSelectors(enabledPlugins?: string[]) {
151
+ if (!this.pluginSelectorsEl) return;
152
+ for (const pluginName in this.config.plugins) {
153
+ const plugin = this.plugins[pluginName];
154
+ if (plugin && !plugin.hideFromSelection) {
155
+ if (enabledPlugins) {
156
+ if (enabledPlugins.indexOf(pluginName) >= 0) {
157
+ //make sure enabled
158
+ removeClass(this.pluginSelectorsEl.querySelector(".select_" + pluginName), "disabled");
159
+ } else {
160
+ //make sure disabled
161
+ addClass(this.pluginSelectorsEl.querySelector(".select_" + pluginName), "disabled");
162
+ }
163
+ }
164
+ if (pluginName === this.getSelectedPluginName()) {
165
+ addClass(this.pluginSelectorsEl.querySelector(".select_" + pluginName), "selected");
166
+ } else {
167
+ removeClass(this.pluginSelectorsEl.querySelector(".select_" + pluginName), "selected");
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ private getCompatiblePlugins(): string[] {
174
+ if (!this.results)
175
+ return Object.keys(
176
+ filter(this.config.plugins, (val) => (typeof val === "object" && val.enabled) || (val as any) === true),
177
+ );
178
+
179
+ const supportedPlugins: { name: string; priority: number }[] = [];
180
+ for (const pluginName in this.plugins) {
181
+ if (this.plugins[pluginName].canHandleResults()) {
182
+ supportedPlugins.push({ name: pluginName, priority: this.plugins[pluginName].priority });
183
+ }
184
+ }
185
+ return supportedPlugins.sort((p1, p2) => p2.priority - p1.priority).map((p) => p.name);
186
+ }
187
+ public draw() {
188
+ this.updateHelpButton();
189
+ this.updateResponseInfo();
190
+ if (!this.results) return;
191
+ const compatiblePlugins = this.getCompatiblePlugins();
192
+ if (this.drawnPlugin && this.getSelectedPluginName() !== this.drawnPlugin) {
193
+ while (this.pluginControls.firstChild) {
194
+ this.pluginControls.firstChild.remove();
195
+ }
196
+ this.plugins[this.drawnPlugin].destroy?.();
197
+ }
198
+ let pluginToDraw: string | undefined;
199
+ if (this.getSelectedPlugin() && this.getSelectedPlugin()?.canHandleResults()) {
200
+ pluginToDraw = this.getSelectedPluginName();
201
+ // When present remove fallback box
202
+ this.emptyFallbackElement();
203
+ } else if (compatiblePlugins[0]) {
204
+ if (this.drawnPlugin) {
205
+ this.plugins[this.drawnPlugin].destroy?.();
206
+ }
207
+ pluginToDraw = compatiblePlugins[0];
208
+ // Signal to create the plugin+
209
+ this.fillFallbackBox(pluginToDraw);
210
+ }
211
+ if (pluginToDraw) {
212
+ this.drawnPlugin = pluginToDraw;
213
+
214
+ this.emit("draw", this, this.plugins[pluginToDraw]);
215
+ const plugin = this.plugins[pluginToDraw];
216
+ const initPromise = plugin.initialize ? plugin.initialize() : Promise.resolve();
217
+ initPromise.then(
218
+ async () => {
219
+ if (pluginToDraw) {
220
+ // make sure to clear the object _here_
221
+ // otherwise we run into race conditions when draw is executed
222
+ // shortly after each other, and the plugin uses an initialize function
223
+ // as a result, things can be rendered _twice_
224
+ while (this.resultsEl.firstChild) this.resultsEl.firstChild.remove();
225
+ await this.plugins[pluginToDraw].draw(this.config.plugins[pluginToDraw].dynamicConfig);
226
+ this.emit("drawn", this, this.plugins[pluginToDraw]);
227
+ this.updateExportHeaders();
228
+ this.updatePluginSelectors(compatiblePlugins);
229
+ }
230
+ },
231
+ (_e) => console.error,
232
+ );
233
+ } else {
234
+ this.resultsEl.textContent = "cannot render result";
235
+ this.updateExportHeaders();
236
+ this.updatePluginSelectors(compatiblePlugins);
237
+ }
238
+ }
239
+ //just an alias for `draw`. That way, we've got a consistent api with yasqe
240
+ public refresh() {
241
+ this.draw();
242
+ }
243
+ public destroy() {
244
+ if (this.drawnPlugin) this.plugins[this.drawnPlugin]?.destroy?.();
245
+ this.removeAllListeners();
246
+ this.rootEl.remove();
247
+ }
248
+
249
+ getPrefixes(): Prefixes {
250
+ if (this.config.prefixes) {
251
+ if (typeof this.config.prefixes === "function") {
252
+ return this.config.prefixes(this);
253
+ } else {
254
+ return this.config.prefixes;
255
+ }
256
+ }
257
+ return {};
258
+ }
259
+ public selectPlugin(plugin: string) {
260
+ if (this.selectedPlugin === plugin) {
261
+ // Don't re-render when selecting the same plugin. Also see #1893
262
+ return;
263
+ }
264
+ if (this.config.plugins[plugin]) {
265
+ this.selectedPlugin = plugin;
266
+ } else {
267
+ console.warn(`Plugin ${plugin} does not exist.`);
268
+ this.selectedPlugin = this.config.defaultPlugin;
269
+ }
270
+ this.storeConfig();
271
+ this.emit("change", this);
272
+ this.updatePluginSelectors();
273
+ this.draw();
274
+ }
275
+ private pluginSelectorsEl!: HTMLUListElement;
276
+ drawPluginSelectors() {
277
+ this.pluginSelectorsEl = document.createElement("ul");
278
+ this.pluginSelectorsEl.className = "yasr_btnGroup";
279
+ const pluginOrder = this.config.pluginOrder;
280
+ Object.keys(this.config.plugins)
281
+ .sort()
282
+ .forEach((plugin) => {
283
+ if (pluginOrder.indexOf(plugin) === -1) pluginOrder.push(plugin);
284
+ });
285
+ for (const pluginName of pluginOrder) {
286
+ if (!this.config.plugins[pluginName] || !this.config.plugins[pluginName].enabled) {
287
+ continue;
288
+ }
289
+ const plugin = this.plugins[pluginName];
290
+
291
+ if (!plugin) continue; //plugin not loaded
292
+ if (plugin.hideFromSelection) continue;
293
+ const name = plugin.label || pluginName;
294
+ const button = document.createElement("button");
295
+ addClass(button, "yasr_btn", "select_" + pluginName);
296
+ button.title = name;
297
+ button.type = "button";
298
+ button.setAttribute("aria-label", `Shows ${name} view`);
299
+ if (plugin.getIcon) {
300
+ const icon = plugin.getIcon();
301
+ if (icon) {
302
+ // icon.className = '';
303
+ addClass(icon, "plugin_icon");
304
+ button.appendChild(icon);
305
+ }
306
+ }
307
+ const nameEl = document.createElement("span");
308
+ nameEl.textContent = name;
309
+ button.appendChild(nameEl);
310
+ button.addEventListener("click", () => this.selectPlugin(pluginName));
311
+ const li = document.createElement("li");
312
+ li.appendChild(button);
313
+ this.pluginSelectorsEl.appendChild(li);
314
+ }
315
+
316
+ if (this.pluginSelectorsEl.children.length >= 1) this.headerEl.appendChild(this.pluginSelectorsEl);
317
+ this.updatePluginSelectors();
318
+ }
319
+ private fillFallbackBox(fallbackElement?: string) {
320
+ this.emptyFallbackElement();
321
+
322
+ // Do not show fallback render warnings for plugins without a selector.
323
+ if (this.plugins[fallbackElement || this.drawnPlugin || ""]?.hideFromSelection) return;
324
+
325
+ const selectedPlugin = this.getSelectedPlugin();
326
+ const fallbackPluginLabel =
327
+ this.plugins[fallbackElement || this.drawnPlugin || ""]?.label || fallbackElement || this.drawnPlugin;
328
+ const selectedPluginLabel = selectedPlugin?.label || this.getSelectedPluginName();
329
+
330
+ const textElement = document.createElement("p");
331
+ textElement.innerText = `Could not render results with the ${selectedPluginLabel} plugin, the results currently are rendered with the ${fallbackPluginLabel} plugin. ${
332
+ this.getSelectedPlugin()?.helpReference ? "See " : ""
333
+ }`;
334
+
335
+ if (selectedPlugin?.helpReference) {
336
+ const linkElement = document.createElement("a");
337
+ linkElement.innerText = `${selectedPluginLabel} documentation`;
338
+ linkElement.href = selectedPlugin.helpReference;
339
+ linkElement.rel = "noopener noreferrer";
340
+ linkElement.target = "_blank";
341
+ textElement.append(linkElement);
342
+ textElement.innerHTML += " for more information.";
343
+ }
344
+
345
+ this.fallbackInfoEl.appendChild(textElement);
346
+ }
347
+ private drawPluginElement() {
348
+ const spaceElement = document.createElement("div");
349
+ addClass(spaceElement, "space_element");
350
+ this.headerEl.appendChild(spaceElement);
351
+ this.pluginControls = document.createElement("div");
352
+ this.pluginControls.setAttribute("id", "yasr_plugin_control");
353
+ addClass(this.pluginControls, "yasr_plugin_control");
354
+ this.pluginControls.setAttribute("aria-controls", this.resultsEl.id);
355
+ this.headerEl.appendChild(this.pluginControls);
356
+ }
357
+
358
+ private drawHeader() {
359
+ this.drawPluginSelectors();
360
+ this.drawResponseInfo();
361
+ this.drawPluginElement();
362
+ this.drawDownloadIcon();
363
+ this.drawFullscreenButton();
364
+ if (this.loadingEl) {
365
+ this.headerEl.appendChild(this.loadingEl);
366
+ }
367
+ this.drawDocumentationButton();
368
+ }
369
+ private downloadBtn: HTMLAnchorElement | undefined;
370
+ private drawDownloadIcon() {
371
+ this.downloadBtn = document.createElement("a");
372
+ addClass(this.downloadBtn, "yasr_btn", "yasr_downloadIcon", "btn_icon");
373
+ this.downloadBtn.download = ""; // should default to the file name of the blob
374
+ this.downloadBtn.setAttribute("aria-label", "Download Results");
375
+ this.downloadBtn.setAttribute("tabindex", "0"); // anchor elements with no href are not automatically included in the tabindex
376
+ this.downloadBtn.setAttribute("role", "button");
377
+ const iconEl = drawSvgStringAsElement(drawFontAwesomeIconAsSvg(faDownload));
378
+ iconEl.setAttribute("aria-hidden", "true");
379
+ this.downloadBtn.appendChild(iconEl);
380
+ this.downloadBtn.addEventListener("click", () => {
381
+ if (hasClass(this.downloadBtn, "disabled")) return;
382
+ this.download();
383
+ });
384
+ this.downloadBtn.addEventListener("keydown", (event) => {
385
+ // needed for accessibility
386
+ if (event.code === "Space" || event.code === "Enter") {
387
+ if (hasClass(this.downloadBtn, "disabled")) return;
388
+ this.download();
389
+ }
390
+ });
391
+
392
+ this.headerEl.appendChild(this.downloadBtn);
393
+ }
394
+ private dataElement!: HTMLDivElement;
395
+ private drawResponseInfo() {
396
+ this.dataElement = document.createElement("div");
397
+ addClass(this.dataElement, "yasr_response_chip");
398
+ this.headerEl.appendChild(this.dataElement);
399
+ this.updateResponseInfo();
400
+ }
401
+ private updateResponseInfo() {
402
+ let innerText = "";
403
+ if (this.results) {
404
+ removeClass(this.dataElement, "empty");
405
+ const bindings = this.results.getBindings();
406
+ if (bindings) {
407
+ innerText += `${bindings.length} result${bindings.length === 1 ? "" : "s"}`; // Set amount of results
408
+ }
409
+ const responseTime = this.results.getResponseTime();
410
+ if (responseTime) {
411
+ if (!innerText) innerText = "Response";
412
+ const time = responseTime / 1000;
413
+ innerText += ` in ${time} second${time === 1 ? "" : "s"}`;
414
+ }
415
+ } else {
416
+ addClass(this.dataElement, "empty");
417
+ }
418
+ this.dataElement.innerText = innerText;
419
+ }
420
+ private updateHelpButton() {
421
+ const selectedPlugin = this.getSelectedPlugin();
422
+ if (selectedPlugin?.helpReference) {
423
+ const titleLabel = `View documentation of ${selectedPlugin.label || this.getSelectedPluginName()}`;
424
+ this.documentationLink.href = selectedPlugin.helpReference;
425
+ this.documentationLink.title = titleLabel;
426
+ this.documentationLink.setAttribute("aria-label", titleLabel);
427
+ removeClass(this.documentationLink, "disabled");
428
+ } else {
429
+ addClass(this.documentationLink, "disabled");
430
+ this.documentationLink.title =
431
+ "This plugin doesn't have a help reference yet. Please contact the maintainer to fix this";
432
+ }
433
+ }
434
+ updateExportHeaders() {
435
+ if (this.downloadBtn && this.drawnPlugin) {
436
+ this.downloadBtn.title = "";
437
+ const plugin = this.plugins[this.drawnPlugin];
438
+ if (plugin && plugin.download) {
439
+ const downloadInfo = plugin.download(this.config.getDownloadFileName?.());
440
+ removeClass(this.downloadBtn, "disabled");
441
+ if (downloadInfo) {
442
+ if (downloadInfo.title) this.downloadBtn.title = downloadInfo.title;
443
+ return;
444
+ }
445
+ }
446
+ this.downloadBtn.title = "Download not supported";
447
+ addClass(this.downloadBtn, "disabled");
448
+ }
449
+ }
450
+
451
+ private drawFullscreenButton() {
452
+ this.fullscreenBtn = document.createElement("button");
453
+ addClass(this.fullscreenBtn, "yasr_btn", "yasr_fullscreenButton", "btn_icon");
454
+ this.fullscreenBtn.setAttribute("aria-label", "Toggle fullscreen");
455
+ this.fullscreenBtn.setAttribute("tabindex", "0");
456
+ this.fullscreenBtn.setAttribute("role", "button");
457
+
458
+ const fullscreenIcon = drawSvgStringAsElement(imgs.fullscreen);
459
+ addClass(fullscreenIcon, "fullscreenIcon");
460
+ fullscreenIcon.setAttribute("aria-hidden", "true");
461
+ this.fullscreenBtn.appendChild(fullscreenIcon);
462
+
463
+ const fullscreenExitIcon = drawSvgStringAsElement(imgs.fullscreenExit);
464
+ addClass(fullscreenExitIcon, "fullscreenExitIcon");
465
+ fullscreenExitIcon.setAttribute("aria-hidden", "true");
466
+ this.fullscreenBtn.appendChild(fullscreenExitIcon);
467
+
468
+ this.fullscreenBtn.title = "Toggle fullscreen (F10)";
469
+ this.fullscreenBtn.addEventListener("click", () => {
470
+ this.toggleFullscreen();
471
+ });
472
+ this.fullscreenBtn.addEventListener("keydown", (event) => {
473
+ if (event.code === "Space" || event.code === "Enter") {
474
+ this.toggleFullscreen();
475
+ }
476
+ });
477
+
478
+ this.headerEl.appendChild(this.fullscreenBtn);
479
+ }
480
+
481
+ public toggleFullscreen() {
482
+ this.isFullscreen = !this.isFullscreen;
483
+ if (this.isFullscreen) {
484
+ addClass(this.rootEl, "fullscreen");
485
+ if (this.fullscreenBtn) this.fullscreenBtn.title = "Exit fullscreen (F10)";
486
+ } else {
487
+ removeClass(this.rootEl, "fullscreen");
488
+ if (this.fullscreenBtn) this.fullscreenBtn.title = "Toggle fullscreen (F10)";
489
+ }
490
+ }
491
+
492
+ public getIsFullscreen() {
493
+ return this.isFullscreen;
494
+ }
495
+
496
+ private createLoadingElement() {
497
+ this.loadingEl = document.createElement("div");
498
+ this.loadingEl.className = "yasr_loading_indicator";
499
+ this.loadingEl.title = "Executing query...";
500
+ this.loadingEl.innerHTML = `<div class="yasr_loading_spinner"></div>`;
501
+ this.loadingEl.style.display = "none";
502
+ // Will be appended to header in drawHeader
503
+ }
504
+
505
+ public showLoading() {
506
+ this.isLoading = true;
507
+ if (this.loadingEl) {
508
+ this.loadingEl.style.display = "flex";
509
+ }
510
+ }
511
+
512
+ public hideLoading() {
513
+ this.isLoading = false;
514
+ if (this.loadingEl) {
515
+ this.loadingEl.style.display = "none";
516
+ }
517
+ }
518
+
519
+ private documentationLink!: HTMLAnchorElement;
520
+ private drawDocumentationButton() {
521
+ this.documentationLink = document.createElement("a");
522
+ addClass(this.documentationLink, "yasr_btn", "yasr_external_ref_btn");
523
+ this.documentationLink.appendChild(drawSvgStringAsElement(drawFontAwesomeIconAsSvg(faQuestionCircle)));
524
+ this.documentationLink.href = "https://docs.triply.cc/yasgui/";
525
+ this.documentationLink.target = "_blank";
526
+ this.documentationLink.rel = "noopener noreferrer";
527
+ this.headerEl.appendChild(this.documentationLink); // We can do this as long as the help-element is the last item in the row
528
+ }
529
+ download() {
530
+ if (!this.drawnPlugin) return;
531
+ const currentPlugin = this.plugins[this.drawnPlugin];
532
+ if (currentPlugin && currentPlugin.download) {
533
+ const downloadInfo = currentPlugin.download(this.config.getDownloadFileName?.());
534
+ if (!downloadInfo) return;
535
+ const data = downloadInfo.getData();
536
+ let downloadUrl: string;
537
+ if (data.startsWith("data:")) {
538
+ downloadUrl = data;
539
+ } else {
540
+ const blob = new Blob([data], { type: downloadInfo.contentType ?? "text/plain" });
541
+ downloadUrl = window.URL.createObjectURL(blob);
542
+ }
543
+ const mockLink = document.createElement("a");
544
+ mockLink.href = downloadUrl;
545
+ mockLink.download = downloadInfo.filename;
546
+ mockLink.click();
547
+ }
548
+ }
549
+
550
+ public handleLocalStorageQuotaFull(_e: any) {
551
+ console.warn("Localstorage quota exceeded. Clearing all queries");
552
+ Yasr.clearStorage();
553
+ }
554
+
555
+ public getResponseFromStorage() {
556
+ const storageId = this.getStorageId(this.config.persistenceLabelResponse);
557
+ if (storageId) {
558
+ return this.storage.get(storageId);
559
+ }
560
+ }
561
+ public getPersistentConfig(): PersistentConfig {
562
+ return {
563
+ selectedPlugin: this.getSelectedPluginName(),
564
+ pluginsConfig: mapValues(this.config.plugins, (plugin) => plugin.dynamicConfig),
565
+ };
566
+ }
567
+ //This doesnt store the plugin complete config. Only those configs we want persisted
568
+ public storePluginConfig(pluginName: string, conf: any) {
569
+ this.config.plugins[pluginName].dynamicConfig = conf;
570
+ this.storeConfig();
571
+ this.emit("change", this);
572
+ }
573
+ private storeConfig() {
574
+ const storageId = this.getStorageId(this.config.persistenceLabelConfig);
575
+ if (storageId) {
576
+ this.storage.set(
577
+ storageId,
578
+ this.getPersistentConfig(),
579
+ this.config.persistencyExpire,
580
+ this.handleLocalStorageQuotaFull,
581
+ );
582
+ }
583
+ }
584
+ private storeResponse() {
585
+ const storageId = this.getStorageId(this.config.persistenceLabelResponse);
586
+ if (storageId && this.results) {
587
+ const storeObj = this.results.getAsStoreObject(this.config.maxPersistentResponseSize);
588
+ if (storeObj && !storeObj.error) {
589
+ this.storage.set(storageId, storeObj, this.config.persistencyExpire, this.handleLocalStorageQuotaFull);
590
+ } else {
591
+ //remove old string;
592
+ this.storage.remove(storageId);
593
+ }
594
+ }
595
+ }
596
+ public setResponse(data: any, duration?: number) {
597
+ if (!data) return;
598
+ this.hideLoading();
599
+ this.results = new Parser(data, duration);
600
+
601
+ this.draw();
602
+
603
+ this.storeResponse();
604
+ }
605
+
606
+ private initializePlugins() {
607
+ for (const plugin in this.config.plugins) {
608
+ if (!this.config.plugins[plugin]) continue; //falsy value, so assuming it should be disabled
609
+ if (Yasr.plugins[plugin]) {
610
+ this.plugins[plugin] = new (<any>Yasr.plugins[plugin])(this);
611
+ } else {
612
+ console.warn("Wanted to initialize plugin " + plugin + " but could not find a matching registered plugin");
613
+ }
614
+ }
615
+ }
616
+
617
+ static defaults: Config = getDefaults();
618
+ static plugins: { [key: string]: typeof Plugin & { defaults?: any } } = {};
619
+ static registerPlugin(name: string, plugin: typeof Plugin, enable = true) {
620
+ Yasr.plugins[name] = plugin;
621
+ if (enable) {
622
+ Yasr.defaults.plugins[name] = { enabled: true };
623
+ } else {
624
+ Yasr.defaults.plugins[name] = { enabled: false };
625
+ }
626
+ }
627
+ /**
628
+ * Collection of Promises to load external scripts used by Yasr Plugins
629
+ * That way, the plugins wont load similar scripts simultaneously
630
+ */
631
+ static Dependencies: { [name: string]: Promise<any> } = {};
632
+ static storageNamespace = "triply";
633
+ static clearStorage() {
634
+ const storage = new YStorage(Yasr.storageNamespace);
635
+ storage.removeNamespace();
636
+ }
637
+ }
638
+
639
+ export type Prefixes = { [prefixLabel: string]: string };
640
+ export interface PluginConfig {
641
+ dynamicConfig?: any;
642
+ staticConfig?: any;
643
+ enabled?: boolean;
644
+ }
645
+ export interface Config {
646
+ persistenceId: ((yasr: Yasr) => string) | string | null;
647
+ persistenceLabelResponse: string;
648
+ persistenceLabelConfig: string;
649
+ maxPersistentResponseSize: number;
650
+ persistencyExpire: number;
651
+ getPlainQueryLinkToEndpoint: (() => string | undefined) | undefined;
652
+ getDownloadFileName?: () => string | undefined;
653
+ plugins: { [pluginName: string]: PluginConfig };
654
+ pluginOrder: string[];
655
+ defaultPlugin: string;
656
+
657
+ prefixes: Prefixes | ((yasr: Yasr) => Prefixes);
658
+
659
+ /**
660
+ * Custom renderers for errors.
661
+ * Allow multiple to be able to add new custom renderers without having to
662
+ * overwrite or explicitly call previously added or default ones.
663
+ */
664
+ errorRenderers?: ((error: Parser.ErrorSummary) => Promise<HTMLElement | undefined>)[];
665
+ }
666
+
667
+ export function registerPlugin(name: string, plugin: typeof Plugin, enable = true) {
668
+ Yasr.plugins[name] = plugin;
669
+ if (enable) {
670
+ Yasr.defaults.plugins[name] = { enabled: true };
671
+ } else {
672
+ Yasr.defaults.plugins[name] = { enabled: false };
673
+ }
674
+ }
675
+
676
+ import * as YasrPluginTable from "./plugins/table";
677
+ import * as YasrPluginBoolean from "./plugins/boolean";
678
+ import * as YasrPluginResponse from "./plugins/response";
679
+ import * as YasrPluginError from "./plugins/error";
680
+
681
+ Yasr.registerPlugin("table", YasrPluginTable.default as any);
682
+ Yasr.registerPlugin("boolean", YasrPluginBoolean.default as any);
683
+ Yasr.registerPlugin("response", YasrPluginResponse.default as any);
684
+ Yasr.registerPlugin("error", YasrPluginError.default as any);
685
+
686
+ export { Plugin, DownloadInfo } from "./plugins";
687
+
688
+ export default Yasr;
@@ -0,0 +1 @@
1
+ require("./tableToCsv.js");