├── js ├── jupyterlab-deck │ ├── style │ │ ├── index.js │ │ ├── img │ │ │ ├── image-filter-hdr.svg │ │ │ ├── checkbox-blank-outline.svg │ │ │ ├── plus-box.svg │ │ │ ├── checkbox-intermediate-variant.svg │ │ │ ├── message-image-outline.svg │ │ │ ├── image-outline.svg │ │ │ ├── plus-box-multiple.svg │ │ │ ├── image-off-outline.svg │ │ │ ├── close-box-outline.svg │ │ │ ├── image-outline-multiple.svg │ │ │ ├── loupe.svg │ │ │ ├── transform.svg │ │ │ ├── order-numeric-descending.svg │ │ │ ├── square-opacity.svg │ │ │ ├── transform-stop.svg │ │ │ ├── chat-remove-outline.svg │ │ │ └── deck.svg │ │ ├── decktools.css │ │ ├── index.css │ │ ├── markdown.css │ │ ├── variables.css │ │ ├── edit.css │ │ ├── remote.css │ │ ├── notebook.css │ │ ├── shell.css │ │ ├── design.css │ │ └── layover.css │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ ├── typings.d.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.cov.json │ │ ├── labcompat.ts │ │ ├── markdown │ │ │ ├── extension.ts │ │ │ └── presenter.ts │ │ ├── notebook │ │ │ ├── extension.ts │ │ │ └── metadata.tsx │ │ ├── icons.ts │ │ ├── plugin.ts │ │ └── tools │ │ │ ├── remote.tsx │ │ │ └── layover.ts │ ├── tsconfig.json │ ├── webpack.config.js │ ├── LICENSE │ ├── package.json │ └── schema │ │ └── plugin.json ├── _meta │ ├── src │ │ ├── index.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ └── package.json ├── tsconfig.eslint.json └── tsconfigbase.json ├── .binder ├── apt.txt ├── postBuild └── environment.yml ├── src └── jupyterlab_deck │ ├── tests │ ├── __init__.py │ └── test_metadata.py │ ├── __init__.py │ └── _version.py ├── .github ├── requirements-build.txt ├── environment-robot.yml ├── environment-base.yml ├── environment-build.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── docs.md │ ├── release.md │ └── bug_report.md ├── environment-test-lab35.yml ├── environment-test.yml ├── pull_request_template.md ├── environment-lint.yml ├── environment-docs.yml └── workflows │ ├── pages.yml │ └── ci.yml ├── lerna.json ├── atest ├── fixtures │ ├── page_config.json │ ├── overrides.json │ └── jupyter_config.json ├── resources │ ├── Notebook.resource │ ├── Coverage.resource │ ├── Screenshots.resource │ ├── CodeMirror.resource │ ├── Sidebar.resource │ ├── Fixtures.resource │ ├── Docs.resource │ ├── DeckSelectors.resource │ ├── LabSelectors.resource │ ├── Server.resource │ ├── Design.resource │ ├── Lab.resource │ └── Deck.resource └── suites │ ├── lab │ ├── 00-smoke.robot │ ├── __init__.robot │ ├── 05-slide-layout.robot │ ├── 02-navigate.robot │ ├── 03-layers.robot │ ├── 04-design-tools.robot │ └── 01-examples.robot │ ├── nb │ ├── 00-smoke.robot │ ├── __init__.robot │ └── 01-examples.robot │ └── __init__.robot ├── examples ├── jupyter_lite_config.json ├── jupyter-lite.json ├── overrides.json ├── hello_world.py ├── deck.svg ├── README.md └── Layers.ipynb ├── docs ├── _static │ ├── theme.css │ ├── deck.svg │ └── anvil.svg ├── dictionary.txt ├── index.md └── conf.py ├── CODE_OF_CONDUCT.md ├── pages-lite ├── overrides.json ├── jupyter-lite.json ├── jupyter_lite_config.json └── README.ipynb ├── codecov.yml ├── .gitignore ├── .readthedocs.yml ├── .yarnrc.yml ├── CONTRIBUTING.md ├── _scripts └── labextension.py ├── LICENSE ├── CHANGELOG.md ├── pyproject.toml └── package.json /js/jupyterlab-deck/style/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.binder/apt.txt: -------------------------------------------------------------------------------- 1 | firefox 2 | chromium-browser 3 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/.npmignore: -------------------------------------------------------------------------------- 1 | *.tsbuildinfo 2 | src/ 3 | -------------------------------------------------------------------------------- /js/_meta/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@deathbeds/jupyterlab-deck'; 2 | -------------------------------------------------------------------------------- /src/jupyterlab_deck/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests of ``jupyterlab-deck``.""" 2 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tokens'; 2 | export * from './icons'; 3 | -------------------------------------------------------------------------------- /.github/requirements-build.txt: -------------------------------------------------------------------------------- 1 | doit[toml] 2 | flit 3 | jupyterlab >=4.0.7,<5 4 | pip 5 | ruff 6 | -------------------------------------------------------------------------------- /js/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": ["**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "7.0.0", 3 | "npmClient": "jlpm", 4 | "version": "independent" 5 | } 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const script: string; 3 | export default script; 4 | } 5 | -------------------------------------------------------------------------------- /atest/fixtures/page_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabledExtensions": { 3 | "@jupyterlab/apputils-extension:notification": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/jupyter_lite_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LiteBuildConfig": { 3 | "contents": ["."], 4 | "output_dir": "../build/lite" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/_static/theme.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | box-shadow: 2px 2px 5px var(--pst-color-text-muted); 3 | } 4 | 5 | .table { 6 | --bs-table-bg: transparent; 7 | } 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | > We adhere to the 4 | > [Jupyter Code Of Conduct](https://jupyter.org/governance/conduct/code_of_conduct.html) 5 | -------------------------------------------------------------------------------- /examples/jupyter-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter-lite-schema-version": 0, 3 | "jupyter-config-data": { 4 | "appName": "jupyterlab-deck", 5 | "exposeAppInBrowser": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyterlab-videochat:plugin": { 3 | "disablePublicRooms": false 4 | }, 5 | "@deathbeds/jupyterlab-deck:plugin": { 6 | "active": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pages-lite/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyterlab-videochat:plugin": { 3 | "disablePublicRooms": false 4 | }, 5 | "@deathbeds/jupyterlab-deck:plugin": { 6 | "active": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello_world.py: -------------------------------------------------------------------------------- 1 | """Just an example of a code file 2 | 3 | Click the link in the remote to go back! 4 | """ 5 | 6 | def hello_world(world="Jupyter") -> str: 7 | return f"Hello {world}" 8 | -------------------------------------------------------------------------------- /pages-lite/jupyter-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter-lite-schema-version": 0, 3 | "jupyter-config-data": { 4 | "appName": "jupyterlab-deck-reports", 5 | "exposeAppInBrowser": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | flag_management: 2 | default_rules: 3 | carryforward: true 4 | statuses: 5 | - type: project 6 | target: auto 7 | threshold: 1% 8 | - type: patch 9 | target: 90% 10 | -------------------------------------------------------------------------------- /js/_meta/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": ".root.tsbuildinfo" 6 | }, 7 | "extends": "../tsconfigbase", 8 | "files": ["package.json"] 9 | } 10 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": ".root.tsbuildinfo" 6 | }, 7 | "extends": "../tsconfigbase", 8 | "files": ["package.json"] 9 | } 10 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eux 3 | source activate ${NB_PYTHON_PREFIX} 4 | IN_BINDER=1 doit dev 5 | 6 | mkdir -p ${NB_PYTHON_PREFIX}/share/jupyter/lab/settings 7 | cp examples/overrides.json ${NB_PYTHON_PREFIX}/share/jupyter/lab/settings 8 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/image-filter-hdr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/environment-robot.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | ### environment-robot.yml ### 3 | - robotframework >=6.1 4 | - robotframework-pabot 5 | # browser 6 | - firefox 115.* 7 | - geckodriver 8 | - robotframework-jupyterlibrary >=0.5.0 9 | - lxml 10 | ### environment-robot.yml ### 11 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "outDir": "../lib", 5 | "tsBuildInfoFile": "../.src.tsbuildinfo" 6 | }, 7 | "extends": "../../tsconfigbase", 8 | "include": ["./**/*"], 9 | "references": [{ "path": "../" }] 10 | } 11 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/decktools.css: -------------------------------------------------------------------------------- 1 | .jp-Deck-SelectSplit { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .jp-Deck-SelectSplit .jp-select-wrapper { 7 | flex: 1; 8 | margin: 0; 9 | height: unset; 10 | } 11 | 12 | .jp-Deck-SelectSplit button { 13 | flex-shrink: 1; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | _d/ 3 | _output/ 4 | .binder/*.requirements.txt 5 | .cache/ 6 | .coverage 7 | .env 8 | .ipynb_checkpoints/ 9 | .pabotsuitenames 10 | .venv*/ 11 | .yarn-packages/ 12 | *.doit.* 13 | *.egg-info 14 | *.log 15 | *.tsbuildinfo 16 | build/ 17 | dist/ 18 | lib/ 19 | node_modules/ 20 | -------------------------------------------------------------------------------- /js/_meta/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "../lib", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": "../.src.tsbuildinfo" 6 | }, 7 | "extends": "../../tsconfigbase", 8 | "include": ["./**/*"], 9 | "references": [{ "path": "../../jupyterlab-deck/src" }] 10 | } 11 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/checkbox-blank-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/plus-box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/environment-base.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | ### environment-base.yml ### 3 | - doit-with-toml 4 | - ipywidgets >=7 5 | - jupyterlab >=3.5,<5.0.0a0 6 | - jupyterlab-fonts >=3.0.0 7 | - notebook >=6.5,<8.0.0a0 8 | - pip 9 | - python >=3.8,<3.13 10 | - python-dotenv 11 | ### environment-base.yml ### 12 | -------------------------------------------------------------------------------- /pages-lite/jupyter_lite_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LiteBuildConfig": { 3 | "contents": ["../build/reports", "."], 4 | "output_dir": "../build/pages-lite", 5 | "extra_ignore_contents": [ 6 | ".*/robot/.*/pabot_results", 7 | ".*/robot/.*/dry_run", 8 | ".*/robot/.*firefox.*" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/jupyterlab_deck/__init__.py: -------------------------------------------------------------------------------- 1 | """A lightweight presentation mode for JupyterLab.""" 2 | 3 | from ._version import __js__, __version__, __js_src__ 4 | 5 | __all__ = ["__version__", "_jupyter_labextension_paths"] 6 | 7 | 8 | def _jupyter_labextension_paths(): 9 | return [{"src": __js_src__, "dest": __js__["name"]}] 10 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/checkbox-intermediate-variant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "outDir": "../lib", 5 | "inlineSourceMap": true, 6 | "tsBuildInfoFile": "../.src.tsbuildinfo", 7 | "sourceMap": false 8 | }, 9 | "extends": "./tsconfig", 10 | "include": ["./**/*"], 11 | "references": [{ "path": "../" }] 12 | } 13 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/message-image-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/image-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/plus-box-multiple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/image-off-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./variables.css'); 2 | @import url('./shell.css'); 3 | 4 | /* tools */ 5 | @import url('./remote.css'); 6 | @import url('./decktools.css'); 7 | @import url('./layover.css'); 8 | @import url('./design.css'); 9 | 10 | /* docs */ 11 | @import url('./markdown.css'); 12 | @import url('./notebook.css'); 13 | @import url('./edit.css'); 14 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/close-box-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/image-outline-multiple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /atest/fixtures/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@jupyterlab/apputils-extension:notification": { 3 | "checkForUpdates": false, 4 | "doNotDisturbMode": true, 5 | "fetchNews": "false" 6 | }, 7 | "@jupyterlab/apputils-extension:palette": { 8 | "modal": false 9 | }, 10 | "@jupyterlab/extensionmanager-extension:plugin": { 11 | "enabled": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/loupe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/transform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-20.04 4 | tools: 5 | python: mambaforge-4.10 6 | jobs: 7 | pre_build: 8 | - doit setup 9 | - doit build 10 | - doit dist 11 | - doit dev 12 | - doit lite 13 | sphinx: 14 | builder: html 15 | configuration: docs/conf.py 16 | fail_on_warning: true 17 | conda: 18 | environment: .github/environment-docs.yml 19 | -------------------------------------------------------------------------------- /docs/dictionary.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | ci 3 | composable 4 | conda 5 | cov 6 | DOM 7 | IFrame 8 | JSON 9 | Jupyter 10 | jupyterlab 11 | JupyterLab 12 | JupyterLite 13 | Mambaforge 14 | MathJax 15 | MermaidJS 16 | npm 17 | PRs 18 | PyData 19 | pypi 20 | PyPI 21 | README 22 | Renderers 23 | rtd 24 | scrollable 25 | spacebar 26 | submodule 27 | subslide 28 | Subslide 29 | subtasks 30 | TBD 31 | themey 32 | UI 33 | un 34 | UX 35 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | 3 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '0').toLowerCase()); 4 | 5 | if (WITH_JS_COV) { 6 | console.error('Building with coverage'); 7 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 8 | } 9 | 10 | module.exports = { 11 | output: { clean: true }, 12 | devtool: 'source-map', 13 | module: { rules }, 14 | }; 15 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/order-numeric-descending.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/square-opacity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /atest/fixtures/jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LabApp": { 3 | "expose_app_in_browser": true, 4 | "log_level": "DEBUG", 5 | "open_browser": false 6 | }, 7 | "JupyterNotebookApp": { 8 | "expose_app_in_browser": true 9 | }, 10 | "ServerApp": { 11 | "tornado_settings": { 12 | "page_config_data": { 13 | "buildAvailable": false, 14 | "buildCheck": false, 15 | "exposeAppInBrowser": true 16 | } 17 | } 18 | }, 19 | "LanguageServerManager": { 20 | "autodetect": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Open in new tab 4 | 5 | 6 | 10 | 11 | ```{include} ../README.md 12 | 13 | ``` 14 | 15 | ```{include} ../CHANGELOG.md 16 | 17 | ``` 18 | 19 | ```{include} ../CONTRIBUTING.md 20 | 21 | ``` 22 | 23 | ## Open Source 24 | 25 | ```{include} ../LICENSE 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableInlineBuilds: false 2 | enableTelemetry: false 3 | httpTimeout: 60000 4 | nodeLinker: node-modules 5 | npmRegistryServer: https://registry.npmjs.org/ 6 | installStatePath: ./build/.cache/yarn/install-state.gz 7 | cacheFolder: ./build/.cache/yarn/cache 8 | logFilters: 9 | - code: YN0006 10 | level: discard 11 | - code: YN0002 12 | level: discard 13 | - code: YN0007 14 | level: discard 15 | - code: YN0013 16 | level: discard 17 | - code: YN0019 18 | level: discard 19 | - code: YN0008 20 | level: discard 21 | - code: YN0032 22 | level: discard 23 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/transform-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/jupyterlab_deck/tests/test_metadata.py: -------------------------------------------------------------------------------- 1 | """jupyterlab-deck metadata tests.""" 2 | import jupyterlab_deck 3 | 4 | 5 | def test_version() -> None: 6 | """Verify a version is present.""" 7 | assert jupyterlab_deck.__version__, "no version" 8 | 9 | 10 | def test_js() -> None: 11 | """Verify the js metadata present.""" 12 | assert jupyterlab_deck.__js__, "no js metadata" 13 | 14 | 15 | def test_magic_lab_extensions() -> None: 16 | """Verify the expected number of extensions are exposed.""" 17 | assert ( 18 | len(jupyterlab_deck._jupyter_labextension_paths()) == 1 19 | ), "too many/few labextensions" 20 | -------------------------------------------------------------------------------- /js/tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "strictNullChecks": true, 19 | "target": "es2019", 20 | "sourceMap": true, 21 | "types": [] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/environment-build.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-test 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | - python >=3.10,<3.13 9 | ### environment-base.yml ### 10 | - doit-with-toml 11 | - ipywidgets >=7 12 | - jupyterlab >=3.5,<5.0.0a0 13 | - jupyterlab-fonts >=3.0.0 14 | - notebook >=6.5,<8.0.0a0 15 | - pip 16 | - python >=3.8,<3.13 17 | - python-dotenv 18 | ### environment-base.yml ### 19 | ### environment-build.yml ### 20 | # runtimes 21 | - nodejs >=20,<21 22 | # host app 23 | - ipywidgets >=7 24 | # build 25 | - flit >=3.9.0,<4.0.0 26 | - twine 27 | ### environment-build.yml ### 28 | -------------------------------------------------------------------------------- /atest/resources/Notebook.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with the Notebook shell. 3 | 4 | Resource ./CodeMirror.resource 5 | Resource ./Server.resource 6 | Library JupyterLibrary 7 | 8 | 9 | *** Keywords *** 10 | Initialize Jupyter Notebook 11 | [Documentation] Get the web app set up for testing. 12 | ${executable_path} = Get GeckoDriver Executable Path 13 | Open Notebook executable_path=${executable_path} 14 | Initialize CodeMirror 15 | Set Window Size 1366 768 16 | Reload Page 17 | Wait Until Element Is Visible css:${JNB CSS TREE LIST} timeout=10s 18 | -------------------------------------------------------------------------------- /atest/suites/lab/00-smoke.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation JupyterLab is not broken. 3 | 4 | Library JupyterLibrary 5 | Resource ../../resources/Coverage.resource 6 | Resource ../../resources/Lab.resource 7 | Resource ../../resources/Screenshots.resource 8 | 9 | Suite Setup Set Attempt Screenshot Directory lab${/}smoke 10 | 11 | Force Tags suite:smoke 12 | 13 | 14 | *** Test Cases *** 15 | JupyterLab Opens 16 | [Documentation] JupyterLab opens. 17 | Capture Page Screenshot 00-smoke.png 18 | Plugins Should Be Disabled 19 | ... @jupyterlab/apputils-extension:notification 20 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/markdown.css: -------------------------------------------------------------------------------- 1 | .jp-Deck > .jp-MarkdownViewer { 2 | display: flex; 3 | flex-direction: column; 4 | position: fixed; 5 | left: var(--jp-private-sidebar-tab-width); 6 | right: var(--jp-private-sidebar-tab-width); 7 | place-content: center center; 8 | align-items: stretch; 9 | height: 100vh; 10 | overflow-y: hidden; 11 | } 12 | 13 | .jp-Deck > .jp-MarkdownViewer > .jp-RenderedMarkdown { 14 | flex-shrink: 0; 15 | gap: 0; 16 | overflow-y: auto; 17 | max-height: 100vh; 18 | margin: 0; 19 | padding: 0; 20 | background: transparent; 21 | } 22 | 23 | .jp-Deck > .jp-MarkdownViewer > .jp-RenderedMarkdown > * { 24 | display: none; 25 | } 26 | -------------------------------------------------------------------------------- /atest/suites/nb/00-smoke.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Jupyter Notebook is not broken. 3 | 4 | Library JupyterLibrary 5 | Resource ../../resources/Lab.resource 6 | Resource ../../resources/Coverage.resource 7 | Resource ../../resources/Screenshots.resource 8 | 9 | Suite Setup Set Attempt Screenshot Directory nb${/}smoke 10 | 11 | Force Tags suite:smoke 12 | 13 | 14 | *** Test Cases *** 15 | Jupyter Notebook Opens 16 | [Documentation] Jupyter Notebook opens. 17 | Capture Page Screenshot 00-smoke.png 18 | Plugins Should Be Disabled 19 | ... @jupyterlab/apputils-extension:notification 20 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/chat-remove-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/deck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/_static/deck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a Future Roadmap item 3 | about: Help us build future features 4 | labels: enhancement 5 | --- 6 | 7 | 13 | 14 | ## Elevator Pitch 15 | 16 | 17 | 18 | ## Motivation 19 | 20 | 21 | 22 | ## Design Ideas 23 | 24 | 25 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/img/deck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/labcompat.ts: -------------------------------------------------------------------------------- 1 | import type { ICellModel } from '@jupyterlab/cells'; 2 | import type { INotebookModel } from '@jupyterlab/notebook'; 3 | import { toArray } from '@lumino/algorithm'; 4 | import type { DockPanel, TabBar, Widget } from '@lumino/widgets'; 5 | 6 | export function getTabBars(dockPanel: DockPanel): TabBar[] { 7 | return toArray(dockPanel.tabBars()); 8 | } 9 | 10 | export function getCellModels(notebookModel: INotebookModel): ICellModel[] { 11 | return toArray(notebookModel.cells); 12 | } 13 | 14 | export function getSelectedWidget(dockPanel: DockPanel): Widget | null { 15 | const selectedWidgets = toArray(dockPanel.selectedWidgets()); 16 | return selectedWidgets.length ? selectedWidgets[0] : null; 17 | } 18 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/variables.css: -------------------------------------------------------------------------------- 1 | body[data-jp-deck-mode='presenting'] { 2 | --jp-cell-padding: 0; 3 | --jp-statusbar-height: 0; 4 | --jp-private-menubar-height: 0; 5 | 6 | /* z-stack */ 7 | --jp-deck-z-remote: 9999; 8 | --jp-deck-z-lab-chrome: 9998; 9 | --jp-deck-z-layover: 9997; 10 | 11 | /* layover */ 12 | --jp-deck-layover-color: var(--jp-warn-color1); 13 | --jp-deck-handle-size: var(--jp-content-font-size4); 14 | --jp-deck-chrome-visibility: visible; 15 | 16 | /* design */ 17 | --jp-deck-vslider-width: calc(1.2 * var(--jp-content-font-size4)); 18 | --jp-deck-checkbox-width: calc(1.1 * var(--jp-content-font-size4)); 19 | } 20 | 21 | body[data-jp-deck-mode='presenting'][data-jp-deck-layout-mode='designing'] { 22 | --jp-deck-chrome-visibility: hidden; 23 | } 24 | -------------------------------------------------------------------------------- /js/_meta/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@deathbeds/jupyterlab-deck-metapackage", 4 | "version": "0.0.0", 5 | "description": "JupyterLab Deck - Metapackage", 6 | "license": "BSD-3-Clause", 7 | "author": "jupyterlab-deck contributors", 8 | "homepage": "https://github.com/deathbeds/jupyterlab-deck", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/deathbeds/jupyterlab-deck.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/deathbeds/jupyterlab-deck/issues" 15 | }, 16 | "main": "lib/index.js", 17 | "scripts": { 18 | "build": "tsc -b src", 19 | "watch": "jlpm build -w --preserveWatchOutput" 20 | }, 21 | "types": "lib/index.d.ts", 22 | "dependencies": { 23 | "@deathbeds/jupyterlab-deck": "workspace:^" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /atest/suites/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Global test configuration. 3 | 4 | Library Process 5 | Library JupyterLibrary 6 | Resource ../resources/Screenshots.resource 7 | 8 | Suite Setup Set Up Root Suite 9 | Suite Teardown Tear Down Root Suite 10 | 11 | Force Tags attempt:${attempt} os:${os} py:${py} 12 | 13 | 14 | *** Keywords *** 15 | Set Up Root Suite 16 | [Documentation] Do global suite setup. 17 | Set Attempt Screenshot Directory ${EMPTY} 18 | Register Keyword To Run On Failure Capture Page Screenshot And Tag With Error 19 | 20 | Tear Down Root Suite 21 | [Documentation] Do global suite teardown. 22 | Close All Browsers 23 | Terminate All Jupyter Servers 24 | Terminate All Processes 25 | Empty Screenshot Trash 26 | -------------------------------------------------------------------------------- /src/jupyterlab_deck/_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | from pathlib import Path 4 | 5 | HERE = Path(__file__).parent 6 | _PKG_PATH = "share/jupyter/labextensions/@deathbeds/jupyterlab-deck/package.json" 7 | 8 | _D = HERE.parent / "_d" 9 | 10 | if _D.exists() and _D.parent.name == "src": # pragma: no cover 11 | __prefix__ = _D.parent 12 | __package_json__ = _D / _PKG_PATH 13 | __js_src__ = "../" + __package_json__.parent.relative_to(_D.parent).as_posix() 14 | else: # pragma: no cover 15 | __prefix__ = Path(sys.prefix) 16 | __package_json__ = __prefix__ / _PKG_PATH 17 | __js_src__ = __package_json__.parent.relative_to(__prefix__).as_posix() 18 | 19 | 20 | __js__ = json.loads(__package_json__.read_text(encoding="utf-8")) 21 | __version__ = __js__["version"].replace("-alpha.", "a").replace("-beta.", "b").replace("-rc.", "rc") 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation request 3 | about: Ask for clarification in documentation 4 | labels: documentation 5 | --- 6 | 7 | 13 | 14 | ## What I am trying to do... 15 | 16 | 17 | 18 | ## How I would like to learn how to do it... 19 | 20 | 21 | 22 | ## How the project might keep the docs accurate... 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/environment-test-lab35.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-test-35 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | # a more precise python pin is added in CI 9 | - jupyterlab >=3.5,<3.6.0a0 10 | - notebook <7.0.0a0 11 | ### environment-base.yml ### 12 | - doit-with-toml 13 | - ipywidgets >=7 14 | - jupyterlab >=3.5,<5.0.0a0 15 | - jupyterlab-fonts >=3.0.0 16 | - notebook >=6.5,<8.0.0a0 17 | - pip 18 | - python >=3.8,<3.13 19 | - python-dotenv 20 | ### environment-base.yml ### 21 | ### environment-test.yml ### 22 | # test 23 | - pytest-cov 24 | - pytest-html 25 | ### environment-test.yml ### 26 | ### environment-robot.yml ### 27 | - robotframework >=6.1 28 | - robotframework-pabot 29 | # browser 30 | - firefox 115.* 31 | - geckodriver 32 | - robotframework-jupyterlibrary >=0.5.0 33 | - lxml 34 | ### environment-robot.yml ### 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | ### Setup 4 | 5 | - Start with [Mambaforge](https://conda-forge.io/miniforge) 6 | 7 | ```bash 8 | mamba env update --prefix .venv --file .binder/environment.yml 9 | source activate ./.venv 10 | ``` 11 | 12 | ### `doit` 13 | 14 | The various build tasks are managed by [`doit`](https://pydoit.org). To get up to a 15 | ready-to-play JupyterLab: 16 | 17 | ```bash 18 | doit serve:lab 19 | ``` 20 | 21 | See other available tasks with: 22 | 23 | ```bash 24 | doit list 25 | ``` 26 | 27 | ### Legacy 28 | 29 | Support for JupyterLab 3 is verified with the `legacy` subtasks. 30 | 31 | Run all legacy tasks: 32 | 33 | ```bash 34 | doit legacy 35 | ``` 36 | 37 | Run an isolated JupyterLab 3 application: 38 | 39 | ```bash 40 | doit serve:lab:legacy 41 | ``` 42 | 43 | ### Releasing 44 | 45 | - Start a [release issue](https://github.com/jupyterlab-deck/issues) 46 | - Follow the checklist 47 | -------------------------------------------------------------------------------- /_scripts/labextension.py: -------------------------------------------------------------------------------- 1 | """A custom `jupyter-labextension` to enable the semi-weird directory layout.""" 2 | import importlib 3 | import sys 4 | from pathlib import Path 5 | 6 | from jupyterlab import federated_labextensions 7 | from jupyterlab.labextensions import LabExtensionApp 8 | 9 | HERE = Path(__file__).parent.resolve() 10 | ROOT = HERE.parent 11 | NODE_MODULES = ROOT / "node_modules" 12 | BUILDER = NODE_MODULES / "@jupyterlab/builder/lib/build-labextension.js" 13 | 14 | 15 | def _get_labextension_metadata(module): 16 | module = "jupyterlab_deck" 17 | m = importlib.import_module(module) 18 | return m, m._jupyter_labextension_paths() 19 | 20 | 21 | federated_labextensions._get_labextension_metadata = _get_labextension_metadata 22 | federated_labextensions._ensure_builder = lambda *_: str(BUILDER) 23 | 24 | main = LabExtensionApp.launch_instance 25 | 26 | if __name__ == "__main__": 27 | sys.exit(main()) 28 | -------------------------------------------------------------------------------- /atest/suites/nb/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Tests for Notebook. 3 | 4 | Library JupyterLibrary 5 | Resource ../../resources/Coverage.resource 6 | Resource ../../resources/Lab.resource 7 | Resource ../../resources/Notebook.resource 8 | Resource ../../resources/Server.resource 9 | Resource ../../resources/LabSelectors.resource 10 | 11 | Suite Setup Set Up Notebook Suite 12 | Suite Teardown Tear Down Notebook Suite 13 | 14 | Force Tags app:nb 15 | 16 | 17 | *** Keywords *** 18 | Set Up Notebook Suite 19 | [Documentation] Ensure a testable server is running 20 | Set Suite Variable ${JD_APP_UNDER_TEST} nb children=${TRUE} 21 | ${home_dir} = Initialize Fake Home 22 | Initialize Jupyter Server ${home_dir} 23 | Initialize Jupyter Notebook 24 | 25 | Tear Down Notebook Suite 26 | [Documentation] Do clean up stuff 27 | Maybe Accept A JupyterLab Prompt 28 | Capture Page Coverage 29 | -------------------------------------------------------------------------------- /.github/environment-test.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-test 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | # a more precise python pin is added in CI 9 | ### environment-base.yml ### 10 | - doit-with-toml 11 | - ipywidgets >=7 12 | - jupyterlab >=3.5,<5.0.0a0 13 | - jupyterlab-fonts >=3.0.0 14 | - notebook >=6.5,<8.0.0a0 15 | - pip 16 | - python >=3.8,<3.13 17 | - python-dotenv 18 | ### environment-base.yml ### 19 | ### environment-build.yml ### 20 | # runtimes 21 | - nodejs >=20,<21 22 | # host app 23 | - ipywidgets >=7 24 | # build 25 | - flit >=3.9.0,<4.0.0 26 | - twine 27 | ### environment-build.yml ### 28 | ### environment-test.yml ### 29 | # test 30 | - pytest-cov 31 | - pytest-html 32 | ### environment-test.yml ### 33 | ### environment-robot.yml ### 34 | - robotframework >=6.1 35 | - robotframework-pabot 36 | # browser 37 | - firefox 115.* 38 | - geckodriver 39 | - robotframework-jupyterlibrary >=0.5.0 40 | - lxml 41 | ### environment-robot.yml ### 42 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Checklist 10 | 11 | - [ ] ran `doit lint` locally 12 | 13 | ## References 14 | 15 | 16 | 17 | 18 | 19 | ## Code changes 20 | 21 | 22 | 23 | ## User-facing changes 24 | 25 | 26 | 27 | 28 | 29 | ## Backwards-incompatible changes 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- 4 | 5 | # `jupyterlab-deck` examples 6 | 7 | The contents of this folder are copied into the JupyterLite site. 8 | 9 | ``` 10 | If you got here from the main presentation, you can [go back](./README.ipynb#Multiple-Documents). 11 | ``` 12 | 13 | > This file is **also** a [basic deck](#Simple-Markdown-Decks) that can be viewed with 14 | > the _Start Deck_ command. 15 | 16 | --- 17 | 18 | ## Simple Markdown Decks 19 | 20 | - each `---` starts a new slide 21 | - including the first 22 | - not-yet-appearing 23 | - no subslides 24 | - no fragments 25 | - no document styles 26 | - no live editing: see [workaround](#Live-Updating-Workaround) 27 | 28 | --- 29 | 30 | ## Future Work 31 | 32 | - running code blocks 33 | - styling with front/back matter 34 | 35 | --- 36 | 37 | ## Live Updating Workaround 38 | 39 | - doesn't currently support this 40 | - well, sorta: right click and select _Show Markdown Editor_ 41 | - ctrl+shift+d 42 | 43 | > this may be improved in [future work](#Future-Work) 44 | -------------------------------------------------------------------------------- /atest/resources/Coverage.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with browser coverage data 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Library uuid 7 | 8 | 9 | *** Keywords *** 10 | Get Next Coverage File 11 | [Documentation] Get a random filename. 12 | ${uuid} = UUID1 13 | RETURN ${uuid.__str__()} 14 | 15 | Capture Page Coverage 16 | [Documentation] Fetch coverage data from the browser. 17 | [Arguments] ${name}=${EMPTY} 18 | IF not '''${name}''' 19 | ${name} = Get Next Coverage File 20 | END 21 | ${cov_json} = Execute Javascript 22 | ... return window.__coverage__ && JSON.stringify(window.__coverage__, null, 2) 23 | IF ${cov_json} 24 | Create File ${OUTPUT_DIR}${/}__coverage__${/}${name}.cov.json ${cov_json} 25 | END 26 | 27 | Reset JupyterLab And Close With Coverage 28 | [Documentation] Close JupyterLab after gathering coverage. 29 | Capture Page Coverage 30 | Reset JupyterLab And Close 31 | -------------------------------------------------------------------------------- /.github/environment-lint.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-lint 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | - python >=3.10,<3.13 9 | ### environment-base.yml ### 10 | - doit-with-toml 11 | - ipywidgets >=7 12 | - jupyterlab >=3.5,<5.0.0a0 13 | - jupyterlab-fonts >=3.0.0 14 | - notebook >=6.5,<8.0.0a0 15 | - pip 16 | - python >=3.8,<3.13 17 | - python-dotenv 18 | ### environment-base.yml ### 19 | ### environment-build.yml ### 20 | # runtimes 21 | - nodejs >=20,<21 22 | # host app 23 | - ipywidgets >=7 24 | # build 25 | - flit >=3.9.0,<4.0.0 26 | - twine 27 | ### environment-build.yml ### 28 | ### environment-lint.yml ### 29 | # formatters 30 | - ssort 31 | - ruff 32 | - robotframework-tidy >=3.3 33 | # linters 34 | - robotframework-robocop >=2.6 35 | ### environment-lint.yml ### 36 | ### environment-robot.yml ### 37 | - robotframework >=6.1 38 | - robotframework-pabot 39 | # browser 40 | - firefox 115.* 41 | - geckodriver 42 | - robotframework-jupyterlibrary >=0.5.0 43 | - lxml 44 | ### environment-robot.yml ### 45 | -------------------------------------------------------------------------------- /.github/environment-docs.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-docs 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | - python >=3.10,<3.13 9 | ### environment-base.yml ### 10 | - doit-with-toml 11 | - ipywidgets >=7 12 | - jupyterlab >=3.5,<5.0.0a0 13 | - jupyterlab-fonts >=3.0.0 14 | - notebook >=6.5,<8.0.0a0 15 | - pip 16 | - python >=3.8,<3.13 17 | - python-dotenv 18 | ### environment-base.yml ### 19 | ### environment-build.yml ### 20 | # runtimes 21 | - nodejs >=20,<21 22 | # host app 23 | - ipywidgets >=7 24 | # build 25 | - flit >=3.9.0,<4.0.0 26 | - twine 27 | ### environment-build.yml ### 28 | ### environment-docs.yml ### 29 | # docs 30 | - docutils >=0.18 31 | - mdit-py-plugins <0.4.0 32 | - myst-nb 33 | - pydata-sphinx-theme ==0.14.2 34 | - sphinx >=5.1,<6 35 | - sphinx-autobuild 36 | - sphinx-copybutton 37 | # check 38 | - hunspell 39 | - hunspell-en 40 | - pytest-check-links 41 | # lite 42 | - python-libarchive-c 43 | - jupyterlite-core ==0.2.1 44 | - jupyterlite-pyodide-kernel ==0.2.0 45 | ### environment-docs.yml ### 46 | -------------------------------------------------------------------------------- /docs/_static/anvil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /atest/suites/lab/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Tests for JupyterLab. 3 | 4 | Library JupyterLibrary 5 | Resource ../../resources/Coverage.resource 6 | Resource ../../resources/Lab.resource 7 | Resource ../../resources/Server.resource 8 | Resource ../../resources/LabSelectors.resource 9 | 10 | Suite Setup Set Up Lab Suite 11 | Suite Teardown Tear Down Lab Suite 12 | 13 | Force Tags app:lab 14 | 15 | 16 | *** Keywords *** 17 | Set Up Lab Suite 18 | [Documentation] Ensure a testable server is running 19 | Set Suite Variable ${JD_APP_UNDER_TEST} lab children=${TRUE} 20 | ${home_dir} = Initialize Fake Home 21 | Initialize Jupyter Server ${home_dir} 22 | Initialize JupyterLab 23 | 24 | Tear Down Lab Suite 25 | [Documentation] Do clean up stuff 26 | Maybe Accept A JupyterLab Prompt 27 | Maybe Open JupyterLab Sidebar File Browser 28 | Maybe Accept A JupyterLab Prompt 29 | Click Element css:${CSS_LAB_FILES_HOME} 30 | Execute JupyterLab Command Close All Tabs 31 | Execute JupyterLab Command Shut Down All Kernels 32 | Reset JupyterLab And Close With Coverage 33 | -------------------------------------------------------------------------------- /atest/suites/lab/05-slide-layout.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The slide layout overlay works. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Coverage.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Lab.resource 9 | Resource ../../resources/Screenshots.resource 10 | Resource ../../resources/Docs.resource 11 | Resource ../../resources/Design.resource 12 | 13 | Suite Setup Set Up Interactive Suite layout 14 | Suite Teardown Tear Down Interactive Suite 15 | Test Teardown Reset Design Tools Test 16 | 17 | Force Tags suite:design 18 | 19 | 20 | *** Test Cases *** 21 | Slide Layout 22 | [Documentation] Use the design tools to work with parts. 23 | [Tags] activity:notebook feature:layover 24 | Set Attempt Screenshot Directory lab${/}layout${/}layover 25 | Start Basic Notebook Deck 26 | Maybe Open Slide Layout 27 | Capture Page Screenshot 00-layover.png 28 | Move A Part 1 0 -100 01-moved.png 29 | FOR ${i} ${handle} IN ENUMERATE @{PART_HANDLES} 30 | Resize A Part 1 ${handle} 10 10 02-${i}-resized.png 31 | END 32 | Unfix A Part 1 03-unfixed.png 33 | Maybe Close Design Tools 34 | Capture Page Screenshot 04-presenting.png 35 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/markdown/extension.ts: -------------------------------------------------------------------------------- 1 | import { CommandToolbarButton } from '@jupyterlab/apputils'; 2 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { FileEditorPanel } from '@jupyterlab/fileeditor'; 4 | import { CommandRegistry } from '@lumino/commands'; 5 | import { IDisposable, DisposableDelegate } from '@lumino/disposable'; 6 | 7 | import { CommandIds, MARKDOWN_MIMETYPES } from '../tokens'; 8 | 9 | export class EditorDeckExtension 10 | implements 11 | DocumentRegistry.IWidgetExtension 12 | { 13 | private _commands: CommandRegistry; 14 | 15 | constructor(options: EditorDeckExtension.IOptions) { 16 | this._commands = options.commands; 17 | } 18 | 19 | createNew( 20 | panel: FileEditorPanel, 21 | context: DocumentRegistry.IContext, 22 | ): IDisposable { 23 | /* istanbul ignore if */ 24 | if (!MARKDOWN_MIMETYPES.includes(context.model.mimeType)) { 25 | return new DisposableDelegate(() => {}); 26 | } 27 | 28 | const button = new CommandToolbarButton({ 29 | commands: this._commands, 30 | label: '', 31 | id: CommandIds.toggle, 32 | }); 33 | 34 | panel.toolbar.insertItem(5, 'deck', button); 35 | 36 | return new DisposableDelegate(() => button.dispose()); 37 | } 38 | } 39 | 40 | export namespace EditorDeckExtension { 41 | export interface IOptions { 42 | commands: CommandRegistry; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/notebook/extension.ts: -------------------------------------------------------------------------------- 1 | import { CommandToolbarButton } from '@jupyterlab/apputils'; 2 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook'; 4 | import { CommandRegistry } from '@lumino/commands'; 5 | import { IDisposable, DisposableDelegate } from '@lumino/disposable'; 6 | 7 | import { CommandIds } from '../tokens'; 8 | 9 | import { NotebookPresenter } from './presenter'; 10 | 11 | export class NotebookDeckExtension 12 | implements DocumentRegistry.IWidgetExtension 13 | { 14 | private _commands: CommandRegistry; 15 | private _presenter: NotebookPresenter; 16 | 17 | constructor(options: DeckExtension.IOptions) { 18 | this._commands = options.commands; 19 | this._presenter = options.presenter; 20 | } 21 | 22 | createNew( 23 | panel: NotebookPanel, 24 | context: DocumentRegistry.IContext, 25 | ): IDisposable { 26 | const button = new CommandToolbarButton({ 27 | commands: this._commands, 28 | label: '', 29 | id: CommandIds.toggle, 30 | }); 31 | 32 | panel.toolbar.insertItem(5, 'deck', button); 33 | 34 | this._presenter.preparePanel(panel); 35 | 36 | return new DisposableDelegate(() => button.dispose()); 37 | } 38 | } 39 | 40 | export namespace DeckExtension { 41 | export interface IOptions { 42 | commands: CommandRegistry; 43 | presenter: NotebookPresenter; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /atest/resources/Screenshots.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with screenshots 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | 7 | 8 | *** Variables *** 9 | ${SCREENSHOT_TRASH} ${OUTPUT_DIR}${/}__trash__ 10 | ${OLD_SCREENSHOTS} ${OUTPUT_DIR}${/}screenshots 11 | 12 | 13 | *** Keywords *** 14 | Set Attempt Screenshot Directory 15 | [Documentation] Set a screenshot directory that includes the attempt 16 | [Arguments] ${path} 17 | Set Screenshot Directory 18 | ... ${OUTPUT_DIR}${/}screenshots${/}${OS.lower()[:2]}_${ATTEMPT}${/}${path} 19 | 20 | Send Error Screenshots To Trash 21 | [Documentation] Throw screenshots in the trash for a while. 22 | ${old_screens} = Set Screenshot Directory ${SCREENSHOT_TRASH} 23 | Set Global Variable ${OLD_SCREENSHOTS} ${old_screens} 24 | 25 | Resume Screenshots 26 | [Documentation] Restore Screenshots 27 | Set Screenshot Directory ${OLD_SCREENSHOTS} 28 | 29 | Empty Screenshot Trash 30 | [Documentation] Clean out trash. 31 | Run Keyword And Ignore Error 32 | ... Remove Directory ${SCREENSHOT_TRASH} ${TRUE} 33 | 34 | Capture Page Screenshot And Tag With Error 35 | [Documentation] Capture a screenshot if not going to the trash 36 | ${path} = Capture Page Screenshot 37 | IF "__trash__" not in "${path}" 38 | Run Keyword And Ignore Error Set Tags screenshot:unexpected 39 | END 40 | -------------------------------------------------------------------------------- /atest/resources/CodeMirror.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with CodeMirror 3 | 4 | Library JupyterLibrary 5 | 6 | 7 | *** Variables *** 8 | ${CM JS TO STRING} view.state.doc.toString() 9 | 10 | 11 | *** Keywords *** 12 | Initialize CodeMirror 13 | [Documentation] Fix apparently-broken CSS/JS variable updates. 14 | IF "${JD_APP_UNDER_TEST}" == "nb" 15 | Update Globals For JupyterLab 4 16 | ELSE 17 | Update Globals For JupyterLab Version 18 | END 19 | Set Suite Variable ${CM CSS EDITOR} ${CM CSS EDITOR} children=${TRUE} 20 | Set Suite Variable ${CM JS INSTANCE} ${CM JS INSTANCE} children=${TRUE} 21 | IF "${CM JS INSTANCE}" == "${CM6 JS INSTANCE}" 22 | Set Suite Variable ${CM JS TO STRING} view.state.doc.toString() children=${TRUE} 23 | ELSE 24 | Set Suite Variable ${CM JS TO STRING} getValue() children=${TRUE} 25 | END 26 | 27 | Return CodeMirror Method 28 | [Documentation] Construct and a method call against in the CodeMirror attached to the element 29 | ... that matches a ``css`` selector with the given ``js`` code. 30 | ... The CodeMirror editor instance will be available as `cm`. 31 | [Arguments] ${css} ${js} 32 | 33 | ${result} = Execute JavaScript 34 | ... return (() => { 35 | ... const cm = document.querySelector(`${css}`)${CM JS INSTANCE}; 36 | ... return cm.${js}; 37 | ... }).call(this); 38 | RETURN ${result} 39 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/edit.css: -------------------------------------------------------------------------------- 1 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #top-panel-wrapper, 2 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #spacer-widget-top, 3 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #spacer-widget-bottom { 4 | display: none; 5 | max-height: 0 !important; 6 | height: 0 !important; 7 | } 8 | 9 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #menu-panel-wrapper { 10 | top: 0 !important; 11 | opacity: 0; 12 | } 13 | 14 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #main-panel, 15 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #main-panel .jp-FileEditor { 16 | top: var(--jp-private-topbar-height) !important; 17 | bottom: 0 !important; 18 | height: unset !important; 19 | } 20 | 21 | body[data-notebook='edit'][data-jp-deck-mode='presenting'][data-notebook]:not( 22 | body[data-notebook='notebooks'] 23 | ) 24 | #main-panel { 25 | box-shadow: none; 26 | } 27 | 28 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] 29 | #main-panel 30 | > .jp-Document 31 | > .jp-Toolbar { 32 | opacity: 0; 33 | } 34 | 35 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] #menu-panel-wrapper:hover, 36 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] 37 | #main-panel 38 | > .jp-Document 39 | > .jp-Toolbar:hover { 40 | opacity: 0.75; 41 | transition: opacity 0.1s; 42 | } 43 | 44 | body[data-notebook='edit'][data-jp-deck-mode='presenting'] .jp-Editor { 45 | bottom: 0 !important; 46 | height: unset !important; 47 | } 48 | -------------------------------------------------------------------------------- /.binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-deck-demo 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | - python >=3.11,<3.12 9 | ### environment-base.yml ### 10 | - doit-with-toml 11 | - ipywidgets >=7 12 | - jupyterlab >=3.5,<5.0.0a0 13 | - jupyterlab-fonts >=3.0.0 14 | - notebook >=6.5,<8.0.0a0 15 | - pip 16 | - python >=3.8,<3.13 17 | - python-dotenv 18 | ### environment-base.yml ### 19 | ### environment-build.yml ### 20 | # runtimes 21 | - nodejs >=20,<21 22 | # host app 23 | - ipywidgets >=7 24 | # build 25 | - flit >=3.9.0,<4.0.0 26 | - twine 27 | ### environment-build.yml ### 28 | ### environment-lint.yml ### 29 | # formatters 30 | - ssort 31 | - ruff 32 | - robotframework-tidy >=3.3 33 | # linters 34 | - robotframework-robocop >=2.6 35 | ### environment-lint.yml ### 36 | ### environment-docs.yml ### 37 | # docs 38 | - docutils >=0.18 39 | - mdit-py-plugins <0.4.0 40 | - myst-nb 41 | - pydata-sphinx-theme ==0.14.2 42 | - sphinx >=5.1,<6 43 | - sphinx-autobuild 44 | - sphinx-copybutton 45 | # check 46 | - hunspell 47 | - hunspell-en 48 | - pytest-check-links 49 | # lite 50 | - python-libarchive-c 51 | - jupyterlite-core ==0.2.1 52 | - jupyterlite-pyodide-kernel ==0.2.0 53 | ### environment-docs.yml ### 54 | ### environment-test.yml ### 55 | # test 56 | - pytest-cov 57 | - pytest-html 58 | ### environment-test.yml ### 59 | ### environment-robot.yml ### 60 | - robotframework >=6.1 61 | - robotframework-pabot 62 | # browser 63 | - firefox 115.* 64 | - geckodriver 65 | - robotframework-jupyterlibrary >=0.5.0 66 | - lxml 67 | ### environment-robot.yml ### 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, jupyterlab-deck contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, jupyterlab-deck contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /atest/suites/lab/02-navigate.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Building a deck interactively is fun. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Coverage.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Lab.resource 9 | Resource ../../resources/Screenshots.resource 10 | Resource ../../resources/Docs.resource 11 | Resource ../../resources/Design.resource 12 | 13 | Suite Setup Set Up Interactive Suite navigate 14 | Suite Teardown Tear Down Interactive Suite 15 | Test Teardown Reset Interactive Test 16 | 17 | Force Tags suite:navigate activity:notebook 18 | 19 | 20 | *** Test Cases *** 21 | Build and Navigate a Notebook Slide With Keyboard 22 | [Documentation] Build and navigate a basic slide. 23 | Set Attempt Screenshot Directory lab${/}navigate${/}keyboard 24 | Start Basic Notebook Deck 25 | Really Back Up Deck With Keyboard s0-03-backup.png item1234 26 | Really Back Up Deck With Keyboard s0-04-backup.png World 27 | Really Advance Notebook Deck With Keyboard s0-05-advance.png item1234 28 | Really Advance Notebook Deck With Keyboard s0-06-advance.png item4567 29 | 30 | Build and Navigate a Notebook Slide With Anchors 31 | [Documentation] Build and navigate a basic slide. 32 | Set Attempt Screenshot Directory lab${/}navigate${/}anchors 33 | Start Notebook Deck With Anchors 34 | Click Element css:${CSS_DECK_VISIBLE} a[href^\="#Hello-World"] 35 | Wait Until Element Is Visible css:${CSS_DECK_VISIBLE} h1 36 | Capture Page Screenshot s1-02-back.png 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### `0.2.1` 4 | 5 | - > TBD 6 | 7 | ### `0.2.0` 8 | 9 | - [#56] addresses style and behavior differences on Notebook 7 and JupyterLab 4 10 | - [#36] adds support for Jupyter Notebook 7 and JupyterLab 4 11 | 12 | ### `0.2.0a1` 13 | 14 | - [#56] addresses style and behavior differences on Notebook 7 and JupyterLab 4 15 | 16 | [#56]: https://github.com/deathbeds/jupyterlab-deck/issues/56 17 | 18 | ### `0.2.0a0` 19 | 20 | - [#36] adds support for Jupyter Notebook 7 and JupyterLab 4 21 | 22 | [#36]: https://github.com/deathbeds/jupyterlab-deck/issues/36 23 | 24 | ### `0.1.3` 25 | 26 | - [#19] adds a basic Markdown presenter, with slides delimited by `---` 27 | - [#22] adds a stack of previously-viewed documents when navigating between documents 28 | - [#27] adds a drag-and-drop _slide layout_ overlay and design tools to support 29 | customization 30 | - [#29] adds support for using `#
` anchors while presenting 31 | 32 | [#19]: https://github.com/deathbeds/jupyterlab-deck/issues/19 33 | [#22]: https://github.com/deathbeds/jupyterlab-deck/issues/22 34 | [#27]: https://github.com/deathbeds/jupyterlab-deck/issues/27 35 | [#29]: https://github.com/deathbeds/jupyterlab-deck/issues/29 36 | 37 | ### `0.1.2` 38 | 39 | - [#17] adds foreground and background layers with customized per-cell styling 40 | 41 | [#17]: https://github.com/deathbeds/jupyterlab-deck/issues/15 42 | 43 | ### `0.1.1` 44 | 45 | #### Enhancements 46 | 47 | - improve keyboard navigation with (shift)space 48 | - the active cell is scrolled into view when exiting presentation mode 49 | 50 | #### Bug Fixes 51 | 52 | - fix some packaging metadata and documentation issues 53 | - fix handling of `null` cells 54 | 55 | ### `0.1.0` 56 | 57 | _initial release_ 58 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/remote.css: -------------------------------------------------------------------------------- 1 | .jp-Deck-Remote { 2 | position: absolute; 3 | right: 0; 4 | bottom: 0; 5 | display: flex; 6 | flex-direction: column; 7 | z-index: var(--jp-deck-z-remote); 8 | } 9 | 10 | .jp-Deck-Remote-directions { 11 | display: flex; 12 | flex: 1; 13 | flex-direction: column; 14 | align-items: center; 15 | } 16 | 17 | .jp-Deck-Remote label { 18 | color: var(--jp-ui-font-color1); 19 | } 20 | 21 | /* stack */ 22 | .jp-Deck-Remote .jp-Deck-Remote-WidgetStack { 23 | display: flex; 24 | flex-direction: column; 25 | align-items: end; 26 | list-style: none; 27 | } 28 | 29 | /* buttons */ 30 | .jp-Deck-Remote button { 31 | border: 0; 32 | background-color: transparent; 33 | flex: 1; 34 | } 35 | 36 | .jp-Deck-Remote .jp-Deck-Remote-WidgetStack li { 37 | list-style: none; 38 | } 39 | 40 | .jp-Deck-Remote .jp-Deck-Remote-WidgetStack button { 41 | display: flex; 42 | flex-direction: row; 43 | align-items: center; 44 | gap: 0.5em; 45 | } 46 | 47 | .jp-Deck-Remote button:not(.jp-mod-disabled) { 48 | opacity: 0.75; 49 | cursor: pointer; 50 | } 51 | 52 | .jp-Deck-Remote button:not(.jp-mod-disabled) label { 53 | cursor: pointer; 54 | } 55 | 56 | .jp-Deck-Remote button.jp-mod-disabled { 57 | opacity: 0.1; 58 | } 59 | 60 | .jp-Deck-Remote button:hover:not(.jp-mod-disabled) { 61 | opacity: 1; 62 | transition: opacity 0.1s; 63 | } 64 | 65 | .jp-Deck-Remote-directions > * { 66 | flex: 1; 67 | } 68 | 69 | .jp-Deck-Remote-directions > div { 70 | display: flex; 71 | flex-direction: row; 72 | } 73 | 74 | .jp-deck-mod-direction-forward { 75 | transform: rotate(0.25turn); 76 | } 77 | 78 | .jp-deck-mod-direction-down { 79 | transform: rotate(0.5turn); 80 | } 81 | 82 | .jp-deck-mod-direction-back { 83 | transform: rotate(0.75turn); 84 | } 85 | -------------------------------------------------------------------------------- /atest/resources/Sidebar.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with decks. 3 | 4 | Resource ./LabSelectors.resource 5 | Resource ./DeckSelectors.resource 6 | Resource ./Lab.resource 7 | 8 | 9 | *** Keywords *** 10 | Make Cell Layer With Sidebar 11 | [Documentation] Use the Property Inspector to make a cell a layer 12 | [Arguments] ${idx} ${layer} ${screenshot}=${EMPTY} 13 | Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${idx}) 14 | Maybe Open JupyterLab Sidebar Property Inspector 15 | Maybe Expand Panel With Title Advanced Tools 16 | Maybe Expand Panel With Title Common Tools 17 | Maybe Open Cell Metadata JSON 18 | Select From List By Value css:${CSS_DECK_LAYER_SELECT} ${layer} 19 | IF '${layer}' != '-' 20 | Wait Until Cell Metadata Contains "layer": "${layer}" 21 | ELSE 22 | Wait Until Cell Metadata Does Not Contain jupyterlab-deck 23 | END 24 | IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} 25 | 26 | Use Cell Style Preset 27 | [Documentation] Use the Property Inspector to use a cell preset 28 | [Arguments] ${idx} ${preset} ${expect}=${EMPTY} ${screenshot}=${EMPTY} 29 | Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${idx}) 30 | Maybe Open JupyterLab Sidebar Property Inspector 31 | Maybe Expand Panel With Title Advanced Tools 32 | Maybe Expand Panel With Title Common Tools 33 | Maybe Open Cell Metadata JSON 34 | Select From List By Value css:${CSS_DECK_PRESET_SELECT} ${preset} 35 | Click Element css:${CSS_DECK_TOOL_PRESET} button 36 | IF ${expect.__len__()} Wait Until Cell Metadata Contains ${expect} 37 | IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} 38 | -------------------------------------------------------------------------------- /atest/resources/Fixtures.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with fixtures 3 | 4 | Library JupyterLibrary 5 | Resource ./LabSelectors.resource 6 | Resource ./Coverage.resource 7 | 8 | 9 | *** Variables *** 10 | ${ROOT_EXAMPLES} ${ROOT}${/}examples 11 | ${LOGO_SVG} deck.svg 12 | ${HISTORY_IPYNB} History.ipynb 13 | ${LAYERS_IPYNB} Layers.ipynb 14 | ${README_IPYNB} README.ipynb 15 | ${README_MD} README.md 16 | @{ALL_EXAMPLES} 17 | ... ${ROOT_EXAMPLES}${/}${LOGO_SVG} 18 | ... ${ROOT_EXAMPLES}${/}${HISTORY_IPYNB} 19 | ... ${ROOT_EXAMPLES}${/}${LAYERS_IPYNB} 20 | ... ${ROOT_EXAMPLES}${/}${README_IPYNB} 21 | ... ${ROOT_EXAMPLES}${/}${README_MD} 22 | 23 | 24 | *** Keywords *** 25 | Copy Examples 26 | [Documentation] Get the examples from disk into jupyter 27 | ${nbdir} = Get Jupyter Directory 28 | Copy Files @{ALL_EXAMPLES} ${nbdir}${/}examples 29 | 30 | Clean Examples 31 | [Documentation] Clean out the examples 32 | ${nbdir} = Get Jupyter Directory 33 | Run Keyword And Ignore Error 34 | ... Remove Directory ${nbdir}${/}examples recursive=${TRUE} 35 | 36 | Open Example 37 | [Documentation] Open an example 38 | [Arguments] ${name}=README.ipynb ${switch_window}=${EMPTY} 39 | Maybe Open JupyterLab Sidebar File Browser 40 | ${selectors} = Create List 41 | ... ${CSS_LAB_FILES_HOME} 42 | ... ${CSS_LAB_FILES_DIR_ITEM}\[title^="Name: examples"] 43 | ... ${CSS_LAB_FILES_DIR_ITEM}\[title^="Name: ${name}"] 44 | FOR ${sel} IN @{selectors} 45 | Wait Until Element Is Visible css:${sel} 46 | Double Click Element css:${sel} 47 | END 48 | IF "${switch_window}" 49 | Sleep 5s 50 | Switch Window NEW 51 | END 52 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deathbeds/jupyterlab-deck", 3 | "version": "0.2.1", 4 | "description": "Lightweight presentations for JupyterLab", 5 | "license": "BSD-3-Clause", 6 | "author": "jupyterlab-deck contributors", 7 | "homepage": "https://github.com/deathbeds/jupyterlab-deck", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/deathbeds/jupyterlab-deck.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/deathbeds/jupyterlab-deck/issues" 14 | }, 15 | "main": "lib/index.js", 16 | "scripts": { 17 | "labextension": "python ../../_scripts/labextension.py", 18 | "labextension:build": "jlpm labextension build --debug .", 19 | "labextension:build:cov": "tsc -b src/tsconfig.cov.json && jlpm labextension:build", 20 | "watch": "jlpm labextension watch ." 21 | }, 22 | "types": "lib/index.d.ts", 23 | "dependencies": { 24 | "@jupyterlab/application": "3 || 4", 25 | "@jupyterlab/apputils": "3 || 4", 26 | "@jupyterlab/fileeditor": "3 || 4", 27 | "@jupyterlab/markdownviewer": "3 || 4", 28 | "@jupyterlab/notebook": "3 || 4", 29 | "@jupyterlab/statusbar": "3 || 4", 30 | "@jupyterlab/ui-components": "3 || 4", 31 | "d3-drag": "3" 32 | }, 33 | "devDependencies": { 34 | "@deathbeds/jupyterlab-fonts": "^3.0.0", 35 | "@jupyter-notebook/application": "^7.0.5", 36 | "@jupyterlab/builder": "^4.0.7", 37 | "@types/d3-drag": "3" 38 | }, 39 | "jupyterlab": { 40 | "extension": "lib/plugin.js", 41 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-deck", 42 | "schemaDir": "schema", 43 | "webpackConfig": "./webpack.config.js", 44 | "sharedPackages": { 45 | "@deathbeds/jupyterlab-fonts": { 46 | "bundled": false, 47 | "singleton": true 48 | }, 49 | "d3-drag": { 50 | "bundled": true 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /atest/resources/Docs.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for interactive tests 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ./Coverage.resource 7 | Resource ./Deck.resource 8 | Resource ./Lab.resource 9 | Resource ./Screenshots.resource 10 | 11 | 12 | *** Keywords *** 13 | Start Empty Notebook Deck 14 | [Documentation] Start an empty deck 15 | Launch A New JupyterLab Document 16 | Wait Until JupyterLab Kernel Is Idle 17 | Really Start Deck With Toolbar Button 18 | 19 | Start Basic Notebook Deck 20 | [Documentation] Make a few cells 21 | Execute JupyterLab Command Close All Tabs 22 | Start Empty Notebook Deck 23 | Click Element css:${JLAB CSS ACTIVE CELL} 24 | Make Markdown Cell \# Hello World Hello World new=${FALSE} screenshot=s0-00-hello.png 25 | Make Markdown Cell - item1234 item1234 screenshot=s0-01-1234.png 26 | Make Markdown Cell - item4567 item4567 screenshot=s0-02-4567.png 27 | 28 | Start Notebook Deck With Anchors 29 | [Documentation] Make a few cells with anchors. 30 | Start Basic Notebook Deck 31 | Make Markdown Cell back to [Hello World](#Hello-World) Hello World 32 | Select A Slide Type 4 subslide s1-01-new-slide.png 33 | 34 | Tear Down Interactive Suite 35 | [Documentation] Clean up after this suite. 36 | Execute JupyterLab Command Close All Tabs 37 | 38 | Reset Interactive Test 39 | [Documentation] Clean up after each test. 40 | Maybe Open JupyterLab Sidebar Commands 41 | Execute JupyterLab Command Save Notebook 42 | ${nbdir} = Get Jupyter Directory 43 | Remove File Untitled.ipynb 44 | Execute JupyterLab Command Close All Tabs 45 | 46 | Set Up Interactive Suite 47 | [Documentation] Prepare for this suite. 48 | [Arguments] ${screens} 49 | Set Attempt Screenshot Directory lab${/}${screens} 50 | Initialize CodeMirror 51 | -------------------------------------------------------------------------------- /atest/suites/lab/03-layers.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Building a deck interactively is fun. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Coverage.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Lab.resource 9 | Resource ../../resources/Screenshots.resource 10 | Resource ../../resources/Docs.resource 11 | Resource ../../resources/Sidebar.resource 12 | 13 | Suite Setup Set Up Interactive Suite layers 14 | Suite Teardown Tear Down Interactive Suite 15 | Test Teardown Reset Interactive Test 16 | 17 | Force Tags suite:layers feature:layers activity:notebook 18 | 19 | 20 | *** Variables *** 21 | ${FADE_JSON} "opacity": "0.125" 22 | 23 | 24 | *** Test Cases *** 25 | Build A Slide With Layers 26 | [Documentation] Use the sidebar to work with layers. 27 | Set Attempt Screenshot Directory lab${/}interactive${/}layers 28 | Start Basic Notebook Deck 29 | Make Markdown Cell - item91011 item91011 screenshot=s0-03-91011.png 30 | Make Markdown Cell - item121314 item121314 screenshot=s0-04-121314.png 31 | Make Cell Layer With Sidebar 2 deck s0-05-deck.png 32 | Make Cell Layer With Sidebar 3 stack s0-06-stack.png 33 | Make Cell Layer With Sidebar 4 slide s0-07-slide.png 34 | Make Cell Layer With Sidebar 4 - s0-08-null.png 35 | Make Cell Layer With Sidebar 5 fragment s0-09-fragment.png 36 | 37 | Build A Slide With Style Presets 38 | [Documentation] Use the sidebar to work with presets. 39 | Set Attempt Screenshot Directory lab${/}interactive${/}presets 40 | Start Basic Notebook Deck 41 | Use Cell Style Preset 2 fade-out ${FADE_JSON} s0-03-fragment-fade.png 42 | Make Cell Layer With Sidebar 2 deck s0-04-deck-layer.png 43 | Capture Page Screenshot s0-05-deck.png 44 | [Teardown] Stop Deck With Remote 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | about: Prepare for a release 4 | labels: maintenance 5 | --- 6 | 7 | - [ ] merge all outstanding PRs [![ms-badge]](ms) 8 | 9 | [ms-badge]: 10 | https://img.shields.io/github/milestones/progress/deathbeds/jupyterlab-deck/1234 11 | 12 | [ms]: 13 | https://github.com/deathbeds/jupyterlab-deck/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.2.3.4 14 | - [ ] _blocking #PR here_ 15 | - [ ] ensure `CHANGELOG.md` is up-to-date 16 | - [ ] ensure the versions have been bumped 17 | - [ ] validate on ReadTheDocs 18 | - [ ] _URL of build_ 19 | - [ ] wait for a successful build of `main` 20 | - [ ] _URL of build_ 21 | - [ ] download the `dist` archive and unpack somewhere 22 | - [ ] create a new release through the GitHub UI 23 | - [ ] paste in the relevant `CHANGELOG.md` entries 24 | - [ ] upload the artifacts 25 | - [ ] upload distribution to package repositories 26 | ```bash 27 | #!/usr/bin/env bash 28 | set -eux 29 | ls jupyterlab_deck.*.tar.gz jupyterlab_deck.*.whl deathbeds-jupyterlab-deck*.tgz 30 | twine upload jupyterlab_deck.*.tar.gz jupyterlab_deck.*.whl 31 | npm login 32 | for tarball in deathbeds-jupyterlab-deck*.tgz; do 33 | npm publish $tarball 34 | done 35 | npm logout 36 | ``` 37 | - [ ] _URL on npmjs.org here_ 38 | - [ ] _URL on pypi here_ 39 | - [ ] postmortem 40 | - [ ] handle `conda-forge` [feedstock] tasks 41 | - [ ] _URL on `conda-forge/jupyterlab-deck-feedstock` here_ 42 | - [ ] _URL on `anaconda.org`_ 43 | - [ ] validate on binder via simplest-possible gists (if viable) 44 | - [ ] create postmortem PR _PR# here_ 45 | - [ ] bump to next development version 46 | - [ ] bump the `CACHE_EPOCH` 47 | - [ ] rebuild nodejs locks 48 | - [ ] `rm yarn.lock` 49 | - [ ] `doit lint || doit lint` 50 | - [ ] commit the new locks 51 | - [ ] update release procedures with lessons learned in 52 | `.github/ISSUE_TEMPLATE/release.md` 53 | 54 | [feedstock]: https://github.com/conda-forge/jupyterlab-deck-feedstock 55 | [release]: https://github.com/deathbeds/jupyterlab-deck/releases 56 | -------------------------------------------------------------------------------- /atest/resources/DeckSelectors.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Selectors defined in this repo... could be loaded from JSON? 3 | 4 | Resource ./LabSelectors.resource 5 | 6 | 7 | *** Variables *** 8 | # body 9 | ${CSS_DECK_PRESENTING} [data-jp-deck-mode="presenting"] 10 | 11 | # deck 12 | ${CSS_DECK} .jp-Deck 13 | 14 | # remote 15 | ${CSS_DECK_REMOTE} .jp-Deck-Remote 16 | ${CSS_DECK_STOP} ${CSS_DECK_REMOTE} .jp-deck-mod-stop 17 | ${CSS_DECK_FORWARD} ${CSS_DECK_REMOTE} .jp-deck-mod-direction-forward 18 | ${CSS_DECK_BACK} ${CSS_DECK_REMOTE} .jp-deck-mod-direction-back 19 | ${CSS_DECK_UP} ${CSS_DECK_REMOTE} .jp-deck-mod-direction-up 20 | ${CSS_DECK_DOWN} ${CSS_DECK_REMOTE} .jp-deck-mod-direction-down 21 | ${CSS_DECK_DIR_STEM} ${CSS_DECK_REMOTE} .jp-deck-mod-direction 22 | ${CSS_DECK_DIR_STACK} ${CSS_DECK_REMOTE} .jp-Deck-Remote-WidgetStack 23 | 24 | @{CSS_DECK_NEXT} down forward 25 | @{CSS_DECK_PREV} up back 26 | 27 | # notebook 28 | ${CSS_DECK_TOOLBAR_BUTTON} ${CSS_LAB_DOC_TOOLBAR_BTN}\[data-command="deck:toggle"] 29 | ${CSS_DECK_VISIBLE} .jp-deck-mod-visible 30 | ${CSS_DECK_ONSCREEN} .jp-deck-mod-onscreen 31 | 32 | # metadata 33 | ${CSS_DECK_LAYER_SELECT} \#id-jp-decktools-select-layer 34 | ${CSS_DECK_PRESET_SELECT} \#id-jp-decktools-select-preset 35 | ${CSS_DECK_TOOL_PRESET} .jp-Deck-Tool-preset 36 | 37 | # design 38 | ${CSS_DECK_DESIGN_TOOLS} .jp-Deck-DesignTools 39 | ${CSS_DECK_ICON_LAYOVER_START} [data-icon="deck:layover-start"] 40 | ${CSS_DECK_ICON_LAYOVER_STOP} [data-icon="deck:layover-stop"] 41 | ${CSS_DECK_LAYOVER_PART} .jp-Deck-LayoverPart 42 | ${CSS_DECK_LAYOVER_UNSTYLE} .jp-Deck-LayoverUnstyle 43 | ${CSS_DECK_PART_HANDLE} .jp-Deck-LayoverHandle 44 | ${CSS_DECK_MOD_SLIDE_TYPE} .jp-deck-mod-slidetype 45 | ${CSS_DECK_MOD_LAYER_SCOPE} .jp-deck-mod-layerscope 46 | ${CSS_DECK_ICON_SLIDE_FMT} [data-icon="deck:slide-{}"] 47 | ${CSS_DECK_ICON_LAYER_FMT} [data-icon="deck:layer-{}"] 48 | ${CSS_DECK_SLIDER} .jp-Deck-DesignTools-Slider 49 | ${CSS_DECK_SLIDER_FMT} ${CSS_DECK_SLIDER}.jp-deck-mod-{} 50 | -------------------------------------------------------------------------------- /atest/suites/lab/04-design-tools.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The design tools work. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Coverage.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Lab.resource 9 | Resource ../../resources/Screenshots.resource 10 | Resource ../../resources/Docs.resource 11 | Resource ../../resources/Design.resource 12 | 13 | Suite Setup Set Up Interactive Suite design 14 | Suite Teardown Tear Down Interactive Suite 15 | Test Teardown Reset Design Tools Test 16 | 17 | Force Tags suite:design activity:notebook 18 | 19 | 20 | *** Test Cases *** 21 | Slide Types 22 | [Documentation] Use the slide type tool to work with parts. 23 | [Tags] feature:slidetype 24 | Set Attempt Screenshot Directory lab${/}design${/}slide-types 25 | Start Basic Notebook Deck 26 | Maybe Open Design Tools 27 | FOR ${i} ${type} IN ENUMERATE @{SLIDE_TYPES} 28 | Select A Slide Type 2 ${type} 01-${i}-${type}.png 29 | END 30 | Capture Page Screenshot 02-presenting.png 31 | 32 | Layer Scopes 33 | [Documentation] Use the layer scope tool to work with parts. 34 | [Tags] feature:layers 35 | Set Attempt Screenshot Directory lab${/}design${/}layer-scopes 36 | Start Basic Notebook Deck 37 | Make Markdown Cell - itemA itemA 38 | Make Markdown Cell - itemB itemB 39 | Make Markdown Cell - itemC itemC 40 | Make Markdown Cell - itemD itemD 41 | Maybe Open Design Tools 42 | FOR ${i} ${scope} IN ENUMERATE @{LAYER_SCOPES} 43 | Select A Layer Scope ${i + 2} ${scope} 01-${i}-${scope}.png 44 | END 45 | Capture Page Screenshot 02-presenting.png 46 | 47 | Sliders 48 | [Documentation] Use the slider tools to work with parts. 49 | [Tags] feature:slidestyle 50 | Set Attempt Screenshot Directory lab${/}design${/}sliders 51 | Start Basic Notebook Deck 52 | Maybe Open Design Tools 53 | FOR ${i} ${slider} IN ENUMERATE @{SLIDERS} 54 | Configure A Style With Slider 1 ${slider} 01-${i}-0-${slider}.png 55 | Unconfigure A Style With Slider 1 ${slider} 01-${i}-1-no-${slider}.png 56 | END 57 | Capture Page Screenshot 02-presenting.png 58 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | env: 12 | PYTHONUNBUFFERED: '1' 13 | PIP_DISABLE_PIP_VERSION_CHECK: '1' 14 | CI: '1' 15 | 16 | # our stuff 17 | ROBOT_RETRIES: '3' 18 | CACHE_EPOCH: '6' 19 | PABOT_PROCESSES: '3' 20 | 21 | jobs: 22 | build: 23 | runs-on: ${{ matrix.os }}-latest 24 | strategy: 25 | matrix: 26 | os: [ubuntu] 27 | python-version: ['3.10'] 28 | env: 29 | WITH_JS_COV: 1 30 | defaults: 31 | run: 32 | shell: bash -l {0} 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: cache (conda) 38 | uses: actions/cache@v3 39 | with: 40 | path: ~/conda_pkgs_dir 41 | key: | 42 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-demo-${{ matrix.python-version }}-${{ hashFiles('.binder/environment.yml') }} 43 | restore-keys: | 44 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-demo-${{ matrix.python-version }}- 45 | 46 | - name: Cache (node_modules) 47 | uses: actions/cache@v3 48 | id: cache-node-modules 49 | with: 50 | path: node_modules/ 51 | key: | 52 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }} 53 | 54 | - name: install (conda) 55 | uses: conda-incubator/setup-miniconda@v3 56 | with: 57 | environment-file: .binder/environment.yml 58 | miniforge-variant: Mambaforge 59 | use-mamba: true 60 | 61 | - name: lint 62 | run: doit lint 63 | 64 | - name: dev 65 | run: doit dev 66 | 67 | - name: test latest (pytest) 68 | run: doit test:pytest 69 | 70 | - name: test latest (robot with cov) 71 | run: doit test:robot 72 | 73 | - name: test legacy (pytest) 74 | run: doit legacy:pytest 75 | 76 | - name: test legacy (robot with cov) 77 | run: doit legacy:robot 78 | 79 | - name: rport 80 | run: doit report 81 | 82 | - name: site 83 | run: doit site 84 | 85 | - uses: actions/upload-pages-artifact@v1 86 | with: 87 | path: build/pages-lite 88 | 89 | deploy: 90 | needs: build 91 | runs-on: ubuntu-latest 92 | permissions: 93 | pages: write 94 | id-token: write 95 | environment: 96 | name: github-pages 97 | url: ${{ steps.deployment.outputs.page_url }} 98 | steps: 99 | - id: deployment 100 | uses: actions/deploy-pages@v1 101 | -------------------------------------------------------------------------------- /atest/suites/nb/01-examples.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The examples work in Notebook. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Fixtures.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Screenshots.resource 9 | 10 | Suite Setup Set Up Example Suite 11 | Suite Teardown Clean Examples 12 | 13 | Force Tags suite:examples 14 | 15 | 16 | *** Test Cases *** 17 | The README Markdown Can Be Navigated 18 | [Documentation] All slides and fragments are reachable. 19 | [Tags] activity:markdown 20 | Visit All Example Slides And Fragments ${README_MD} 21 | Execute JupyterLab Command Start Deck 22 | Wait Until Element Is Visible css:${CSS_DECK} 23 | ${anchors} = Get WebElements css:${CSS_LAB_MARKDOWN_VIEWER} a[href\^="#"] 24 | ${anchors} = Filter Visible Elements ${anchors} 25 | Click Element ${anchors[0]} 26 | Capture Page Screenshot readme.md-10-post-deck-anchor.png 27 | Press Keys css:body SHIFT+SPACE 28 | Capture Page Screenshot readme.md-10-post-deck-reverse.png 29 | [Teardown] Reset Example Test 30 | 31 | The README Notebook Can Be Navigated 32 | [Documentation] All slides and fragments are reachable. 33 | [Tags] activity:notebook 34 | Visit All Example Slides And Fragments ${README_IPYNB} 35 | [Teardown] Reset Example Test 36 | 37 | 38 | *** Keywords *** 39 | Visit All Example Slides And Fragments 40 | [Documentation] The given file in `examples` operates as expected. 41 | [Arguments] ${example}=README.ipynb 42 | ${stem} = Set Variable ${example.lower().replace(" ", "_")} 43 | Open Example ${example} switch_window=README 44 | Capture Page Screenshot ${stem}-00-before-deck.png 45 | IF ${example.endswith('.ipynb')} or ${example.endswith('.md')} 46 | Really Start Deck With Toolbar Button 47 | ELSE 48 | Execute JupyterLab Command Start Deck 49 | END 50 | Capture Page Screenshot ${stem}-01-deck.png 51 | Visit Slides And Fragments With Remote ${example} ${stem}-02-walk 52 | Stop Deck With Remote 53 | Capture Page Screenshot ${stem}-03-after-deck.png 54 | 55 | Set Up Example Suite 56 | [Documentation] Prepare for this suite. 57 | Set Attempt Screenshot Directory lab${/}examples 58 | Copy Examples 59 | 60 | Reset Example Test 61 | [Documentation] Clean up after each test. 62 | Maybe Open JupyterLab Sidebar Commands 63 | Execute JupyterLab Command Stop Deck 64 | Execute JupyterLab Command Save 65 | Capture Page Coverage 66 | Switch Window title:Home 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | 13 | 14 | ## Description 15 | 16 | 17 | 18 | ## Reproduce 19 | 20 | 21 | 22 | 1. Go to '...' 23 | 2. Click on '...' 24 | 3. Scroll down to '...' 25 | 4. See error '...' 26 | 27 | 29 | 30 | ## Expected behavior 31 | 32 | 33 | 34 | ## Context 35 | 36 | 37 | 38 | - Operating System and version: 39 | - Browser and version: 40 | - JupyterLab version: 41 | - jupyterlab-deck version(s): 42 | 43 |
Required: installed server extensions 44 |
45 |   Paste the output from running `jupyter server extension list` (JupyterLab >= 3)
46 |   or `jupyter serverextension list` (JupyterLab < 3) from the command line here.
47 |   You may want to sanitize the paths in the output.
48 | 
49 |
50 | 51 |
Required: installed lab extensions 52 |
53 |   Paste the output from running `jupyter labextension list` from the command line here.
54 |   You may want to sanitize the paths in the output.
55 | 
56 |
57 | 58 | 59 | 60 |
Troubleshoot Output 61 |
62 |   Paste the output from running `jupyter troubleshoot` from the command line here.
63 |   You may want to sanitize the paths in the output.
64 | 
65 |
66 | 67 |
Command Line Output 68 |
69 |   Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
70 | 
71 |
72 | 73 |
Browser Output (recommended for all interface issues) 74 |
75 |   Paste the output from your browser JavaScript console replacing the text in here.
76 | 
77 | To learn how to open the developer tools in your browser:
78 | https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#How_to_open_the_devtools_in_your_browser
79 | If too many messages accumulated after many hours of working in JupyterLab, consider
80 | refreshing the window and then reproducing the bug to reduce the noise in the logs.
81 | 
82 | 
83 |
84 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "definitions": { 4 | "style-presets": { 5 | "type": "object", 6 | "description": "Style presets to apply to @deathbeds/jupyterlab-fonts", 7 | "additionalItems": { 8 | "$ref": "#/definitions/style-preset" 9 | } 10 | }, 11 | "style-preset": { 12 | "type": "object", 13 | "required": ["label", "styles"], 14 | "properties": { 15 | "label": { 16 | "label": "string", 17 | "description": "the human-readable label" 18 | }, 19 | "scope": { 20 | "type": "string", 21 | "enum": ["layer", "slide", "deck", "any"] 22 | }, 23 | "styles": { 24 | "type": "object", 25 | "description": "a JSS-compatible description of a stylesheet: will be nested correctly" 26 | } 27 | } 28 | } 29 | }, 30 | "description": "Configure Deck Settings", 31 | "jupyter.lab.setting-icon": "deck:start", 32 | "jupyter.lab.setting-icon-label": "Decks", 33 | "properties": { 34 | "active": { 35 | "title": "Activate Deck", 36 | "type": "boolean", 37 | "default": false, 38 | "description": "Whether presentation mode is currently active." 39 | }, 40 | "stylePresets": { 41 | "title": "Style Presets", 42 | "$ref": "#/definitions/style-presets", 43 | "default": { 44 | "background": { 45 | "scope": "layer", 46 | "label": "Move to the background", 47 | "styles": { 48 | "z-index": "-1" 49 | } 50 | }, 51 | "top": { 52 | "scope": "layer", 53 | "label": "Move to the top", 54 | "styles": { 55 | "top": "0" 56 | } 57 | }, 58 | "right": { 59 | "scope": "layer", 60 | "label": "Move to the right", 61 | "styles": { 62 | "right": "0" 63 | } 64 | }, 65 | "bottom": { 66 | "scope": "layer", 67 | "label": "Move to the bottom", 68 | "styles": { 69 | "bottom": "0" 70 | } 71 | }, 72 | "left": { 73 | "scope": "layer", 74 | "label": "Move to the left", 75 | "styles": { 76 | "left": "0" 77 | } 78 | }, 79 | "fade-out": { 80 | "scope": "layer", 81 | "label": "Fade out", 82 | "styles": { 83 | "opacity": "0.125" 84 | } 85 | }, 86 | "unclickable": { 87 | "scope": "layer", 88 | "label": "Unclickable", 89 | "styles": { 90 | "pointer-events": "none" 91 | } 92 | } 93 | } 94 | } 95 | }, 96 | "title": "Decks", 97 | "type": "object" 98 | } 99 | -------------------------------------------------------------------------------- /atest/resources/LabSelectors.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Selectors that should maybe go upstream. 3 | 4 | 5 | *** Variables *** 6 | # # lumino ## 7 | ${CSS_LM_MOD_HIDDEN} .lm-mod-hidden 8 | ${CSS_LM_MOD_ACTIVE} .lm-mod-active 9 | ${CSS_LM_MENU_ITEM_LABEL} .lm-Menu-itemLabel 10 | ${CSS_LM_CLOSE_ICON} .lm-TabBar-tabCloseIcon 11 | 12 | # # lab # # 13 | 14 | # mod 15 | ${CSS_LAB_MOD_DISABLED} .jp-mod-disabled 16 | ${CSS_LAB_MOD_CMD} .jp-mod-commandMode 17 | ${CSS_LAB_MOD_ACTIVE} .jp-mod-active 18 | ${CSS_LAB_MOD_EDIT} .jp-mod-editMode 19 | ${CSS_LAB_MOD_RENDERED} .jp-mod-rendered 20 | 21 | # files 22 | ${CSS_LAB_FILES_HOME} .jp-BreadCrumbs-home 23 | ${CSS_LAB_FILES_DIR_ITEM} .jp-DirListing-item 24 | 25 | # docpanel 26 | ${CSS_LAB_NOT_INTERNAL_ANCHOR} a[href*\="#"]:not([href^="https"]):not(${CSS_LAB_INTERNAL_ANCHOR}) 27 | ${CSS_LAB_TAB_NOT_CURRENT} .lm-DockPanel .lm-TabBar-tab:not(.jp-mod-current) 28 | 29 | # docs 30 | ${CSS_LAB_DOC} .jp-Document 31 | ${CSS_LAB_DOC_VISIBLE} ${CSS_LAB_DOC}:not(${CSS_LM_MOD_HIDDEN}) 32 | ${CSS_LAB_SPINNER} .jp-Spinner 33 | ${CSS_LAB_INTERNAL_ANCHOR} .jp-InternalAnchorLink 34 | ${CSS_LAB_TOOLBAR_BTN} .jp-ToolbarButtonComponent 35 | ${CSS_LAB_DOC_TOOLBAR_BTN} ${CSS_LAB_DOC_VISIBLE} ${CSS_LAB_TOOLBAR_BTN} 36 | 37 | # meta 38 | ${CSS_LAB_ADVANCED_COLLAPSE} .jp-NotebookTools .jp-Collapse-header 39 | ${CSS_LAB_CELL_META_JSON} .jp-MetadataEditorTool 40 | ${CSS_LAB_CELL_META_JSON_HIDDEN} ${CSS_LM_MOD_HIDDEN} ${CSS_LAB_CELL_META_JSON} 41 | 42 | # notebook 43 | ${CSS_LAB_NB_TOOLBAR} .jp-NotebookPanel-toolbar 44 | ${CSS_LAB_NB_TOOLBAR_CELLTYPE} .jp-Notebook-toolbarCellType select 45 | ${CSS_LAB_CELL_MARKDOWN} .jp-MarkdownCell 46 | ${CSS_LAB_CELL_CODE} .jp-CodeCell 47 | ${CSS_LAB_CELL_RAW} .jp-RawCell 48 | &{CSS_LAB_CELL_TYPE} 49 | ... code=${CSS_LAB_CELL_CODE} 50 | ... markdown=${CSS_LAB_CELL_MARKDOWN} 51 | ... raw=${CSS_LAB_CELL_RAW} 52 | 53 | # icons 54 | ${CSS_LAB_ICON_ELLIPSES} [data-icon="ui-components:ellipses"] 55 | ${CSS_LAB_ICON_CARET_LEFT} [data-icon="ui-components:caret-left"] 56 | 57 | # markdown 58 | ${CSS_LAB_EDITOR} .jp-FileEditor 59 | ${CSS_LAB_MARKDOWN_VIEWER} .jp-MarkdownViewer 60 | ${CSS_LAB_CMD_MARKDOWN_PREVIEW} [data-command="fileeditor:markdown-preview"] 61 | 62 | # lab 7 63 | ${XP_LAB4_COLLAPSED_PANEL} //*[contains(@class, 'jp-Collapse-header-collapsed')] 64 | ${XP_LAB4_COLLAPSED_PANEL_TITLE} ${XP_LAB4_COLLAPSED_PANEL}//*[contains(@class, 'jp-Collapser-title')] 65 | 66 | # rfjl bugs 67 | ${CM CSS EDITOR} .CodeMirror 68 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/notebook.css: -------------------------------------------------------------------------------- 1 | /** heavy ui tweaks **/ 2 | .jp-Deck .jp-InputPrompt, 3 | .jp-Deck .jp-OutputPrompt, 4 | .jp-Deck .jp-Notebook-footer, 5 | .jp-Deck .jp-cell-toolbar { 6 | display: none; 7 | } 8 | 9 | .jp-Deck .jp-NotebookPanel-toolbar { 10 | position: absolute; 11 | border: 0; 12 | opacity: 0; 13 | bottom: 0; 14 | left: 0; 15 | right: 6em; 16 | height: var(--jp-private-toolbar-height) !important; 17 | max-height: var(--jp-private-toolbar-height) !important; 18 | box-shadow: unset; 19 | } 20 | 21 | .jp-Deck .jp-NotebookPanel-toolbar:hover { 22 | opacity: 0.8; 23 | transition: opacity 0.1s; 24 | } 25 | 26 | .jp-Deck.jp-NotebookPanel { 27 | display: flex; 28 | flex-direction: column; 29 | position: fixed; 30 | left: var(--jp-private-sidebar-tab-width); 31 | right: var(--jp-private-sidebar-tab-width); 32 | place-content: center center; 33 | align-items: stretch; 34 | height: 100vh; 35 | overflow-y: hidden; 36 | } 37 | 38 | .jp-Deck .jp-Notebook { 39 | flex-shrink: 0; 40 | gap: 0; 41 | overflow-y: auto; 42 | max-height: 100vh; 43 | margin: 0; 44 | padding: 0; 45 | background: transparent; 46 | } 47 | 48 | .jp-Deck .jp-Notebook.jp-WindowedPanel-outer, 49 | .jp-Deck .jp-Notebook .jp-WindowedPanel-inner, 50 | body[data-notebook='notebooks'] .jp-Deck .jp-WindowedPanel-window, 51 | body[data-notebook='notebooks'] .jp-Deck .jp-Notebook-cell { 52 | background: transparent; 53 | } 54 | 55 | body[data-notebook='notebooks'] 56 | .jp-Deck 57 | .jp-Notebook.jp-mod-commandMode 58 | .jp-Cell.jp-mod-active.jp-mod-selected:not(.jp-mod-multiSelected) { 59 | background: transparent !important; 60 | } 61 | 62 | .jp-Deck .jp-Notebook.jp-mod-scrollPastEnd::after { 63 | display: none; 64 | } 65 | 66 | .jp-Deck .jp-Collapser { 67 | margin-right: 0.25em; 68 | } 69 | 70 | /** on-screen real-estate management **/ 71 | .jp-Deck .jp-Cell:not(.jp-deck-mod-onscreen) { 72 | max-height: 0; 73 | height: 0; 74 | overflow: hidden; 75 | visibility: hidden; 76 | pointer-events: none; 77 | padding: 0; 78 | margin: 0; 79 | } 80 | 81 | .jp-Deck .jp-Cell.jp-deck-mod-onscreen { 82 | flex: 0; 83 | } 84 | 85 | .jp-Deck .jp-Cell.jp-deck-mod-onscreen:not(.jp-deck-mod-visible) { 86 | opacity: 0; 87 | } 88 | 89 | .jp-Deck .jp-Cell.jp-deck-mod-onscreen.jp-deck-mod-visible { 90 | opacity: 1; 91 | transition: opacity 0.1s; 92 | } 93 | 94 | .jp-Deck .jp-deck-mod-layer { 95 | position: fixed; 96 | } 97 | 98 | /** markdown tweaks **/ 99 | .jp-Deck .jp-MarkdownOutput { 100 | border: 0; 101 | } 102 | 103 | /** editor **/ 104 | .jp-Deck .jp-InputArea-editor, 105 | .jp-Deck .jp-Notebook.jp-mod-editMode .jp-Cell.jp-mod-active .jp-InputArea-editor { 106 | border: 0; 107 | box-shadow: unset; 108 | } 109 | 110 | /** side-by-side **/ 111 | .jp-Deck .jp-mod-sideBySide.jp-Notebook .jp-Notebook-cell { 112 | margin: 0 !important; 113 | } 114 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/shell.css: -------------------------------------------------------------------------------- 1 | body[data-jp-deck-mode='presenting'] { 2 | background: var(--jp-layout-color0); 3 | } 4 | 5 | body[data-jp-deck-mode='presenting'] #jp-main-content-panel { 6 | width: 100vw !important; 7 | height: 100vh !important; 8 | border: 0 !important; 9 | padding: 0 !important; 10 | margin: 0 !important; 11 | top: 0 !important; 12 | } 13 | 14 | body[data-jp-deck-mode='presenting'] #jp-menu-panel, 15 | body[data-jp-deck-mode='presenting'] #jp-top-panel { 16 | display: none; 17 | } 18 | 19 | body[data-jp-deck-mode='presenting'] .jp-SideBar { 20 | opacity: 0; 21 | transition: opacity 0.1s; 22 | z-index: var(--jp-deck-z-lab-chrome); 23 | background: none; 24 | border-color: transparent; 25 | visibility: var(--jp-deck-chrome-visibility); 26 | } 27 | 28 | body[data-jp-deck-mode='presenting'] .jp-SideBar.lm-TabBar.jp-mod-left, 29 | body[data-jp-deck-mode='presenting'] .jp-SideBar.lm-TabBar.jp-mod-right { 30 | border-color: transparent; 31 | } 32 | 33 | body[data-jp-deck-mode='presenting'] .jp-SideBar .lm-TabBar-tab:not(.lm-mod-current) { 34 | background: unset; 35 | border-color: transparent; 36 | } 37 | 38 | body[data-jp-deck-mode='presenting'] #jp-right-stack, 39 | body[data-jp-deck-mode='presenting'] #jp-left-stack { 40 | opacity: 0.25; 41 | z-index: var(--jp-deck-z-lab-chrome); 42 | transition: opacity 0.1s; 43 | visibility: var(--jp-deck-chrome-visibility); 44 | } 45 | 46 | body[data-jp-deck-mode='presenting'] .jp-SideBar:hover, 47 | body[data-jp-deck-mode='presenting'] #jp-right-stack:hover, 48 | body[data-jp-deck-mode='presenting'] #jp-left-stack:hover { 49 | opacity: 0.9; 50 | transition: opacity 0.1s; 51 | } 52 | 53 | body[data-jp-deck-mode='presenting'] #jp-main-dock-panel { 54 | padding: 0; 55 | } 56 | 57 | /* notebook 7 */ 58 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] #main-panel { 59 | width: 100vw !important; 60 | height: 100vh !important; 61 | border: 0 !important; 62 | padding: 0 !important; 63 | margin: 0 !important; 64 | top: 0 !important; 65 | } 66 | 67 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] .jp-Notebook { 68 | padding-top: unset; 69 | padding-left: unset; 70 | padding-right: unset; 71 | background: transparent; 72 | box-shadow: unset; 73 | } 74 | 75 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] #top-panel-wrapper, 76 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] #menu-panel-wrapper, 77 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] .jp-Notebook-footer, 78 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] 79 | .jp-OutputArea-promptOverlay { 80 | display: none; 81 | } 82 | 83 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] 84 | .jp-Notebook 85 | > *:first-child:not(:last-child) { 86 | box-shadow: unset; 87 | } 88 | 89 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] 90 | .jp-WindowedPanel-inner { 91 | margin: 0; 92 | } 93 | 94 | body[data-jp-deck-mode='presenting'][data-notebook='notebooks'] 95 | .jp-Notebook-cell[data-windowed-list-index='0'] { 96 | padding-top: 0; 97 | } 98 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/icons.ts: -------------------------------------------------------------------------------- 1 | import { LabIcon, caretUpEmptyThinIcon } from '@jupyterlab/ui-components'; 2 | 3 | import NOTE from '../style/img/chat-remove-outline.svg'; 4 | import NULL from '../style/img/checkbox-blank-outline.svg'; 5 | import FRAGMENT from '../style/img/checkbox-intermediate-variant.svg'; 6 | import SKIP from '../style/img/close-box-outline.svg'; 7 | import DECK_START from '../style/img/deck.svg'; 8 | import LAYER_DECK from '../style/img/image-filter-hdr.svg'; 9 | import LAYER_NULL from '../style/img/image-off-outline.svg'; 10 | import LAYER_STACK from '../style/img/image-outline-multiple.svg'; 11 | import LAYER_SLIDE from '../style/img/image-outline.svg'; 12 | import ZOOM from '../style/img/loupe.svg'; 13 | import LAYER_FRAGMENT from '../style/img/message-image-outline.svg'; 14 | import Z_INDEX from '../style/img/order-numeric-descending.svg'; 15 | import SUBSLIDE from '../style/img/plus-box-multiple.svg'; 16 | import SLIDE from '../style/img/plus-box.svg'; 17 | import OPACITY from '../style/img/square-opacity.svg'; 18 | import TRANSFORM_STOP from '../style/img/transform-stop.svg'; 19 | import TRANSFORM from '../style/img/transform.svg'; 20 | 21 | import { CSS, TLayerScope, TSlideType } from './tokens'; 22 | 23 | const DECK_STOP = DECK_START.replace(CSS.icon, CSS.iconWarn); 24 | 25 | export namespace ICONS { 26 | export const deckStart = new LabIcon({ name: 'deck:start', svgstr: DECK_START }); 27 | export const deckStop = new LabIcon({ name: 'deck:stop', svgstr: DECK_STOP }); 28 | export const goEnabled = new LabIcon({ 29 | name: 'deck:go', 30 | svgstr: caretUpEmptyThinIcon.svgstr.replace(CSS.icon, CSS.iconContrast), 31 | }); 32 | export const goDisabled = caretUpEmptyThinIcon; 33 | // layover 34 | export const transformStart = new LabIcon({ 35 | name: 'deck:layover-start', 36 | svgstr: TRANSFORM, 37 | }); 38 | export const transformStop = new LabIcon({ 39 | name: 'deck:layover-stop', 40 | svgstr: TRANSFORM_STOP, 41 | }); 42 | // design 43 | export const slideshow: Record<'null' | Exclude, LabIcon> = { 44 | slide: new LabIcon({ name: 'deck:slide-slide', svgstr: SLIDE }), 45 | subslide: new LabIcon({ name: 'deck:slide-subslide', svgstr: SUBSLIDE }), 46 | null: new LabIcon({ name: 'deck:slide-null', svgstr: NULL }), 47 | fragment: new LabIcon({ name: 'deck:slide-fragment', svgstr: FRAGMENT }), 48 | skip: new LabIcon({ name: 'deck:slide-skip', svgstr: SKIP }), 49 | notes: new LabIcon({ name: 'deck:slide-note', svgstr: NOTE }), 50 | }; 51 | export const layer: Record = { 52 | deck: new LabIcon({ name: 'deck:layer-deck', svgstr: LAYER_DECK }), 53 | stack: new LabIcon({ name: 'deck:layer-stack', svgstr: LAYER_STACK }), 54 | slide: new LabIcon({ name: 'deck:layer-slide', svgstr: LAYER_SLIDE }), 55 | fragment: new LabIcon({ name: 'deck:layer-fragment', svgstr: LAYER_FRAGMENT }), 56 | null: new LabIcon({ name: 'deck:layer-null', svgstr: LAYER_NULL }), 57 | }; 58 | export const zoom = new LabIcon({ name: 'deck:zoom', svgstr: ZOOM }); 59 | export const zIndex = new LabIcon({ name: 'deck:z-index', svgstr: Z_INDEX }); 60 | export const opacity = new LabIcon({ name: 'deck:opacity', svgstr: OPACITY }); 61 | } 62 | -------------------------------------------------------------------------------- /atest/suites/lab/01-examples.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The examples work. 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Resource ../../resources/Fixtures.resource 7 | Resource ../../resources/Deck.resource 8 | Resource ../../resources/Screenshots.resource 9 | 10 | Suite Setup Set Up Example Suite 11 | Suite Teardown Clean Examples 12 | 13 | Force Tags suite:examples 14 | 15 | 16 | *** Test Cases *** 17 | The README Markdown Can Be Navigated 18 | [Documentation] All slides and fragments are reachable. 19 | [Tags] activity:markdown 20 | Visit All Example Slides And Fragments ${README_MD} 21 | Execute JupyterLab Command Start Deck 22 | Wait Until Element Is Visible css:${CSS_DECK} 23 | ${anchors} = Get WebElements css:${CSS_LAB_MARKDOWN_VIEWER} a[href\^="#"] 24 | ${anchors} = Filter Visible Elements ${anchors} 25 | Click Element ${anchors[0]} 26 | Capture Page Screenshot readme.md-10-post-deck-anchor.png 27 | Press Keys css:body SHIFT+SPACE 28 | Capture Page Screenshot readme.md-10-post-deck-reverse.png 29 | [Teardown] Reset Example Test 30 | 31 | The README Notebook Can Be Navigated 32 | [Documentation] All slides and fragments are reachable. 33 | [Tags] activity:notebook 34 | Visit All Example Slides And Fragments ${README_IPYNB} 35 | [Teardown] Reset Example Test 36 | 37 | The History Notebook Can Be Navigated 38 | [Documentation] All slides and fragments are reachable. 39 | [Tags] activity:notebook 40 | Visit All Example Slides And Fragments ${HISTORY_IPYNB} 41 | [Teardown] Reset Example Test 42 | 43 | The Layers Notebook Can Be Navigated 44 | [Documentation] All slides and fragments are reachable. 45 | [Tags] activity:notebook feature:layers 46 | Visit All Example Slides And Fragments ${LAYERS_IPYNB} 47 | [Teardown] Reset Example Test 48 | 49 | 50 | *** Keywords *** 51 | Visit All Example Slides And Fragments 52 | [Documentation] The given file in `examples` operates as expected. 53 | [Arguments] ${example}=README.ipynb 54 | ${stem} = Set Variable ${example.lower().replace(" ", "_")} 55 | Open Example ${example} 56 | Capture Page Screenshot ${stem}-00-before-deck.png 57 | IF ${example.endswith('.ipynb')} or ${example.endswith('.md')} 58 | Really Start Deck With Toolbar Button 59 | ELSE 60 | Execute JupyterLab Command Start Deck 61 | END 62 | Capture Page Screenshot ${stem}-01-deck.png 63 | Visit Slides And Fragments With Remote ${example} ${stem}-02-walk 64 | Stop Deck With Remote 65 | Capture Page Screenshot ${stem}-03-after-deck.png 66 | 67 | Set Up Example Suite 68 | [Documentation] Prepare for this suite. 69 | Set Attempt Screenshot Directory lab${/}examples 70 | Copy Examples 71 | 72 | Reset Example Test 73 | [Documentation] Clean up after each test. 74 | Maybe Open JupyterLab Sidebar Commands 75 | Execute JupyterLab Command Stop Deck 76 | Execute JupyterLab Command Save 77 | Execute JupyterLab Command Close All Tabs 78 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.9.0,<4.0.0"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "jupyterlab-deck" 7 | version = "0.2.1" 8 | authors = [ 9 | {name = "jupyterlab-deck contributors", email = "deathbeds@googlegroups.com"}, 10 | ] 11 | readme = "README.md" 12 | classifiers = [ 13 | "Framework :: Jupyter :: JupyterLab :: 3", 14 | "Framework :: Jupyter :: JupyterLab :: 4", 15 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 16 | "Framework :: Jupyter :: JupyterLab :: Extensions", 17 | "Framework :: Jupyter :: JupyterLab", 18 | "Framework :: Jupyter", 19 | "License :: OSI Approved :: BSD License", 20 | "Programming Language :: Python :: 3 :: Only", 21 | "Programming Language :: Python :: 3", 22 | ] 23 | requires-python = ">=3.8" 24 | dynamic = ["description"] 25 | dependencies = [ 26 | "jupyterlab >=3.5,<5.0.0a0", 27 | "jupyterlab-fonts >=3.0.0" 28 | ] 29 | 30 | [project.urls] 31 | "Bug Tracker" = "https://github.com/deathbeds/jupyterlab-deck/issues" 32 | "Changelog" = "https://github.com/deathbeds/jupyterlab-deck/blob/main/CHANGELOG.md" 33 | "Documentation" = "https://jupyterlab-deck.rtfd.io" 34 | "Source" = "https://github.com/deathbeds/jupyterlab-deck" 35 | 36 | [tool.flit.sdist] 37 | include = ["src/_d"] 38 | 39 | [tool.flit.module] 40 | name = "jupyterlab_deck" 41 | 42 | [tool.flit.external-data] 43 | directory = "src/_d" 44 | 45 | [tool.doit] 46 | backend = "sqlite3" 47 | verbosity = 2 48 | 49 | [tool.doit.commands.list] 50 | status = true 51 | subtasks = true 52 | 53 | [tool.robocop] 54 | exclude = [ 55 | "deprecated-statement", 56 | "inline-if-can-be-used", 57 | "unnecessary-string-conversion", 58 | "unused-argument", 59 | "unused-variable", 60 | "variable-overwritten-before-usage", 61 | ] 62 | 63 | [tool.ruff] 64 | ignore = [ 65 | "D211", 66 | "D213", 67 | # if it doesn't bother black.. 68 | "E501", 69 | # if it doesn't bother mypy... 70 | "ANN101", 71 | "ANN401", 72 | "PGH003", 73 | # keep 3.8 compat 74 | "UP006", 75 | "UP007", 76 | # meh 77 | "N812", 78 | # format? 79 | "COM812", 80 | "ISC001", 81 | ] 82 | cache-dir = "build/.cache/ruff" 83 | select = [ 84 | "A", 85 | "ARG", 86 | "B", 87 | "BLE", 88 | "C4", 89 | "C90", 90 | "COM", 91 | "D", 92 | "DJ", 93 | "DTZ", 94 | "E", 95 | "EM", 96 | # "ERA", # causes syntax errors, check later 97 | "EXE", 98 | "F", 99 | "FBT", 100 | "G", 101 | "I", 102 | "ICN", 103 | "INP", 104 | "ISC", 105 | "N", 106 | "NPY", 107 | "PD", 108 | "PGH", 109 | "PIE", 110 | "PL", 111 | "PT", 112 | "PTH", 113 | "PYI", 114 | "Q", 115 | "RET", 116 | "RSE", 117 | "RUF", 118 | "S", 119 | "SIM", 120 | "SLF", 121 | "T10", 122 | "T20", 123 | "TCH", 124 | "TID", 125 | "UP", 126 | "W", 127 | "YTT", 128 | ] 129 | [tool.ruff.per-file-ignores] 130 | "dodo.py" = ["RUF012", "S603", "T201", "BLE001", "D103", "D102", "D101", "S607", "PLR0912", "S320"] 131 | "conf.py" = ["INP001", "A001", "D103", "ARG001", "S603", "S607"] 132 | "**/tests/**/*.py" = ["SLF001", "S101"] 133 | "_scripts/*.py" = ["INP001", "SLF001"] 134 | 135 | [tool.ruff.isort] 136 | known-first-party = ["jupyterlab_deck"] 137 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/design.css: -------------------------------------------------------------------------------- 1 | .jp-Deck-DesignTools { 2 | position: absolute; 3 | left: 0; 4 | bottom: 0; 5 | display: flex; 6 | flex-direction: column; 7 | z-index: var(--jp-deck-z-remote); 8 | } 9 | 10 | .jp-Deck-DesignTools label { 11 | color: var(--jp-ui-font-color1); 12 | } 13 | 14 | .jp-Deck-DesignTools > div { 15 | display: flex; 16 | flex: 1; 17 | flex-direction: row; 18 | align-items: end; 19 | } 20 | 21 | .jp-Deck-DesignTools button { 22 | border: 0; 23 | background-color: transparent; 24 | display: flex; 25 | flex-direction: row; 26 | align-items: center; 27 | cursor: pointer; 28 | } 29 | 30 | .jp-Deck-DesignTools button label { 31 | cursor: pointer; 32 | } 33 | 34 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector { 35 | display: flex; 36 | flex: 1; 37 | flex-direction: column; 38 | margin: 0; 39 | padding: 0; 40 | list-style: none; 41 | } 42 | 43 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li { 44 | align-items: center; 45 | display: flex; 46 | flex-direction: row; 47 | flex: 1; 48 | margin: 0; 49 | opacity: 0.25; 50 | padding: 0; 51 | transition: opacity 0.1s; 52 | } 53 | 54 | /* stlyelint wants these here... */ 55 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider label { 56 | display: flex; 57 | flex-direction: row; 58 | align-items: center; 59 | } 60 | 61 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider label span { 62 | display: none; 63 | } 64 | 65 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider:hover label span { 66 | display: block; 67 | } 68 | 69 | /* and now back ... */ 70 | 71 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li label { 72 | display: none; 73 | } 74 | 75 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li:hover, 76 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li:active { 77 | opacity: 0.8; 78 | transition: opacity 0.1s; 79 | } 80 | 81 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li:hover label, 82 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li:active label { 83 | display: block; 84 | } 85 | 86 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li.jp-mod-active { 87 | opacity: 1; 88 | transition: opacity 0.1s; 89 | } 90 | 91 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector li:not(.jp-mod-active) { 92 | display: none; 93 | } 94 | 95 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Selector:hover li:not(.jp-mod-active) { 96 | display: block; 97 | } 98 | 99 | /* slider */ 100 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider { 101 | display: flex; 102 | flex-direction: column; 103 | align-items: start; 104 | opacity: 0.5; 105 | } 106 | 107 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider.jp-mod-active { 108 | opacity: 1; 109 | } 110 | 111 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider input { 112 | display: none; 113 | } 114 | 115 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider input[type='checkbox'] { 116 | width: var(--jp-deck-checkbox-width); 117 | flex: 0; 118 | } 119 | 120 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider input[type='range'] { 121 | flex: 1; 122 | appearance: slider-vertical; 123 | width: var(--jp-deck-vslider-width); 124 | } 125 | 126 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider:hover input, 127 | .jp-Deck-DesignTools .jp-Deck-DesignTools-Slider:active input { 128 | display: block; 129 | } 130 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | """documentation for jupyterlab-deck.""" 2 | import datetime 3 | from pathlib import Path 4 | 5 | import tomli 6 | 7 | IGNORED_MESSAGES = [ 8 | # thrown with `---` in places docutils doesn't like, but deck wants 9 | r"Document or section may not begin with a transition", 10 | ] 11 | 12 | 13 | CONF_PY = Path(__file__) 14 | HERE = CONF_PY.parent 15 | ROOT = HERE.parent 16 | PYPROJ = ROOT / "pyproject.toml" 17 | PROJ_DATA = tomli.loads(PYPROJ.read_text(encoding="utf-8")) 18 | 19 | # metadata 20 | author = PROJ_DATA["project"]["authors"][0]["name"] 21 | project = PROJ_DATA["project"]["name"] 22 | copyright = f"{datetime.datetime.now(tz=datetime.timezone.utc).date().year}, {author}" 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = PROJ_DATA["project"]["version"] 26 | 27 | # The short X.Y version 28 | version = ".".join(release.rsplit(".", 1)) 29 | 30 | # sphinx config 31 | extensions = [ 32 | "sphinx.ext.autosectionlabel", 33 | "myst_nb", 34 | "sphinx_copybutton", 35 | ] 36 | 37 | autosectionlabel_prefix_document = True 38 | myst_heading_anchors = 3 39 | suppress_warnings = ["autosectionlabel.*"] 40 | 41 | # files 42 | # rely on the order of these to patch json, labextensions correctly 43 | html_static_path = [ 44 | # docs stuff 45 | "_static", 46 | # as-built application 47 | "../build/lite", 48 | ] 49 | html_css_files = [ 50 | "theme.css", 51 | ] 52 | 53 | exclude_patterns = [ 54 | "_build", 55 | ".ipynb_checkpoints", 56 | "**/.ipynb_checkpoints", 57 | "**/~.*", 58 | "**/node_modules", 59 | "babel.config.*", 60 | "jest-setup.js", 61 | "jest.config.js", 62 | "jupyter_execute", 63 | ".jupyter_cache", 64 | "test/", 65 | "tsconfig.*", 66 | "webpack.config.*", 67 | ] 68 | 69 | # theme 70 | html_theme = "pydata_sphinx_theme" 71 | html_favicon = "_static/deck.svg" 72 | html_logo = "_static/deck.svg" 73 | html_theme_options = { 74 | "github_url": PROJ_DATA["project"]["urls"]["Source"], 75 | "use_edit_page_button": True, 76 | "logo": {"text": PROJ_DATA["project"]["name"]}, 77 | "icon_links": [ 78 | { 79 | "name": "PyPI", 80 | "url": "https://pypi.org/project/jupyterlab-deck", 81 | "icon": "fa-brands fa-python", 82 | }, 83 | { 84 | "name": "conda-forge", 85 | "url": "https://github.com/conda-forge/jupyterlab-deck-feedstock#about-jupyterlab-deck", 86 | "icon": "_static/anvil.svg", 87 | "type": "local", 88 | }, 89 | { 90 | "name": "npm", 91 | "url": "https://www.npmjs.com/package/@deathbeds/jupyterlab-deck", 92 | "icon": "fa-brands fa-npm", 93 | }, 94 | ], 95 | # rely on browser-native accessibility features instead of custom JS 96 | "navigation_with_keys": False, 97 | } 98 | 99 | html_context = { 100 | "github_user": "deathbeds", 101 | "github_repo": "jupyterlab-deck", 102 | "github_version": "main", 103 | "doc_path": "docs", 104 | } 105 | 106 | html_sidebars = {"**": []} 107 | 108 | 109 | def setup(app): 110 | """Perform startup things before sphinx runs.""" 111 | import re 112 | 113 | from docutils.utils import Reporter 114 | 115 | _old_system_message = Reporter.system_message 116 | 117 | def _filtered_system_message(self, lvl, msg, *args, **kwargs): 118 | """Don't allow sphinx to warn on known issues.""" 119 | if any(re.search(pat, msg) is not None for pat in IGNORED_MESSAGES): 120 | return None 121 | return _old_system_message(lvl, msg, *args, **kwargs) 122 | 123 | Reporter.system_message = _filtered_system_message 124 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/style/layover.css: -------------------------------------------------------------------------------- 1 | .jp-Deck-Layover { 2 | position: absolute; 3 | inset: 0; 4 | background-color: rgba(0, 0, 0, 0.06); 5 | pointer-events: none; 6 | } 7 | 8 | .jp-Deck-Layover .jp-deck-mod-dragging { 9 | z-index: var(--jp-deck-z-layover); 10 | cursor: grabbing; 11 | } 12 | 13 | .jp-Deck-Layover .jp-Deck-LayoverPart { 14 | position: absolute; 15 | pointer-events: all; 16 | border: solid 2px var(--jp-deck-layover-color); 17 | opacity: 0.25; 18 | cursor: grab; 19 | } 20 | 21 | .jp-Deck-Layover .jp-Deck-LayoverPart:hover, 22 | .jp-Deck-Layover .jp-Deck-LayoverPart:active { 23 | opacity: 0.8; 24 | transition: opacity 0.1s; 25 | } 26 | 27 | .jp-Deck-LayoverHandle { 28 | border: solid 1px var(--jp-deck-layover-color); 29 | } 30 | 31 | .jp-Deck-Layover .jp-Deck-LayoverPart .jp-Deck-LayoverHandle { 32 | position: absolute; 33 | width: var(--jp-deck-handle-size); 34 | height: var(--jp-deck-handle-size); 35 | opacity: 0.1; 36 | transition: opacity 0.1s; 37 | } 38 | 39 | .jp-Deck-Layover .jp-Deck-LayoverHandle:hover { 40 | opacity: 1; 41 | background-color: var(--jp-deck-layover-color); 42 | transition: opacity 0.1s; 43 | } 44 | 45 | .jp-Deck-Layover .jp-Deck-LayoverPart:hover .jp-Deck-LayoverHandle, 46 | .jp-Deck-Layover .jp-Deck-LayoverPart:active .jp-Deck-LayoverHandle { 47 | opacity: 0.8; 48 | transition: opacity 0.1s; 49 | } 50 | 51 | /* centering */ 52 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-n, 53 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-s { 54 | left: 50%; 55 | margin-left: calc(-0.5 * var(--jp-deck-handle-size)); 56 | cursor: ns-resize; 57 | } 58 | 59 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-e, 60 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-w { 61 | top: 50%; 62 | margin-top: calc(-0.5 * var(--jp-deck-handle-size)); 63 | cursor: ew-resize; 64 | } 65 | 66 | /* border and position */ 67 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-n, 68 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-nw, 69 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-ne { 70 | top: 0; 71 | border-top: 0; 72 | } 73 | 74 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-s, 75 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-sw, 76 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-se { 77 | bottom: 0; 78 | border-bottom: 0; 79 | } 80 | 81 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-e, 82 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-ne, 83 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-se { 84 | right: 0; 85 | border-right: 0; 86 | } 87 | 88 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-w, 89 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-nw, 90 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-sw { 91 | left: 0; 92 | border-left: 0; 93 | } 94 | 95 | /** extra cursors */ 96 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-ne, 97 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-sw { 98 | cursor: nesw-resize; 99 | } 100 | 101 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-nw, 102 | .jp-Deck-LayoverHandle.jp-Deck-LayoverHandle-se { 103 | cursor: nwse-resize; 104 | } 105 | 106 | /* index */ 107 | 108 | .jp-Deck-Layover .jp-Deck-LayoverUnstyle, 109 | .jp-Deck-Layover .jp-Deck-LayoverLabel { 110 | line-height: 1.75; 111 | font-weight: bold; 112 | position: absolute; 113 | margin-top: calc(-0.5 * var(--jp-deck-handle-size)); 114 | margin-left: calc(-0.5 * var(--jp-deck-handle-size)); 115 | text-align: center; 116 | height: var(--jp-deck-handle-size); 117 | width: var(--jp-deck-handle-size); 118 | color: var(--jp-layout-color0); 119 | } 120 | 121 | .jp-Deck-Layover .jp-Deck-LayoverLabel { 122 | background-color: var(--jp-deck-layover-color); 123 | left: 50%; 124 | top: 50%; 125 | border-radius: var(--jp-deck-handle-size); 126 | border: solid 1px var(--jp-deck-layover-color); 127 | } 128 | 129 | .jp-Deck-Layover .jp-Deck-LayoverUnstyle { 130 | background-color: var(--jp-error-color1); 131 | border: none; 132 | left: 75%; 133 | margin-top: calc(-0.5 * var(--jp-deck-handle-size)); 134 | top: 50%; 135 | cursor: pointer; 136 | } 137 | -------------------------------------------------------------------------------- /atest/resources/Server.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for testing jupyterlab-fonts 3 | 4 | Library BuiltIn 5 | Library Collections 6 | Library String 7 | Library OperatingSystem 8 | Library JupyterLibrary 9 | Library shutil 10 | Library uuid 11 | 12 | 13 | *** Variables *** 14 | ${JUPYTERLAB_EXE} ["jupyter-lab"] 15 | ${JSCOV} ${EMPTY} 16 | &{ETC_OVERRIDES} 17 | ... jupyter_config.json=jupyter_config.json 18 | ... overrides.json=labconfig${/}default_setting_overrides.json 19 | ... page_config.json=labconfig${/}page_config.json 20 | ${FIXTURES} ${ROOT}${/}atest${/}fixtures 21 | 22 | 23 | *** Keywords *** 24 | Initialize Jupyter Server 25 | [Documentation] Set up server with command as defined in atest.py. 26 | [Arguments] ${home_dir} 27 | ${port} = Get Unused Port 28 | ${token} = Generate Random String 64 29 | ${base url} = Set Variable /jl@d/ 30 | @{args} = Build Custom JupyterLab Args ${port} ${token} ${base url} 31 | ${rest_args} = Get Slice From List ${args} 1 32 | ${config} = Initialize Jupyter Server Config ${home_dir} 33 | ${lab} = Start New Jupyter Server 34 | ... ${args[0]} 35 | ... ${port} 36 | ... ${base url} 37 | ... ${config["cwd"]} 38 | ... ${token} 39 | ... @{rest_args} 40 | ... &{config} 41 | Wait For Jupyter Server To Be Ready ${lab} 42 | RETURN ${lab} 43 | 44 | Initialize Jupyter Server Config 45 | [Documentation] Prepare keyword arguments to launch a custom jupyter server. 46 | [Arguments] ${home_dir} 47 | ${notebook_dir} = Set Variable ${home_dir}${/}work 48 | ${app_data} = Get Windows App Data ${home_dir} 49 | &{config} = Create Dictionary 50 | ... stdout=${OUTPUT DIR}${/}lab.log 51 | ... stderr=STDOUT 52 | ... cwd=${notebook_dir} 53 | ... env:HOME=${home_dir} 54 | ... env:APPDATA=${app_data} 55 | ... env:JUPYTER_PREFER_ENV_PATH=0 56 | RETURN ${config} 57 | 58 | Get Windows App Data 59 | [Documentation] Get an overloaded appdata directory. 60 | [Arguments] ${home_dir} 61 | RETURN ${home_dir}${/}AppData${/}Roaming 62 | 63 | Build Custom JupyterLab Args 64 | [Documentation] Generate some args 65 | [Arguments] ${port} ${token} ${base url} 66 | @{args} = Loads ${JUPYTERLAB_EXE} 67 | ${config} = Normalize Path ${ROOT}${/}atest${/}fixtures${/}jupyter_config.json 68 | @{args} = Set Variable 69 | ... @{args} 70 | ... --no-browser 71 | ... --debug 72 | ... --expose-app-in-browser 73 | ... --port\=${port} 74 | ... --IdentityProvider.token\=${token} 75 | ... --ServerApp.base_url\=${base url} 76 | Log ${args} 77 | RETURN @{args} 78 | 79 | Initialize Fake Home 80 | [Documentation] Populate a fake HOME 81 | ${home_dir} = Set Variable ${OUTPUT_DIR}${/}.home 82 | ${local} = Get XDG Local Path ${home_dir} 83 | ${etc} = Set Variable ${local}${/}etc${/}jupyter 84 | FOR ${src} ${dest} IN &{ETC_OVERRIDES} 85 | OperatingSystem.Copy File ${FIXTURES}${/}${src} ${etc}${/}${dest} 86 | END 87 | Create Directory ${home_dir}${/}work 88 | RETURN ${home_dir} 89 | 90 | Get XDG Local Path 91 | [Documentation] Get the root of the XDG local data for this platform. 92 | [Arguments] ${home_dir} 93 | IF "${OS}" == "Windows" 94 | ${app_data} = Get Windows App Data ${home_dir} 95 | ${local} = Set Variable ${app_data}${/}Python 96 | ELSE 97 | ${local} = Set Variable ${home_dir}${/}.local 98 | END 99 | RETURN ${local} 100 | 101 | Get GeckoDriver Executable Path 102 | [Documentation] Find geckodriver 103 | IF "${OS}" == "Windows" 104 | ${executable_path} = Which geckodriver.exe 105 | ELSE 106 | ${executable_path} = Which geckodriver 107 | END 108 | RETURN ${executable_path} 109 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { IFontManager } from '@deathbeds/jupyterlab-fonts'; 2 | import { 3 | JupyterFrontEnd, 4 | JupyterFrontEndPlugin, 5 | ILabShell, 6 | ILayoutRestorer, 7 | } from '@jupyterlab/application'; 8 | import { ICommandPalette } from '@jupyterlab/apputils'; 9 | import { IDocumentManager } from '@jupyterlab/docmanager'; 10 | import { INotebookTools } from '@jupyterlab/notebook'; 11 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 12 | import { IStatusBar, StatusBar } from '@jupyterlab/statusbar'; 13 | import { ITranslator, nullTranslator } from '@jupyterlab/translation'; 14 | import { Widget } from '@lumino/widgets'; 15 | 16 | import { DeckManager } from './manager'; 17 | import { EditorDeckExtension } from './markdown/extension'; 18 | import { SimpleMarkdownPresenter } from './markdown/presenter'; 19 | import { NotebookDeckExtension } from './notebook/extension'; 20 | import { NotebookPresenter } from './notebook/presenter'; 21 | import { 22 | NS, 23 | IDeckManager, 24 | CommandIds, 25 | CATEGORY, 26 | PLUGIN_ID, 27 | NOTEBOOK_FACTORY, 28 | } from './tokens'; 29 | 30 | import '../style/index.css'; 31 | 32 | const plugin: JupyterFrontEndPlugin = { 33 | id: `${NS}:plugin`, 34 | requires: [ITranslator, ISettingRegistry, IFontManager], 35 | optional: [ILabShell, ILayoutRestorer, ICommandPalette, IStatusBar], 36 | provides: IDeckManager, 37 | autoStart: true, 38 | activate: ( 39 | app: JupyterFrontEnd, 40 | translator: ITranslator, 41 | settings: ISettingRegistry, 42 | fonts: IFontManager, 43 | labShell?: ILabShell, 44 | restorer?: ILayoutRestorer, 45 | palette?: ICommandPalette, 46 | statusbar?: IStatusBar, 47 | ) => { 48 | const { commands, shell } = app; 49 | 50 | const theStatusBar = 51 | statusbar instanceof StatusBar ? statusbar : /* istanbul ignore next */ null; 52 | 53 | const manager = new DeckManager({ 54 | commands, 55 | shell, 56 | labShell: labShell || null, 57 | translator: (translator || /* istanbul ignore next */ nullTranslator).load(NS), 58 | statusbar: theStatusBar, 59 | fonts, 60 | settings: settings.load(PLUGIN_ID), 61 | appStarted: async () => 62 | await Promise.all([app.started, ...(restorer ? [restorer.restored] : [])]), 63 | }); 64 | 65 | const { __ } = manager; 66 | 67 | let category = __(CATEGORY); 68 | 69 | if (palette) { 70 | palette.addItem({ command: CommandIds.start, category }); 71 | palette.addItem({ command: CommandIds.stop, category }); 72 | palette.addItem({ command: CommandIds.showLayover, category }); 73 | palette.addItem({ command: CommandIds.hideLayover, category }); 74 | } 75 | 76 | return manager; 77 | }, 78 | }; 79 | 80 | const notebookPlugin: JupyterFrontEndPlugin = { 81 | id: `${NS}:notebooks`, 82 | requires: [IDeckManager], 83 | optional: [INotebookTools], 84 | autoStart: true, 85 | activate: ( 86 | app: JupyterFrontEnd, 87 | decks: IDeckManager, 88 | notebookTools?: INotebookTools, 89 | ) => { 90 | if (!notebookTools) { 91 | return; 92 | } 93 | const { commands } = app; 94 | const presenter = new NotebookPresenter({ 95 | manager: decks, 96 | notebookTools, 97 | commands, 98 | }); 99 | decks.addPresenter(presenter); 100 | 101 | app.docRegistry.addWidgetExtension( 102 | NOTEBOOK_FACTORY, 103 | new NotebookDeckExtension({ commands, presenter }), 104 | ); 105 | }, 106 | }; 107 | 108 | const simpleMarkdownPlugin: JupyterFrontEndPlugin = { 109 | id: `${NS}:simple-markdown`, 110 | requires: [IDeckManager, IDocumentManager], 111 | optional: [ILabShell], 112 | autoStart: true, 113 | activate: ( 114 | app: JupyterFrontEnd, 115 | decks: IDeckManager, 116 | docManager: IDocumentManager, 117 | labShell?: ILabShell, 118 | ) => { 119 | const { commands } = app; 120 | const presenter = new SimpleMarkdownPresenter({ 121 | manager: decks, 122 | commands, 123 | docManager, 124 | activateWidget: (widget: Widget) => labShell?.activateById(widget.node.id), 125 | }); 126 | decks.addPresenter(presenter); 127 | 128 | app.docRegistry.addWidgetExtension('Editor', new EditorDeckExtension({ commands })); 129 | }, 130 | }; 131 | 132 | export default [plugin, notebookPlugin, simpleMarkdownPlugin]; 133 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/tools/remote.tsx: -------------------------------------------------------------------------------- 1 | import { VDomRenderer, VDomModel } from '@jupyterlab/apputils'; 2 | import { PathExt } from '@jupyterlab/coreutils'; 3 | import { LabIcon } from '@jupyterlab/ui-components'; 4 | import { JSONExt } from '@lumino/coreutils'; 5 | import type { Widget } from '@lumino/widgets'; 6 | import React from 'react'; 7 | 8 | import { ICONS } from '../icons'; 9 | import { 10 | CSS, 11 | IDeckManager, 12 | DIRECTION, 13 | DIRECTION_LABEL, 14 | TCanGoDirection, 15 | IPresenter, 16 | } from '../tokens'; 17 | 18 | export class DeckRemote extends VDomRenderer { 19 | constructor(options: DeckRemote.IOptions) { 20 | super(new DeckRemote.Model(options)); 21 | this.addClass(CSS.remote); 22 | document.body.appendChild(this.node); 23 | } 24 | 25 | dispose() { 26 | this.model.dispose(); 27 | super.dispose(); 28 | document.body.removeChild(this.node); 29 | } 30 | 31 | protected render(): JSX.Element { 32 | const { manager, canGo } = this.model; 33 | const { __ } = manager; 34 | 35 | const directions: Record = {}; 36 | 37 | for (const direction of Object.values(DIRECTION)) { 38 | const enabled = !!canGo[direction]; 39 | directions[direction] = this.makeButton( 40 | enabled ? ICONS.goEnabled : ICONS.goDisabled, 41 | DIRECTION_LABEL[direction], 42 | enabled ? () => manager.go(direction) : () => null, 43 | `${CSS.direction}-${direction} ${enabled ? '' : CSS.disabled}`, 44 | ); 45 | } 46 | 47 | const exit = this.makeButton( 48 | ICONS.deckStop, 49 | __('Exit Deck'), 50 | () => void this.model.manager.stop(), 51 | CSS.stop, 52 | ); 53 | 54 | return ( 55 |
56 | {this.makeStack()} 57 | {directions.up} 58 |
59 | {directions.back} 60 | {exit} 61 | {directions.forward} 62 |
63 | {directions.down} 64 |
65 | ); 66 | } 67 | 68 | makeStack(): JSX.Element { 69 | let { manager } = this.model; 70 | if (!manager.activeWidgetStack.length) { 71 | return <>; 72 | } 73 | let stack: JSX.Element[] = []; 74 | for (const widget of manager.activeWidgetStack) { 75 | let icon = widget.title.icon as LabIcon; 76 | let label = PathExt.basename(widget.title.label); 77 | stack.push( 78 |
  • 79 | 83 |
  • , 84 | ); 85 | } 86 | return
      {stack}
    ; 87 | } 88 | 89 | makeButton( 90 | icon: LabIcon, 91 | title: string, 92 | onClick: () => void, 93 | className: string = '', 94 | ) { 95 | return ( 96 | 103 | ); 104 | } 105 | } 106 | 107 | export namespace DeckRemote { 108 | export class Model extends VDomModel { 109 | private _manager: IDeckManager; 110 | private _canGo: Partial = {}; 111 | private _activePresenter: IPresenter | null = null; 112 | 113 | constructor(options: IOptions) { 114 | super(); 115 | this._manager = options.manager; 116 | this._manager.activeChanged.connect(this._onActiveChanged, this); 117 | } 118 | 119 | dispose() { 120 | this._manager.activeChanged.disconnect(this._onActiveChanged, this); 121 | super.dispose(); 122 | } 123 | 124 | get manager(): IDeckManager { 125 | return this._manager; 126 | } 127 | 128 | get canGo(): Partial { 129 | return this._canGo; 130 | } 131 | 132 | private async _onActiveChanged() { 133 | const canGo = await this._manager.canGo(); 134 | let emit = false; 135 | if (!JSONExt.deepEqual(canGo, this._canGo)) { 136 | this._canGo = canGo; 137 | emit = true; 138 | } 139 | let { activePresenter } = this._manager; 140 | if (activePresenter !== this._activePresenter) { 141 | this._activePresenter = activePresenter; 142 | emit = true; 143 | } 144 | if (emit) { 145 | this.emit(); 146 | } 147 | } 148 | 149 | private emit = () => { 150 | this.stateChanged.emit(void 0); 151 | }; 152 | } 153 | export interface IOptions { 154 | manager: IDeckManager; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@deathbeds/jupyterlab-deck-repo", 4 | "version": "0.0.0", 5 | "author": "jupyterlab-deck contributors", 6 | "homepage": "https://github.com/deathbeds/jupyterlab-deck", 7 | "workspaces": { 8 | "packages": [ 9 | "js/*" 10 | ] 11 | }, 12 | "devDependencies": { 13 | "@ephesoft/webpack.istanbul.loader": "^2.2.0", 14 | "@istanbuljs/nyc-config-typescript": "^1.0.2", 15 | "@typescript-eslint/eslint-plugin": "^6.7.3", 16 | "@typescript-eslint/parser": "^6.7.3", 17 | "eslint": "^8.50.0", 18 | "eslint-config-prettier": "^9.0.0", 19 | "eslint-plugin-import": "^2.28.1", 20 | "eslint-plugin-prettier": "^5.0.0", 21 | "eslint-plugin-react": "^7.33.2", 22 | "lerna": "^7.3.0", 23 | "nyc": "^15.1.0", 24 | "prettier": "^3.0.3", 25 | "prettier-package-json": "^2.8.0", 26 | "prettier-plugin-sort-json": "^3.1.0", 27 | "source-map-loader": "^4.0.1", 28 | "stylelint": "^15.10.3", 29 | "stylelint-config-recommended": "^13.0.0", 30 | "stylelint-config-standard": "^34.0.0", 31 | "stylelint-prettier": "^4.0.2", 32 | "ts-loader": "^9.4.4", 33 | "ts-node": "^10.9.1", 34 | "typescript": "~5.2.2", 35 | "yarn-berry-deduplicate": "^6.1.1" 36 | }, 37 | "eslintConfig": { 38 | "env": { 39 | "browser": true, 40 | "es6": true, 41 | "commonjs": true, 42 | "node": true 43 | }, 44 | "globals": { 45 | "JSX": "readonly" 46 | }, 47 | "root": true, 48 | "extends": [ 49 | "eslint:recommended", 50 | "plugin:import/errors", 51 | "plugin:import/warnings", 52 | "plugin:import/typescript", 53 | "plugin:@typescript-eslint/eslint-recommended", 54 | "plugin:@typescript-eslint/recommended", 55 | "prettier", 56 | "plugin:react/recommended" 57 | ], 58 | "ignorePatterns": [ 59 | "**/node_modules/**/*", 60 | "**/lib/**/*", 61 | "**/_*.ts", 62 | "**/_*.d.ts", 63 | "**/typings/**/*.d.ts", 64 | "**/dist/*" 65 | ], 66 | "parser": "@typescript-eslint/parser", 67 | "parserOptions": { 68 | "project": "js/tsconfig.eslint.json" 69 | }, 70 | "plugins": [ 71 | "@typescript-eslint", 72 | "import" 73 | ], 74 | "rules": { 75 | "@typescript-eslint/camelcase": "off", 76 | "@typescript-eslint/explicit-function-return-type": "off", 77 | "@typescript-eslint/explicit-module-boundary-types": "off", 78 | "@typescript-eslint/no-empty-interface": "off", 79 | "@typescript-eslint/no-explicit-any": "off", 80 | "@typescript-eslint/no-floating-promises": [ 81 | "error", 82 | { 83 | "ignoreVoid": true 84 | } 85 | ], 86 | "@typescript-eslint/no-inferrable-types": "off", 87 | "@typescript-eslint/no-namespace": "off", 88 | "@typescript-eslint/no-non-null-assertion": "off", 89 | "@typescript-eslint/no-unused-vars": [ 90 | "warn", 91 | { 92 | "args": "none" 93 | } 94 | ], 95 | "@typescript-eslint/no-use-before-define": "off", 96 | "@typescript-eslint/no-var-requires": "off", 97 | "no-case-declarations": "warn", 98 | "no-control-regex": "warn", 99 | "no-inner-declarations": "off", 100 | "no-prototype-builtins": "off", 101 | "no-undef": "warn", 102 | "no-useless-escape": "off", 103 | "prefer-const": "off", 104 | "import/no-unresolved": "off", 105 | "import/order": [ 106 | "warn", 107 | { 108 | "groups": [ 109 | "builtin", 110 | "external", 111 | "parent", 112 | "sibling", 113 | "index", 114 | "object", 115 | "unknown" 116 | ], 117 | "pathGroups": [ 118 | { 119 | "pattern": "react/**", 120 | "group": "builtin", 121 | "position": "after" 122 | }, 123 | { 124 | "pattern": "codemirror/**", 125 | "group": "external", 126 | "position": "before" 127 | }, 128 | { 129 | "pattern": "@lumino/**", 130 | "group": "builtin", 131 | "position": "before" 132 | }, 133 | { 134 | "pattern": "@jupyterlab/**", 135 | "group": "external", 136 | "position": "after" 137 | } 138 | ], 139 | "newlines-between": "always", 140 | "alphabetize": { 141 | "order": "asc" 142 | } 143 | } 144 | ], 145 | "import/no-cycle": "off", 146 | "import/export": "off", 147 | "@typescript-eslint/triple-slash-reference": "off", 148 | "no-async-promise-executor": "off", 149 | "prefer-spread": "off", 150 | "react/display-name": "off" 151 | }, 152 | "settings": { 153 | "react": { 154 | "version": "detect" 155 | } 156 | } 157 | }, 158 | "nyc": { 159 | "extends": "@istanbuljs/nyc-config-typescript", 160 | "all": true, 161 | "skip-full": true, 162 | "require": [ 163 | "ts-node/register", 164 | "source-map-support/register" 165 | ], 166 | "reporter": [ 167 | "lcov", 168 | "html", 169 | "text", 170 | "text-summary" 171 | ], 172 | "extension": [ 173 | ".js", 174 | ".jsx", 175 | ".ts", 176 | ".tsx" 177 | ] 178 | }, 179 | "prettier": { 180 | "singleQuote": true, 181 | "proseWrap": "always", 182 | "printWidth": 88 183 | }, 184 | "stylelint": { 185 | "extends": [ 186 | "stylelint-config-recommended", 187 | "stylelint-config-standard", 188 | "stylelint-prettier/recommended" 189 | ], 190 | "rules": { 191 | "property-no-vendor-prefix": null, 192 | "selector-no-vendor-prefix": null, 193 | "value-no-vendor-prefix": null, 194 | "alpha-value-notation": null, 195 | "color-function-notation": null, 196 | "selector-class-pattern": null 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /atest/resources/Design.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with decks. 3 | 4 | Resource ./LabSelectors.resource 5 | Resource ./DeckSelectors.resource 6 | Resource ./Lab.resource 7 | 8 | 9 | *** Variables *** 10 | @{PART_HANDLES} nw n ne w e sw s se 11 | @{SLIDE_TYPES} slide subslide null fragment 12 | @{LAYER_SCOPES} deck stack slide fragment null 13 | @{SLIDERS} z-index zoom opacity 14 | 15 | 16 | *** Keywords *** 17 | Maybe Open Design Tools 18 | [Documentation] Ensure the design tools are open. 19 | ${sel} = Set Variable css:${CSS_DECK_DESIGN_TOOLS} ${CSS_LAB_ICON_ELLIPSES} 20 | ${el} = Get WebElements ${sel} 21 | IF ${el.__len__()} Click Element ${sel} 22 | Wait Until Element Is Visible css:${CSS_DECK_DESIGN_TOOLS} ${CSS_LAB_ICON_CARET_LEFT} 23 | 24 | Maybe Open Slide Layout 25 | [Documentation] Ensure the slide layout overlay is open. 26 | Maybe Open Design Tools 27 | ${sel} = Set Variable css:${CSS_DECK_DESIGN_TOOLS} ${CSS_DECK_ICON_LAYOVER_START} 28 | ${el} = Get WebElements ${sel} 29 | IF ${el.__len__()} Click Element ${sel} 30 | Wait Until Element Is Visible css:${CSS_DECK_LAYOVER_PART} 31 | 32 | Maybe Close Slide Layout 33 | [Documentation] Ensure the slide layout overlay is closed. 34 | ${sel} = Set Variable css:${CSS_DECK_DESIGN_TOOLS} ${CSS_DECK_ICON_LAYOVER_STOP} 35 | ${el} = Get WebElements ${sel} 36 | IF ${el.__len__()} Click Element ${sel} 37 | Wait Until Element Is Not Visible css:${CSS_DECK_LAYOVER_PART} 38 | 39 | Maybe Close Design Tools 40 | [Documentation] Ensure the design tools are closed. 41 | Maybe Close Slide Layout 42 | ${sel} = Set Variable css:${CSS_DECK_DESIGN_TOOLS} ${CSS_LAB_ICON_CARET_LEFT} 43 | ${el} = Get WebElements ${sel} 44 | IF ${el.__len__()} Click Element ${sel} 45 | Wait Until Element Is Visible css:${CSS_DECK_DESIGN_TOOLS} ${CSS_LAB_ICON_ELLIPSES} 46 | 47 | Move A Part 48 | [Documentation] Move a part by dragging and dropping 49 | [Arguments] ${index} ${x} ${y} ${screenshot}=${EMPTY} 50 | ${sel} = Set Variable ${CSS_DECK_LAYOVER_PART}:nth-child(${index}) 51 | Drag And Drop By Offset css:${sel} ${x} ${y} 52 | Sleep 0.1s 53 | IF not ${screenshot.__len__()} RETURN 54 | Capture Page Screenshot ${screenshot} 55 | 56 | Resize A Part 57 | [Documentation] Resize a part by dragging and dropping the handle 58 | [Arguments] ${index} ${handle} ${x} ${y} ${screenshot}=${EMPTY} 59 | ${sel} = Set Variable ${CSS_DECK_LAYOVER_PART}:nth-child(${index}) ${CSS_DECK_PART_HANDLE}-${handle} 60 | Drag And Drop By Offset css:${sel} ${x} ${y} 61 | Sleep 0.1s 62 | IF not ${screenshot.__len__()} RETURN 63 | Capture Page Screenshot ${screenshot} 64 | 65 | Unfix A Part 66 | [Documentation] Use the reset button to unfix a part. 67 | [Arguments] ${index} ${screenshot}=${EMPTY} 68 | ${part} = Set Variable ${CSS_DECK_LAYOVER_PART}:nth-child(${index}) 69 | ${unstyle} = Set Variable ${part} ${CSS_DECK_LAYOVER_UNSTYLE} 70 | Mouse Over css:${part} 71 | Click Element css:${unstyle} 72 | Wait Until Page Does Not Contain Element css:${unstyle} 73 | IF not ${screenshot.__len__()} RETURN 74 | Capture Page Screenshot ${screenshot} 75 | 76 | Select A Slide Type 77 | [Documentation] Make a cell a slide type with the design tools. 78 | [Arguments] ${index} ${type} ${screenshot}=${EMPTY} 79 | Select From Selector Tool ${index} ${type} ${screenshot} 80 | ... ${CSS_DECK_MOD_SLIDE_TYPE} ${CSS_DECK_ICON_SLIDE_FMT} 81 | 82 | Select A Layer Scope 83 | [Documentation] Make a cell a layer with the design tools. 84 | [Arguments] ${index} ${scope} ${screenshot}=${EMPTY} 85 | Select From Selector Tool ${index} ${scope} ${screenshot} 86 | ... ${CSS_DECK_MOD_LAYER_SCOPE} ${CSS_DECK_ICON_LAYER_FMT} 87 | 88 | Select From Selector Tool 89 | [Documentation] Select an option from a selector tool. 90 | [Arguments] ${index} ${kind} ${screenshot} ${tool} ${fmt} 91 | Maybe Open Design Tools 92 | Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) 93 | Really Mouse Over css:${tool} 94 | ${icon} = Set Variable ${fmt.format("${kind}")} 95 | Click Element css:${tool} ${icon} 96 | Mouse Over css:${CSS_DECK_DESIGN_TOOLS} ${CSS_LAB_ICON_CARET_LEFT} 97 | Wait Until Element Is Visible css:${tool} ${CSS_LAB_MOD_ACTIVE} ${icon} 98 | IF not ${screenshot.__len__()} RETURN 99 | Capture Page Screenshot ${screenshot} 100 | 101 | Really Mouse Over 102 | [Documentation] Mouse over something twice, waiting for it to react. 103 | [Arguments] ${selector} 104 | Mouse Over ${selector} 105 | Sleep 0.1s 106 | Mouse Over ${selector} 107 | 108 | Configure A Style With Slider 109 | [Documentation] Use a design tool slider to configure a part 110 | [Arguments] ${index} ${attr} ${screenshot} ${inverse}=${FALSE} 111 | ${tool} = Set Variable ${CSS_DECK_SLIDER_FMT.format("${attr}")} 112 | Really Mouse Over css:${tool} 113 | IF ${inverse} 114 | Click Element css:${tool} input\[type\="checkbox"] 115 | Wait Until Page Does Not Contain Element css:${tool}.jp-mod-active 116 | ELSE 117 | Click Element css:${tool} input\[type\="range"] 118 | Wait Until Page Contains Element css:${tool}.jp-mod-active 119 | END 120 | IF not ${screenshot.__len__()} RETURN 121 | Capture Page Screenshot ${screenshot} 122 | 123 | Unconfigure A Style With Slider 124 | [Documentation] Use a design tool slider to unconfigure a part 125 | [Arguments] ${index} ${attr} ${screenshot} 126 | Configure A Style With Slider ${index} ${attr} ${screenshot} ${TRUE} 127 | 128 | Reset Design Tools Test 129 | [Documentation] Reset the test, ensuring the design tools are closed first. 130 | Maybe Close Design Tools 131 | Reset Interactive Test 132 | -------------------------------------------------------------------------------- /atest/resources/Lab.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with the Lab shell. 3 | 4 | Resource ./CodeMirror.resource 5 | Resource ./LabSelectors.resource 6 | Resource ./Screenshots.resource 7 | Library Collections 8 | Library JupyterLibrary 9 | 10 | 11 | *** Keywords *** 12 | Initialize JupyterLab 13 | [Documentation] Get the web app set up for testing. 14 | ${executable_path} = Get GeckoDriver Executable Path 15 | Open JupyterLab 16 | ... executable_path=${executable_path} 17 | ... service_log_path=${OUTPUT_DIR}${/}geckodriver.log # this doesn't work yet 18 | Initialize CodeMirror 19 | Set Window Size 1366 768 20 | Reload Page 21 | Wait For JupyterLab Splash Screen 22 | 23 | Plugins Should Be Disabled 24 | [Documentation] Check that some JupyterLab extensions are disabled by config 25 | [Arguments] @{plugins} 26 | ${disabled} = Get JupyterLab Page Info disabledExtensions 27 | FOR ${plugin} IN @{plugins} 28 | Should Contain ${disabled} ${plugin} msg=${plugin} was not disabled 29 | END 30 | 31 | Add And Activate Cell With Keyboard 32 | [Documentation] Add a cell with the keyboard. 33 | ${index} = Get Active Cell Index 34 | Enter Command Mode 35 | Press Keys css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) b 36 | Wait Until Cell Is Not Active ${index} 1s 37 | Sleep 0.2s 38 | 39 | Enter Command Mode 40 | [Documentation] Activate command mode. 41 | Press Keys css:body ESCAPE 42 | Wait Until Page Does Not Contain Element css:${CSS_LAB_MOD_EDIT} timeout=1s 43 | Wait Until Page Contains Element css:${CSS_LAB_MOD_CMD} timeout=1s 44 | Press Keys css:body ESCAPE 45 | Sleep 0.2s 46 | 47 | Set Cell Type 48 | [Documentation] Use the command to change the cell type. 49 | [Arguments] ${index} ${type} ${timeout}=5s 50 | Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) 51 | Mouse Over css:${CSS_LAB_NB_TOOLBAR} 52 | Wait Until Element Is Visible css:${CSS_LAB_NB_TOOLBAR_CELLTYPE} 53 | Select From List By Label css:${CSS_LAB_NB_TOOLBAR_CELLTYPE} ${type.title()} 54 | Click Element css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) 55 | ${sel} = Get From Dictionary ${CSS_LAB_CELL_TYPE} ${type} 56 | Wait Until Page Contains Element 57 | ... css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index})${sel} 58 | ... timeout=${timeout} 59 | 60 | Make Markdown Cell 61 | [Documentation] Turn the current cell into markdown. 62 | [Arguments] ${code} ${expect}=${EMPTY} ${new}=${TRUE} ${screenshot}=${EMPTY} 63 | Initialize CodeMirror 64 | ${index} = Get Active Cell Index 65 | IF ${new} 66 | Add And Activate Cell With Keyboard 67 | ${index} = Set Variable ${index.__add__(1)} 68 | END 69 | ${cm} = Set Variable ${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) ${CM CSS EDITOR} 70 | Set CodeMirror Value ${cm} ${code} 71 | Set Cell Type ${index} markdown 72 | IF ${expect.__len__()} Render Markdown Cell ${index} ${expect} 73 | IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} 74 | 75 | Render Markdown Cell 76 | [Documentation] Render the current cell. 77 | [Arguments] ${index} ${expect}=${EMPTY} 78 | ${sel} = Set Variable css:${JLAB CSS ACTIVE DOC CELLS}:nth-child(${index}) 79 | Click Element ${sel} 80 | ${accel} = Get ACCEL Key 81 | Press Keys ${sel} ${accel}+ENTER 82 | IF ${expect.__len__()} 83 | Wait Until Element Contains ${sel}${CSS_LAB_MOD_RENDERED} ${expect} timeout=1s 84 | ELSE 85 | Wait Until Page Contains Element ${sel}${CSS_LAB_MOD_RENDERED} timeout=1s 86 | END 87 | 88 | Get Active Cell Index 89 | [Documentation] Get the 1-indexed position of the active cell. 90 | ${cells} = Get WebElements css:${JLAB CSS ACTIVE DOC CELLS} 91 | ${active} = Get WebElement css:${JLAB CSS ACTIVE CELL} 92 | ${index} = Get Index From List ${cells} ${active} 93 | RETURN ${index.__add__(1)} 94 | 95 | Wait Until Cell Is Not Active 96 | [Documentation] Wait until the given cell is not active. 97 | [Arguments] ${index} ${timeout}=1s 98 | Wait Until Page Does Not Contain Element css:${JLAB CSS ACTIVE CELL}:nth-child(${index}) timeout=${timeout} 99 | 100 | Maybe Open Cell Metadata JSON 101 | [Documentation] Ensure the Cell Metadata viewer is open. 102 | 103 | ${el} = Get WebElements ${CSS_LAB_CELL_META_JSON} ${CM CSS EDITOR} 104 | 105 | IF not ${el.__len__()} RETURN 106 | 107 | Click Element ${CSS_LAB_ADVANCED_COLLAPSE} 108 | Wait Until Page Does Not Contain Element ${CSS_LAB_CELL_META_JSON_HIDDEN} ${CM CSS EDITOR} 109 | 110 | Wait Until Cell Metadata Contains 111 | [Documentation] Ensure a string appears in the Cell Metadata JSON 112 | [Arguments] ${text} ${attempts}=5 ${timeout}=0.1s ${inverse}=${FALSE} 113 | 114 | ${ok} = Set Variable ${FALSE} 115 | FOR ${i} IN RANGE ${attempts} 116 | ${src} = Return CodeMirror Method ${CSS_LAB_CELL_META_JSON} ${CM CSS EDITOR} ${CM JS TO STRING} 117 | ${contains} = Set Variable ${src.__contains__('''${text}''')} 118 | IF ${inverse} and not ${contains} 119 | ${ok} = Set Variable ${TRUE} 120 | BREAK 121 | ELSE IF not ${inverse} and ${contains} 122 | ${ok} = Set Variable ${TRUE} 123 | BREAK 124 | END 125 | Sleep ${timeout} 126 | END 127 | 128 | IF not ${ok} Capture Page Screenshot 129 | 130 | Should Be True ${ok} 131 | 132 | Wait Until Cell Metadata Does Not Contain 133 | [Documentation] Ensure a string does _not_ appear in the Cell Metadata JSON 134 | [Arguments] ${text} ${attempts}=5 ${timeout}=0.1s 135 | Wait Until Cell Metadata Contains text=${text} attempts=${attempts} timeout=${timeout} inverse=${TRUE} 136 | 137 | Maybe Expand Panel With Title 138 | [Documentation] Ensure a collapsed panel in a sidebar is expanded 139 | [Arguments] ${label} 140 | ${els} = Get WebElements 141 | ... xpath:${XP_LAB4_COLLAPSED_PANEL_TITLE}\[contains(., '${label}')] 142 | IF not ${els.__len__()} RETURN 143 | Click Element ${els[0]} 144 | -------------------------------------------------------------------------------- /atest/resources/Deck.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Keywords for working with decks. 3 | 4 | Resource ./LabSelectors.resource 5 | Resource ./DeckSelectors.resource 6 | Resource ./Lab.resource 7 | 8 | 9 | *** Variables *** 10 | ${ZERO_PAD} {0:03d} 11 | 12 | 13 | *** Keywords *** 14 | Start Deck With Toolbar Button 15 | [Documentation] Use the toolbar to start deck. 16 | Wait Until Element Is Not Visible css:${CSS_LAB_SPINNER} timeout=1s 17 | Click Element css:${JLAB CSS ACTIVE DOC} 18 | Click Element css:${CSS_DECK_TOOLBAR_BUTTON} 19 | Wait Until Element Is Visible css:${CSS_DECK_PRESENTING} timeout=1s 20 | Wait Until Element Is Visible css:${CSS_DECK_REMOTE} timeout=1s 21 | 22 | Really Start Deck With Toolbar Button 23 | [Documentation] REALLY use the toolbar to start deck. 24 | Send Error Screenshots To Trash 25 | Wait Until Keyword Succeeds 5x 0.1s Start Deck With Toolbar Button 26 | [Teardown] Resume Screenshots 27 | 28 | Select From Context Menu 29 | [Documentation] Open the context menu and click an item. 30 | [Arguments] ${host} ${item} 31 | Open Context Menu css:${host} 32 | Wait Until Element Is Visible css:${item} 33 | Mouse Over css:${item} ${CSS_LM_MENU_ITEM_LABEL} 34 | Wait Until Element Is Visible css:${item}${CSS_LM_MOD_ACTIVE} 35 | Click Element css:${item}${CSS_LM_MOD_ACTIVE} ${CSS_LM_MENU_ITEM_LABEL} 36 | Wait Until Element Is Not Visible css:${item} 37 | 38 | Stop Deck With Remote 39 | [Documentation] Use the on-screen remote to stop deck. 40 | Wait Until Element Is Visible css:${CSS_DECK_STOP} 41 | Click Element css:${CSS_DECK_STOP} 42 | Wait Until Element Is Not Visible css:${CSS_DECK_PRESENTING} 43 | 44 | Visit Slides And Fragments With Remote 45 | [Documentation] Walk through all slides and fragments. 46 | [Arguments] ${host} ${prefix} ${directions}=${CSS_DECK_NEXT} ${limit}=${100} 47 | ${keep_going} = Set Variable ${TRUE} 48 | ${i} = Set Variable ${0} 49 | WHILE ${keep_going} limit=${limit} 50 | ${i} = Set Variable ${i.__add__(1)} 51 | ${keep_going} = Set Variable ${FALSE} 52 | FOR ${direction} IN @{directions} 53 | ${els} = Get WebElements css:${CSS_DECK_DIR_STEM}-${direction}:not(${CSS_LAB_MOD_DISABLED}) 54 | IF ${els.__len__()} 55 | ${keep_going} = Advance Deck With Remote And Screenshot 56 | ... ${host} ${els[0]} ${prefix} ${i} ${direction} 57 | BREAK 58 | END 59 | END 60 | END 61 | Capture Page Screenshot ${prefix}-${ZERO_PAD.format(${i.__add__(1)})}-FIN.png 62 | 63 | Advance Deck With Remote And Screenshot 64 | [Documentation] Advance a direction, wait a bit, and take a screenshot. 65 | [Arguments] ${host} ${element} ${prefix} ${i} ${suffix} 66 | Click Element ${element} 67 | Sleep 0.1s 68 | Capture Page Screenshot ${prefix}-${ZERO_PAD.format(${i})}-${suffix}.png 69 | Maybe Click An Anchor And Return ${host} ${prefix}-${ZERO_PAD.format(${i})}-${suffix}-anchor.png 70 | RETURN ${TRUE} 71 | 72 | Maybe Click An Anchor And Return 73 | [Documentation] If a `#` anchor exists, click it, and come back 74 | [Arguments] ${host} ${screenshot}=${EMPTY} 75 | ${anchor_links} = Find Cross-Document Anchor Links ${host} 76 | IF not ${anchor_links.__len__()} RETURN 77 | ${href} = Get Element Attribute ${anchor_links[0]} href 78 | IF ${href.__contains__('''${host}''')} RETURN 79 | Click Element ${anchor_links[0]} 80 | Sleep 0.1s 81 | IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} 82 | Click Element css:${CSS_DECK_DIR_STACK} button 83 | Sleep 0.1s 84 | 85 | Find Cross-Document Anchor Links 86 | [Documentation] Find anchor links that don't reference their host. 87 | [Arguments] ${host} 88 | ${anchor_links} = Create List 89 | IF ${host.__contains__(".ipynb")} 90 | ${anchor_links} = Get WebElements 91 | ... css:${JLAB CSS ACTIVE CELL}${CSS_DECK_VISIBLE} ${CSS_LAB_NOT_INTERNAL_ANCHOR} 92 | ELSE IF ${host.__contains__(".md")} 93 | ${anchor_links} = Get WebElements 94 | ... css:${CSS_LAB_MARKDOWN_VIEWER} ${CSS_LAB_NOT_INTERNAL_ANCHOR}:not([href^\='#']) 95 | END 96 | ${anchor_links} = Filter Visible Elements ${anchor_links} 97 | RETURN ${anchor_links} 98 | 99 | Filter Visible Elements 100 | [Documentation] Filter a list down to just the visible ones. 101 | [Arguments] ${elements} 102 | ${visible} = Create List 103 | Send Error Screenshots To Trash 104 | FOR ${el} IN @{elements} 105 | TRY 106 | Element Should Be Visible ${el} 107 | Append To List ${visible} ${el} 108 | EXCEPT 109 | Log ${el} is not visible 110 | END 111 | END 112 | RETURN ${visible} 113 | [Teardown] Resume Screenshots 114 | 115 | Advance Notebook Deck With Keyboard 116 | [Documentation] Go to the down/forward slide with space, wait a bit, then screenshot. 117 | [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} ${backup}=${FALSE} 118 | ${index} = Get Active Cell Index 119 | Send Error Screenshots To Trash 120 | IF ${backup} 121 | Press Keys css:body SHIFT+SPACE 122 | ELSE 123 | Press Keys css:body SPACE 124 | END 125 | Wait Until Cell Is Not Active ${index} 1s 126 | IF ${expect.__len__()} 127 | Wait Until Element Contains css:${JLAB CSS ACTIVE CELL} ${expect} 128 | END 129 | Resume Screenshots 130 | IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} 131 | [Teardown] Resume Screenshots 132 | 133 | Back Up Deck With Keyboard 134 | [Documentation] Go to the up/back slide with space, wait a bit, then screenshot. 135 | [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} 136 | Advance Notebook Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} 137 | 138 | Really Advance Notebook Deck With Keyboard 139 | [Documentation] REALLY go to the down/forward slide with space, wait a bit, then screenshot. 140 | [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} ${backup}=${FALSE} 141 | Wait Until Keyword Succeeds 5x 0.5s 142 | ... Advance Notebook Deck With Keyboard ${screenshot} ${expect} ${backup} 143 | 144 | Really Back Up Deck With Keyboard 145 | [Documentation] REALLY go to the up/back slide with space, wait a bit, then screenshot. 146 | [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} 147 | Really Advance Notebook Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} 148 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | workflow_dispatch: 11 | 12 | env: 13 | PYTHONUNBUFFERED: '1' 14 | PIP_DISABLE_PIP_VERSION_CHECK: '1' 15 | CI: '1' 16 | 17 | # our stuff 18 | ROBOT_RETRIES: '3' 19 | CACHE_EPOCH: '6' 20 | DOIT_N_BUILD: '-n4' 21 | PABOT_PROCESSES: '3' 22 | 23 | jobs: 24 | build: 25 | name: build 26 | runs-on: ${{ matrix.os }}-latest 27 | strategy: 28 | matrix: 29 | os: [ubuntu] 30 | python-version: ['3.11'] 31 | defaults: 32 | run: 33 | shell: bash -l {0} 34 | env: 35 | BUILDING_IN_CI: '1' 36 | steps: 37 | - uses: actions/checkout@v4 38 | 39 | # configure builtin providers 40 | - name: setup (python) 41 | uses: actions/setup-python@v4 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | architecture: x64 45 | 46 | - name: setup (node) 47 | uses: actions/setup-node@v3 48 | with: 49 | node-version: '20' 50 | 51 | # restore caches 52 | - name: cache (pip) 53 | uses: actions/cache@v3 54 | with: 55 | path: ~/.cache/pip 56 | key: | 57 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-pip-build-${{ hashFiles('.github/requirements-build.txt') }} 58 | restore-keys: | 59 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-pip-build- 60 | 61 | - name: cache (node) 62 | uses: actions/cache@v3 63 | id: cache-node-modules 64 | with: 65 | path: '**/node_modules' 66 | key: | 67 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-node-build-${{ hashFiles('yarn.lock') }} 68 | 69 | - name: cache (yarn) 70 | uses: actions/cache@v3 71 | if: steps.cache-node-modules.outputs.cache-hit != 'true' 72 | id: cache-yarn-packages 73 | with: 74 | path: .yarn-packages 75 | key: | 76 | ${{ env.CACHE_EPOCH }}-yarn-${{ runner.os }}-${{ hashFiles('yarn.lock') }} 77 | restore-keys: | 78 | ${{ env.CACHE_EPOCH }}-yarn-${{ runner.os }}- 79 | ${{ env.CACHE_EPOCH }}-yarn- 80 | 81 | - name: setup (pip) 82 | run: pip install -U pip wheel setuptools 83 | 84 | - name: setup (pip build) 85 | run: pip install -U -v -r .github/requirements-build.txt 86 | 87 | - name: check (pip) 88 | run: | 89 | set -eux 90 | mkdir -p build/pip 91 | pip freeze | tee build/pip/dist.pip.freeze 92 | pip check 93 | 94 | - name: list 95 | run: doit list --all --status 96 | 97 | - name: build 98 | run: doit ${{ matrix.DOIT_N_BUILD }} dist || doit dist 99 | 100 | - name: status 101 | run: doit list --all --status | sort 102 | if: always() 103 | 104 | - name: Upload (dist) 105 | uses: actions/upload-artifact@v3 106 | with: 107 | name: jupyterlab-deck-dist-${{ github.run_number }} 108 | path: ./dist 109 | 110 | lint: 111 | runs-on: ${{ matrix.os }}-latest 112 | strategy: 113 | matrix: 114 | os: [ubuntu] 115 | python-version: ['3.11'] 116 | env: 117 | WITH_JS_COV: 1 118 | defaults: 119 | run: 120 | shell: bash -l {0} 121 | steps: 122 | - name: Checkout 123 | uses: actions/checkout@v4 124 | 125 | - name: cache (conda) 126 | uses: actions/cache@v3 127 | with: 128 | path: ~/conda_pkgs_dir 129 | key: | 130 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-lint-${{ matrix.python-version }}-${{ hashFiles('.binder/environment.yml') }} 131 | restore-keys: | 132 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-lint-${{ matrix.python-version }}- 133 | 134 | - name: Cache (node_modules) 135 | uses: actions/cache@v3 136 | id: cache-node-modules 137 | with: 138 | path: node_modules/ 139 | key: | 140 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }} 141 | 142 | - name: install (conda) 143 | uses: conda-incubator/setup-miniconda@v3 144 | with: 145 | environment-file: .binder/environment.yml 146 | miniforge-variant: Mambaforge 147 | use-mamba: true 148 | 149 | - name: lint 150 | run: doit lint 151 | 152 | - name: dist 153 | env: 154 | WITH_JS_COV: 0 155 | run: doit dist 156 | 157 | - name: build 158 | run: doit build 159 | 160 | - name: dev 161 | run: doit dev 162 | 163 | - name: test latest (with cov) 164 | run: doit test:robot 165 | 166 | - name: dev (legacy) 167 | run: doit legacy:pip 168 | 169 | - name: test legacy 170 | run: doit legacy:robot 171 | 172 | - name: report 173 | run: doit report 174 | 175 | - name: docs 176 | run: doit docs 177 | 178 | - name: check 179 | run: doit check 180 | 181 | - name: upload (reports) 182 | if: always() 183 | uses: actions/upload-artifact@v3 184 | with: 185 | name: jupyterlab-deck-lint-reports-${{ github.run_number }} 186 | path: ./build/reports 187 | 188 | - uses: codecov/codecov-action@v3 189 | with: 190 | directory: ./build/reports/nyc/ 191 | verbose: true 192 | flags: front-end 193 | 194 | test: 195 | needs: [build] 196 | name: ${{ matrix.os }} ${{ matrix.python-version }} 197 | runs-on: ${{ matrix.os }}-latest 198 | strategy: 199 | fail-fast: false 200 | matrix: 201 | os: ['ubuntu', 'macos', 'windows'] 202 | python-version: ['3.8', '3.11'] 203 | include: 204 | - python-version: '3.8' 205 | CI_ARTIFACT: 'sdist' 206 | - python-version: '3.11' 207 | CI_ARTIFACT: 'wheel' 208 | env: 209 | TESTING_IN_CI: '1' 210 | steps: 211 | - name: configure line endings 212 | run: | 213 | git config --global core.autocrlf false 214 | 215 | - name: checkout 216 | uses: actions/checkout@v4 217 | 218 | - name: cache (conda) 219 | uses: actions/cache@v3 220 | with: 221 | path: ~/conda_pkgs_dir 222 | key: | 223 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-test-${{ matrix.python-version }}-${{ hashFiles('.github/environment-test.yml') }} 224 | restore-keys: | 225 | ${{ env.CACHE_EPOCH }}-${{ runner.os }}-conda-test-${{ matrix.python-version }}- 226 | 227 | - name: install (conda) 228 | uses: conda-incubator/setup-miniconda@v3 229 | with: 230 | miniforge-variant: Mambaforge 231 | python-version: ${{ matrix.python-version }} 232 | environment-file: .github/environment-test.yml 233 | use-mamba: true 234 | 235 | - name: download (dist) 236 | uses: actions/download-artifact@v3 237 | with: 238 | name: jupyterlab-deck-dist-${{ github.run_number }} 239 | path: ./dist 240 | 241 | - name: dev (unix) 242 | if: matrix.os != 'windows' 243 | shell: bash -l {0} 244 | run: doit dev 245 | 246 | - name: dev (windows) 247 | if: matrix.os == 'windows' 248 | shell: cmd /C CALL {0} 249 | run: doit dev 250 | 251 | - name: test latest (unix) 252 | if: matrix.os != 'windows' 253 | shell: bash -l {0} 254 | run: doit test 255 | 256 | - name: test latest (windows) 257 | if: matrix.os == 'windows' 258 | shell: cmd /C CALL {0} 259 | run: doit test 260 | 261 | - name: upload (reports) 262 | if: always() 263 | uses: actions/upload-artifact@v3 264 | with: 265 | name: |- 266 | jupyterlab-deck-reports-${{ matrix.os }}-${{matrix.python-version }}-${{ github.run_number }} 267 | path: ./build/reports 268 | -------------------------------------------------------------------------------- /js/jupyterlab-deck/src/tools/layover.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalStyles } from '@deathbeds/jupyterlab-fonts/lib/_schema'; 2 | import { VDomModel } from '@jupyterlab/apputils'; 3 | import { JSONExt } from '@lumino/coreutils'; 4 | import { Widget } from '@lumino/widgets'; 5 | import { drag, D3DragEvent } from 'd3-drag'; 6 | import * as d3 from 'd3-selection'; 7 | 8 | import { IDeckManager, CSS, DATA } from '../tokens'; 9 | 10 | /** An interactive layer positioner. */ 11 | export class Layover extends Widget { 12 | protected _model: Layover.Model; 13 | protected _d3: typeof d3 | null = null; 14 | protected _drag: typeof drag | null = null; 15 | 16 | constructor(options: Layover.IOptions) { 17 | super(options); 18 | this.addClass(CSS.layover); 19 | this._model = new Layover.Model(); 20 | document.body.appendChild(this.node); 21 | this.model.stateChanged.connect(this.render, this); 22 | window.addEventListener('resize', this.render); 23 | document.body.dataset[DATA.layoutMode] = DATA.designing; 24 | this.render(); 25 | } 26 | 27 | dispose(): void { 28 | /* istanbul ignore if */ 29 | if (this.isDisposed) { 30 | return; 31 | } 32 | this.model.dispose(); 33 | const { node } = this; 34 | super.dispose(); 35 | document.body.removeChild(node); 36 | window.removeEventListener('resize', this.render); 37 | delete document.body.dataset[DATA.layoutMode]; 38 | } 39 | 40 | render = () => { 41 | const boxes = d3 42 | .select(this.node) 43 | .selectAll(`.${CSS.layoverPart}`) 44 | .data(this.model.partData, Layover.getPartKey as any) 45 | .join('div') 46 | .classed(CSS.layoverPart, true) 47 | .style('left', ({ bounds }) => `${bounds.left}px`) 48 | .style('top', ({ bounds }) => `${bounds.top}px`) 49 | .style('height', ({ bounds }) => `${bounds.height}px`) 50 | .style('width', ({ bounds }) => `${bounds.width}px`) 51 | .call(Layover.boxDrag as any); 52 | 53 | boxes 54 | .selectAll(`.${CSS.layoverPartLabel}`) 55 | .data((d, i) => [i]) 56 | .join('div') 57 | .classed(CSS.layoverPartLabel, true) 58 | .text((d) => d + 1); 59 | 60 | boxes 61 | .selectAll(`.${CSS.layoverHandle}`) 62 | .data(Layover.handleData) 63 | .join('div') 64 | .attr('class', Layover.getHandleClass) 65 | .classed(CSS.layoverHandle, true) 66 | .call(Layover.handleDrag.container(this.node) as any); 67 | 68 | boxes 69 | .selectAll(`.${CSS.layoverUnstyle}`) 70 | .data(Layover.resetData) 71 | .join('button') 72 | .classed(CSS.layoverUnstyle, true) 73 | .text('↺') 74 | .on('click', Layover.onReset as any); 75 | }; 76 | 77 | get model() { 78 | return this._model; 79 | } 80 | } 81 | 82 | export namespace Layover { 83 | export interface IOptions extends Widget.IOptions { 84 | manager: IDeckManager; 85 | } 86 | export class Model extends VDomModel { 87 | private _parts: BasePart[] = []; 88 | 89 | get partData() { 90 | return this._parts.map(this._partDatum); 91 | } 92 | 93 | get parts() { 94 | return this._parts; 95 | } 96 | 97 | set parts(parts: BasePart[]) { 98 | this._parts = parts; 99 | this.stateChanged.emit(void 0); 100 | } 101 | 102 | protected _partDatum = (part: BasePart) => { 103 | const { left, top, width, height } = part.node.getBoundingClientRect(); 104 | let zoom = parseFloat( 105 | window.getComputedStyle(part.node).getPropertyValue('zoom') || '1.0', 106 | ); 107 | return { 108 | ...part, 109 | bounds: { 110 | left: left * zoom, 111 | top: top * zoom, 112 | width: width * zoom, 113 | height: height * zoom, 114 | }, 115 | }; 116 | }; 117 | } 118 | export interface DOMRectLike { 119 | top: number; 120 | left: number; 121 | width: number; 122 | height: number; 123 | } 124 | export interface BasePart { 125 | key: string; 126 | node: HTMLElement; 127 | getStyles(): GlobalStyles | null; 128 | setStyles(styles: GlobalStyles | null): void; 129 | } 130 | export interface Part extends BasePart { 131 | bounds: DOMRectLike; 132 | } 133 | 134 | function setStyles(d: Layover.Part) { 135 | const { bounds } = d; 136 | 137 | const { innerWidth, innerHeight } = window; 138 | 139 | d.setStyles({ 140 | ...(d.getStyles() || JSONExt.emptyObject), 141 | position: 'fixed', 142 | left: `${100 * (bounds.left / innerWidth)}%`, 143 | top: `${100 * (bounds.top / innerHeight)}%`, 144 | width: `${100 * (bounds.width / innerWidth)}%`, 145 | height: `${100 * (bounds.height / innerHeight)}%`, 146 | }); 147 | } 148 | 149 | export function resetData(d: Part) { 150 | let style = d.getStyles(); 151 | return style && style.position === 'fixed' ? [d] : []; 152 | } 153 | 154 | export function onReset(event: PointerEvent, d: Layover.Part) { 155 | let styles = d.getStyles() || JSONExt.emptyObject; 156 | d.setStyles({ 157 | ...styles, 158 | position: null as any, 159 | left: null as any, 160 | top: null as any, 161 | width: null as any, 162 | height: null as any, 163 | }); 164 | } 165 | 166 | export function getPartKey(d: Part) { 167 | return d.key; 168 | } 169 | 170 | export function getHandleClass(h: PartHandle) { 171 | return `${CSS.layoverHandle}-${h.handle}`; 172 | } 173 | 174 | function onBoxDragStart(this: HTMLDivElement, event: TPartDrag, d: Layover.Part) { 175 | d3.select(this).classed(CSS.dragging, true); 176 | } 177 | 178 | function onBoxDrag(this: HTMLDivElement, event: TPartDrag, d: Layover.Part) { 179 | d.bounds.left += event.dx; 180 | d.bounds.top += event.dy; 181 | d3.select(this) 182 | .style('left', `${d.bounds.left}px`) 183 | .style('top', `${d.bounds.top}px`); 184 | } 185 | 186 | function onBoxDragEnd(this: HTMLDivElement, event: TPartDrag, d: Layover.Part) { 187 | d3.select(this).classed(CSS.dragging, false); 188 | setStyles(d); 189 | } 190 | 191 | export const boxDrag = drag() 192 | .on('start', onBoxDragStart) 193 | .on('drag', onBoxDrag) 194 | .on('end', onBoxDragEnd); 195 | 196 | function onHandleDragStart( 197 | this: HTMLDivElement, 198 | event: TPartDrag, 199 | d: Layover.PartHandle, 200 | ) { 201 | d3.select(this.parentElement).classed(CSS.dragging, true); 202 | } 203 | 204 | function onHandleDrag(this: HTMLDivElement, event: TPartDrag, d: Layover.PartHandle) { 205 | let { dx, dy } = event; 206 | let { bounds } = d.part; 207 | let h = d.handle; 208 | 209 | if (h.includes('n')) { 210 | bounds.top += dy; 211 | bounds.height -= dy; 212 | } else if (h.includes('s')) { 213 | bounds.height += dy; 214 | } 215 | if (h.includes('w')) { 216 | bounds.left += dx; 217 | bounds.width -= dx; 218 | } else if (h.includes('e')) { 219 | bounds.width += dx; 220 | } 221 | 222 | d3.select(this.parentElement) 223 | .style('left', `${bounds.left}px`) 224 | .style('top', `${bounds.top}px`) 225 | .style('height', `${bounds.height}px`) 226 | .style('width', `${bounds.width}px`); 227 | } 228 | 229 | function onHandleDragEnd( 230 | this: HTMLDivElement, 231 | event: TPartDrag, 232 | d: Layover.PartHandle, 233 | ) { 234 | d3.select(this.parentElement).classed(CSS.dragging, false); 235 | setStyles(d.part); 236 | } 237 | 238 | export const handleDrag = drag() 239 | .on('start', onHandleDragStart) 240 | .on('drag', onHandleDrag) 241 | .on('end', onHandleDragEnd); 242 | 243 | export interface PartHandle { 244 | part: Part; 245 | handle: THandle; 246 | } 247 | export function handleData(part: Part) { 248 | let handleData: PartHandle[] = []; 249 | for (const handle of HANDLES) { 250 | handleData.push({ part, handle }); 251 | } 252 | return handleData; 253 | } 254 | 255 | const HANDLES = ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'] as const; 256 | 257 | type THandle = (typeof HANDLES)[number]; 258 | 259 | type TPartDrag = D3DragEvent; 260 | } 261 | -------------------------------------------------------------------------------- /pages-lite/README.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "kernelspec": { 4 | "name": "python", 5 | "display_name": "Python (Pyodide)", 6 | "language": "python" 7 | }, 8 | "language_info": { 9 | "codemirror_mode": { 10 | "name": "python", 11 | "version": 3 12 | }, 13 | "file_extension": ".py", 14 | "mimetype": "text/x-python", 15 | "name": "python", 16 | "nbconvert_exporter": "python", 17 | "pygments_lexer": "ipython3", 18 | "version": "3.8" 19 | } 20 | }, 21 | "nbformat_minor": 4, 22 | "nbformat": 4, 23 | "cells": [ 24 | { 25 | "cell_type": "markdown", 26 | "source": "# jupyterlab-deck reports\n\n> For documentation, see the [ReadTheDocs] site!\n\n[ReadTheDocs]: https://jupyterlab-deck.rtfd.io", 27 | "metadata": { 28 | "slideshow": { 29 | "slide_type": "slide" 30 | }, 31 | "tags": [] 32 | } 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "source": "## Aceptance Tests: Robot Framework\n\n> [Robot Framework][rf] is used with [JupyterLibrary] to test that the system\n> functions as expected in the browser\n\n[rf]: https://robotframework.org\n[JupyterLibrary]: https://robotframework-jupyterlibrary.rtfd.io\n", 37 | "metadata": { 38 | "slideshow": { 39 | "slide_type": "slide" 40 | }, 41 | "tags": [] 42 | } 43 | }, 44 | { 45 | "cell_type": "code", 46 | "source": "%%html\n