diff --git a/.gitignore b/.gitignore index c2da64815b..06e98145ab 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,9 @@ ui-tests/playwright-report ui-tests/.yarn/* ui-tests/.pnp.* +# keep potential upstream patches +!.yarn/patches + # generated html notebook/templates/*.html diff --git a/app/package.json b/app/package.json index 9d23d56540..99df4c46c5 100644 --- a/app/package.json +++ b/app/package.json @@ -63,6 +63,7 @@ "@jupyterlab/imageviewer-extension": "~4.4.0-rc.1", "@jupyterlab/javascript-extension": "~4.4.0-rc.1", "@jupyterlab/json-extension": "~4.4.0-rc.1", + "@jupyterlab/logconsole-extension": "~4.4.0-rc.1", "@jupyterlab/lsp": "~4.4.0-rc.1", "@jupyterlab/lsp-extension": "~4.4.0-rc.1", "@jupyterlab/mainmenu": "~4.4.0-rc.1", @@ -107,18 +108,18 @@ "@jupyterlab/vega5-extension": "~4.4.0-rc.1", "@lezer/common": "~1.2.1", "@lezer/highlight": "~1.2.0", - "@lumino/algorithm": "~2.0.2", - "@lumino/application": "~2.4.2", - "@lumino/commands": "~2.3.1", - "@lumino/coreutils": "~2.2.0", - "@lumino/disposable": "~2.1.3", - "@lumino/domutils": "~2.0.2", - "@lumino/dragdrop": "~2.1.5", - "@lumino/messaging": "~2.0.2", - "@lumino/properties": "~2.0.2", - "@lumino/signaling": "~2.1.3", - "@lumino/virtualdom": "~2.0.2", - "@lumino/widgets": "~2.6.0", + "@lumino/algorithm": "~2.0.3", + "@lumino/application": "~2.4.3", + "@lumino/commands": "~2.3.2", + "@lumino/coreutils": "~2.2.1", + "@lumino/disposable": "~2.1.4", + "@lumino/domutils": "~2.0.3", + "@lumino/dragdrop": "~2.1.6", + "@lumino/messaging": "~2.0.3", + "@lumino/properties": "~2.0.3", + "@lumino/signaling": "~2.1.4", + "@lumino/virtualdom": "~2.0.3", + "@lumino/widgets": "~2.7.0", "react": "~18.2.0", "react-dom": "~18.2.0", "yjs": "~13.6.8" @@ -158,6 +159,7 @@ "@jupyterlab/imageviewer-extension": "~4.4.0-rc.1", "@jupyterlab/javascript-extension": "~4.4.0-rc.1", "@jupyterlab/json-extension": "~4.4.0-rc.1", + "@jupyterlab/logconsole-extension": "~4.4.0-rc.1", "@jupyterlab/lsp": "~4.4.0-rc.1", "@jupyterlab/lsp-extension": "~4.4.0-rc.1", "@jupyterlab/mainmenu-extension": "~4.4.0-rc.1", @@ -337,6 +339,7 @@ "@jupyterlab/debugger-extension:sidebar", "@jupyterlab/debugger-extension:sources" ], + "@jupyterlab/logconsole-extension": true, "@jupyterlab/metadataform-extension": true, "@jupyterlab/notebook-extension": [ "@jupyterlab/notebook-extension:active-cell-tool", diff --git a/packages/application/src/shell.ts b/packages/application/src/shell.ts index aa8b52e7e1..65a7159c14 100644 --- a/packages/application/src/shell.ts +++ b/packages/application/src/shell.ts @@ -14,9 +14,11 @@ import { FocusTracker, Panel, SplitPanel, + TabPanel, Widget, } from '@lumino/widgets'; import { PanelHandler, SidePanelHandler } from './panelhandler'; +import { TabPanelSvg } from '@jupyterlab/ui-components'; /** * The Jupyter Notebook application shell token. @@ -37,7 +39,7 @@ export namespace INotebookShell { /** * The areas of the application shell where widgets can reside. */ - export type Area = 'main' | 'top' | 'menu' | 'left' | 'right'; + export type Area = 'main' | 'top' | 'menu' | 'left' | 'right' | 'down'; /** * Widget position @@ -134,6 +136,18 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { middlePanel.addWidget(this._spacer_bottom); middlePanel.layout = middleLayout; + const vsplitPanel = new SplitPanel(); + vsplitPanel.id = 'jp-main-vsplit-panel'; + vsplitPanel.spacing = 1; + vsplitPanel.orientation = 'vertical'; + SplitPanel.setStretch(vsplitPanel, 1); + + const downPanel = new TabPanelSvg({ + tabsMovable: true, + }); + this._downPanel = downPanel; + this._downPanel.id = 'jp-down-stack'; + // TODO: Consider storing this as an attribute this._hsplitPanel if saving/restoring layout needed const hsplitPanel = new SplitPanel(); hsplitPanel.id = 'main-split-panel'; @@ -153,8 +167,21 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { // panel. hsplitPanel.setRelativeSizes([1, 2.5, 1]); + vsplitPanel.addWidget(hsplitPanel); + vsplitPanel.addWidget(downPanel); + rootLayout.spacing = 0; - rootLayout.addWidget(hsplitPanel); + rootLayout.addWidget(vsplitPanel); + + // initially hiding the down panel + this._downPanel.hide(); + + // Connect down panel change listeners + this._downPanel.tabBar.tabMoved.connect(this._onTabPanelChanged, this); + this._downPanel.stackedPanel.widgetRemoved.connect( + this._onTabPanelChanged, + this + ); this.layout = rootLayout; @@ -267,7 +294,7 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { */ activateById(id: string): void { // Search all areas that can have widgets for this widget, starting with main. - for (const area of ['main', 'top', 'left', 'right', 'menu']) { + for (const area of ['main', 'top', 'left', 'right', 'menu', 'down']) { const widget = find( this.widgets(area as INotebookShell.Area), (w) => w.id === id @@ -277,6 +304,9 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { this.expandLeft(id); } else if (area === 'right') { this.expandRight(id); + } else if (area === 'down') { + this._downPanel.show(); + widget.activate(); } else { widget.activate(); } @@ -342,6 +372,8 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { return this._leftHandler.addWidget(widget, rank); case 'right': return this._rightHandler.addWidget(widget, rank); + case 'down': + return this._downPanel.addWidget(widget); default: console.warn(`Cannot add widget to area: ${area}`); } @@ -385,6 +417,9 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { case 'right': yield* this._rightHandler.widgets; return; + case 'down': + yield* this._downPanel.widgets; + return; default: console.error(`This shell has no area called "${area}"`); return; @@ -432,6 +467,15 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { this._userLayout = configuration; } + /** + * Handle a change on the down panel widgets + */ + private _onTabPanelChanged(): void { + if (this._downPanel.stackedPanel.widgets.length === 0) { + this._downPanel.hide(); + } + } + private _topWrapper: Panel; private _topHandler: PanelHandler; private _menuWrapper: Panel; @@ -442,6 +486,7 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell { private _spacer_bottom: Widget; private _skipLinkWidgetHandler: Private.SkipLinkWidgetHandler; private _main: Panel; + private _downPanel: TabPanel; private _translator: ITranslator = nullTranslator; private _currentChanged = new Signal>( this diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png index dd2a993c7d..580f322ed5 100644 Binary files a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png index 8c2b352ce2..115770b9eb 100644 Binary files a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png differ diff --git a/ui-tests/test/notebook.spec.ts b/ui-tests/test/notebook.spec.ts index 4a7c2bcf9f..9ecab41844 100644 --- a/ui-tests/test/notebook.spec.ts +++ b/ui-tests/test/notebook.spec.ts @@ -206,4 +206,21 @@ test.describe('Notebook', () => { await page.menu.clickMenuItem(menuPath); await expect(notebookPanel).not.toHaveClass(/jp-mod-fullwidth/); }); + + test('Open the log console widget in the down area', async ({ + page, + tmpPath, + }) => { + const notebook = 'simple.ipynb'; + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${notebook}`), + `${tmpPath}/${notebook}` + ); + await page.goto(`notebooks/${tmpPath}/${notebook}`); + + const menuPath = 'View>Show Log Console'; + await page.menu.clickMenuItem(menuPath); + + await expect(page.locator('.jp-LogConsole')).toBeVisible(); + }); }); diff --git a/yarn.lock b/yarn.lock index e6af74f4db..ab51d6a9f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2167,6 +2167,7 @@ __metadata: "@jupyterlab/imageviewer-extension": ~4.4.0-rc.1 "@jupyterlab/javascript-extension": ~4.4.0-rc.1 "@jupyterlab/json-extension": ~4.4.0-rc.1 + "@jupyterlab/logconsole-extension": ~4.4.0-rc.1 "@jupyterlab/lsp": ~4.4.0-rc.1 "@jupyterlab/lsp-extension": ~4.4.0-rc.1 "@jupyterlab/mainmenu-extension": ~4.4.0-rc.1 @@ -3832,6 +3833,28 @@ __metadata: languageName: node linkType: hard +"@jupyterlab/logconsole-extension@npm:~4.4.0-rc.1": + version: 4.4.0-rc.1 + resolution: "@jupyterlab/logconsole-extension@npm:4.4.0-rc.1" + dependencies: + "@jupyterlab/application": ^4.4.0-rc.1 + "@jupyterlab/apputils": ^4.5.0-rc.1 + "@jupyterlab/coreutils": ^6.4.0-rc.1 + "@jupyterlab/docregistry": ^4.4.0-rc.1 + "@jupyterlab/logconsole": ^4.4.0-rc.1 + "@jupyterlab/rendermime": ^4.4.0-rc.1 + "@jupyterlab/settingregistry": ^4.4.0-rc.1 + "@jupyterlab/statusbar": ^4.4.0-rc.1 + "@jupyterlab/translation": ^4.4.0-rc.1 + "@jupyterlab/ui-components": ^4.4.0-rc.1 + "@lumino/coreutils": ^2.2.1 + "@lumino/signaling": ^2.1.4 + "@lumino/widgets": ^2.7.0 + react: ^18.2.0 + checksum: b49a7a16162dd612f8e7acb24d505cfaa4540957c82630c53392e5d93be8333795777c5a31cd3a5c3864f89cff0ba725980832a86778baf952a65eacf2659a14 + languageName: node + linkType: hard + "@jupyterlab/logconsole@npm:^4.4.0-rc.1": version: 4.4.0-rc.1 resolution: "@jupyterlab/logconsole@npm:4.4.0-rc.1"