@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.
Files changed (44) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/cdn/perspective-workspace.js +45 -0
  3. package/dist/cdn/perspective-workspace.js.map +7 -0
  4. package/dist/css/pro-dark.css +1 -0
  5. package/dist/css/pro.css +1 -0
  6. package/dist/esm/perspective-workspace.d.ts +181 -0
  7. package/dist/esm/perspective-workspace.js +14 -0
  8. package/dist/esm/perspective-workspace.js.map +7 -0
  9. package/dist/esm/utils/custom_elements.d.ts +14 -0
  10. package/dist/esm/utils/observable_map.d.ts +9 -0
  11. package/dist/esm/workspace/commands.d.ts +3 -0
  12. package/dist/esm/workspace/dockpanel.d.ts +13 -0
  13. package/dist/esm/workspace/index.d.ts +1 -0
  14. package/dist/esm/workspace/menu.d.ts +11 -0
  15. package/dist/esm/workspace/tabbar.d.ts +25 -0
  16. package/dist/esm/workspace/tabbarrenderer.d.ts +19 -0
  17. package/dist/esm/workspace/widget.d.ts +27 -0
  18. package/dist/esm/workspace/workspace.d.ts +123 -0
  19. package/package.json +55 -0
  20. package/src/html/workspace.html +11 -0
  21. package/src/less/dockpanel.less +95 -0
  22. package/src/less/injected.less +14 -0
  23. package/src/less/menu.less +128 -0
  24. package/src/less/tabbar.less +366 -0
  25. package/src/less/viewer.less +86 -0
  26. package/src/less/widget.less +40 -0
  27. package/src/less/workspace.less +70 -0
  28. package/src/svg/bookmark-icon.svg +4 -0
  29. package/src/svg/drag-handle.svg +10 -0
  30. package/src/themes/pro-dark.less +139 -0
  31. package/src/themes/pro.less +93 -0
  32. package/src/ts/external.d.ts +21 -0
  33. package/src/ts/external.js +11 -0
  34. package/src/ts/perspective-workspace.ts +306 -0
  35. package/src/ts/utils/custom_elements.ts +95 -0
  36. package/src/ts/utils/observable_map.ts +39 -0
  37. package/src/ts/workspace/commands.ts +269 -0
  38. package/src/ts/workspace/dockpanel.ts +145 -0
  39. package/src/ts/workspace/index.ts +13 -0
  40. package/src/ts/workspace/menu.ts +213 -0
  41. package/src/ts/workspace/tabbar.ts +237 -0
  42. package/src/ts/workspace/tabbarrenderer.ts +91 -0
  43. package/src/ts/workspace/widget.ts +101 -0
  44. package/src/ts/workspace/workspace.ts +1056 -0
