Merge pull request #6487 from brichet/left-right-widgets
Left right panel in Notebook view
22
app/index.js
@ -83,6 +83,7 @@ async function main() {
|
||||
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
|
||||
[
|
||||
'@jupyterlab/apputils-extension:palette',
|
||||
'@jupyter/apputils-extension:sanitizer',
|
||||
'@jupyterlab/apputils-extension:settings',
|
||||
'@jupyterlab/apputils-extension:state',
|
||||
'@jupyterlab/apputils-extension:themes',
|
||||
@ -175,11 +176,30 @@ async function main() {
|
||||
}
|
||||
case 'notebooks': {
|
||||
baseMods = baseMods.concat([
|
||||
require('@jupyterlab/celltags-extension'),
|
||||
require('@jupyterlab/cell-toolbar-extension'),
|
||||
require('@jupyterlab/debugger-extension').default.filter(({ id }) =>
|
||||
[
|
||||
'@jupyterlab/debugger-extension:config',
|
||||
'@jupyterlab/debugger-extension:main',
|
||||
'@jupyterlab/debugger-extension:notebooks',
|
||||
'@jupyterlab/debugger-extension:service',
|
||||
'@jupyterlab/debugger-extension:sidebar',
|
||||
'@jupyterlab/debugger-extension:sources'
|
||||
].includes(id)
|
||||
),
|
||||
require('@jupyterlab/notebook-extension').default.filter(({ id }) =>
|
||||
[
|
||||
'@jupyterlab/notebook-extension:completer',
|
||||
'@jupyterlab/notebook-extension:search'
|
||||
'@jupyterlab/notebook-extension:search',
|
||||
'@jupyterlab/notebook-extension:toc',
|
||||
'@jupyterlab/notebook-extension:tools'
|
||||
].includes(id)
|
||||
),
|
||||
require('@jupyterlab/toc-extension').default.filter(({ id }) =>
|
||||
[
|
||||
'@jupyterlab/toc-extension:registry',
|
||||
'@jupyterlab/toc-extension:tracker'
|
||||
].includes(id)
|
||||
),
|
||||
require('@jupyterlab/tooltip-extension').default.filter(({ id }) =>
|
||||
|
@ -29,6 +29,7 @@
|
||||
"@jupyterlab/cell-toolbar": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/cell-toolbar-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/celltags": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/celltags-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/codeeditor": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/codemirror-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/collaboration": "~4.0.0-alpha.14",
|
||||
@ -76,6 +77,7 @@
|
||||
"@jupyterlab/terminal-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/theme-dark-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/theme-light-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/toc-extension": "~6.0.0-alpha.14",
|
||||
"@jupyterlab/tooltip": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/tooltip-extension": "~4.0.0-alpha.14",
|
||||
"@jupyterlab/translation": "~4.0.0-alpha.14",
|
||||
@ -114,11 +116,13 @@
|
||||
"@jupyterlab/apputils-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/cell-toolbar-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/celltags": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/celltags-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/codemirror-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/collaboration-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/completer-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/console-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/coreutils": "^6.0.0-alpha.14",
|
||||
"@jupyterlab/debugger-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/docmanager-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/docprovider-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/documentsearch-extension": "^4.0.0-alpha.14",
|
||||
@ -140,6 +144,7 @@
|
||||
"@jupyterlab/terminal-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/theme-dark-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/theme-light-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/toc-extension": "^6.0.0-alpha.14",
|
||||
"@jupyterlab/tooltip-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/translation-extension": "^4.0.0-alpha.14",
|
||||
"@jupyterlab/vega5-extension": "^4.0.0-alpha.14"
|
||||
@ -181,6 +186,7 @@
|
||||
"@jupyterlab/codemirror-extension",
|
||||
"@jupyterlab/completer-extension",
|
||||
"@jupyterlab/console-extension",
|
||||
"@jupyterlab/debugger-extension",
|
||||
"@jupyterlab/docmanager-extension",
|
||||
"@jupyterlab/documentsearch-extension",
|
||||
"@jupyterlab/filebrowser-extension",
|
||||
@ -211,6 +217,7 @@
|
||||
"@jupyterlab/completer",
|
||||
"@jupyterlab/console",
|
||||
"@jupyterlab/coreutils",
|
||||
"@jupyterlab/debugger",
|
||||
"@jupyterlab/docmanager",
|
||||
"@jupyterlab/docprovider",
|
||||
"@jupyterlab/documentsearch",
|
||||
|
@ -35,16 +35,24 @@ import { ITranslator } from '@jupyterlab/translation';
|
||||
import {
|
||||
NotebookApp,
|
||||
NotebookShell,
|
||||
INotebookShell
|
||||
INotebookShell,
|
||||
SideBarPanel,
|
||||
SideBarHandler
|
||||
} from '@jupyter-notebook/application';
|
||||
|
||||
import { jupyterIcon } from '@jupyter-notebook/ui-components';
|
||||
|
||||
import { PromiseDelegate } from '@lumino/coreutils';
|
||||
|
||||
import { DisposableDelegate, DisposableSet } from '@lumino/disposable';
|
||||
import {
|
||||
DisposableDelegate,
|
||||
DisposableSet,
|
||||
IDisposable
|
||||
} from '@lumino/disposable';
|
||||
|
||||
import { Widget } from '@lumino/widgets';
|
||||
import { Menu, Widget } from '@lumino/widgets';
|
||||
|
||||
import { SideBarPalette } from './sidebarpalette';
|
||||
|
||||
/**
|
||||
* A regular expression to match path to notebooks and documents
|
||||
@ -65,6 +73,11 @@ namespace CommandIDs {
|
||||
*/
|
||||
export const toggleTop = 'application:toggle-top';
|
||||
|
||||
/**
|
||||
* Toggle sidebar visibility
|
||||
*/
|
||||
export const togglePanel = 'application:toggle-panel';
|
||||
|
||||
/**
|
||||
* Toggle the Zen mode
|
||||
*/
|
||||
@ -173,7 +186,7 @@ const opener: JupyterFrontEndPlugin<void> = {
|
||||
const file = decodeURIComponent(path);
|
||||
const urlParams = new URLSearchParams(parsed.search);
|
||||
const factory = urlParams.get('factory') ?? 'default';
|
||||
app.restored.then(async () => {
|
||||
app.started.then(async () => {
|
||||
docManager.open(file, factory, undefined, {
|
||||
ref: '_noref'
|
||||
});
|
||||
@ -187,8 +200,6 @@ const opener: JupyterFrontEndPlugin<void> = {
|
||||
|
||||
/**
|
||||
* A plugin to customize menus
|
||||
*
|
||||
* TODO: use this plugin to customize the menu items and their order
|
||||
*/
|
||||
const menus: JupyterFrontEndPlugin<void> = {
|
||||
id: '@jupyter-notebook/application-extension:menus',
|
||||
@ -557,6 +568,219 @@ const topVisibility: JupyterFrontEndPlugin<void> = {
|
||||
autoStart: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin to toggle the left or right sidebar's visibility.
|
||||
*/
|
||||
const sidebarVisibility: JupyterFrontEndPlugin<void> = {
|
||||
id: '@jupyter-notebook/application-extension:sidebar',
|
||||
requires: [INotebookShell, ITranslator],
|
||||
optional: [IMainMenu, ICommandPalette],
|
||||
autoStart: true,
|
||||
activate: (
|
||||
app: JupyterFrontEnd<JupyterFrontEnd.IShell>,
|
||||
notebookShell: INotebookShell,
|
||||
translator: ITranslator,
|
||||
menu: IMainMenu | null,
|
||||
palette: ICommandPalette | null
|
||||
) => {
|
||||
const trans = translator.load('notebook');
|
||||
|
||||
/* Arguments for togglePanel command:
|
||||
* side, left or right area
|
||||
* title, widget title to show in the menu
|
||||
* id, widget ID to activate in the sidebar
|
||||
*/
|
||||
app.commands.addCommand(CommandIDs.togglePanel, {
|
||||
label: args => args['title'] as string,
|
||||
caption: args => {
|
||||
// We do not substitute the parameter into the string because the parameter is not
|
||||
// localized (e.g., it is always 'left') even though the string is localized.
|
||||
if (args['side'] === 'left') {
|
||||
return trans.__(
|
||||
'Show %1 in the left sidebar',
|
||||
args['title'] as string
|
||||
);
|
||||
} else if (args['side'] === 'right') {
|
||||
return trans.__(
|
||||
'Show %1 in the right sidebar',
|
||||
args['title'] as string
|
||||
);
|
||||
}
|
||||
return trans.__('Show %1 in the sidebar', args['title'] as string);
|
||||
},
|
||||
execute: args => {
|
||||
switch (args['side'] as string) {
|
||||
case 'left':
|
||||
if (notebookShell.leftCollapsed) {
|
||||
notebookShell.expandLeft(args.id as string);
|
||||
} else if (
|
||||
notebookShell.leftHandler.currentWidget?.id !== args.id
|
||||
) {
|
||||
notebookShell.expandLeft(args.id as string);
|
||||
} else {
|
||||
notebookShell.collapseLeft();
|
||||
if (notebookShell.currentWidget) {
|
||||
notebookShell.activateById(notebookShell.currentWidget.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'right':
|
||||
if (notebookShell.rightCollapsed) {
|
||||
notebookShell.expandRight(args.id as string);
|
||||
} else if (
|
||||
notebookShell.rightHandler.currentWidget?.id !== args.id
|
||||
) {
|
||||
notebookShell.expandRight(args.id as string);
|
||||
} else {
|
||||
notebookShell.collapseRight();
|
||||
if (notebookShell.currentWidget) {
|
||||
notebookShell.activateById(notebookShell.currentWidget.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
isToggled: args => {
|
||||
switch (args['side'] as string) {
|
||||
case 'left': {
|
||||
if (notebookShell.leftCollapsed) {
|
||||
return false;
|
||||
}
|
||||
const currentWidget = notebookShell.leftHandler.currentWidget;
|
||||
if (!currentWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentWidget.id === (args['id'] as string);
|
||||
}
|
||||
case 'right': {
|
||||
if (notebookShell.rightCollapsed) {
|
||||
return false;
|
||||
}
|
||||
const currentWidget = notebookShell.rightHandler.currentWidget;
|
||||
if (!currentWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentWidget.id === (args['id'] as string);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const sideBarMenu: { [area in SideBarPanel.Area]: IDisposable | null } = {
|
||||
left: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
/**
|
||||
* The function which adds entries to the View menu for each widget of a sidebar.
|
||||
*
|
||||
* @param area - 'left' or 'right', the area of the side bar.
|
||||
* @param entryLabel - the name of the main entry in the View menu for that sidebar.
|
||||
* @returns - The disposable menu added to the View menu or null.
|
||||
*/
|
||||
const updateMenu = (area: SideBarPanel.Area, entryLabel: string) => {
|
||||
if (menu === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove the previous menu entry for this sidebar.
|
||||
sideBarMenu[area]?.dispose();
|
||||
|
||||
// Creates a new menu entry and populates it with sidebar widgets.
|
||||
const newMenu = new Menu({ commands: app.commands });
|
||||
newMenu.title.label = entryLabel;
|
||||
const widgets = notebookShell.widgets(area);
|
||||
let menuToAdd = false;
|
||||
|
||||
for (let widget of widgets) {
|
||||
newMenu.addItem({
|
||||
command: CommandIDs.togglePanel,
|
||||
args: {
|
||||
side: area,
|
||||
title: `Show ${widget.title.caption}`,
|
||||
id: widget.id
|
||||
}
|
||||
});
|
||||
menuToAdd = true;
|
||||
}
|
||||
|
||||
// If there are widgets, add the menu to the main menu entry.
|
||||
if (menuToAdd) {
|
||||
sideBarMenu[area] = menu.viewMenu.addItem({
|
||||
type: 'submenu',
|
||||
submenu: newMenu
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
app.restored.then(() => {
|
||||
// Create menu entries for the left and right panel.
|
||||
if (menu) {
|
||||
const getSideBarLabel = (area: SideBarPanel.Area): string => {
|
||||
if (area === 'left') {
|
||||
return trans.__(`Left Sidebar`);
|
||||
} else {
|
||||
return trans.__(`Right Sidebar`);
|
||||
}
|
||||
};
|
||||
const leftArea = notebookShell.leftHandler.area;
|
||||
const leftLabel = getSideBarLabel(leftArea);
|
||||
updateMenu(leftArea, leftLabel);
|
||||
|
||||
const rightArea = notebookShell.rightHandler.area;
|
||||
const rightLabel = getSideBarLabel(rightArea);
|
||||
updateMenu(rightArea, rightLabel);
|
||||
|
||||
const handleSideBarChange = (
|
||||
sidebar: SideBarHandler,
|
||||
widget: Widget
|
||||
) => {
|
||||
const label = getSideBarLabel(sidebar.area);
|
||||
updateMenu(sidebar.area, label);
|
||||
};
|
||||
|
||||
notebookShell.leftHandler.widgetAdded.connect(handleSideBarChange);
|
||||
notebookShell.leftHandler.widgetRemoved.connect(handleSideBarChange);
|
||||
notebookShell.rightHandler.widgetAdded.connect(handleSideBarChange);
|
||||
notebookShell.rightHandler.widgetRemoved.connect(handleSideBarChange);
|
||||
}
|
||||
|
||||
// Add palette entries for side panels.
|
||||
if (palette) {
|
||||
const sideBarPalette = new SideBarPalette({
|
||||
commandPalette: palette as ICommandPalette,
|
||||
command: CommandIDs.togglePanel
|
||||
});
|
||||
|
||||
notebookShell.leftHandler.widgets.forEach(widget => {
|
||||
sideBarPalette.addItem(widget, notebookShell.leftHandler.area);
|
||||
});
|
||||
|
||||
notebookShell.rightHandler.widgets.forEach(widget => {
|
||||
sideBarPalette.addItem(widget, notebookShell.rightHandler.area);
|
||||
});
|
||||
|
||||
// Update menu and palette when widgets are added or removed from sidebars.
|
||||
notebookShell.leftHandler.widgetAdded.connect((sidebar, widget) => {
|
||||
sideBarPalette.addItem(widget, sidebar.area);
|
||||
});
|
||||
notebookShell.leftHandler.widgetRemoved.connect((sidebar, widget) => {
|
||||
sideBarPalette.removeItem(widget, sidebar.area);
|
||||
});
|
||||
notebookShell.rightHandler.widgetAdded.connect((sidebar, widget) => {
|
||||
sideBarPalette.addItem(widget, sidebar.area);
|
||||
});
|
||||
notebookShell.rightHandler.widgetRemoved.connect((sidebar, widget) => {
|
||||
sideBarPalette.removeItem(widget, sidebar.area);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The default tree route resolver plugin.
|
||||
*/
|
||||
@ -711,6 +935,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
|
||||
router,
|
||||
sessionDialogs,
|
||||
shell,
|
||||
sidebarVisibility,
|
||||
status,
|
||||
tabTitle,
|
||||
title,
|
||||
|
114
packages/application-extension/src/sidebarpalette.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { ICommandPalette } from '@jupyterlab/apputils';
|
||||
import { IDisposable } from '@lumino/disposable';
|
||||
import { Widget } from '@lumino/widgets';
|
||||
|
||||
/**
|
||||
* A class to manages the palette entries associated to the side bar.
|
||||
*/
|
||||
export class SideBarPalette {
|
||||
/**
|
||||
* Construct a new side bar palette.
|
||||
*/
|
||||
constructor(options: SideBarPaletteOption) {
|
||||
this._commandPalette = options.commandPalette;
|
||||
this._command = options.command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a command palette item from the widget id and the area.
|
||||
*/
|
||||
getItem(
|
||||
widget: Readonly<Widget>,
|
||||
area: 'left' | 'right'
|
||||
): SideBarPaletteItem | null {
|
||||
const itemList = this._items;
|
||||
for (let i = 0; i < itemList.length; i++) {
|
||||
const item = itemList[i];
|
||||
if (item.widgetId == widget.id && item.area == area) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the command palette.
|
||||
*/
|
||||
addItem(widget: Readonly<Widget>, area: 'left' | 'right'): void {
|
||||
// Check if the item does not already exist.
|
||||
if (this.getItem(widget, area)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a new item in command palette.
|
||||
const disposableDelegate = this._commandPalette.addItem({
|
||||
command: this._command,
|
||||
category: 'View',
|
||||
args: {
|
||||
side: area,
|
||||
title: `Show ${widget.title.caption}`,
|
||||
id: widget.id
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the disposableDelegate objet to be able to dispose of the item if the widget
|
||||
// is remove from the side bar.
|
||||
this._items.push({
|
||||
widgetId: widget.id,
|
||||
area: area,
|
||||
disposable: disposableDelegate
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the command palette.
|
||||
*/
|
||||
removeItem(widget: Readonly<Widget>, area: 'left' | 'right'): void {
|
||||
const item = this.getItem(widget, area);
|
||||
if (item) {
|
||||
item.disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_command: string;
|
||||
_commandPalette: ICommandPalette;
|
||||
_items: SideBarPaletteItem[] = [];
|
||||
}
|
||||
|
||||
type SideBarPaletteItem = {
|
||||
/**
|
||||
* The ID of the widget associated to the command palette.
|
||||
*/
|
||||
widgetId: string;
|
||||
|
||||
/**
|
||||
* The area of the panel associated to the command palette.
|
||||
*/
|
||||
area: 'left' | 'right';
|
||||
|
||||
/**
|
||||
* The disposable object to remove the item from command palette.
|
||||
*/
|
||||
disposable: IDisposable;
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface for the options to include in SideBarPalette constructor.
|
||||
*/
|
||||
type SideBarPaletteOption = {
|
||||
/**
|
||||
* The commands palette.
|
||||
*/
|
||||
commandPalette: ICommandPalette;
|
||||
|
||||
/**
|
||||
* The command to call from each sidebar menu entry.
|
||||
*
|
||||
* ### Notes
|
||||
* That command required 3 args :
|
||||
* side: 'left' | 'right', the area to toggle
|
||||
* title: string, label of the command
|
||||
* id: string, id of the widget to activate
|
||||
*/
|
||||
command: string;
|
||||
};
|
@ -34,6 +34,9 @@ export class NotebookApp extends JupyterFrontEnd<INotebookShell> {
|
||||
this.registerPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
this.restored = this.shell.restored;
|
||||
|
||||
this.restored.then(() => this._formatter.invoke());
|
||||
}
|
||||
|
||||
@ -52,6 +55,11 @@ export class NotebookApp extends JupyterFrontEnd<INotebookShell> {
|
||||
*/
|
||||
readonly status = new LabStatus(this);
|
||||
|
||||
/**
|
||||
* Promise that resolves when the state is first restored
|
||||
*/
|
||||
readonly restored: Promise<void>;
|
||||
|
||||
/**
|
||||
* The version of the application.
|
||||
*/
|
||||
|
@ -2,18 +2,21 @@
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import { JupyterFrontEnd } from '@jupyterlab/application';
|
||||
|
||||
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
||||
import { closeIcon } from '@jupyterlab/ui-components';
|
||||
|
||||
import { ArrayExt } from '@lumino/algorithm';
|
||||
|
||||
import { Token } from '@lumino/coreutils';
|
||||
|
||||
import { ArrayExt, find } from '@lumino/algorithm';
|
||||
import { PromiseDelegate, Token } from '@lumino/coreutils';
|
||||
import { Message, MessageLoop, IMessageHandler } from '@lumino/messaging';
|
||||
|
||||
import { ISignal, Signal } from '@lumino/signaling';
|
||||
|
||||
import { Panel, Widget, BoxLayout } from '@lumino/widgets';
|
||||
import {
|
||||
BoxLayout,
|
||||
Panel,
|
||||
SplitPanel,
|
||||
StackedPanel,
|
||||
Widget
|
||||
} from '@lumino/widgets';
|
||||
|
||||
/**
|
||||
* The Jupyter Notebook application shell token.
|
||||
@ -40,37 +43,75 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
|
||||
super();
|
||||
this.id = 'main';
|
||||
|
||||
const rootLayout = new BoxLayout();
|
||||
|
||||
this._topHandler = new Private.PanelHandler();
|
||||
this._menuHandler = new Private.PanelHandler();
|
||||
this._leftHandler = new SideBarHandler('left');
|
||||
this._rightHandler = new SideBarHandler('right');
|
||||
this._main = new Panel();
|
||||
const topWrapper = (this._topWrapper = new Panel());
|
||||
const menuWrapper = (this._menuWrapper = new Panel());
|
||||
|
||||
this._topHandler.panel.id = 'top-panel';
|
||||
this._menuHandler.panel.id = 'menu-panel';
|
||||
this._main.id = 'main-panel';
|
||||
|
||||
// create wrappers around the top and menu areas
|
||||
const topWrapper = (this._topWrapper = new Panel());
|
||||
topWrapper.id = 'top-panel-wrapper';
|
||||
topWrapper.addWidget(this._topHandler.panel);
|
||||
|
||||
const menuWrapper = (this._menuWrapper = new Panel());
|
||||
menuWrapper.id = 'menu-panel-wrapper';
|
||||
menuWrapper.addWidget(this._menuHandler.panel);
|
||||
|
||||
BoxLayout.setStretch(topWrapper, 0);
|
||||
BoxLayout.setStretch(menuWrapper, 0);
|
||||
BoxLayout.setStretch(this._main, 1);
|
||||
|
||||
this._spacer = new Widget();
|
||||
this._spacer.id = 'spacer-widget';
|
||||
|
||||
// create wrappers around the top and menu areas
|
||||
topWrapper.id = 'top-panel-wrapper';
|
||||
topWrapper.addWidget(this._topHandler.panel);
|
||||
|
||||
menuWrapper.id = 'menu-panel-wrapper';
|
||||
menuWrapper.addWidget(this._menuHandler.panel);
|
||||
|
||||
const rootLayout = new BoxLayout();
|
||||
const leftHandler = this._leftHandler;
|
||||
const rightHandler = this._rightHandler;
|
||||
|
||||
leftHandler.panel.id = 'jp-left-stack';
|
||||
rightHandler.panel.id = 'jp-right-stack';
|
||||
|
||||
// Hide the side panels by default.
|
||||
leftHandler.hide();
|
||||
rightHandler.hide();
|
||||
|
||||
const middleLayout = new BoxLayout({
|
||||
spacing: 0,
|
||||
direction: 'top-to-bottom'
|
||||
});
|
||||
BoxLayout.setStretch(this._topWrapper, 0);
|
||||
BoxLayout.setStretch(this._menuWrapper, 0);
|
||||
BoxLayout.setStretch(this._main, 1);
|
||||
|
||||
const middlePanel = new Panel({ layout: middleLayout });
|
||||
middlePanel.addWidget(this._topWrapper);
|
||||
middlePanel.addWidget(this._menuWrapper);
|
||||
middlePanel.addWidget(this._spacer);
|
||||
middlePanel.addWidget(this._main);
|
||||
middlePanel.layout = middleLayout;
|
||||
|
||||
// TODO: Consider storing this as an attribute this._hsplitPanel if saving/restoring layout needed
|
||||
const hsplitPanel = new SplitPanel();
|
||||
hsplitPanel.id = 'main-split-panel';
|
||||
hsplitPanel.spacing = 1;
|
||||
BoxLayout.setStretch(hsplitPanel, 1);
|
||||
|
||||
SplitPanel.setStretch(leftHandler.panel, 0);
|
||||
SplitPanel.setStretch(rightHandler.panel, 0);
|
||||
SplitPanel.setStretch(middlePanel, 1);
|
||||
|
||||
hsplitPanel.addWidget(leftHandler.panel);
|
||||
hsplitPanel.addWidget(middlePanel);
|
||||
hsplitPanel.addWidget(rightHandler.panel);
|
||||
|
||||
// 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.
|
||||
hsplitPanel.setRelativeSizes([1, 2.5, 1]);
|
||||
|
||||
rootLayout.spacing = 0;
|
||||
rootLayout.addWidget(topWrapper);
|
||||
rootLayout.addWidget(menuWrapper);
|
||||
rootLayout.addWidget(this._spacer);
|
||||
rootLayout.addWidget(this._main);
|
||||
rootLayout.addWidget(hsplitPanel);
|
||||
|
||||
this.layout = rootLayout;
|
||||
}
|
||||
@ -103,13 +144,59 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
|
||||
return this._menuWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left area handler
|
||||
*/
|
||||
get leftHandler(): SideBarHandler {
|
||||
return this._leftHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the right area handler
|
||||
*/
|
||||
get rightHandler(): SideBarHandler {
|
||||
return this._rightHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the left sidebar visible?
|
||||
*/
|
||||
get leftCollapsed(): boolean {
|
||||
return !(this._leftHandler.isVisible && this._leftHandler.panel.isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the right sidebar visible?
|
||||
*/
|
||||
get rightCollapsed(): boolean {
|
||||
return !(
|
||||
this._rightHandler.isVisible && this._rightHandler.panel.isVisible
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise that resolves when the main widget is loaded
|
||||
*/
|
||||
get restored(): Promise<void> {
|
||||
return this._mainWidgetLoaded.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a widget in its area.
|
||||
*/
|
||||
activateById(id: string): void {
|
||||
const widget = this._main.widgets.find(w => w.id === id);
|
||||
if (widget) {
|
||||
widget.activate();
|
||||
// Search all areas that can have widgets for this widget, starting with main.
|
||||
for (const area of ['main', 'top', 'left', 'right', 'menu']) {
|
||||
const widget = find(this.widgets(area as Shell.Area), w => w.id === id);
|
||||
if (widget) {
|
||||
if (area === 'left') {
|
||||
this.expandLeft(id);
|
||||
} else if (area === 'right') {
|
||||
this.expandRight(id);
|
||||
} else {
|
||||
widget.activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,20 +217,28 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
|
||||
options?: DocumentRegistry.IOpenOptions
|
||||
): void {
|
||||
const rank = options?.rank ?? DEFAULT_RANK;
|
||||
if (area === 'top') {
|
||||
return this._topHandler.addWidget(widget, rank);
|
||||
}
|
||||
if (area === 'menu') {
|
||||
return this._menuHandler.addWidget(widget, rank);
|
||||
}
|
||||
if (area === 'main' || area === undefined) {
|
||||
if (this._main.widgets.length > 0) {
|
||||
// do not add the widget if there is already one
|
||||
return;
|
||||
}
|
||||
this._main.addWidget(widget);
|
||||
this._main.update();
|
||||
this._currentChanged.emit(void 0);
|
||||
switch (area) {
|
||||
case 'top':
|
||||
return this._topHandler.addWidget(widget, rank);
|
||||
case 'menu':
|
||||
return this._menuHandler.addWidget(widget, rank);
|
||||
case 'main':
|
||||
case undefined:
|
||||
if (this._main.widgets.length > 0) {
|
||||
// do not add the widget if there is already one
|
||||
return;
|
||||
}
|
||||
this._main.addWidget(widget);
|
||||
this._main.update();
|
||||
this._currentChanged.emit(void 0);
|
||||
this._mainWidgetLoaded.resolve();
|
||||
break;
|
||||
case 'left':
|
||||
return this._leftHandler.addWidget(widget, rank);
|
||||
case 'right':
|
||||
return this._rightHandler.addWidget(widget, rank);
|
||||
default:
|
||||
console.warn(`Cannot add widget to area: ${area}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,19 +274,60 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
|
||||
case 'main':
|
||||
yield* this._main.widgets;
|
||||
return;
|
||||
case 'left':
|
||||
yield* this._leftHandler.widgets;
|
||||
return;
|
||||
case 'right':
|
||||
yield* this._rightHandler.widgets;
|
||||
return;
|
||||
default:
|
||||
console.error(`This shell has no area called "${area}"`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the left panel to show the sidebar with its widget.
|
||||
*/
|
||||
expandLeft(id?: string): void {
|
||||
this._leftHandler.panel.show();
|
||||
this._leftHandler.expand(id); // Show the current widget, if any
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the left panel
|
||||
*/
|
||||
collapseLeft(): void {
|
||||
this._leftHandler.collapse();
|
||||
this._leftHandler.panel.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the right panel to show the sidebar with its widget.
|
||||
*/
|
||||
expandRight(id?: string): void {
|
||||
this._rightHandler.panel.show();
|
||||
this._rightHandler.expand(id); // Show the current widget, if any
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the right panel
|
||||
*/
|
||||
collapseRight(): void {
|
||||
this._rightHandler.collapse();
|
||||
this._rightHandler.panel.hide();
|
||||
}
|
||||
|
||||
private _topWrapper: Panel;
|
||||
private _topHandler: Private.PanelHandler;
|
||||
private _menuWrapper: Panel;
|
||||
private _menuHandler: Private.PanelHandler;
|
||||
private _leftHandler: SideBarHandler;
|
||||
private _rightHandler: SideBarHandler;
|
||||
private _spacer: Widget;
|
||||
private _main: Panel;
|
||||
private _currentChanged = new Signal<this, void>(this);
|
||||
private _mainWidgetLoaded = new PromiseDelegate<void>();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,7 +337,243 @@ export namespace Shell {
|
||||
/**
|
||||
* The areas of the application shell where widgets can reside.
|
||||
*/
|
||||
export type Area = 'main' | 'top' | 'menu';
|
||||
export type Area = 'main' | 'top' | 'left' | 'right' | 'menu';
|
||||
}
|
||||
|
||||
/**
|
||||
* A name space for SideBarPanel functions.
|
||||
*/
|
||||
export namespace SideBarPanel {
|
||||
/**
|
||||
* The areas of the sidebar panel
|
||||
*/
|
||||
export type Area = 'left' | 'right';
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which manages a side bar that can show at most one widget at a time.
|
||||
*/
|
||||
export class SideBarHandler {
|
||||
/**
|
||||
* Construct a new side bar handler.
|
||||
*/
|
||||
constructor(area: SideBarPanel.Area) {
|
||||
this._area = area;
|
||||
this._panel = new Panel();
|
||||
this._panel.hide();
|
||||
|
||||
this._currentWidget = null;
|
||||
this._lastCurrentWidget = null;
|
||||
|
||||
this._widgetPanel = new StackedPanel();
|
||||
this._widgetPanel.widgetRemoved.connect(this._onWidgetRemoved, this);
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeIcon.element({
|
||||
container: closeButton,
|
||||
height: '16px',
|
||||
width: 'auto'
|
||||
});
|
||||
closeButton.onclick = () => {
|
||||
this.collapse();
|
||||
this.hide();
|
||||
};
|
||||
closeButton.className = 'jp-Button jp-SidePanel-collapse';
|
||||
const icon = new Widget({ node: closeButton });
|
||||
this._panel.addWidget(icon);
|
||||
this._panel.addWidget(this._widgetPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current widget in the sidebar panel.
|
||||
*/
|
||||
get currentWidget(): Widget | null {
|
||||
return (
|
||||
this._currentWidget ||
|
||||
this._lastCurrentWidget ||
|
||||
(this._items.length > 0 ? this._items[0].widget : null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the area of the side panel
|
||||
*/
|
||||
get area(): SideBarPanel.Area {
|
||||
return this._area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the panel is visible
|
||||
*/
|
||||
get isVisible(): boolean {
|
||||
return this._panel.isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stacked panel managed by the handler
|
||||
*/
|
||||
get panel(): Panel {
|
||||
return this._panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the widgets list.
|
||||
*/
|
||||
get widgets(): Readonly<Widget[]> {
|
||||
return this._items.map(obj => obj.widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal fired when a widget is added to the panel
|
||||
*/
|
||||
get widgetAdded(): ISignal<SideBarHandler, Widget> {
|
||||
return this._widgetAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal fired when a widget is removed from the panel
|
||||
*/
|
||||
get widgetRemoved(): ISignal<SideBarHandler, Widget> {
|
||||
return this._widgetRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the sidebar.
|
||||
*
|
||||
* #### Notes
|
||||
* This will open the most recently used widget, or the first widget
|
||||
* if there is no most recently used.
|
||||
*/
|
||||
expand(id?: string): void {
|
||||
if (this._currentWidget) {
|
||||
this.collapse();
|
||||
}
|
||||
if (id) {
|
||||
this.activate(id);
|
||||
} else {
|
||||
const visibleWidget = this.currentWidget;
|
||||
if (visibleWidget) {
|
||||
this._currentWidget = visibleWidget;
|
||||
this.activate(visibleWidget.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a widget residing in the stacked panel by ID.
|
||||
*
|
||||
* @param id - The widget's unique ID.
|
||||
*/
|
||||
activate(id: string): void {
|
||||
const widget = this._findWidgetByID(id);
|
||||
if (widget) {
|
||||
this._currentWidget = widget;
|
||||
widget.show();
|
||||
widget.activate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the sidebar has the given widget by id.
|
||||
*/
|
||||
has(id: string): boolean {
|
||||
return this._findWidgetByID(id) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the sidebar so no items are expanded.
|
||||
*/
|
||||
collapse(): void {
|
||||
this._currentWidget?.hide();
|
||||
this._currentWidget = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a widget and its title to the stacked panel.
|
||||
*
|
||||
* If the widget is already added, it will be moved.
|
||||
*/
|
||||
addWidget(widget: Widget, rank: number): void {
|
||||
widget.parent = null;
|
||||
widget.hide();
|
||||
const item = { widget, rank };
|
||||
const index = this._findInsertIndex(item);
|
||||
ArrayExt.insert(this._items, index, item);
|
||||
this._widgetPanel.insertWidget(index, widget);
|
||||
|
||||
this._refreshVisibility();
|
||||
|
||||
this._widgetAdded.emit(widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the side panel
|
||||
*/
|
||||
hide(): void {
|
||||
this._isHiddenByUser = true;
|
||||
this._refreshVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the side panel
|
||||
*/
|
||||
show(): void {
|
||||
this._isHiddenByUser = false;
|
||||
this._refreshVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the insertion index for a rank item.
|
||||
*/
|
||||
private _findInsertIndex(item: Private.IRankItem): number {
|
||||
return ArrayExt.upperBound(this._items, item, Private.itemCmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the item with the given widget, or `-1`.
|
||||
*/
|
||||
private _findWidgetIndex(widget: Widget): number {
|
||||
return ArrayExt.findFirstIndex(this._items, i => i.widget === widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the widget with the given id, or `null`.
|
||||
*/
|
||||
private _findWidgetByID(id: string): Widget | null {
|
||||
const item = find(this._items, value => value.widget.id === id);
|
||||
return item ? item.widget : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the visibility of the stacked panel.
|
||||
*/
|
||||
private _refreshVisibility(): void {
|
||||
this._panel.setHidden(this._isHiddenByUser);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the `widgetRemoved` signal from the panel.
|
||||
*/
|
||||
private _onWidgetRemoved(sender: StackedPanel, widget: Widget): void {
|
||||
if (widget === this._lastCurrentWidget) {
|
||||
this._lastCurrentWidget = null;
|
||||
}
|
||||
ArrayExt.removeAt(this._items, this._findWidgetIndex(widget));
|
||||
|
||||
this._refreshVisibility();
|
||||
|
||||
this._widgetRemoved.emit(widget);
|
||||
}
|
||||
|
||||
private _area: SideBarPanel.Area;
|
||||
private _isHiddenByUser = false;
|
||||
private _items = new Array<Private.IRankItem>();
|
||||
private _panel: Panel;
|
||||
private _widgetPanel: StackedPanel;
|
||||
private _currentWidget: Widget | null;
|
||||
private _lastCurrentWidget: Widget | null;
|
||||
private _widgetAdded: Signal<SideBarHandler, Widget> = new Signal(this);
|
||||
private _widgetRemoved: Signal<SideBarHandler, Widget> = new Signal(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,3 +8,4 @@
|
||||
@import url('~@jupyterlab/ui-components/style/index.css');
|
||||
|
||||
@import url('./base.css');
|
||||
@import url('./sidepanel.css');
|
||||
|
@ -8,3 +8,4 @@ import '@jupyterlab/mainmenu/style/index.js';
|
||||
import '@jupyterlab/ui-components/style/index.js';
|
||||
|
||||
import './base.css';
|
||||
import './sidepanel.css';
|
||||
|
48
packages/application/style/sidepanel.css
Normal file
@ -0,0 +1,48 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) Jupyter Development Team.
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| Adapted from JupyterLab's packages/application/style/sidepanel.css.
|
||||
|----------------------------------------------------------------------------*/
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Variables
|
||||
|----------------------------------------------------------------------------*/
|
||||
|
||||
:root {
|
||||
--jp-private-sidebar-tab-width: 32px;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
| SideBar
|
||||
|----------------------------------------------------------------------------*/
|
||||
|
||||
/* Stack panels */
|
||||
|
||||
#jp-right-stack,
|
||||
#jp-left-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: var(--jp-sidebar-min-width);
|
||||
}
|
||||
|
||||
#jp-left-stack .jp-SidePanel-collapse,
|
||||
#jp-right-stack .jp-SidePanel-collapse {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#jp-left-stack .jp-SidePanel-collapse {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
#jp-right-stack .jp-SidePanel-collapse {
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
#jp-left-stack .lm-StackedPanel,
|
||||
#jp-right-stack .lm-StackedPanel {
|
||||
flex: 1 1 auto;
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import { NotebookShell, INotebookShell } from '@jupyter-notebook/application';
|
||||
import {
|
||||
INotebookShell,
|
||||
NotebookShell,
|
||||
Shell
|
||||
} from '@jupyter-notebook/application';
|
||||
|
||||
import { JupyterFrontEnd } from '@jupyterlab/application';
|
||||
|
||||
import { Widget } from '@lumino/widgets';
|
||||
|
||||
describe('Shell', () => {
|
||||
describe('Shell for notebooks', () => {
|
||||
let shell: INotebookShell;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -23,10 +27,17 @@ describe('Shell', () => {
|
||||
it('should create a LabShell instance', () => {
|
||||
expect(shell).toBeInstanceOf(NotebookShell);
|
||||
});
|
||||
|
||||
it('should make all areas empty initially', () => {
|
||||
['main', 'top', 'left', 'right', 'menu'].forEach(area => {
|
||||
const widgets = Array.from(shell.widgets(area as Shell.Area));
|
||||
expect(widgets.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#widgets()', () => {
|
||||
it('should add widgets to existing areas', () => {
|
||||
it('should add widgets to main area', () => {
|
||||
const widget = new Widget();
|
||||
shell.add(widget, 'main');
|
||||
const widgets = Array.from(shell.widgets('main'));
|
||||
@ -36,7 +47,7 @@ describe('Shell', () => {
|
||||
it('should be empty and console.error if area does not exist', () => {
|
||||
const spy = jest.spyOn(console, 'error');
|
||||
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
|
||||
expect(Array.from(jupyterFrontEndShell.widgets('left'))).toHaveLength(0);
|
||||
expect(Array.from(jupyterFrontEndShell.widgets('fake'))).toHaveLength(0);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -82,4 +93,86 @@ describe('Shell', () => {
|
||||
expect(widgets.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add(widget, "left")', () => {
|
||||
it('should add a widget to the left area', () => {
|
||||
const widget = new Widget();
|
||||
widget.id = 'foo';
|
||||
shell.add(widget, 'left');
|
||||
const widgets = Array.from(shell.widgets('left'));
|
||||
expect(widgets.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add(widget, "right")', () => {
|
||||
it('should add a widget to the right area', () => {
|
||||
const widget = new Widget();
|
||||
widget.id = 'foo';
|
||||
shell.add(widget, 'right');
|
||||
const widgets = Array.from(shell.widgets('right'));
|
||||
expect(widgets.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shell for tree view', () => {
|
||||
let shell: INotebookShell;
|
||||
|
||||
beforeEach(() => {
|
||||
shell = new NotebookShell();
|
||||
Widget.attach(shell, document.body);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shell.dispose();
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should create a LabShell instance', () => {
|
||||
expect(shell).toBeInstanceOf(NotebookShell);
|
||||
});
|
||||
|
||||
it('should make all areas empty initially', () => {
|
||||
['main', 'top', 'left', 'right', 'menu'].forEach(area => {
|
||||
const widgets = Array.from(shell.widgets(area as Shell.Area));
|
||||
expect(widgets.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#widgets()', () => {
|
||||
it('should add widgets to existing areas', () => {
|
||||
const widget = new Widget();
|
||||
shell.add(widget, 'main');
|
||||
const widgets = Array.from(shell.widgets('main'));
|
||||
expect(widgets).toEqual([widget]);
|
||||
});
|
||||
|
||||
it('should throw an exception if a fake area does not exist', () => {
|
||||
const spy = jest.spyOn(console, 'error');
|
||||
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
|
||||
expect(Array.from(jupyterFrontEndShell.widgets('fake'))).toHaveLength(0);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add(widget, "left")', () => {
|
||||
it('should add a widget to the left area', () => {
|
||||
const widget = new Widget();
|
||||
widget.id = 'foo';
|
||||
shell.add(widget, 'left');
|
||||
const widgets = Array.from(shell.widgets('left'));
|
||||
expect(widgets.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add(widget, "right")', () => {
|
||||
it('should add a widget to the right area', () => {
|
||||
const widget = new Widget();
|
||||
widget.id = 'foo';
|
||||
shell.add(widget, 'right');
|
||||
const widgets = Array.from(shell.widgets('right'));
|
||||
expect(widgets.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,7 +18,11 @@ import { Text, Time } from '@jupyterlab/coreutils';
|
||||
|
||||
import { IDocumentManager } from '@jupyterlab/docmanager';
|
||||
|
||||
import { NotebookPanel, INotebookTracker } from '@jupyterlab/notebook';
|
||||
import {
|
||||
NotebookPanel,
|
||||
INotebookTracker,
|
||||
INotebookTools
|
||||
} from '@jupyterlab/notebook';
|
||||
|
||||
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
||||
|
||||
@ -327,6 +331,34 @@ const scrollOutput: JupyterFrontEndPlugin<void> = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A plugin to add the NotebookTools to the side panel;
|
||||
*/
|
||||
const notebookToolsWidget: JupyterFrontEndPlugin<void> = {
|
||||
id: '@jupyter-notebook/notebook-extension:notebook-tools',
|
||||
autoStart: true,
|
||||
requires: [INotebookShell],
|
||||
optional: [INotebookTools],
|
||||
activate: (
|
||||
app: JupyterFrontEnd,
|
||||
shell: INotebookShell,
|
||||
notebookTools: INotebookTools | null
|
||||
) => {
|
||||
const onChange = async () => {
|
||||
const current = shell.currentWidget;
|
||||
if (!(current instanceof NotebookPanel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the notebook tools in right area.
|
||||
if (notebookTools) {
|
||||
shell.add(notebookTools, 'right');
|
||||
}
|
||||
};
|
||||
shell.currentChanged.connect(onChange);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the plugins as default.
|
||||
*/
|
||||
@ -334,7 +366,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
|
||||
checkpoints,
|
||||
kernelLogo,
|
||||
kernelStatus,
|
||||
scrollOutput
|
||||
scrollOutput,
|
||||
notebookToolsWidget
|
||||
];
|
||||
|
||||
export default plugins;
|
||||
|
@ -6,6 +6,7 @@ import path from 'path';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from './fixtures';
|
||||
|
||||
import { waitForKernelReady } from './utils';
|
||||
|
||||
test.describe('General', () => {
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -6,6 +6,7 @@ import path from 'path';
|
||||
import { test } from './fixtures';
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
import { waitForKernelReady } from './utils';
|
||||
|
||||
const NOTEBOOK = 'empty.ipynb';
|
||||
|
||||
@ -32,19 +33,18 @@ test.describe('Notebook Menus', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.kernel.shutdownAll();
|
||||
});
|
||||
|
||||
MENU_PATHS.forEach(menuPath => {
|
||||
test(`Open menu item ${menuPath}`, async ({ page, tmpPath }) => {
|
||||
await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`);
|
||||
await waitForKernelReady(page);
|
||||
|
||||
await page.menu.open(menuPath);
|
||||
expect(await page.menu.isOpen(menuPath)).toBeTruthy();
|
||||
|
||||
const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`;
|
||||
const menu = await page.menu.getOpenMenu();
|
||||
expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase());
|
||||
expect(menu).toBeDefined();
|
||||
expect(await menu!.screenshot()).toMatchSnapshot(imageName.toLowerCase());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 40 KiB |
@ -6,9 +6,10 @@ import path from 'path';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from './fixtures';
|
||||
|
||||
import { waitForKernelReady } from './utils';
|
||||
|
||||
test.use({ autoGoto: false, viewport: { width: 512, height: 768 } });
|
||||
test.use({ autoGoto: false });
|
||||
|
||||
test.describe('Mobile', () => {
|
||||
test('The layout should be more compact on the file browser page', async ({
|
||||
@ -16,7 +17,13 @@ test.describe('Mobile', () => {
|
||||
tmpPath
|
||||
}) => {
|
||||
await page.goto(`tree/${tmpPath}`);
|
||||
|
||||
// temporary workaround to trigger a toolbar resize
|
||||
// TODO: investigate in https://github.com/jupyter/notebook/issues/6553
|
||||
await page.setViewportSize({ width: 524, height: 800 });
|
||||
|
||||
await page.waitForSelector('#top-panel-wrapper', { state: 'hidden' });
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot('tree.png');
|
||||
});
|
||||
|
||||
@ -30,12 +37,14 @@ test.describe('Mobile', () => {
|
||||
`${tmpPath}/${notebook}`
|
||||
);
|
||||
await page.goto(`notebooks/${tmpPath}/${notebook}`);
|
||||
// TODO: investigate why this does not run the cells in Jupyter Notebook
|
||||
// await page.notebook.run();
|
||||
|
||||
// wait for the kernel status animations to be finished
|
||||
await waitForKernelReady(page);
|
||||
|
||||
// temporary workaround to trigger a toolbar resize
|
||||
// TODO: investigate in https://github.com/jupyter/notebook/issues/6553
|
||||
await page.setViewportSize({ width: 524, height: 800 });
|
||||
|
||||
// force switching back to command mode to avoid capturing the cursor in the screenshot
|
||||
await page.evaluate(async () => {
|
||||
await window.jupyterapp.commands.execute('notebook:enter-command-mode');
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -7,7 +7,7 @@ import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from './fixtures';
|
||||
|
||||
import { runAndAdvance } from './utils';
|
||||
import { runAndAdvance, waitForKernelReady } from './utils';
|
||||
|
||||
const NOTEBOOK = 'example.ipynb';
|
||||
|
||||
@ -99,4 +99,60 @@ test.describe('Notebook', () => {
|
||||
// check the short output area is not auto scrolled
|
||||
expect(await checkCell(1)).toBe(false);
|
||||
});
|
||||
|
||||
test('Open table of content left panel', async ({ page, tmpPath }) => {
|
||||
const notebook = 'simple_toc.ipynb';
|
||||
const menuPath = 'View>Left Sidebar>Show Table of Contents';
|
||||
await page.contents.uploadFile(
|
||||
path.resolve(__dirname, `./notebooks/${notebook}`),
|
||||
`${tmpPath}/${notebook}`
|
||||
);
|
||||
await page.goto(`notebooks/${tmpPath}/${notebook}`);
|
||||
|
||||
await waitForKernelReady(page);
|
||||
|
||||
await page.menu.clickMenuItem(menuPath);
|
||||
|
||||
const panel = page.locator('#jp-left-stack');
|
||||
expect(await panel.isVisible());
|
||||
|
||||
await expect(
|
||||
panel.locator(
|
||||
'.jp-SidePanel-content > .jp-TableOfContents-tree > .jp-TableOfContents-content'
|
||||
)
|
||||
).toHaveCount(1);
|
||||
await expect(
|
||||
panel.locator(
|
||||
'.jp-SidePanel-content > .jp-TableOfContents-tree > .jp-TableOfContents-content > .jp-tocItem'
|
||||
)
|
||||
).toHaveCount(3);
|
||||
|
||||
const imageName = `toc-left-panel.png`;
|
||||
|
||||
expect(await panel.screenshot()).toMatchSnapshot(imageName);
|
||||
});
|
||||
|
||||
test('Open notebook tools right panel', async ({ page, tmpPath }) => {
|
||||
const notebook = 'simple.ipynb';
|
||||
const menuPath = 'View>Right Sidebar>Show Notebook Tools';
|
||||
await page.contents.uploadFile(
|
||||
path.resolve(__dirname, `./notebooks/${notebook}`),
|
||||
`${tmpPath}/${notebook}`
|
||||
);
|
||||
await page.goto(`notebooks/${tmpPath}/${notebook}`);
|
||||
|
||||
await waitForKernelReady(page);
|
||||
|
||||
await page.menu.clickMenuItem(menuPath);
|
||||
|
||||
const panel = page.locator('#jp-right-stack');
|
||||
expect(await panel.isVisible());
|
||||
|
||||
await page.isVisible('#notebook-tools.jp-NotebookTools');
|
||||
|
||||
await page.isVisible('#notebook-tools.jp-NotebookTools > #add-tag.tag');
|
||||
|
||||
const imageName = `notebooktools-right-panel.png`;
|
||||
expect(await panel.screenshot()).toMatchSnapshot(imageName);
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 11 KiB |
65
ui-tests/test/notebooks/simple_toc.ipynb
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# part 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## subpart 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## subpart 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# part 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# part 3"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.5"
|
||||
},
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "0508733a7f73e6ddc798c911e704189485d436785f398d29dd8c20885fc63cb3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
@ -72,7 +72,7 @@ math.pi`);
|
||||
|
||||
// Shut down the kernels
|
||||
await tree2.click('text="Running"');
|
||||
await tree2.click('text="Shut Down All"');
|
||||
await tree2.click('#main-panel button :text("Shut Down All")');
|
||||
await tree2.press('.jp-Dialog', 'Enter');
|
||||
|
||||
// Close the pages
|
||||
@ -89,9 +89,10 @@ math.pi`);
|
||||
await page.goto(`tree/${tmpPath}`);
|
||||
|
||||
// Open JupyterLab
|
||||
await page.menu.clickMenuItem('View>Open JupyterLab');
|
||||
|
||||
const lab = await page.waitForEvent('popup');
|
||||
const [lab] = await Promise.all([
|
||||
page.waitForEvent('popup'),
|
||||
page.menu.clickMenuItem('View>Open JupyterLab')
|
||||
]);
|
||||
await lab.waitForSelector('.jp-Launcher');
|
||||
await lab.close();
|
||||
|
||||
|
@ -32,4 +32,5 @@ export async function waitForKernelReady(
|
||||
}, true);
|
||||
return finished;
|
||||
});
|
||||
await page.waitForSelector('.jp-DebuggerBugButton[aria-disabled="false"]');
|
||||
}
|
||||
|
138
yarn.lock
@ -1429,10 +1429,10 @@
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jupyter-notebook/application-extension@file:packages/application-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/ui-components" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyter-notebook/ui-components" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/celltags" "^4.0.0-alpha.14"
|
||||
@ -1450,7 +1450,7 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/application@file:packages/application":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1465,7 +1465,7 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/console-extension@file:packages/console-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/console" "^4.0.0-alpha.14"
|
||||
@ -1473,7 +1473,7 @@
|
||||
"@lumino/algorithm" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/docmanager-extension@file:packages/docmanager-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1483,26 +1483,26 @@
|
||||
"@lumino/algorithm" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/documentsearch-extension@file:packages/documentsearch-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/documentsearch" "^4.0.0-alpha.14"
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/help-extension@file:packages/help-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/ui-components" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/ui-components" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/mainmenu" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
|
||||
"@jupyter-notebook/lab-extension@file:packages/lab-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1513,9 +1513,9 @@
|
||||
"@lumino/disposable" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/notebook-extension@file:packages/notebook-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/cells" "^4.0.0-alpha.14"
|
||||
@ -1527,7 +1527,7 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/terminal-extension@file:packages/terminal-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1535,10 +1535,10 @@
|
||||
"@lumino/algorithm" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/tree-extension@file:packages/tree-extension":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/tree" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyter-notebook/tree" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1555,9 +1555,9 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/tree@file:packages/tree":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.5"
|
||||
"@jupyter-notebook/application" "^7.0.0-alpha.6"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
@ -1575,7 +1575,7 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@jupyter-notebook/ui-components@file:packages/ui-components":
|
||||
version "7.0.0-alpha.5"
|
||||
version "7.0.0-alpha.6"
|
||||
dependencies:
|
||||
"@jupyterlab/ui-components" "^4.0.0-alpha.29"
|
||||
react "^17.0.1"
|
||||
@ -1813,6 +1813,16 @@
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
react "^17.0.1"
|
||||
|
||||
"@jupyterlab/celltags-extension@^4.0.0-alpha.14":
|
||||
version "4.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/celltags-extension/-/celltags-extension-4.0.0-alpha.14.tgz#cad83537b31e31910f10099ae260a26aa66a65e8"
|
||||
integrity sha512-S7o/l8n2K5eCnbXCFKsGaOlbl4PnQb10oeRo1PQDQSPAvpN2WgUzp83YYMXlLjngsDs41DGaSBj+7h6qfGCVmw==
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/celltags" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/notebook" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
|
||||
"@jupyterlab/celltags@^4.0.0-alpha.14":
|
||||
version "4.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/celltags/-/celltags-4.0.0-alpha.14.tgz#d1ad45da532c62b0ba1f3778e2feff04d0800698"
|
||||
@ -2041,6 +2051,61 @@
|
||||
path-browserify "^1.0.0"
|
||||
url-parse "~1.5.4"
|
||||
|
||||
"@jupyterlab/debugger-extension@^4.0.0-alpha.14":
|
||||
version "4.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/debugger-extension/-/debugger-extension-4.0.0-alpha.14.tgz#598b6046b40e10e3d811d11ae7b1b2c3611ce031"
|
||||
integrity sha512-K5u32ACz4YNaz2inh+g4mYkUdJTuyAxQSquFvVAImp27oyGKn4GxYrtcHoUCoJlib/0DyDTOg2LOCup6iocMng==
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/codeeditor" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/console" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
"@jupyterlab/debugger" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/docregistry" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/fileeditor" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/logconsole" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/notebook" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/rendermime" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/services" "^7.0.0-alpha.14"
|
||||
"@jupyterlab/settingregistry" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
|
||||
"@jupyterlab/debugger@^4.0.0-alpha.14":
|
||||
version "4.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/debugger/-/debugger-4.0.0-alpha.14.tgz#d9d0c48800b30ec9d083add8bde32cfac8a8c620"
|
||||
integrity sha512-qtewnV9Dfkl2iV9KC911k1LU+Jv9vn7JABvaTM7vZyS8FqVuZ15zigk1HessvRGy657MD5SZoUtpBa4ETcerOQ==
|
||||
dependencies:
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.0.0"
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/cells" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/codeeditor" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/codemirror" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/console" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/coreutils" "^6.0.0-alpha.14"
|
||||
"@jupyterlab/docregistry" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/fileeditor" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/notebook" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/observables" "^5.0.0-alpha.14"
|
||||
"@jupyterlab/rendermime" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/services" "^7.0.0-alpha.14"
|
||||
"@jupyterlab/shared-models" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/ui-components" "^4.0.0-alpha.29"
|
||||
"@lumino/algorithm" "^2.0.0-alpha.6"
|
||||
"@lumino/commands" "^2.0.0-alpha.6"
|
||||
"@lumino/coreutils" "^2.0.0-alpha.6"
|
||||
"@lumino/datagrid" "^1.0.0-alpha.6"
|
||||
"@lumino/disposable" "^2.0.0-alpha.6"
|
||||
"@lumino/messaging" "^2.0.0-alpha.6"
|
||||
"@lumino/polling" "^2.0.0-alpha.6"
|
||||
"@lumino/signaling" "^2.0.0-alpha.6"
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
"@vscode/debugprotocol" "^1.51.0"
|
||||
react "^17.0.1"
|
||||
|
||||
"@jupyterlab/docmanager-extension@^4.0.0-alpha.14":
|
||||
version "4.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/docmanager-extension/-/docmanager-extension-4.0.0-alpha.14.tgz#dd31c98e924c12d44ced652410e09451bded155b"
|
||||
@ -2795,6 +2860,17 @@
|
||||
"@jupyterlab/apputils" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
|
||||
"@jupyterlab/toc-extension@^6.0.0-alpha.14":
|
||||
version "6.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/toc-extension/-/toc-extension-6.0.0-alpha.14.tgz#706e5c2f7abf72546592dea5f675c55dc257bcea"
|
||||
integrity sha512-ilksYGjsXszgdhl9beZtFRmRJLm+ZN2lQ0WJDxsnRaNG+d6tRfiw8toAS111bbSmxyfRNBysdqNIbtNfbioFEg==
|
||||
dependencies:
|
||||
"@jupyterlab/application" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/settingregistry" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/toc" "^6.0.0-alpha.14"
|
||||
"@jupyterlab/translation" "^4.0.0-alpha.14"
|
||||
"@jupyterlab/ui-components" "^4.0.0-alpha.29"
|
||||
|
||||
"@jupyterlab/toc@^6.0.0-alpha.14":
|
||||
version "6.0.0-alpha.14"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/toc/-/toc-6.0.0-alpha.14.tgz#6fad008f8252cbdcd87921ade3c4f42fbfd46012"
|
||||
@ -3735,6 +3811,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-2.0.0-alpha.6.tgz#77279303fc72932e3131a085dcaa34e808a09739"
|
||||
integrity sha512-W0qqJZoPRHscHL5k/DHSOea7LugPVx7DmART925bdrD8PU1Rw4K0mUzKb/Zsin4m1O5IMBoPuGEdEG5Jhq3KyA==
|
||||
|
||||
"@lumino/datagrid@^1.0.0-alpha.6":
|
||||
version "1.0.0-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@lumino/datagrid/-/datagrid-1.0.0-alpha.6.tgz#90364797ab0cc7cdb60b72b6448462d56e07219c"
|
||||
integrity sha512-dwQvaEzka8DBbaHHi/HHtzI4sW+U0iX/RgBFfOlFQWtprZfV+Rjeaw2TWPmXPiR/ugy8zGBYhwxPigfR4zO2Kg==
|
||||
dependencies:
|
||||
"@lumino/algorithm" "^2.0.0-alpha.6"
|
||||
"@lumino/coreutils" "^2.0.0-alpha.6"
|
||||
"@lumino/disposable" "^2.0.0-alpha.6"
|
||||
"@lumino/domutils" "^2.0.0-alpha.6"
|
||||
"@lumino/dragdrop" "^2.0.0-alpha.6"
|
||||
"@lumino/keyboard" "^2.0.0-alpha.6"
|
||||
"@lumino/messaging" "^2.0.0-alpha.6"
|
||||
"@lumino/signaling" "^2.0.0-alpha.6"
|
||||
"@lumino/widgets" "^2.0.0-alpha.6"
|
||||
|
||||
"@lumino/disposable@^2.0.0-alpha.6":
|
||||
version "2.0.0-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@lumino/disposable/-/disposable-2.0.0-alpha.6.tgz#ccb899b7b4ad6db359c351fe2b2ba989977b06a7"
|
||||
@ -4559,6 +4650,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@verdaccio/ui-theme/-/ui-theme-6.0.0-6-next.48.tgz#23bbc8037bf9e1b27600f5160a0ff716982db48b"
|
||||
integrity sha512-1jls+cpfEXqXc1ZzqLGGNs6YCyG6B6QwDCezEkSvgKm+9A49FnSJ2n2dNIGcQYOszwHmd8EvwN98OEIx3Bbtrw==
|
||||
|
||||
"@vscode/debugprotocol@^1.51.0":
|
||||
version "1.57.0"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/debugprotocol/-/debugprotocol-1.57.0.tgz#f055c0422b1f77358a12b1415623099ba0541647"
|
||||
integrity sha512-eww0WhAtj3lPX7+7tGkxQ3P7IRC3hS7+SVL7fmM8CAat2DMM+PVjg1FQbTCtMw6EwNSmT/qMx1iZCyzQguJJKA==
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
|
||||
|