@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/lib/shell.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
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
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
4
5
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
5
|
-
import { classes,
|
|
6
|
+
import { classes, LabIcon, TabBarSvg, tabIcon, TabPanelSvg } from '@jupyterlab/ui-components';
|
|
6
7
|
import { ArrayExt, find, map } from '@lumino/algorithm';
|
|
7
8
|
import { JSONExt, PromiseDelegate, Token } from '@lumino/coreutils';
|
|
8
9
|
import { MessageLoop } from '@lumino/messaging';
|
|
9
10
|
import { Debouncer } from '@lumino/polling';
|
|
10
11
|
import { Signal } from '@lumino/signaling';
|
|
11
12
|
import { AccordionPanel, BoxLayout, BoxPanel, FocusTracker, Panel, SplitPanel, StackedPanel, TabBar, Widget } from '@lumino/widgets';
|
|
13
|
+
import { OptimizedDockPanelSvg } from './dockpanel';
|
|
12
14
|
/**
|
|
13
15
|
* The class name added to AppShell instances.
|
|
14
16
|
*/
|
|
@@ -30,6 +32,10 @@ const ACTIVE_CLASS = 'jp-mod-active';
|
|
|
30
32
|
*/
|
|
31
33
|
const DEFAULT_RANK = 900;
|
|
32
34
|
const ACTIVITY_CLASS = 'jp-Activity';
|
|
35
|
+
/**
|
|
36
|
+
* The default relative size of the down area when it is expanded.
|
|
37
|
+
*/
|
|
38
|
+
const DEFAULT_DOWN_AREA_SIZE = 0.25;
|
|
33
39
|
/**
|
|
34
40
|
* The JupyterLab application shell token.
|
|
35
41
|
*/
|
|
@@ -68,6 +74,7 @@ export class LabShell extends Widget {
|
|
|
68
74
|
this._currentPathChanged = new Signal(this);
|
|
69
75
|
this._modeChanged = new Signal(this);
|
|
70
76
|
this._isRestored = false;
|
|
77
|
+
this._lastDownAreaSize = DEFAULT_DOWN_AREA_SIZE;
|
|
71
78
|
this._layoutModified = new Signal(this);
|
|
72
79
|
this._layoutDebouncer = new Debouncer(() => {
|
|
73
80
|
this._layoutModified.emit(undefined);
|
|
@@ -101,7 +108,7 @@ export class LabShell extends Widget {
|
|
|
101
108
|
const hboxPanel = new BoxPanel();
|
|
102
109
|
const vsplitPanel = (this._vsplitPanel =
|
|
103
110
|
new Private.RestorableSplitPanel());
|
|
104
|
-
const dockPanel = (this._dockPanel = new
|
|
111
|
+
const dockPanel = (this._dockPanel = new OptimizedDockPanelSvg({
|
|
105
112
|
hiddenMode: Widget.HiddenMode.Display
|
|
106
113
|
}));
|
|
107
114
|
MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
|
|
@@ -110,8 +117,14 @@ export class LabShell extends Widget {
|
|
|
110
117
|
const downPanel = (this._downPanel = new TabPanelSvg({
|
|
111
118
|
tabsMovable: true
|
|
112
119
|
}));
|
|
113
|
-
const leftHandler = (this._leftHandler = new Private.SideBarHandler(
|
|
114
|
-
|
|
120
|
+
const leftHandler = (this._leftHandler = new Private.SideBarHandler({
|
|
121
|
+
side: 'left',
|
|
122
|
+
host: hboxPanel
|
|
123
|
+
}));
|
|
124
|
+
const rightHandler = (this._rightHandler = new Private.SideBarHandler({
|
|
125
|
+
side: 'right',
|
|
126
|
+
host: hboxPanel
|
|
127
|
+
}));
|
|
115
128
|
const rootLayout = new BoxLayout();
|
|
116
129
|
headerPanel.id = 'jp-header-panel';
|
|
117
130
|
menuHandler.panel.id = 'jp-menu-panel';
|
|
@@ -126,10 +139,14 @@ export class LabShell extends Widget {
|
|
|
126
139
|
leftHandler.sideBar.addClass('jp-mod-left');
|
|
127
140
|
leftHandler.sideBar.node.setAttribute('role', 'complementary');
|
|
128
141
|
leftHandler.stackedPanel.id = 'jp-left-stack';
|
|
142
|
+
leftHandler.area.addClass('jp-SideArea');
|
|
143
|
+
leftHandler.area.node.setAttribute('data-side', 'left');
|
|
129
144
|
rightHandler.sideBar.addClass(SIDEBAR_CLASS);
|
|
130
145
|
rightHandler.sideBar.addClass('jp-mod-right');
|
|
131
146
|
rightHandler.sideBar.node.setAttribute('role', 'complementary');
|
|
132
147
|
rightHandler.stackedPanel.id = 'jp-right-stack';
|
|
148
|
+
rightHandler.area.addClass('jp-SideArea');
|
|
149
|
+
rightHandler.area.node.setAttribute('data-side', 'right');
|
|
133
150
|
dockPanel.node.setAttribute('role', 'main');
|
|
134
151
|
hboxPanel.spacing = 0;
|
|
135
152
|
vsplitPanel.spacing = 1;
|
|
@@ -140,17 +157,17 @@ export class LabShell extends Widget {
|
|
|
140
157
|
hboxPanel.direction = 'left-to-right';
|
|
141
158
|
hsplitPanel.orientation = 'horizontal';
|
|
142
159
|
bottomPanel.direction = 'bottom-to-top';
|
|
143
|
-
SplitPanel.setStretch(leftHandler.
|
|
160
|
+
SplitPanel.setStretch(leftHandler.area, 0);
|
|
144
161
|
SplitPanel.setStretch(downPanel, 0);
|
|
145
162
|
SplitPanel.setStretch(dockPanel, 1);
|
|
146
|
-
SplitPanel.setStretch(rightHandler.
|
|
163
|
+
SplitPanel.setStretch(rightHandler.area, 0);
|
|
147
164
|
BoxPanel.setStretch(leftHandler.sideBar, 0);
|
|
148
165
|
BoxPanel.setStretch(hsplitPanel, 1);
|
|
149
166
|
BoxPanel.setStretch(rightHandler.sideBar, 0);
|
|
150
167
|
SplitPanel.setStretch(vsplitPanel, 1);
|
|
151
|
-
hsplitPanel.addWidget(leftHandler.
|
|
168
|
+
hsplitPanel.addWidget(leftHandler.area);
|
|
152
169
|
hsplitPanel.addWidget(dockPanel);
|
|
153
|
-
hsplitPanel.addWidget(rightHandler.
|
|
170
|
+
hsplitPanel.addWidget(rightHandler.area);
|
|
154
171
|
vsplitPanel.addWidget(hsplitPanel);
|
|
155
172
|
vsplitPanel.addWidget(downPanel);
|
|
156
173
|
hboxPanel.addWidget(leftHandler.sideBar);
|
|
@@ -282,6 +299,12 @@ export class LabShell extends Widget {
|
|
|
282
299
|
get currentWidget() {
|
|
283
300
|
return this._tracker.currentWidget;
|
|
284
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Whether the down area is collapsed.
|
|
304
|
+
*/
|
|
305
|
+
get downCollapsed() {
|
|
306
|
+
return this._downPanel.isHidden;
|
|
307
|
+
}
|
|
285
308
|
/**
|
|
286
309
|
* A signal emitted when the main area's layout is modified.
|
|
287
310
|
*/
|
|
@@ -443,6 +466,7 @@ export class LabShell extends Widget {
|
|
|
443
466
|
* Activate a widget in its area.
|
|
444
467
|
*/
|
|
445
468
|
activateById(id) {
|
|
469
|
+
var _a;
|
|
446
470
|
if (this._leftHandler.has(id)) {
|
|
447
471
|
this._leftHandler.activate(id);
|
|
448
472
|
return;
|
|
@@ -453,7 +477,13 @@ export class LabShell extends Widget {
|
|
|
453
477
|
}
|
|
454
478
|
const tabIndex = this._downPanel.tabBar.titles.findIndex(title => title.owner.id === id);
|
|
455
479
|
if (tabIndex >= 0) {
|
|
480
|
+
const wasHidden = this._downPanel.isHidden;
|
|
456
481
|
this._downPanel.currentIndex = tabIndex;
|
|
482
|
+
if (wasHidden) {
|
|
483
|
+
this._showDownPanel();
|
|
484
|
+
this._onLayoutModified();
|
|
485
|
+
}
|
|
486
|
+
(_a = this._downPanel.currentWidget) === null || _a === void 0 ? void 0 : _a.activate();
|
|
457
487
|
return;
|
|
458
488
|
}
|
|
459
489
|
const dock = this._dockPanel;
|
|
@@ -613,6 +643,12 @@ export class LabShell extends Widget {
|
|
|
613
643
|
...userPosition === null || userPosition === void 0 ? void 0 : userPosition.options
|
|
614
644
|
}
|
|
615
645
|
: undefined;
|
|
646
|
+
if ((options === null || options === void 0 ? void 0 : options.rank) !== undefined) {
|
|
647
|
+
this._sideOptionsCache.set(widget, {
|
|
648
|
+
...this._sideOptionsCache.get(widget),
|
|
649
|
+
rank: options.rank
|
|
650
|
+
});
|
|
651
|
+
}
|
|
616
652
|
switch (area || 'main') {
|
|
617
653
|
case 'bottom':
|
|
618
654
|
return this._addToBottomArea(widget, options);
|
|
@@ -635,13 +671,19 @@ export class LabShell extends Widget {
|
|
|
635
671
|
}
|
|
636
672
|
}
|
|
637
673
|
/**
|
|
638
|
-
* Move a widget
|
|
674
|
+
* Move a widget to a new area and update the shell user layout.
|
|
639
675
|
*
|
|
640
|
-
* The
|
|
676
|
+
* The widget is reparented to `area` immediately. The type used as the
|
|
677
|
+
* user-layout key is determined from `widget.id`, falling back to
|
|
678
|
+
* `widget.id` itself.
|
|
641
679
|
*
|
|
642
680
|
* #### Notes
|
|
643
|
-
* If `mode` is undefined, both
|
|
644
|
-
*
|
|
681
|
+
* If `mode` is undefined, both modes are updated in the user layout.
|
|
682
|
+
* When `mode` is set, only that mode's user layout is updated, but the
|
|
683
|
+
* live widget is still reparented regardless of mode.
|
|
684
|
+
*
|
|
685
|
+
* The new layout is stored in the shell user layout. Callers are
|
|
686
|
+
* responsible for persisting it when needed.
|
|
645
687
|
*
|
|
646
688
|
* @param widget Widget to move
|
|
647
689
|
* @param area New area
|
|
@@ -649,12 +691,22 @@ export class LabShell extends Widget {
|
|
|
649
691
|
* @returns The new user layout
|
|
650
692
|
*/
|
|
651
693
|
move(widget, area, mode) {
|
|
652
|
-
var _a;
|
|
694
|
+
var _a, _b;
|
|
653
695
|
const type = (_a = this._idTypeMap.get(widget.id)) !== null && _a !== void 0 ? _a : widget.id;
|
|
696
|
+
const rank = (_b = this._sideOptionsCache.get(widget)) === null || _b === void 0 ? void 0 : _b.rank;
|
|
654
697
|
for (const m of ['single-document', 'multiple-document'].filter(c => !mode || c === mode)) {
|
|
698
|
+
const position = this._userLayout[m][type];
|
|
655
699
|
this._userLayout[m][type] = {
|
|
656
|
-
...
|
|
657
|
-
area
|
|
700
|
+
...position,
|
|
701
|
+
area,
|
|
702
|
+
...(rank !== undefined
|
|
703
|
+
? {
|
|
704
|
+
options: {
|
|
705
|
+
...position === null || position === void 0 ? void 0 : position.options,
|
|
706
|
+
rank
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
: {})
|
|
658
710
|
};
|
|
659
711
|
}
|
|
660
712
|
this.add(widget, area);
|
|
@@ -674,6 +726,15 @@ export class LabShell extends Widget {
|
|
|
674
726
|
this._rightHandler.collapse();
|
|
675
727
|
this._onLayoutModified();
|
|
676
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Collapse the down area.
|
|
731
|
+
*/
|
|
732
|
+
collapseDown() {
|
|
733
|
+
if (!this._downPanel.isHidden) {
|
|
734
|
+
this._hideDownPanel();
|
|
735
|
+
this._onLayoutModified();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
677
738
|
/**
|
|
678
739
|
* Dispose the shell.
|
|
679
740
|
*/
|
|
@@ -706,6 +767,18 @@ export class LabShell extends Widget {
|
|
|
706
767
|
this._rightHandler.expand();
|
|
707
768
|
this._onLayoutModified();
|
|
708
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Expand the down area.
|
|
772
|
+
*/
|
|
773
|
+
expandDown() {
|
|
774
|
+
var _a;
|
|
775
|
+
if (this._downPanel.stackedPanel.widgets.length === 0) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
this._showDownPanel();
|
|
779
|
+
(_a = this._downPanel.currentWidget) === null || _a === void 0 ? void 0 : _a.activate();
|
|
780
|
+
this._onLayoutModified();
|
|
781
|
+
}
|
|
709
782
|
/**
|
|
710
783
|
* Close all widgets in the main and down area.
|
|
711
784
|
*/
|
|
@@ -770,7 +843,7 @@ export class LabShell extends Widget {
|
|
|
770
843
|
* This should only be called once.
|
|
771
844
|
*/
|
|
772
845
|
async restoreLayout(mode, layoutRestorer, configuration = {}) {
|
|
773
|
-
var _a, _b, _c, _d;
|
|
846
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
774
847
|
// Set the configuration and add widgets added before the shell was ready.
|
|
775
848
|
this._userLayout = {
|
|
776
849
|
'single-document': (_a = configuration['single-document']) !== null && _a !== void 0 ? _a : {},
|
|
@@ -813,11 +886,39 @@ export class LabShell extends Widget {
|
|
|
813
886
|
// Rehydrate the down area
|
|
814
887
|
if (downArea) {
|
|
815
888
|
const { currentWidget, widgets, size } = downArea;
|
|
816
|
-
const
|
|
889
|
+
const collapsed = (_c = downArea.collapsed) !== null && _c !== void 0 ? _c : !size;
|
|
890
|
+
const widgetIds = (_d = widgets === null || widgets === void 0 ? void 0 : widgets.map(widget => widget.id)) !== null && _d !== void 0 ? _d : [];
|
|
891
|
+
const otherAreaWidgetIds = new Set();
|
|
892
|
+
const collectMainWidgetIds = (area) => {
|
|
893
|
+
if (!area) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (area.type === 'tab-area') {
|
|
897
|
+
area.widgets.forEach(widget => {
|
|
898
|
+
otherAreaWidgetIds.add(widget.id);
|
|
899
|
+
});
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
area.children.forEach(collectMainWidgetIds);
|
|
903
|
+
};
|
|
904
|
+
collectMainWidgetIds((_e = mainArea === null || mainArea === void 0 ? void 0 : mainArea.dock) === null || _e === void 0 ? void 0 : _e.main);
|
|
905
|
+
(_f = leftArea === null || leftArea === void 0 ? void 0 : leftArea.widgets) === null || _f === void 0 ? void 0 : _f.forEach(widget => {
|
|
906
|
+
otherAreaWidgetIds.add(widget.id);
|
|
907
|
+
});
|
|
908
|
+
(_g = rightArea === null || rightArea === void 0 ? void 0 : rightArea.widgets) === null || _g === void 0 ? void 0 : _g.forEach(widget => {
|
|
909
|
+
otherAreaWidgetIds.add(widget.id);
|
|
910
|
+
});
|
|
817
911
|
// Remove absent widgets
|
|
818
912
|
this._downPanel.tabBar.titles
|
|
819
913
|
.filter(title => !widgetIds.includes(title.owner.id))
|
|
820
|
-
.
|
|
914
|
+
.forEach(title => {
|
|
915
|
+
if (otherAreaWidgetIds.has(title.owner.id)) {
|
|
916
|
+
title.owner.parent = null;
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
title.owner.close();
|
|
920
|
+
}
|
|
921
|
+
});
|
|
821
922
|
// Add new widgets
|
|
822
923
|
const titleIds = this._downPanel.tabBar.titles.map(title => title.owner.id);
|
|
823
924
|
widgets === null || widgets === void 0 ? void 0 : widgets.filter(widget => !titleIds.includes(widget.id)).map(widget => this._downPanel.addWidget(widget));
|
|
@@ -832,18 +933,23 @@ export class LabShell extends Widget {
|
|
|
832
933
|
}
|
|
833
934
|
if (currentWidget) {
|
|
834
935
|
const index = this._downPanel.stackedPanel.widgets.findIndex(widget => widget.id === currentWidget.id);
|
|
835
|
-
if (index) {
|
|
936
|
+
if (index >= 0) {
|
|
836
937
|
this._downPanel.currentIndex = index;
|
|
837
|
-
(_d = this._downPanel.currentWidget) === null || _d === void 0 ? void 0 : _d.activate();
|
|
838
938
|
}
|
|
839
939
|
}
|
|
840
|
-
if (size && size > 0.0) {
|
|
841
|
-
this.
|
|
940
|
+
if (!collapsed && (widgets === null || widgets === void 0 ? void 0 : widgets.length) && size && size > 0.0) {
|
|
941
|
+
this._showDownPanel(size);
|
|
942
|
+
(_h = this._downPanel.currentWidget) === null || _h === void 0 ? void 0 : _h.activate();
|
|
842
943
|
}
|
|
843
944
|
else {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
945
|
+
this._hideDownPanel();
|
|
946
|
+
if (size && size > 0.0) {
|
|
947
|
+
// Remember the saved size so a later expand restores the user's
|
|
948
|
+
// previous height. `_hideDownPanel` seeds `_lastDownAreaSize`
|
|
949
|
+
// from the current splitter, which at cold startup reflects the
|
|
950
|
+
// default layout rather than the persisted value.
|
|
951
|
+
this._lastDownAreaSize = size;
|
|
952
|
+
}
|
|
847
953
|
}
|
|
848
954
|
}
|
|
849
955
|
// Rehydrate the left area.
|
|
@@ -890,9 +996,14 @@ export class LabShell extends Widget {
|
|
|
890
996
|
: this._dockPanel.saveLayout()
|
|
891
997
|
},
|
|
892
998
|
downArea: {
|
|
999
|
+
collapsed: this.downCollapsed,
|
|
893
1000
|
currentWidget: this._downPanel.currentWidget,
|
|
894
1001
|
widgets: Array.from(this._downPanel.stackedPanel.widgets),
|
|
895
|
-
size: this.
|
|
1002
|
+
size: this._downPanel.stackedPanel.widgets.length === 0
|
|
1003
|
+
? 0
|
|
1004
|
+
: this.downCollapsed
|
|
1005
|
+
? this._lastDownAreaSize
|
|
1006
|
+
: this._vsplitPanel.relativeSizes()[1]
|
|
896
1007
|
},
|
|
897
1008
|
leftArea: this._leftHandler.dehydrate(),
|
|
898
1009
|
rightArea: this._rightHandler.dehydrate(),
|
|
@@ -971,12 +1082,32 @@ export class LabShell extends Widget {
|
|
|
971
1082
|
}
|
|
972
1083
|
this._dockPanel.fit();
|
|
973
1084
|
}
|
|
1085
|
+
if (config.optimizeResize !== undefined) {
|
|
1086
|
+
this._dockPanel.optimizeResize = config.optimizeResize;
|
|
1087
|
+
}
|
|
1088
|
+
if (config.activityBarPosition !== undefined) {
|
|
1089
|
+
this._setActivityBarPosition('left', config.activityBarPosition);
|
|
1090
|
+
this._setActivityBarPosition('right', config.activityBarPosition);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Move the activity bar of a side area to a new position.
|
|
1095
|
+
*
|
|
1096
|
+
* @param side The side area to update.
|
|
1097
|
+
* @param position The new position of the activity bar.
|
|
1098
|
+
*/
|
|
1099
|
+
_setActivityBarPosition(side, position) {
|
|
1100
|
+
const handler = side === 'left' ? this._leftHandler : this._rightHandler;
|
|
1101
|
+
if (handler.position === position) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
handler.position = position;
|
|
1105
|
+
this._onLayoutModified();
|
|
974
1106
|
}
|
|
975
1107
|
/**
|
|
976
1108
|
* Returns the widgets for an application area.
|
|
977
1109
|
*/
|
|
978
1110
|
widgets(area) {
|
|
979
|
-
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
980
1111
|
switch (area !== null && area !== void 0 ? area : 'main') {
|
|
981
1112
|
case 'main':
|
|
982
1113
|
return this._dockPanel.widgets();
|
|
@@ -992,6 +1123,8 @@ export class LabShell extends Widget {
|
|
|
992
1123
|
return this._menuHandler.panel.children();
|
|
993
1124
|
case 'bottom':
|
|
994
1125
|
return this._bottomPanel.children();
|
|
1126
|
+
case 'down':
|
|
1127
|
+
return this._downPanel.stackedPanel.children();
|
|
995
1128
|
default:
|
|
996
1129
|
throw new Error(`Invalid area: ${area}`);
|
|
997
1130
|
}
|
|
@@ -1008,7 +1141,7 @@ export class LabShell extends Widget {
|
|
|
1008
1141
|
_updateTitlePanelTitle() {
|
|
1009
1142
|
let current = this.currentWidget;
|
|
1010
1143
|
const inputElement = this._titleHandler.inputElement;
|
|
1011
|
-
inputElement.value = current ? current.title
|
|
1144
|
+
inputElement.value = current ? TabBarSvg.titleLabel(current.title) : '';
|
|
1012
1145
|
inputElement.title = current ? current.title.caption : '';
|
|
1013
1146
|
}
|
|
1014
1147
|
/**
|
|
@@ -1205,10 +1338,26 @@ export class LabShell extends Widget {
|
|
|
1205
1338
|
title.iconClass = classes(title.iconClass, 'jp-Icon');
|
|
1206
1339
|
}
|
|
1207
1340
|
this._downPanel.addWidget(widget);
|
|
1208
|
-
this._onLayoutModified();
|
|
1209
1341
|
if (this._downPanel.isHidden) {
|
|
1210
|
-
this.
|
|
1342
|
+
this._showDownPanel();
|
|
1211
1343
|
}
|
|
1344
|
+
this._onLayoutModified();
|
|
1345
|
+
}
|
|
1346
|
+
_showDownPanel(size = this._lastDownAreaSize) {
|
|
1347
|
+
const downSize = size > 0.0 ? size : DEFAULT_DOWN_AREA_SIZE;
|
|
1348
|
+
this._lastDownAreaSize = downSize;
|
|
1349
|
+
this._vsplitPanel.setRelativeSizes([
|
|
1350
|
+
Math.max(1.0 - downSize, 0.0),
|
|
1351
|
+
downSize
|
|
1352
|
+
]);
|
|
1353
|
+
this._downPanel.show();
|
|
1354
|
+
}
|
|
1355
|
+
_hideDownPanel() {
|
|
1356
|
+
const size = this._vsplitPanel.relativeSizes()[1];
|
|
1357
|
+
if (size > 0.0) {
|
|
1358
|
+
this._lastDownAreaSize = size;
|
|
1359
|
+
}
|
|
1360
|
+
this._downPanel.hide();
|
|
1212
1361
|
}
|
|
1213
1362
|
/*
|
|
1214
1363
|
* Return the tab bar adjacent to the current TabBar or `null`.
|
|
@@ -1273,7 +1422,7 @@ export class LabShell extends Widget {
|
|
|
1273
1422
|
*/
|
|
1274
1423
|
_onTabPanelChanged() {
|
|
1275
1424
|
if (this._downPanel.stackedPanel.widgets.length === 0) {
|
|
1276
|
-
this.
|
|
1425
|
+
this._hideDownPanel();
|
|
1277
1426
|
}
|
|
1278
1427
|
this._onLayoutModified();
|
|
1279
1428
|
}
|
|
@@ -1401,21 +1550,36 @@ var Private;
|
|
|
1401
1550
|
/**
|
|
1402
1551
|
* Construct a new side bar handler.
|
|
1403
1552
|
*/
|
|
1404
|
-
constructor() {
|
|
1553
|
+
constructor(options) {
|
|
1405
1554
|
this._isHiddenByUser = false;
|
|
1555
|
+
this._isCollapsedByUser = false;
|
|
1406
1556
|
this._items = new Array();
|
|
1557
|
+
this._position = 'side';
|
|
1407
1558
|
this._updated = new Signal(this);
|
|
1559
|
+
this._side = options.side;
|
|
1560
|
+
this._host = options.host;
|
|
1408
1561
|
this._sideBar = new TabBar({
|
|
1409
1562
|
insertBehavior: 'none',
|
|
1410
1563
|
removeBehavior: 'none',
|
|
1411
1564
|
allowDeselect: true,
|
|
1412
1565
|
orientation: 'vertical'
|
|
1413
1566
|
});
|
|
1567
|
+
// Mirror the initial position on the bar via `data-side`. The setter
|
|
1568
|
+
// keeps it in sync on subsequent transitions.
|
|
1569
|
+
this._sideBar.node.setAttribute('data-side', this._side);
|
|
1414
1570
|
this._stackedPanel = new StackedPanel();
|
|
1571
|
+
this._area = new BoxPanel({ direction: 'top-to-bottom', spacing: 0 });
|
|
1572
|
+
// The stacked panel always lives inside the area wrapper. It is the
|
|
1573
|
+
// only stretchable child so the activity bar (when inside the wrapper)
|
|
1574
|
+
// takes only its natural size at the top or bottom.
|
|
1575
|
+
BoxPanel.setStretch(this._stackedPanel, 1);
|
|
1576
|
+
BoxPanel.setStretch(this._sideBar, 0);
|
|
1577
|
+
this._area.addWidget(this._stackedPanel);
|
|
1415
1578
|
this._sideBar.hide();
|
|
1416
1579
|
this._stackedPanel.hide();
|
|
1417
1580
|
this._lastCurrent = null;
|
|
1418
1581
|
this._sideBar.currentChanged.connect(this._onCurrentChanged, this);
|
|
1582
|
+
this._sideBar.tabCloseRequested.connect(this._onTabCloseRequested, this);
|
|
1419
1583
|
this._sideBar.tabActivateRequested.connect(this._onTabActivateRequested, this);
|
|
1420
1584
|
this._stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this);
|
|
1421
1585
|
}
|
|
@@ -1437,6 +1601,70 @@ var Private;
|
|
|
1437
1601
|
get stackedPanel() {
|
|
1438
1602
|
return this._stackedPanel;
|
|
1439
1603
|
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Get the wrapper panel for this side area.
|
|
1606
|
+
*
|
|
1607
|
+
* The wrapper always contains the stacked panel. When the activity bar is
|
|
1608
|
+
* positioned inside the area (top or bottom), it is also a child of this
|
|
1609
|
+
* wrapper. The wrapper is what gets added to the main horizontal split.
|
|
1610
|
+
*/
|
|
1611
|
+
get area() {
|
|
1612
|
+
return this._area;
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Get the current position of the activity bar.
|
|
1616
|
+
*/
|
|
1617
|
+
get position() {
|
|
1618
|
+
return this._position;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Set the position of the activity bar relative to the area.
|
|
1622
|
+
*
|
|
1623
|
+
* In `'side'` mode the activity bar is reattached to the host panel
|
|
1624
|
+
* (e.g. the shell's hboxPanel) on its natural side. In `'top'` or
|
|
1625
|
+
* `'bottom'` mode the activity bar is reparented inside the area wrapper
|
|
1626
|
+
* above or below the stacked panel and laid out horizontally.
|
|
1627
|
+
*/
|
|
1628
|
+
set position(value) {
|
|
1629
|
+
if (this._position === value) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
this._position = value;
|
|
1633
|
+
// The user-collapse flag only has meaning in horizontal mode and should
|
|
1634
|
+
// not carry over from a prior interaction in a different mode.
|
|
1635
|
+
this._isCollapsedByUser = false;
|
|
1636
|
+
// Detach the activity bar from its current parent before reattaching
|
|
1637
|
+
// it to the new one (host or area wrapper).
|
|
1638
|
+
this._sideBar.parent = null;
|
|
1639
|
+
// Update the orientation and the position-related attributes on the
|
|
1640
|
+
// activity bar. The icon/label rotation depends on these.
|
|
1641
|
+
const isLeft = this._side === 'left';
|
|
1642
|
+
this._sideBar.orientation = value === 'side' ? 'vertical' : 'horizontal';
|
|
1643
|
+
// Keep `jp-mod-left`/`jp-mod-right` for backward compatibility with
|
|
1644
|
+
// existing themes that target those classes.
|
|
1645
|
+
this._sideBar.toggleClass('jp-mod-left', isLeft && value === 'side');
|
|
1646
|
+
this._sideBar.toggleClass('jp-mod-right', !isLeft && value === 'side');
|
|
1647
|
+
// `data-side` mirrors the `data-orientation` attribute set by Lumino
|
|
1648
|
+
// and is the canonical hook for new CSS rules.
|
|
1649
|
+
const dataSide = value === 'side' ? (isLeft ? 'left' : 'right') : value;
|
|
1650
|
+
this._sideBar.node.setAttribute('data-side', dataSide);
|
|
1651
|
+
// In 'side' mode, clicking the active tab collapses the area (the
|
|
1652
|
+
// activity bar stays as a thin strip). That collapse UX makes no sense
|
|
1653
|
+
// when the activity bar is at the top or bottom — clicking would just
|
|
1654
|
+
// leave an empty horizontal strip — so disable deselection there.
|
|
1655
|
+
this._sideBar.allowDeselect = value === 'side';
|
|
1656
|
+
if (value === 'side') {
|
|
1657
|
+
const index = isLeft ? 0 : this._host.widgets.length;
|
|
1658
|
+
this._host.insertWidget(index, this._sideBar);
|
|
1659
|
+
}
|
|
1660
|
+
else if (value === 'top') {
|
|
1661
|
+
this._area.insertWidget(0, this._sideBar);
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
this._area.addWidget(this._sideBar);
|
|
1665
|
+
}
|
|
1666
|
+
this._refreshVisibility();
|
|
1667
|
+
}
|
|
1440
1668
|
/**
|
|
1441
1669
|
* Signal fires when the stack panel or the sidebar changes
|
|
1442
1670
|
*/
|
|
@@ -1460,13 +1688,20 @@ var Private;
|
|
|
1460
1688
|
*
|
|
1461
1689
|
* #### Notes
|
|
1462
1690
|
* This will open the most recently used tab, or the first tab
|
|
1463
|
-
* if there is no most recently used.
|
|
1691
|
+
* if there is no most recently used. In `'top'` or `'bottom'` mode it
|
|
1692
|
+
* also re-shows the wrapper area if a previous `collapse()` call hid it.
|
|
1464
1693
|
*/
|
|
1465
1694
|
expand() {
|
|
1695
|
+
this._isCollapsedByUser = false;
|
|
1466
1696
|
const previous = this._lastCurrent || (this._items.length > 0 && this._items[0].widget);
|
|
1467
1697
|
if (previous) {
|
|
1468
1698
|
this.activate(previous.id);
|
|
1469
1699
|
}
|
|
1700
|
+
else {
|
|
1701
|
+
// No tab to activate, but we still need to reflect the cleared
|
|
1702
|
+
// collapse flag (relevant in 'top'/'bottom' mode).
|
|
1703
|
+
this._refreshVisibility();
|
|
1704
|
+
}
|
|
1470
1705
|
}
|
|
1471
1706
|
/**
|
|
1472
1707
|
* Activate a widget residing in the side bar by ID.
|
|
@@ -1476,6 +1711,7 @@ var Private;
|
|
|
1476
1711
|
activate(id) {
|
|
1477
1712
|
const widget = this._findWidgetByID(id);
|
|
1478
1713
|
if (widget) {
|
|
1714
|
+
this._isCollapsedByUser = false;
|
|
1479
1715
|
this._sideBar.currentTitle = widget.title;
|
|
1480
1716
|
widget.activate();
|
|
1481
1717
|
}
|
|
@@ -1488,9 +1724,16 @@ var Private;
|
|
|
1488
1724
|
}
|
|
1489
1725
|
/**
|
|
1490
1726
|
* Collapse the sidebar so no items are expanded.
|
|
1727
|
+
*
|
|
1728
|
+
* #### Notes
|
|
1729
|
+
* In `'side'` mode this only deselects the active tab, leaving the
|
|
1730
|
+
* activity bar visible. In `'top'` or `'bottom'` mode it also hides the
|
|
1731
|
+
* wrapper area entirely so the column can reclaim its space.
|
|
1491
1732
|
*/
|
|
1492
1733
|
collapse() {
|
|
1734
|
+
this._isCollapsedByUser = true;
|
|
1493
1735
|
this._sideBar.currentTitle = null;
|
|
1736
|
+
this._refreshVisibility();
|
|
1494
1737
|
}
|
|
1495
1738
|
/**
|
|
1496
1739
|
* Add a widget and its title to the stacked panel and side bar.
|
|
@@ -1508,7 +1751,7 @@ var Private;
|
|
|
1508
1751
|
const title = this._sideBar.insertTab(index, widget.title);
|
|
1509
1752
|
// Store the parent id in the title dataset
|
|
1510
1753
|
// in order to dispatch click events to the right widget.
|
|
1511
|
-
title.dataset = { id: widget.id };
|
|
1754
|
+
title.dataset = { ...title.dataset, id: widget.id };
|
|
1512
1755
|
if (title.icon instanceof LabIcon) {
|
|
1513
1756
|
// bind an appropriate style to the icon
|
|
1514
1757
|
title.icon = title.icon.bindprops({
|
|
@@ -1559,15 +1802,43 @@ var Private;
|
|
|
1559
1802
|
* Rehydrate the side bar.
|
|
1560
1803
|
*/
|
|
1561
1804
|
rehydrate(data) {
|
|
1805
|
+
if (Array.isArray(data.widgets)) {
|
|
1806
|
+
const widgetIds = data.widgets.map(widget => widget.id);
|
|
1807
|
+
const widgetIdSet = new Set(widgetIds);
|
|
1808
|
+
// Add widgets that are in the saved layout but not currently
|
|
1809
|
+
// in the sidebar.
|
|
1810
|
+
const currentIds = this._stackedPanel.widgets.map(widget => widget.id);
|
|
1811
|
+
data.widgets
|
|
1812
|
+
.filter(widget => !currentIds.includes(widget.id))
|
|
1813
|
+
.forEach(widget => {
|
|
1814
|
+
this.addWidget(widget, DEFAULT_RANK);
|
|
1815
|
+
});
|
|
1816
|
+
// Merge the saved order into the current sidebar slots so widgets
|
|
1817
|
+
// absent from the saved layout keep their rank-relative positions.
|
|
1818
|
+
let savedIndex = 0;
|
|
1819
|
+
const targetIds = this._stackedPanel.widgets.map(widget => widgetIdSet.has(widget.id) ? widgetIds[savedIndex++] : widget.id);
|
|
1820
|
+
targetIds.forEach((id, targetIndex) => {
|
|
1821
|
+
const currentIndex = this._stackedPanel.widgets.findIndex(widget => widget.id === id);
|
|
1822
|
+
if (currentIndex >= 0 && currentIndex !== targetIndex) {
|
|
1823
|
+
const widget = this._stackedPanel.widgets[currentIndex];
|
|
1824
|
+
ArrayExt.move(this._items, currentIndex, targetIndex);
|
|
1825
|
+
this._stackedPanel.insertWidget(targetIndex, widget);
|
|
1826
|
+
this._sideBar.insertTab(targetIndex, widget.title);
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
if (data.visible) {
|
|
1831
|
+
this.show();
|
|
1832
|
+
}
|
|
1833
|
+
else {
|
|
1834
|
+
this.hide();
|
|
1835
|
+
}
|
|
1562
1836
|
if (data.currentWidget) {
|
|
1563
1837
|
this.activate(data.currentWidget.id);
|
|
1564
1838
|
}
|
|
1565
|
-
if (data.collapsed) {
|
|
1839
|
+
else if (data.collapsed) {
|
|
1566
1840
|
this.collapse();
|
|
1567
1841
|
}
|
|
1568
|
-
if (!data.visible) {
|
|
1569
|
-
this.hide();
|
|
1570
|
-
}
|
|
1571
1842
|
if (data.widgetStates) {
|
|
1572
1843
|
this._stackedPanel.widgets.forEach((w) => {
|
|
1573
1844
|
var _a;
|
|
@@ -1578,7 +1849,12 @@ var Private;
|
|
|
1578
1849
|
const expansion = ((_a = state.expansionStates) !== null && _a !== void 0 ? _a : [])[widx];
|
|
1579
1850
|
if (typeof expansion === 'boolean' &&
|
|
1580
1851
|
w.content instanceof AccordionPanel) {
|
|
1581
|
-
|
|
1852
|
+
if (expansion) {
|
|
1853
|
+
w.content.expand(widx);
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
w.content.collapse(widx);
|
|
1857
|
+
}
|
|
1582
1858
|
}
|
|
1583
1859
|
});
|
|
1584
1860
|
if (state.sizes) {
|
|
@@ -1600,6 +1876,7 @@ var Private;
|
|
|
1600
1876
|
*/
|
|
1601
1877
|
show() {
|
|
1602
1878
|
this._isHiddenByUser = false;
|
|
1879
|
+
this._isCollapsedByUser = false;
|
|
1603
1880
|
this._refreshVisibility();
|
|
1604
1881
|
}
|
|
1605
1882
|
/**
|
|
@@ -1632,8 +1909,34 @@ var Private;
|
|
|
1632
1909
|
* Refresh the visibility of the side bar and stacked panel.
|
|
1633
1910
|
*/
|
|
1634
1911
|
_refreshVisibility() {
|
|
1635
|
-
|
|
1912
|
+
if (this._position === 'side') {
|
|
1913
|
+
// In 'side' mode the activity bar lives outside the wrapper and the
|
|
1914
|
+
// wrapper holds only the stack panel. Hiding the stack panel when no
|
|
1915
|
+
// widget is current collapses the area down to the activity bar.
|
|
1916
|
+
this._stackedPanel.setHidden(this._sideBar.currentTitle === null);
|
|
1917
|
+
}
|
|
1918
|
+
else {
|
|
1919
|
+
// In 'top'/'bottom' mode the activity bar lives inside the wrapper
|
|
1920
|
+
// alongside the stack panel. The stack panel stays visible (and
|
|
1921
|
+
// stretches as an empty area when no widget is current) so the
|
|
1922
|
+
// activity bar stays anchored at the top or bottom of the wrapper.
|
|
1923
|
+
this._stackedPanel.setHidden(this._items.length === 0);
|
|
1924
|
+
}
|
|
1636
1925
|
this._sideBar.setHidden(this._isHiddenByUser || this._sideBar.titles.length === 0);
|
|
1926
|
+
// Hide the wrapper area so the parent split panel can reclaim its
|
|
1927
|
+
// allocated width when no contents would be visible.
|
|
1928
|
+
if (this._position === 'side') {
|
|
1929
|
+
// In 'side' mode wrapper visibility tracks the stack panel.
|
|
1930
|
+
this._area.setHidden(this._stackedPanel.isHidden);
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
// In 'top'/'bottom' mode the wrapper hides only when the user has
|
|
1934
|
+
// explicitly collapsed the side area or there are no widgets at all.
|
|
1935
|
+
// Toggling the activity bar visibility ('Show Left/Right Activity Bar')
|
|
1936
|
+
// hides only the bar — the stack panel and selected widget stay
|
|
1937
|
+
// visible inside the wrapper, matching the 'side' mode semantic.
|
|
1938
|
+
this._area.setHidden(this._isCollapsedByUser || this._sideBar.titles.length === 0);
|
|
1939
|
+
}
|
|
1637
1940
|
this._updated.emit();
|
|
1638
1941
|
}
|
|
1639
1942
|
/**
|
|
@@ -1661,6 +1964,12 @@ var Private;
|
|
|
1661
1964
|
_onTabActivateRequested(sender, args) {
|
|
1662
1965
|
args.title.owner.activate();
|
|
1663
1966
|
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Handle a `tabCloseRequested` signal from the sidebar.
|
|
1969
|
+
*/
|
|
1970
|
+
_onTabCloseRequested(sender, args) {
|
|
1971
|
+
args.title.owner.close();
|
|
1972
|
+
}
|
|
1664
1973
|
/*
|
|
1665
1974
|
* Handle the `widgetRemoved` signal from the stacked panel.
|
|
1666
1975
|
*/
|
|
@@ -1774,7 +2083,7 @@ var Private;
|
|
|
1774
2083
|
if (widget == null) {
|
|
1775
2084
|
return;
|
|
1776
2085
|
}
|
|
1777
|
-
const oldName = widget.title
|
|
2086
|
+
const oldName = TabBarSvg.titleLabel(widget.title);
|
|
1778
2087
|
const inputElement = this.inputElement;
|
|
1779
2088
|
const newName = inputElement.value;
|
|
1780
2089
|
inputElement.blur();
|