@@ -0,0 +1,237 @@
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 { ArrayExt } from "@lumino/algorithm";
14
+ import { ElementExt } from "@lumino/domutils";
15
+ import { TabBar } from "@lumino/widgets";
16
+ import { TabBarItems, PerspectiveTabBarRenderer } from "./tabbarrenderer";
17
+ import { VirtualDOM } from "@lumino/virtualdom";
18
+ import { CommandRegistry } from "@lumino/commands";
19
+ import { Menu } from "@lumino/widgets";
20
+ import { PerspectiveWorkspace } from "./workspace";
21
+ import { Message } from "@lumino/messaging";
22
+ import { Title } from "@lumino/widgets";
23
+ import { Signal } from "@lumino/signaling";
24
+ import { ReadonlyJSONObject, ReadonlyJSONValue } from "@lumino/coreutils";
25
+ import { WorkspaceMenu } from "./menu";
26
+
27
+ export class PerspectiveTabBar extends TabBar<any> {
28
+ _workspace: PerspectiveWorkspace;
29
+ __content_node__?: HTMLElement;
30
+ _menu?: Menu;
31
+ __titles: string[];
32
+
33
+ constructor(workspace: PerspectiveWorkspace, options = {}) {
34
+ super(options);
35
+ this._addEventListeners();
36
+ this.__content_node__ = undefined;
37
+ this._workspace = workspace;
38
+ this.__titles = [];
39
+ }
40
+
41
+ get private_titles(): Title<any>[] {
42
+ let titles: Title<any>[] = (this as any)._titles;
43
+ return titles;
44
+ }
45
+
46
+ onUpdateRequest(msg: Message) {
47
+ // NOT INERT! This is a lumino bug fix.
48
+ // lumino/virtualdom keeps a weakmap on contentNode which is later
49
+ // reset - this causes the diff to double some elements. Memoizing
50
+ // prevent collection from the weakmap.
51
+ this.__content_node__ = this.contentNode;
52
+ this.node.style.contain = "";
53
+
54
+ // super.onUpdateRequest(msg);
55
+
56
+ let titles: Title<any>[] = (this as any)._titles;
57
+ let renderer = this.renderer;
58
+ let currentTitle = this.currentTitle!;
59
+
60
+ // Another hack. `TabBar` selects by index and I don't want to fork this
61
+ // logic, so insert empty divs until the indices match.
62
+ let content = new Array();
63
+ for (let i = 0, n = titles.length; i < n; ++i) {
64
+ let title = titles[i];
65
+ let current = title === currentTitle;
66
+ let otherTitles = titles.filter((x) => x !== currentTitle);
67
+ let onClick;
68
+ if (otherTitles.length > 0) {
69
+ onClick = this.onClick.bind(this, otherTitles, i);
70
+ }
71
+
72
+ if (current) {
73
+ content[i] = (
74
+ renderer as unknown as PerspectiveTabBarRenderer
75
+ ).renderTab(
76
+ {
77
+ title,
78
+ zIndex: titles.length + 1,
79
+ current,
80
+ } as TabBar.IRenderData<any>,
81
+ onClick,
82
+ );
83
+ } else {
84
+ content[i] = (
85
+ renderer as unknown as PerspectiveTabBarRenderer
86
+ ).renderInert();
87
+ }
88
+ }
89
+
90
+ VirtualDOM.render(content, this.contentNode);
91
+ this._check_shade();
92
+ }
93
+
94
+ onClick(otherTitles: Title<any>[], index: number, event: MouseEvent) {
95
+ const commands = new CommandRegistry();
96
+ this._menu = new WorkspaceMenu(
97
+ this._workspace.menu_elem.shadowRoot!,
98
+ this._workspace.element,
99
+ {
100
+ commands,
101
+ },
102
+ );
103
+
104
+ this._menu.addClass("perspective-workspace-menu");
105
+ this._menu.dataset.minwidth = this.__titles[index];
106
+ for (const title of otherTitles) {
107
+ this._menu.addItem({
108
+ command: "tabbar:switch",
109
+ args: { title } as unknown as ReadonlyJSONObject,
110
+ });
111
+ }
112
+
113
+ commands.addCommand("tabbar:switch", {
114
+ execute: async ({ title }) => {
115
+ const psp_title = title as any as Title<any>;
116
+ const index = this.__titles.findIndex((t) => t === title);
117
+ this.currentTitle = psp_title;
118
+ (
119
+ this.tabActivateRequested as unknown as Signal<
120
+ TabBar<any>,
121
+ TabBar.ITabActivateRequestedArgs<any>
122
+ >
123
+ ).emit({
124
+ index,
125
+ title: psp_title,
126
+ });
127
+ },
128
+ label: ({ title }) => {
129
+ const psp_title = title as any as Title<any>;
130
+ return psp_title.label || "untitled";
131
+ },
132
+ mnemonic: 0,
133
+ });
134
+
135
+ const box = (event.target as HTMLElement).getBoundingClientRect();
136
+ const outer_box = this._workspace.element.getBoundingClientRect();
137
+ this._menu.open(box.x - outer_box.x, box.y + box.height - outer_box.y);
138
+ this._menu.aboutToClose.connect(() => {
139
+ this._menu = undefined;
140
+ });
141
+
142
+ event.preventDefault();
143
+ event.stopPropagation();
144
+ }
145
+
146
+ _check_shade() {
147
+ if (
148
+ Array.from(this.contentNode.children).filter(
149
+ (x) =>
150
+ x.classList.contains("settings_open") &&
151
+ x.classList.contains("lm-mod-current"),
152
+ ).length > 0
153
+ ) {
154
+ this.contentNode.classList.add("inactive-blur");
155
+ } else {
156
+ this.contentNode.classList.remove("inactive-blur");
157
+ }
158
+ }
159
+
160
+ handleEvent(event: MouseEvent) {
161
+ this._menu?.close();
162
+ this.retargetEvent(event);
163
+ switch (event.type) {
164
+ case "contextmenu":
165
+ const widget = this.currentTitle?.owner;
166
+ let parent = widget.parent;
167
+
168
+ // TODO There is probably a better way to find the workspace
169
+ // relative to a widget command
170
+ while (parent && !(parent instanceof PerspectiveWorkspace)) {
171
+ parent = parent.parent;
172
+ }
173
+
174
+ (parent as unknown as PerspectiveWorkspace).showContextMenu(
175
+ widget,
176
+ event,
177
+ );
178
+
179
+ if (!event.shiftKey) {
180
+ event.preventDefault();
181
+ }
182
+
183
+ break;
184
+ case "mousedown":
185
+ if ((event.target as HTMLElement).id === TabBarItems.Label) {
186
+ return;
187
+ }
188
+ break;
189
+ case "pointerdown":
190
+ if ((event.target as HTMLElement).id === TabBarItems.Label) {
191
+ const tabs = this.contentNode.children;
192
+
193
+ // Find the index of the released tab.
194
+ const index = ArrayExt.findFirstIndex(tabs, (tab) => {
195
+ return ElementExt.hitTest(
196
+ tab,
197
+ event.clientX,
198
+ event.clientY,
199
+ );
200
+ });
201
+
202
+ if (index < 0) {
203
+ break;
204
+ }
205
+
206
+ const title = this.titles[index];
207
+ this._workspace._maximize(title.owner);
208
+ requestAnimationFrame(() =>
209
+ title.owner.viewer.toggleConfig(),
210
+ );
211
+
212
+ return;
213
+ }
214
+ break;
215
+ }
216
+ super.handleEvent(event);
217
+ }
218
+
219
+ /**
220
+ * Shadow dom targets events at the host, not the clicked element, which
221
+ * Lumino dislikes. This makes the event look like it is not crossing
222
+ * the ShadowDom boundary.
223
+ *
224
+ */
225
+ retargetEvent(event: MouseEvent) {
226
+ Object.defineProperty(event, "target", {
227
+ value: event.composedPath()[0],
228
+ enumerable: true,
229
+ });
230
+ return event;
231
+ }
232
+
233
+ _addEventListeners() {
234
+ this.node.addEventListener("dblclick", this);
235
+ this.node.addEventListener("contextmenu", this);
236
+ }
237
+ }
@@ -0,0 +1,91 @@
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 { h } from "@lumino/virtualdom";
14
+ import { TabBar } from "@lumino/widgets";
15
+
16
+ export const TabBarItems = {
17
+ Config: "config",
18
+ Label: "label",
19
+ };
20
+
21
+ export const DEFAULT_TITLE = "untitled";
22
+
23
+ export class PerspectiveTabBarRenderer extends TabBar.Renderer {
24
+ maximized: boolean;
25
+
26
+ constructor(maximized: boolean) {
27
+ super();
28
+ this.maximized = maximized;
29
+ }
30
+
31
+ renderLabel(data: { title: { label?: string } }) {
32
+ return h.span(
33
+ {
34
+ className: "lm-TabBar-tabLabel",
35
+ id: TabBarItems.Label,
36
+ },
37
+ data.title.label || DEFAULT_TITLE,
38
+ );
39
+ }
40
+
41
+ renderInert() {
42
+ return h.div();
43
+ }
44
+
45
+ renderTab(
46
+ data: TabBar.IRenderData<any>,
47
+ onclick?: (this: HTMLElement, event: MouseEvent) => any,
48
+ ) {
49
+ const title = data.title.caption;
50
+ const key = this.createTabKey(data);
51
+ const style = this.createTabStyle(data);
52
+ let className = this.createTabClass(data);
53
+ const dataset = this.createTabDataset(data);
54
+ const more: h.Child[] = [];
55
+ if (onclick) {
56
+ more.push(
57
+ h.div(
58
+ { onclick, className: "bookmarks-button" },
59
+ h.div({ className: "bookmarks" }),
60
+ ),
61
+ );
62
+ }
63
+
64
+ return h.li(
65
+ { key, className, title, style, dataset },
66
+ this.renderDragHandle(),
67
+ ...more,
68
+ this.renderLabel(data),
69
+ this.renderCloseIcon(),
70
+ );
71
+ }
72
+
73
+ renderDragHandle() {
74
+ return h.div({
75
+ className: "drag-handle",
76
+ });
77
+ }
78
+
79
+ // renderConfigIcon() {
80
+ // return h.div({
81
+ // className: "lm-TabBar-tabConfigIcon",
82
+ // id: TabBarItems.Config,
83
+ // });
84
+ // }
85
+
86
+ renderCloseIcon() {
87
+ return h.div({
88
+ className: "lm-TabBar-tabCloseIcon" + " p-TabBar-tabCloseIcon",
89
+ });
90
+ }
91
+ }
@@ -0,0 +1,101 @@
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 { Widget } from "@lumino/widgets";
14
+ import { Message } from "@lumino/messaging";
15
+
16
+ import type * as psp_viewer from "@perspective-dev/viewer";
17
+ import type * as psp from "@perspective-dev/client";
18
+
19
+ interface IPerspectiveViewerWidgetOptions {
20
+ node: HTMLElement;
21
+ viewer: psp_viewer.HTMLPerspectiveViewerElement;
22
+ }
23
+
24
+ export class PerspectiveViewerWidget extends Widget {
25
+ viewer: psp_viewer.HTMLPerspectiveViewerElement;
26
+ _title: string;
27
+ _is_table_loaded: boolean;
28
+ _is_pivoted: boolean;
29
+ _restore_config?: () => Promise<void>;
30
+ task?: Promise<void>;
31
+
32
+ constructor({ viewer, node }: IPerspectiveViewerWidgetOptions) {
33
+ super({ node });
34
+ this.viewer = viewer;
35
+ this._title = "";
36
+ this._is_table_loaded = false;
37
+ this._is_pivoted = false;
38
+ }
39
+
40
+ get name(): string {
41
+ return this._title;
42
+ }
43
+
44
+ toggleConfig(): Promise<void> {
45
+ return this.viewer.toggleConfig();
46
+ }
47
+
48
+ async load(table: psp.Table | Promise<psp.Table>) {
49
+ this._is_table_loaded = true;
50
+ let promises = [this.viewer.load(table)];
51
+ if (this._restore_config) {
52
+ promises.push(this._restore_config());
53
+ }
54
+
55
+ await Promise.all(promises);
56
+ }
57
+
58
+ restore(
59
+ config: psp_viewer.ViewerConfigUpdate & {
60
+ table: string;
61
+ },
62
+ ) {
63
+ const { table, ...viewerConfig } = config;
64
+ this._title = config.title as string;
65
+ this.title.label = config.title as string;
66
+ if (table) {
67
+ this.viewer.setAttribute("table", table);
68
+ }
69
+
70
+ const restore_config = () => this.viewer.restore({ ...viewerConfig });
71
+ if (this._is_table_loaded) {
72
+ return restore_config();
73
+ } else {
74
+ this._restore_config = restore_config;
75
+ }
76
+ }
77
+
78
+ async save() {
79
+ let config = {
80
+ ...(await this.viewer.save()),
81
+ table: this.viewer.getAttribute("table"),
82
+ };
83
+
84
+ delete config["settings"];
85
+ return config;
86
+ }
87
+
88
+ removeClass(name: string) {
89
+ super.removeClass(name);
90
+ this.viewer && this.viewer.classList.remove(name);
91
+ }
92
+
93
+ async onCloseRequest(msg: Message) {
94
+ super.onCloseRequest(msg);
95
+ if (this.viewer.parentElement) {
96
+ this.viewer.parentElement.removeChild(this.viewer);
97
+ }
98
+
99
+ await this.viewer.delete();
100
+ }
101
+ }