@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,39 @@
|
|
|
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
|
+
export class ObservableMap<K, V> extends Map<K, V> {
|
|
14
|
+
_set_listener?: (name: K, val: V) => void;
|
|
15
|
+
_delete_listener?: (name: K) => void;
|
|
16
|
+
|
|
17
|
+
set(name: K, item: V) {
|
|
18
|
+
this._set_listener?.(name, item);
|
|
19
|
+
super.set(name, item);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(name: K): V {
|
|
24
|
+
return super.get(name)!;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
delete(name: K) {
|
|
28
|
+
this._delete_listener?.(name);
|
|
29
|
+
return super.delete(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
addSetListener(listener: (name: K, val: V) => void) {
|
|
33
|
+
this._set_listener = listener;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addDeleteListener(listener: (name: K) => void) {
|
|
37
|
+
this._delete_listener = listener;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
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 { CommandRegistry } from "@lumino/commands";
|
|
14
|
+
import { Menu, Widget } from "@lumino/widgets";
|
|
15
|
+
import { Signal } from "@lumino/signaling";
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
HTMLPerspectiveViewerCopyMenu,
|
|
19
|
+
HTMLPerspectiveViewerExportMenu,
|
|
20
|
+
} from "@perspective-dev/viewer";
|
|
21
|
+
|
|
22
|
+
import type { PerspectiveWorkspace } from "./workspace";
|
|
23
|
+
import { WorkspaceMenu } from "./menu";
|
|
24
|
+
|
|
25
|
+
export const createCommands = (
|
|
26
|
+
workspace: PerspectiveWorkspace,
|
|
27
|
+
indicator: HTMLElement,
|
|
28
|
+
) => {
|
|
29
|
+
const commands = new CommandRegistry();
|
|
30
|
+
|
|
31
|
+
commands.addCommand("workspace:export", {
|
|
32
|
+
execute: async (args) => {
|
|
33
|
+
const menu = document.createElement(
|
|
34
|
+
"perspective-export-menu",
|
|
35
|
+
) as unknown as HTMLPerspectiveViewerExportMenu;
|
|
36
|
+
|
|
37
|
+
workspace.apply_indicator_theme();
|
|
38
|
+
const widget = workspace.getWidgetByName(
|
|
39
|
+
args.widget_name as string,
|
|
40
|
+
)!;
|
|
41
|
+
|
|
42
|
+
menu.set_model(widget.viewer.get_model());
|
|
43
|
+
menu.open(indicator);
|
|
44
|
+
workspace.get_context_menu()?.init_overlay?.();
|
|
45
|
+
menu.addEventListener("blur", () => {
|
|
46
|
+
const context_menu = workspace.get_context_menu()!;
|
|
47
|
+
const signal = context_menu.aboutToClose as Signal<
|
|
48
|
+
WorkspaceMenu,
|
|
49
|
+
any
|
|
50
|
+
>;
|
|
51
|
+
signal.emit({});
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
isEnabled: (args) => {
|
|
55
|
+
if (workspace.get_context_menu()?.node.isConnected) {
|
|
56
|
+
const box = workspace
|
|
57
|
+
.get_context_menu()
|
|
58
|
+
?.node.getBoundingClientRect();
|
|
59
|
+
|
|
60
|
+
if (box) {
|
|
61
|
+
indicator.style.top = box.top + "px";
|
|
62
|
+
indicator.style.left = box.left + "px";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
},
|
|
68
|
+
// iconClass: "menu-export",
|
|
69
|
+
label: "Export",
|
|
70
|
+
mnemonic: 0,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
commands.addCommand("workspace:copy", {
|
|
74
|
+
execute: async (args) => {
|
|
75
|
+
const menu = document.createElement(
|
|
76
|
+
"perspective-copy-menu",
|
|
77
|
+
) as HTMLPerspectiveViewerCopyMenu;
|
|
78
|
+
|
|
79
|
+
workspace.apply_indicator_theme();
|
|
80
|
+
const widget = workspace.getWidgetByName(
|
|
81
|
+
args.widget_name as string,
|
|
82
|
+
)!;
|
|
83
|
+
menu.set_model(widget.viewer.get_model());
|
|
84
|
+
|
|
85
|
+
menu.open(indicator);
|
|
86
|
+
workspace.get_context_menu()?.init_overlay?.();
|
|
87
|
+
menu.addEventListener("blur", () => {
|
|
88
|
+
(
|
|
89
|
+
workspace.get_context_menu()?.aboutToClose as
|
|
90
|
+
| Signal<WorkspaceMenu, any>
|
|
91
|
+
| undefined
|
|
92
|
+
)?.emit({});
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
isEnabled: (_) => {
|
|
96
|
+
if (workspace.get_context_menu()?.node.isConnected) {
|
|
97
|
+
const box = workspace
|
|
98
|
+
.get_context_menu()
|
|
99
|
+
?.node.getBoundingClientRect();
|
|
100
|
+
|
|
101
|
+
if (box) {
|
|
102
|
+
indicator.style.top = box.top + "px";
|
|
103
|
+
indicator.style.left = box.left + "px";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
},
|
|
109
|
+
// iconClass: "menu-copy",
|
|
110
|
+
label: "Copy",
|
|
111
|
+
mnemonic: 0,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
commands.addCommand("workspace:new", {
|
|
115
|
+
execute: (args) => {
|
|
116
|
+
const widget = workspace._createWidgetAndNode({
|
|
117
|
+
config: { table: args.table as string },
|
|
118
|
+
slot: undefined,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
workspace.get_dock_panel().addWidget(widget, {
|
|
122
|
+
mode: "split-right",
|
|
123
|
+
ref: workspace.getWidgetByName(args.widget_name as string),
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
// iconClass: "menu-new-tables",
|
|
127
|
+
label: (args) => {
|
|
128
|
+
return args.table as string;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
commands.addCommand("workspace:newview", {
|
|
133
|
+
execute: async (args) => {
|
|
134
|
+
const widget = workspace.getWidgetByName(
|
|
135
|
+
args.widget_name as string,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const target_widget = workspace.getWidgetByName(
|
|
139
|
+
args.target_widget_name as string,
|
|
140
|
+
)!;
|
|
141
|
+
|
|
142
|
+
const config = await target_widget.save();
|
|
143
|
+
const new_widget = workspace._createWidgetAndNode({
|
|
144
|
+
config,
|
|
145
|
+
slot: undefined,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
workspace.get_dock_panel().addWidget(new_widget, {
|
|
149
|
+
mode: "split-right",
|
|
150
|
+
ref: widget,
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
// iconClass: "menu-new-tables",
|
|
154
|
+
isVisible: (args) => {
|
|
155
|
+
const target_widget = workspace.getWidgetByName(
|
|
156
|
+
args.target_widget_name as string,
|
|
157
|
+
)!;
|
|
158
|
+
|
|
159
|
+
return target_widget.title.label !== "";
|
|
160
|
+
},
|
|
161
|
+
label: (args) => {
|
|
162
|
+
const target_widget = workspace.getWidgetByName(
|
|
163
|
+
args.target_widget_name as string,
|
|
164
|
+
)!;
|
|
165
|
+
|
|
166
|
+
return target_widget.title.label || "untitled";
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
commands.addCommand("workspace:reset", {
|
|
171
|
+
execute: (args) => {
|
|
172
|
+
workspace
|
|
173
|
+
.getWidgetByName(args.widget_name as string)!
|
|
174
|
+
.viewer.reset();
|
|
175
|
+
},
|
|
176
|
+
label: "Reset",
|
|
177
|
+
mnemonic: 0,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
commands.addCommand("workspace:settings", {
|
|
181
|
+
execute: async ({ widget_name }) => {
|
|
182
|
+
const widget = workspace.getWidgetByName(widget_name as string);
|
|
183
|
+
if (!widget) {
|
|
184
|
+
throw new Error(`No widget ${widget_name}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!widget.viewer.hasAttribute("settings")) {
|
|
188
|
+
workspace._maximize(widget);
|
|
189
|
+
requestAnimationFrame(() => widget.viewer.toggleConfig());
|
|
190
|
+
} else {
|
|
191
|
+
widget.viewer.toggleConfig();
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
isVisible: (args) => {
|
|
195
|
+
const widget = workspace.getWidgetByName(
|
|
196
|
+
args.widget_name as string,
|
|
197
|
+
)!;
|
|
198
|
+
|
|
199
|
+
return widget.parent! === (workspace.get_dock_panel() as Widget)
|
|
200
|
+
? true
|
|
201
|
+
: false;
|
|
202
|
+
},
|
|
203
|
+
label: (args) => {
|
|
204
|
+
const widget = workspace.getWidgetByName(
|
|
205
|
+
args.widget_name as string,
|
|
206
|
+
)!;
|
|
207
|
+
if (widget.viewer.hasAttribute("settings")) {
|
|
208
|
+
return "Close Settings";
|
|
209
|
+
} else {
|
|
210
|
+
return "Open Settings";
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
mnemonic: 0,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
commands.addCommand("workspace:duplicate", {
|
|
217
|
+
execute: ({ widget_name }) =>
|
|
218
|
+
workspace.duplicate(
|
|
219
|
+
workspace.getWidgetByName(widget_name as string)!,
|
|
220
|
+
),
|
|
221
|
+
// iconClass: "menu-duplicate",
|
|
222
|
+
isVisible: (args) => {
|
|
223
|
+
return workspace.getWidgetByName(args.widget_name as string)!
|
|
224
|
+
.parent! === (workspace.get_dock_panel() as Widget)
|
|
225
|
+
? true
|
|
226
|
+
: false;
|
|
227
|
+
},
|
|
228
|
+
label: "Duplicate",
|
|
229
|
+
mnemonic: 0,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
commands.addCommand("workspace:master", {
|
|
233
|
+
execute: (args) =>
|
|
234
|
+
workspace.toggleMasterDetail(
|
|
235
|
+
workspace.getWidgetByName(args.widget_name as string)!,
|
|
236
|
+
),
|
|
237
|
+
isVisible: (args) => {
|
|
238
|
+
return !!workspace.getWidgetByName(args.widget_name as string)
|
|
239
|
+
?._is_pivoted;
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
label: (args) => {
|
|
243
|
+
return workspace.getWidgetByName(args.widget_name as string)!
|
|
244
|
+
.parent === workspace.get_dock_panel()
|
|
245
|
+
? "Create Global Filter"
|
|
246
|
+
: "Remove Global Filter";
|
|
247
|
+
},
|
|
248
|
+
mnemonic: 0,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
commands.addCommand("workspace:close", {
|
|
252
|
+
execute: (args) => {
|
|
253
|
+
workspace.getWidgetByName(args.widget_name as string)!.close();
|
|
254
|
+
},
|
|
255
|
+
// iconClass: "menu-close",
|
|
256
|
+
label: () => "Close",
|
|
257
|
+
mnemonic: 0,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
commands.addCommand("workspace:help", {
|
|
261
|
+
// iconClass: "menu-close",
|
|
262
|
+
execute: () => {},
|
|
263
|
+
label: "Shift+Click for Browser Menu",
|
|
264
|
+
isEnabled: () => false,
|
|
265
|
+
// mnemonic: 0,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return commands;
|
|
269
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
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 { DockLayout, DockPanel, TabBar, Widget } from "@lumino/widgets";
|
|
14
|
+
import { PerspectiveTabBar } from "./tabbar";
|
|
15
|
+
import { PerspectiveTabBarRenderer } from "./tabbarrenderer";
|
|
16
|
+
import { PerspectiveWorkspace } from "./workspace";
|
|
17
|
+
import { PerspectiveViewerWidget } from "./widget";
|
|
18
|
+
|
|
19
|
+
class PerspectiveDockPanelRenderer extends DockPanel.Renderer {
|
|
20
|
+
_workspace: PerspectiveWorkspace;
|
|
21
|
+
|
|
22
|
+
constructor(workspace: PerspectiveWorkspace) {
|
|
23
|
+
super();
|
|
24
|
+
this._workspace = workspace;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
createTabBar() {
|
|
28
|
+
const tabbar = new PerspectiveTabBar(this._workspace, {
|
|
29
|
+
renderer: new PerspectiveTabBarRenderer(false),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
tabbar.addClass("lm-DockPanel-tabBar");
|
|
33
|
+
return tabbar;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// @ts-ignore: extending a private member `_onTabDetachRequested`
|
|
38
|
+
export class PerspectiveDockPanel extends DockPanel {
|
|
39
|
+
constructor(workspace: PerspectiveWorkspace) {
|
|
40
|
+
super({ renderer: new PerspectiveDockPanelRenderer(workspace) });
|
|
41
|
+
|
|
42
|
+
// @ts-ignore: accessing a private member `_renderer`
|
|
43
|
+
this._renderer.dock = this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_onTabDetachRequested(
|
|
47
|
+
sender: TabBar<Widget>,
|
|
48
|
+
args: TabBar.ITabDetachRequestedArgs<Widget>,
|
|
49
|
+
) {
|
|
50
|
+
// @ts-ignore: accessing a private member `_onTabDetachRequested`
|
|
51
|
+
super._onTabDetachRequested(sender, args);
|
|
52
|
+
|
|
53
|
+
// blur widget on when it's being moved
|
|
54
|
+
const widget = sender.titles[args.index].owner;
|
|
55
|
+
const layout = this.layout as DockLayout;
|
|
56
|
+
const old = layout.saveLayout();
|
|
57
|
+
if (Array.from(layout.widgets()).length > 1) {
|
|
58
|
+
layout.removeWidget(widget);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
widget.addClass("widget-blur");
|
|
62
|
+
document.body.classList.add("lm-mod-override-cursor");
|
|
63
|
+
|
|
64
|
+
// @ts-ignore: accessing a private member `_drag`
|
|
65
|
+
const drag = this._drag;
|
|
66
|
+
if (drag) {
|
|
67
|
+
drag.dragImage?.parentElement.removeChild(drag.dragImage);
|
|
68
|
+
drag.dragImage = null;
|
|
69
|
+
drag._promise.then(() => {
|
|
70
|
+
if (!widget.node.isConnected) {
|
|
71
|
+
layout.restoreLayout(old);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
document.body.classList.remove("lm-mod-override-cursor");
|
|
75
|
+
widget.removeClass("widget-blur");
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static getWidgets(
|
|
81
|
+
layout: DockPanel.ILayoutConfig,
|
|
82
|
+
): PerspectiveViewerWidget[] {
|
|
83
|
+
if (!!layout.main) {
|
|
84
|
+
return PerspectiveDockPanel.getAreaWidgets(layout.main);
|
|
85
|
+
} else {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static getAreaWidgets(
|
|
91
|
+
layout: DockLayout.AreaConfig,
|
|
92
|
+
): PerspectiveViewerWidget[] {
|
|
93
|
+
if (layout?.hasOwnProperty("children")) {
|
|
94
|
+
const split_panel = layout as DockLayout.ISplitAreaConfig;
|
|
95
|
+
return split_panel.children.flatMap((widget) =>
|
|
96
|
+
PerspectiveDockPanel.getAreaWidgets(widget),
|
|
97
|
+
);
|
|
98
|
+
} else if (layout?.hasOwnProperty("widgets")) {
|
|
99
|
+
const tab_panel = layout as DockLayout.ITabAreaConfig;
|
|
100
|
+
return tab_panel.widgets as PerspectiveViewerWidget[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
widgets(): IterableIterator<PerspectiveViewerWidget> {
|
|
107
|
+
return super.widgets() as IterableIterator<PerspectiveViewerWidget>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static mapWidgets(
|
|
111
|
+
widgetFunc: (widget: any) => any,
|
|
112
|
+
layout: any,
|
|
113
|
+
): DockPanel.ILayoutConfig {
|
|
114
|
+
if (!!layout.main) {
|
|
115
|
+
layout.main = PerspectiveDockPanel.mapAreaWidgets(
|
|
116
|
+
widgetFunc,
|
|
117
|
+
layout.main,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return layout;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static mapAreaWidgets(
|
|
125
|
+
widgetFunc: (widget: any) => any,
|
|
126
|
+
layout: DockLayout.AreaConfig,
|
|
127
|
+
): DockLayout.AreaConfig {
|
|
128
|
+
if (layout.hasOwnProperty("children")) {
|
|
129
|
+
const split_panel = layout as DockLayout.ISplitAreaConfig;
|
|
130
|
+
split_panel.children = split_panel.children.map((widget) =>
|
|
131
|
+
PerspectiveDockPanel.mapAreaWidgets(widgetFunc, widget),
|
|
132
|
+
);
|
|
133
|
+
} else if (layout.hasOwnProperty("widgets")) {
|
|
134
|
+
const tab_panel = layout as DockLayout.ITabAreaConfig;
|
|
135
|
+
tab_panel.widgets = tab_panel.widgets.map(widgetFunc);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return layout;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onAfterAttach() {
|
|
142
|
+
this.spacing =
|
|
143
|
+
parseInt(window.getComputedStyle(this.node).padding) || 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
export * from "./workspace";
|
|
@@ -0,0 +1,213 @@
|
|
|
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 { ElementExt } from "@lumino/domutils";
|
|
14
|
+
import { MessageLoop } from "@lumino/messaging";
|
|
15
|
+
import { h } from "@lumino/virtualdom";
|
|
16
|
+
import { Menu, Widget } from "@lumino/widgets";
|
|
17
|
+
|
|
18
|
+
export class WorkspaceMenu extends Menu {
|
|
19
|
+
private _host: ShadowRoot;
|
|
20
|
+
private _workspace: HTMLElement;
|
|
21
|
+
init_overlay?: () => void;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
host: ShadowRoot,
|
|
25
|
+
workspace: HTMLElement,
|
|
26
|
+
options: Menu.IOptions,
|
|
27
|
+
) {
|
|
28
|
+
options.renderer = new MenuRenderer();
|
|
29
|
+
super(options);
|
|
30
|
+
this._host = host;
|
|
31
|
+
this._workspace = workspace;
|
|
32
|
+
(this as any)._openChildMenu = this._overrideOpenChildMenu.bind(this);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
open(x: number, y: number, options?: Menu.IOpenOptions) {
|
|
36
|
+
options ||= {};
|
|
37
|
+
options.host = this._host as any as HTMLElement;
|
|
38
|
+
const box = this._workspace.getBoundingClientRect();
|
|
39
|
+
super.open(x, y, options);
|
|
40
|
+
const menu_box = this.node.getBoundingClientRect();
|
|
41
|
+
if (
|
|
42
|
+
menu_box.height + y > box.height &&
|
|
43
|
+
menu_box.height + y < document.documentElement.clientHeight
|
|
44
|
+
) {
|
|
45
|
+
this.node.style.top = `-${menu_box.height}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (menu_box.width + x > box.width) {
|
|
49
|
+
if (menu_box.width + x < document.documentElement.clientWidth) {
|
|
50
|
+
this.node.style.left = `-${menu_box.width + x - box.width}px`;
|
|
51
|
+
} else {
|
|
52
|
+
this.node.style.left = `-${
|
|
53
|
+
document.documentElement.clientWidth - box.width
|
|
54
|
+
}px`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Override this lumino private method because it will otherwise always
|
|
60
|
+
// attach to `document.body`.
|
|
61
|
+
private _overrideOpenChildMenu(activateFirst = false) {
|
|
62
|
+
const self = this as any;
|
|
63
|
+
let item = this.activeItem;
|
|
64
|
+
if (!item || item.type !== "submenu" || !item.submenu) {
|
|
65
|
+
self._closeChildMenu();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let submenu = item.submenu;
|
|
70
|
+
if (submenu === self._childMenu) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Menu.saveWindowData();
|
|
75
|
+
self._closeChildMenu();
|
|
76
|
+
self._childMenu = submenu;
|
|
77
|
+
self._childIndex = self._activeIndex;
|
|
78
|
+
(submenu as any)._parentMenu = this;
|
|
79
|
+
MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
|
|
80
|
+
let itemNode = this.contentNode.children[self._activeIndex];
|
|
81
|
+
openSubmenu(submenu, itemNode as HTMLElement, self._host);
|
|
82
|
+
if (activateFirst) {
|
|
83
|
+
submenu.activeIndex = -1;
|
|
84
|
+
submenu.activateNextItem();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
submenu.activate();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class MenuRenderer extends Menu.Renderer {
|
|
92
|
+
formatLabel(data: Menu.IRenderData) {
|
|
93
|
+
let { label, mnemonic } = data.item;
|
|
94
|
+
if (mnemonic < 0 || mnemonic >= label.length) {
|
|
95
|
+
return label;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let prefix = label.slice(0, mnemonic);
|
|
99
|
+
let suffix = label.slice(mnemonic + 1);
|
|
100
|
+
let char = label[mnemonic];
|
|
101
|
+
let span = h.span(
|
|
102
|
+
{
|
|
103
|
+
className: "lm-Menu-itemMnemonic p-Menu-itemMnemonic",
|
|
104
|
+
},
|
|
105
|
+
char,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return [prefix, span, suffix];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
renderLabel(data: Menu.IRenderData) {
|
|
112
|
+
let content = this.formatLabel(data);
|
|
113
|
+
return h.div(
|
|
114
|
+
{
|
|
115
|
+
className: "lm-Menu-itemLabel p-Menu-itemLabel",
|
|
116
|
+
},
|
|
117
|
+
content,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
renderSubmenu(data: Menu.IRenderData) {
|
|
122
|
+
return h.div({
|
|
123
|
+
className: "lm-Menu-itemSubmenuIcon" + " p-Menu-itemSubmenuIcon",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
renderItem(data: Menu.IRenderData) {
|
|
128
|
+
let className = this.createItemClass(data);
|
|
129
|
+
let dataset = this.createItemDataset(data);
|
|
130
|
+
let aria = this.createItemARIA(data);
|
|
131
|
+
return h.li(
|
|
132
|
+
{
|
|
133
|
+
className,
|
|
134
|
+
dataset,
|
|
135
|
+
tabindex: "0",
|
|
136
|
+
onfocus: data.onfocus,
|
|
137
|
+
...aria,
|
|
138
|
+
},
|
|
139
|
+
this.renderLabel(data),
|
|
140
|
+
this.renderShortcut(data),
|
|
141
|
+
this.renderSubmenu(data),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Prevent submenus from attaching outside the Shadow DOM.
|
|
147
|
+
// Forked from [Lumino](https://github.com/jupyterlab/lumino/blob/main/packages/widgets/src/menu.ts).
|
|
148
|
+
// [License](https://github.com/jupyterlab/lumino/blob/main/LICENSE)
|
|
149
|
+
export function openSubmenu(
|
|
150
|
+
submenu: Menu,
|
|
151
|
+
itemNode: HTMLElement,
|
|
152
|
+
host: HTMLElement,
|
|
153
|
+
): void {
|
|
154
|
+
const windowData = getWindowData();
|
|
155
|
+
let px = windowData.pageXOffset;
|
|
156
|
+
let py = windowData.pageYOffset;
|
|
157
|
+
let cw = windowData.clientWidth;
|
|
158
|
+
let ch = windowData.clientHeight;
|
|
159
|
+
const hostData = (host as any).host.getBoundingClientRect();
|
|
160
|
+
let hx = hostData.x;
|
|
161
|
+
let hy = hostData.y;
|
|
162
|
+
MessageLoop.sendMessage(submenu, Widget.Msg.UpdateRequest);
|
|
163
|
+
let maxHeight = ch;
|
|
164
|
+
let node = submenu.node;
|
|
165
|
+
let style = node.style;
|
|
166
|
+
style.opacity = "0";
|
|
167
|
+
style.maxHeight = `${maxHeight}px`;
|
|
168
|
+
Widget.attach(submenu, host);
|
|
169
|
+
let { width, height } = node.getBoundingClientRect();
|
|
170
|
+
let box = ElementExt.boxSizing(submenu.node);
|
|
171
|
+
let itemRect = itemNode.getBoundingClientRect();
|
|
172
|
+
let x = itemRect.right - SUBMENU_OVERLAP - hx;
|
|
173
|
+
if (x + width > px + cw + hx) {
|
|
174
|
+
x = itemRect.left + SUBMENU_OVERLAP - width;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let y = itemRect.top - box.borderTop - box.paddingTop - hy;
|
|
178
|
+
if (y + height > py + ch + hy) {
|
|
179
|
+
y = itemRect.bottom + box.borderBottom + box.paddingBottom - height;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
style.transform = `translate(${Math.max(0, x)}px, ${Math.max(0, y)}px`;
|
|
183
|
+
style.opacity = "1";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const SUBMENU_OVERLAP = 3;
|
|
187
|
+
|
|
188
|
+
let transientWindowDataCache: IWindowData | null = null;
|
|
189
|
+
let transientCacheCounter: number = 0;
|
|
190
|
+
|
|
191
|
+
function getWindowData(): IWindowData {
|
|
192
|
+
if (transientCacheCounter > 0) {
|
|
193
|
+
transientCacheCounter--;
|
|
194
|
+
return transientWindowDataCache!;
|
|
195
|
+
}
|
|
196
|
+
return _getWindowData();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _getWindowData(): IWindowData {
|
|
200
|
+
return {
|
|
201
|
+
pageXOffset: window.pageXOffset,
|
|
202
|
+
pageYOffset: window.pageYOffset,
|
|
203
|
+
clientWidth: document.documentElement.clientWidth,
|
|
204
|
+
clientHeight: document.documentElement.clientHeight,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface IWindowData {
|
|
209
|
+
pageXOffset: number;
|
|
210
|
+
pageYOffset: number;
|
|
211
|
+
clientWidth: number;
|
|
212
|
+
clientHeight: number;
|
|
213
|
+
}
|