@jupyterlab/application 4.6.0-alpha.5 → 4.6.0-beta.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/lib/dockpanel.d.ts +40 -0
- package/lib/dockpanel.js +231 -0
- package/lib/dockpanel.js.map +1 -0
- package/lib/frontend.js +1 -0
- package/lib/frontend.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js.map +1 -1
- package/lib/lab.js +1 -0
- package/lib/lab.js.map +1 -1
- package/lib/layoutrestorer.js +7 -0
- package/lib/layoutrestorer.js.map +1 -1
- package/lib/mimerenderers.js +1 -0
- package/lib/mimerenderers.js.map +1 -1
- package/lib/shell.d.ts +61 -4
- package/lib/shell.js +350 -41
- package/lib/shell.js.map +1 -1
- package/lib/status.d.ts +1 -1
- package/lib/status.js +0 -2
- package/lib/status.js.map +1 -1
- package/lib/tokens.d.ts +2 -2
- package/lib/tokens.js +0 -2
- package/lib/tokens.js.map +1 -1
- package/lib/utils.js +1 -0
- package/lib/utils.js.map +1 -1
- package/package.json +13 -13
- package/src/dockpanel.ts +267 -0
- package/src/frontend.ts +1 -0
- package/src/index.ts +2 -5
- package/src/lab.ts +1 -0
- package/src/layoutrestorer.ts +13 -0
- package/src/mimerenderers.ts +1 -0
- package/src/shell.ts +438 -40
- package/src/status.ts +1 -2
- package/src/tokens.ts +2 -3
- package/src/utils.ts +1 -0
- package/style/buttons.css +12 -4
- package/style/dockpanel.css +5 -1
- package/style/menus.css +10 -2
- package/style/scrollbar.css +69 -28
- package/style/sidepanel.css +162 -19
- package/style/tabs.css +5 -7
package/src/dockpanel.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { DockPanelSvg } from '@jupyterlab/ui-components';
|
|
2
|
+
import type { Widget } from '@lumino/widgets';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_NODE_THRESHOLD = 1000;
|
|
5
|
+
const DEFAULT_TEXT_LENGTH_THRESHOLD = 25000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A dock panel that freezes heavy panel dimensions during handle drags to
|
|
9
|
+
* reduce layout reflow and improve resize performance.
|
|
10
|
+
*/
|
|
11
|
+
export class OptimizedDockPanelSvg extends DockPanelSvg {
|
|
12
|
+
/**
|
|
13
|
+
* Handle the DOM events for the dock panel.
|
|
14
|
+
*
|
|
15
|
+
* @param event - The DOM event sent to the panel.
|
|
16
|
+
*
|
|
17
|
+
* #### Notes
|
|
18
|
+
* This method implements the DOM `EventListener` interface and is
|
|
19
|
+
* called in response to events registered for the node. It should
|
|
20
|
+
* not be called directly by user code.
|
|
21
|
+
*/
|
|
22
|
+
override handleEvent(event: Event): void {
|
|
23
|
+
if (event.type === 'pointerdown') {
|
|
24
|
+
this._isResizeDragActive = this._isHandlePointerDown(event);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Unfreeze before super processes the drag-release events so Lumino
|
|
28
|
+
// measures natural sizes when it finalises the split position.
|
|
29
|
+
if (this._frozenGroups.length > 0 && this._isResizeDragActive) {
|
|
30
|
+
const t = event.type;
|
|
31
|
+
if (t === 'pointerup' || t === 'pointercancel' || t === 'keydown') {
|
|
32
|
+
this._isResizeDragActive = false;
|
|
33
|
+
this._unfreezeElements();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
super.handleEvent(event);
|
|
38
|
+
|
|
39
|
+
if (!this._optimizeResize) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (event.type === 'pointerdown' && this._isResizeDragActive) {
|
|
44
|
+
this._freezeHeavyLeaves();
|
|
45
|
+
} else if (event.type === 'pointermove' && this._isResizeDragActive) {
|
|
46
|
+
this._scheduleRefresh();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private _isHandlePointerDown(event: Event): boolean {
|
|
51
|
+
const pointerEvent = event as PointerEvent;
|
|
52
|
+
if (pointerEvent.button !== 0) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const target = pointerEvent.target;
|
|
56
|
+
if (!(target instanceof HTMLElement)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
for (const handle of this.handles()) {
|
|
60
|
+
if (handle.contains(target)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether resize optimizations are enabled.
|
|
69
|
+
*
|
|
70
|
+
* When `true` (the default), panels with heavy DOM content have their
|
|
71
|
+
* dimensions frozen during a handle drag, avoiding repeated reflows.
|
|
72
|
+
* Setting this to `false` immediately unfreezes any frozen panels.
|
|
73
|
+
*/
|
|
74
|
+
get optimizeResize(): boolean {
|
|
75
|
+
return this._optimizeResize;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set optimizeResize(enabled: boolean) {
|
|
79
|
+
this._optimizeResize = enabled;
|
|
80
|
+
if (!enabled) {
|
|
81
|
+
this._unfreezeElements();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private _freezeHeavyLeaves(): void {
|
|
86
|
+
if (this._frozenGroups.length > 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const targets: Widget[] = [];
|
|
91
|
+
for (const child of this.widgets()) {
|
|
92
|
+
this._collectHeavyWidgets(child, targets);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const target of targets) {
|
|
96
|
+
const head = target.node;
|
|
97
|
+
const elements: HTMLElement[] = [head];
|
|
98
|
+
for (let i = 0; i < head.children.length; i++) {
|
|
99
|
+
elements.push(head.children[i] as HTMLElement);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Read all rects before writing to avoid layout thrashing.
|
|
103
|
+
const rects = elements.map(el => el.getBoundingClientRect());
|
|
104
|
+
|
|
105
|
+
const frozenGroup: Private.IFrozenElement[] = elements.map((el, i) => ({
|
|
106
|
+
element: el,
|
|
107
|
+
isHead: i === 0,
|
|
108
|
+
prevWidth: el.style.width,
|
|
109
|
+
prevMaxWidth: el.style.maxWidth,
|
|
110
|
+
prevHeight: el.style.height,
|
|
111
|
+
prevMaxHeight: el.style.maxHeight
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < frozenGroup.length; i++) {
|
|
115
|
+
const el = frozenGroup[i].element;
|
|
116
|
+
const rect = rects[i];
|
|
117
|
+
el.style.width = `${rect.width}px`;
|
|
118
|
+
el.style.maxWidth = `${rect.width}px`;
|
|
119
|
+
el.style.maxHeight = `${rect.height}px`;
|
|
120
|
+
if (frozenGroup[i].isHead) {
|
|
121
|
+
el.style.height = `${rect.height}px`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this._frozenGroups.push(frozenGroup);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this._frozenGroups.length > 0 && this._intervalId === 0) {
|
|
128
|
+
this._intervalId = window.setInterval(() => {
|
|
129
|
+
this._refreshFrozenElements();
|
|
130
|
+
}, 3000);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _scheduleRefresh(): void {
|
|
135
|
+
if (this._frozenGroups.length === 0) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this._refreshTimerId !== 0) {
|
|
139
|
+
clearTimeout(this._refreshTimerId);
|
|
140
|
+
}
|
|
141
|
+
this._refreshTimerId = window.setTimeout(() => {
|
|
142
|
+
this._refreshTimerId = 0;
|
|
143
|
+
this._refreshFrozenElements();
|
|
144
|
+
}, 300);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private _refreshFrozenElements(): void {
|
|
148
|
+
let g = 0;
|
|
149
|
+
const step = () => {
|
|
150
|
+
if (g >= this._frozenGroups.length) {
|
|
151
|
+
this._refreshRAFId = 0;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let group = this._frozenGroups[g];
|
|
156
|
+
for (let entry of group) {
|
|
157
|
+
let el = entry.element;
|
|
158
|
+
el.style.width = '';
|
|
159
|
+
el.style.maxWidth = '';
|
|
160
|
+
el.style.height = '';
|
|
161
|
+
el.style.maxHeight = '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._refreshRAFId = requestAnimationFrame(() => {
|
|
165
|
+
let rects = group.map(entry => entry.element.getBoundingClientRect());
|
|
166
|
+
for (let i = 0; i < group.length; i++) {
|
|
167
|
+
let entry = group[i];
|
|
168
|
+
let rect = rects[i];
|
|
169
|
+
let el = entry.element;
|
|
170
|
+
el.style.width = `${rect.width}px`;
|
|
171
|
+
el.style.maxWidth = `${rect.width}px`;
|
|
172
|
+
el.style.maxHeight = `${rect.height}px`;
|
|
173
|
+
if (entry.isHead) {
|
|
174
|
+
el.style.height = `${rect.height}px`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
g++;
|
|
178
|
+
this._refreshRAFId = requestAnimationFrame(step);
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
this._refreshRAFId = requestAnimationFrame(step);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private _unfreezeElements(): void {
|
|
186
|
+
if (this._refreshTimerId !== 0) {
|
|
187
|
+
clearTimeout(this._refreshTimerId);
|
|
188
|
+
this._refreshTimerId = 0;
|
|
189
|
+
}
|
|
190
|
+
if (this._refreshRAFId !== 0) {
|
|
191
|
+
cancelAnimationFrame(this._refreshRAFId);
|
|
192
|
+
this._refreshRAFId = 0;
|
|
193
|
+
}
|
|
194
|
+
if (this._intervalId !== 0) {
|
|
195
|
+
clearInterval(this._intervalId);
|
|
196
|
+
this._intervalId = 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (let group of this._frozenGroups) {
|
|
200
|
+
for (let entry of group) {
|
|
201
|
+
entry.element.style.width = entry.prevWidth;
|
|
202
|
+
entry.element.style.maxWidth = entry.prevMaxWidth;
|
|
203
|
+
entry.element.style.height = entry.prevHeight;
|
|
204
|
+
entry.element.style.maxHeight = entry.prevMaxHeight;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this._frozenGroups = [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private _collectHeavyWidgets(widget: Widget, result: Widget[]): void {
|
|
212
|
+
if (!this._isDOMHeavy(widget.node)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let layout = widget.layout;
|
|
217
|
+
if (!layout) {
|
|
218
|
+
result.push(widget);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let anyChildHeavy = false;
|
|
223
|
+
for (let child of layout) {
|
|
224
|
+
if (this._isDOMHeavy(child.node)) {
|
|
225
|
+
anyChildHeavy = true;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (anyChildHeavy) {
|
|
231
|
+
for (let child of layout) {
|
|
232
|
+
this._collectHeavyWidgets(child, result);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
result.push(widget);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private _isDOMHeavy(el: HTMLElement): boolean {
|
|
240
|
+
if (el.querySelectorAll('*').length >= DEFAULT_NODE_THRESHOLD) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if ((el.textContent?.length ?? 0) >= DEFAULT_TEXT_LENGTH_THRESHOLD) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private _optimizeResize = true;
|
|
250
|
+
private _isResizeDragActive = false;
|
|
251
|
+
private _frozenGroups: Private.IFrozenElement[][] = [];
|
|
252
|
+
private _refreshTimerId = 0;
|
|
253
|
+
private _refreshRAFId = 0;
|
|
254
|
+
private _intervalId = 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Namespace for OptimizedDockPanelSvg statics */
|
|
258
|
+
namespace Private {
|
|
259
|
+
export interface IFrozenElement {
|
|
260
|
+
element: HTMLElement;
|
|
261
|
+
isHead: boolean;
|
|
262
|
+
prevWidth: string;
|
|
263
|
+
prevMaxWidth: string;
|
|
264
|
+
prevHeight: string;
|
|
265
|
+
prevMaxHeight: string;
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/frontend.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
|
|
4
5
|
import { CommandLinker } from '@jupyterlab/apputils';
|
|
5
6
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
package/src/index.ts
CHANGED
|
@@ -6,11 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export { ConnectionLost } from './connectionlost';
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
JupyterFrontEndContextMenu,
|
|
12
|
-
JupyterFrontEndPlugin
|
|
13
|
-
} from './frontend';
|
|
9
|
+
export { JupyterFrontEnd, JupyterFrontEndContextMenu } from './frontend';
|
|
10
|
+
export type { JupyterFrontEndPlugin } from './frontend';
|
|
14
11
|
export { JupyterLab } from './lab';
|
|
15
12
|
export { ILayoutRestorer, LayoutRestorer } from './layoutrestorer';
|
|
16
13
|
export {
|
package/src/lab.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
|
|
4
5
|
import { PageConfig } from '@jupyterlab/coreutils';
|
|
5
6
|
import { Base64ModelFactory } from '@jupyterlab/docregistry';
|
package/src/layoutrestorer.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
| Copyright (c) Jupyter Development Team.
|
|
3
3
|
| Distributed under the terms of the Modified BSD License.
|
|
4
4
|
|----------------------------------------------------------------------------*/
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
6
|
|
|
6
7
|
import type { WidgetTracker } from '@jupyterlab/apputils';
|
|
7
8
|
import type { IDataConnector, IRestorer } from '@jupyterlab/statedb';
|
|
@@ -398,6 +399,10 @@ export class LayoutRestorer implements ILayoutRestorer {
|
|
|
398
399
|
size: area.size
|
|
399
400
|
};
|
|
400
401
|
|
|
402
|
+
if (area.collapsed !== undefined) {
|
|
403
|
+
dehydrated.collapsed = area.collapsed;
|
|
404
|
+
}
|
|
405
|
+
|
|
401
406
|
if (area.currentWidget) {
|
|
402
407
|
const current = Private.nameProperty.get(area.currentWidget);
|
|
403
408
|
if (current) {
|
|
@@ -441,6 +446,9 @@ export class LayoutRestorer implements ILayoutRestorer {
|
|
|
441
446
|
)
|
|
442
447
|
.filter(widget => !!widget);
|
|
443
448
|
return {
|
|
449
|
+
...(typeof area.collapsed === 'boolean'
|
|
450
|
+
? { collapsed: area.collapsed }
|
|
451
|
+
: {}),
|
|
444
452
|
currentWidget: currentWidget!,
|
|
445
453
|
size: area.size ?? 0.0,
|
|
446
454
|
widgets: widgets as Widget[] | null
|
|
@@ -737,6 +745,11 @@ namespace Private {
|
|
|
737
745
|
* The restorable description of the down area in the user interface
|
|
738
746
|
*/
|
|
739
747
|
export interface IDownArea extends PartialJSONObject {
|
|
748
|
+
/**
|
|
749
|
+
* Whether the down area is collapsed.
|
|
750
|
+
*/
|
|
751
|
+
collapsed?: boolean | null;
|
|
752
|
+
|
|
740
753
|
/**
|
|
741
754
|
* The current widget that has application focus.
|
|
742
755
|
*/
|
package/src/mimerenderers.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
|
|
4
5
|
import type { IWidgetTracker } from '@jupyterlab/apputils';
|
|
5
6
|
import { WidgetTracker } from '@jupyterlab/apputils';
|