@matdata/yasgui 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.
- package/CHANGELOG.md +175 -0
- package/build/ts/src/PersistentConfig.d.ts +49 -0
- package/build/ts/src/Tab.d.ts +105 -0
- package/build/ts/src/TabContextMenu.d.ts +29 -0
- package/build/ts/src/TabElements.d.ts +45 -0
- package/build/ts/src/TabSettingsModal.d.ts +28 -0
- package/build/ts/src/defaults.d.ts +3 -0
- package/build/ts/src/endpointSelect.d.ts +44 -0
- package/build/ts/src/index.d.ts +104 -0
- package/build/ts/src/linkUtils.d.ts +43 -0
- package/build/yasgui.html +24 -0
- package/build/yasgui.min.css +2 -0
- package/build/yasgui.min.css.map +1 -0
- package/build/yasgui.min.js +3 -0
- package/build/yasgui.min.js.LICENSE.txt +59 -0
- package/build/yasgui.min.js.map +1 -0
- package/package.json +48 -0
- package/src/PersistentConfig.ts +159 -0
- package/src/Tab.ts +775 -0
- package/src/TabContextMenu.scss +42 -0
- package/src/TabContextMenu.ts +143 -0
- package/src/TabElements.scss +134 -0
- package/src/TabElements.ts +336 -0
- package/src/TabSettingsModal.scss +226 -0
- package/src/TabSettingsModal.ts +424 -0
- package/src/defaults.ts +64 -0
- package/src/endpointSelect.scss +122 -0
- package/src/endpointSelect.ts +339 -0
- package/src/index.scss +97 -0
- package/src/index.ts +373 -0
- package/src/linkUtils.ts +234 -0
- package/src/tab.scss +61 -0
- package/static/yasgui.bootstrap.css +36 -0
- package/static/yasgui.polyfill.min.js +4 -0
- package/typings-custom/@tarekraafat/autocomplete.js/index.d.ts +48 -0
- package/typings-custom/main.d.ts +1 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { merge, find, isEqual } from "lodash-es";
|
|
3
|
+
import initializeDefaults from "./defaults";
|
|
4
|
+
import PersistentConfig from "./PersistentConfig";
|
|
5
|
+
import { default as Tab, PersistedJson as PersistedTabJson } from "./Tab";
|
|
6
|
+
|
|
7
|
+
import { EndpointSelectConfig, CatalogueItem } from "./endpointSelect";
|
|
8
|
+
import * as shareLink from "./linkUtils";
|
|
9
|
+
import TabElements from "./TabElements";
|
|
10
|
+
import { default as Yasqe, PartialConfig as YasqeConfig, RequestConfig } from "@matdata/yasqe";
|
|
11
|
+
import { default as Yasr, Config as YasrConfig } from "@matdata/yasr";
|
|
12
|
+
import { addClass, removeClass } from "@matdata/yasgui-utils";
|
|
13
|
+
import GeoPlugin from "yasgui-geo-tg";
|
|
14
|
+
import GraphPlugin from "@matdata/yasgui-graph-plugin";
|
|
15
|
+
require("./index.scss");
|
|
16
|
+
require("@matdata/yasr/src/scss/global.scss");
|
|
17
|
+
|
|
18
|
+
// Register plugins to Yasr
|
|
19
|
+
Yasr.registerPlugin("Geo", GeoPlugin);
|
|
20
|
+
Yasr.registerPlugin("Graph", GraphPlugin);
|
|
21
|
+
if (window) {
|
|
22
|
+
//We're storing yasqe and yasr as a member of Yasgui, but _also_ in the window
|
|
23
|
+
//That way, we dont have to tweak e.g. pro plugins to register themselves to both
|
|
24
|
+
//Yasgui.Yasr _and_ Yasr.
|
|
25
|
+
if (Yasqe) (window as any).Yasqe = Yasqe;
|
|
26
|
+
if (Yasr) (window as any).Yasr = Yasr;
|
|
27
|
+
}
|
|
28
|
+
export type YasguiRequestConfig = Omit<RequestConfig<Yasgui>, "adjustQueryBeforeRequest"> & {
|
|
29
|
+
adjustQueryBeforeRequest: RequestConfig<Yasqe>["adjustQueryBeforeRequest"];
|
|
30
|
+
};
|
|
31
|
+
export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
|
|
32
|
+
/**
|
|
33
|
+
* Autofocus yasqe on load or tab switch
|
|
34
|
+
*/
|
|
35
|
+
autofocus: boolean;
|
|
36
|
+
endpointInfo: ((tab?: Tab) => Element) | undefined;
|
|
37
|
+
copyEndpointOnNewTab: boolean;
|
|
38
|
+
tabName: string;
|
|
39
|
+
corsProxy: string | undefined;
|
|
40
|
+
endpointCatalogueOptions: EndpointSelectConfig<EndpointObject>;
|
|
41
|
+
//The function allows us to modify the config before we pass it on to a tab
|
|
42
|
+
populateFromUrl: boolean | ((configFromUrl: PersistedTabJson) => PersistedTabJson);
|
|
43
|
+
autoAddOnInit: boolean;
|
|
44
|
+
persistenceId: ((yasr: Yasgui) => string) | string | null;
|
|
45
|
+
persistenceLabelConfig: string;
|
|
46
|
+
persistenceLabelResponse: string;
|
|
47
|
+
persistencyExpire: number;
|
|
48
|
+
yasqe: Partial<YasqeConfig>;
|
|
49
|
+
yasr: YasrConfig;
|
|
50
|
+
requestConfig: YasguiRequestConfig;
|
|
51
|
+
contextMenuContainer: HTMLElement | undefined;
|
|
52
|
+
nonSslDomain?: string;
|
|
53
|
+
}
|
|
54
|
+
export type PartialConfig = {
|
|
55
|
+
[P in keyof Config]?: Config[P] extends object ? Partial<Config[P]> : Config[P];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type TabJson = PersistedTabJson;
|
|
59
|
+
|
|
60
|
+
export interface Yasgui {
|
|
61
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
62
|
+
|
|
63
|
+
on(event: "tabSelect", listener: (instance: Yasgui, newTabId: string) => void): this;
|
|
64
|
+
emit(event: "tabSelect", instance: Yasgui, newTabId: string): boolean;
|
|
65
|
+
on(event: "tabClose", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
66
|
+
emit(event: "tabClose", instance: Yasgui, tab: Tab): boolean;
|
|
67
|
+
on(event: "query", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
68
|
+
emit(event: "query", instance: Yasgui, tab: Tab): boolean;
|
|
69
|
+
on(event: "queryBefore", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
70
|
+
emit(event: "queryBefore", instance: Yasgui, tab: Tab): boolean;
|
|
71
|
+
on(event: "queryAbort", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
72
|
+
emit(event: "queryAbort", instance: Yasgui, tab: Tab): boolean;
|
|
73
|
+
on(event: "queryResponse", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
74
|
+
emit(event: "queryResponse", instance: Yasgui, tab: Tab): boolean;
|
|
75
|
+
on(event: "tabChange", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
76
|
+
emit(event: "tabChange", instance: Yasgui, tab: Tab): boolean;
|
|
77
|
+
on(event: "tabAdd", listener: (instance: Yasgui, newTabId: string) => void): this;
|
|
78
|
+
emit(event: "tabAdd", instance: Yasgui, newTabId: string): boolean;
|
|
79
|
+
on(event: "tabOrderChanged", listener: (instance: Yasgui, tabList: string[]) => void): this;
|
|
80
|
+
emit(event: "tabOrderChanged", instance: Yasgui, tabList: string[]): boolean;
|
|
81
|
+
on(event: "fullscreen-enter", listener: (instance: Yasgui) => void): this;
|
|
82
|
+
emit(event: "fullscreen-enter", instance: Yasgui): boolean;
|
|
83
|
+
on(event: "fullscreen-leave", listener: (instance: Yasgui) => void): this;
|
|
84
|
+
emit(event: "fullscreen-leave", instance: Yasgui): boolean;
|
|
85
|
+
on(event: "endpointHistoryChange", listener: (instance: Yasgui, history: string[]) => void): this;
|
|
86
|
+
emit(event: "endpointHistoryChange", instance: Yasgui, history: string[]): boolean;
|
|
87
|
+
on(event: "autocompletionShown", listener: (instance: Yasgui, tab: Tab, widget: any) => void): this;
|
|
88
|
+
emit(event: "autocompletionShown", instance: Yasgui, tab: Tab, widget: any): boolean;
|
|
89
|
+
on(event: "autocompletionClose", listener: (instance: Yasgui, tab: Tab) => void): this;
|
|
90
|
+
emit(event: "autocompletionClose", instance: Yasgui, tab: Tab): boolean;
|
|
91
|
+
}
|
|
92
|
+
export class Yasgui extends EventEmitter {
|
|
93
|
+
public rootEl: HTMLDivElement;
|
|
94
|
+
public tabElements: TabElements;
|
|
95
|
+
public _tabs: { [tabId: string]: Tab } = {};
|
|
96
|
+
public tabPanelsEl: HTMLDivElement;
|
|
97
|
+
public config: Config;
|
|
98
|
+
public persistentConfig: PersistentConfig;
|
|
99
|
+
public static Tab = Tab;
|
|
100
|
+
constructor(parent: HTMLElement, config: PartialConfig) {
|
|
101
|
+
super();
|
|
102
|
+
this.rootEl = document.createElement("div");
|
|
103
|
+
addClass(this.rootEl, "yasgui");
|
|
104
|
+
parent.appendChild(this.rootEl);
|
|
105
|
+
|
|
106
|
+
this.config = merge({}, Yasgui.defaults, config);
|
|
107
|
+
this.persistentConfig = new PersistentConfig(this);
|
|
108
|
+
|
|
109
|
+
this.tabElements = new TabElements(this);
|
|
110
|
+
this.tabPanelsEl = document.createElement("div");
|
|
111
|
+
|
|
112
|
+
this.rootEl.appendChild(this.tabElements.drawTabsList());
|
|
113
|
+
this.rootEl.appendChild(this.tabPanelsEl);
|
|
114
|
+
let executeIdAfterInit: string | undefined;
|
|
115
|
+
let optionsFromUrl: PersistedTabJson | undefined;
|
|
116
|
+
if (this.config.populateFromUrl) {
|
|
117
|
+
optionsFromUrl = shareLink.getConfigFromUrl(Tab.getDefaults(this));
|
|
118
|
+
if (optionsFromUrl) {
|
|
119
|
+
const tabId = this.findTabIdForConfig(optionsFromUrl);
|
|
120
|
+
if (tabId) {
|
|
121
|
+
// when a config is already present,
|
|
122
|
+
const persistentYasr = this.persistentConfig.getTab(tabId).yasr;
|
|
123
|
+
this.persistentConfig.getTab(tabId).yasr = {
|
|
124
|
+
// Override the settings
|
|
125
|
+
settings: optionsFromUrl.yasr.settings,
|
|
126
|
+
// Keep the old response to save data/time
|
|
127
|
+
response: persistentYasr.response,
|
|
128
|
+
};
|
|
129
|
+
this.persistentConfig.setActive(tabId);
|
|
130
|
+
if (!persistentYasr.response) {
|
|
131
|
+
//we did have a tab already open for this link, but there wasnt a response
|
|
132
|
+
//probably, it's too large to put in local storage
|
|
133
|
+
//so, lets make sure we execute the query
|
|
134
|
+
executeIdAfterInit = tabId;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
this.persistentConfig.setTab(
|
|
138
|
+
optionsFromUrl.id,
|
|
139
|
+
typeof this.config.populateFromUrl === "function"
|
|
140
|
+
? this.config.populateFromUrl(optionsFromUrl)
|
|
141
|
+
: optionsFromUrl,
|
|
142
|
+
);
|
|
143
|
+
executeIdAfterInit = optionsFromUrl.id;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const tabs = this.persistentConfig.getTabs();
|
|
148
|
+
if (!tabs.length && this.config.autoAddOnInit) {
|
|
149
|
+
const newTab = this.addTab(true);
|
|
150
|
+
this.persistentConfig.setActive(newTab.getId());
|
|
151
|
+
this.emit("tabChange", this, newTab);
|
|
152
|
+
} else {
|
|
153
|
+
for (const tabId of tabs) {
|
|
154
|
+
this._tabs[tabId] = new Tab(this, this.persistentConfig.getTab(tabId));
|
|
155
|
+
this._registerTabListeners(this._tabs[tabId]);
|
|
156
|
+
// this.tabs[tabId].on("close", tab => this.closeTabId(tab.getId()));
|
|
157
|
+
this.tabElements.drawTab(tabId);
|
|
158
|
+
}
|
|
159
|
+
const activeTabId = this.persistentConfig.getActiveId();
|
|
160
|
+
if (activeTabId) {
|
|
161
|
+
this.markTabSelected(activeTabId);
|
|
162
|
+
if (executeIdAfterInit && executeIdAfterInit === activeTabId) {
|
|
163
|
+
(this.getTab(activeTabId) as Tab).query().catch(() => {});
|
|
164
|
+
}
|
|
165
|
+
// }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
public hasFullscreen(fullscreen: boolean) {
|
|
170
|
+
if (fullscreen) {
|
|
171
|
+
this.emit("fullscreen-enter", this);
|
|
172
|
+
addClass(this.rootEl, "hasFullscreen");
|
|
173
|
+
} else {
|
|
174
|
+
this.emit("fullscreen-leave", this);
|
|
175
|
+
removeClass(this.rootEl, "hasFullscreen");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
public getStorageId(label: string, getter?: Config["persistenceId"]): string | undefined {
|
|
179
|
+
const persistenceId = getter || this.config.persistenceId;
|
|
180
|
+
if (!persistenceId) return undefined;
|
|
181
|
+
if (typeof persistenceId === "string") return persistenceId + "_" + label;
|
|
182
|
+
return persistenceId(this) + "_" + label;
|
|
183
|
+
}
|
|
184
|
+
public createTabName(name?: string, i: number = 0) {
|
|
185
|
+
if (!name) name = this.config.tabName;
|
|
186
|
+
var fullName = name + (i > 0 ? " " + i : "");
|
|
187
|
+
if (this.tabNameTaken(fullName)) fullName = this.createTabName(name, i + 1);
|
|
188
|
+
return fullName;
|
|
189
|
+
}
|
|
190
|
+
public tabNameTaken(name: string) {
|
|
191
|
+
return find(this._tabs, (tab) => tab.getName() === name);
|
|
192
|
+
}
|
|
193
|
+
public getTab(tabId?: string): Tab | undefined {
|
|
194
|
+
if (tabId) {
|
|
195
|
+
return this._tabs[tabId];
|
|
196
|
+
}
|
|
197
|
+
const currentTabId = this.persistentConfig.currentId();
|
|
198
|
+
if (currentTabId) return this._tabs[currentTabId];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//only handle UI interaction, don't emit or store anything
|
|
202
|
+
private markTabSelected(tabId: string): boolean {
|
|
203
|
+
if (!this.persistentConfig.getTab(tabId)) {
|
|
204
|
+
//there is no tab config for this id. We _probably_ deleted a tab by pressing 'x', which fires the 'selectTab'
|
|
205
|
+
//event after. I.e., nothing to select anymore, and we should just ignore this
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
//mark tab active
|
|
209
|
+
this.tabElements.selectTab(tabId);
|
|
210
|
+
|
|
211
|
+
//draw tab content
|
|
212
|
+
if (!this._tabs[tabId]) {
|
|
213
|
+
this._tabs[tabId] = new Tab(this, Tab.getDefaults(this));
|
|
214
|
+
}
|
|
215
|
+
this._tabs[tabId].show();
|
|
216
|
+
for (const otherTabId in this._tabs) {
|
|
217
|
+
if (otherTabId !== tabId) this._tabs[otherTabId].hide();
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
public selectTabId(tabId: string) {
|
|
222
|
+
const tab = this.getTab();
|
|
223
|
+
if (tab && tab.getId() !== tabId) {
|
|
224
|
+
if (this.markTabSelected(tabId)) {
|
|
225
|
+
//emit
|
|
226
|
+
this.emit("tabSelect", this, tabId);
|
|
227
|
+
this.persistentConfig.setActive(tabId);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return tab;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Checks if two persistent tab configuration are the same based.
|
|
234
|
+
* It isnt a strict equality, as falsy values (e.g. a header that isnt set in one tabjson) isnt taken into consideration
|
|
235
|
+
* Things like the yasr response are also not taken into consideration
|
|
236
|
+
* @param tab1 Base comparable object
|
|
237
|
+
* @param tab2 Second comparable object
|
|
238
|
+
*/
|
|
239
|
+
private tabConfigEquals(tab1: PersistedTabJson, tab2: PersistedTabJson): boolean {
|
|
240
|
+
let sameRequest = true;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Check request config
|
|
244
|
+
*/
|
|
245
|
+
let key: keyof RequestConfig<Yasgui>;
|
|
246
|
+
for (key in tab1.requestConfig) {
|
|
247
|
+
if (!tab1.requestConfig[key]) continue;
|
|
248
|
+
if (!isEqual(tab2.requestConfig[key], tab1.requestConfig[key])) {
|
|
249
|
+
sameRequest = false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check yasqe settings
|
|
254
|
+
*/
|
|
255
|
+
if (sameRequest) {
|
|
256
|
+
sameRequest = (<Array<keyof PersistedTabJson["yasqe"]>>["endpoint", "value"]).every(
|
|
257
|
+
(key) => tab1.yasqe[key] === tab2.yasqe[key],
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Check yasr settings
|
|
263
|
+
*/
|
|
264
|
+
if (sameRequest) {
|
|
265
|
+
sameRequest =
|
|
266
|
+
tab1.yasr.settings.selectedPlugin === tab2.yasr.settings.selectedPlugin &&
|
|
267
|
+
isEqual(
|
|
268
|
+
tab1.yasr.settings.pluginsConfig?.[tab1.yasr.settings?.selectedPlugin || ""],
|
|
269
|
+
tab2.yasr.settings.pluginsConfig?.[tab2.yasr.settings?.selectedPlugin || ""],
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return sameRequest && tab1.name === tab2.name;
|
|
274
|
+
}
|
|
275
|
+
private findTabIdForConfig(tabConfig: PersistedTabJson) {
|
|
276
|
+
return this.persistentConfig.getTabs().find((tabId) => {
|
|
277
|
+
const tab = this.persistentConfig.getTab(tabId);
|
|
278
|
+
return this.tabConfigEquals(tab, tabConfig);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private _registerTabListeners(tab: Tab) {
|
|
283
|
+
tab.on("change", (tab) => this.emit("tabChange", this, tab));
|
|
284
|
+
tab.on("query", (tab) => this.emit("query", this, tab));
|
|
285
|
+
tab.on("queryBefore", (tab) => this.emit("queryBefore", this, tab));
|
|
286
|
+
tab.on("queryAbort", (tab) => this.emit("queryAbort", this, tab));
|
|
287
|
+
tab.on("queryResponse", (tab) => this.emit("queryResponse", this, tab));
|
|
288
|
+
tab.on("autocompletionShown", (tab, widget) => this.emit("autocompletionShown", this, tab, widget));
|
|
289
|
+
tab.on("autocompletionClose", (tab) => this.emit("autocompletionClose", this, tab));
|
|
290
|
+
}
|
|
291
|
+
public _setPanel(panelId: string, panel: HTMLDivElement) {
|
|
292
|
+
for (const id in this._tabs) {
|
|
293
|
+
if (id !== panelId) this._tabs[id].hide();
|
|
294
|
+
}
|
|
295
|
+
this.tabPanelsEl.appendChild(panel);
|
|
296
|
+
}
|
|
297
|
+
public _removePanel(panel: HTMLDivElement | undefined) {
|
|
298
|
+
if (panel) this.tabPanelsEl.removeChild(panel);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Adds a tab to yasgui
|
|
302
|
+
* @param setActive if the tab should become active when added
|
|
303
|
+
* @param [partialTabConfig] config to add to the Tab
|
|
304
|
+
* @param [opts] extra options, atIndex, at which position the tab should be added, avoidDuplicateTabs: if the config already exists make that tab active
|
|
305
|
+
*
|
|
306
|
+
* @returns tab
|
|
307
|
+
*/
|
|
308
|
+
public addTab(
|
|
309
|
+
setActive: boolean,
|
|
310
|
+
partialTabConfig?: Partial<PersistedTabJson>,
|
|
311
|
+
opts: { atIndex?: number; avoidDuplicateTabs?: boolean } = {},
|
|
312
|
+
): Tab {
|
|
313
|
+
const tabConfig = merge({}, Tab.getDefaults(this), partialTabConfig);
|
|
314
|
+
if (tabConfig.id && this.getTab(tabConfig.id)) {
|
|
315
|
+
throw new Error("Duplicate tab ID");
|
|
316
|
+
}
|
|
317
|
+
// Check if we should copy the endpoint in the new tab and only copy if the tabConfig doesn't contain an endpoint
|
|
318
|
+
if (this.config.copyEndpointOnNewTab && !partialTabConfig?.requestConfig?.endpoint) {
|
|
319
|
+
const currentTab = this.getTab();
|
|
320
|
+
if (currentTab) {
|
|
321
|
+
tabConfig.requestConfig.endpoint = currentTab.getEndpoint();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (opts.avoidDuplicateTabs) {
|
|
325
|
+
const foundTabId = this.findTabIdForConfig(tabConfig);
|
|
326
|
+
if (foundTabId) {
|
|
327
|
+
return this.selectTabId(foundTabId) as Tab;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const tabId = tabConfig.id;
|
|
331
|
+
const index = opts.atIndex;
|
|
332
|
+
this.persistentConfig.addToTabList(tabId, index);
|
|
333
|
+
this.emit("tabAdd", this, tabId);
|
|
334
|
+
this._tabs[tabId] = new Tab(this, tabConfig);
|
|
335
|
+
this.emit("tabChange", this, this._tabs[tabId]); //do emit, so the default config is persisted
|
|
336
|
+
|
|
337
|
+
this.tabElements.addTab(tabId, index);
|
|
338
|
+
this._registerTabListeners(this._tabs[tabId]);
|
|
339
|
+
if (setActive) {
|
|
340
|
+
this.persistentConfig.setActive(tabId);
|
|
341
|
+
this._tabs[tabId].show();
|
|
342
|
+
}
|
|
343
|
+
return this._tabs[tabId];
|
|
344
|
+
}
|
|
345
|
+
public restoreLastTab() {
|
|
346
|
+
const config = this.persistentConfig.retrieveLastClosedTab();
|
|
347
|
+
if (config) {
|
|
348
|
+
this.addTab(true, config.tab, { atIndex: config.index });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
public destroy() {
|
|
352
|
+
this.removeAllListeners();
|
|
353
|
+
this.tabElements.destroy();
|
|
354
|
+
for (const tabId in this._tabs) {
|
|
355
|
+
const tab = this._tabs[tabId];
|
|
356
|
+
tab.destroy();
|
|
357
|
+
}
|
|
358
|
+
this._tabs = {};
|
|
359
|
+
|
|
360
|
+
while (this.rootEl.firstChild) this.rootEl.firstChild.remove();
|
|
361
|
+
}
|
|
362
|
+
public static linkUtils = shareLink;
|
|
363
|
+
public static Yasr = Yasr;
|
|
364
|
+
public static Yasqe = Yasqe;
|
|
365
|
+
public static defaults = initializeDefaults();
|
|
366
|
+
public static corsEnabled: { [endpoint: string]: boolean } = {};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function getRandomId() {
|
|
370
|
+
return Math.random().toString(36).substring(7);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export default Yasgui;
|
package/src/linkUtils.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const JsUri = require("jsuri");
|
|
2
|
+
|
|
3
|
+
import { default as Tab, PersistedJson } from "./Tab";
|
|
4
|
+
import Yasr from "@matdata/yasr";
|
|
5
|
+
import { PlainRequestConfig } from "@matdata/yasqe";
|
|
6
|
+
import { getAsValue } from "@matdata/yasgui-utils";
|
|
7
|
+
var getUrlParams = function (_url?: string) {
|
|
8
|
+
var urlFromWindow = false;
|
|
9
|
+
if (!_url) {
|
|
10
|
+
_url = window.location.href;
|
|
11
|
+
urlFromWindow = true;
|
|
12
|
+
}
|
|
13
|
+
var url = new JsUri(_url);
|
|
14
|
+
if (url.anchor().length > 0) {
|
|
15
|
+
//firefox does some decoding if we're using window.location.hash (e.g. the + sign in contentType settings)
|
|
16
|
+
//Don't want this. So simply get the hash string ourselves
|
|
17
|
+
url.query(url.anchor());
|
|
18
|
+
if (urlFromWindow) window.location.hash = ""; //clear hash
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type RequestArgs = { [argName: string]: string | string[] };
|
|
24
|
+
export function appendArgsToUrl(_url: string, args: RequestArgs): string {
|
|
25
|
+
var url = new JsUri(_url);
|
|
26
|
+
for (const arg in args) {
|
|
27
|
+
const val = args[arg];
|
|
28
|
+
if (Array.isArray(val)) {
|
|
29
|
+
for (const subVal of val) {
|
|
30
|
+
url.addQueryParam(arg, subVal);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
url.addQueryParam(arg, val);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
export function createShareLink(forUrl: string, tab: Tab) {
|
|
39
|
+
const currentUrl = new JsUri(forUrl);
|
|
40
|
+
const tmpUrl = new JsUri();
|
|
41
|
+
const configObject = createShareConfig(tab);
|
|
42
|
+
let key: keyof ShareConfigObject; // Need to specify here because left hand side of a for..in cannot be typed
|
|
43
|
+
for (key in configObject) {
|
|
44
|
+
if (!configObject.hasOwnProperty(key)) continue;
|
|
45
|
+
if (key === "namedGraphs") {
|
|
46
|
+
configObject.namedGraphs.forEach((ng) => tmpUrl.addQueryParam("namedGraph", ng));
|
|
47
|
+
} else if (key === "defaultGraphs") {
|
|
48
|
+
configObject.defaultGraphs.forEach((dg) => tmpUrl.addQueryParam("defaultGraph", dg));
|
|
49
|
+
} else if (key === "args") {
|
|
50
|
+
const args = getAsValue(configObject.args, tab.yasgui);
|
|
51
|
+
args.forEach((arg) => tmpUrl.addQueryParam(arg.name, arg.value));
|
|
52
|
+
} else if (typeof configObject[key] === "object") {
|
|
53
|
+
if (configObject[key]) tmpUrl.addQueryParam(key, JSON.stringify(configObject[key]));
|
|
54
|
+
} else {
|
|
55
|
+
if (configObject[key]) tmpUrl.addQueryParam(key, configObject[key]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//extend existing link, so first fetch current arguments. But: make sure we don't include items already used in share link
|
|
60
|
+
// if (window.location.hash.length > 1) {
|
|
61
|
+
const currentUrlParams = getUrlParams(forUrl);
|
|
62
|
+
const currentParams: [string, string][] = (<any>currentUrlParams).queryPairs;
|
|
63
|
+
currentParams.forEach(function (paramPair) {
|
|
64
|
+
if (!tmpUrl.hasQueryParam(paramPair[0])) {
|
|
65
|
+
tmpUrl.addQueryParam(paramPair[0], paramPair[1]);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
currentUrl.anchor(tmpUrl.query().substr(1));
|
|
71
|
+
return currentUrl.toString();
|
|
72
|
+
}
|
|
73
|
+
export type ShareConfigObject = {
|
|
74
|
+
query: string;
|
|
75
|
+
endpoint: string;
|
|
76
|
+
requestMethod: PlainRequestConfig["method"];
|
|
77
|
+
tabTitle: string;
|
|
78
|
+
headers: PlainRequestConfig["headers"];
|
|
79
|
+
contentTypeConstruct: string;
|
|
80
|
+
contentTypeSelect: string;
|
|
81
|
+
args: PlainRequestConfig["args"];
|
|
82
|
+
namedGraphs: PlainRequestConfig["namedGraphs"];
|
|
83
|
+
defaultGraphs: PlainRequestConfig["defaultGraphs"];
|
|
84
|
+
outputFormat?: string;
|
|
85
|
+
outputSettings: any;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function createShareConfig(tab: Tab): ShareConfigObject {
|
|
89
|
+
const yasgui = tab.yasgui;
|
|
90
|
+
const requestConfig = tab.getRequestConfig();
|
|
91
|
+
const yasrPersistentSetting = tab.getPersistedJson().yasr.settings;
|
|
92
|
+
return {
|
|
93
|
+
query: tab.getQuery(),
|
|
94
|
+
endpoint: tab.getEndpoint(),
|
|
95
|
+
requestMethod: getAsValue(requestConfig.method, yasgui),
|
|
96
|
+
tabTitle: tab.getName(),
|
|
97
|
+
// headers: isFunction(requestConfig.headers) ? requestConfig.headers(tab.yasgui) : requestConfig.headers,
|
|
98
|
+
headers: getAsValue(requestConfig.headers, yasgui),
|
|
99
|
+
contentTypeConstruct: getAsValue(requestConfig.acceptHeaderGraph, yasgui),
|
|
100
|
+
contentTypeSelect: getAsValue(requestConfig.acceptHeaderSelect, yasgui),
|
|
101
|
+
args: getAsValue(requestConfig.args, yasgui),
|
|
102
|
+
namedGraphs: getAsValue(requestConfig.namedGraphs, yasgui),
|
|
103
|
+
defaultGraphs: getAsValue(requestConfig.defaultGraphs, yasgui),
|
|
104
|
+
outputFormat: yasrPersistentSetting.selectedPlugin,
|
|
105
|
+
outputSettings:
|
|
106
|
+
yasrPersistentSetting.pluginsConfig && yasrPersistentSetting.selectedPlugin
|
|
107
|
+
? yasrPersistentSetting.pluginsConfig[yasrPersistentSetting.selectedPlugin]
|
|
108
|
+
: undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getConfigFromUrl(defaults: PersistedJson, _url?: string): PersistedJson | undefined {
|
|
113
|
+
const options = defaults;
|
|
114
|
+
|
|
115
|
+
var url = getUrlParams(_url);
|
|
116
|
+
var hasQuery = false;
|
|
117
|
+
const currentParams: [string, string][] = (<any>url).queryPairs;
|
|
118
|
+
var pluginsConfig: any;
|
|
119
|
+
currentParams.forEach(function ([key, value]) {
|
|
120
|
+
if (key === "query") {
|
|
121
|
+
hasQuery = true;
|
|
122
|
+
options.yasqe.value = value;
|
|
123
|
+
} else if (key === "outputFormat" && value.length) {
|
|
124
|
+
if (Yasr.plugins[value]) {
|
|
125
|
+
options.yasr.settings.selectedPlugin = value;
|
|
126
|
+
} else {
|
|
127
|
+
console.warn(`Output format plugin "${value}" not found`);
|
|
128
|
+
}
|
|
129
|
+
} else if (key == "outputSettings") {
|
|
130
|
+
pluginsConfig = JSON.parse(value);
|
|
131
|
+
} else if (key == "contentTypeConstruct") {
|
|
132
|
+
options.requestConfig.acceptHeaderGraph = value;
|
|
133
|
+
} else if (key == "contentTypeSelect") {
|
|
134
|
+
options.requestConfig.acceptHeaderSelect = value;
|
|
135
|
+
} else if (key == "endpoint") {
|
|
136
|
+
options.requestConfig.endpoint = value;
|
|
137
|
+
} else if (key == "requestMethod") {
|
|
138
|
+
options.requestConfig.method = <any>value;
|
|
139
|
+
} else if (key == "tabTitle") {
|
|
140
|
+
options.name = value;
|
|
141
|
+
} else if (key == "namedGraph") {
|
|
142
|
+
if (!Array.isArray(options.requestConfig.namedGraphs)) options.requestConfig.namedGraphs = [];
|
|
143
|
+
options.requestConfig.namedGraphs.push(value);
|
|
144
|
+
} else if (key == "defaultGraph") {
|
|
145
|
+
if (!Array.isArray(options.requestConfig.defaultGraphs)) options.requestConfig.defaultGraphs = [];
|
|
146
|
+
options.requestConfig.defaultGraphs.push(value);
|
|
147
|
+
} else if (key == "headers") {
|
|
148
|
+
if (!options.requestConfig.headers) options.requestConfig.headers = {};
|
|
149
|
+
options.requestConfig.headers = JSON.parse(value);
|
|
150
|
+
} else {
|
|
151
|
+
//regular arguments. So store them as regular arguments
|
|
152
|
+
if (!options.requestConfig.args) options.requestConfig.args = [];
|
|
153
|
+
(options.requestConfig.args as Array<{ name: string; value: string }>).push({ name: key, value: value });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
//Only know where to store the plugins config after we've saved the selected plugin
|
|
157
|
+
//i.e., do this after the previous loop
|
|
158
|
+
if (pluginsConfig && options.yasr.settings.selectedPlugin && options.yasr.settings.pluginsConfig) {
|
|
159
|
+
options.yasr.settings.pluginsConfig[options.yasr.settings.selectedPlugin] = pluginsConfig;
|
|
160
|
+
}
|
|
161
|
+
if (hasQuery) {
|
|
162
|
+
return options;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function queryCatalogConfigToTabConfig<Q extends QueryCatalogConfig>(
|
|
167
|
+
catalogConfig: Q,
|
|
168
|
+
defaults?: PersistedJson,
|
|
169
|
+
): PersistedJson {
|
|
170
|
+
const options = defaults || Tab.getDefaults();
|
|
171
|
+
if (catalogConfig.service) {
|
|
172
|
+
options.requestConfig.endpoint = catalogConfig.service;
|
|
173
|
+
}
|
|
174
|
+
if (catalogConfig.requestConfig) {
|
|
175
|
+
if (catalogConfig.requestConfig.payload) {
|
|
176
|
+
if (catalogConfig.requestConfig.payload.query) {
|
|
177
|
+
options.yasqe.value = catalogConfig.requestConfig.payload.query;
|
|
178
|
+
}
|
|
179
|
+
if (catalogConfig.requestConfig.payload["default-graph-uri"]) {
|
|
180
|
+
options.requestConfig.defaultGraphs = Array.isArray(catalogConfig.requestConfig.payload["default-graph-uri"])
|
|
181
|
+
? catalogConfig.requestConfig.payload["default-graph-uri"]
|
|
182
|
+
: [catalogConfig.requestConfig.payload["default-graph-uri"]];
|
|
183
|
+
}
|
|
184
|
+
if (catalogConfig.requestConfig.payload["named-graph-uri"]) {
|
|
185
|
+
options.requestConfig.namedGraphs = Array.isArray(catalogConfig.requestConfig.payload["named-graph-uri"])
|
|
186
|
+
? catalogConfig.requestConfig.payload["named-graph-uri"]
|
|
187
|
+
: [catalogConfig.requestConfig.payload["named-graph-uri"]];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (catalogConfig.requestConfig.headers) {
|
|
191
|
+
options.requestConfig.headers = catalogConfig.requestConfig.headers;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (catalogConfig.renderConfig) {
|
|
195
|
+
if (catalogConfig.renderConfig.output) {
|
|
196
|
+
if (Yasr.plugins[catalogConfig.renderConfig.output]) {
|
|
197
|
+
options.yasr.settings.selectedPlugin = catalogConfig.renderConfig.output;
|
|
198
|
+
} else {
|
|
199
|
+
console.warn(`Output format plugin "${catalogConfig.renderConfig.output}" not found`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (catalogConfig.renderConfig.settings) {
|
|
203
|
+
if (Yasr.plugins[catalogConfig.renderConfig.output] && options.yasr.settings.pluginsConfig) {
|
|
204
|
+
options.yasr.settings.pluginsConfig[catalogConfig.renderConfig.output] = catalogConfig.renderConfig.settings;
|
|
205
|
+
} else {
|
|
206
|
+
console.warn(`Output format plugin "${catalogConfig.renderConfig.output}" not found, cannot apply settings`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (catalogConfig.name) {
|
|
211
|
+
options.name = catalogConfig.name;
|
|
212
|
+
}
|
|
213
|
+
return options;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface QueryCatalogConfig {
|
|
217
|
+
service: string;
|
|
218
|
+
name: string;
|
|
219
|
+
description: string;
|
|
220
|
+
requestConfig?: {
|
|
221
|
+
payload: {
|
|
222
|
+
query: string;
|
|
223
|
+
"default-graph-uri"?: string | string[];
|
|
224
|
+
"named-graph-uri"?: string | string[];
|
|
225
|
+
};
|
|
226
|
+
headers?: {
|
|
227
|
+
[key: string]: string;
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
renderConfig?: {
|
|
231
|
+
output: string;
|
|
232
|
+
settings?: any;
|
|
233
|
+
};
|
|
234
|
+
}
|
package/src/tab.scss
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.yasgui {
|
|
2
|
+
.tabPanel {
|
|
3
|
+
display: none;
|
|
4
|
+
position: relative;
|
|
5
|
+
&.active {
|
|
6
|
+
display: block;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Hide editor wrapper when yasr is in fullscreen
|
|
10
|
+
&:has(.yasr.fullscreen) .editorwrapper {
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// When yasqe is fullscreen, keep controlbar visible
|
|
15
|
+
&:has(.yasqe.fullscreen) .controlbar {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 50px;
|
|
18
|
+
left: 0;
|
|
19
|
+
right: 0;
|
|
20
|
+
z-index: 9999;
|
|
21
|
+
background: white;
|
|
22
|
+
border-bottom: 1px solid #ddd;
|
|
23
|
+
padding: 5px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
.yasr {
|
|
27
|
+
margin-top: 5px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tabContextButton {
|
|
31
|
+
border: none;
|
|
32
|
+
background: none;
|
|
33
|
+
align-self: center;
|
|
34
|
+
padding-left: 10px;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
color: #505050;
|
|
37
|
+
fill: #505050;
|
|
38
|
+
|
|
39
|
+
.svgImg {
|
|
40
|
+
width: 15px;
|
|
41
|
+
height: 15px;
|
|
42
|
+
font-family: initial; //font families can slightly misalign svgs with the .svgimg div
|
|
43
|
+
}
|
|
44
|
+
// IE11 Needs this specified otherwise it will not resize the svg
|
|
45
|
+
svg {
|
|
46
|
+
max-width: 15px;
|
|
47
|
+
max-height: 15px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:hover {
|
|
51
|
+
color: black;
|
|
52
|
+
fill: black;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.controlbar {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-content: center;
|
|
59
|
+
max-height: 35px;
|
|
60
|
+
}
|
|
61
|
+
}
|