Skip to content

Commit 62c0fa8

Browse files
authored
Merge pull request #6487 from brichet/left-right-widgets
Left right panel in Notebook view
2 parents e8e0f8f + 94eadd8 commit 62c0fa8

34 files changed

+1235
-84
lines changed

app/index.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ async function main() {
8383
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
8484
[
8585
'@jupyterlab/apputils-extension:palette',
86+
'@jupyter/apputils-extension:sanitizer',
8687
'@jupyterlab/apputils-extension:settings',
8788
'@jupyterlab/apputils-extension:state',
8889
'@jupyterlab/apputils-extension:themes',
@@ -175,11 +176,30 @@ async function main() {
175176
}
176177
case 'notebooks': {
177178
baseMods = baseMods.concat([
179+
require('@jupyterlab/celltags-extension'),
178180
require('@jupyterlab/cell-toolbar-extension'),
181+
require('@jupyterlab/debugger-extension').default.filter(({ id }) =>
182+
[
183+
'@jupyterlab/debugger-extension:config',
184+
'@jupyterlab/debugger-extension:main',
185+
'@jupyterlab/debugger-extension:notebooks',
186+
'@jupyterlab/debugger-extension:service',
187+
'@jupyterlab/debugger-extension:sidebar',
188+
'@jupyterlab/debugger-extension:sources'
189+
].includes(id)
190+
),
179191
require('@jupyterlab/notebook-extension').default.filter(({ id }) =>
180192
[
181193
'@jupyterlab/notebook-extension:completer',
182-
'@jupyterlab/notebook-extension:search'
194+
'@jupyterlab/notebook-extension:search',
195+
'@jupyterlab/notebook-extension:toc',
196+
'@jupyterlab/notebook-extension:tools'
197+
].includes(id)
198+
),
199+
require('@jupyterlab/toc-extension').default.filter(({ id }) =>
200+
[
201+
'@jupyterlab/toc-extension:registry',
202+
'@jupyterlab/toc-extension:tracker'
183203
].includes(id)
184204
),
185205
require('@jupyterlab/tooltip-extension').default.filter(({ id }) =>

app/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@jupyterlab/cell-toolbar": "~4.0.0-alpha.14",
3030
"@jupyterlab/cell-toolbar-extension": "~4.0.0-alpha.14",
3131
"@jupyterlab/celltags": "~4.0.0-alpha.14",
32+
"@jupyterlab/celltags-extension": "~4.0.0-alpha.14",
3233
"@jupyterlab/codeeditor": "~4.0.0-alpha.14",
3334
"@jupyterlab/codemirror-extension": "~4.0.0-alpha.14",
3435
"@jupyterlab/collaboration": "~4.0.0-alpha.14",
@@ -76,6 +77,7 @@
7677
"@jupyterlab/terminal-extension": "~4.0.0-alpha.14",
7778
"@jupyterlab/theme-dark-extension": "~4.0.0-alpha.14",
7879
"@jupyterlab/theme-light-extension": "~4.0.0-alpha.14",
80+
"@jupyterlab/toc-extension": "~6.0.0-alpha.14",
7981
"@jupyterlab/tooltip": "~4.0.0-alpha.14",
8082
"@jupyterlab/tooltip-extension": "~4.0.0-alpha.14",
8183
"@jupyterlab/translation": "~4.0.0-alpha.14",
@@ -114,11 +116,13 @@
114116
"@jupyterlab/apputils-extension": "^4.0.0-alpha.14",
115117
"@jupyterlab/cell-toolbar-extension": "^4.0.0-alpha.14",
116118
"@jupyterlab/celltags": "^4.0.0-alpha.14",
119+
"@jupyterlab/celltags-extension": "^4.0.0-alpha.14",
117120
"@jupyterlab/codemirror-extension": "^4.0.0-alpha.14",
118121
"@jupyterlab/collaboration-extension": "^4.0.0-alpha.14",
119122
"@jupyterlab/completer-extension": "^4.0.0-alpha.14",
120123
"@jupyterlab/console-extension": "^4.0.0-alpha.14",
121124
"@jupyterlab/coreutils": "^6.0.0-alpha.14",
125+
"@jupyterlab/debugger-extension": "^4.0.0-alpha.14",
122126
"@jupyterlab/docmanager-extension": "^4.0.0-alpha.14",
123127
"@jupyterlab/docprovider-extension": "^4.0.0-alpha.14",
124128
"@jupyterlab/documentsearch-extension": "^4.0.0-alpha.14",
@@ -140,6 +144,7 @@
140144
"@jupyterlab/terminal-extension": "^4.0.0-alpha.14",
141145
"@jupyterlab/theme-dark-extension": "^4.0.0-alpha.14",
142146
"@jupyterlab/theme-light-extension": "^4.0.0-alpha.14",
147+
"@jupyterlab/toc-extension": "^6.0.0-alpha.14",
143148
"@jupyterlab/tooltip-extension": "^4.0.0-alpha.14",
144149
"@jupyterlab/translation-extension": "^4.0.0-alpha.14",
145150
"@jupyterlab/vega5-extension": "^4.0.0-alpha.14"
@@ -181,6 +186,7 @@
181186
"@jupyterlab/codemirror-extension",
182187
"@jupyterlab/completer-extension",
183188
"@jupyterlab/console-extension",
189+
"@jupyterlab/debugger-extension",
184190
"@jupyterlab/docmanager-extension",
185191
"@jupyterlab/documentsearch-extension",
186192
"@jupyterlab/filebrowser-extension",
@@ -211,6 +217,7 @@
211217
"@jupyterlab/completer",
212218
"@jupyterlab/console",
213219
"@jupyterlab/coreutils",
220+
"@jupyterlab/debugger",
214221
"@jupyterlab/docmanager",
215222
"@jupyterlab/docprovider",
216223
"@jupyterlab/documentsearch",

packages/application-extension/src/index.ts

+231-6
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,24 @@ import { ITranslator } from '@jupyterlab/translation';
3535
import {
3636
NotebookApp,
3737
NotebookShell,
38-
INotebookShell
38+
INotebookShell,
39+
SideBarPanel,
40+
SideBarHandler
3941
} from '@jupyter-notebook/application';
4042

4143
import { jupyterIcon } from '@jupyter-notebook/ui-components';
4244

4345
import { PromiseDelegate } from '@lumino/coreutils';
4446

45-
import { DisposableDelegate, DisposableSet } from '@lumino/disposable';
47+
import {
48+
DisposableDelegate,
49+
DisposableSet,
50+
IDisposable
51+
} from '@lumino/disposable';
52+
53+
import { Menu, Widget } from '@lumino/widgets';
4654

47-
import { Widget } from '@lumino/widgets';
55+
import { SideBarPalette } from './sidebarpalette';
4856

4957
/**
5058
* A regular expression to match path to notebooks and documents
@@ -65,6 +73,11 @@ namespace CommandIDs {
6573
*/
6674
export const toggleTop = 'application:toggle-top';
6775

76+
/**
77+
* Toggle sidebar visibility
78+
*/
79+
export const togglePanel = 'application:toggle-panel';
80+
6881
/**
6982
* Toggle the Zen mode
7083
*/
@@ -173,7 +186,7 @@ const opener: JupyterFrontEndPlugin<void> = {
173186
const file = decodeURIComponent(path);
174187
const urlParams = new URLSearchParams(parsed.search);
175188
const factory = urlParams.get('factory') ?? 'default';
176-
app.restored.then(async () => {
189+
app.started.then(async () => {
177190
docManager.open(file, factory, undefined, {
178191
ref: '_noref'
179192
});
@@ -187,8 +200,6 @@ const opener: JupyterFrontEndPlugin<void> = {
187200

188201
/**
189202
* A plugin to customize menus
190-
*
191-
* TODO: use this plugin to customize the menu items and their order
192203
*/
193204
const menus: JupyterFrontEndPlugin<void> = {
194205
id: '@jupyter-notebook/application-extension:menus',
@@ -557,6 +568,219 @@ const topVisibility: JupyterFrontEndPlugin<void> = {
557568
autoStart: true
558569
};
559570

571+
/**
572+
* Plugin to toggle the left or right sidebar's visibility.
573+
*/
574+
const sidebarVisibility: JupyterFrontEndPlugin<void> = {
575+
id: '@jupyter-notebook/application-extension:sidebar',
576+
requires: [INotebookShell, ITranslator],
577+
optional: [IMainMenu, ICommandPalette],
578+
autoStart: true,
579+
activate: (
580+
app: JupyterFrontEnd<JupyterFrontEnd.IShell>,
581+
notebookShell: INotebookShell,
582+
translator: ITranslator,
583+
menu: IMainMenu | null,
584+
palette: ICommandPalette | null
585+
) => {
586+
const trans = translator.load('notebook');
587+
588+
/* Arguments for togglePanel command:
589+
* side, left or right area
590+
* title, widget title to show in the menu
591+
* id, widget ID to activate in the sidebar
592+
*/
593+
app.commands.addCommand(CommandIDs.togglePanel, {
594+
label: args => args['title'] as string,
595+
caption: args => {
596+
// We do not substitute the parameter into the string because the parameter is not
597+
// localized (e.g., it is always 'left') even though the string is localized.
598+
if (args['side'] === 'left') {
599+
return trans.__(
600+
'Show %1 in the left sidebar',
601+
args['title'] as string
602+
);
603+
} else if (args['side'] === 'right') {
604+
return trans.__(
605+
'Show %1 in the right sidebar',
606+
args['title'] as string
607+
);
608+
}
609+
return trans.__('Show %1 in the sidebar', args['title'] as string);
610+
},
611+
execute: args => {
612+
switch (args['side'] as string) {
613+
case 'left':
614+
if (notebookShell.leftCollapsed) {
615+
notebookShell.expandLeft(args.id as string);
616+
} else if (
617+
notebookShell.leftHandler.currentWidget?.id !== args.id
618+
) {
619+
notebookShell.expandLeft(args.id as string);
620+
} else {
621+
notebookShell.collapseLeft();
622+
if (notebookShell.currentWidget) {
623+
notebookShell.activateById(notebookShell.currentWidget.id);
624+
}
625+
}
626+
break;
627+
case 'right':
628+
if (notebookShell.rightCollapsed) {
629+
notebookShell.expandRight(args.id as string);
630+
} else if (
631+
notebookShell.rightHandler.currentWidget?.id !== args.id
632+
) {
633+
notebookShell.expandRight(args.id as string);
634+
} else {
635+
notebookShell.collapseRight();
636+
if (notebookShell.currentWidget) {
637+
notebookShell.activateById(notebookShell.currentWidget.id);
638+
}
639+
}
640+
break;
641+
}
642+
},
643+
isToggled: args => {
644+
switch (args['side'] as string) {
645+
case 'left': {
646+
if (notebookShell.leftCollapsed) {
647+
return false;
648+
}
649+
const currentWidget = notebookShell.leftHandler.currentWidget;
650+
if (!currentWidget) {
651+
return false;
652+
}
653+
654+
return currentWidget.id === (args['id'] as string);
655+
}
656+
case 'right': {
657+
if (notebookShell.rightCollapsed) {
658+
return false;
659+
}
660+
const currentWidget = notebookShell.rightHandler.currentWidget;
661+
if (!currentWidget) {
662+
return false;
663+
}
664+
665+
return currentWidget.id === (args['id'] as string);
666+
}
667+
}
668+
return false;
669+
}
670+
});
671+
672+
const sideBarMenu: { [area in SideBarPanel.Area]: IDisposable | null } = {
673+
left: null,
674+
right: null
675+
};
676+
677+
/**
678+
* The function which adds entries to the View menu for each widget of a sidebar.
679+
*
680+
* @param area - 'left' or 'right', the area of the side bar.
681+
* @param entryLabel - the name of the main entry in the View menu for that sidebar.
682+
* @returns - The disposable menu added to the View menu or null.
683+
*/
684+
const updateMenu = (area: SideBarPanel.Area, entryLabel: string) => {
685+
if (menu === null) {
686+
return null;
687+
}
688+
689+
// Remove the previous menu entry for this sidebar.
690+
sideBarMenu[area]?.dispose();
691+
692+
// Creates a new menu entry and populates it with sidebar widgets.
693+
const newMenu = new Menu({ commands: app.commands });
694+
newMenu.title.label = entryLabel;
695+
const widgets = notebookShell.widgets(area);
696+
let menuToAdd = false;
697+
698+
for (let widget of widgets) {
699+
newMenu.addItem({
700+
command: CommandIDs.togglePanel,
701+
args: {
702+
side: area,
703+
title: `Show ${widget.title.caption}`,
704+
id: widget.id
705+
}
706+
});
707+
menuToAdd = true;
708+
}
709+
710+
// If there are widgets, add the menu to the main menu entry.
711+
if (menuToAdd) {
712+
sideBarMenu[area] = menu.viewMenu.addItem({
713+
type: 'submenu',
714+
submenu: newMenu
715+
});
716+
}
717+
};
718+
719+
app.restored.then(() => {
720+
// Create menu entries for the left and right panel.
721+
if (menu) {
722+
const getSideBarLabel = (area: SideBarPanel.Area): string => {
723+
if (area === 'left') {
724+
return trans.__(`Left Sidebar`);
725+
} else {
726+
return trans.__(`Right Sidebar`);
727+
}
728+
};
729+
const leftArea = notebookShell.leftHandler.area;
730+
const leftLabel = getSideBarLabel(leftArea);
731+
updateMenu(leftArea, leftLabel);
732+
733+
const rightArea = notebookShell.rightHandler.area;
734+
const rightLabel = getSideBarLabel(rightArea);
735+
updateMenu(rightArea, rightLabel);
736+
737+
const handleSideBarChange = (
738+
sidebar: SideBarHandler,
739+
widget: Widget
740+
) => {
741+
const label = getSideBarLabel(sidebar.area);
742+
updateMenu(sidebar.area, label);
743+
};
744+
745+
notebookShell.leftHandler.widgetAdded.connect(handleSideBarChange);
746+
notebookShell.leftHandler.widgetRemoved.connect(handleSideBarChange);
747+
notebookShell.rightHandler.widgetAdded.connect(handleSideBarChange);
748+
notebookShell.rightHandler.widgetRemoved.connect(handleSideBarChange);
749+
}
750+
751+
// Add palette entries for side panels.
752+
if (palette) {
753+
const sideBarPalette = new SideBarPalette({
754+
commandPalette: palette as ICommandPalette,
755+
command: CommandIDs.togglePanel
756+
});
757+
758+
notebookShell.leftHandler.widgets.forEach(widget => {
759+
sideBarPalette.addItem(widget, notebookShell.leftHandler.area);
760+
});
761+
762+
notebookShell.rightHandler.widgets.forEach(widget => {
763+
sideBarPalette.addItem(widget, notebookShell.rightHandler.area);
764+
});
765+
766+
// Update menu and palette when widgets are added or removed from sidebars.
767+
notebookShell.leftHandler.widgetAdded.connect((sidebar, widget) => {
768+
sideBarPalette.addItem(widget, sidebar.area);
769+
});
770+
notebookShell.leftHandler.widgetRemoved.connect((sidebar, widget) => {
771+
sideBarPalette.removeItem(widget, sidebar.area);
772+
});
773+
notebookShell.rightHandler.widgetAdded.connect((sidebar, widget) => {
774+
sideBarPalette.addItem(widget, sidebar.area);
775+
});
776+
notebookShell.rightHandler.widgetRemoved.connect((sidebar, widget) => {
777+
sideBarPalette.removeItem(widget, sidebar.area);
778+
});
779+
}
780+
});
781+
}
782+
};
783+
560784
/**
561785
* The default tree route resolver plugin.
562786
*/
@@ -711,6 +935,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
711935
router,
712936
sessionDialogs,
713937
shell,
938+
sidebarVisibility,
714939
status,
715940
tabTitle,
716941
title,

0 commit comments

Comments
 (0)