Skip to content

Commit 92f2336

Browse files
andrii-iJasonWeill
andauthored
Add option to open a notebook in NbClassic if it is installed; show "Open in..." dropdown menu if there are multiple options, show single button otherwise (#6866)
* add option to open in nbclassic if installed * account that getOption() can return truthy 'false' * immidiately convert nbclassicInstalled to boolean * Capitalize NbClassic as in PyPI per @JasonWeill * capitalize commandDescription * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * Update packages/lab-extension/src/index.ts Co-authored-by: Jason Weill <[email protected]> * add openInNewIcon based on ##6793 by @JasonWeill * show single 'open' button if only one option is available * add button styling * add css class to toolbarButton * rename addCommand to addSwitcherCommand * set page_config["nbclassic_installed"] * Update snapshots * fix general.spec.ts lint problem as detected in CI run https://github.com/jupyter/notebook/actions/runs/5008582825/jobs/8976612053?pr=6866 * Use optional chaining instead of non-null assertion to fix lint error https://github.com/jupyter/notebook/actions/runs/5008628805/jobs/8976713072?pr=6866 * add new line to try to fix eslint error * fix prettier CI errors * Revert "fix general.spec.ts lint problem as detected in CI run https://github.com/jupyter/notebook/actions/runs/5008582825/jobs/8976612053?pr=6866" This reverts commit 9f0b544. * Revert "add openInNewIcon based on ##6793 by @JasonWeill" This reverts commit eca0a4e. * use launchIcon instead of openInNewIcon * fix nbclassic urlprefix * update general snapshots to account for use of launchIcon * update mobile snapshots to account for launchIcon * check if extension is enabled, not installed * add bundled server extension * Revert "add bundled server extension" This reverts commit 7d261db. * set page_config in initalize_handlers --------- Co-authored-by: Jason Weill <[email protected]>
1 parent 95baeab commit 92f2336

File tree

9 files changed

+110
-36
lines changed

9 files changed

+110
-36
lines changed

notebook/app.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,22 @@ def _default_user_settings_dir(self):
261261
def _default_workspaces_dir(self):
262262
return get_workspaces_dir()
263263

264+
def server_extension_is_enabled(self, extension):
265+
"""Check if server extension is enabled."""
266+
try:
267+
extension_enabled = (
268+
self.serverapp.extension_manager.extensions[extension].enabled is True
269+
)
270+
except (AttributeError, KeyError, TypeError):
271+
extension_enabled = False
272+
return extension_enabled
273+
264274
def initialize_handlers(self):
265275
"""Initialize handlers."""
276+
page_config = self.serverapp.web_app.settings.setdefault("page_config_data", {})
277+
nbclassic_enabled = self.server_extension_is_enabled("nbclassic")
278+
page_config["nbclassic_enabled"] = nbclassic_enabled
279+
266280
self.handlers.append(
267281
(
268282
rf"/{self.file_url_prefix}/((?!.*\.ipynb($|\?)).*)",

packages/lab-extension/schema/interface-switcher.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
"args": {
2323
"isMenu": true
2424
}
25+
},
26+
{
27+
"command": "jupyter-notebook:open-nbclassic",
28+
"rank": 10,
29+
"args": {
30+
"isMenu": true
31+
}
2532
}
2633
]
2734
}

packages/lab-extension/src/index.ts

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
1515

1616
import { ITranslator } from '@jupyterlab/translation';
1717

18-
import { Menu, MenuBar } from '@lumino/widgets';
18+
import { Menu, MenuBar, Widget } from '@lumino/widgets';
1919

2020
import { INotebookShell } from '@jupyter-notebook/application';
2121

22-
import { caretDownIcon } from '@jupyterlab/ui-components';
22+
import {
23+
caretDownIcon,
24+
CommandToolbarButton,
25+
launchIcon,
26+
} from '@jupyterlab/ui-components';
2327

2428
/**
2529
* The command IDs used by the application plugin.
@@ -39,6 +43,11 @@ namespace CommandIDs {
3943
* Open in JupyterLab
4044
*/
4145
export const openLab = 'jupyter-notebook:open-lab';
46+
47+
/**
48+
* Open in NbClassic
49+
*/
50+
export const openNbClassic = 'jupyter-notebook:open-nbclassic';
4251
}
4352

