|
@@ -2,9 +2,15 @@
|
|
|
// Distributed under the terms of the Modified BSD License.
|
|
|
|
|
|
import { DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry';
|
|
|
+
|
|
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
|
|
|
|
-import { classes, DockPanelSvg, LabIcon } from '@jupyterlab/ui-components';
|
|
|
+import {
|
|
|
+ classes,
|
|
|
+ DockPanelSvg,
|
|
|
+ LabIcon,
|
|
|
+ TabPanelSvg
|
|
|
+} from '@jupyterlab/ui-components';
|
|
|
|
|
|
import { ArrayExt, find, IIterator, iter, toArray } from '@lumino/algorithm';
|
|
|
|
|
@@ -26,6 +32,7 @@ import {
|
|
|
SplitPanel,
|
|
|
StackedPanel,
|
|
|
TabBar,
|
|
|
+ TabPanel,
|
|
|
Title,
|
|
|
Widget
|
|
|
} from '@lumino/widgets';
|
|
@@ -87,7 +94,8 @@ export namespace ILabShell {
|
|
|
| 'menu'
|
|
|
| 'left'
|
|
|
| 'right'
|
|
|
- | 'bottom';
|
|
|
+ | 'bottom'
|
|
|
+ | 'down';
|
|
|
|
|
|
/**
|
|
|
* The restorable description of an area within the main dock panel.
|
|
@@ -144,6 +152,11 @@ export namespace ILabShell {
|
|
|
*/
|
|
|
readonly mainArea: IMainArea | null;
|
|
|
|
|
|
+ /**
|
|
|
+ * The down area of the user interface.
|
|
|
+ */
|
|
|
+ readonly downArea: IDownArea | null;
|
|
|
+
|
|
|
/**
|
|
|
* The left area of the user interface.
|
|
|
*/
|
|
@@ -175,6 +188,25 @@ export namespace ILabShell {
|
|
|
readonly dock: DockLayout.ILayoutConfig | null;
|
|
|
}
|
|
|
|
|
|
+ export interface IDownArea {
|
|
|
+ /**
|
|
|
+ * The current widget that has down area focus.
|
|
|
+ */
|
|
|
+ readonly currentWidget: Widget | null;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The collection of widgets held by the panel.
|
|
|
+ */
|
|
|
+ readonly widgets: Array<Widget> | null;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Vertical relative size of the down area
|
|
|
+ *
|
|
|
+ * The main area will take the rest of the height
|
|
|
+ */
|
|
|
+ readonly size: number | null;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* The restorable description of a sidebar in the user interface.
|
|
|
*/
|
|
@@ -220,10 +252,14 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
const bottomPanel = (this._bottomPanel = new BoxPanel());
|
|
|
bottomPanel.node.setAttribute('role', 'contentinfo');
|
|
|
const hboxPanel = new BoxPanel();
|
|
|
+ const vsplitPanel = (this._vsplitPanel = new Private.RestorableSplitPanel());
|
|
|
const dockPanel = (this._dockPanel = new DockPanelSvg());
|
|
|
MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
|
|
|
|
|
|
const hsplitPanel = (this._hsplitPanel = new Private.RestorableSplitPanel());
|
|
|
+ const downPanel = (this._downPanel = new TabPanelSvg({
|
|
|
+ tabsMovable: true
|
|
|
+ }));
|
|
|
const leftHandler = (this._leftHandler = new Private.SideBarHandler());
|
|
|
const rightHandler = (this._rightHandler = new Private.SideBarHandler());
|
|
|
const rootLayout = new BoxLayout();
|
|
@@ -233,8 +269,10 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
topHandler.panel.id = 'jp-top-panel';
|
|
|
bottomPanel.id = 'jp-bottom-panel';
|
|
|
hboxPanel.id = 'jp-main-content-panel';
|
|
|
+ vsplitPanel.id = 'jp-main-vsplit-panel';
|
|
|
dockPanel.id = 'jp-main-dock-panel';
|
|
|
hsplitPanel.id = 'jp-main-split-panel';
|
|
|
+ downPanel.id = 'jp-down-stack';
|
|
|
|
|
|
leftHandler.sideBar.addClass(SIDEBAR_CLASS);
|
|
|
leftHandler.sideBar.addClass('jp-mod-left');
|
|
@@ -265,15 +303,18 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
dockPanel.node.setAttribute('role', 'main');
|
|
|
|
|
|
hboxPanel.spacing = 0;
|
|
|
+ vsplitPanel.spacing = 1;
|
|
|
dockPanel.spacing = 5;
|
|
|
hsplitPanel.spacing = 1;
|
|
|
|
|
|
headerPanel.direction = 'top-to-bottom';
|
|
|
+ vsplitPanel.orientation = 'vertical';
|
|
|
hboxPanel.direction = 'left-to-right';
|
|
|
hsplitPanel.orientation = 'horizontal';
|
|
|
bottomPanel.direction = 'bottom-to-top';
|
|
|
|
|
|
SplitPanel.setStretch(leftHandler.stackedPanel, 0);
|
|
|
+ SplitPanel.setStretch(downPanel, 0);
|
|
|
SplitPanel.setStretch(dockPanel, 1);
|
|
|
SplitPanel.setStretch(rightHandler.stackedPanel, 0);
|
|
|
|
|
@@ -281,12 +322,17 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
BoxPanel.setStretch(hsplitPanel, 1);
|
|
|
BoxPanel.setStretch(rightHandler.sideBar, 0);
|
|
|
|
|
|
+ SplitPanel.setStretch(vsplitPanel, 1);
|
|
|
+
|
|
|
hsplitPanel.addWidget(leftHandler.stackedPanel);
|
|
|
hsplitPanel.addWidget(dockPanel);
|
|
|
hsplitPanel.addWidget(rightHandler.stackedPanel);
|
|
|
|
|
|
+ vsplitPanel.addWidget(hsplitPanel);
|
|
|
+ vsplitPanel.addWidget(downPanel);
|
|
|
+
|
|
|
hboxPanel.addWidget(leftHandler.sideBar);
|
|
|
- hboxPanel.addWidget(hsplitPanel);
|
|
|
+ hboxPanel.addWidget(vsplitPanel);
|
|
|
hboxPanel.addWidget(rightHandler.sideBar);
|
|
|
|
|
|
rootLayout.direction = 'top-to-bottom';
|
|
@@ -294,6 +340,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
// Use relative sizing to set the width of the side panels.
|
|
|
// This will still respect the min-size of children widget in the stacked
|
|
|
// panel. The default sizes will be overwritten during layout restoration.
|
|
|
+ vsplitPanel.setRelativeSizes([3, 1]);
|
|
|
hsplitPanel.setRelativeSizes([1, 2.5, 1]);
|
|
|
|
|
|
BoxLayout.setStretch(headerPanel, 0);
|
|
@@ -310,6 +357,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
// initially hiding header and bottom panel when no elements inside,
|
|
|
this._headerPanel.hide();
|
|
|
this._bottomPanel.hide();
|
|
|
+ this._downPanel.hide();
|
|
|
|
|
|
this.layout = rootLayout;
|
|
|
|
|
@@ -320,6 +368,17 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
// Connect main layout change listener.
|
|
|
this._dockPanel.layoutModified.connect(this._onLayoutModified, this);
|
|
|
|
|
|
+ // Connect vsplit layout change listener
|
|
|
+ this._vsplitPanel.updated.connect(this._onLayoutModified, this);
|
|
|
+
|
|
|
+ // Connect down panel change listeners
|
|
|
+ this._downPanel.currentChanged.connect(this._onLayoutModified, this);
|
|
|
+ this._downPanel.tabBar.tabMoved.connect(this._onTabPanelChanged, this);
|
|
|
+ this._downPanel.stackedPanel.widgetRemoved.connect(
|
|
|
+ this._onTabPanelChanged,
|
|
|
+ this
|
|
|
+ );
|
|
|
+
|
|
|
// Catch current changed events on the side handlers.
|
|
|
this._leftHandler.sideBar.currentChanged.connect(
|
|
|
this._onLayoutModified,
|
|
@@ -473,9 +532,11 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
|
|
|
const applicationCurrentWidget = this.currentWidget;
|
|
|
|
|
|
+ // Toggle back to multiple document mode.
|
|
|
+ dock.mode = mode;
|
|
|
+
|
|
|
if (mode === 'single-document') {
|
|
|
this._cachedLayout = dock.saveLayout();
|
|
|
- dock.mode = mode;
|
|
|
|
|
|
// In case the active widget in the dock panel is *not* the active widget
|
|
|
// of the application, defer to the application.
|
|
@@ -483,62 +544,53 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
dock.activateWidget(this.currentWidget);
|
|
|
}
|
|
|
|
|
|
- // Set the mode data attribute on the application shell node.
|
|
|
- this.node.dataset.shellMode = mode;
|
|
|
-
|
|
|
// Adjust menu and title
|
|
|
- // this.add(this._menuHandler.panel, 'top', {rank: 100});
|
|
|
(this.layout as BoxLayout).insertWidget(2, this._menuHandler.panel);
|
|
|
this._titleWidgetHandler.show();
|
|
|
this._updateTitlePanelTitle();
|
|
|
+ } else {
|
|
|
+ // Cache a reference to every widget currently in the dock panel.
|
|
|
+ const widgets = toArray(dock.widgets());
|
|
|
+
|
|
|
+ // Restore the original layout.
|
|
|
+ if (this._cachedLayout) {
|
|
|
+ // Remove any disposed widgets in the cached layout and restore.
|
|
|
+ Private.normalizeAreaConfig(dock, this._cachedLayout.main);
|
|
|
+ dock.restoreLayout(this._cachedLayout);
|
|
|
+ this._cachedLayout = null;
|
|
|
+ }
|
|
|
|
|
|
- this._modeChanged.emit(mode);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Cache a reference to every widget currently in the dock panel.
|
|
|
- const widgets = toArray(dock.widgets());
|
|
|
-
|
|
|
- // Toggle back to multiple document mode.
|
|
|
- dock.mode = mode;
|
|
|
+ // Add any widgets created during single document mode, which have
|
|
|
+ // subsequently been removed from the dock panel after the multiple document
|
|
|
+ // layout has been restored. If the widget has add options cached for
|
|
|
+ // the widget (i.e., if it has been placed with respect to another widget),
|
|
|
+ // then take that into account.
|
|
|
+ widgets.forEach(widget => {
|
|
|
+ if (!widget.parent) {
|
|
|
+ this._addToMainArea(widget, {
|
|
|
+ ...this._mainOptionsCache.get(widget),
|
|
|
+ activate: false
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this._mainOptionsCache.clear();
|
|
|
|
|
|
- // Restore the original layout.
|
|
|
- if (this._cachedLayout) {
|
|
|
- // Remove any disposed widgets in the cached layout and restore.
|
|
|
- Private.normalizeAreaConfig(dock, this._cachedLayout.main);
|
|
|
- dock.restoreLayout(this._cachedLayout);
|
|
|
- this._cachedLayout = null;
|
|
|
- }
|
|
|
-
|
|
|
- // Add any widgets created during single document mode, which have
|
|
|
- // subsequently been removed from the dock panel after the multiple document
|
|
|
- // layout has been restored. If the widget has add options cached for
|
|
|
- // the widget (i.e., if it has been placed with respect to another widget),
|
|
|
- // then take that into account.
|
|
|
- widgets.forEach(widget => {
|
|
|
- if (!widget.parent) {
|
|
|
- this._addToMainArea(widget, {
|
|
|
- ...this._mainOptionsCache.get(widget),
|
|
|
- activate: false
|
|
|
- });
|
|
|
+ // In case the active widget in the dock panel is *not* the active widget
|
|
|
+ // of the application, defer to the application.
|
|
|
+ if (applicationCurrentWidget) {
|
|
|
+ dock.activateWidget(applicationCurrentWidget);
|
|
|
}
|
|
|
- });
|
|
|
- this._mainOptionsCache.clear();
|
|
|
|
|
|
- // In case the active widget in the dock panel is *not* the active widget
|
|
|
- // of the application, defer to the application.
|
|
|
- if (applicationCurrentWidget) {
|
|
|
- dock.activateWidget(applicationCurrentWidget);
|
|
|
+ // Adjust menu and title
|
|
|
+ this.add(this._menuHandler.panel, 'top', { rank: 100 });
|
|
|
+ // this._topHandler.addWidget(this._menuHandler.panel, 100)
|
|
|
+ this._titleWidgetHandler.hide();
|
|
|
}
|
|
|
|
|
|
// Set the mode data attribute on the applications shell node.
|
|
|
this.node.dataset.shellMode = mode;
|
|
|
|
|
|
- // Adjust menu and title
|
|
|
- this.add(this._menuHandler.panel, 'top', { rank: 100 });
|
|
|
- // this._topHandler.addWidget(this._menuHandler.panel, 100)
|
|
|
- this._titleWidgetHandler.hide();
|
|
|
-
|
|
|
+ this._downPanel.fit();
|
|
|
// Emit the mode changed signal
|
|
|
this._modeChanged.emit(mode);
|
|
|
}
|
|
@@ -565,6 +617,14 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ const tabIndex = this._downPanel.tabBar.titles.findIndex(
|
|
|
+ title => title.owner.id === id
|
|
|
+ );
|
|
|
+ if (tabIndex >= 0) {
|
|
|
+ this._downPanel.currentIndex = tabIndex;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
const dock = this._dockPanel;
|
|
|
const widget = find(dock.widgets(), value => value.id === id);
|
|
|
|
|
@@ -670,20 +730,22 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
options?: DocumentRegistry.IOpenOptions
|
|
|
): void {
|
|
|
switch (area || 'main') {
|
|
|
- case 'main':
|
|
|
- return this._addToMainArea(widget, options);
|
|
|
+ case 'bottom':
|
|
|
+ return this._addToBottomArea(widget, options);
|
|
|
+ case 'down':
|
|
|
+ return this._addToDownArea(widget, options);
|
|
|
+ case 'header':
|
|
|
+ return this._addToHeaderArea(widget, options);
|
|
|
case 'left':
|
|
|
return this._addToLeftArea(widget, options);
|
|
|
+ case 'main':
|
|
|
+ return this._addToMainArea(widget, options);
|
|
|
+ case 'menu':
|
|
|
+ return this._addToMenuArea(widget, options);
|
|
|
case 'right':
|
|
|
return this._addToRightArea(widget, options);
|
|
|
- case 'header':
|
|
|
- return this._addToHeaderArea(widget, options);
|
|
|
case 'top':
|
|
|
return this._addToTopArea(widget, options);
|
|
|
- case 'menu':
|
|
|
- return this._addToMenuArea(widget, options);
|
|
|
- case 'bottom':
|
|
|
- return this._addToBottomArea(widget, options);
|
|
|
default:
|
|
|
throw new Error(`Invalid area: ${area}`);
|
|
|
}
|
|
@@ -742,13 +804,15 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Close all widgets in the main area.
|
|
|
+ * Close all widgets in the main and down area.
|
|
|
*/
|
|
|
closeAll(): void {
|
|
|
// Make a copy of all the widget in the dock panel (using `toArray()`)
|
|
|
// before removing them because removing them while iterating through them
|
|
|
// modifies the underlying data of the iterator.
|
|
|
toArray(this._dockPanel.widgets()).forEach(widget => widget.close());
|
|
|
+
|
|
|
+ this._downPanel.stackedPanel.widgets.forEach(widget => widget.close());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -756,20 +820,22 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
*/
|
|
|
isEmpty(area: ILabShell.Area): boolean {
|
|
|
switch (area) {
|
|
|
+ case 'bottom':
|
|
|
+ return this._bottomPanel.widgets.length === 0;
|
|
|
+ case 'down':
|
|
|
+ return this._downPanel.stackedPanel.widgets.length === 0;
|
|
|
+ case 'header':
|
|
|
+ return this._headerPanel.widgets.length === 0;
|
|
|
case 'left':
|
|
|
return this._leftHandler.stackedPanel.widgets.length === 0;
|
|
|
case 'main':
|
|
|
return this._dockPanel.isEmpty;
|
|
|
- case 'header':
|
|
|
- return this._headerPanel.widgets.length === 0;
|
|
|
- case 'top':
|
|
|
- return this._topHandler.panel.widgets.length === 0;
|
|
|
case 'menu':
|
|
|
return this._menuHandler.panel.widgets.length === 0;
|
|
|
- case 'bottom':
|
|
|
- return this._bottomPanel.widgets.length === 0;
|
|
|
case 'right':
|
|
|
return this._rightHandler.stackedPanel.widgets.length === 0;
|
|
|
+ case 'top':
|
|
|
+ return this._topHandler.panel.widgets.length === 0;
|
|
|
default:
|
|
|
return true;
|
|
|
}
|
|
@@ -779,7 +845,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
* Restore the layout state for the application shell.
|
|
|
*/
|
|
|
restoreLayout(mode: DockPanel.Mode, layout: ILabShell.ILayout): void {
|
|
|
- const { mainArea, leftArea, rightArea, relativeSizes } = layout;
|
|
|
+ const { mainArea, downArea, leftArea, rightArea, relativeSizes } = layout;
|
|
|
// Rehydrate the main area.
|
|
|
if (mainArea) {
|
|
|
const { currentWidget, dock } = mainArea;
|
|
@@ -800,6 +866,56 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Rehydrate the down area
|
|
|
+ if (downArea) {
|
|
|
+ const { currentWidget, widgets, size } = downArea;
|
|
|
+
|
|
|
+ const widgetIds = widgets?.map(widget => widget.id) ?? [];
|
|
|
+ // Remove absent widgets
|
|
|
+ this._downPanel.tabBar.titles
|
|
|
+ .filter(title => !widgetIds.includes(title.owner.id))
|
|
|
+ .map(title => title.owner.close());
|
|
|
+ // Add new widgets
|
|
|
+ const titleIds = this._downPanel.tabBar.titles.map(
|
|
|
+ title => title.owner.id
|
|
|
+ );
|
|
|
+ widgets
|
|
|
+ ?.filter(widget => !titleIds.includes(widget.id))
|
|
|
+ .map(widget => this._downPanel.addWidget(widget));
|
|
|
+ // Reorder tabs
|
|
|
+ while (
|
|
|
+ !ArrayExt.shallowEqual(
|
|
|
+ widgetIds,
|
|
|
+ this._downPanel.tabBar.titles.map(title => title.owner.id)
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ this._downPanel.tabBar.titles.forEach((title, index) => {
|
|
|
+ const position = widgetIds.findIndex(id => title.owner.id == id);
|
|
|
+ if (position >= 0 && position != index) {
|
|
|
+ this._downPanel.tabBar.insertTab(position, title);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentWidget) {
|
|
|
+ const index = this._downPanel.stackedPanel.widgets.findIndex(
|
|
|
+ widget => widget.id === currentWidget.id
|
|
|
+ );
|
|
|
+ if (index) {
|
|
|
+ this._downPanel.currentIndex = index;
|
|
|
+ this._downPanel.currentWidget?.activate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (size && size > 0.0) {
|
|
|
+ this._vsplitPanel.setRelativeSizes([1.0 - size, size]);
|
|
|
+ } else {
|
|
|
+ // Close all tabs and hide the panel
|
|
|
+ this._downPanel.stackedPanel.widgets.forEach(widget => widget.close());
|
|
|
+ this._downPanel.hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// Rehydrate the left area.
|
|
|
if (leftArea) {
|
|
|
this._leftHandler.rehydrate(leftArea);
|
|
@@ -846,10 +962,16 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
? this._cachedLayout || this._dockPanel.saveLayout()
|
|
|
: this._dockPanel.saveLayout()
|
|
|
},
|
|
|
+ downArea: {
|
|
|
+ currentWidget: this._downPanel.currentWidget,
|
|
|
+ widgets: toArray(this._downPanel.stackedPanel.widgets),
|
|
|
+ size: this._vsplitPanel.relativeSizes()[1]
|
|
|
+ },
|
|
|
leftArea: this._leftHandler.dehydrate(),
|
|
|
rightArea: this._rightHandler.dehydrate(),
|
|
|
relativeSizes: this._hsplitPanel.relativeSizes()
|
|
|
};
|
|
|
+
|
|
|
return layout;
|
|
|
}
|
|
|
|
|
@@ -1106,6 +1228,40 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private _addToDownArea(
|
|
|
+ widget: Widget,
|
|
|
+ options?: DocumentRegistry.IOpenOptions
|
|
|
+ ): void {
|
|
|
+ if (!widget.id) {
|
|
|
+ console.error('Widgets added to app shell must have unique id property.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ options = options || {};
|
|
|
+
|
|
|
+ const { title } = widget;
|
|
|
+ // Add widget ID to tab so that we can get a handle on the tab's widget
|
|
|
+ // (for context menu support)
|
|
|
+ title.dataset = { ...title.dataset, id: widget.id };
|
|
|
+
|
|
|
+ if (title.icon instanceof LabIcon) {
|
|
|
+ // bind an appropriate style to the icon
|
|
|
+ title.icon = title.icon.bindprops({
|
|
|
+ stylesheet: 'mainAreaTab'
|
|
|
+ });
|
|
|
+ } else if (typeof title.icon === 'string' || !title.icon) {
|
|
|
+ // add some classes to help with displaying css background imgs
|
|
|
+ title.iconClass = classes(title.iconClass, 'jp-Icon');
|
|
|
+ }
|
|
|
+
|
|
|
+ this._downPanel.addWidget(widget);
|
|
|
+ this._onLayoutModified();
|
|
|
+
|
|
|
+ if (this._downPanel.isHidden) {
|
|
|
+ this._downPanel.show();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Return the tab bar adjacent to the current TabBar or `null`.
|
|
|
*/
|
|
@@ -1184,6 +1340,16 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
this._onLayoutModified();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Handle a change on the down panel widgets
|
|
|
+ */
|
|
|
+ private _onTabPanelChanged(): void {
|
|
|
+ if (this._downPanel.stackedPanel.widgets.length === 0) {
|
|
|
+ this._downPanel.hide();
|
|
|
+ }
|
|
|
+ this._onLayoutModified();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Handle a change to the layout.
|
|
|
*/
|
|
@@ -1223,6 +1389,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
>(this);
|
|
|
private _modeChanged = new Signal<this, DockPanel.Mode>(this);
|
|
|
private _dockPanel: DockPanel;
|
|
|
+ private _downPanel: TabPanel;
|
|
|
private _isRestored = false;
|
|
|
private _layoutModified = new Signal<this, void>(this);
|
|
|
private _layoutDebouncer = new Debouncer(() => {
|
|
@@ -1234,6 +1401,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
private _tracker = new FocusTracker<Widget>();
|
|
|
private _headerPanel: Panel;
|
|
|
private _hsplitPanel: Private.RestorableSplitPanel;
|
|
|
+ private _vsplitPanel: Private.RestorableSplitPanel;
|
|
|
private _topHandler: Private.PanelHandler;
|
|
|
private _menuHandler: Private.PanelHandler;
|
|
|
private _skipLinkWidgetHandler: Private.SkipLinkWidgetHandler;
|