Add a setting to enable the notebook to take up the full width (#7487)

* Toggle full width

* settings only

* enable more ways to toggle

* lint

* fixes

* add ui test

* fix snapshots

* reusable waitForNotebook

* more updates

* fix

* update
This commit is contained in:
Jeremy Tuloup 2024-10-23 09:59:04 +02:00 committed by GitHub
parent 676a0fec82
commit 390c4526df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 194 additions and 25 deletions

View File

@ -0,0 +1,27 @@
{
"title": "Jupyter Notebook Full Width Notebook",
"description": "Jupyter Notebook Notebook With settings",
"jupyter.lab.menus": {
"main": [
{
"id": "jp-mainmenu-view",
"items": [
{
"command": "notebook:toggle-full-width",
"rank": 4
}
]
}
]
},
"properties": {
"fullWidthNotebook": {
"type": "boolean",
"title": "Full Width Notebook",
"description": "Whether to the notebook should take up the full width of the application",
"default": false
}
},
"additionalProperties": false,
"type": "object"
}

View File

@ -64,6 +64,11 @@ const KERNEL_STATUS_FADE_OUT_CLASS = 'jp-NotebookKernelStatus-fade';
*/
const SCROLLED_OUTPUTS_CLASS = 'jp-mod-outputsScrolled';
/**
* The class for the full width notebook
*/
const FULL_WIDTH_NOTEBOOK_CLASS = 'jp-mod-fullwidth';
/**
* The command IDs used by the notebook plugins.
*/
@ -72,6 +77,11 @@ namespace CommandIDs {
* A command to open right sidebar for Editing Notebook Metadata
*/
export const openEditNotebookMetadata = 'notebook:edit-metadata';
/**
* A command to toggle full width of the notebook
*/
export const toggleFullWidth = 'notebook:toggle-full-width';
}
/**
@ -202,6 +212,83 @@ const openTreeTab: JupyterFrontEndPlugin<void> = {
},
};
/**
* A plugin to set the notebook to full width.
*/
const fullWidthNotebook: JupyterFrontEndPlugin<void> = {
id: '@jupyter-notebook/notebook-extension:full-width-notebook',
description: 'A plugin to set the notebook to full width.',
autoStart: true,
requires: [INotebookTracker],
optional: [ICommandPalette, ISettingRegistry, ITranslator],
activate: (
app: JupyterFrontEnd,
tracker: INotebookTracker,
palette: ICommandPalette | null,
settingRegistry: ISettingRegistry | null,
translator: ITranslator | null
) => {
const trans = (translator ?? nullTranslator).load('notebook');
let fullWidth = false;
const toggleFullWidth = () => {
const current = tracker.currentWidget;
fullWidth = !fullWidth;
if (!current) {
return;
}
const content = current;
content.toggleClass(FULL_WIDTH_NOTEBOOK_CLASS, fullWidth);
};
let notebookSettings: ISettingRegistry.ISettings;
if (settingRegistry) {
const loadSettings = settingRegistry.load(fullWidthNotebook.id);
const updateSettings = (settings: ISettingRegistry.ISettings): void => {
const newFullWidth = settings.get('fullWidthNotebook')
.composite as boolean;
if (newFullWidth !== fullWidth) {
toggleFullWidth();
}
};
Promise.all([loadSettings, app.restored])
.then(([settings]) => {
notebookSettings = settings;
updateSettings(settings);
settings.changed.connect((settings) => {
updateSettings(settings);
});
})
.catch((reason: Error) => {
console.error(reason.message);
});
}
app.commands.addCommand(CommandIDs.toggleFullWidth, {
label: trans.__('Enable Full Width Notebook'),
execute: () => {
toggleFullWidth();
if (notebookSettings) {
notebookSettings.set('fullWidthNotebook', fullWidth);
}
},
isEnabled: () => tracker.currentWidget !== null,
isToggled: () => fullWidth,
});
if (palette) {
palette.addItem({
command: CommandIDs.toggleFullWidth,
category: 'Notebook Operations',
});
}
},
};
/**
* The kernel logo plugin.
*/
@ -597,6 +684,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
closeTab,
openTreeTab,
editNotebookMetadata,
fullWidthNotebook,
kernelLogo,
kernelStatus,
notebookToolsWidget,

View File

@ -16,6 +16,16 @@
- compact view on mobile
*/
/* Make the notebook take up the full width of the page when jp-mod-fullwidth is set */
body[data-notebook='notebooks']
.jp-NotebookPanel.jp-mod-fullwidth
.jp-WindowedPanel-outer {
padding-left: unset;
padding-right: unset !important;
width: unset;
}
/* Keep the notebook centered on the page */
body[data-notebook='notebooks'] .jp-NotebookPanel-toolbar {

View File

@ -7,7 +7,7 @@ import { expect } from '@jupyterlab/galata';
import { test } from './fixtures';
import { hideAddCellButton, waitForKernelReady } from './utils';
import { waitForNotebook } from './utils';
test.describe('General', () => {
test('The notebook should render', async ({ page, tmpPath, browserName }) => {
@ -18,23 +18,6 @@ test.describe('General', () => {
);
await page.goto(`notebooks/${tmpPath}/${notebook}`);
// wait for the kernel status animations to be finished
await waitForKernelReady(page);
await page.waitForSelector(
".jp-Notebook-ExecutionIndicator[data-status='idle']"
);
const checkpointLocator = '.jp-NotebookCheckpoint';
// wait for the checkpoint indicator to be displayed
await page.waitForSelector(checkpointLocator);
// remove the amount of seconds manually since it might display strings such as "3 seconds ago"
await page
.locator(checkpointLocator)
.evaluate(
(element) => (element.innerHTML = 'Last Checkpoint: 3 seconds ago')
);
// check the notebook footer shows up on hover
const notebookFooter = '.jp-Notebook-footer';
await page.hover(notebookFooter);
@ -46,11 +29,8 @@ test.describe('General', () => {
// click to make the blue border around the cell disappear
await page.click('.jp-WindowedPanel-outer');
// special case for firefox headless issue
// see https://github.com/jupyter/notebook/pull/6872#issuecomment-1549594166 for more details
if (browserName === 'firefox') {
await hideAddCellButton(page);
}
// wait for the notebook to be ready
await waitForNotebook(page, browserName);
expect(await page.screenshot()).toMatchSnapshot('notebook.png');
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -7,7 +7,7 @@ import { expect } from '@jupyterlab/galata';
import { test } from './fixtures';
import { runAndAdvance, waitForKernelReady } from './utils';
import { waitForNotebook, runAndAdvance, waitForKernelReady } from './utils';
const NOTEBOOK = 'example.ipynb';
@ -175,4 +175,35 @@ test.describe('Notebook', () => {
expect(page.isClosed());
});
test('Toggle the full width of the notebook', async ({
page,
browserName,
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>Enable Full Width Notebook';
await page.menu.clickMenuItem(menuPath);
const notebookPanel = page.locator('.jp-NotebookPanel').first();
await expect(notebookPanel).toHaveClass(/jp-mod-fullwidth/);
// click to make the blue border around the cell disappear
await page.click('.jp-WindowedPanel-outer');
// wait for the notebook to be ready
await waitForNotebook(page, browserName);
expect(await page.screenshot()).toMatchSnapshot('notebook-full-width.png');
// undo the full width
await page.menu.clickMenuItem(menuPath);
await expect(notebookPanel).not.toHaveClass(/jp-mod-fullwidth/);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -30,7 +30,9 @@ export async function waitForKernelReady(page: Page): Promise<void> {
}, true);
return finished;
});
if (page.viewportSize()?.width > 600) {
const viewport = page.viewportSize();
const width = viewport?.width;
if (width && width > 600) {
await page.waitForSelector('.jp-DebuggerBugButton[aria-disabled="false"]');
}
}
@ -44,3 +46,34 @@ export async function hideAddCellButton(page: Page): Promise<void> {
.locator('.jp-Notebook-footer')
.evaluate((element) => (element.style.display = 'none'));
}
/**
* Wait for the notebook to be ready
*/
export async function waitForNotebook(
page: Page,
browserName = ''
): Promise<void> {
// wait for the kernel status animations to be finished
await waitForKernelReady(page);
await page.waitForSelector(
".jp-Notebook-ExecutionIndicator[data-status='idle']"
);
const checkpointLocator = '.jp-NotebookCheckpoint';
// wait for the checkpoint indicator to be displayed
await page.waitForSelector(checkpointLocator);
// remove the amount of seconds manually since it might display strings such as "3 seconds ago"
await page
.locator(checkpointLocator)
.evaluate(
(element) => (element.innerHTML = 'Last Checkpoint: 3 seconds ago')
);
// special case for firefox headless issue
// see https://github.com/jupyter/notebook/pull/6872#issuecomment-1549594166 for more details
if (browserName === 'firefox') {
await hideAddCellButton(page);
}
}