4453
interface ISwitcherChoice {
@@ -74,14 +83,40 @@ const interfaceSwitcher: JupyterFrontEndPlugin<void> = {
7483
const { commands, shell } = app;
7584
const baseUrl = PageConfig.getBaseUrl();
7685
const trans = translator.load('notebook');
77-
const overflowOptions = {
78-
overflowMenuOptions: { isVisible: false },
79-
};
80-
const menubar = new MenuBar(overflowOptions);
86+
const nbClassicEnabled =
87+
PageConfig.getOption('nbclassic_enabled') === 'true';
8188
const switcher = new Menu({ commands });
82-
switcher.title.label = trans.__('Open in...');
83-
switcher.title.icon = caretDownIcon;
84-
menubar.addMenu(switcher);
89+
const switcherOptions: ISwitcherChoice[] = [];
90+
91+
if (!notebookShell) {
92+
switcherOptions.push({
93+
command: CommandIDs.openNotebook,
94+
commandLabel: trans.__('Notebook'),
95+
commandDescription: trans.__('Open in %1', 'Jupyter Notebook'),
96+
buttonLabel: 'openNotebook',
97+
urlPrefix: `${baseUrl}tree/`,
98+
});
99+
}
100+
101+
if (!labShell) {
102+
switcherOptions.push({
103+
command: CommandIDs.openLab,
104+
commandLabel: trans.__('JupyterLab'),
105+
commandDescription: trans.__('Open in %1', 'JupyterLab'),
106+
buttonLabel: 'openLab',
107+
urlPrefix: `${baseUrl}doc/tree/`,
108+
});
109+
}
110+
111+
if (nbClassicEnabled) {
112+
switcherOptions.push({
113+
command: CommandIDs.openNbClassic,
114+
commandLabel: trans.__('NbClassic'),
115+
commandDescription: trans.__('Open in %1', 'NbClassic'),
116+
buttonLabel: 'openNbClassic',
117+
urlPrefix: `${baseUrl}nbclassic/notebooks/`,
118+
});
119+
}
85120

86121
const isEnabled = () => {
87122
return (
@@ -90,7 +125,7 @@ const interfaceSwitcher: JupyterFrontEndPlugin<void> = {
90125
);
91126
};
92127

93-
const addInterface = (option: ISwitcherChoice) => {
128+
const addSwitcherCommand = (option: ISwitcherChoice) => {
94129
const { command, commandLabel, commandDescription, urlPrefix } = option;
95130

96131
const execute = () => {
@@ -121,40 +156,48 @@ const interfaceSwitcher: JupyterFrontEndPlugin<void> = {
121156
args: { isPalette: true },
122157
});
123158
}
124-
125-
switcher.addItem({ command });
126159
};
127160

128-
if (!notebookShell) {
129-
addInterface({
130-
command: CommandIDs.openNotebook,
131-
commandLabel: trans.__('Notebook'),
132-
commandDescription: trans.__('Open in %1', 'Jupyter Notebook'),
133-
buttonLabel: 'openNotebook',
134-
urlPrefix: `${baseUrl}tree/`,
135-
});
136-
}
161+
switcherOptions.forEach((option) => {
162+
const { command } = option;
163+
addSwitcherCommand(option);
164+
switcher.addItem({ command });
165+
});
137166

138-
if (!labShell) {
139-
addInterface({
140-
command: CommandIDs.openLab,
141-
commandLabel: trans.__('JupyterLab'),
142-
commandDescription: trans.__('Open in %1', 'JupyterLab'),
143-
buttonLabel: 'openLab',
144-
urlPrefix: `${baseUrl}doc/tree/`,
145-
});
167+
let toolbarFactory: (panel: NotebookPanel) => Widget;
168+
if (switcherOptions.length === 1) {
169+
toolbarFactory = (panel: NotebookPanel) => {
170+
const toolbarButton = new CommandToolbarButton({
171+
commands,
172+
id: switcherOptions[0].command,
173+
label: switcherOptions[0].commandLabel,
174+
icon: launchIcon,
175+
});
176+
toolbarButton.addClass('jp-nb-interface-switcher-button');
177+
return toolbarButton;
178+
};
179+
} else {
180+
const overflowOptions = {
181+
overflowMenuOptions: { isVisible: false },
182+
};
183+
const menubar = new MenuBar(overflowOptions);
184+
switcher.title.label = trans.__('Open in...');
185+
switcher.title.icon = caretDownIcon;
186+
menubar.addMenu(switcher);
187+
188+
toolbarFactory = (panel: NotebookPanel) => {
189+
const menubar = new MenuBar(overflowOptions);
190+
menubar.addMenu(switcher);
191+
menubar.addClass('jp-InterfaceSwitcher');
192+
return menubar;
193+
};
146194
}
147195

148196
if (toolbarRegistry) {
149197
toolbarRegistry.addFactory<NotebookPanel>(
150198
'Notebook',
151199
'interfaceSwitcher',
152-
(panel) => {
153-
const menubar = new MenuBar(overflowOptions);
154-
menubar.addMenu(switcher);
155-
menubar.addClass('jp-InterfaceSwitcher');
156-
return menubar;
157-
}
200+
toolbarFactory
158201
);
159202
}
160203
},

packages/lab-extension/style/base.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,13 @@
1313
.jp-InterfaceSwitcher .lm-MenuBar-itemIcon svg {
1414
vertical-align: sub;
1515
}
16+
17+
.jp-nb-interface-switcher-button > .jp-ToolbarButtonComponent {
18+
flex-direction: row-reverse;
19+
}
20+
21+
.jp-nb-interface-switcher-button
22+
> .jp-ToolbarButtonComponent
23+
> .jp-ToolbarButtonComponent-icon {
24+
padding-left: 3px;
25+
}
637 Bytes
Loading
483 Bytes
Loading

ui-tests/test/menus.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ test.describe('Notebook Menus', () => {
4444
const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`;
4545
const menu = await page.menu.getOpenMenu();
4646
expect(menu).toBeDefined();
47-
expect(await menu!.screenshot()).toMatchSnapshot(imageName.toLowerCase());
47+
expect(await menu?.screenshot()).toMatchSnapshot(imageName.toLowerCase());
4848
});
4949
});
5050
});
478 Bytes
Loading
385 Bytes
Loading

0 commit comments

Comments
 (0)