diff --git a/.binder/environment.yml b/.binder/environment.yml
index e4195d0..12ea5e2 100644
--- a/.binder/environment.yml
+++ b/.binder/environment.yml
@@ -3,58 +3,57 @@ name: jupyterlab-deck-demo
channels:
- conda-forge
- nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
dependencies:
- - python >=3.10,<3.11
+ - python >=3.11,<3.12
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
### environment-build.yml ###
# runtimes
- - nodejs >=16,<17
+ - nodejs >=20,<21
# host app
- - ipywidgets >=8
+ - ipywidgets >=7
# build
- - flit >=3.7.1
+ - flit >=3.9.0,<4.0.0
- twine
### environment-build.yml ###
### environment-lint.yml ###
# formatters
- black
- - isort
- ssort
- - docformatter
+ - ruff
- robotframework-tidy >=3.3
# linters
- robotframework-robocop >=2.6
- - pyflakes
### environment-lint.yml ###
### environment-docs.yml ###
- # demo
- - ipydrawio
- - jupyter-videochat
- - jupyterlab-myst
- - jupyterlab-webrtc-docprovider
# docs
- docutils >=0.18
+ - mdit-py-plugins <0.4.0
+ - myst-nb
- pydata-sphinx-theme
- sphinx >=5.1,<6
- sphinx-autobuild
- sphinx-copybutton
- - myst-nb
# check
- hunspell
- hunspell-en
- pytest-check-links
- # lite cruft
- - pkginfo
- - pip:
- - jupyterlite ==0.1.0b14
+ # lite
+ - python-libarchive-c
+ - jupyterlite-core ==0.2.0rc1
+ - jupyterlite-pyodide-kernel ==0.2.0a2
### environment-docs.yml ###
### environment-test.yml ###
# test
@@ -62,11 +61,11 @@ dependencies:
- pytest-html
### environment-test.yml ###
### environment-robot.yml ###
- - robotframework >=6
+ - robotframework >=6.1
- robotframework-pabot
# browser
- - firefox
+ - firefox 115.*
- geckodriver
- - robotframework-jupyterlibrary >=0.4.1
+ - robotframework-jupyterlibrary >=0.5.0
- lxml
### environment-robot.yml ###
diff --git a/.github/environment-base.yml b/.github/environment-base.yml
index d9d64f7..891f6e1 100644
--- a/.github/environment-base.yml
+++ b/.github/environment-base.yml
@@ -1,9 +1,11 @@
dependencies:
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
diff --git a/.github/environment-build.yml b/.github/environment-build.yml
index ab4ba6b..888177e 100644
--- a/.github/environment-build.yml
+++ b/.github/environment-build.yml
@@ -3,23 +3,28 @@ name: jupyterlab-deck-test
channels:
- conda-forge
- nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
dependencies:
- - python >=3.10,<3.11
+ - python >=3.10,<3.13
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
### environment-build.yml ###
# runtimes
- - nodejs >=16,<17
+ - nodejs >=20,<21
# host app
- - ipywidgets >=8
+ - ipywidgets >=7
# build
- - flit >=3.7.1
+ - flit >=3.9.0,<4.0.0
- twine
### environment-build.yml ###
diff --git a/.github/environment-docs.yml b/.github/environment-docs.yml
index 2443064..0bb6205 100644
--- a/.github/environment-docs.yml
+++ b/.github/environment-docs.yml
@@ -3,45 +3,46 @@ name: jupyterlab-deck-docs
channels:
- conda-forge
- nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
dependencies:
- - python >=3.10,<3.11
+ - python >=3.10,<3.13
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
### environment-build.yml ###
# runtimes
- - nodejs >=16,<17
+ - nodejs >=20,<21
# host app
- - ipywidgets >=8
+ - ipywidgets >=7
# build
- - flit >=3.7.1
+ - flit >=3.9.0,<4.0.0
- twine
### environment-build.yml ###
### environment-docs.yml ###
- # demo
- - ipydrawio
- - jupyter-videochat
- - jupyterlab-myst
- - jupyterlab-webrtc-docprovider
# docs
- docutils >=0.18
+ - mdit-py-plugins <0.4.0
+ - myst-nb
- pydata-sphinx-theme
- sphinx >=5.1,<6
- sphinx-autobuild
- sphinx-copybutton
- - myst-nb
# check
- hunspell
- hunspell-en
- pytest-check-links
- # lite cruft
- - pkginfo
- - pip:
- - jupyterlite ==0.1.0b14
+ # lite
+ - python-libarchive-c
+ - jupyterlite-core ==0.2.0rc1
+ - jupyterlite-pyodide-kernel ==0.2.0a2
### environment-docs.yml ###
diff --git a/.github/environment-lint.yml b/.github/environment-lint.yml
index 2b948a1..89d5fec 100644
--- a/.github/environment-lint.yml
+++ b/.github/environment-lint.yml
@@ -3,43 +3,46 @@ name: jupyterlab-deck-lint
channels:
- conda-forge
- nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
dependencies:
- - python >=3.10,<3.11
+ - python >=3.10,<3.13
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
### environment-build.yml ###
# runtimes
- - nodejs >=16,<17
+ - nodejs >=20,<21
# host app
- - ipywidgets >=8
+ - ipywidgets >=7
# build
- - flit >=3.7.1
+ - flit >=3.9.0,<4.0.0
- twine
### environment-build.yml ###
### environment-lint.yml ###
# formatters
- black
- - isort
- ssort
- - docformatter
+ - ruff
- robotframework-tidy >=3.3
# linters
- robotframework-robocop >=2.6
- - pyflakes
### environment-lint.yml ###
### environment-robot.yml ###
- - robotframework >=6
+ - robotframework >=6.1
- robotframework-pabot
# browser
- - firefox
+ - firefox 115.*
- geckodriver
- - robotframework-jupyterlibrary >=0.4.1
+ - robotframework-jupyterlibrary >=0.5.0
- lxml
### environment-robot.yml ###
diff --git a/.github/environment-robot.yml b/.github/environment-robot.yml
index 6512b3f..131db57 100644
--- a/.github/environment-robot.yml
+++ b/.github/environment-robot.yml
@@ -1,10 +1,10 @@
dependencies:
### environment-robot.yml ###
- - robotframework >=6
+ - robotframework >=6.1
- robotframework-pabot
# browser
- - firefox
+ - firefox 115.*
- geckodriver
- - robotframework-jupyterlibrary >=0.4.1
+ - robotframework-jupyterlibrary >=0.5.0
- lxml
### environment-robot.yml ###
diff --git a/.github/environment-test-lab35.yml b/.github/environment-test-lab35.yml
new file mode 100644
index 0000000..3ec62e3
--- /dev/null
+++ b/.github/environment-test-lab35.yml
@@ -0,0 +1,37 @@
+name: jupyterlab-deck-test-35
+
+channels:
+ - conda-forge
+ - nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
+
+dependencies:
+ # a more precise python pin is added in CI
+ - jupyterlab >=3.5,<3.6.0a0
+ - notebook <7.0.0a0
+ ### environment-base.yml ###
+ - doit-with-toml
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
+ - pip
+ - python >=3.8,<3.13
+ - python-dotenv
+ ### environment-base.yml ###
+ ### environment-test.yml ###
+ # test
+ - pytest-cov
+ - pytest-html
+ ### environment-test.yml ###
+ ### environment-robot.yml ###
+ - robotframework >=6.1
+ - robotframework-pabot
+ # browser
+ - firefox 115.*
+ - geckodriver
+ - robotframework-jupyterlibrary >=0.5.0
+ - lxml
+ ### environment-robot.yml ###
diff --git a/.github/environment-test.yml b/.github/environment-test.yml
index 9edc47e..83a0174 100644
--- a/.github/environment-test.yml
+++ b/.github/environment-test.yml
@@ -3,24 +3,29 @@ name: jupyterlab-deck-test
channels:
- conda-forge
- nodefaults
+ - conda-forge/label/jupyterlab_fonts_alpha
+ - conda-forge/label/jupyterlite_core_rc
+ - conda-forge/label/jupyterlite_pyodide_kernel_alpha
dependencies:
# a more precise python pin is added in CI
### environment-base.yml ###
- doit-with-toml
- - ipywidgets >=8
- - jupyterlab >=3.4.8,<4
- - jupyterlab-fonts >=2.1.1
+ - ipywidgets >=7
+ - jupyterlab >=3.5,<5.0.0a0
+ - jupyterlab-fonts >=3.0.0a3
+ - notebook >=6.5,<8.0.0a0
- pip
- - python >=3.7,<3.11
+ - python >=3.8,<3.13
+ - python-dotenv
### environment-base.yml ###
### environment-build.yml ###
# runtimes
- - nodejs >=16,<17
+ - nodejs >=20,<21
# host app
- - ipywidgets >=8
+ - ipywidgets >=7
# build
- - flit >=3.7.1
+ - flit >=3.9.0,<4.0.0
- twine
### environment-build.yml ###
### environment-test.yml ###
@@ -29,11 +34,11 @@ dependencies:
- pytest-html
### environment-test.yml ###
### environment-robot.yml ###
- - robotframework >=6
+ - robotframework >=6.1
- robotframework-pabot
# browser
- - firefox
+ - firefox 115.*
- geckodriver
- - robotframework-jupyterlibrary >=0.4.1
+ - robotframework-jupyterlibrary >=0.5.0
- lxml
### environment-robot.yml ###
diff --git a/.github/requirements-build.txt b/.github/requirements-build.txt
index 107e3f4..587173c 100644
--- a/.github/requirements-build.txt
+++ b/.github/requirements-build.txt
@@ -1,5 +1,6 @@
black
-doit
+doit[toml]
flit
-jupyterlab ==3.*
+jupyterlab >=4.0.7,<5
pip
+ruff
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 07209ed..d984874 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,7 @@ on:
pull_request:
branches:
- '*'
+ workflow_dispatch:
env:
PYTHONUNBUFFERED: '1'
@@ -15,7 +16,7 @@ env:
# our stuff
ROBOT_RETRIES: '3'
- CACHE_EPOCH: '1'
+ CACHE_EPOCH: '4'
DOIT_N_BUILD: '-n4'
PABOT_PROCESSES: '3'
@@ -26,14 +27,14 @@ jobs:
strategy:
matrix:
os: [ubuntu]
- python-version: ['3.10']
+ python-version: ['3.11']
defaults:
run:
shell: bash -l {0}
env:
BUILDING_IN_CI: '1'
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# configure builtin providers
- name: setup (python)
@@ -45,7 +46,7 @@ jobs:
- name: setup (node)
uses: actions/setup-node@v3
with:
- node-version: '16'
+ node-version: '20'
# restore caches
- name: cache (pip)
@@ -111,7 +112,7 @@ jobs:
strategy:
matrix:
os: [ubuntu]
- python-version: ['3.10']
+ python-version: ['3.11']
env:
WITH_JS_COV: 1
defaults:
@@ -119,7 +120,7 @@ jobs:
shell: bash -l {0}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: cache (conda)
uses: actions/cache@v3
@@ -143,37 +144,42 @@ jobs:
with:
environment-file: .binder/environment.yml
miniforge-variant: Mambaforge
- use-only-tar-bz2: true
use-mamba: true
- - name: Lint
+ - name: lint
run: doit lint
- - name: Dev
+ - name: dist
+ env:
+ WITH_JS_COV: 0
+ run: doit dist
+
+ - name: build
+ run: doit build
+
+ - name: dev
run: doit dev
- - name: Test (with cov)
+ - name: test latest (with cov)
run: doit test:robot
- - name: Docs
+ - name: dev (legacy)
+ run: doit legacy:pip
+
+ - name: test legacy
+ run: doit legacy:robot
+
+ - name: docs
run: doit docs
- - name: Check
+ - name: check
run: doit check
- - name: Upload (report)
- if: always()
- uses: actions/upload-artifact@v3
- with:
- name: jupyterlab-deck-nyc-${{ github.run_number }}
- path: ./build/reports/nyc/
-
- - name: upload (atest)
+ - name: upload (reports)
if: always()
uses: actions/upload-artifact@v3
with:
- name: |-
- jupyterlab-deck-test-cov-${{ matrix.os }}-${{matrix.python-version }}-${{ github.run_number }}
+ name: jupyterlab-deck-lint-reports-${{ github.run_number }}
path: ./build/reports
- uses: codecov/codecov-action@v3
@@ -190,11 +196,11 @@ jobs:
fail-fast: false
matrix:
os: ['ubuntu', 'macos', 'windows']
- python-version: ['3.7', '3.10']
+ python-version: ['3.8', '3.11']
include:
- - python-version: '3.7'
+ - python-version: '3.8'
CI_ARTIFACT: 'sdist'
- - python-version: '3.10'
+ - python-version: '3.11'
CI_ARTIFACT: 'wheel'
env:
TESTING_IN_CI: '1'
@@ -204,7 +210,7 @@ jobs:
git config --global core.autocrlf false
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: cache (conda)
uses: actions/cache@v3
@@ -221,7 +227,6 @@ jobs:
miniforge-variant: Mambaforge
python-version: ${{ matrix.python-version }}
environment-file: .github/environment-test.yml
- use-only-tar-bz2: true
use-mamba: true
- name: download (dist)
@@ -240,26 +245,20 @@ jobs:
shell: cmd /C CALL {0}
run: doit dev
- - name: test (unix)
+ - name: test latest (unix)
if: matrix.os != 'windows'
shell: bash -l {0}
run: doit test
- - name: test (windows)
+ - name: test latest (windows)
if: matrix.os == 'windows'
shell: cmd /C CALL {0}
run: doit test
- - uses: codecov/codecov-action@v3
- with:
- directory: ./build/reports/coverage-xml/
- verbose: true
- flags: back-end
-
- - name: upload (atest)
+ - name: upload (reports)
if: always()
uses: actions/upload-artifact@v3
with:
name: |-
- jupyterlab-deck-test-${{ matrix.os }}-${{matrix.python-version }}-${{ github.run_number }}
+ jupyterlab-deck-reports-${{ matrix.os }}-${{matrix.python-version }}-${{ github.run_number }}
path: ./build/reports
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index baac2bd..afd5c9a 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -15,7 +15,7 @@ env:
# our stuff
ROBOT_RETRIES: '3'
- CACHE_EPOCH: '0'
+ CACHE_EPOCH: '4'
PABOT_PROCESSES: '3'
jobs:
@@ -32,7 +32,7 @@ jobs:
shell: bash -l {0}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: cache (conda)
uses: actions/cache@v3
@@ -56,22 +56,21 @@ jobs:
with:
environment-file: .binder/environment.yml
miniforge-variant: Mambaforge
- use-only-tar-bz2: true
use-mamba: true
- - name: Lint
+ - name: lint
run: doit lint
- - name: Dev
+ - name: dev
run: doit dev
- - name: Test (pytest)
+ - name: test latest (pytest)
run: doit test:pytest
- - name: Test (robot with cov)
+ - name: test latest (robot with cov)
run: doit test:robot
- - name: Site
+ - name: site
run: doit site
- uses: actions/upload-pages-artifact@v1
diff --git a/.gitignore b/.gitignore
index 9798997..f3382e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,12 +4,14 @@ _output/
.binder/*.requirements.txt
.cache/
.coverage
+.env
.ipynb_checkpoints/
.pabotsuitenames
-.venv/
+.venv*/
.yarn-packages/
*.doit.*
*.egg-info
+*.log
*.tsbuildinfo
build/
dist/
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 47c3adb..43880d3 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -3,6 +3,13 @@ build:
os: ubuntu-20.04
tools:
python: mambaforge-4.10
+ jobs:
+ pre_build:
+ - doit setup
+ - doit build
+ - doit dist
+ - doit dev
+ - doit lite
sphinx:
builder: html
configuration: docs/conf.py
diff --git a/.yarnrc b/.yarnrc
deleted file mode 100644
index ce91b1c..0000000
--- a/.yarnrc
+++ /dev/null
@@ -1,6 +0,0 @@
-disable-self-update-check true
-ignore-optional true
-network-timeout "300000"
-registry "https://registry.npmjs.org/"
-yarn-offline-mirror "./.yarn-packages"
-yarn-offline-mirror-pruning true
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 0000000..8402834
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1,22 @@
+enableInlineBuilds: false
+enableTelemetry: false
+httpTimeout: 60000
+nodeLinker: node-modules
+npmRegistryServer: https://registry.npmjs.org/
+installStatePath: ./build/.cache/yarn/install-state.gz
+cacheFolder: ./build/.cache/yarn/cache
+logFilters:
+ - code: YN0006
+ level: discard
+ - code: YN0002
+ level: discard
+ - code: YN0007
+ level: discard
+ - code: YN0013
+ level: discard
+ - code: YN0019
+ level: discard
+ - code: YN0008
+ level: discard
+ - code: YN0032
+ level: discard
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d3b471..fe76642 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,10 @@
## Changelog
-### `0.1.4` (unreleased)
+### `0.2.0a0`
-> TBD
+- [#36] adds support for Jupyter Notebook 7 and JupyterLab 4
+
+[#36]: https://github.com/deathbeds/jupyterlab-deck/issues/36
### `0.1.3`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ff99b68..729c70b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,6 +24,22 @@ See other available tasks with:
doit list
```
+### Legacy
+
+Support for JupyterLab 3 is verified with the `legacy` subtasks.
+
+Run all legacy tasks:
+
+```bash
+doit legacy
+```
+
+Run an isolated JupyterLab 3 application:
+
+```bash
+doit serve:lab:legacy
+```
+
### Releasing
- Start a [release issue](https://github.com/jupyterlab-deck/issues)
diff --git a/README.md b/README.md
index 28434c5..a9856a6 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,11 @@
[binder-badge]: https://mybinder.org/badge_logo.svg
[binder]:
https://mybinder.org/v2/gh/deathbeds/jupyterlab-deck/HEAD?urlpath=lab/tree/examples/README.ipynb
-[ci-badge]: https://img.shields.io/github/workflow/status/deathbeds/jupyterlab-deck/CI
+[ci-badge]:
+ https://img.shields.io/github/actions/workflow/status/deathbeds/jupyterlab-deck/ci.yml
[ci]: https://github.com/deathbeds/jupyterlab-deck/actions?query=branch%3Amain
[reports-badge]:
- https://img.shields.io/github/workflow/status/deathbeds/jupyterlab-deck/pages?label=reports
+ https://img.shields.io/github/actions/workflow/status/deathbeds/jupyterlab-deck/pages.yml?label=reports
[reports]: https://deathbeds.github.io/jupyterlab-deck/lab/index.html?path=README.ipynb
[rtd-badge]: https://img.shields.io/readthedocs/jupyterlab-deck
[rtd]: https://jupyterlab-deck.rtfd.io
@@ -230,9 +231,10 @@ restore the part to the default layout.
### Does it work with `notebook 7`?
-**Not yet.** Navigating multiple documents during the same presentation will probably
+**Mostly.** Navigating multiple documents during the same presentation will probably
never work, as this is incompatible with the one-document-at-a-time design constraint of
-the Notebook UX.
+the Notebook UX. Each skip to another document will open a new browser tab, though deck
+should be installed.
### Will it generate PowerPoint?
diff --git a/_scripts/labextension.py b/_scripts/labextension.py
new file mode 100644
index 0000000..b7bf22c
--- /dev/null
+++ b/_scripts/labextension.py
@@ -0,0 +1,27 @@
+"""A custom `jupyter-labextension` to enable the semi-weird directory layout."""
+import importlib
+import sys
+from pathlib import Path
+
+from jupyterlab import federated_labextensions
+from jupyterlab.labextensions import LabExtensionApp
+
+HERE = Path(__file__).parent.resolve()
+ROOT = HERE.parent
+NODE_MODULES = ROOT / "node_modules"
+BUILDER = NODE_MODULES / "@jupyterlab/builder/lib/build-labextension.js"
+
+
+def _get_labextension_metadata(module):
+ module = "jupyterlab_deck"
+ m = importlib.import_module(module)
+ return m, m._jupyter_labextension_paths()
+
+
+federated_labextensions._get_labextension_metadata = _get_labextension_metadata
+federated_labextensions._ensure_builder = lambda *_: str(BUILDER)
+
+main = LabExtensionApp.launch_instance
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/atest/fixtures/jupyter_config.json b/atest/fixtures/jupyter_config.json
index b2ef6be..a4da239 100644
--- a/atest/fixtures/jupyter_config.json
+++ b/atest/fixtures/jupyter_config.json
@@ -1,14 +1,22 @@
{
"LabApp": {
+ "expose_app_in_browser": true,
"log_level": "DEBUG",
"open_browser": false
},
+ "JupyterNotebookApp": {
+ "expose_app_in_browser": true
+ },
"ServerApp": {
"tornado_settings": {
"page_config_data": {
+ "buildAvailable": false,
"buildCheck": false,
- "buildAvailable": false
+ "exposeAppInBrowser": true
}
}
+ },
+ "LanguageServerManager": {
+ "autodetect": false
}
}
diff --git a/atest/fixtures/overrides.json b/atest/fixtures/overrides.json
new file mode 100644
index 0000000..5560581
--- /dev/null
+++ b/atest/fixtures/overrides.json
@@ -0,0 +1,13 @@
+{
+ "@jupyterlab/apputils-extension:notification": {
+ "checkForUpdates": false,
+ "doNotDisturbMode": true,
+ "fetchNews": "false"
+ },
+ "@jupyterlab/apputils-extension:palette": {
+ "modal": false
+ },
+ "@jupyterlab/extensionmanager-extension:plugin": {
+ "enabled": false
+ }
+ }
diff --git a/atest/fixtures/page_config.json b/atest/fixtures/page_config.json
new file mode 100644
index 0000000..bd3c81f
--- /dev/null
+++ b/atest/fixtures/page_config.json
@@ -0,0 +1,5 @@
+{
+ "disabledExtensions": {
+ "@jupyterlab/apputils-extension:notification": true
+ }
+}
diff --git a/atest/resources/CodeMirror.resource b/atest/resources/CodeMirror.resource
new file mode 100644
index 0000000..b07c716
--- /dev/null
+++ b/atest/resources/CodeMirror.resource
@@ -0,0 +1,38 @@
+*** Settings ***
+Documentation Keywords for working with CodeMirror
+
+Library JupyterLibrary
+
+
+*** Variables ***
+${CM JS TO STRING} view.state.doc.toString()
+
+
+*** Keywords ***
+Initialize CodeMirror
+ [Documentation] Fix apparently-broken CSS/JS variable updates.
+ IF "${JD_APP_UNDER_TEST}" == "nb"
+ Update Globals For JupyterLab 4
+ ELSE
+ Update Globals For JupyterLab Version
+ END
+ Set Suite Variable ${CM CSS EDITOR} ${CM CSS EDITOR} children=${TRUE}
+ Set Suite Variable ${CM JS INSTANCE} ${CM JS INSTANCE} children=${TRUE}
+ IF "${CM JS INSTANCE}" == "${CM6 JS INSTANCE}"
+ Set Suite Variable ${CM JS TO STRING} view.state.doc.toString() children=${TRUE}
+ ELSE
+ Set Suite Variable ${CM JS TO STRING} getValue() children=${TRUE}
+ END
+
+Return CodeMirror Method
+ [Documentation] Construct and a method call against in the CodeMirror attached to the element
+ ... that matches a ``css`` selector with the given ``js`` code.
+ ... The CodeMirror editor instance will be available as `cm`.
+ [Arguments] ${css} ${js}
+
+ ${result} = Execute JavaScript
+ ... return (() => {
+ ... const cm = document.querySelector(`${css}`)${CM JS INSTANCE};
+ ... return cm.${js};
+ ... }).call(this);
+ RETURN ${result}
diff --git a/atest/resources/Docs.resource b/atest/resources/Docs.resource
index 96d5c4f..fd3cf0f 100644
--- a/atest/resources/Docs.resource
+++ b/atest/resources/Docs.resource
@@ -47,3 +47,4 @@ Set Up Interactive Suite
[Documentation] Prepare for this suite.
[Arguments] ${screens}
Set Attempt Screenshot Directory lab${/}${screens}
+ Initialize CodeMirror
diff --git a/atest/resources/Fixtures.resource b/atest/resources/Fixtures.resource
index 6723a1b..ab49f44 100644
--- a/atest/resources/Fixtures.resource
+++ b/atest/resources/Fixtures.resource
@@ -33,7 +33,7 @@ Clean Examples
Open Example
[Documentation] Open an example
- [Arguments] ${name}=README.ipynb
+ [Arguments] ${name}=README.ipynb ${switch_window}=${EMPTY}
Maybe Open JupyterLab Sidebar File Browser
${selectors} = Create List
... ${CSS_LAB_FILES_HOME}
@@ -43,3 +43,7 @@ Open Example
Wait Until Element Is Visible css:${sel}
Double Click Element css:${sel}
END
+ IF "${switch_window}"
+ Sleep 5s
+ Switch Window NEW
+ END
diff --git a/atest/resources/Lab.resource b/atest/resources/Lab.resource
index bdc68ff..4009750 100644
--- a/atest/resources/Lab.resource
+++ b/atest/resources/Lab.resource
@@ -1,6 +1,7 @@
*** Settings ***
-Documentation Keywords for working with decks.
+Documentation Keywords for working with the Lab shell.
+Resource ./CodeMirror.resource
Resource ./LabSelectors.resource
Resource ./Screenshots.resource
Library Collections
@@ -8,6 +9,23 @@ Library JupyterLibrary
*** Keywords ***
+Initialize JupyterLab
+ [Documentation] Get the web app set up for testing.
+ ${executable_path} = Get GeckoDriver Executable Path
+ Open JupyterLab executable_path=${executable_path}
+ Initialize CodeMirror
+ Set Window Size 1366 768
+ Reload Page
+ Wait For JupyterLab Splash Screen
+
+Plugins Should Be Disabled
+ [Documentation] Check that some JupyterLab extensions are disabled by config
+ [Arguments] @{plugins}
+ ${disabled} = Get JupyterLab Page Info disabledExtensions
+ FOR ${plugin} IN @{plugins}
+ Should Contain ${disabled} ${plugin} msg=${plugin} was not disabled
+ END
+
Add And Activate Cell With Keyboard
[Documentation] Add a cell with the keyboard.
${index} = Get Active Cell Index
@@ -40,12 +58,13 @@ Set Cell Type
Make Markdown Cell
[Documentation] Turn the current cell into markdown.
[Arguments] ${code} ${expect}=${EMPTY} ${new}=${TRUE} ${screenshot}=${EMPTY}
+ Initialize CodeMirror
${index} = Get Active Cell Index
IF ${new}
Add And Activate Cell With Keyboard
${index} = Set Variable ${index.__add__(1)}
END
- ${cm} = Set Variable ${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) .CodeMirror
+ ${cm} = Set Variable ${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) ${CM CSS EDITOR}
Set CodeMirror Value ${cm} ${code}
Set Cell Type ${index} markdown
IF ${expect.__len__()} Render Markdown Cell ${index} ${expect}
@@ -78,19 +97,21 @@ Wait Until Cell Is Not Active
Maybe Open Cell Metadata JSON
[Documentation] Ensure the Cell Metadata viewer is open.
- ${el} = Get WebElements ${CSS_LAB_CELL_META_JSON_CM_HIDDEN}
+
+ ${el} = Get WebElements ${CSS_LAB_CELL_META_JSON} ${CM CSS EDITOR}
IF not ${el.__len__()} RETURN
Click Element ${CSS_LAB_ADVANCED_COLLAPSE}
- Wait Until Page Does Not Contain Element ${CSS_LAB_CELL_META_JSON_CM_HIDDEN}
+ Wait Until Page Does Not Contain Element ${CSS_LAB_CELL_META_JSON_HIDDEN} ${CM CSS EDITOR}
Wait Until Cell Metadata Contains
[Documentation] Ensure a string appears in the Cell Metadata JSON
[Arguments] ${text} ${attempts}=5 ${timeout}=0.1s ${inverse}=${FALSE}
+
${ok} = Set Variable ${FALSE}
FOR ${i} IN RANGE ${attempts}
- ${src} = Return CodeMirror Method ${CSS_LAB_CELL_META_JSON_CM} getValue()
+ ${src} = Return CodeMirror Method ${CSS_LAB_CELL_META_JSON} ${CM CSS EDITOR} ${CM JS TO STRING}
${contains} = Set Variable ${src.__contains__('''${text}''')}
IF ${inverse} and not ${contains}
${ok} = Set Variable ${TRUE}
@@ -111,9 +132,10 @@ Wait Until Cell Metadata Does Not Contain
[Arguments] ${text} ${attempts}=5 ${timeout}=0.1s
Wait Until Cell Metadata Contains text=${text} attempts=${attempts} timeout=${timeout} inverse=${TRUE}
-Return CodeMirror Method
- [Documentation] Construct and a method call against in the CodeMirror attached to the element
- ... that matches a ``css`` selector with the given ``js`` code.
- [Arguments] ${css} ${js}
- ${result} = Execute JavaScript return document.querySelector(`${css}`).CodeMirror.${js}
- RETURN ${result}
+Maybe Expand Panel With Title
+ [Documentation] Ensure a collapsed panel in a sidebar is expanded
+ [Arguments] ${label}
+ ${els} = Get WebElements
+ ... xpath:${XP_LAB4_COLLAPSED_PANEL_TITLE}\[contains(., '${label}')]
+ IF not ${els.__len__()} RETURN
+ Click Element ${els[0]}
diff --git a/atest/resources/LabSelectors.resource b/atest/resources/LabSelectors.resource
index 5abc490..d398dcd 100644
--- a/atest/resources/LabSelectors.resource
+++ b/atest/resources/LabSelectors.resource
@@ -4,53 +4,60 @@ Documentation Selectors that should maybe go upstream.
*** Variables ***
# # lumino ##
-${CSS_LM_MOD_ACTIVE} .lm-mod-active
-${CSS_LM_MENU_ITEM_LABEL} .lm-Menu-itemLabel
-${CSS_LM_CLOSE_ICON} .lm-TabBar-tabCloseIcon
+${CSS_LM_MOD_ACTIVE} .lm-mod-active
+${CSS_LM_MENU_ITEM_LABEL} .lm-Menu-itemLabel
+${CSS_LM_CLOSE_ICON} .lm-TabBar-tabCloseIcon
# # lab # #
# mod
-${CSS_LAB_MOD_DISABLED} .jp-mod-disabled
-${CSS_LAB_MOD_CMD} .jp-mod-commandMode
-${CSS_LAB_MOD_ACTIVE} .jp-mod-active
-${CSS_LAB_MOD_EDIT} .jp-mod-editMode
-${CSS_LAB_MOD_RENDERED} .jp-mod-rendered
-${CSS_LAB_MOD_HIDDEN} .lm-mod-hidden
+${CSS_LAB_MOD_DISABLED} .jp-mod-disabled
+${CSS_LAB_MOD_CMD} .jp-mod-commandMode
+${CSS_LAB_MOD_ACTIVE} .jp-mod-active
+${CSS_LAB_MOD_EDIT} .jp-mod-editMode
+${CSS_LAB_MOD_RENDERED} .jp-mod-rendered
+${CSS_LAB_MOD_HIDDEN} .lm-mod-hidden
# files
-${CSS_LAB_FILES_HOME} .jp-BreadCrumbs-home
-${CSS_LAB_FILES_DIR_ITEM} .jp-DirListing-item
+${CSS_LAB_FILES_HOME} .jp-BreadCrumbs-home
+${CSS_LAB_FILES_DIR_ITEM} .jp-DirListing-item
# docpanel
-${CSS_LAB_NOT_INTERNAL_ANCHOR} a[href*\="#"]:not([href^="https"]):not(${CSS_LAB_INTERNAL_ANCHOR})
-${CSS_LAB_TAB_NOT_CURRENT} .lm-DockPanel .lm-TabBar-tab:not(.jp-mod-current)
+${CSS_LAB_NOT_INTERNAL_ANCHOR} a[href*\="#"]:not([href^="https"]):not(${CSS_LAB_INTERNAL_ANCHOR})
+${CSS_LAB_TAB_NOT_CURRENT} .lm-DockPanel .lm-TabBar-tab:not(.jp-mod-current)
# docs
-${CSS_LAB_SPINNER} .jp-Spinner
-${CSS_LAB_INTERNAL_ANCHOR} .jp-InternalAnchorLink
+${CSS_LAB_SPINNER} .jp-Spinner
+${CSS_LAB_INTERNAL_ANCHOR} .jp-InternalAnchorLink
# meta
-${CSS_LAB_ADVANCED_COLLAPSE} .jp-NotebookTools .jp-Collapse-header
-${CSS_LAB_CELL_META_JSON_CM} .jp-MetadataEditorTool .CodeMirror
-${CSS_LAB_CELL_META_JSON_CM_HIDDEN} ${CSS_LAB_MOD_HIDDEN} ${CSS_LAB_CELL_META_JSON_CM}
+${CSS_LAB_ADVANCED_COLLAPSE} .jp-NotebookTools .jp-Collapse-header
+${CSS_LAB_CELL_META_JSON} .jp-MetadataEditorTool
+${CSS_LAB_CELL_META_JSON_HIDDEN} ${CSS_LAB_MOD_HIDDEN} ${CSS_LAB_CELL_META_JSON}
# notebook
-${CSS_LAB_NB_TOOLBAR} .jp-NotebookPanel-toolbar
-${CSS_LAB_NB_TOOLBAR_CELLTYPE} .jp-Notebook-toolbarCellType select
-${CSS_LAB_CELL_MARKDOWN} .jp-MarkdownCell
-${CSS_LAB_CELL_CODE} .jp-CodeCell
-${CSS_LAB_CELL_RAW} .jp-RawCell
+${CSS_LAB_NB_TOOLBAR} .jp-NotebookPanel-toolbar
+${CSS_LAB_NB_TOOLBAR_CELLTYPE} .jp-Notebook-toolbarCellType select
+${CSS_LAB_CELL_MARKDOWN} .jp-MarkdownCell
+${CSS_LAB_CELL_CODE} .jp-CodeCell
+${CSS_LAB_CELL_RAW} .jp-RawCell
&{CSS_LAB_CELL_TYPE}
-... code=${CSS_LAB_CELL_CODE}
-... markdown=${CSS_LAB_CELL_MARKDOWN}
-... raw=${CSS_LAB_CELL_RAW}
+... code=${CSS_LAB_CELL_CODE}
+... markdown=${CSS_LAB_CELL_MARKDOWN}
+... raw=${CSS_LAB_CELL_RAW}
# icons
-${CSS_LAB_ICON_ELLIPSES} [data-icon="ui-components:ellipses"]
-${CSS_LAB_ICON_CARET_LEFT} [data-icon="ui-components:caret-left"]
+${CSS_LAB_ICON_ELLIPSES} [data-icon="ui-components:ellipses"]
+${CSS_LAB_ICON_CARET_LEFT} [data-icon="ui-components:caret-left"]
# markdown
-${CSS_LAB_EDITOR} .jp-FileEditor
-${CSS_LAB_MARKDOWN_VIEWER} .jp-MarkdownViewer
-${CSS_LAB_CMD_MARKDOWN_PREVIEW} [data-command="fileeditor:markdown-preview"]
+${CSS_LAB_EDITOR} .jp-FileEditor
+${CSS_LAB_MARKDOWN_VIEWER} .jp-MarkdownViewer
+${CSS_LAB_CMD_MARKDOWN_PREVIEW} [data-command="fileeditor:markdown-preview"]
+
+# lab 7
+${XP_LAB4_COLLAPSED_PANEL} //*[contains(@class, 'jp-Collapse-header-collapsed')]
+${XP_LAB4_COLLAPSED_PANEL_TITLE} ${XP_LAB4_COLLAPSED_PANEL}//*[contains(@class, 'jp-Collapser-title')]
+
+# rfjl bugs
+${CM CSS EDITOR} .CodeMirror
diff --git a/atest/resources/Notebook.resource b/atest/resources/Notebook.resource
new file mode 100644
index 0000000..c4cefb1
--- /dev/null
+++ b/atest/resources/Notebook.resource
@@ -0,0 +1,17 @@
+*** Settings ***
+Documentation Keywords for working with the Notebook shell.
+
+Resource ./CodeMirror.resource
+Resource ./Server.resource
+Library JupyterLibrary
+
+
+*** Keywords ***
+Initialize Jupyter Notebook
+ [Documentation] Get the web app set up for testing.
+ ${executable_path} = Get GeckoDriver Executable Path
+ Open Notebook executable_path=${executable_path}
+ Initialize CodeMirror
+ Set Window Size 1366 768
+ Reload Page
+ Wait Until Element Is Visible css:${JNB CSS TREE LIST} timeout=10s
diff --git a/atest/resources/Screenshots.resource b/atest/resources/Screenshots.resource
index 77fabd2..227edec 100644
--- a/atest/resources/Screenshots.resource
+++ b/atest/resources/Screenshots.resource
@@ -34,4 +34,6 @@ Empty Screenshot Trash
Capture Page Screenshot And Tag With Error
[Documentation] Capture a screenshot if not going to the trash
${path} = Capture Page Screenshot
- IF "__trash__" not in "${path}" Set Tags screenshot:unexpected
+ IF "__trash__" not in "${path}"
+ Run Keyword And Ignore Error Set Tags screenshot:unexpected
+ END
diff --git a/atest/resources/Server.resource b/atest/resources/Server.resource
new file mode 100644
index 0000000..c746e67
--- /dev/null
+++ b/atest/resources/Server.resource
@@ -0,0 +1,108 @@
+*** Settings ***
+Documentation Keywords for testing jupyterlab-fonts
+
+Library BuiltIn
+Library Collections
+Library String
+Library OperatingSystem
+Library JupyterLibrary
+Library shutil
+Library uuid
+
+
+*** Variables ***
+${JUPYTERLAB_EXE} ["jupyter-lab"]
+${JSCOV} ${EMPTY}
+&{ETC_OVERRIDES}
+... jupyter_config.json=jupyter_config.json
+... overrides.json=labconfig${/}default_setting_overrides.json
+... page_config.json=labconfig${/}page_config.json
+${FIXTURES} ${ROOT}${/}atest${/}fixtures
+
+
+*** Keywords ***
+Initialize Jupyter Server
+ [Documentation] Set up server with command as defined in atest.py.
+ [Arguments] ${home_dir}
+ ${port} = Get Unused Port
+ ${token} = Generate Random String 64
+ ${base url} = Set Variable /jl@d/
+ @{args} = Build Custom JupyterLab Args ${port} ${token} ${base url}
+ ${rest_args} = Get Slice From List ${args} 1
+ ${config} = Initialize Jupyter Server Config ${home_dir}
+ ${lab} = Start New Jupyter Server
+ ... ${args[0]}
+ ... ${port}
+ ... ${base url}
+ ... ${config["cwd"]}
+ ... ${token}
+ ... @{rest_args}
+ ... &{config}
+ Wait For Jupyter Server To Be Ready ${lab}
+ RETURN ${lab}
+
+Initialize Jupyter Server Config
+ [Documentation] Prepare keyword arguments to launch a custom jupyter server.
+ [Arguments] ${home_dir}
+ ${notebook_dir} = Set Variable ${home_dir}${/}work
+ ${app_data} = Get Windows App Data ${home_dir}
+ &{config} = Create Dictionary
+ ... stdout=${OUTPUT DIR}${/}lab.log
+ ... stderr=STDOUT
+ ... cwd=${notebook_dir}
+ ... env:HOME=${home_dir}
+ ... env:APPDATA=${app_data}
+ ... env:JUPYTER_PREFER_ENV_PATH=0
+ RETURN ${config}
+
+Get Windows App Data
+ [Documentation] Get an overloaded appdata directory.
+ [Arguments] ${home_dir}
+ RETURN ${home_dir}${/}AppData${/}Roaming
+
+Build Custom JupyterLab Args
+ [Documentation] Generate some args
+ [Arguments] ${port} ${token} ${base url}
+ @{args} = Loads ${JUPYTERLAB_EXE}
+ ${config} = Normalize Path ${ROOT}${/}atest${/}fixtures${/}jupyter_config.json
+ @{args} = Set Variable
+ ... @{args}
+ ... --no-browser
+ ... --debug
+ ... --expose-app-in-browser
+ ... --port\=${port}
+ ... --IdentityProvider.token\=${token}
+ ... --ServerApp.base_url\=${base url}
+ Log ${args}
+ RETURN @{args}
+
+Initialize Fake Home
+ [Documentation] Populate a fake HOME
+ ${home_dir} = Set Variable ${OUTPUT_DIR}${/}.home
+ ${local} = Get XDG Local Path ${home_dir}
+ ${etc} = Set Variable ${local}${/}etc${/}jupyter
+ FOR ${src} ${dest} IN &{ETC_OVERRIDES}
+ OperatingSystem.Copy File ${FIXTURES}${/}${src} ${etc}${/}${dest}
+ END
+ Create Directory ${home_dir}${/}work
+ RETURN ${home_dir}
+
+Get XDG Local Path
+ [Documentation] Get the root of the XDG local data for this platform.
+ [Arguments] ${home_dir}
+ IF "${OS}" == "Windows"
+ ${app_data} = Get Windows App Data ${home_dir}
+ ${local} = Set Variable ${app_data}${/}Python
+ ELSE
+ ${local} = Set Variable ${home_dir}${/}.local
+ END
+ RETURN ${local}
+
+Get GeckoDriver Executable Path
+ [Documentation] Find geckodriver
+ IF "${OS}" == "Windows"
+ ${executable_path} = Which geckodriver.exe
+ ELSE
+ ${executable_path} = Which geckodriver
+ END
+ RETURN ${executable_path}
diff --git a/atest/resources/Sidebar.resource b/atest/resources/Sidebar.resource
index 53bb515..d915862 100644
--- a/atest/resources/Sidebar.resource
+++ b/atest/resources/Sidebar.resource
@@ -12,6 +12,8 @@ Make Cell Layer With Sidebar
[Arguments] ${idx} ${layer} ${screenshot}=${EMPTY}
Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${idx})
Maybe Open JupyterLab Sidebar Property Inspector
+ Maybe Expand Panel With Title Advanced Tools
+ Maybe Expand Panel With Title Common Tools
Maybe Open Cell Metadata JSON
Select From List By Value css:${CSS_DECK_LAYER_SELECT} ${layer}
IF '${layer}' != '-'
@@ -26,6 +28,8 @@ Use Cell Style Preset
[Arguments] ${idx} ${preset} ${expect}=${EMPTY} ${screenshot}=${EMPTY}
Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${idx})
Maybe Open JupyterLab Sidebar Property Inspector
+ Maybe Expand Panel With Title Advanced Tools
+ Maybe Expand Panel With Title Common Tools
Maybe Open Cell Metadata JSON
Select From List By Value css:${CSS_DECK_PRESET_SELECT} ${preset}
Click Element css:${CSS_DECK_TOOL_PRESET} button
diff --git a/atest/suites/__init__.robot b/atest/suites/__init__.robot
index 080d79b..1e1ca2a 100644
--- a/atest/suites/__init__.robot
+++ b/atest/suites/__init__.robot
@@ -20,5 +20,6 @@ Set Up Root Suite
Tear Down Root Suite
[Documentation] Do global suite teardown.
Close All Browsers
+ Terminate All Jupyter Servers
Terminate All Processes
Empty Screenshot Trash
diff --git a/atest/suites/lab/00-smoke.robot b/atest/suites/lab/00-smoke.robot
index 4e2978e..7039a03 100644
--- a/atest/suites/lab/00-smoke.robot
+++ b/atest/suites/lab/00-smoke.robot
@@ -3,6 +3,7 @@ Documentation JupyterLab is not broken.
Library JupyterLibrary
Resource ../../resources/Coverage.resource
+Resource ../../resources/Lab.resource
Resource ../../resources/Screenshots.resource
Suite Setup Set Attempt Screenshot Directory lab${/}smoke
@@ -14,3 +15,5 @@ Force Tags suite:smoke
JupyterLab Opens
[Documentation] JupyterLab opens.
Capture Page Screenshot 00-smoke.png
+ Plugins Should Be Disabled
+ ... @jupyterlab/apputils-extension:notification
diff --git a/atest/suites/lab/__init__.robot b/atest/suites/lab/__init__.robot
index 52888bb..b0700fd 100644
--- a/atest/suites/lab/__init__.robot
+++ b/atest/suites/lab/__init__.robot
@@ -1,9 +1,10 @@
*** Settings ***
Documentation Tests for JupyterLab.
-Library uuid
Library JupyterLibrary
Resource ../../resources/Coverage.resource
+Resource ../../resources/Lab.resource
+Resource ../../resources/Server.resource
Resource ../../resources/LabSelectors.resource
Suite Setup Set Up Lab Suite
@@ -12,35 +13,13 @@ Suite Teardown Tear Down Lab Suite
Force Tags app:lab
-*** Variables ***
-${LOG_DIR} ${OUTPUT_DIR}${/}logs
-
-
*** Keywords ***
Set Up Lab Suite
[Documentation] Ensure a testable server is running
- ${port} = Get Unused Port
- ${base_url} = Set Variable /@rf/
- ${token} = UUID4
- Create Directory ${LOG_DIR}
- Wait For New Jupyter Server To Be Ready
- ... jupyter-lab
- ... ${port}
- ... ${base_url}
- ... ${NONE} # notebook_dir
- ... ${token.__str__()}
- ... --config\=${ROOT}${/}atest${/}fixtures${/}jupyter_config.json
- ... --no-browser
- ... --debug
- ... --port\=${port}
- ... --NotebookApp.token\='${token.__str__()}'
- ... --NotebookApp.base_url\='${base_url}'
- ... stdout=${LOG_DIR}${/}lab.log
- Open JupyterLab
- Disable JupyterLab Modal Command Palette
- Set Window Size 1366 768
- Reload Page
- Wait For JupyterLab Splash Screen
+ Set Suite Variable ${JD_APP_UNDER_TEST} lab children=${TRUE}
+ ${home_dir} = Initialize Fake Home
+ Initialize Jupyter Server ${home_dir}
+ Initialize JupyterLab
Tear Down Lab Suite
[Documentation] Do clean up stuff
diff --git a/atest/suites/nb/00-smoke.robot b/atest/suites/nb/00-smoke.robot
new file mode 100644
index 0000000..99d82bb
--- /dev/null
+++ b/atest/suites/nb/00-smoke.robot
@@ -0,0 +1,19 @@
+*** Settings ***
+Documentation Jupyter Notebook is not broken.
+
+Library JupyterLibrary
+Resource ../../resources/Lab.resource
+Resource ../../resources/Coverage.resource
+Resource ../../resources/Screenshots.resource
+
+Suite Setup Set Attempt Screenshot Directory nb${/}smoke
+
+Force Tags suite:smoke
+
+
+*** Test Cases ***
+Jupyter Notebook Opens
+ [Documentation] Jupyter Notebook opens.
+ Capture Page Screenshot 00-smoke.png
+ Plugins Should Be Disabled
+ ... @jupyterlab/apputils-extension:notification
diff --git a/atest/suites/nb/01-examples.robot b/atest/suites/nb/01-examples.robot
new file mode 100644
index 0000000..eb6d5f9
--- /dev/null
+++ b/atest/suites/nb/01-examples.robot
@@ -0,0 +1,54 @@
+*** Settings ***
+Documentation The examples work in Notebook.
+
+Library OperatingSystem
+Library JupyterLibrary
+Resource ../../resources/Fixtures.resource
+Resource ../../resources/Deck.resource
+Resource ../../resources/Screenshots.resource
+
+Suite Setup Set Up Example Suite
+Suite Teardown Clean Examples
+
+Force Tags suite:examples
+
+
+*** Test Cases ***
+The README Notebook Can Be Navigated
+ [Documentation] All slides and fragments are reachable.
+ [Tags] activity:notebook
+ Visit All Example Slides And Fragments ${README_IPYNB}
+ [Teardown] Reset Example Test
+
+
+*** Keywords ***
+Visit All Example Slides And Fragments
+ [Documentation] The given file in `examples` operates as expected.
+ [Arguments] ${example}=README.ipynb
+ ${stem} = Set Variable ${example.lower().replace(" ", "_")}
+ Open Example ${example} switch_window=README
+ Capture Page Screenshot ${stem}-00-before-deck.png
+ IF ${example.endswith('.ipynb')}
+ Really Start Deck With Notebook Toolbar Button
+ ELSE IF ${example.endswith('.md')}
+ Start Markdown Deck From Editor ${example}
+ ELSE
+ Execute JupyterLab Command Start Deck
+ END
+ Capture Page Screenshot ${stem}-01-deck.png
+ Visit Slides And Fragments With Remote ${example} ${stem}-02-walk
+ Stop Deck With Remote
+ Capture Page Screenshot ${stem}-03-after-deck.png
+
+Set Up Example Suite
+ [Documentation] Prepare for this suite.
+ Set Attempt Screenshot Directory lab${/}examples
+ Copy Examples
+
+Reset Example Test
+ [Documentation] Clean up after each test.
+ Maybe Open JupyterLab Sidebar Commands
+ Execute JupyterLab Command Stop Deck
+ Execute JupyterLab Command Save
+ Capture Page Coverage
+ Switch Window title:Home
diff --git a/atest/suites/nb/__init__.robot b/atest/suites/nb/__init__.robot
new file mode 100644
index 0000000..2d3be95
--- /dev/null
+++ b/atest/suites/nb/__init__.robot
@@ -0,0 +1,28 @@
+*** Settings ***
+Documentation Tests for Notebook.
+
+Library JupyterLibrary
+Resource ../../resources/Coverage.resource
+Resource ../../resources/Lab.resource
+Resource ../../resources/Notebook.resource
+Resource ../../resources/Server.resource
+Resource ../../resources/LabSelectors.resource
+
+Suite Setup Set Up Notebook Suite
+Suite Teardown Tear Down Notebook Suite
+
+Force Tags app:nb
+
+
+*** Keywords ***
+Set Up Notebook Suite
+ [Documentation] Ensure a testable server is running
+ Set Suite Variable ${JD_APP_UNDER_TEST} nb children=${TRUE}
+ ${home_dir} = Initialize Fake Home
+ Initialize Jupyter Server ${home_dir}
+ Initialize Jupyter Notebook
+
+Tear Down Notebook Suite
+ [Documentation] Do clean up stuff
+ Maybe Accept A JupyterLab Prompt
+ Capture Page Coverage
diff --git a/docs/conf.py b/docs/conf.py
index 8c88dd2..468df39 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,25 +1,19 @@
-"""documentation for jupyterlab-deck"""
+"""documentation for jupyterlab-deck."""
import datetime
-import json
-import os
-import subprocess
from pathlib import Path
import tomli
-os.environ.update(IN_SPHINX="1")
-
CONF_PY = Path(__file__)
HERE = CONF_PY.parent
ROOT = HERE.parent
PYPROJ = ROOT / "pyproject.toml"
PROJ_DATA = tomli.loads(PYPROJ.read_text(encoding="utf-8"))
-RTD = json.loads(os.environ.get("READTHEDOCS", "False").lower())
# metadata
author = PROJ_DATA["project"]["authors"][0]["name"]
project = PROJ_DATA["project"]["name"]
-copyright = f"{datetime.date.today().year}, {author}"
+copyright = f"{datetime.datetime.now(tz=datetime.timezone.utc).date().year}, {author}"
# The full version, including alpha/beta/rc tags
release = PROJ_DATA["project"]["version"]
@@ -73,7 +67,7 @@
html_theme_options = {
"github_url": PROJ_DATA["project"]["urls"]["Source"],
"use_edit_page_button": True,
- "logo": dict(text=PROJ_DATA["project"]["name"]),
+ "logo": {"text": PROJ_DATA["project"]["name"]},
"icon_links": [
{
"name": "PyPI",
@@ -102,7 +96,3 @@
}
html_sidebars = {"**": []}
-
-
-def setup(app):
- subprocess.check_call(["doit", "lite"], cwd=ROOT)
diff --git a/docs/dictionary.txt b/docs/dictionary.txt
index ab934bf..445e423 100644
--- a/docs/dictionary.txt
+++ b/docs/dictionary.txt
@@ -1,3 +1,6 @@
+0a0
+0b0
+0rc0
Changelog
ci
composable
@@ -15,6 +18,7 @@ MathJax
MermaidJS
npm
PRs
+PyData
pypi
PyPI
README
@@ -25,7 +29,9 @@ spacebar
submodule
subslide
Subslide
+subtasks
TBD
+themey
UI
un
UX
diff --git a/dodo.py b/dodo.py
index 6f3f26a..480fb10 100644
--- a/dodo.py
+++ b/dodo.py
@@ -1,4 +1,4 @@
-"""automation for jupyterlab-deck"""
+"""automation for jupyterlab-deck."""
import json
import os
import platform
@@ -11,11 +11,22 @@
import doit.tools
+DOT_ENV = Path(".env")
+
+dotenv_loaded = {}
+
+if DOT_ENV.exists():
+ dotenv_loaded = __import__("dotenv").dotenv_values(DOT_ENV)
+ os.environ.update(dotenv_loaded)
+
class C:
NPM_NAME = "@deathbeds/jupyterlab-deck"
- OLD_VERSION = "0.1.3"
- VERSION = "0.1.4"
+ OLD_VERSION = "0.1.4"
+ VERSION = "0.2.0a0"
+ JS_VERSION = (
+ VERSION.replace("a", "-alpha.").replace("b", "-beta.").replace("rc", "-rc.")
+ )
PACKAGE_JSON = "package.json"
PYPROJECT_TOML = "pyproject.toml"
PABOT_DEFAULTS = [
@@ -24,9 +35,11 @@ class C:
"png,log,txt,svg,ipynb,json",
]
PLATFORM = platform.system()
- PY_VERSION = "{}.{}".format(sys.version_info[0], sys.version_info[1])
+ PY_VERSION = f"{sys.version_info[0]}.{sys.version_info[1]}"
ROBOT_DRYRUN = "--dryrun"
NYC = ["jlpm", "nyc", "report"]
+ HISTORY = "conda-meta/history"
+ CONDA_RUN = ["conda", "run", "--no-capture-output", "--prefix"]
class P:
@@ -35,8 +48,10 @@ class P:
BINDER = ROOT / ".binder"
DOCS = ROOT / "docs"
CI = ROOT / ".github"
+ SCRIPTS = ROOT / "_scripts"
DEMO_ENV_YAML = BINDER / "environment.yml"
TEST_ENV_YAML = CI / "environment-test.yml"
+ TEST_35_ENV_YAML = CI / "environment-test-lab35.yml"
DOCS_ENV_YAML = CI / "environment-docs.yml"
BASE_ENV_YAML = CI / "environment-base.yml"
BUILD_ENV_YAML = CI / "environment-build.yml"
@@ -54,9 +69,10 @@ class P:
],
DOCS_ENV_YAML: [BUILD_ENV_YAML, BASE_ENV_YAML],
TEST_ENV_YAML: [BASE_ENV_YAML, BUILD_ENV_YAML, ROBOT_ENV_YAML],
+ TEST_35_ENV_YAML: [BASE_ENV_YAML, TEST_ENV_YAML, ROBOT_ENV_YAML],
LINT_ENV_YAML: [BASE_ENV_YAML, BUILD_ENV_YAML, ROBOT_ENV_YAML],
}
- YARNRC = ROOT / ".yarnrc"
+ YARNRC = ROOT / ".yarnrc.yml"
YARN_LOCK = ROOT / "yarn.lock"
JS = ROOT / "js"
JS_META = JS / "_meta"
@@ -67,7 +83,8 @@ class P:
EXT_JS_WEBPACK = EXT_JS_PKG / "webpack.config.js"
EXT_JS_LICENSE = EXT_JS_PKG / "LICENSE"
EXT_JS_README = EXT_JS_PKG / "README.md"
- PY_SRC = ROOT / "src/jupyterlab_deck"
+ SRC = ROOT / "src"
+ PY_SRC = ROOT / "jupyterlab_deck"
PYPROJECT_TOML = ROOT / C.PYPROJECT_TOML
DOCS_STATIC = DOCS / "_static"
DOCS_PY = [*DOCS.glob("*.py")]
@@ -80,59 +97,77 @@ class P:
PAGES_LITE = ROOT / "pages-lite"
PAGES_LITE_CONFIG = PAGES_LITE / "jupyter_lite_config.json"
PAGES_LITE_JSON = PAGES_LITE / "jupyter-lite.json"
- ESLINTRC = JS / ".eslintrc.js"
ALL_PLUGIN_SCHEMA = [*JS.glob("*/schema/*.json")]
ATEST = ROOT / "atest"
ROBOT_SUITES = ATEST / "suites"
+ SCRIPT_LABEXT = SCRIPTS / "labextension.py"
+ ATEST_JP_CONFIG = ATEST / "fixtures/jupyter_config.json"
+
+
+def _fromenv(name, default, *, coerce=None, lower=None):
+ lower = True if lower is None else lower
+ raw = os.environ.get(name, default)
+ raw = raw.lower() if lower else raw
+ coerce = coerce or bool
+ return coerce(json.loads(raw))
class E:
- IN_CI = bool(json.loads(os.environ.get("CI", "false").lower()))
- BUILDING_IN_CI = bool(json.loads(os.environ.get("BUILDING_IN_CI", "false").lower()))
- TESTING_IN_CI = bool(json.loads(os.environ.get("TESTING_IN_CI", "false").lower()))
- IN_RTD = bool(json.loads(os.environ.get("READTHEDOCS", "False").lower()))
- IN_BINDER = bool(json.loads(os.environ.get("IN_BINDER", "0")))
+ IN_CI = _fromenv("CI", "false")
+ BUILDING_IN_CI = _fromenv("BUILDING_IN_CI", "false")
+ TESTING_IN_CI = _fromenv("TESTING_IN_CI", "false")
+ IN_RTD = _fromenv("READTHEDOCS", "False")
+ IN_BINDER = _fromenv("IN_BINDER", "0")
LOCAL = not (IN_BINDER or IN_CI or IN_RTD)
- ROBOT_RETRIES = json.loads(os.environ.get("ROBOT_RETRIES", "0"))
- ROBOT_ARGS = json.loads(os.environ.get("ROBOT_ARGS", "[]"))
- PABOT_ARGS = json.loads(os.environ.get("PABOT_ARGS", "[]"))
- WITH_JS_COV = bool(json.loads(os.environ.get("WITH_JS_COV", "0")))
- PABOT_PROCESSES = int(json.loads(os.environ.get("PABOT_PROCESSES", "4")))
+ ROBOT_RETRIES = _fromenv("ROBOT_RETRIES", "0", coerce=int)
+ ROBOT_ATTEMPT = _fromenv("ROBOT_ATTEMPT", "0", coerce=int)
+ ROBOT_ARGS = _fromenv("ROBOT_ARGS", "[]", coerce=list, lower=False)
+ PABOT_ARGS = _fromenv("PABOT_ARGS", "[]", coerce=list, lower=False)
+ WITH_JS_COV = _fromenv("WITH_JS_COV", "0", coerce=int)
+ PABOT_PROCESSES = _fromenv("PABOT_PROCESSES", "4", coerce=int)
+ MOZ_HEADLESS = _fromenv("MOZ_HEADLESS", "1", coerce=int)
class B:
+ BUILD = P.ROOT / "build"
ENV = P.ROOT / ".venv" if E.LOCAL else Path(sys.prefix)
- HISTORY = [ENV / "conda-meta/history"] if E.LOCAL else []
+ HISTORY = [ENV / C.HISTORY] if E.LOCAL else []
+ ENV_LEGACY = BUILD / ".venv-legacy"
+ HISTORY_LEGACY = ENV_LEGACY / C.HISTORY
NODE_MODULES = P.ROOT / "node_modules"
- YARN_INTEGRITY = NODE_MODULES / ".yarn-integrity"
+ YARN_INTEGRITY = NODE_MODULES / ".yarn-state.yml"
JS_META_TSBUILDINFO = P.JS_META / ".src.tsbuildinfo"
- BUILD = P.ROOT / "build"
DIST = P.ROOT / "dist"
DOCS = BUILD / "docs"
DOCS_BUILDINFO = DOCS / ".buildinfo"
LITE = BUILD / "lite"
- STATIC = P.PY_SRC / f"_d/share/jupyter/labextensions/{C.NPM_NAME}"
+ STATIC = P.SRC / f"_d/share/jupyter/labextensions/{C.NPM_NAME}"
STATIC_PKG_JSON = STATIC / C.PACKAGE_JSON
WHEEL = DIST / f"jupyterlab_deck-{C.VERSION}-py3-none-any.whl"
- SDIST = DIST / f"jupyterlab-deck-{C.VERSION}.tar.gz"
+ SDIST = DIST / f"jupyterlab_deck-{C.VERSION}.tar.gz"
LITE_SHASUMS = LITE / "SHA256SUMS"
STYLELINT_CACHE = BUILD / ".stylelintcache"
- NPM_TARBALL = DIST / f"deathbeds-jupyterlab-deck-{C.VERSION}.tgz"
+ NPM_TARBALL = DIST / f"deathbeds-jupyterlab-deck-{C.JS_VERSION}.tgz"
DIST_HASH_DEPS = [NPM_TARBALL, WHEEL, SDIST]
DIST_SHASUMS = DIST / "SHA256SUMS"
ENV_PKG_JSON = ENV / f"share/jupyter/labextensions/{C.NPM_NAME}/{C.PACKAGE_JSON}"
PIP_FROZEN = BUILD / "pip-freeze.txt"
+ PIP_FROZEN_LEGACY = BUILD / "pip-freeze-legacy.txt"
REPORTS = BUILD / "reports"
ROBOCOV = BUILD / "__robocov__"
REPORTS_NYC = REPORTS / "nyc"
REPORTS_NYC_LCOV = REPORTS_NYC / "lcov.info"
REPORTS_COV_XML = REPORTS / "coverage-xml"
PYTEST_HTML = REPORTS / "pytest.html"
+ PYTEST_HTML_LEGACY = REPORTS / "pytest-legacy.html"
PYTEST_COV_XML = REPORTS_COV_XML / "pytest.coverage.xml"
+ PYTEST_COV_XML_LEGACY = REPORTS_COV_XML / "pytest-legacy.coverage.xml"
HTMLCOV_HTML = REPORTS / "htmlcov/index.html"
+ HTMLCOV_HTML_LEGACY = REPORTS / "htmlcov-legacy/index.html"
ROBOT = REPORTS / "robot"
- ROBOT_SCREENSHOTS = ROBOT / "screenshots"
- ROBOT_LOG_HTML = ROBOT / "log.html"
+ ROBOT_LATEST = ROBOT / "latest"
+ ROBOT_LEGACY = ROBOT / "legacy"
+ ROBOT_LOG_HTML = ROBOT_LATEST / "log.html"
PAGES_LITE = BUILD / "pages-lite"
PAGES_LITE_SHASUMS = PAGES_LITE / "SHA256SUMS"
SPELLING = BUILD / "spelling"
@@ -142,20 +177,26 @@ class B:
class L:
ALL_DOCS_MD = [*P.DOCS.rglob("*.md")]
ALL_PY_SRC = [*P.PY_SRC.rglob("*.py")]
- ALL_BLACK = [P.DODO, *ALL_PY_SRC, *P.DOCS_PY]
- ALL_CSS = [*P.DOCS_STATIC.rglob("*.css"), *P.JS.glob("*/style/**/*.css")]
+ ALL_PY_SCRIPTS = [*P.SCRIPTS.rglob("*.py")]
+ ALL_BLACK = [P.DODO, *ALL_PY_SRC, *P.DOCS_PY, *ALL_PY_SCRIPTS]
+ ALL_CSS_SRC = [*P.JS.glob("*/style/**/*.css")]
+ ALL_CSS = [*P.DOCS_STATIC.rglob("*.css"), *ALL_CSS_SRC]
ALL_JSON = [
- *P.ROOT.glob(".json"),
+ *P.ALL_PLUGIN_SCHEMA,
+ *P.EXAMPLES.glob("*.json"),
*P.JS.glob("*.json"),
*P.JS.glob("*/src/**/*.json"),
- *P.ALL_PLUGIN_SCHEMA,
+ *P.PAGES_LITE.glob("*.json"),
+ *P.ROOT.glob(".json"),
]
ALL_MD = [
- *P.ROOT.glob("*.md"),
- *P.DOCS.rglob("*.md"),
*P.CI.rglob("*.md"),
+ *P.DOCS.rglob("*.md"),
+ *P.EXAMPLES.glob("*.md"),
*P.EXAMPLES.glob("*.md"),
*P.EXT_JS_PKG.glob("*.md"),
+ *P.PAGES_LITE.glob("*.md"),
+ *P.ROOT.glob("*.md"),
]
ALL_TS = [*P.JS.glob("*/src/**/*.ts"), *P.JS.glob("*/src/**/*.tsx")]
ALL_YML = [*P.BINDER.glob("*.yml"), *P.CI.rglob("*.yml"), *P.ROOT.glob("*.yml")]
@@ -165,11 +206,13 @@ class L:
class U:
+ @staticmethod
def do(args, **kwargs):
cwd = kwargs.pop("cwd", P.ROOT)
shell = kwargs.pop("shell", False)
return doit.tools.CmdAction(args, shell=shell, cwd=cwd, **kwargs)
+ @staticmethod
def source_date_epoch():
return (
subprocess.check_output(["git", "log", "-1", "--format=%ct"])
@@ -177,6 +220,7 @@ def source_date_epoch():
.strip()
)
+ @staticmethod
def hash_files(hashfile, *hash_deps):
from hashlib import sha256
@@ -191,11 +235,18 @@ def hash_files(hashfile, *hash_deps):
print(output)
hashfile.write_text(output)
- def pip_list():
- B.PIP_FROZEN.write_bytes(
- subprocess.check_output([sys.executable, "-m", "pip", "freeze"])
+ @staticmethod
+ def pip_list(frozen_file=None, pip_args=None):
+ frozen_file = frozen_file or B.PIP_FROZEN
+ pip_args = pip_args or [sys.executable, "-m", "pip"]
+ frozen_file.parent.mkdir(exist_ok=True, parents=True)
+ frozen_file.write_bytes(
+ subprocess.check_output([*pip_args, "list", "--format=freeze"]),
)
+ with frozen_file.open("a", encoding="utf-8") as fd:
+ fd.write(f"\n# {time.time()}\n")
+ @staticmethod
def copy_one(src, dest):
if not dest.parent.exists():
dest.parent.mkdir(parents=True)
@@ -203,18 +254,20 @@ def copy_one(src, dest):
dest.unlink()
shutil.copy2(src, dest)
+ @staticmethod
def copy_some(dest, srcs):
for src in srcs:
U.copy_one(src, dest / src.name)
+ @staticmethod
def clean_some(*paths):
-
for path in paths:
if path.is_dir():
shutil.rmtree(path)
elif path.exists():
path.unlink()
+ @staticmethod
def ensure_version(path: Path):
text = path.read_text(encoding="utf-8")
if path.name == C.PACKAGE_JSON:
@@ -239,7 +292,9 @@ def ensure_version(path: Path):
print(f"Patching {path} with: {expected}")
path.write_text(new_text)
+ return None
+ @staticmethod
def update_env_fragments(dest_env: Path, src_envs: typing.List[Path]):
dest_text = dest_env.read_text(encoding="utf-8")
print(f"... adding packages to {dest_env.relative_to(P.ROOT)}")
@@ -256,54 +311,82 @@ def update_env_fragments(dest_env: Path, src_envs: typing.List[Path]):
f" {src_chunk.strip()}",
pattern,
f" {dest_chunks[2].strip()}",
- ]
+ ],
)
dest_env.write_text(dest_text.strip() + "\n")
- def make_robot_tasks(extra_args=None):
+ @staticmethod
+ def make_robot_tasks(lab_env: Path, out_root: Path, extra_args=None):
extra_args = extra_args or []
name = "robot"
- file_dep = [*B.HISTORY, *L.ALL_ROBOT]
+ file_dep = [lab_env / C.HISTORY, *L.ALL_ROBOT]
if C.ROBOT_DRYRUN in extra_args:
name = f"{name}:dryrun"
else:
- file_dep += [B.PIP_FROZEN, *L.ALL_PY_SRC, *L.ALL_TS, *L.ALL_JSON]
- out_dir = B.ROBOT / U.get_robot_stem(attempt=1, extra_args=extra_args)
+ file_dep += [*L.ALL_PY_SRC, *L.ALL_TS, *L.ALL_JSON]
+ if lab_env == B.ENV:
+ file_dep += [B.PIP_FROZEN]
+ else:
+ file_dep += [B.PIP_FROZEN_LEGACY]
+ out_dir = out_root / U.get_robot_stem(
+ attempt=1,
+ extra_args=extra_args,
+ )
targets = [
out_dir / "output.xml",
out_dir / "log.html",
out_dir / "report.html",
]
actions = []
- if E.WITH_JS_COV and C.ROBOT_DRYRUN not in extra_args:
+
+ if (
+ out_root.name == "latest"
+ and E.WITH_JS_COV
+ and C.ROBOT_DRYRUN not in extra_args
+ ):
targets += [B.REPORTS_NYC_LCOV]
actions += [
(U.clean_some, [B.ROBOCOV, B.REPORTS_NYC]),
(doit.tools.create_folder, [B.ROBOCOV]),
]
- yield dict(
- name=name,
- uptodate=[
- doit.tools.config_changed(dict(cov=E.WITH_JS_COV, args=E.ROBOT_ARGS))
+
+ yield {
+ "name": name,
+ "uptodate": [
+ doit.tools.config_changed({"cov": E.WITH_JS_COV, "args": E.ROBOT_ARGS}),
],
- file_dep=file_dep,
- actions=[*actions, (U.run_robot_with_retries, [extra_args])],
- targets=targets,
- )
+ "file_dep": file_dep,
+ "actions": [
+ *actions,
+ (U.run_robot_with_retries, [lab_env, out_root, extra_args]),
+ ],
+ "targets": targets,
+ }
- def run_robot_with_retries(extra_args=None):
- attempt = 0
- fail_count = -1
+ @staticmethod
+ def run_robot_with_retries(lab_env, out_root, extra_args=None):
extra_args = [*(extra_args or []), *E.ROBOT_ARGS]
is_dryrun = C.ROBOT_DRYRUN in extra_args
- retries = 0 if is_dryrun else E.ROBOT_RETRIES
+ fail_count = -1
+
+ retries = E.ROBOT_RETRIES
+ attempt = E.ROBOT_ATTEMPT
+
+ if is_dryrun:
+ retries = 0
+ attempt = 0
while fail_count != 0 and attempt <= retries:
attempt += 1
- print("attempt {} of {}...".format(attempt, retries + 1), flush=True)
+ print(f"attempt {attempt} of {retries + 1}...", flush=True)
start_time = time.time()
- fail_count = U.run_robot(attempt=attempt, extra_args=extra_args)
+ fail_count = U.run_robot(
+ lab_env=lab_env,
+ out_root=out_root,
+ attempt=attempt,
+ extra_args=extra_args,
+ )
print(
fail_count,
"failed in",
@@ -320,22 +403,30 @@ def run_robot_with_retries(extra_args=None):
print(f"did not generate any coverage files in {B.ROBOCOV}")
fail_count = -2
else:
-
subprocess.call(
- [*C.NYC, f"--report-dir={B.REPORTS_NYC}", f"--temp-dir={B.ROBOCOV}"]
+ [
+ *C.NYC,
+ f"--report-dir={B.REPORTS_NYC}",
+ f"--temp-dir={B.ROBOCOV}",
+ ],
)
- final = B.ROBOT / "output.xml"
+ final = out_root / "output.xml"
all_robot = [
str(p)
- for p in B.ROBOT.rglob("output.xml")
+ for p in out_root.rglob("output.xml")
if p != final and "dry_run" not in str(p) and "pabot_results" not in str(p)
]
+ runner = ["python"]
+
+ if lab_env != B.ENV:
+ runner = [*C.CONDA_RUN, str(lab_env), *runner]
+
subprocess.call(
[
- "python",
+ *runner,
"-m",
"robot.rebot",
"--name",
@@ -344,21 +435,28 @@ def run_robot_with_retries(extra_args=None):
"--merge",
*all_robot,
],
- cwd=B.ROBOT,
+ cwd=out_root,
)
- if B.ROBOT_SCREENSHOTS.exists():
- shutil.rmtree(B.ROBOT_SCREENSHOTS)
+ screens = out_root / "screenshots"
+
+ if screens.exists():
+ shutil.rmtree(screens)
- B.ROBOT_SCREENSHOTS.mkdir()
+ screens.mkdir(parents=True)
- for screen_root in B.ROBOT.glob("*/screenshots/*"):
- shutil.copytree(screen_root, B.ROBOT_SCREENSHOTS / screen_root.name)
+ for screen_root in out_root.glob("*/screenshots/*"):
+ shutil.copytree(screen_root, screens / screen_root.name)
return fail_count == 0
- def get_robot_stem(attempt=0, extra_args=None, browser="headlessfirefox"):
- """get the directory in B.ROBOT for this platform/app"""
+ @staticmethod
+ def get_robot_stem(
+ attempt=0,
+ extra_args=None,
+ browser="headlessfirefox",
+ ):
+ """Get the directory in B.ROBOT for this platform/app."""
extra_args = extra_args or []
browser = browser.replace("headless", "")
@@ -370,18 +468,44 @@ def get_robot_stem(attempt=0, extra_args=None, browser="headlessfirefox"):
return stem
- def run_robot(attempt=0, extra_args=None):
+ @staticmethod
+ def prep_robot(out_dir: Path):
+ if out_dir.exists():
+ print(f">>> trying to clean out {out_dir}", flush=True)
+ try:
+ shutil.rmtree(out_dir)
+ except Exception as err:
+ print(
+ f"... error, hopefully harmless: {err}",
+ flush=True,
+ )
+
+ if not out_dir.exists():
+ print(
+ f">>> trying to prepare output directory: {out_dir}",
+ flush=True,
+ )
+ try:
+ out_dir.mkdir(parents=True)
+ except Exception as err:
+ print(
+ f"... Error, hopefully harmless: {err}",
+ flush=True,
+ )
+
+ @staticmethod
+ def run_robot(out_root: Path, lab_env: Path, attempt=0, extra_args=None):
import lxml.etree as ET
extra_args = extra_args or []
stem = U.get_robot_stem(attempt=attempt, extra_args=extra_args)
- out_dir = B.ROBOT / stem
+ out_dir = out_root / stem
if attempt > 1:
extra_args += ["--loglevel", "TRACE"]
prev_stem = U.get_robot_stem(attempt=attempt - 1, extra_args=extra_args)
- previous = B.ROBOT / prev_stem / "output.xml"
+ previous = out_root / prev_stem / "output.xml"
if previous.exists():
extra_args += ["--rerunfailed", str(previous)]
@@ -392,6 +516,10 @@ def run_robot(attempt=0, extra_args=None):
*E.PABOT_ARGS,
]
+ if lab_env == B.ENV_LEGACY:
+ runner = [*C.CONDA_RUN, str(lab_env), *runner]
+ extra_args += ["--exclude", "app:nb"]
+
if C.ROBOT_DRYRUN in extra_args:
runner = ["robot"]
@@ -412,39 +540,12 @@ def run_robot(attempt=0, extra_args=None):
*extra_args,
]
- if out_dir.exists():
- print(">>> trying to clean out {}".format(out_dir), flush=True)
- try:
- shutil.rmtree(out_dir)
- except Exception as err:
- print(
- "... error, hopefully harmless: {}".format(err),
- flush=True,
- )
-
- if not out_dir.exists():
- print(
- ">>> trying to prepare output directory: {}".format(out_dir), flush=True
- )
- try:
- out_dir.mkdir(parents=True)
- except Exception as err:
- print(
- "... Error, hopefully harmless: {}".format(err),
- flush=True,
- )
+ str_args = [*map(str, [*args, P.ROBOT_SUITES])]
- str_args = [
- *map(
- str,
- [
- *args,
- P.ROBOT_SUITES,
- ],
- )
- ]
print(">>> ", " ".join(str_args), flush=True)
+ U.prep_robot(out_dir)
+
proc = subprocess.Popen(str_args, cwd=P.ATEST)
try:
@@ -465,9 +566,11 @@ def run_robot(attempt=0, extra_args=None):
return fail_count
+ @staticmethod
def rel(*paths):
return [p.relative_to(P.ROOT) for p in paths]
+ @staticmethod
def check_one_spell(html: Path, findings: Path):
proc = subprocess.Popen(
[
@@ -476,7 +579,6 @@ def check_one_spell(html: Path, findings: Path):
"-p",
P.DOCS_DICTIONARY,
"-l",
- "-L",
"-H",
str(html),
],
@@ -491,22 +593,75 @@ def check_one_spell(html: Path, findings: Path):
print("...", html)
print(out_text)
return False
+ return None
+ @staticmethod
def rewrite_links(path: Path):
text = path.read_text(encoding="utf-8")
text = text.replace(".md", ".html")
text = text.replace(".ipynb", ".ipynb.html")
path.write_text(text)
+ @staticmethod
+ def lab(lab_env: Path):
+ fake_home = lab_env / ".fake_home"
+ if fake_home.exists():
+ shutil.rmtree(fake_home)
+ fake_home.mkdir(parents=True)
+
+ env = dict(**os.environ)
+ env["HOME"] = str(fake_home)
+
+ run_args = [*C.CONDA_RUN, str(lab_env)]
+ args = [*run_args, "jupyter", "lab", "--config", P.ATEST_JP_CONFIG]
+
+ str_args = list(map(str, args))
+ print(">>>", "\t".join(str_args))
+ proc = subprocess.Popen(str_args, stdin=subprocess.PIPE, env=env)
+
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ print("attempting to stop lab, you may want to check your process monitor")
+ proc.terminate()
+ proc.communicate(b"y\n")
+
+ proc.wait()
+ return True
+
+ @staticmethod
+ def make_pytest_tasks(file_dep, pytest_html, htmlcov, pytest_cov_xml):
+ yield {
+ "name": "pytest",
+ "file_dep": file_dep,
+ "actions": [
+ [
+ "pytest",
+ "--pyargs",
+ P.PY_SRC.name,
+ f"--cov={P.PY_SRC.name}",
+ "--cov-branch",
+ "--no-cov-on-fail",
+ "--cov-fail-under=100",
+ "--cov-report=term-missing:skip-covered",
+ f"--cov-report=html:{htmlcov.parent}",
+ f"--html={pytest_html}",
+ "--self-contained-html",
+ f"--cov-report=xml:{pytest_cov_xml}",
+ ],
+ ],
+ "targets": [pytest_html, htmlcov, pytest_cov_xml],
+ }
+
def task_env():
for env_dest, env_src in P.ENV_INHERIT.items():
- yield dict(
- name=f"conda:{env_dest.name}",
- targets=[env_dest],
- file_dep=[*env_src],
- actions=[(U.update_env_fragments, [env_dest, env_src])],
- )
+ yield {
+ "name": f"conda:{env_dest.name}",
+ "targets": [env_dest],
+ "file_dep": [*env_src],
+ "actions": [(U.update_env_fragments, [env_dest, env_src])],
+ }
def task_setup():
@@ -515,48 +670,97 @@ def task_setup():
dedupe = []
if E.LOCAL:
- dedupe = [["jlpm", "yarn-deduplicate", "-s", "fewer", "--fail"]]
- yield dict(
- name="conda",
- file_dep=[P.DEMO_ENV_YAML],
- targets=[*B.HISTORY],
- actions=[
- ["mamba", "env", "update", "--prefix", B.ENV, "--file", P.DEMO_ENV_YAML]
+ dedupe = [["jlpm", "yarn-berry-deduplicate", "-s", "fewer", "--fail"]]
+ yield {
+ "name": "conda",
+ "file_dep": [P.DEMO_ENV_YAML],
+ "targets": [*B.HISTORY],
+ "actions": [
+ [
+ "mamba",
+ "env",
+ "update",
+ "--prefix",
+ B.ENV,
+ "--file",
+ P.DEMO_ENV_YAML,
+ ],
],
- )
+ }
if E.LOCAL or not B.YARN_INTEGRITY.exists():
- yield dict(
- name="yarn",
- file_dep=[
+ yield {
+ "name": "yarn",
+ "file_dep": [
P.YARNRC,
*B.HISTORY,
*P.ALL_PACKAGE_JSONS,
*([P.YARN_LOCK] if P.YARN_LOCK.exists() else []),
],
- actions=[
- ["jlpm", *([] if E.LOCAL else ["--frozen-lockfile"])],
+ "actions": [
+ ["jlpm", *([] if E.LOCAL else ["--immutable"])],
*dedupe,
],
- targets=[B.YARN_INTEGRITY],
- )
+ "targets": [B.YARN_INTEGRITY],
+ }
-def task_watch():
- yield dict(
- name="js",
- actions=[["jlpm", "lerna", "run", "watch", "--stream", "--parallel"]],
- file_dep=[B.YARN_INTEGRITY],
+def task_legacy():
+ yield {
+ "name": "conda",
+ "file_dep": [P.TEST_35_ENV_YAML],
+ "targets": [B.HISTORY_LEGACY],
+ "actions": [
+ [
+ "mamba",
+ "env",
+ "update",
+ "--prefix",
+ B.ENV_LEGACY,
+ "--file",
+ P.TEST_35_ENV_YAML,
+ ],
+ ],
+ }
+
+ legacy_pip = [*C.CONDA_RUN, B.ENV_LEGACY, "python", "-m", "pip"]
+
+ yield {
+ "name": "pip",
+ "file_dep": [B.HISTORY_LEGACY, B.WHEEL],
+ "targets": [B.PIP_FROZEN_LEGACY],
+ "actions": [
+ [*legacy_pip, "install", "--no-deps", "--ignore-installed", B.WHEEL],
+ [*legacy_pip, "check"],
+ (U.pip_list, [B.PIP_FROZEN_LEGACY, legacy_pip]),
+ ],
+ }
+
+ yield from U.make_pytest_tasks(
+ file_dep=[B.PIP_FROZEN_LEGACY],
+ pytest_html=B.PYTEST_HTML_LEGACY,
+ htmlcov=B.HTMLCOV_HTML_LEGACY,
+ pytest_cov_xml=B.PYTEST_COV_XML_LEGACY,
)
+ yield from U.make_robot_tasks(lab_env=B.ENV_LEGACY, out_root=B.ROBOT_LEGACY)
+
+
+def task_watch():
+ yield {
+ "name": "js",
+ "actions": [["jlpm", "lerna", "run", "watch", "--stream", "--parallel"]],
+ "file_dep": [B.YARN_INTEGRITY],
+ }
+
def task_docs():
- yield dict(
- name="sphinx",
- file_dep=[*P.DOCS_PY, *L.ALL_MD, *B.HISTORY, B.WHEEL, B.LITE_SHASUMS],
- actions=[["sphinx-build", "-b", "html", "docs", "build/docs"]],
- targets=[B.DOCS_BUILDINFO],
- )
+ yield {
+ "name": "sphinx",
+ "file_dep": [*P.DOCS_PY, *L.ALL_MD, *B.HISTORY, B.WHEEL, B.LITE_SHASUMS],
+ "actions": [["sphinx-build", "-b", "html", "docs", "build/docs"]],
+ "targets": [B.DOCS_BUILDINFO],
+ }
@doit.create_after("docs")
@@ -572,21 +776,21 @@ def task_check():
for example in P.EXAMPLES.glob("*.ipynb"):
out_html = B.EXAMPLE_HTML / f"{example.name}.html"
all_spell += [out_html]
- yield dict(
- name=f"nbconvert:{example.name}",
- actions=[
+ yield {
+ "name": f"nbconvert:{example.name}",
+ "actions": [
(doit.tools.create_folder, [B.EXAMPLE_HTML]),
["jupyter", "nbconvert", "--to=html", "--output", out_html, example],
(U.rewrite_links, [out_html]),
],
- file_dep=[example],
- targets=[out_html],
- )
-
- yield dict(
- name="links",
- file_dep=[B.DOCS_BUILDINFO, *all_html],
- actions=[
+ "file_dep": [example],
+ "targets": [out_html],
+ }
+
+ yield {
+ "name": "links",
+ "file_dep": [B.DOCS_BUILDINFO, *all_html],
+ "actions": [
[
"pytest-check-links",
"-vv",
@@ -594,22 +798,22 @@ def task_check():
"--check-links-ignore",
"http.*",
*all_html,
- ]
+ ],
],
- )
+ }
for html_path in all_spell:
stem = html_path.relative_to(P.ROOT)
report = B.SPELLING / f"{stem}.txt"
- yield dict(
- name=f"spelling:{stem}",
- actions=[
+ yield {
+ "name": f"spelling:{stem}",
+ "actions": [
(doit.tools.create_folder, [report.parent]),
(U.check_one_spell, [html_path, report]),
],
- file_dep=[html_path, P.DOCS_DICTIONARY],
- targets=[report],
- )
+ "file_dep": [html_path, P.DOCS_DICTIONARY],
+ "targets": [report],
+ }
def task_dist():
@@ -632,34 +836,34 @@ def build_with_sde():
)
return rc == 0
- yield dict(
- name="flit",
- file_dep=[*L.ALL_PY_SRC, P.PYPROJECT_TOML, B.STATIC_PKG_JSON],
- actions=[build_with_sde],
- targets=[B.WHEEL, B.SDIST],
- )
+ yield {
+ "name": "flit",
+ "file_dep": [*L.ALL_PY_SRC, P.PYPROJECT_TOML, B.STATIC_PKG_JSON],
+ "actions": [build_with_sde],
+ "targets": [B.WHEEL, B.SDIST],
+ }
- yield dict(
- name="npm",
- file_dep=[
+ yield {
+ "name": "npm",
+ "file_dep": [
B.JS_META_TSBUILDINFO,
*P.ALL_PACKAGE_JSONS,
P.EXT_JS_README,
P.EXT_JS_LICENSE,
],
- targets=[B.NPM_TARBALL],
- actions=[
+ "targets": [B.NPM_TARBALL],
+ "actions": [
(doit.tools.create_folder, [B.DIST]),
U.do(["npm", "pack", P.EXT_JS_PKG], cwd=B.DIST),
],
- )
+ }
- yield dict(
- name="hash",
- file_dep=[*B.DIST_HASH_DEPS],
- targets=[B.DIST_SHASUMS],
- actions=[(U.hash_files, [B.DIST_SHASUMS, *B.DIST_HASH_DEPS])],
- )
+ yield {
+ "name": "hash",
+ "file_dep": [*B.DIST_HASH_DEPS],
+ "targets": [B.DIST_SHASUMS],
+ "actions": [(U.hash_files, [B.DIST_SHASUMS, *B.DIST_HASH_DEPS])],
+ }
def task_dev():
@@ -668,21 +872,28 @@ def task_dev():
pip_args = [ci_artifact]
py_dep = [ci_artifact]
else:
- py_dep = [B.ENV_PKG_JSON]
+ py_dep = [B.STATIC_PKG_JSON]
pip_args = [
"-e",
".",
"--ignore-installed",
"--no-deps",
+ "--no-build-isolation",
+ "--disable-pip-version-check",
]
- yield dict(
- name="ext",
- actions=[
- ["jupyter", "labextension", "develop", "--overwrite", "."],
+ yield {
+ "name": "ext",
+ "actions": [
+ ["python", P.SCRIPT_LABEXT, "develop", "--debug", "--overwrite", "."],
],
- file_dep=[B.STATIC_PKG_JSON, *P.ALL_PLUGIN_SCHEMA],
- targets=[B.ENV_PKG_JSON],
- )
+ "file_dep": [
+ B.STATIC_PKG_JSON,
+ *P.ALL_PLUGIN_SCHEMA,
+ P.SCRIPT_LABEXT,
+ B.PIP_FROZEN,
+ ],
+ "targets": [B.ENV_PKG_JSON],
+ }
check = []
@@ -690,48 +901,32 @@ def task_dev():
# avoid sphinx-rtd-theme
check = [[sys.executable, "-m", "pip", "check"]]
- yield dict(
- name="py",
- file_dep=py_dep,
- targets=[B.PIP_FROZEN],
- actions=[
+ yield {
+ "name": "pip",
+ "file_dep": py_dep,
+ "targets": [B.PIP_FROZEN],
+ "actions": [
[sys.executable, "-m", "pip", "install", "-vv", *pip_args],
*check,
- (doit.tools.create_folder, [B.BUILD]),
U.pip_list,
],
- )
+ }
def task_test():
- file_dep = [B.STATIC_PKG_JSON, *L.ALL_PY_SRC]
+ file_dep = [B.PIP_FROZEN]
- if E.TESTING_IN_CI:
- file_dep = []
+ if not E.TESTING_IN_CI:
+ file_dep += [B.STATIC_PKG_JSON, *L.ALL_PY_SRC]
- yield dict(
- name="pytest",
- file_dep=[B.PIP_FROZEN, *file_dep],
- actions=[
- [
- "pytest",
- "--pyargs",
- P.PY_SRC.name,
- f"--cov={P.PY_SRC.name}",
- "--cov-branch",
- "--no-cov-on-fail",
- "--cov-fail-under=100",
- "--cov-report=term-missing:skip-covered",
- f"--cov-report=html:{B.HTMLCOV_HTML.parent}",
- f"--html={B.PYTEST_HTML}",
- "--self-contained-html",
- f"--cov-report=xml:{B.PYTEST_COV_XML}",
- ]
- ],
- targets=[B.PYTEST_HTML, B.HTMLCOV_HTML, B.PYTEST_COV_XML],
+ yield from U.make_pytest_tasks(
+ file_dep=file_dep,
+ pytest_html=B.PYTEST_HTML,
+ htmlcov=B.HTMLCOV_HTML,
+ pytest_cov_xml=B.PYTEST_COV_XML,
)
- yield from U.make_robot_tasks()
+ yield from U.make_robot_tasks(lab_env=B.ENV, out_root=B.ROBOT_LATEST)
def task_lint():
@@ -743,24 +938,17 @@ def task_lint():
path = pkg_json.parent.relative_to(P.ROOT)
name = f"js:{C.PACKAGE_JSON}:{path}"
pkg_json_tasks += [f"lint:{name}"]
- yield dict(
- uptodate=[version_uptodate],
- name=f"js:version:{path}",
- file_dep=[pkg_json],
- actions=[(U.ensure_version, [pkg_json])],
- )
- yield dict(
- name=name,
- task_dep=[f"lint:js:version:{path}"],
- file_dep=[pkg_json, B.YARN_INTEGRITY],
- actions=[["jlpm", "prettier-package-json", "--write", *U.rel(pkg_json)]],
- )
-
- yield dict(
- name="js:prettier",
- file_dep=[*L.ALL_PRETTIER, B.YARN_INTEGRITY],
- task_dep=pkg_json_tasks,
- actions=[
+ yield {
+ "name": name,
+ "file_dep": [pkg_json, B.YARN_INTEGRITY],
+ "actions": [["jlpm", "prettier-package-json", "--write", *U.rel(pkg_json)]],
+ }
+
+ yield {
+ "name": "js:prettier",
+ "file_dep": [*L.ALL_PRETTIER, B.YARN_INTEGRITY],
+ "task_dep": pkg_json_tasks,
+ "actions": [
[
"jlpm",
"stylelint",
@@ -778,120 +966,128 @@ def task_lint():
*U.rel(*L.ALL_PRETTIER),
],
],
- )
+ }
- yield dict(
- name="js:eslint",
- task_dep=["lint:js:prettier"],
- file_dep=[*L.ALL_TS, P.ESLINTRC, B.YARN_INTEGRITY],
- actions=[
+ yield {
+ "name": "js:eslint",
+ "task_dep": ["lint:js:prettier"],
+ "file_dep": [*L.ALL_TS, *P.ALL_PACKAGE_JSONS, B.YARN_INTEGRITY],
+ "actions": [
[
"jlpm",
"eslint",
"--cache",
"--cache-location",
*U.rel(B.BUILD / ".eslintcache"),
- "--config",
- *U.rel(P.ESLINTRC),
"--ext",
".js,.jsx,.ts,.tsx",
*([] if E.IN_CI else ["--fix"]),
*U.rel(P.JS),
- ]
+ ],
],
- )
+ }
- yield dict(
- name="version:py",
- uptodate=[version_uptodate],
- file_dep=[P.PYPROJECT_TOML],
- actions=[(U.ensure_version, [P.PYPROJECT_TOML])],
- )
+ yield {
+ "name": "version:py",
+ "uptodate": [version_uptodate],
+ "file_dep": [P.PYPROJECT_TOML],
+ "actions": [(U.ensure_version, [P.PYPROJECT_TOML])],
+ }
check = ["--check"] if E.IN_CI else []
rel_black = U.rel(*L.ALL_BLACK)
- yield dict(
- name="py:black",
- file_dep=[*L.ALL_BLACK, *B.HISTORY, P.PYPROJECT_TOML],
- task_dep=["lint:version:py"],
- actions=[
- ["isort", *check, *rel_black],
+ yield {
+ "name": "py:black",
+ "file_dep": [*L.ALL_BLACK, *B.HISTORY, P.PYPROJECT_TOML],
+ "task_dep": ["lint:version:py"],
+ "actions": [
["ssort", *check, *rel_black],
["black", *check, *rel_black],
+ ["ruff", "--fix-only", *rel_black],
],
- )
+ }
- yield dict(
- name="py:pyflakes",
- file_dep=[*L.ALL_BLACK, *B.HISTORY, P.PYPROJECT_TOML],
- task_dep=["lint:py:black"],
- actions=[["pyflakes", *rel_black]],
- )
+ yield {
+ "name": "py:ruff",
+ "file_dep": [*L.ALL_BLACK, *B.HISTORY, P.PYPROJECT_TOML],
+ "task_dep": ["lint:py:black"],
+ "actions": [["ruff", *rel_black]],
+ }
- yield dict(
- name="robot:tidy",
- file_dep=[*L.ALL_ROBOT, *B.HISTORY],
- actions=[["robotidy", *U.rel(P.ATEST)]],
- )
+ yield {
+ "name": "robot:tidy",
+ "file_dep": [*L.ALL_ROBOT, *B.HISTORY],
+ "actions": [["robotidy", *U.rel(P.ATEST)]],
+ }
- yield dict(
- name="robot:cop",
- task_dep=["lint:robot:tidy"],
- file_dep=[*L.ALL_ROBOT, *B.HISTORY],
- actions=[["robocop", *U.rel(P.ATEST)]],
- )
+ yield {
+ "name": "robot:cop",
+ "task_dep": ["lint:robot:tidy"],
+ "file_dep": [*L.ALL_ROBOT, *B.HISTORY],
+ "actions": [["robocop", *U.rel(P.ATEST)]],
+ }
- yield from U.make_robot_tasks(extra_args=[C.ROBOT_DRYRUN])
+ yield from U.make_robot_tasks(
+ lab_env=B.ENV,
+ out_root=B.ROBOT_LATEST,
+ extra_args=[C.ROBOT_DRYRUN],
+ )
def task_build():
for dest in [P.EXT_JS_README, P.EXT_JS_LICENSE]:
src = P.ROOT / dest.name
- yield dict(
- name=f"meta:js:{dest.name}",
- file_dep=[src],
- actions=[(U.copy_one, [src, dest])],
- targets=[dest],
- )
-
- uptodate = [doit.tools.config_changed(dict(WITH_JS_COV=E.WITH_JS_COV))]
-
- ext_dep = [*P.JS_PACKAGE_JSONS, P.EXT_JS_WEBPACK]
+ yield {
+ "name": f"meta:js:{dest.name}",
+ "file_dep": [src],
+ "actions": [(U.copy_one, [src, dest])],
+ "targets": [dest],
+ }
+
+ uptodate = [doit.tools.config_changed({"WITH_JS_COV": E.WITH_JS_COV})]
+
+ ext_dep = [
+ *P.JS_PACKAGE_JSONS,
+ P.EXT_JS_WEBPACK,
+ *L.ALL_CSS_SRC,
+ *L.ALL_TS,
+ *L.ALL_CSS_SRC,
+ ]
if E.WITH_JS_COV:
ext_task = "labextension:build:cov"
else:
ext_task = "labextension:build"
ext_dep += [B.JS_META_TSBUILDINFO]
- yield dict(
- uptodate=uptodate,
- name="js",
- actions=[["jlpm", "lerna", "run", "build"]],
- file_dep=[*L.ALL_TS, B.YARN_INTEGRITY],
- targets=[B.JS_META_TSBUILDINFO],
- )
-
- yield dict(
- uptodate=uptodate,
- name="ext",
- actions=[["jlpm", "lerna", "run", ext_task]],
- file_dep=ext_dep,
- targets=[B.STATIC_PKG_JSON],
- )
+ yield {
+ "uptodate": uptodate,
+ "name": "js",
+ "actions": [["jlpm", "lerna", "run", "build"]],
+ "file_dep": [*L.ALL_TS, B.YARN_INTEGRITY],
+ "targets": [B.JS_META_TSBUILDINFO],
+ }
+
+ yield {
+ "uptodate": uptodate,
+ "name": "ext",
+ "actions": [["jlpm", "lerna", "run", ext_task]],
+ "file_dep": ext_dep,
+ "targets": [B.STATIC_PKG_JSON],
+ }
def task_site():
- yield dict(
- name="build",
- file_dep=[
+ yield {
+ "name": "build",
+ "file_dep": [
P.PAGES_LITE_CONFIG,
P.PAGES_LITE_JSON,
B.ENV_PKG_JSON,
B.ROBOT_LOG_HTML,
B.PIP_FROZEN,
],
- targets=[B.PAGES_LITE_SHASUMS],
- actions=[
+ "targets": [B.PAGES_LITE_SHASUMS],
+ "actions": [
U.do(
["jupyter", "lite", "--debug", "build"],
cwd=P.PAGES_LITE,
@@ -901,22 +1097,21 @@ def task_site():
cwd=P.PAGES_LITE,
),
],
- )
+ }
def task_lite():
-
- yield dict(
- name="build",
- file_dep=[
+ yield {
+ "name": "build",
+ "file_dep": [
P.LITE_CONFIG,
P.LITE_JSON,
B.ENV_PKG_JSON,
*P.ALL_EXAMPLES,
B.PIP_FROZEN,
],
- targets=[B.LITE_SHASUMS],
- actions=[
+ "targets": [B.LITE_SHASUMS],
+ "actions": [
U.do(
["jupyter", "lite", "--debug", "build"],
cwd=P.EXAMPLES,
@@ -926,32 +1121,37 @@ def task_lite():
cwd=P.EXAMPLES,
),
],
- )
+ }
def task_serve():
+ yield {
+ "name": "lab",
+ "uptodate": [lambda: False],
+ "file_dep": [B.ENV_PKG_JSON, B.PIP_FROZEN],
+ "actions": [doit.tools.PythonInteractiveAction(U.lab, [B.ENV])],
+ }
- import subprocess
-
- def lab():
- proc = subprocess.Popen(
- list(map(str, ["jupyter", "lab", "--no-browser", "--debug"])),
- stdin=subprocess.PIPE,
- )
-
- try:
- proc.wait()
- except KeyboardInterrupt:
- print("attempting to stop lab, you may want to check your process monitor")
- proc.terminate()
- proc.communicate(b"y\n")
+ yield {
+ "name": "lab:legacy",
+ "uptodate": [lambda: False],
+ "file_dep": [B.PIP_FROZEN_LEGACY],
+ "actions": [doit.tools.PythonInteractiveAction(U.lab, [B.ENV_LEGACY])],
+ }
- proc.wait()
- return True
- yield dict(
- name="lab",
- uptodate=[lambda: False],
- file_dep=[B.ENV_PKG_JSON, B.PIP_FROZEN],
- actions=[doit.tools.PythonInteractiveAction(lab)],
- )
+# otherwise it goes.... somewhere
+{
+ os.environ.update({k: f"{v}"})
+ for k, v in {
+ "JUPYTER_PLATFORM_DIRS": 1,
+ "MOZ_HEADLESS": E.MOZ_HEADLESS,
+ "NX_CACHE_DIRECTORY": P.ROOT / "build/.cache/nx",
+ "NX_PROJECT_GRAPH_CACHE_DIRECTORY": P.ROOT / "build/.cache/nx-graph",
+ "PYDEVD_DISABLE_FILE_VALIDATION": 1,
+ }.items()
+ if k not in os.environ
+}
+
+if dotenv_loaded:
+ os.environ.update(dotenv_loaded)
diff --git a/examples/History.ipynb b/examples/History.ipynb
index 17b8be6..ff1cfc2 100644
--- a/examples/History.ipynb
+++ b/examples/History.ipynb
@@ -25,11 +25,11 @@
"tags": []
},
"source": [
- "```{note}\n",
- "Before we go any further, **PRETTY PLEASE**\n",
- "- f11 to view full-screen\n",
- "- ctrl+ up to something comfortable to you (maybe around 200%)\n",
- "```"
+ "> **Note**\n",
+ "> \n",
+ "> Before we go any further, **PRETTY PLEASE**\n",
+ "> - f11 to view full-screen\n",
+ "> - ctrl+ up to something comfortable to you (maybe around 200%)"
]
},
{
@@ -303,7 +303,7 @@
"source": [
"### `jupyterlab-drawio`\n",
"\n",
- "> `jupyterlab-drawio` demonstrated a direct embedding of the core `mxgraph`, editor inside JupyterLab."
+ "> `jupyterlab-drawio` demonstrated a direct embedding of the core `mxgraph` editor inside JupyterLab."
]
},
{
@@ -472,10 +472,9 @@
"source": [
"## Thanks!\n",
"\n",
- " \n",
- "```{hint}\n",
- "[Back to the main Deck](./README.ipynb#Documents)\n",
- "```\n",
+ "> **Hint**\n",
+ ">\n",
+ "> [Back to the main Deck](./README.ipynb#Documents)\n",
"\n",
"... or learn more, below..."
]
@@ -519,7 +518,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.6"
+ "version": "3.11.5"
},
"toc-autonumbering": true
},
diff --git a/examples/Layers.ipynb b/examples/Layers.ipynb
index 553ff60..e3ad645 100644
--- a/examples/Layers.ipynb
+++ b/examples/Layers.ipynb
@@ -133,9 +133,9 @@
"tags": []
},
"source": [
- "```{note}\n",
- "`fragment` layer with a `top` of `60%`\n",
- "```"
+ "> **Note**\n",
+ "> \n",
+ "> `fragment` layer with a `top` of `60%`"
]
},
{
@@ -173,9 +173,9 @@
"tags": []
},
"source": [
- "```{warning}\n",
- "`slide` layer with a `top` of `30%`\n",
- "```"
+ "> **Note**\n",
+ "> \n",
+ "> `slide` layer with a `top` of `30%`"
]
},
{
@@ -214,9 +214,9 @@
"tags": []
},
"source": [
- "```{hint}\n",
- "`stack` layer with a `top` of `5%`\n",
- "```"
+ "> **Hint**\n",
+ "> \n",
+ "> `stack` layer with a `top` of `5%`"
]
},
{
@@ -260,7 +260,7 @@
},
"source": [
"- `top`, `bottom`, `right`, `left`, `width` and `height` do what they say\n",
- " - using relative values, `0` to `100%`, is the most *reliable"
+ " - using relative values, `0` to `100%`, is the most *reliable*"
]
},
{
@@ -301,9 +301,9 @@
"source": [
"- `pointer-events` can be set to `\"none\"` which makes an element **non-interactive**\n",
"\n",
- "```{danger}\n",
- "It's **pretty hard** to fix elements like this while presenting!\n",
- "```"
+ "> **Danger**\n",
+ "> \n",
+ "> It's **pretty hard** to fix elements like this while presenting!"
]
},
{
@@ -335,9 +335,9 @@
"id": "2b365b99-8c62-4a0e-9179-bb9b3337aa14",
"metadata": {},
"source": [
- "```{note}\n",
- "The slide above has `\"zoom\": \"200%\"`... but always remember to crank up your browser zoom, anyway.\n",
- "```"
+ "> **Note**\n",
+ ">\n",
+ "> The slide above has `\"zoom\": \"200%\"`... but always remember to crank up your browser zoom, anyway."
]
}
],
@@ -361,7 +361,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.6"
+ "version": "3.11.5"
}
},
"nbformat": 4,
diff --git a/examples/README.ipynb b/examples/README.ipynb
index 229519e..e46e40b 100644
--- a/examples/README.ipynb
+++ b/examples/README.ipynb
@@ -4,6 +4,7 @@
"cell_type": "markdown",
"id": "b9ef2cd6-b100-4e32-856e-831034e5c829",
"metadata": {
+ "editable": true,
"slideshow": {
"slide_type": "slide"
},
@@ -20,11 +21,11 @@
"id": "10231e4c-f9b0-484b-b77c-28aff72c8408",
"metadata": {},
"source": [
- "```{note}\n",
- "Before we go any further, **PRETTY PLEASE**\n",
- "- f11 to view full-screen\n",
- "- ctrl+ up to something comfortable to you (maybe around 200%)\n",
- "```"
+ "> **Note**\n",
+ "> \n",
+ "> Before we go any further, **PRETTY PLEASE**\n",
+ "> - f11 to view full-screen\n",
+ "> - ctrl+ up to something comfortable to you (maybe around 200%)"
]
},
{
@@ -54,6 +55,7 @@
"cell_type": "markdown",
"id": "8c7c3ba2-6741-4be8-b0b5-677c7f50afd7",
"metadata": {
+ "editable": true,
"slideshow": {
"slide_type": "slide"
},
@@ -238,9 +240,9 @@
"source": [
"### This is a Subslide\n",
"\n",
- "```{hint}\n",
- "- go back to the `slide` above with ↑ or shift space\n",
- "```"
+ "> **Hint**\n",
+ ">\n",
+ "> go back to the `slide` above with ↑ or shift space\n"
]
},
{
@@ -561,9 +563,9 @@
"tags": []
},
"source": [
- "```{hint}\n",
- "For more themey goodness, try [jupyterlab-fonts](https://github.com/deathbeds/jupyterlab-fonts).\n",
- "```"
+ "> **Hint**\n",
+ "> \n",
+ "> For more themey goodness, try [jupyterlab-fonts](https://github.com/deathbeds/jupyterlab-fonts)."
]
},
{
@@ -633,6 +635,7 @@
"cell_type": "markdown",
"id": "3f5bdf51-553c-4ca9-ba02-08a09e4cef28",
"metadata": {
+ "editable": true,
"slideshow": {
"slide_type": "fragment"
},
@@ -666,7 +669,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.6"
+ "version": "3.11.5"
},
"toc-autonumbering": true,
"toc-showcode": false,
diff --git a/examples/jupyter-lite.json b/examples/jupyter-lite.json
index ed9a244..2215ec8 100644
--- a/examples/jupyter-lite.json
+++ b/examples/jupyter-lite.json
@@ -2,11 +2,6 @@
"jupyter-lite-schema-version": 0,
"jupyter-config-data": {
"appName": "jupyterlab-deck",
- "collaborative": true,
- "exposeAppInBrowser": true,
- "disabledExtensions": [
- "@jupyterlite/javascript-kernel-extension",
- "jupyterlab-videochat:rooms-server"
- ]
+ "exposeAppInBrowser": true
}
}
diff --git a/js/.eslintrc.js b/js/.eslintrc.js
deleted file mode 100644
index b50941c..0000000
--- a/js/.eslintrc.js
+++ /dev/null
@@ -1,91 +0,0 @@
-module.exports = {
- env: {
- browser: true,
- es6: true,
- commonjs: true,
- node: true,
- },
- globals: { JSX: 'readonly' },
- root: true,
- extends: [
- 'eslint:recommended',
- 'plugin:import/errors',
- 'plugin:import/warnings',
- 'plugin:import/typescript',
- 'plugin:@typescript-eslint/eslint-recommended',
- 'plugin:@typescript-eslint/recommended',
- 'prettier',
- 'plugin:react/recommended',
- ],
- ignorePatterns: [
- '**/node_modules/**/*',
- '**/lib/**/*',
- '**/_*.ts',
- '**/_*.d.ts',
- '**/typings/**/*.d.ts',
- '**/dist/*',
- 'js/.eslintrc.js',
- ],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- project: 'js/tsconfig.eslint.json',
- },
- plugins: ['@typescript-eslint', 'import'],
- rules: {
- '@typescript-eslint/camelcase': 'off',
- '@typescript-eslint/explicit-function-return-type': 'off',
- '@typescript-eslint/explicit-module-boundary-types': 'off',
- '@typescript-eslint/no-empty-interface': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
- '@typescript-eslint/no-inferrable-types': 'off',
- '@typescript-eslint/no-namespace': 'off',
- '@typescript-eslint/no-non-null-assertion': 'off',
- '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
- '@typescript-eslint/no-use-before-define': 'off',
- '@typescript-eslint/no-var-requires': 'off',
- 'no-case-declarations': 'warn',
- 'no-control-regex': 'warn',
- 'no-inner-declarations': 'off',
- 'no-prototype-builtins': 'off',
- 'no-undef': 'warn',
- 'no-useless-escape': 'off',
- 'prefer-const': 'off',
- 'import/no-unresolved': 'off',
- // the default, but for reference...
- 'import/order': [
- 'warn',
- {
- groups: [
- 'builtin',
- 'external',
- 'parent',
- 'sibling',
- 'index',
- 'object',
- 'unknown',
- ],
- pathGroups: [
- { pattern: 'react/**', group: 'builtin', order: 'after' },
- { pattern: 'codemirror/**', group: 'external', order: 'before' },
- { pattern: '@lumino/**', group: 'builtin', order: 'before' },
- { pattern: '@jupyterlab/**', group: 'external', order: 'after' },
- ],
- 'newlines-between': 'always',
- alphabetize: { order: 'asc' },
- },
- ],
- // deviations from jupyterlab, should probably be fixed
- 'import/no-cycle': 'off', // somehow we lapsed here... ~200 cycles now
- 'import/export': 'off', // we do class/interface + NS pun exports _all over_
- '@typescript-eslint/triple-slash-reference': 'off',
- 'no-async-promise-executor': 'off',
- 'prefer-spread': 'off',
- 'react/display-name': 'off',
- },
- settings: {
- react: {
- version: 'detect',
- },
- },
-};
diff --git a/js/_meta/package.json b/js/_meta/package.json
index beca711..0332ce9 100644
--- a/js/_meta/package.json
+++ b/js/_meta/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@deathbeds/jupyterlab-deck-metapackage",
- "version": "0.1.4",
+ "version": "0.0.0",
"description": "JupyterLab Deck - Metapackage",
"license": "BSD-3-Clause",
"author": "jupyterlab-deck contributors",
@@ -20,6 +20,6 @@
},
"types": "lib/index.d.ts",
"dependencies": {
- "@deathbeds/jupyterlab-deck": "file:../jupyterlab-deck"
+ "@deathbeds/jupyterlab-deck": "workspace:^"
}
}
diff --git a/js/jupyterlab-deck/README.md b/js/jupyterlab-deck/README.md
index f031938..a9856a6 100644
--- a/js/jupyterlab-deck/README.md
+++ b/js/jupyterlab-deck/README.md
@@ -7,10 +7,11 @@
[binder-badge]: https://mybinder.org/badge_logo.svg
[binder]:
https://mybinder.org/v2/gh/deathbeds/jupyterlab-deck/HEAD?urlpath=lab/tree/examples/README.ipynb
-[ci-badge]: https://img.shields.io/github/workflow/status/deathbeds/jupyterlab-deck/CI
+[ci-badge]:
+ https://img.shields.io/github/actions/workflow/status/deathbeds/jupyterlab-deck/ci.yml
[ci]: https://github.com/deathbeds/jupyterlab-deck/actions?query=branch%3Amain
[reports-badge]:
- https://img.shields.io/github/workflow/status/deathbeds/jupyterlab-deck/pages?label=reports
+ https://img.shields.io/github/actions/workflow/status/deathbeds/jupyterlab-deck/pages.yml?label=reports
[reports]: https://deathbeds.github.io/jupyterlab-deck/lab/index.html?path=README.ipynb
[rtd-badge]: https://img.shields.io/readthedocs/jupyterlab-deck
[rtd]: https://jupyterlab-deck.rtfd.io
@@ -164,7 +165,7 @@ following _scopes_:
### Design Tools
-> In [Deck mode](#deck-mode), click the _ellipsis_ icon in the bottom right corner
+> In [Deck mode](#deck-mode), click the _ellipsis_ icon in the bottom left corner
The design tools offer lightweight buttons to:
@@ -230,9 +231,10 @@ restore the part to the default layout.
### Does it work with `notebook 7`?
-**Not yet.** Navigating multiple documents during the same presentation will probably
+**Mostly.** Navigating multiple documents during the same presentation will probably
never work, as this is incompatible with the one-document-at-a-time design constraint of
-the Notebook UX.
+the Notebook UX. Each skip to another document will open a new browser tab, though deck
+should be installed.
### Will it generate PowerPoint?
diff --git a/js/jupyterlab-deck/package.json b/js/jupyterlab-deck/package.json
index 388be4b..c5ef383 100644
--- a/js/jupyterlab-deck/package.json
+++ b/js/jupyterlab-deck/package.json
@@ -1,6 +1,6 @@
{
"name": "@deathbeds/jupyterlab-deck",
- "version": "0.1.4",
+ "version": "0.2.0-alpha.0",
"description": "Lightweight presentations for JupyterLab",
"license": "BSD-3-Clause",
"author": "jupyterlab-deck contributors",
@@ -14,28 +14,30 @@
},
"main": "lib/index.js",
"scripts": {
- "labextension:build": "jupyter labextension build .",
- "labextension:build:cov": "tsc -b src/tsconfig.cov.json && jupyter labextension build .",
- "watch": "jupyter labextension watch ."
+ "labextension": "python ../../_scripts/labextension.py",
+ "labextension:build": "jlpm labextension build --debug .",
+ "labextension:build:cov": "tsc -b src/tsconfig.cov.json && jlpm labextension:build",
+ "watch": "jlpm labextension watch ."
},
"types": "lib/index.d.ts",
"dependencies": {
- "@jupyterlab/application": "3",
- "@jupyterlab/apputils": "3",
- "@jupyterlab/markdownviewer": "3",
- "@jupyterlab/notebook": "3",
- "@jupyterlab/statusbar": "3",
- "@jupyterlab/ui-components": "3",
+ "@jupyterlab/application": "3 || 4",
+ "@jupyterlab/apputils": "3 || 4",
+ "@jupyterlab/markdownviewer": "3 || 4",
+ "@jupyterlab/notebook": "3 || 4",
+ "@jupyterlab/statusbar": "3 || 4",
+ "@jupyterlab/ui-components": "3 || 4",
"d3-drag": "3"
},
"devDependencies": {
- "@deathbeds/jupyterlab-fonts": "^2.1.1",
- "@jupyterlab/builder": "^3.4.8",
+ "@deathbeds/jupyterlab-fonts": "^3.0.0-alpha.3",
+ "@jupyter-notebook/application": "^7.0.5",
+ "@jupyterlab/builder": "^4.0.7",
"@types/d3-drag": "3"
},
"jupyterlab": {
"extension": "lib/plugin.js",
- "outputDir": "../../src/jupyterlab_deck/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-deck",
+ "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-deck",
"schemaDir": "schema",
"webpackConfig": "./webpack.config.js",
"sharedPackages": {
diff --git a/js/jupyterlab-deck/src/labcompat.ts b/js/jupyterlab-deck/src/labcompat.ts
new file mode 100644
index 0000000..c7a1df7
--- /dev/null
+++ b/js/jupyterlab-deck/src/labcompat.ts
@@ -0,0 +1,28 @@
+import type { ICellModel } from '@jupyterlab/cells';
+import type { INotebookModel } from '@jupyterlab/notebook';
+import { toArray } from '@lumino/algorithm';
+import { JSONExt } from '@lumino/coreutils';
+import type { DockPanel, TabBar, Widget } from '@lumino/widgets';
+
+const { emptyArray } = JSONExt;
+
+export function getTabBars(dockPanel: DockPanel): TabBar[] {
+ if (!dockPanel) {
+ return emptyArray as any as TabBar[];
+ }
+
+ return toArray(dockPanel.tabBars());
+}
+
+export function getCellModels(notebookModel: INotebookModel): ICellModel[] {
+ if (!notebookModel) {
+ return emptyArray as any as ICellModel[];
+ }
+
+ return toArray(notebookModel.cells);
+}
+
+export function getSelectedWidget(dockPanel: DockPanel): Widget | null {
+ const selectedWidgets = toArray(dockPanel.selectedWidgets());
+ return selectedWidgets.length ? selectedWidgets[0] : null;
+}
diff --git a/js/jupyterlab-deck/src/manager.ts b/js/jupyterlab-deck/src/manager.ts
index 85c379c..95ea949 100644
--- a/js/jupyterlab-deck/src/manager.ts
+++ b/js/jupyterlab-deck/src/manager.ts
@@ -1,15 +1,16 @@
import { IFontManager } from '@deathbeds/jupyterlab-fonts';
import { GlobalStyles } from '@deathbeds/jupyterlab-fonts/lib/_schema';
-import { LabShell } from '@jupyterlab/application';
+import type { INotebookShell } from '@jupyter-notebook/application';
+import { LabShell, JupyterFrontEnd, ILabShell } from '@jupyterlab/application';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { StatusBar } from '@jupyterlab/statusbar';
import { TranslationBundle } from '@jupyterlab/translation';
-import { each } from '@lumino/algorithm';
import { CommandRegistry } from '@lumino/commands';
import { Signal, ISignal } from '@lumino/signaling';
import { Widget, DockPanel } from '@lumino/widgets';
import { ICONS } from './icons';
+import { getSelectedWidget, getTabBars } from './labcompat';
import {
IDeckManager,
DATA,
@@ -37,12 +38,14 @@ export class DeckManager implements IDeckManager {
protected _activeChanged = new Signal(this);
protected _activeWidget: Widget | null = null;
protected _presenters: IPresenter[] = [];
- protected _appStarted: Promise;
+ protected _appStarted: () => Promise;
protected _commands: CommandRegistry;
protected _remote: DeckRemote | null = null;
protected _designTools: DesignTools | null = null;
protected _settings: Promise;
- protected _shell: LabShell;
+ protected _labShell?: LabShell | null;
+ protected _dockPanel: DockPanel | null = null;
+ protected _shell: JupyterFrontEnd.IShell;
protected _statusbar: StatusBar | null;
protected _statusBarWasEnabled = false;
protected _styleCache = new Map();
@@ -59,13 +62,17 @@ export class DeckManager implements IDeckManager {
this._appStarted = options.appStarted;
this._commands = options.commands;
this._shell = options.shell;
+ this._labShell = options.labShell || null;
this._statusbar = options.statusbar;
this._trans = options.translator;
this._settings = options.settings;
this._fonts = options.fonts;
+ if (this._labShell) {
+ this._dockPanel = (this._shell as any)._dockPanel;
+ this._labShell.activeChanged.connect(this._onActiveWidgetChanged, this);
+ this._labShell.layoutModified.connect(this._addDeckStylesLater, this);
+ }
- this._shell.activeChanged.connect(this._onActiveWidgetChanged, this);
- this._shell.layoutModified.connect(this._addDeckStylesLater, this);
this._addCommands();
this._addKeyBindings();
this._settings
@@ -126,7 +133,7 @@ export class DeckManager implements IDeckManager {
/** enable deck mode */
public start = async (force: boolean = false): Promise => {
- await this._appStarted;
+ await this._appStarted();
const wasActive = this._active;
/* istanbul ignore if */
@@ -144,7 +151,7 @@ export class DeckManager implements IDeckManager {
}
}
- const { _shell, _activeWidget } = this;
+ const { _labShell, _shell, _activeWidget } = this;
if (!wasActive) {
this._active = true;
@@ -153,10 +160,18 @@ export class DeckManager implements IDeckManager {
this._statusBarWasEnabled = this._statusbar.isVisible;
this._statusbar.hide();
}
- _shell.presentationMode = false;
+ if (_labShell) {
+ _labShell.presentationMode = false;
+ }
document.body.dataset[DATA.deckMode] = DATA.presenting;
- each(this._dockpanel.tabBars(), (bar) => bar.hide());
- _shell.mode = 'single-document';
+ if (this._dockPanel) {
+ for (const bar of getTabBars(this._dockPanel)) {
+ bar.hide();
+ }
+ }
+ if (_labShell) {
+ _labShell.mode = 'single-document';
+ }
this._remote = new DeckRemote({ manager: this });
this._designTools = new DesignTools({ manager: this });
window.addEventListener('resize', this._addDeckStylesLater);
@@ -166,6 +181,8 @@ export class DeckManager implements IDeckManager {
await this._onActiveWidgetChanged();
if (_activeWidget) {
+ // TODO: hoist to an appropriate upstream
+ await (this._fonts as any)._stylist.ensureJss();
const presenter = this._getPresenter(_activeWidget);
if (presenter) {
this._activePresenter = presenter;
@@ -175,14 +192,16 @@ export class DeckManager implements IDeckManager {
}
}
- _shell.expandLeft();
- _shell.expandRight();
- _shell.collapseLeft();
- _shell.collapseRight();
- setTimeout(() => {
- _shell.collapseLeft();
- _shell.collapseRight();
- }, 1000);
+ if (_labShell) {
+ _labShell.expandLeft();
+ _labShell.expandRight();
+ _labShell.collapseLeft();
+ _labShell.collapseRight();
+ setTimeout(() => {
+ _labShell.collapseLeft();
+ _labShell.collapseRight();
+ }, 1000);
+ }
if (!wasActive) {
this._addDeckStyles();
@@ -201,7 +220,15 @@ export class DeckManager implements IDeckManager {
return;
}
- const { _activeWidget, _shell, _statusbar, _remote, _layover, _designTools } = this;
+ const {
+ _activeWidget,
+ _labShell,
+ _statusbar,
+ _remote,
+ _layover,
+ _designTools,
+ _dockPanel,
+ } = this;
/* istanbul ignore if */
if (_layover) {
@@ -219,7 +246,11 @@ export class DeckManager implements IDeckManager {
_statusbar.show();
}
- each(this._dockpanel.tabBars(), (bar) => bar.show());
+ if (_dockPanel) {
+ for (const bar of getTabBars(_dockPanel)) {
+ bar.show();
+ }
+ }
if (_remote) {
_remote.dispose();
@@ -229,14 +260,23 @@ export class DeckManager implements IDeckManager {
_designTools.dispose();
this._designTools = null;
}
- _shell.presentationMode = false;
- _shell.mode = 'multiple-document';
+ if (_labShell) {
+ _labShell.presentationMode = false;
+ _labShell.mode = 'multiple-document';
+ }
window.removeEventListener('resize', this._addDeckStylesLater);
delete document.body.dataset[DATA.deckMode];
this._activeWidget = null;
this._active = false;
this._activeWidgetStack = [];
- void this._settings.then((settings) => settings.set('active', false));
+ void this._settings.then(async (settings) => {
+ await settings.set('active', false);
+ this._shell.update();
+ const _main = (this._shell as any)._main;
+ if (_main && typeof _main.update == 'function') {
+ _main.update();
+ }
+ });
};
/** move around */
@@ -426,10 +466,6 @@ export class DeckManager implements IDeckManager {
}
}
- protected get _dockpanel(): DockPanel {
- return (this._shell as any)._dockPanel as DockPanel;
- }
-
/** handle the active widget changing */
protected async _onActiveWidgetChanged(): Promise {
if (!this._active) {
@@ -460,7 +496,7 @@ export class DeckManager implements IDeckManager {
if (this._activeWidgetStack.includes(_shellActiveWidget)) {
this._activeWidgetStack.splice(
this._activeWidgetStack.indexOf(_shellActiveWidget),
- 1
+ 1,
);
}
const presenter = this._getPresenter(_shellActiveWidget);
@@ -479,12 +515,15 @@ export class DeckManager implements IDeckManager {
}
protected get _shellActiveWidget(): Widget | null {
- if (this._shell.activeWidget) {
- return this._shell.activeWidget;
+ const { _labShell, _shell, _dockPanel } = this;
+ if (_labShell && _dockPanel) {
+ if (_labShell.activeWidget) {
+ return _labShell.activeWidget;
+ }
+ return getSelectedWidget(_dockPanel);
+ } else {
+ return (_shell as INotebookShell).currentWidget || null;
}
- const selected = this._dockpanel.selectedWidgets();
- const widget = selected.next();
- return widget || null;
}
protected async _onSettingsChanged() {
@@ -568,11 +607,12 @@ export class DeckManager implements IDeckManager {
export namespace DeckManager {
export interface IOptions {
commands: CommandRegistry;
- shell: LabShell;
+ labShell: ILabShell | null;
+ shell: JupyterFrontEnd.IShell;
translator: TranslationBundle;
statusbar: StatusBar | null;
settings: Promise;
- appStarted: Promise;
+ appStarted: () => Promise;
fonts: IFontManager;
}
export interface IExtent {
diff --git a/js/jupyterlab-deck/src/markdown/presenter.ts b/js/jupyterlab-deck/src/markdown/presenter.ts
index 282752e..6e153d6 100644
--- a/js/jupyterlab-deck/src/markdown/presenter.ts
+++ b/js/jupyterlab-deck/src/markdown/presenter.ts
@@ -56,7 +56,7 @@ export class SimpleMarkdownPresenter implements IPresenter {
public async go(
panel: MarkdownDocument,
direction: TDirection,
- alternate?: TDirection
+ alternate?: TDirection,
): Promise {
await panel.content.ready;
let index = this._activeSlide.get(panel) || 1;
diff --git a/js/jupyterlab-deck/src/notebook/extension.ts b/js/jupyterlab-deck/src/notebook/extension.ts
index 0c8fa8c..3406d5f 100644
--- a/js/jupyterlab-deck/src/notebook/extension.ts
+++ b/js/jupyterlab-deck/src/notebook/extension.ts
@@ -21,7 +21,7 @@ export class NotebookDeckExtension
createNew(
panel: NotebookPanel,
- context: DocumentRegistry.IContext
+ context: DocumentRegistry.IContext,
): IDisposable {
const button = new CommandToolbarButton({
commands: this._commands,
diff --git a/js/jupyterlab-deck/src/notebook/metadata.tsx b/js/jupyterlab-deck/src/notebook/metadata.tsx
index 1dcc4fe..32298a1 100644
--- a/js/jupyterlab-deck/src/notebook/metadata.tsx
+++ b/js/jupyterlab-deck/src/notebook/metadata.tsx
@@ -1,8 +1,14 @@
import { ISettings, Stylist } from '@deathbeds/jupyterlab-fonts';
+import {
+ deleteCellMetadata,
+ setCellMetadata,
+ getCellMetadata,
+ getPanelMetadata,
+} from '@deathbeds/jupyterlab-fonts/lib/labcompat';
import { VDomModel, VDomRenderer } from '@jupyterlab/apputils';
import { Cell, ICellModel } from '@jupyterlab/cells';
import { INotebookTools, NotebookTools } from '@jupyterlab/notebook';
-import { JSONExt, ReadonlyPartialJSONObject } from '@lumino/coreutils';
+import { JSONExt } from '@lumino/coreutils';
import { PanelLayout } from '@lumino/widgets';
import React from 'react';
@@ -133,7 +139,7 @@ export class DeckCellEditor extends VDomRenderer {
presetOptions.push(
+ ,
);
}
return presetOptions;
@@ -146,7 +152,7 @@ export class DeckCellEditor extends VDomRenderer {
layerOptions.push(
+ ,
);
}
return layerOptions;
@@ -163,8 +169,9 @@ export namespace DeckCellEditor {
update() {
this._activeMeta =
- (this._activeCell?.model.metadata.get(META.deck) as any as ICellDeckMetadata) ||
- JSONExt.emptyObject;
+ (this._activeCell?.model
+ ? (getCellMetadata(this._activeCell?.model, META.deck) as ICellDeckMetadata)
+ : null) || JSONExt.emptyObject;
this.stateChanged.emit(void 0);
}
@@ -190,7 +197,7 @@ export namespace DeckCellEditor {
return;
}
let meta = {
- ...((this._activeCell.model.metadata.get(META.fonts) ||
+ ...((getCellMetadata(this._activeCell.model, META.fonts) ||
JSONExt.emptyObject) as ISettings),
};
for (const preset of this._manager.stylePresets) {
@@ -216,8 +223,8 @@ export namespace DeckCellEditor {
for (let [key, value] of Object.entries(preset.styles)) {
presenting[key] = value;
}
- this._activeCell.model.metadata.delete(META.fonts);
- this._activeCell.model.metadata.set(META.fonts, meta as any);
+ deleteCellMetadata(this._activeCell.model, META.fonts);
+ setCellMetadata(this._activeCell.model, META.fonts, meta);
this.forceStyle();
this._notebookTools.update();
return;
@@ -230,7 +237,9 @@ export namespace DeckCellEditor {
return;
}
let stylist = (this._manager.fonts as any)._stylist as Stylist;
- let meta = panel.model?.metadata.get(META.fonts) || JSONExt.emptyObject;
+ let meta =
+ (panel.model ? getPanelMetadata(panel.model, META.fonts) : null) ||
+ JSONExt.emptyObject;
stylist.stylesheet(meta as ISettings, panel);
}
@@ -249,9 +258,9 @@ export namespace DeckCellEditor {
protected _setDeckMetadata(newMeta: ICellDeckMetadata, cell: Cell) {
if (Object.keys(newMeta).length) {
- cell.model.metadata.set(META.deck, newMeta as ReadonlyPartialJSONObject);
+ setCellMetadata(cell.model, META.deck, newMeta);
} else {
- cell.model.metadata.delete(META.deck);
+ deleteCellMetadata(cell.model, META.deck);
}
}
diff --git a/js/jupyterlab-deck/src/notebook/presenter.ts b/js/jupyterlab-deck/src/notebook/presenter.ts
index 2c92129..26db5ef 100644
--- a/js/jupyterlab-deck/src/notebook/presenter.ts
+++ b/js/jupyterlab-deck/src/notebook/presenter.ts
@@ -1,5 +1,11 @@
import { ISettings, Stylist } from '@deathbeds/jupyterlab-fonts';
import type { GlobalStyles } from '@deathbeds/jupyterlab-fonts/lib/_schema';
+import {
+ getCellMetadata,
+ getPanelMetadata,
+ setCellMetadata,
+ deleteCellMetadata,
+} from '@deathbeds/jupyterlab-fonts/lib/labcompat';
import { Cell, ICellModel } from '@jupyterlab/cells';
import {
INotebookModel,
@@ -7,13 +13,13 @@ import {
Notebook,
NotebookPanel,
} from '@jupyterlab/notebook';
-import { toArray } from '@lumino/algorithm';
import { CommandRegistry } from '@lumino/commands';
import { JSONExt } from '@lumino/coreutils';
import { ElementExt } from '@lumino/domutils';
import { ISignal, Signal } from '@lumino/signaling';
import { Widget } from '@lumino/widgets';
+import { getCellModels } from '../labcompat';
import {
DIRECTION,
IPresenter,
@@ -35,6 +41,7 @@ import type { Layover } from '../tools/layover';
import { NotebookMetaTools } from './metadata';
const emptyMap = Object.freeze(new Map());
+const { emptyObject, emptyArray } = JSONExt;
/** An presenter for working with notebooks */
export class NotebookPresenter implements IPresenter {
@@ -127,7 +134,8 @@ export class NotebookPresenter implements IPresenter {
public getSlideType(panel: NotebookPanel): TSlideType {
let { activeCell } = panel.content;
if (activeCell) {
- const meta = (activeCell.model.metadata.get(META.slideshow) || {}) as any;
+ const meta = (getCellMetadata(activeCell.model, META.slideshow) ||
+ emptyObject) as any;
return (meta[META.slideType] || null) as TSlideType;
}
return null;
@@ -140,12 +148,15 @@ export class NotebookPresenter implements IPresenter {
return;
}
let oldMeta =
- (activeCell.model.metadata.get(META.slideshow) as Record) || null;
+ ((getCellMetadata(activeCell.model, META.slideshow) || emptyObject) as Record<
+ string,
+ any
+ >) || null;
if (slideType == null) {
if (oldMeta == null) {
- activeCell.model.metadata.delete(META.slideshow);
+ deleteCellMetadata(activeCell.model, META.slideshow);
} else {
- activeCell.model.metadata.set(META.slideshow, {
+ setCellMetadata(activeCell.model, META.slideshow, {
...oldMeta,
[META.slideType]: slideType,
});
@@ -154,7 +165,7 @@ export class NotebookPresenter implements IPresenter {
if (oldMeta == null) {
oldMeta = {};
}
- activeCell.model.metadata.set(META.slideshow, {
+ setCellMetadata(activeCell.model, META.slideshow, {
...oldMeta,
[META.slideType]: slideType,
});
@@ -168,7 +179,7 @@ export class NotebookPresenter implements IPresenter {
public getLayerScope(panel: NotebookPanel): string | null {
let { activeCell } = panel.content;
if (activeCell) {
- const meta = (activeCell.model.metadata.get(META.deck) || {}) as any;
+ const meta = (getCellMetadata(activeCell.model, META.deck) || emptyObject) as any;
return (meta[META.layer] || null) as TLayerScope;
}
return null;
@@ -180,12 +191,15 @@ export class NotebookPresenter implements IPresenter {
return;
}
let oldMeta =
- (activeCell.model.metadata.get(META.deck) as Record) || null;
+ ((getCellMetadata(activeCell.model, META.deck) || emptyObject) as Record<
+ string,
+ any
+ >) || null;
if (layerScope == null) {
if (oldMeta == null) {
- activeCell.model.metadata.delete(META.layer);
+ deleteCellMetadata(activeCell.model, META.deck);
} else {
- activeCell.model.metadata.set(META.deck, {
+ setCellMetadata(activeCell.model, META.deck, {
...oldMeta,
[META.layer]: layerScope,
});
@@ -194,7 +208,7 @@ export class NotebookPresenter implements IPresenter {
if (oldMeta == null) {
oldMeta = {};
}
- activeCell.model.metadata.set(META.deck, {
+ setCellMetadata(activeCell.model, META.deck, {
...oldMeta,
[META.layer]: layerScope,
});
@@ -225,19 +239,18 @@ export class NotebookPresenter implements IPresenter {
public preparePanel(panel: NotebookPanel) {
let notebook = panel.content;
let oldSetFragment = notebook.setFragment;
- notebook.setFragment = (fragment: string): void => {
- oldSetFragment.call(notebook, fragment);
+ notebook.setFragment = async (fragment: string): Promise => {
+ await oldSetFragment.call(notebook, fragment);
if (this._manager.activePresenter === this) {
- void Promise.all(notebook.widgets.map((widget) => widget.ready)).then(() => {
- this._activateByAnchor(notebook, fragment);
- });
+ await Promise.all(notebook.widgets.map((widget) => widget.ready));
+ this._activateByAnchor(notebook, fragment);
}
};
}
protected _makeDeckTools(notebookTools: INotebookTools) {
const tool = new NotebookMetaTools({ manager: this._manager, notebookTools });
- notebookTools.addItem({ tool, section: 'common', rank: 3 });
+ notebookTools.addItem({ tool, section: 'commonToolsSection', rank: 3 });
}
protected _addWindowListeners() {
@@ -335,14 +348,14 @@ export class NotebookPresenter implements IPresenter {
back: back != null,
};
}
- return JSONExt.emptyObject;
+ return emptyObject;
}
/** move around */
public go = async (
panel: NotebookPanel,
direction: TDirection,
- alternate?: TDirection
+ alternate?: TDirection,
): Promise => {
const notebookModel = panel.content.model;
/* istanbul ignore if */
@@ -375,7 +388,11 @@ export class NotebookPresenter implements IPresenter {
} else {
console.warn(
EMOJI,
- this._manager.__(`Cannot go "%1" from cell %2`, direction, `${activeCellIndex}`)
+ this._manager.__(
+ `Cannot go "%1" from cell %2`,
+ direction,
+ `${activeCellIndex}`,
+ ),
);
}
};
@@ -406,7 +423,7 @@ export class NotebookPresenter implements IPresenter {
let { activeCellIndex } = notebook;
- let cell = notebookModel.cells.get(activeCellIndex);
+ let cell = getCellModels(notebookModel)[activeCellIndex];
let layerIndex: number | null = null;
@@ -465,7 +482,7 @@ export class NotebookPresenter implements IPresenter {
cell.removeClass(CSS.layer);
if (activeExtent.visible.includes(idx)) {
cell.addClass(CSS.visible);
- cell.editorWidget.update();
+ cell.editorWidget?.update();
} else {
cell.removeClass(CSS.visible);
}
@@ -482,7 +499,7 @@ export class NotebookPresenter implements IPresenter {
ElementExt.scrollIntoViewIfNeeded(
notebook.node,
- notebook.widgets[activeCellIndex].node
+ notebook.widgets[activeCellIndex].node,
);
this._activeChanged.emit(void 0);
if (this._manager.layover) {
@@ -496,7 +513,8 @@ export class NotebookPresenter implements IPresenter {
return;
}
let stylist = (this._manager.fonts as any)._stylist as Stylist;
- let meta = panel.model?.metadata.get(META.fonts) || JSONExt.emptyObject;
+ let meta =
+ (panel.model ? getPanelMetadata(panel.model, META.fonts) : null) || emptyObject;
stylist.stylesheet(meta as ISettings, panel);
this._manager.layover?.render();
}
@@ -512,16 +530,18 @@ export class NotebookPresenter implements IPresenter {
protected _getCellStyles(cell: Cell) {
try {
- const meta = cell.model.metadata.get(META.fonts) as any;
+ const meta = (getCellMetadata(cell.model, META.fonts) || emptyObject) as any;
const styles = meta.styles[META.nullSelector][META.presentingCell];
return styles;
} catch {
- return JSONExt.emptyObject;
+ return emptyObject;
}
}
protected _setCellStyles(cell: Cell, styles: GlobalStyles | null) {
- let meta = (cell.model.metadata.get(META.fonts) || {}) as ISettings;
+ let meta = {
+ ...(getCellMetadata(cell.model, META.fonts) || emptyObject),
+ } as ISettings;
if (!meta.styles) {
meta.styles = {};
}
@@ -529,23 +549,22 @@ export class NotebookPresenter implements IPresenter {
meta.styles[META.nullSelector] = {};
}
(meta.styles[META.nullSelector] as any)[META.presentingCell] = styles;
- cell.model.metadata.set(META.fonts, JSONExt.emptyObject as any);
- cell.model.metadata.set(META.fonts, { ...meta } as any);
+ setCellMetadata(cell.model, META.fonts, emptyObject);
+ setCellMetadata(cell.model, META.fonts, { ...meta });
this._forceStyle();
}
/** Get the nbconvert-compatible `slide_type` from metadata. */
protected _getSlideType(cell: ICellModel): TSlideType {
return (
- ((cell.metadata.get('slideshow') || JSONExt.emptyObject) as any)['slide_type'] ||
- null
+ (getCellMetadata(cell, META.slideshow) || emptyObject)[META.slideType] || null
);
}
protected _initExtent(
index: number,
slideType: TSlideType,
- extent: Partial = JSONExt.emptyObject
+ extent: Partial = emptyObject,
): NotebookPresenter.IExtent {
return {
parent: null,
@@ -564,7 +583,7 @@ export class NotebookPresenter implements IPresenter {
protected _lastOnScreenOf(
index: number,
- extents: NotebookPresenter.TExtentMap
+ extents: NotebookPresenter.TExtentMap,
): null | NotebookPresenter.IExtent {
let e = extents.get(index);
/* istanbul ignore if */
@@ -576,8 +595,8 @@ export class NotebookPresenter implements IPresenter {
/** Get layer metadata from `jupyterlab-deck` namespace */
protected _getCellDeckMetadata(cell: ICellModel): ICellDeckMetadata {
- return (cell.metadata.get(META.deck) ||
- JSONExt.emptyObject) as any as ICellDeckMetadata;
+ return (getCellMetadata(cell, META.deck) ||
+ emptyObject) as any as ICellDeckMetadata;
}
_numSort(a: number, b: number): number {
@@ -586,7 +605,7 @@ export class NotebookPresenter implements IPresenter {
protected _getLayers(
notebookModel: INotebookModel | null,
- extents: NotebookPresenter.TExtentMap
+ extents: NotebookPresenter.TExtentMap,
): NotebookPresenter.TLayerMap {
if (!notebookModel) {
return emptyMap;
@@ -636,7 +655,7 @@ export class NotebookPresenter implements IPresenter {
let start = -1;
let end = -1;
- for (const cell of toArray(notebookModel.cells)) {
+ for (const cell of getCellModels(notebookModel)) {
i++;
let { layer } = this._getCellDeckMetadata(cell);
if (!layer) {
@@ -715,7 +734,7 @@ export class NotebookPresenter implements IPresenter {
* - what are the notes
*/
protected _getExtents(
- notebookModel: INotebookModel | null
+ notebookModel: INotebookModel | null,
): NotebookPresenter.TExtentMap {
/* istanbul ignore if */
if (!notebookModel) {
@@ -735,7 +754,7 @@ export class NotebookPresenter implements IPresenter {
};
let index = -1;
- for (const cell of toArray(notebookModel.cells)) {
+ for (const cell of getCellModels(notebookModel)) {
index++;
let slideType = this._getSlideType(cell);
let { layer } = this._getCellDeckMetadata(cell);
@@ -785,10 +804,10 @@ export class NotebookPresenter implements IPresenter {
stacks.onScreen.unshift(extent);
stacks.nulls.unshift(extent);
extent.onScreen.unshift(
- ...(a0?.onScreen || /* istanbul ignore next */ JSONExt.emptyArray)
+ ...(a0?.onScreen || /* istanbul ignore next */ emptyArray),
);
extent.visible.unshift(
- ...(a0?.visible || /* istanbul ignore next */ JSONExt.emptyArray)
+ ...(a0?.visible || /* istanbul ignore next */ emptyArray),
);
break;
case 'slide':
@@ -864,8 +883,8 @@ export class NotebookPresenter implements IPresenter {
}
stacks.nulls = [];
stacks.onScreen.unshift(extent);
- extent.onScreen.unshift(...(a0?.onScreen || JSONExt.emptyArray));
- extent.visible.unshift(index, ...(a0?.visible || JSONExt.emptyArray));
+ extent.onScreen.unshift(...(a0?.onScreen || emptyArray));
+ extent.visible.unshift(index, ...(a0?.visible || emptyArray));
stacks.fragments.unshift(extent);
break;
case 'notes':
diff --git a/js/jupyterlab-deck/src/plugin.ts b/js/jupyterlab-deck/src/plugin.ts
index eee686a..2da0f87 100644
--- a/js/jupyterlab-deck/src/plugin.ts
+++ b/js/jupyterlab-deck/src/plugin.ts
@@ -21,21 +21,21 @@ import '../style/index.css';
const plugin: JupyterFrontEndPlugin = {
id: `${NS}:plugin`,
- requires: [ITranslator, ILabShell, ISettingRegistry, ILayoutRestorer, IFontManager],
- optional: [ICommandPalette, IStatusBar],
+ requires: [ITranslator, ISettingRegistry, IFontManager],
+ optional: [ILabShell, ILayoutRestorer, ICommandPalette, IStatusBar],
provides: IDeckManager,
autoStart: true,
activate: (
app: JupyterFrontEnd,
translator: ITranslator,
- shell: ILabShell,
settings: ISettingRegistry,
- restorer: ILayoutRestorer,
fonts: IFontManager,
+ labShell?: ILabShell,
+ restorer?: ILayoutRestorer,
palette?: ICommandPalette,
- statusbar?: IStatusBar
+ statusbar?: IStatusBar,
) => {
- const { commands } = app;
+ const { commands, shell } = app;
const theStatusBar =
statusbar instanceof StatusBar ? statusbar : /* istanbul ignore next */ null;
@@ -43,11 +43,13 @@ const plugin: JupyterFrontEndPlugin = {
const manager = new DeckManager({
commands,
shell,
+ labShell: labShell || null,
translator: (translator || /* istanbul ignore next */ nullTranslator).load(NS),
statusbar: theStatusBar,
fonts,
settings: settings.load(PLUGIN_ID),
- appStarted: Promise.all([app.started, restorer.restored]).then(() => void 0),
+ appStarted: async () =>
+ await Promise.all([app.started, ...(restorer ? [restorer.restored] : [])]),
});
const { __ } = manager;
@@ -72,7 +74,7 @@ const notebookPlugin: JupyterFrontEndPlugin = {
activate: (
app: JupyterFrontEnd,
notebookTools: INotebookTools,
- decks: IDeckManager
+ decks: IDeckManager,
) => {
const { commands } = app;
const presenter = new NotebookPresenter({
@@ -84,7 +86,7 @@ const notebookPlugin: JupyterFrontEndPlugin = {
app.docRegistry.addWidgetExtension(
'Notebook',
- new NotebookDeckExtension({ commands, presenter })
+ new NotebookDeckExtension({ commands, presenter }),
);
},
};
diff --git a/js/jupyterlab-deck/src/tokens.ts b/js/jupyterlab-deck/src/tokens.ts
index ca668ca..ca87902 100644
--- a/js/jupyterlab-deck/src/tokens.ts
+++ b/js/jupyterlab-deck/src/tokens.ts
@@ -229,12 +229,12 @@ export namespace META {
* nbconvert, notebook, and lab UI
**/
export const SLIDE_TYPES = ['slide', 'subslide', null, 'fragment', 'notes', 'skip'];
-export type TSlideType = typeof SLIDE_TYPES[number];
+export type TSlideType = (typeof SLIDE_TYPES)[number];
/** The scope of extents that will have this layer */
export const LAYER_SCOPES = ['deck', 'stack', 'slide', 'fragment'];
-export type TLayerScope = typeof LAYER_SCOPES[number];
+export type TLayerScope = (typeof LAYER_SCOPES)[number];
export type TSelectLabels = Record;
diff --git a/js/jupyterlab-deck/src/tools/design.tsx b/js/jupyterlab-deck/src/tools/design.tsx
index 5a8d2d5..d951147 100644
--- a/js/jupyterlab-deck/src/tools/design.tsx
+++ b/js/jupyterlab-deck/src/tools/design.tsx
@@ -47,7 +47,7 @@ export class DesignTools extends VDomRenderer {
this.makeButton(
ellipsesIcon,
__('Show Design Tools'),
- () => (model.showMore = true)
+ () => (model.showMore = true),
),
];
}
@@ -56,7 +56,7 @@ export class DesignTools extends VDomRenderer {
this.makeButton(
caretLeftIcon,
__('Hide Design Tools'),
- () => (model.showMore = false)
+ () => (model.showMore = false),
),
];
@@ -70,8 +70,8 @@ export class DesignTools extends VDomRenderer {
() => {
let { manager } = this.model;
manager.layover ? manager.hideLayover() : manager.showLayover();
- }
- )
+ },
+ ),
);
}
@@ -94,7 +94,7 @@ export class DesignTools extends VDomRenderer {
items.push(
+ ,
);
}
@@ -117,7 +117,7 @@ export class DesignTools extends VDomRenderer {
items.push(
+ ,
);
}
@@ -126,7 +126,7 @@ export class DesignTools extends VDomRenderer {
items.push(
this.makeSlider('z-index', currentPartStyles),
this.makeSlider('zoom', currentPartStyles),
- this.makeSlider('opacity', currentPartStyles)
+ this.makeSlider('opacity', currentPartStyles),
);
}
@@ -135,7 +135,7 @@ export class DesignTools extends VDomRenderer {
makeSlideTypeItem = (
slideType: TSlideType,
- currentSlideType: TSlideType
+ currentSlideType: TSlideType,
): JSX.Element => {
let { __ } = this.model.manager;
let slideTypeKey = slideType == null ? 'null' : slideType;
@@ -146,7 +146,7 @@ export class DesignTools extends VDomRenderer {
label,
() => this.model.manager.setSlideType(slideType),
'',
- []
+ [],
);
return (
@@ -157,7 +157,7 @@ export class DesignTools extends VDomRenderer {
makeLayerScopeItem = (
layerScope: TLayerScope | null,
- currentLayerScope: TLayerScope | null
+ currentLayerScope: TLayerScope | null,
): JSX.Element => {
let { __ } = this.model.manager;
let layerScopeKey = layerScope == null ? 'null' : layerScope;
@@ -168,7 +168,7 @@ export class DesignTools extends VDomRenderer {
label,
() => this.model.manager.setLayerScope(layerScope),
'',
- []
+ [],
);
return (
{button}
@@ -177,7 +177,7 @@ export class DesignTools extends VDomRenderer {
makeSlider = (
attr: DesignTools.TSliderAttr,
- styles: GlobalStyles | null
+ styles: GlobalStyles | null,
): JSX.Element => {
const config = DesignTools.SLIDER_CONFIG[attr];
const { suffix, className, icon } = config;
@@ -239,7 +239,7 @@ export class DesignTools extends VDomRenderer {
title: string,
onClick: () => void,
className: string = '',
- children: JSX.Element[] = []
+ children: JSX.Element[] = [],
) {
return (
-
+ ,
);
}
return ;
@@ -90,7 +90,7 @@ export class DeckRemote extends VDomRenderer {
icon: LabIcon,
title: string,
onClick: () => void,
- className: string = ''
+ className: string = '',
) {
return (