diff --git a/notebook/app.py b/notebook/app.py index 59a9b3aad..fe20645cc 100644 --- a/notebook/app.py +++ b/notebook/app.py @@ -261,8 +261,22 @@ class JupyterNotebookApp(NotebookConfigShimMixin, LabServerApp): def _default_workspaces_dir(self): return get_workspaces_dir() + def server_extension_is_enabled(self, extension): + """Check if server extension is enabled.""" + try: + extension_enabled = ( + self.serverapp.extension_manager.extensions[extension].enabled is True + ) + except (AttributeError, KeyError, TypeError): + extension_enabled = False + return extension_enabled + def initialize_handlers(self): """Initialize handlers.""" + page_config = self.serverapp.web_app.settings.setdefault("page_config_data", {}) + nbclassic_enabled = self.server_extension_is_enabled("nbclassic") + page_config["nbclassic_enabled"] = nbclassic_enabled + self.handlers.append( ( rf"/{self.file_url_prefix}/((?!.*\.ipynb($|\?)).*)", diff --git a/packages/lab-extension/schema/interface-switcher.json b/packages/lab-extension/schema/interface-switcher.json index a36757ffd..421ccb7c8 100644 --- a/packages/lab-extension/schema/interface-switcher.json +++ b/packages/lab-extension/schema/interface-switcher.json @@ -22,6 +22,13 @@ "args": { "isMenu": true } + }, + { + "command": "jupyter-notebook:open-nbclassic", + "rank": 10, + "args": { + "isMenu": true + } } ] } diff --git a/packages/lab-extension/src/index.ts b/packages/lab-extension/src/index.ts index dbd5da853..dea33fb32 100644 --- a/packages/lab-extension/src/index.ts +++ b/packages/lab-extension/src/index.ts @@ -15,11 +15,15 @@ import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; import { ITranslator } from '@jupyterlab/translation'; -import { Menu, MenuBar } from '@lumino/widgets'; +import { Menu, MenuBar, Widget } from '@lumino/widgets'; import { INotebookShell } from '@jupyter-notebook/application'; -import { caretDownIcon } from '@jupyterlab/ui-components'; +import { + caretDownIcon, + CommandToolbarButton, + launchIcon, +} from '@jupyterlab/ui-components'; /** * The command IDs used by the application plugin. @@ -39,6 +43,11 @@ namespace CommandIDs { * Open in JupyterLab */ export const openLab = 'jupyter-notebook:open-lab'; + + /** + * Open in NbClassic + */ + export const openNbClassic = 'jupyter-notebook:open-nbclassic'; } interface ISwitcherChoice { @@ -74,14 +83,40 @@ const interfaceSwitcher: JupyterFrontEndPlugin = { const { commands, shell } = app; const baseUrl = PageConfig.getBaseUrl(); const trans = translator.load('notebook'); - const overflowOptions = { - overflowMenuOptions: { isVisible: false }, - }; - const menubar = new MenuBar(overflowOptions); + const nbClassicEnabled = + PageConfig.getOption('nbclassic_enabled') === 'true'; const switcher = new Menu({ commands }); - switcher.title.label = trans.__('Open in...'); - switcher.title.icon = caretDownIcon; - menubar.addMenu(switcher); + const switcherOptions: ISwitcherChoice[] = []; + + if (!notebookShell) { + switcherOptions.push({ + command: CommandIDs.openNotebook, + commandLabel: trans.__('Notebook'), + commandDescription: trans.__('Open in %1', 'Jupyter Notebook'), + buttonLabel: 'openNotebook', + urlPrefix: `${baseUrl}tree/`, + }); + } + + if (!labShell) { + switcherOptions.push({ + command: CommandIDs.openLab, + commandLabel: trans.__('JupyterLab'), + commandDescription: trans.__('Open in %1', 'JupyterLab'), + buttonLabel: 'openLab', + urlPrefix: `${baseUrl}doc/tree/`, + }); + } + + if (nbClassicEnabled) { + switcherOptions.push({ + command: CommandIDs.openNbClassic, + commandLabel: trans.__('NbClassic'), + commandDescription: trans.__('Open in %1', 'NbClassic'), + buttonLabel: 'openNbClassic', + urlPrefix: `${baseUrl}nbclassic/notebooks/`, + }); + } const isEnabled = () => { return ( @@ -90,7 +125,7 @@ const interfaceSwitcher: JupyterFrontEndPlugin = { ); }; - const addInterface = (option: ISwitcherChoice) => { + const addSwitcherCommand = (option: ISwitcherChoice) => { const { command, commandLabel, commandDescription, urlPrefix } = option; const execute = () => { @@ -121,40 +156,48 @@ const interfaceSwitcher: JupyterFrontEndPlugin = { args: { isPalette: true }, }); } - - switcher.addItem({ command }); }; - if (!notebookShell) { - addInterface({ - command: CommandIDs.openNotebook, - commandLabel: trans.__('Notebook'), - commandDescription: trans.__('Open in %1', 'Jupyter Notebook'), - buttonLabel: 'openNotebook', - urlPrefix: `${baseUrl}tree/`, - }); - } + switcherOptions.forEach((option) => { + const { command } = option; + addSwitcherCommand(option); + switcher.addItem({ command }); + }); - if (!labShell) { - addInterface({ - command: CommandIDs.openLab, - commandLabel: trans.__('JupyterLab'), - commandDescription: trans.__('Open in %1', 'JupyterLab'), - buttonLabel: 'openLab', - urlPrefix: `${baseUrl}doc/tree/`, - }); + let toolbarFactory: (panel: NotebookPanel) => Widget; + if (switcherOptions.length === 1) { + toolbarFactory = (panel: NotebookPanel) => { + const toolbarButton = new CommandToolbarButton({ + commands, + id: switcherOptions[0].command, + label: switcherOptions[0].commandLabel, + icon: launchIcon, + }); + toolbarButton.addClass('jp-nb-interface-switcher-button'); + return toolbarButton; + }; + } else { + const overflowOptions = { + overflowMenuOptions: { isVisible: false }, + }; + const menubar = new MenuBar(overflowOptions); + switcher.title.label = trans.__('Open in...'); + switcher.title.icon = caretDownIcon; + menubar.addMenu(switcher); + + toolbarFactory = (panel: NotebookPanel) => { + const menubar = new MenuBar(overflowOptions); + menubar.addMenu(switcher); + menubar.addClass('jp-InterfaceSwitcher'); + return menubar; + }; } if (toolbarRegistry) { toolbarRegistry.addFactory( 'Notebook', 'interfaceSwitcher', - (panel) => { - const menubar = new MenuBar(overflowOptions); - menubar.addMenu(switcher); - menubar.addClass('jp-InterfaceSwitcher'); - return menubar; - } + toolbarFactory ); } }, diff --git a/packages/lab-extension/style/base.css b/packages/lab-extension/style/base.css index 7f7325918..f03f7254a 100644 --- a/packages/lab-extension/style/base.css +++ b/packages/lab-extension/style/base.css @@ -13,3 +13,13 @@ .jp-InterfaceSwitcher .lm-MenuBar-itemIcon svg { vertical-align: sub; } + +.jp-nb-interface-switcher-button > .jp-ToolbarButtonComponent { + flex-direction: row-reverse; +} + +.jp-nb-interface-switcher-button + > .jp-ToolbarButtonComponent + > .jp-ToolbarButtonComponent-icon { + padding-left: 3px; +} diff --git a/ui-tests/test/general.spec.ts-snapshots/notebook-chromium-linux.png b/ui-tests/test/general.spec.ts-snapshots/notebook-chromium-linux.png index 89882e85e..b38ce59a7 100644 Binary files a/ui-tests/test/general.spec.ts-snapshots/notebook-chromium-linux.png and b/ui-tests/test/general.spec.ts-snapshots/notebook-chromium-linux.png differ diff --git a/ui-tests/test/general.spec.ts-snapshots/notebook-firefox-linux.png b/ui-tests/test/general.spec.ts-snapshots/notebook-firefox-linux.png index 7f1f6106e..e1685bc1f 100644 Binary files a/ui-tests/test/general.spec.ts-snapshots/notebook-firefox-linux.png and b/ui-tests/test/general.spec.ts-snapshots/notebook-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts b/ui-tests/test/menus.spec.ts index fe0f82334..96ab33a1d 100644 --- a/ui-tests/test/menus.spec.ts +++ b/ui-tests/test/menus.spec.ts @@ -44,7 +44,7 @@ test.describe('Notebook Menus', () => { const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`; const menu = await page.menu.getOpenMenu(); expect(menu).toBeDefined(); - expect(await menu!.screenshot()).toMatchSnapshot(imageName.toLowerCase()); + expect(await menu?.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); }); }); diff --git a/ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png b/ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png index b93533ca0..eebba8fad 100644 Binary files a/ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png and b/ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png differ diff --git a/ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png b/ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png index 85a3badaa..4f185dd03 100644 Binary files a/ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png and b/ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png differ