@perspective-dev/workspace 4.0.0
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/LICENSE.md +193 -0
- package/dist/cdn/perspective-workspace.js +45 -0
- package/dist/cdn/perspective-workspace.js.map +7 -0
- package/dist/css/pro-dark.css +1 -0
- package/dist/css/pro.css +1 -0
- package/dist/esm/perspective-workspace.d.ts +181 -0
- package/dist/esm/perspective-workspace.js +14 -0
- package/dist/esm/perspective-workspace.js.map +7 -0
- package/dist/esm/utils/custom_elements.d.ts +14 -0
- package/dist/esm/utils/observable_map.d.ts +9 -0
- package/dist/esm/workspace/commands.d.ts +3 -0
- package/dist/esm/workspace/dockpanel.d.ts +13 -0
- package/dist/esm/workspace/index.d.ts +1 -0
- package/dist/esm/workspace/menu.d.ts +11 -0
- package/dist/esm/workspace/tabbar.d.ts +25 -0
- package/dist/esm/workspace/tabbarrenderer.d.ts +19 -0
- package/dist/esm/workspace/widget.d.ts +27 -0
- package/dist/esm/workspace/workspace.d.ts +123 -0
- package/package.json +55 -0
- package/src/html/workspace.html +11 -0
- package/src/less/dockpanel.less +95 -0
- package/src/less/injected.less +14 -0
- package/src/less/menu.less +128 -0
- package/src/less/tabbar.less +366 -0
- package/src/less/viewer.less +86 -0
- package/src/less/widget.less +40 -0
- package/src/less/workspace.less +70 -0
- package/src/svg/bookmark-icon.svg +4 -0
- package/src/svg/drag-handle.svg +10 -0
- package/src/themes/pro-dark.less +139 -0
- package/src/themes/pro.less +93 -0
- package/src/ts/external.d.ts +21 -0
- package/src/ts/external.js +11 -0
- package/src/ts/perspective-workspace.ts +306 -0
- package/src/ts/utils/custom_elements.ts +95 -0
- package/src/ts/utils/observable_map.ts +39 -0
- package/src/ts/workspace/commands.ts +269 -0
- package/src/ts/workspace/dockpanel.ts +145 -0
- package/src/ts/workspace/index.ts +13 -0
- package/src/ts/workspace/menu.ts +213 -0
- package/src/ts/workspace/tabbar.ts +237 -0
- package/src/ts/workspace/tabbarrenderer.ts +91 -0
- package/src/ts/workspace/widget.ts +101 -0
- package/src/ts/workspace/workspace.ts +1056 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { find, toArray } from "@lumino/algorithm";
|
|
14
|
+
import { CommandRegistry } from "@lumino/commands";
|
|
15
|
+
import { SplitPanel, Panel, DockPanel } from "@lumino/widgets";
|
|
16
|
+
import uniqBy from "lodash/uniqBy";
|
|
17
|
+
import { DebouncedFunc, isEqual } from "lodash";
|
|
18
|
+
import debounce from "lodash/debounce";
|
|
19
|
+
import type {
|
|
20
|
+
HTMLPerspectiveViewerElement,
|
|
21
|
+
ViewerConfigUpdate,
|
|
22
|
+
} from "@perspective-dev/viewer";
|
|
23
|
+
import type * as psp from "@perspective-dev/client";
|
|
24
|
+
import injectedStyles from "../../../build/css/injected.css";
|
|
25
|
+
import { PerspectiveDockPanel } from "./dockpanel";
|
|
26
|
+
import { WorkspaceMenu } from "./menu";
|
|
27
|
+
import { createCommands } from "./commands";
|
|
28
|
+
import { PerspectiveViewerWidget } from "./widget";
|
|
29
|
+
import { ObservableMap } from "../utils/observable_map";
|
|
30
|
+
|
|
31
|
+
const DEFAULT_WORKSPACE_SIZE = [1, 3];
|
|
32
|
+
|
|
33
|
+
let ID_COUNTER = 0;
|
|
34
|
+
|
|
35
|
+
export interface PerspectiveLayout<T> {
|
|
36
|
+
children?: PerspectiveLayout<T>[];
|
|
37
|
+
widgets?: T[];
|
|
38
|
+
sizes: number[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ViewerConfigUpdateExt extends ViewerConfigUpdate {
|
|
42
|
+
table: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PerspectiveWorkspaceConfig<T> {
|
|
46
|
+
sizes: number[];
|
|
47
|
+
master: PerspectiveLayout<T>;
|
|
48
|
+
detail: PerspectiveLayout<T>;
|
|
49
|
+
viewers: Record<string, ViewerConfigUpdateExt>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class PerspectiveWorkspace extends SplitPanel {
|
|
53
|
+
private dockpanel: PerspectiveDockPanel;
|
|
54
|
+
private detailPanel: Panel;
|
|
55
|
+
private masterPanel: SplitPanel;
|
|
56
|
+
element: HTMLElement;
|
|
57
|
+
menu_elem: HTMLElement;
|
|
58
|
+
private _tables: ObservableMap<string, psp.Table | Promise<psp.Table>>;
|
|
59
|
+
private listeners: WeakMap<PerspectiveViewerWidget, () => void>;
|
|
60
|
+
private indicator: HTMLElement;
|
|
61
|
+
private commands: CommandRegistry;
|
|
62
|
+
private _menu?: WorkspaceMenu;
|
|
63
|
+
private _minimizedLayoutSlots?: DockPanel.ILayoutConfig;
|
|
64
|
+
private _minimizedLayout?: DockPanel.ILayoutConfig;
|
|
65
|
+
private _maximizedWidget?: PerspectiveViewerWidget;
|
|
66
|
+
private _last_updated_state?: PerspectiveWorkspaceConfig<string>;
|
|
67
|
+
// private _context_menu?: Menu & { init_overlay?: () => void };
|
|
68
|
+
|
|
69
|
+
constructor(element: HTMLElement) {
|
|
70
|
+
super({ orientation: "horizontal" });
|
|
71
|
+
this.addClass("perspective-workspace");
|
|
72
|
+
this.dockpanel = new PerspectiveDockPanel(this);
|
|
73
|
+
this.detailPanel = new Panel();
|
|
74
|
+
this.detailPanel.layout!.fitPolicy = "set-no-constraint";
|
|
75
|
+
this.detailPanel.addClass("perspective-scroll-panel");
|
|
76
|
+
this.detailPanel.addWidget(this.dockpanel);
|
|
77
|
+
this.masterPanel = new SplitPanel({ orientation: "vertical" });
|
|
78
|
+
this.masterPanel.addClass("master-panel");
|
|
79
|
+
this.dockpanel.layoutModified.connect(() => {
|
|
80
|
+
this.workspaceUpdated();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.addWidget(this.detailPanel);
|
|
84
|
+
this.spacing = 6;
|
|
85
|
+
this.element = element;
|
|
86
|
+
this.listeners = new WeakMap();
|
|
87
|
+
this._tables = new ObservableMap();
|
|
88
|
+
this._tables.addSetListener(this._set_listener.bind(this));
|
|
89
|
+
this._tables.addDeleteListener(this._delete_listener.bind(this));
|
|
90
|
+
this.indicator = this.init_indicator();
|
|
91
|
+
this.commands = createCommands(this, this.indicator);
|
|
92
|
+
this.menu_elem = document.createElement("perspective-workspace-menu");
|
|
93
|
+
this.menu_elem.attachShadow({ mode: "open" });
|
|
94
|
+
this.menu_elem.shadowRoot!.innerHTML = `<style>:host{position:absolute;}${injectedStyles}</style>`;
|
|
95
|
+
|
|
96
|
+
this.element.shadowRoot!.insertBefore(
|
|
97
|
+
this.menu_elem,
|
|
98
|
+
this.element.shadowRoot!.lastElementChild!,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
element.addEventListener("contextmenu", (event) =>
|
|
102
|
+
this.showContextMenu(null, event),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get_context_menu(): WorkspaceMenu | undefined {
|
|
107
|
+
return this._menu;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get_dock_panel(): PerspectiveDockPanel {
|
|
111
|
+
return this.dockpanel;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
init_indicator() {
|
|
115
|
+
const indicator = document.createElement("perspective-indicator");
|
|
116
|
+
indicator.style.position = "fixed";
|
|
117
|
+
indicator.style.pointerEvents = "none";
|
|
118
|
+
document.body.appendChild(indicator);
|
|
119
|
+
return indicator;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
apply_indicator_theme() {
|
|
123
|
+
const theme_name = JSON.parse(
|
|
124
|
+
window
|
|
125
|
+
.getComputedStyle(this.element)
|
|
126
|
+
.getPropertyValue("--theme-name")
|
|
127
|
+
.trim(),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
this.indicator.setAttribute("theme", theme_name);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/***************************************************************************
|
|
134
|
+
*
|
|
135
|
+
* `<perspective-workspace>` Public API
|
|
136
|
+
*
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
addTable(name: string, table: Promise<psp.Table>) {
|
|
140
|
+
this.tables.set(name, table);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getTable(name: string): psp.Table | Promise<psp.Table> {
|
|
144
|
+
return this.tables.get(name);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
removeTable(name: string) {
|
|
148
|
+
return this.tables.delete(name);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
replaceTable(name: string, table: Promise<psp.Table>) {
|
|
152
|
+
this.tables.set(name, table);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get tables() {
|
|
156
|
+
return this._tables;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async save() {
|
|
160
|
+
const is_settings = this.dockpanel.mode === "single-document";
|
|
161
|
+
let detail = is_settings
|
|
162
|
+
? this._minimizedLayoutSlots
|
|
163
|
+
: PerspectiveDockPanel.mapWidgets(
|
|
164
|
+
(widget) =>
|
|
165
|
+
// this.getWidgetByName(widget)!.viewer.getAttribute("slot")
|
|
166
|
+
(widget as PerspectiveViewerWidget).viewer.getAttribute(
|
|
167
|
+
"slot",
|
|
168
|
+
),
|
|
169
|
+
this.dockpanel.saveLayout(),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const layout = {
|
|
173
|
+
sizes: [...this.relativeSizes()],
|
|
174
|
+
detail,
|
|
175
|
+
master: undefined as
|
|
176
|
+
| { widgets: string[]; sizes: number[] }
|
|
177
|
+
| undefined,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (this.masterPanel.isAttached) {
|
|
181
|
+
const master = {
|
|
182
|
+
widgets: this.masterPanel.widgets.map(
|
|
183
|
+
(widget) =>
|
|
184
|
+
(widget as PerspectiveViewerWidget).viewer.getAttribute(
|
|
185
|
+
"slot",
|
|
186
|
+
)!,
|
|
187
|
+
),
|
|
188
|
+
sizes: [...this.masterPanel.relativeSizes()],
|
|
189
|
+
};
|
|
190
|
+
layout.master = master;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const viewers: Record<string, ViewerConfigUpdate> = {};
|
|
194
|
+
for (const widget of this.masterPanel.widgets) {
|
|
195
|
+
const psp_widget = widget as PerspectiveViewerWidget;
|
|
196
|
+
viewers[psp_widget.viewer.getAttribute("slot")!] =
|
|
197
|
+
await psp_widget.save();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const widgets = PerspectiveDockPanel.getWidgets(
|
|
201
|
+
is_settings ? this._minimizedLayout! : this.dockpanel.saveLayout(),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await Promise.all(
|
|
205
|
+
widgets.map(async (widget) => {
|
|
206
|
+
const psp_widget = widget as PerspectiveViewerWidget;
|
|
207
|
+
const slot = psp_widget.viewer.getAttribute("slot")!;
|
|
208
|
+
viewers[slot] = await psp_widget.save();
|
|
209
|
+
viewers[slot]!.settings = false;
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
return { ...layout, viewers };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async restore(value: PerspectiveWorkspaceConfig<string>) {
|
|
217
|
+
const {
|
|
218
|
+
sizes,
|
|
219
|
+
master,
|
|
220
|
+
detail,
|
|
221
|
+
viewers: viewer_configs = {},
|
|
222
|
+
} = structuredClone(value);
|
|
223
|
+
|
|
224
|
+
if (master && master.widgets!.length > 0) {
|
|
225
|
+
this.setupMasterPanel(sizes || DEFAULT_WORKSPACE_SIZE);
|
|
226
|
+
} else {
|
|
227
|
+
if (this.masterPanel.isAttached) {
|
|
228
|
+
this.detailPanel.removeClass("has-master-panel");
|
|
229
|
+
this.masterPanel.close();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.addWidget(this.detailPanel);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let tasks: Promise<void>[] = [];
|
|
236
|
+
|
|
237
|
+
// Using ES generators as context managers ..
|
|
238
|
+
for (const viewers of this._capture_viewers()) {
|
|
239
|
+
for (const widgets of this._capture_widgets()) {
|
|
240
|
+
for (const v of viewers) {
|
|
241
|
+
v.removeAttribute("class");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const callback = this._restore_callback.bind(
|
|
245
|
+
this,
|
|
246
|
+
viewer_configs,
|
|
247
|
+
viewers,
|
|
248
|
+
widgets,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (detail) {
|
|
252
|
+
const detailLayout = PerspectiveDockPanel.mapWidgets(
|
|
253
|
+
(name: string) => callback.bind(this, false)(name),
|
|
254
|
+
detail,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
this.dockpanel.mode = "multiple-document";
|
|
258
|
+
this.dockpanel.restoreLayout(detailLayout);
|
|
259
|
+
tasks = tasks.concat(
|
|
260
|
+
PerspectiveDockPanel.getWidgets(detailLayout).map(
|
|
261
|
+
(x) => (x as PerspectiveViewerWidget).task!,
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (master) {
|
|
267
|
+
tasks = tasks.concat(
|
|
268
|
+
master.widgets!.map(
|
|
269
|
+
(name) => callback.bind(this, true)(name).task!,
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
master.sizes &&
|
|
274
|
+
this.masterPanel.setRelativeSizes(master.sizes);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await Promise.all(tasks);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
*_capture_widgets() {
|
|
283
|
+
const widgets = this.getAllWidgets();
|
|
284
|
+
yield widgets;
|
|
285
|
+
for (const widget of widgets) {
|
|
286
|
+
if (!widget.node.isConnected) {
|
|
287
|
+
widget.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
*_capture_viewers() {
|
|
293
|
+
const viewers = Array.from(
|
|
294
|
+
this.element.children,
|
|
295
|
+
) as HTMLPerspectiveViewerElement[];
|
|
296
|
+
|
|
297
|
+
yield viewers;
|
|
298
|
+
const ending_widgets = this.getAllWidgets();
|
|
299
|
+
for (const viewer of viewers) {
|
|
300
|
+
let widget = ending_widgets.find((x) => {
|
|
301
|
+
const psp_widget = x as PerspectiveViewerWidget;
|
|
302
|
+
return psp_widget.viewer === viewer;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (
|
|
306
|
+
!widget &&
|
|
307
|
+
Array.from(this.element.children).indexOf(viewer) > -1
|
|
308
|
+
) {
|
|
309
|
+
this.element.removeChild(viewer);
|
|
310
|
+
viewer.delete();
|
|
311
|
+
viewer.free();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_restore_callback(
|
|
317
|
+
viewers: Record<string, ViewerConfigUpdateExt>,
|
|
318
|
+
starting_viewers: HTMLPerspectiveViewerElement[],
|
|
319
|
+
starting_widgets: PerspectiveViewerWidget[],
|
|
320
|
+
master: boolean,
|
|
321
|
+
widgetName: string,
|
|
322
|
+
) {
|
|
323
|
+
let viewer_config;
|
|
324
|
+
viewer_config = viewers[widgetName];
|
|
325
|
+
|
|
326
|
+
let viewer =
|
|
327
|
+
!!widgetName &&
|
|
328
|
+
starting_viewers.find((x) => x.getAttribute("slot") === widgetName);
|
|
329
|
+
|
|
330
|
+
let widget;
|
|
331
|
+
if (viewer) {
|
|
332
|
+
widget = starting_widgets.find((x) => x.viewer === viewer);
|
|
333
|
+
if (widget) {
|
|
334
|
+
widget.load(this.tables.get(viewer_config.table));
|
|
335
|
+
widget.restore({ ...viewer_config });
|
|
336
|
+
} else {
|
|
337
|
+
widget = this._createWidget({
|
|
338
|
+
config: { ...viewer_config },
|
|
339
|
+
viewer,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
} else if (viewer_config) {
|
|
343
|
+
widget = this._createWidgetAndNode({
|
|
344
|
+
config: { ...viewer_config },
|
|
345
|
+
slot: widgetName,
|
|
346
|
+
});
|
|
347
|
+
} else {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Could not find or create <perspective-viewer> for slot "${widgetName}"`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (master) {
|
|
354
|
+
widget.viewer.classList.add("workspace-master-widget");
|
|
355
|
+
widget.viewer.toggleAttribute("selectable", true);
|
|
356
|
+
widget.viewer.addEventListener(
|
|
357
|
+
"perspective-select",
|
|
358
|
+
this.onPerspectiveSelect.bind(this),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
widget.viewer.addEventListener(
|
|
362
|
+
"perspective-click",
|
|
363
|
+
this.onPerspectiveSelect.bind(this),
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// TODO remove event listener
|
|
367
|
+
this.masterPanel.addWidget(widget);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return widget;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
_validate(table: any) {
|
|
374
|
+
if (!table || !("view" in table) || typeof table?.view !== "function") {
|
|
375
|
+
throw new Error(
|
|
376
|
+
"Only `perspective.Table()` instances can be added to `tables`",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return table;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
_set_listener(name: string, table: psp.Table | Promise<psp.Table>) {
|
|
383
|
+
if (table instanceof Promise) {
|
|
384
|
+
table = table.then(this._validate);
|
|
385
|
+
} else {
|
|
386
|
+
this._validate(table);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
this.getAllWidgets().forEach((widget) => {
|
|
390
|
+
const psp_widget = widget as PerspectiveViewerWidget;
|
|
391
|
+
if (psp_widget.viewer.getAttribute("table") === name) {
|
|
392
|
+
psp_widget.load(table);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
_delete_listener(name: string) {
|
|
398
|
+
this.getAllWidgets().some((widget) => {
|
|
399
|
+
const psp_widget = widget as PerspectiveViewerWidget;
|
|
400
|
+
if (psp_widget.viewer.getAttribute("table") === name) {
|
|
401
|
+
psp_widget.viewer.eject();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
update_widget_for_viewer(viewer: HTMLPerspectiveViewerElement) {
|
|
407
|
+
let slot_name = viewer.getAttribute("slot");
|
|
408
|
+
if (!slot_name) {
|
|
409
|
+
slot_name = this._gen_id();
|
|
410
|
+
viewer.setAttribute("slot", slot_name);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const table_name = viewer.getAttribute("table");
|
|
414
|
+
if (table_name) {
|
|
415
|
+
const slot = this.node.querySelector(`slot[name=${slot_name}]`);
|
|
416
|
+
if (!slot) {
|
|
417
|
+
console.warn(
|
|
418
|
+
`Undocked ${viewer.outerHTML}, creating default layout`,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const widget = this._createWidget({
|
|
422
|
+
config: {
|
|
423
|
+
table: viewer.getAttribute("table")!,
|
|
424
|
+
},
|
|
425
|
+
viewer,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
this.dockpanel.addWidget(widget);
|
|
429
|
+
this.dockpanel.activateWidget(widget);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
console.warn(`No table set for ${viewer.outerHTML}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
remove_unslotted_widgets(viewers: HTMLPerspectiveViewerElement[]) {
|
|
437
|
+
const widgets = this.getAllWidgets();
|
|
438
|
+
for (const widget of widgets) {
|
|
439
|
+
const psp_widget = widget as PerspectiveViewerWidget;
|
|
440
|
+
let missing = viewers.indexOf(psp_widget.viewer) === -1;
|
|
441
|
+
if (missing) {
|
|
442
|
+
psp_widget.close();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
update_details_panel(viewers: HTMLPerspectiveViewerElement[]) {
|
|
448
|
+
if (this.masterPanel.widgets.length === 0) {
|
|
449
|
+
this.masterPanel.close();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/***************************************************************************
|
|
454
|
+
*
|
|
455
|
+
* Workspace-level contextmenu actions
|
|
456
|
+
*
|
|
457
|
+
*/
|
|
458
|
+
|
|
459
|
+
async duplicate(widget: PerspectiveViewerWidget): Promise<void> {
|
|
460
|
+
if (this.dockpanel.mode === "single-document") {
|
|
461
|
+
const _task =
|
|
462
|
+
await this._maximizedWidget!.viewer.toggleConfig(false);
|
|
463
|
+
|
|
464
|
+
this._unmaximize();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const config = await widget.save();
|
|
468
|
+
config.settings = false;
|
|
469
|
+
config.title = config.title ? `${config.title} (*)` : "";
|
|
470
|
+
const duplicate = this._createWidgetAndNode({
|
|
471
|
+
config,
|
|
472
|
+
slot: undefined,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
this.dockpanel.addWidget(duplicate, {
|
|
476
|
+
mode: "split-right",
|
|
477
|
+
ref: widget,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
await duplicate.task;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
toggleMasterDetail(widget: PerspectiveViewerWidget) {
|
|
484
|
+
const isGlobalFilter = widget.parent !== this.dockpanel;
|
|
485
|
+
this.element.dispatchEvent(
|
|
486
|
+
new CustomEvent("workspace-toggle-global-filter", {
|
|
487
|
+
detail: {
|
|
488
|
+
widget,
|
|
489
|
+
isGlobalFilter: !isGlobalFilter,
|
|
490
|
+
},
|
|
491
|
+
}),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
if (isGlobalFilter) {
|
|
495
|
+
this.makeDetail(widget);
|
|
496
|
+
} else {
|
|
497
|
+
if (this.dockpanel.mode === "single-document") {
|
|
498
|
+
this.toggleSingleDocument(widget);
|
|
499
|
+
}
|
|
500
|
+
this.makeMaster(widget);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_maximize(widget: PerspectiveViewerWidget) {
|
|
505
|
+
widget.viewer.classList.add("widget-maximize");
|
|
506
|
+
this._minimizedLayout = this.dockpanel.saveLayout();
|
|
507
|
+
this._minimizedLayoutSlots = PerspectiveDockPanel.mapWidgets(
|
|
508
|
+
(widget: PerspectiveViewerWidget) =>
|
|
509
|
+
widget.viewer.getAttribute("slot"),
|
|
510
|
+
this.dockpanel.saveLayout(),
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
this._maximizedWidget = widget;
|
|
514
|
+
this.dockpanel.mode = "single-document";
|
|
515
|
+
this.dockpanel.activateWidget(widget);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_unmaximize() {
|
|
519
|
+
this._maximizedWidget!.viewer.classList.remove("widget-maximize");
|
|
520
|
+
this.dockpanel.mode = "multiple-document";
|
|
521
|
+
this.dockpanel.restoreLayout(this._minimizedLayout!);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
toggleSingleDocument(widget: PerspectiveViewerWidget) {
|
|
525
|
+
if (this.dockpanel.mode !== "single-document") {
|
|
526
|
+
this._maximize(widget);
|
|
527
|
+
} else {
|
|
528
|
+
this._unmaximize();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async _filterViewer(
|
|
533
|
+
viewer: HTMLPerspectiveViewerElement,
|
|
534
|
+
filters: [string, string, string][],
|
|
535
|
+
candidates: Set<string>,
|
|
536
|
+
) {
|
|
537
|
+
const config = await viewer.save();
|
|
538
|
+
const table = await viewer.getTable();
|
|
539
|
+
const availableColumns = Object.keys(await table.schema());
|
|
540
|
+
const currentFilters = config.filter || [];
|
|
541
|
+
const columnAvailable = (filter: [string, string, any]) =>
|
|
542
|
+
filter[0] && availableColumns.includes(filter[0]);
|
|
543
|
+
|
|
544
|
+
const validFilters = filters.filter(columnAvailable);
|
|
545
|
+
validFilters.push(
|
|
546
|
+
...currentFilters.filter(
|
|
547
|
+
(x: [string, ..._: string[]]) => !candidates.has(x[0]),
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const newFilters = uniqBy(validFilters, (item) => item[0]);
|
|
552
|
+
await viewer.restore({ filter: newFilters });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async onPerspectiveSelect(event: CustomEvent) {
|
|
556
|
+
const config = await (
|
|
557
|
+
event.target as HTMLPerspectiveViewerElement
|
|
558
|
+
).save();
|
|
559
|
+
// perspective-select is already handled for hypergrid
|
|
560
|
+
|
|
561
|
+
if (
|
|
562
|
+
event.type === "perspective-click" &&
|
|
563
|
+
(config.plugin === "Datagrid" || config.plugin === null)
|
|
564
|
+
) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const candidates = new Set([
|
|
568
|
+
...(config["group_by"] || []),
|
|
569
|
+
...(config["split_by"] || []),
|
|
570
|
+
...(config.filter || []).map((x: [string, string, any]) => x[0]),
|
|
571
|
+
]);
|
|
572
|
+
|
|
573
|
+
const filters = [...event.detail.config.filter];
|
|
574
|
+
toArray(this.dockpanel.widgets()).forEach((widget) => {
|
|
575
|
+
this._filterViewer(
|
|
576
|
+
(widget as PerspectiveViewerWidget).viewer,
|
|
577
|
+
filters,
|
|
578
|
+
candidates,
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async makeMaster(widget: PerspectiveViewerWidget) {
|
|
584
|
+
if (widget.viewer.hasAttribute("settings")) {
|
|
585
|
+
await widget.toggleConfig();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
widget.viewer.classList.add("workspace-master-widget");
|
|
589
|
+
widget.viewer.toggleAttribute("selectable", true);
|
|
590
|
+
if (!this.masterPanel.isAttached) {
|
|
591
|
+
this.detailPanel.close();
|
|
592
|
+
this.setupMasterPanel(DEFAULT_WORKSPACE_SIZE);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
this.masterPanel.addWidget(widget);
|
|
596
|
+
widget.isHidden && widget.show();
|
|
597
|
+
widget.viewer.restyleElement();
|
|
598
|
+
widget.viewer.addEventListener(
|
|
599
|
+
"perspective-click",
|
|
600
|
+
this.onPerspectiveSelect.bind(this),
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
widget.viewer.addEventListener(
|
|
604
|
+
"perspective-select",
|
|
605
|
+
this.onPerspectiveSelect.bind(this),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
makeDetail(widget: PerspectiveViewerWidget) {
|
|
610
|
+
widget.viewer.classList.remove("workspace-master-widget");
|
|
611
|
+
widget.viewer.toggleAttribute("selectable", false);
|
|
612
|
+
this.dockpanel.addWidget(widget, { mode: `split-left` });
|
|
613
|
+
if (this.masterPanel.widgets.length === 0) {
|
|
614
|
+
this.detailPanel.close();
|
|
615
|
+
this.masterPanel.close();
|
|
616
|
+
this.detailPanel.removeClass("has-master-panel");
|
|
617
|
+
this.addWidget(this.detailPanel);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
widget.viewer.restyleElement();
|
|
621
|
+
widget.viewer.removeEventListener(
|
|
622
|
+
"perspective-click",
|
|
623
|
+
this.onPerspectiveSelect.bind(this),
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
widget.viewer.removeEventListener(
|
|
627
|
+
"perspective-select",
|
|
628
|
+
this.onPerspectiveSelect.bind(this),
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/***************************************************************************
|
|
633
|
+
*
|
|
634
|
+
* Context Menu
|
|
635
|
+
*
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
createContextMenu(widget: PerspectiveViewerWidget | null) {
|
|
639
|
+
this._menu = new WorkspaceMenu(
|
|
640
|
+
this.menu_elem.shadowRoot!,
|
|
641
|
+
this.element,
|
|
642
|
+
{
|
|
643
|
+
commands: this.commands,
|
|
644
|
+
},
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
const tabbar = find(
|
|
648
|
+
this.dockpanel.tabBars(),
|
|
649
|
+
(bar) => bar.currentTitle?.owner === widget,
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
this._menu.init_overlay = () => {
|
|
653
|
+
if (widget) {
|
|
654
|
+
widget.addClass("context-focus");
|
|
655
|
+
widget.viewer.classList.add("context-focus");
|
|
656
|
+
tabbar && tabbar.node.classList.add("context-focus");
|
|
657
|
+
this.element.classList.add("context-menu");
|
|
658
|
+
this.addClass("context-menu");
|
|
659
|
+
if (
|
|
660
|
+
widget.viewer.classList.contains("workspace-master-widget")
|
|
661
|
+
) {
|
|
662
|
+
this._menu!.node.classList.add("workspace-master-menu");
|
|
663
|
+
} else {
|
|
664
|
+
this._menu!.node.classList.remove("workspace-master-menu");
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
if (widget?.parent === this.dockpanel || widget === null) {
|
|
670
|
+
this._menu.addItem({
|
|
671
|
+
type: "submenu",
|
|
672
|
+
command: "workspace:newmenu",
|
|
673
|
+
submenu: (() => {
|
|
674
|
+
const submenu = new WorkspaceMenu(
|
|
675
|
+
this.menu_elem.shadowRoot!,
|
|
676
|
+
this.element,
|
|
677
|
+
{
|
|
678
|
+
commands: this.commands,
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
for (const table of this.tables.keys()) {
|
|
683
|
+
let args;
|
|
684
|
+
if (widget !== null) {
|
|
685
|
+
args = {
|
|
686
|
+
table,
|
|
687
|
+
widget_name: widget.viewer.getAttribute("slot"),
|
|
688
|
+
};
|
|
689
|
+
} else {
|
|
690
|
+
args = { table };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
submenu.addItem({
|
|
694
|
+
command: "workspace:new",
|
|
695
|
+
args,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const widgets = PerspectiveDockPanel.getWidgets(
|
|
700
|
+
this.dockpanel.saveLayout(),
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
if (widgets.length > 0) {
|
|
704
|
+
submenu.addItem({ type: "separator" });
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let seen = new Set();
|
|
708
|
+
for (const target_widget of widgets) {
|
|
709
|
+
if (!seen.has(target_widget.title.label)) {
|
|
710
|
+
let args;
|
|
711
|
+
if (widget !== null) {
|
|
712
|
+
args = {
|
|
713
|
+
target_widget_name:
|
|
714
|
+
target_widget.viewer.getAttribute(
|
|
715
|
+
"slot",
|
|
716
|
+
),
|
|
717
|
+
widget_name:
|
|
718
|
+
widget.viewer.getAttribute("slot"),
|
|
719
|
+
};
|
|
720
|
+
} else {
|
|
721
|
+
args = {
|
|
722
|
+
target_widget_name:
|
|
723
|
+
target_widget.viewer.getAttribute(
|
|
724
|
+
"slot",
|
|
725
|
+
),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
submenu.addItem({
|
|
730
|
+
command: "workspace:newview",
|
|
731
|
+
args,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
seen.add(target_widget.title.label);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
submenu.title.label = "New Table";
|
|
739
|
+
return submenu;
|
|
740
|
+
})(),
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (widget) {
|
|
745
|
+
const widget_name = widget.viewer.getAttribute("slot");
|
|
746
|
+
if (widget?.parent === this.dockpanel) {
|
|
747
|
+
this._menu.addItem({ type: "separator" });
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
this._menu.addItem({
|
|
751
|
+
command: "workspace:duplicate",
|
|
752
|
+
args: { widget_name },
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
this._menu.addItem({
|
|
756
|
+
command: "workspace:master",
|
|
757
|
+
args: { widget_name },
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
this._menu.addItem({ type: "separator" });
|
|
761
|
+
|
|
762
|
+
this._menu.addItem({
|
|
763
|
+
command: "workspace:settings",
|
|
764
|
+
args: { widget_name },
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
this._menu.addItem({
|
|
768
|
+
command: "workspace:reset",
|
|
769
|
+
args: { widget_name },
|
|
770
|
+
});
|
|
771
|
+
this._menu.addItem({
|
|
772
|
+
command: "workspace:export",
|
|
773
|
+
args: { widget_name },
|
|
774
|
+
});
|
|
775
|
+
this._menu.addItem({
|
|
776
|
+
command: "workspace:copy",
|
|
777
|
+
args: { widget_name },
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
this._menu.addItem({ type: "separator" });
|
|
781
|
+
|
|
782
|
+
this._menu.addItem({
|
|
783
|
+
command: "workspace:close",
|
|
784
|
+
args: { widget_name },
|
|
785
|
+
});
|
|
786
|
+
this._menu.addItem({
|
|
787
|
+
command: "workspace:help",
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
this._menu.aboutToClose.connect(() => {
|
|
792
|
+
if (widget) {
|
|
793
|
+
this.element.classList.remove("context-menu");
|
|
794
|
+
this.removeClass("context-menu");
|
|
795
|
+
widget.removeClass("context-focus");
|
|
796
|
+
tabbar?.node?.classList.remove("context-focus");
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
return this._menu;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
showContextMenu(widget: PerspectiveViewerWidget | null, event: MouseEvent) {
|
|
804
|
+
if (!event.shiftKey) {
|
|
805
|
+
const menu = this.createContextMenu(widget);
|
|
806
|
+
menu.init_overlay?.();
|
|
807
|
+
const rect = this.element.getBoundingClientRect();
|
|
808
|
+
menu.open(event.clientX - rect.x, event.clientY - rect.y, {
|
|
809
|
+
host: this.menu_elem.shadowRoot as unknown as HTMLElement,
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
event.preventDefault();
|
|
813
|
+
event.stopPropagation();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/***************************************************************************
|
|
818
|
+
*
|
|
819
|
+
* Context Menu
|
|
820
|
+
*
|
|
821
|
+
*/
|
|
822
|
+
|
|
823
|
+
clearLayout() {
|
|
824
|
+
this.getAllWidgets().forEach((widget) => widget.close());
|
|
825
|
+
this.widgets.forEach((widget) => widget.close());
|
|
826
|
+
this.detailPanel.close();
|
|
827
|
+
if (this.masterPanel.isAttached) {
|
|
828
|
+
this.detailPanel.removeClass("has-master-panel");
|
|
829
|
+
this.masterPanel.close();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
setupMasterPanel(sizes: number[]) {
|
|
834
|
+
this.detailPanel.addClass("has-master-panel");
|
|
835
|
+
this.addWidget(this.masterPanel);
|
|
836
|
+
this.addWidget(this.detailPanel);
|
|
837
|
+
this.setRelativeSizes(sizes);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
addViewer(config: ViewerConfigUpdateExt, is_global_filter?: boolean) {
|
|
841
|
+
if (this.dockpanel.mode === "single-document") {
|
|
842
|
+
const _task = this._maximizedWidget!.viewer.toggleConfig(false);
|
|
843
|
+
this._unmaximize();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const widget = this._createWidgetAndNode({ config });
|
|
847
|
+
if (is_global_filter) {
|
|
848
|
+
if (!this.masterPanel.isAttached) {
|
|
849
|
+
this.setupMasterPanel(DEFAULT_WORKSPACE_SIZE);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
this.masterPanel.addWidget(widget);
|
|
853
|
+
} else {
|
|
854
|
+
if (!this.detailPanel.isAttached) {
|
|
855
|
+
this.addWidget(this.detailPanel);
|
|
856
|
+
}
|
|
857
|
+
this.dockpanel.addWidget(widget, { mode: "split-right" });
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
this.update();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/*********************************************************************
|
|
864
|
+
* Widget helper methods
|
|
865
|
+
*/
|
|
866
|
+
|
|
867
|
+
_createWidgetAndNode({
|
|
868
|
+
config,
|
|
869
|
+
slot: slotname,
|
|
870
|
+
}: {
|
|
871
|
+
config: ViewerConfigUpdateExt;
|
|
872
|
+
slot?: string;
|
|
873
|
+
}) {
|
|
874
|
+
const node = this._createNode(slotname);
|
|
875
|
+
const table = config.table;
|
|
876
|
+
const viewer = document.createElement("perspective-viewer");
|
|
877
|
+
viewer.setAttribute(
|
|
878
|
+
"slot",
|
|
879
|
+
node!.querySelector("slot")!.getAttribute("name")!,
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
if (table) {
|
|
883
|
+
viewer.setAttribute("table", table);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return this._createWidget({
|
|
887
|
+
config,
|
|
888
|
+
elem: node as HTMLElement,
|
|
889
|
+
viewer,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
_gen_id() {
|
|
894
|
+
let genId = `PERSPECTIVE_GENERATED_ID_${ID_COUNTER++}`;
|
|
895
|
+
if (this.element.querySelector(`[slot=${genId}]`)) {
|
|
896
|
+
genId = this._gen_id();
|
|
897
|
+
}
|
|
898
|
+
return genId;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
_createNode(slotname?: string): HTMLElement {
|
|
902
|
+
let node = this.node.querySelector(`slot[name=${slotname}]`);
|
|
903
|
+
if (slotname === undefined || !node) {
|
|
904
|
+
const slot = document.createElement("slot");
|
|
905
|
+
slotname = slotname || this._gen_id();
|
|
906
|
+
slot.setAttribute("name", slotname);
|
|
907
|
+
const div = document.createElement("div");
|
|
908
|
+
div.classList.add("viewer-container");
|
|
909
|
+
div.appendChild(slot);
|
|
910
|
+
node = document.createElement("div");
|
|
911
|
+
node.classList.add("workspace-widget");
|
|
912
|
+
node.appendChild(div);
|
|
913
|
+
} else {
|
|
914
|
+
node = node.parentElement!.parentElement;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return node as HTMLElement;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
_createWidget({
|
|
921
|
+
config,
|
|
922
|
+
elem,
|
|
923
|
+
viewer,
|
|
924
|
+
}: {
|
|
925
|
+
config: ViewerConfigUpdateExt;
|
|
926
|
+
elem?: Element;
|
|
927
|
+
viewer: HTMLPerspectiveViewerElement;
|
|
928
|
+
}) {
|
|
929
|
+
let node: HTMLElement = elem as HTMLElement;
|
|
930
|
+
if (!node) {
|
|
931
|
+
const slotname = viewer.getAttribute("slot") || undefined;
|
|
932
|
+
node = this.node.querySelector(`slot[name=${slotname}]`)!;
|
|
933
|
+
if (!node) {
|
|
934
|
+
node = this._createNode(slotname)!;
|
|
935
|
+
} else {
|
|
936
|
+
node = node.parentElement!.parentElement!;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const table = this.tables.get(
|
|
941
|
+
viewer.getAttribute("table") || config.table,
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
const widget = new PerspectiveViewerWidget({ node, viewer });
|
|
945
|
+
widget.task = (async () => {
|
|
946
|
+
if (table) {
|
|
947
|
+
widget.load(table);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
await widget.restore(config);
|
|
951
|
+
})();
|
|
952
|
+
|
|
953
|
+
const event = new CustomEvent("workspace-new-view", {
|
|
954
|
+
detail: { config, widget },
|
|
955
|
+
});
|
|
956
|
+
this.element.dispatchEvent(event);
|
|
957
|
+
widget.title.closable = true;
|
|
958
|
+
this.element.appendChild(widget.viewer);
|
|
959
|
+
this._addWidgetEventListeners(widget);
|
|
960
|
+
return widget;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
_addWidgetEventListeners(widget: PerspectiveViewerWidget) {
|
|
964
|
+
if (this.listeners.has(widget)) {
|
|
965
|
+
this.listeners.get(widget)!();
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const settings = (event: CustomEvent) => {
|
|
969
|
+
if (!event.detail && this.dockpanel.mode === "single-document") {
|
|
970
|
+
this._unmaximize();
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const contextMenu = (event: MouseEvent) =>
|
|
975
|
+
this.showContextMenu(widget, event);
|
|
976
|
+
|
|
977
|
+
const updated = async (event: CustomEvent) => {
|
|
978
|
+
this.workspaceUpdated();
|
|
979
|
+
// Sometimes plugins or other external code fires this event and
|
|
980
|
+
// does not populate this field!
|
|
981
|
+
const config =
|
|
982
|
+
typeof event.detail === "undefined"
|
|
983
|
+
? await widget.viewer.save()
|
|
984
|
+
: event.detail;
|
|
985
|
+
|
|
986
|
+
widget.title.label = config.title;
|
|
987
|
+
widget._is_pivoted = config.group_by?.length > 0;
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
widget.node.addEventListener("contextmenu", contextMenu);
|
|
991
|
+
widget.viewer.addEventListener("perspective-toggle-settings", settings);
|
|
992
|
+
|
|
993
|
+
// @ts-ignore
|
|
994
|
+
widget.viewer.addEventListener("perspective-config-update", updated);
|
|
995
|
+
|
|
996
|
+
this.listeners.set(widget, () => {
|
|
997
|
+
widget.node.removeEventListener("contextmenu", contextMenu);
|
|
998
|
+
widget.viewer.removeEventListener(
|
|
999
|
+
"perspective-toggle-settings",
|
|
1000
|
+
settings,
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
// @ts-ignore
|
|
1004
|
+
widget.viewer.removeEventListener(
|
|
1005
|
+
"perspective-config-update",
|
|
1006
|
+
updated,
|
|
1007
|
+
);
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
getWidgetByName(name: string): PerspectiveViewerWidget | null {
|
|
1012
|
+
return (
|
|
1013
|
+
this.getAllWidgets().find(
|
|
1014
|
+
(x) => x.viewer.getAttribute("slot") === name,
|
|
1015
|
+
) || null
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
getAllWidgets(): PerspectiveViewerWidget[] {
|
|
1020
|
+
return [
|
|
1021
|
+
...(this.masterPanel.widgets as PerspectiveViewerWidget[]),
|
|
1022
|
+
...toArray(this.dockpanel.widgets()),
|
|
1023
|
+
] as PerspectiveViewerWidget[];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/***************************************************************************
|
|
1027
|
+
*
|
|
1028
|
+
* `workspace-layout-update` event
|
|
1029
|
+
*
|
|
1030
|
+
*/
|
|
1031
|
+
|
|
1032
|
+
async workspaceUpdated() {
|
|
1033
|
+
const layout = await this.save();
|
|
1034
|
+
if (layout) {
|
|
1035
|
+
if (this._last_updated_state) {
|
|
1036
|
+
if (isEqual(this._last_updated_state, layout)) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
this._last_updated_state =
|
|
1042
|
+
layout as any as PerspectiveWorkspaceConfig<string>;
|
|
1043
|
+
|
|
1044
|
+
const tables: Record<string, psp.Table | Promise<psp.Table>> = {};
|
|
1045
|
+
this.tables.forEach((value, key) => {
|
|
1046
|
+
tables[key] = value;
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
this.element.dispatchEvent(
|
|
1050
|
+
new CustomEvent("workspace-layout-update", {
|
|
1051
|
+
detail: { tables, layout },
|
|
1052
|
+
}),
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|