Skip to content

Commit f05fb90

Browse files
fasihafcollonval
andauthored
Example for MainAreaWidget's contentHeader (#185)
* init from cookiecutter * correct package.json to follow others * Basic extension demonstrating mainAreaWidget's contentHeader * update READMEs * prettier * no need to use innerHTML, just use textContent * add extra notes about multiple MainAreaWidgets * prettier * add new extension to integration test * Playwright UI test * added of @fcollonval's code review suggestions * prettier * correct embedme (code snippet line numbers) * Fix CI * Make config files homogeneous with hello-world * Fix playwright config diff * Fix doc links (#2) * Fix doc links * Remove example prettier config * Fix linter * Fix labextension name in setup.py * Apply suggestions from code review Co-authored-by: Frédéric Collonval <[email protected]> * Apply suggestions from code review: code Co-authored-by: Frédéric Collonval <[email protected]> Co-authored-by: Frédéric Collonval <[email protected]>
1 parent c0281bc commit f05fb90

File tree

26 files changed

+724
-18
lines changed

26 files changed

+724
-18
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
- toolbar-button
3434
- widgets
3535
- completer
36+
- contentheader
3637
os: [ubuntu-latest, macos-latest, windows-latest]
3738

3839
defaults:

README.md

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@
77
2. [Develop by Examples](#develop-by-examples)
88
1. [Commands](#commands)
99
2. [Command Palette](#command-palette)
10-
3. [Context Menu](#context-menu)
11-
4. [Custom Log Console](#custom-log-console)
12-
5. [Datagrid](#datagrid)
13-
6. _[Hello World](#hello-world)_
14-
7. [Kernel Messaging](#kernel-messaging)
15-
8. [Kernel Output](#kernel-output)
16-
9. [Launcher](#launcher)
17-
10. [Log Messages](#log-messages)
18-
11. [Main Menu](#main-menu)
19-
12. [React Widget](#react-widget)
20-
13. _[Server Hello World](#server-hello-world)_
21-
14. [Settings](#settings)
22-
15. [Signals](#signals)
23-
16. [State](#state)
24-
17. [Toolbar Item](#toolbar-item)
25-
18. [Widgets](#widgets)
10+
3. Main Widget [Content Header](#main-widget-content-header)
11+
4. [Context Menu](#context-menu)
12+
5. [Custom Log Console](#custom-log-console)
13+
6. [Datagrid](#datagrid)
14+
7. _[Hello World](#hello-world)_
15+
8. [Kernel Messaging](#kernel-messaging)
16+
9. [Kernel Output](#kernel-output)
17+
10. [Launcher](#launcher)
18+
11. [Log Messages](#log-messages)
19+
12. [Main Menu](#main-menu)
20+
13. [React Widget](#react-widget)
21+
14. _[Server Hello World](#server-hello-world)_
22+
15. [Settings](#settings)
23+
16. [Signals](#signals)
24+
17. [State](#state)
25+
18. [Toolbar Item](#toolbar-item)
26+
19. [Widgets](#widgets)
2627
3. [Prerequisites](#prerequisites)
2728
4. [Develop and Use the Examples](#develop-and-use-the-examples)
2829
5. [Test the Examples](#test-the-examples)
@@ -83,6 +84,7 @@ Start with the [Hello World](hello-world) and then jump to the topic you are int
8384
- [Commands](commands)
8485
- [Command Palette](command-palette)
8586
- [Completer](completer)
87+
- Main Widget [Content Header](contentheader)
8688
- [Context Menu](context-menu)
8789
- [Custom Log Console](custom-log-console)
8890
- [Datagrid](datagrid)
@@ -130,6 +132,12 @@ Customize tab autocomplete data sources.
130132

131133
[![Completer](completer/preview.png)](completer)
132134

135+
### Main Widget [Content Header](contentheader)
136+
137+
Put widgets at the top of a main JupyterLab area widget.
138+
139+
[![contentHeader](contentheader/preview.gif)](contentheader)
140+
133141
### [Context Menu](context-menu)
134142

135143
Add a new button to an existent context menu.

completer/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The first part is contained in the `index.ts` file, the second is in `connector.
2727

2828
## Creating a custom DataConnector
2929

30-
`src/customconnector.ts` defines a `CustomConnector` to generate mock autocomplete suggestions. Like the `ContextConnector` it is based on, `CustomConnector` extends _JupyterLab_'s abstract [`DataConnector`](https://jupyterlab.readthedocs.io/en/latest/api/classes/statedb.dataconnector.html) class.
30+
`src/customconnector.ts` defines a `CustomConnector` to generate mock autocomplete suggestions. Like the `ContextConnector` it is based on, `CustomConnector` extends _JupyterLab_'s abstract [`DataConnector`](https://jupyterlab.readthedocs.io/en/latest/api/classes/statedb.DataConnector.html) class.
3131

3232
The only abstract method in `DataConnector` is `fetch`, which must be implemented in your `CustomConnector`.
3333

contentheader/.eslintignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
dist
3+
coverage
4+
**/*.d.ts
5+
ui-tests

contentheader/.eslintrc.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
module.exports = {
2+
extends: [
3+
'eslint:recommended',
4+
'plugin:@typescript-eslint/eslint-recommended',
5+
'plugin:@typescript-eslint/recommended',
6+
'plugin:jsdoc/recommended',
7+
'plugin:prettier/recommended',
8+
'plugin:react/recommended',
9+
],
10+
parser: '@typescript-eslint/parser',
11+
parserOptions: {
12+
project: 'tsconfig.json',
13+
sourceType: 'module',
14+
},
15+
plugins: ['@typescript-eslint', 'jsdoc'],
16+
rules: {
17+
'@typescript-eslint/naming-convention': [
18+
'error',
19+
{
20+
selector: 'interface',
21+
format: ['PascalCase'],
22+
custom: {
23+
regex: '^I[A-Z]',
24+
match: true,
25+
},
26+
},
27+
],
28+
'@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
29+
'@typescript-eslint/no-explicit-any': 'off',
30+
'@typescript-eslint/no-namespace': 'off',
31+
'@typescript-eslint/no-use-before-define': 'off',
32+
'@typescript-eslint/quotes': [
33+
'error',
34+
'single',
35+
{ avoidEscape: true, allowTemplateLiterals: false },
36+
],
37+
curly: ['error', 'all'],
38+
eqeqeq: 'error',
39+
'jsdoc/require-param-type': 'off',
40+
'jsdoc/require-property-type': 'off',
41+
'jsdoc/require-returns-type': 'off',
42+
'jsdoc/no-types': 'warn',
43+
'prefer-arrow-callback': 'error',
44+
},
45+
settings: {
46+
jsdoc: {
47+
mode: 'typescript',
48+
},
49+
react: {
50+
version: 'detect',
51+
},
52+
},
53+
};

contentheader/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.bundle.*
2+
lib/
3+
node_modules/
4+
*.egg-info/
5+
.ipynb_checkpoints
6+
*.tsbuildinfo

contentheader/MANIFEST.in

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
include LICENSE
2+
include *.md
3+
include pyproject.toml
4+
5+
include package.json
6+
include install.json
7+
include ts*.json
8+
include yarn.lock
9+
10+
graft jupyterlab_examples_contentheader/labextension
11+
12+
# Javascript files
13+
graft src
14+
graft style
15+
prune **/node_modules
16+
prune lib
17+
prune binder
18+
19+
# Patterns to exclude from any directory
20+
global-exclude *~
21+
global-exclude *.pyc
22+
global-exclude *.pyo
23+
global-exclude .git
24+
global-exclude .ipynb_checkpoints

contentheader/README.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Main Widget Content Header
2+
3+
> Insert a widget into the `contentHeader` section of a `MainAreaWidget`—useful for toolbars, headers, etc.
4+
5+
![Demo: activate the extension using Command Palette to populate the time in GMT in the content header](./preview.gif)
6+
7+
This JupyterLab example extension is intended to demo one specific feature of `MainAreaWidget`, namely, its `contentHeader` section.
8+
9+
> As background, `MainAreaWidget` is a high-level JupyterLab widget that conventionally is used to enclose the Launcher (shown above) or a notebook editor. The `contentHeader`, in turn, is a Lumino `BoxPanel` widget positioned at the very top of this main area. This makes the `contentHeader` potentially useful to extensions needing some place to put content that the user will _always see_.
10+
11+
In code: after you get a `MainAreaWidget`, for example via
12+
13+
```ts
14+
// src/index.ts#L37-L37
15+
16+
const main = app.shell.currentWidget;
17+
```
18+
19+
you can then create a widget of interest, for example as
20+
21+
```ts
22+
// src/index.ts#L40-L40
23+
24+
const widget = new Widget();
25+
```
26+
27+
before finally adding it to the JupyterLab main area's `contentHeader` real estate at its very top:
28+
29+
```ts
30+
// src/index.ts#L45-L45
31+
32+
main.contentHeader.addWidget(widget);
33+
```
34+
35+
## Install
36+
37+
First, ensure you've followed the installation instructions in the [top-level README](../README.md), e.g.:
38+
39+
```bash
40+
# clone the repository
41+
git clone https://github.com/jupyterlab/extension-examples.git jupyterlab-extension-examples
42+
43+
# go to the extension examples folder
44+
cd jupyterlab-extension-examples
45+
46+
# create a new environment
47+
conda env create
48+
49+
# activate the environment
50+
conda activate jupyterlab-extension-examples
51+
```
52+
53+
Then build this extension and launch it — this largely follows from the instructions in the top-level [README](../README.md), except using this example instead of the `hello-world` one:
54+
55+
```bash
56+
# go to the contentheader example
57+
cd contentheader
58+
59+
# install the extension in editable mode
60+
python -m pip install -e .
61+
62+
# install your development version of the extension with JupyterLab
63+
jupyter labextension develop . --overwrite
64+
65+
# build the TypeScript source after making changes
66+
jlpm run build
67+
68+
# start JupyterLab
69+
jupyter lab
70+
```
71+
72+
## Activate
73+
74+
As in the demo animated GIF above, once in JupyterLab
75+
76+
1. Open the [Command Palette](https://jupyterlab.readthedocs.io/en/stable/user/commands.html) via, e.g., _View__Activate Command Palette_.
77+
2. Type in `populate` to see the command created by this extension: _Populate content header (time example)_.
78+
3. This will create a small header bar at the top of the main JupyterLab window that shows the current time in GMT (Greenwich Mean Time, also called UTC, Coordinated Universal Time).
79+
80+
> Nota bene, you can _toggle_ the `contentHeader` via the _Command Palette__Show Header Above Content_ if you decide you don't want to see it.
81+
82+
> Nota bene 2, the `contentHeader` and the _Show Header Above Content_ toggle apply on a _per_ `MainAreaWidget`: if you run this extension, it will show the time in the `contentHeader` of the _active_ main area, and the time will not appear in another notebook tab. This may be useful for some extension workflows and not others.
83+
84+
## Implementation notes
85+
86+
For full details see the body of the `execute` function in [`index.ts`](./src/index.ts), but in prose, here's what to do to make use of the `contentHeader` widget.
87+
88+
First, get a `MainAreaWidget`. The approach used here will likely be useful for many JupyterLab use cases: `JupyterFrontEnd.shell.currentWidget` will be a `MainAreaWidget` if your extension is being activated with a classic JupyterLab window, and you can ensure that by testing for `app.shell.currentWidget instanceof MainAreaWidget`.
89+
90+
`MainAreaWidget.contentHeader` is a "top-to-bottom" vertical [Lumino BoxPanel](https://jupyterlab.github.io/lumino/widgets/classes/boxpanel.html). You can call its `addWidget` and `insertWidget` methods to populate this space with your own custom widgets.
91+
92+
## Background
93+
94+
The motivation for this extension example was a question on the Jupyter Discourse, ["How to add Widget to an arbitrary HTMLElement?"](https://discourse.jupyter.org/t/how-to-add-widget-to-an-arbitrary-htmlelement/11576), where Michał Krassowski kindly recommended to create this example to remind JupyterLab developers of this feature.
95+
96+
The feature was added in [that PR](https://github.com/jupyterlab/jupyterlab/pull/9984) which has further discussion.

contentheader/install.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"packageManager": "python",
3+
"packageName": "jupyterlab_examples_contentheader",
4+
"uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab_examples_contentheader"
5+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import json
3+
from pathlib import Path
4+
5+
from ._version import __version__
6+
7+
HERE = Path(__file__).parent.resolve()
8+
9+
with (HERE / "labextension" / "package.json").open() as fid:
10+
data = json.load(fid)
11+
12+
def _jupyter_labextension_paths():
13+
return [{
14+
"src": "labextension",
15+
"dest": data["name"]
16+
}]
17+

0 commit comments

Comments
 (0)