Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow extensions and users to customize easily toolbar items. #10469

Merged
merged 2 commits into from Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
145 changes: 142 additions & 3 deletions docs/source/extension/extension_points.rst
Expand Up @@ -55,6 +55,8 @@ might want to use the services in your extensions.
- ``@jupyterlab/apputils:ISplashScreen``: A service for the splash screen for the application.
Use this if you want to show the splash screen for your own purposes.
- ``@jupyterlab/apputils:IThemeManager``: A service for the theme manager for the application. This is used primarily in theme extensions to register new themes.
- ``@jupyterlab/apputils:IToolbarWidgetRegistry``: A registry for toolbar widgets. Require this
if you want to build the toolbar dynamically from a data definition (stored in settings for example).
- ``@jupyterlab/apputils:IWindowResolver``: A service for a window resolver for the
application. JupyterLab workspaces are given a name, which are determined using
the window resolver. Require this if you want to use the name of the current workspace.
Expand Down Expand Up @@ -262,7 +264,7 @@ where ``menuItem`` definition is:

.. literalinclude:: ../snippets/packages/settingregistry/src/plugin-schema.json
:language: json
:lines: 129-167
:lines: 142-180


The same example using the API is shown below. See the Lumino `docs
Expand Down Expand Up @@ -603,13 +605,13 @@ A menu must respect the following schema:

.. literalinclude:: ../snippets/packages/settingregistry/src/plugin-schema.json
:language: json
:lines: 72-125
:lines: 85-141

And an item must follow:

.. literalinclude:: ../snippets/packages/settingregistry/src/plugin-schema.json
:language: json
:lines: 129-167
:lines: 142-180

Menus added to the settings system will be editable by users using the ``mainmenu-extension``
settings. In particular, they can be disabled at the item or the menu level by setting the
Expand Down Expand Up @@ -750,6 +752,143 @@ When the ``labStatus`` busy state changes, we update the text content of the
item: statusWidget
});

.. _toolbar-registry:


Toolbar Registry
----------------

JupyterLab provides an infrastructure to define and customize toolbar widgets of ``DocumentWidget`` s
from the settings, which is similar to that defining the context menu and the main menu
bar. A typical example is the notebook toolbar as in the snippet below:

.. code:: typescript

function activatePlugin(
app: JupyterFrontEnd,
// ...
toolbarRegistry: IToolbarWidgetRegistry | null,
settingRegistry: ISettingRegistry | null
): NotebookWidgetFactory.IFactory {
const { commands } = app;
let toolbarFactory:
| ((widget: NotebookPanel) => DocumentRegistry.IToolbarItem[])
| undefined;

// Register notebook toolbar specific widgets
if (toolbarRegistry) {
toolbarRegistry.registerFactory<NotebookPanel>(FACTORY, 'cellType', panel =>
ToolbarItems.createCellTypeItem(panel, translator)
);

toolbarRegistry.registerFactory<NotebookPanel>(
FACTORY,
'kernelStatus',
panel => Toolbar.createKernelStatusItem(panel.sessionContext, translator)
);
// etc...

if (settingRegistry) {
// Create the factory
toolbarFactory = createToolbarFactory(
toolbarRegistry,
settingRegistry,
// Factory name
FACTORY,
// Setting id in which the toolbar items are defined
'@jupyterlab/notebook-extension:panel',
translator
);
}
}

const factory = new NotebookWidgetFactory({
name: FACTORY,
fileTypes: ['notebook'],
modelName: 'notebook',
defaultFor: ['notebook'],
// ...
toolbarFactory,
translator: translator
});
app.docRegistry.addWidgetFactory(factory);

The registry ``registerFactory`` method allows an extension to provide special widget for a unique pair
(factory name, toolbar item name). Then the helper ``createToolbarFactory`` can be used to extract the
toolbar definition from the settings and build the factory to pass to the widget factory.

The default toolbar items can be defined across multiple extensions by providing an entry in the ``"jupyter.lab.toolbars"``
mapping. For example for the notebook panel:

.. code:: js

"jupyter.lab.toolbars": {
"Notebook": [ // Factory name
// Item with non-default widget - it must be registered within an extension
{
"name": "save", // Unique toolbar item name
"rank": 10 // Item rank
},
// Item with default button widget triggering a command
{ "name": "insert", "command": "notebook:insert-cell-below", "rank": 20 },
{ "name": "cut", "command": "notebook:cut-cell", "rank": 21 },
{ "name": "copy", "command": "notebook:copy-cell", "rank": 22 },
{ "name": "paste", "command": "notebook:paste-cell-below", "rank": 23 },
{ "name": "run", "command": "runmenu:run", "rank": 30 },
{ "name": "interrupt", "command": "kernelmenu:interrupt", "rank": 31 },
{ "name": "restart", "command": "kernelmenu:restart", "rank": 32 },
{
"name": "restart-and-run",
"command": "runmenu:restart-and-run-all",
"rank": 33 // The default rank is 50
},
{ "name": "cellType", "rank": 40 },
// Horizontal spacer widget
{ "name": "spacer", "type": "spacer", "rank": 100 },
{ "name": "kernelName", "rank": 1000 },
{ "name": "kernelStatus", "rank": 1001 }
]
},
"jupyter.lab.transform": true,
"properties": {
"toolbar": {
"title": "Notebook panel toolbar items",
"items": {
"$ref": "#/definitions/toolbarItem"
},
"type": "array",
"default": []
}
}


The settings registry will merge those definitions from settings schema with any
user-provided overrides (customizations) transparently and save them under the
``toolbar`` property in the final settings object. The ``toolbar`` list will be used to
create the toolbar. Both the source settings schema and the final settings object
are identified by the plugin ID passed to ``createToolbarFactory``. The user can
customize the toolbar by adding new items or overriding existing ones (like
providing a different rank or adding ``"disabled": true`` to remove the item).

.. note::

You need to set ``jupyter.lab.transform`` to ``true`` in the plugin id that will gather all items.


The current widget factories supporting the toolbar customization are:

- ``Notebook``: Notebook panel toolbar
- ``Editor``: Text editor toolbar
- ``HTML Viewer``: HTML Viewer toolbar
- ``CSVTable``: CSV (Comma Separated Value) Viewer toolbar
- ``TSVTable``: TSV (Tabulation Separated Value) Viewer toolbar

Add the toolbar item must follow this definition:

.. literalinclude:: ../snippets/packages/settingregistry/src/plugin-schema.json
:language: json
:lines: 207-252

.. _widget-tracker:

Widget Tracker
Expand Down
126 changes: 74 additions & 52 deletions docs/source/extension/notebook.rst
Expand Up @@ -147,11 +147,30 @@ How to extend the Notebook plugin
We'll walk through two notebook extensions:

- adding a button to the toolbar
- adding a widget to the notebook header
- adding an ipywidgets extension

Adding a button to the toolbar
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Since JupyterLab 3.2, adding toolbar item can be done using a :ref:`toolbar-registry` and settings. In particular
for the notebook, if the button is linked to a new command, you can add a button in the toolbar using the
following JSON snippet in your extension settings file:

.. code:: js

"jupyter.lab.toolbars": {
"Notebook": [ // Widget factory name for which you want to add a toolbar item.
// Item with default button widget triggering a command
{ "name": "run", "command": "runmenu:run" }
]
}

You may add a ``rank`` attribute to modify the item position (the default value is 50).

Adding a widget to the notebook header
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Start from the cookie cutter extension template.

::
Expand All @@ -165,102 +184,105 @@ released npm packages, not the development versions.

::

npm install --save @jupyterlab/notebook @jupyterlab/application @jupyterlab/ui-components @jupyterlab/docregistry @lumino/disposable --legacy-peer-deps
jlpm add -D @jupyterlab/notebook @jupyterlab/application @jupyterlab/ui-components @jupyterlab/docregistry @lumino/disposable @lumino/widgets --legacy-peer-deps

Copy the following to ``src/index.ts``:

.. code:: typescript

import {
IDisposable, DisposableDelegate
} from '@lumino/disposable';
import { IDisposable, DisposableDelegate } from '@lumino/disposable';

import {
JupyterFrontEnd, JupyterFrontEndPlugin
} from '@jupyterlab/application';

import {
ToolbarButton
} from '@jupyterlab/ui-components';
import { Widget } from '@lumino/widgets';

import {
DocumentRegistry
} from '@jupyterlab/docregistry';
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';

import {
NotebookActions, NotebookPanel, INotebookModel
} from '@jupyterlab/notebook';
import { DocumentRegistry } from '@jupyterlab/docregistry';

import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook';

/**
* The plugin registration information.
*/
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'my-extension-name:buttonPlugin',
id: 'my-extension-name:widgetPlugin',
autoStart: true
};


/**
* A notebook widget extension that adds a button to the toolbar.
*/
export
class ButtonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
* A notebook widget extension that adds a widget in the notebook header (widget below the toolbar).
*/
export class WidgetExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension object.
*/
createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
let callback = () => {
NotebookActions.runAll(panel.content, context.sessionContext);
};
let button = new ToolbarButton({
className: 'myButton',
iconClass: 'fa fa-fast-forward',
onClick: callback,
tooltip: 'Run All'
});

panel.toolbar.insertItem(0, 'runAll', button);
* Create a new extension object.
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const widget = new Widget({ node: Private.createNode() });
widget.addClass('jp-myextension-myheader');

panel.contentHeader.insertWidget(0, widget);
return new DisposableDelegate(() => {
button.dispose();
widget.dispose();
});
}
}

/**
* Activate the extension.
*/
function activate(app: JupyterFrontEnd) {
app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
};

* Activate the extension.
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new WidgetExtension());
}

/**
* Export the plugin as default.
*/
* Export the plugin as default.
*/
export default plugin;

/**
* Private helpers
*/
namespace Private {
/**
* Generate the widget node
*/
export function createNode(): HTMLElement {
const span = document.createElement('span');
span.textContent = 'My custom header';
return span;
}
}


And the following to ``style/base.css``:

.. code:: css

.myButton.jp-Button.minimal .jp-Icon {
color: black;
}
.jp-myextension-myheader {
min-height: 20px;
background-color: lightsalmon;
}


Run the following commands:

::

pip install -e .
pip install jupyter_packaging
pip install jupyter-packaging
jupyter labextension develop . --overwrite
jupyter lab

Open a notebook and observe the new "Run All" button.
Open a notebook and observe the new "Header" widget.


The *ipywidgets* third party extension
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions packages/apputils-extension/src/index.ts
Expand Up @@ -37,6 +37,7 @@ import { Debouncer, Throttler } from '@lumino/polling';
import { Palette } from './palette';
import { settingsPlugin } from './settingsplugin';
import { themesPaletteMenuPlugin, themesPlugin } from './themesplugins';
import { toolbarRegistry } from './toolbarregistryplugin';
import { workspacesPlugin } from './workspacesplugin';

/**
Expand Down Expand Up @@ -624,6 +625,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
themesPlugin,
themesPaletteMenuPlugin,
toggleHeader,
toolbarRegistry,
utilityCommands,
workspacesPlugin
];
Expand Down