├── src └── jupyter_starters │ ├── tests │ ├── __init__.py │ ├── test_meta.py │ ├── test_notebook.py │ ├── test_cli.py │ ├── test_src.py │ └── conftest.py │ ├── schema │ ├── __init__.py │ └── v3.py │ ├── py_starters │ └── __init__.py │ ├── _d │ └── etc │ │ └── jupyter │ │ ├── jupyter_notebook_config.d │ │ └── jupyter-starters.json │ │ └── jupyter_server_config.d │ │ └── jupyter-starters-serverextension.json │ ├── serverextension.py │ ├── types.py │ ├── _version.py │ ├── __init__.py │ ├── trait_types.py │ ├── json_.py │ ├── app.py │ └── handlers.py ├── scripts ├── fake_pytest.ini ├── preflight.py ├── nblint.py └── integrity.py ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ ├── documentation_request.md │ ├── intent_to_implement.md │ └── bug_report.md ├── specs │ ├── py3.11.yml │ ├── py3.8.yml │ ├── lab3.6.yml │ ├── node.yml │ ├── lab3.5.yml │ ├── build.yml │ ├── binder.yml │ ├── run.yml │ ├── _base.yml │ ├── utest.yml │ ├── lock.yml │ ├── atest.yml │ ├── docs.yml │ └── lint.yml ├── .condarc └── pull_request_template.md ├── packages ├── jupyterlab-rjsf │ ├── src │ │ ├── form │ │ │ ├── index.ts │ │ │ ├── Form │ │ │ │ ├── index.ts │ │ │ │ └── Form.tsx │ │ │ └── Theme │ │ │ │ ├── index.ts │ │ │ │ └── Theme.ts │ │ ├── index.ts │ │ ├── tsconfig.json │ │ ├── fields │ │ │ ├── xml │ │ │ │ └── index.tsx │ │ │ ├── markdown │ │ │ │ └── index.tsx │ │ │ ├── codemirror │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ └── jsonobject │ │ │ │ └── index.tsx │ │ ├── typings.d.ts │ │ └── async-component │ │ │ └── index.tsx │ ├── style │ │ ├── variables.css │ │ ├── codemirror.css │ │ ├── index.css │ │ ├── markdown.css │ │ ├── json.css │ │ ├── errors.css │ │ ├── icons │ │ │ └── form.svg │ │ ├── core.css │ │ ├── array.css │ │ └── input.css │ ├── tsconfig.json │ ├── package.json │ ├── LICENSE │ └── README.md ├── _meta │ ├── src │ │ ├── index.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ └── package.json └── jupyterlab-starters │ ├── src │ ├── index.ts │ ├── widgets │ │ ├── index.ts │ │ ├── previewcard │ │ │ ├── model.ts │ │ │ └── index.tsx │ │ ├── meta │ │ │ └── index.ts │ │ └── builder │ │ │ ├── index.ts │ │ │ ├── buttons.tsx │ │ │ └── share.tsx │ ├── tsconfig.json │ ├── icons.ts │ ├── typings.d.ts │ ├── plugin.ts │ ├── plugins │ │ ├── server.ts │ │ └── browser.ts │ ├── css.ts │ ├── notebookbutton.ts │ ├── providers │ │ ├── server.ts │ │ └── settings.ts │ ├── runners │ │ ├── _base.ts │ │ └── server.ts │ └── tokens.ts │ ├── README.md │ ├── tsconfig.json │ ├── style │ ├── variables.css │ ├── dark.css │ ├── preview.css │ ├── share.css │ ├── icons │ │ ├── cookiecutter.svg │ │ └── starter.svg │ └── index.css │ ├── webpack.config.js │ ├── LICENSE │ └── package.json ├── examples ├── cookiecutter │ ├── cookiecutter.json │ └── {{ cookiecutter.idea_size }} Idea │ │ └── index.ipynb ├── No-Op Notebook.ipynb ├── whitepaper-multiple │ ├── 50 What are the risks.ipynb │ ├── 60 How much will it cost.ipynb │ ├── 70 How long will it take.ipynb │ ├── 40 Who cares.ipynb │ ├── 20 How is it done today.ipynb │ ├── 80 What are the mid-term and final exams.ipynb │ ├── 30 What is new in your approach.ipynb │ ├── 10 What are you trying to do.ipynb │ └── 00 Introduction.ipynb └── whitepaper-single.ipynb ├── docs ├── screenshot.png ├── _static │ ├── copy-button.svg │ ├── copybutton.css │ └── copybutton.js ├── api.rst ├── index.ipynb ├── notebooks │ ├── Developers.ipynb │ ├── History.ipynb │ ├── Use Cases.ipynb │ ├── Users.ipynb │ └── REST API.ipynb └── _templates │ └── demo.html ├── lerna.json ├── MANIFEST.in ├── tsconfig.eslint.json ├── .prettierignore ├── lite ├── jupyter_lite_config.json ├── jupyter-lite.json └── overrides.json ├── .yarnrc ├── .editorconfig ├── ROADMAP.md ├── pylintrc ├── atest ├── lab │ ├── __init__.robot │ ├── 01_Launcher.robot │ ├── 00_Smoke.robot │ ├── 08_Tree_URL.robot │ ├── 02_Simple.robot │ ├── 04_Cookiecutter.robot │ ├── 07_Copy_and_Continue.robot │ ├── 03_Parameters.robot │ └── 05_Notebook.robot ├── lite │ ├── __init__.robot │ ├── 00_Smoke.robot │ ├── Keywords.resource │ └── 01_Todo.robot ├── etc │ └── jupyter_server_config.json ├── ports.py ├── CodeMirror.resource └── Variables.resource ├── .readthedocs.yml ├── .binder ├── postBuild └── overrides.json ├── .gitignore ├── tsconfigbase.json ├── .eslintignore ├── LICENSE ├── CONTRIBUTING.md ├── pyproject.toml ├── .eslintrc.js ├── package.json ├── jupyter_notebook_config.json └── jupyter_server_config.json /src/jupyter_starters/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jupyter_starters/schema/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jupyter_starters/py_starters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/fake_pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | junit_family = xunit2 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/specs/py3.11.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.11.* 3 | -------------------------------------------------------------------------------- /.github/specs/py3.8.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.8.* 3 | -------------------------------------------------------------------------------- /.github/specs/lab3.6.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - jupyterlab >=3.6.1,<4 3 | -------------------------------------------------------------------------------- /.github/specs/node.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - nodejs >=16,!=17.*,<19 3 | -------------------------------------------------------------------------------- /.github/specs/lab3.5.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - jupyterlab >=3.5.2,<3.6 3 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/form/index.ts: -------------------------------------------------------------------------------- 1 | export { Form } from './Form'; 2 | -------------------------------------------------------------------------------- /.github/specs/build.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - flit >=3.7.1,<4 3 | - twine >=3.7.1 4 | -------------------------------------------------------------------------------- /examples/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "idea_size": ["Big", "Little"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/form/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Form } from './Form'; 2 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deathbeds/jupyterlab-starters/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "jlpm", 3 | "useWorkspaces": true, 4 | "version": "independent" 5 | } 6 | -------------------------------------------------------------------------------- /packages/_meta/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@deathbeds/jupyterlab-starters'; 2 | import '@deathbeds/jupyterlab-rjsf'; 3 | -------------------------------------------------------------------------------- /.github/specs/binder.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - jupyter-server-proxy 3 | - jupyter-videochat 4 | - jupyterhub-singleuser 5 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tokens'; 2 | export * from './icons'; 3 | export * from './widgets'; 4 | -------------------------------------------------------------------------------- /.github/specs/run.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - cookiecutter 3 | - jupyter_client >=6.1.0 4 | - python-fastjsonschema 5 | - ujson 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | recursive-include src *.json 3 | recursive-include src/jupyter_starters/labextension *.* 4 | -------------------------------------------------------------------------------- /.github/specs/_base.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - nodefaults 4 | dependencies: 5 | - doit 6 | - pip 7 | - ruamel.yaml 8 | -------------------------------------------------------------------------------- /.github/specs/utest.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - coverage 3 | - pytest-asyncio 4 | - pytest-console-scripts 5 | - pytest-html 6 | - pytest-xdist 7 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/form/Theme/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Theme'; 2 | import '../../../style/index.css'; 3 | export * from './Theme'; 4 | -------------------------------------------------------------------------------- /.github/specs/lock.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - nodefaults 4 | dependencies: 5 | - conda !=22.11.1 6 | - conda-lock >=1.4,<1.5.0 7 | - doit 8 | - mamba >=1.1.0 9 | -------------------------------------------------------------------------------- /.github/specs/atest.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - firefox =102 3 | - geckodriver 4 | - pyflakes 5 | - robotframework >=6 6 | - robotframework-pabot 7 | - robotframework-seleniumlibrary 8 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jp-schema-form-up: '▲'; 3 | --jp-schema-form-down: '▼'; 4 | --jp-schema-form-remove: '×'; 5 | --jp-schema-form-add: '+'; 6 | } 7 | -------------------------------------------------------------------------------- /src/jupyter_starters/_d/etc/jupyter/jupyter_notebook_config.d/jupyter-starters.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "jupyter_starters": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/codemirror.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm .react-codemirror2 { 2 | border: solid var(--jp-border-width) var(--jp-border-color1); 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | -------------------------------------------------------------------------------- /src/jupyter_starters/_d/etc/jupyter/jupyter_server_config.d/jupyter-starters-serverextension.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyter_starters": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-starters 2 | 3 | > _Parameterized file and directory starters for JupyterLab._ 4 | 5 | See more on the [project repository](https://github.com/deathbeds/jupyterlab-starters). 6 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": [ 4 | "packages/**/src/**/*", 5 | "packages/**/test/**/*", 6 | "docs/**/*", 7 | "*", 8 | "packages/**/*.config.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/_meta/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": ".root.tsbuildinfo" 6 | }, 7 | "extends": "../../tsconfigbase", 8 | "files": ["package.json"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './async-component'; 2 | export * from './form'; 3 | export * from './schemaform'; 4 | export * from './schemaform/model'; 5 | // order of export matters here (maybe) 6 | export * from './fields'; 7 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/index.ts: -------------------------------------------------------------------------------- 1 | export * from './builder'; 2 | export * from './builder/model'; 3 | export * from './meta'; 4 | export * from './meta/model'; 5 | export * from './previewcard'; 6 | export * from './previewcard/model'; 7 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": ".root.tsbuildinfo" 6 | }, 7 | "extends": "../../tsconfigbase", 8 | "files": ["package.json"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/index.css: -------------------------------------------------------------------------------- 1 | @import './core.css'; 2 | @import './array.css'; 3 | @import './input.css'; 4 | @import './errors.css'; 5 | @import './variables.css'; 6 | @import './markdown.css'; 7 | @import './json.css'; 8 | @import './codemirror.css'; 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .envs/ 2 | .vscode/ 3 | *.egg-info/ 4 | **/.ipynb_checkpoints/ 5 | **/.mypy_cache/ 6 | **/.pytest_cache/ 7 | **/.yarn-packages/ 8 | **/lib/ 9 | **/node_modules/ 10 | atest/ 11 | build/ 12 | dist/ 13 | docs/_build/ 14 | docs/_static/schema/ 15 | src/**/labextension 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "rootDir": ".", 5 | "tsBuildInfoFile": ".root.tsbuildinfo" 6 | }, 7 | "extends": "../../tsconfigbase", 8 | "files": ["package.json", "schema/settings-provider.json"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "rootDir": ".", 6 | "tsBuildInfoFile": "../.src.tsbuildinfo" 7 | }, 8 | "include": ["./**/*"], 9 | "references": [{ "path": "../" }] 10 | } 11 | -------------------------------------------------------------------------------- /lite/jupyter_lite_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LiteBuildConfig": { 3 | "apps": ["lab"], 4 | "federated_extensions": [ 5 | "https://conda.anaconda.org/conda-forge/noarch/jupyterlab-webrtc-docprovider-0.1.1-pyhd8ed1ab_0.tar.bz2" 6 | ], 7 | "output_dir": "../build/docs-app/" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/form/Theme/Theme.ts: -------------------------------------------------------------------------------- 1 | import { ThemeProps, getDefaultRegistry } from '@rjsf/core'; 2 | 3 | const { fields, widgets } = getDefaultRegistry(); 4 | 5 | const Theme: ThemeProps = { 6 | fields: { ...fields }, 7 | widgets: { ...widgets }, 8 | }; 9 | 10 | export default Theme; 11 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | disable-self-update-check true 2 | ignore-optional true 3 | network-timeout "300000" 4 | registry "https://registry.npmjs.org/" 5 | yarn-ignore-optional true 6 | yarn-offline-mirror "./.yarn-packages" 7 | yarn-offline-mirror-pruning true 8 | yarn-prefer-offline true 9 | yarn-registry "https://registry.npmjs.org/" 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.{js,ts,py,js,md,jsx}] 9 | charset = utf-8 10 | 11 | [*.{py}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{css,ts,tsx,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.github/specs/docs.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - docutils >=0.18 3 | - jupyterlab-myst 4 | - myst-nb 5 | - pkginfo 6 | - pydata-sphinx-theme 7 | - pytest-check-links 8 | - python-graphviz 9 | - sphinx 10 | - sphinx-autobuild 11 | - sphinx-autodoc-typehints 12 | - sphinx-copybutton 13 | - sphinxext-rediraffe 14 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jp-starters-btns-height: calc(5 * var(--jp-ui-font-size3)); 3 | --jp-starters-share-height: calc(7 * var(--jp-ui-font-size3)); 4 | --jp-starters-btn-height: calc(3 * var(--jp-ui-font-size3)); 5 | --jp-starters-btn-icon-size: calc(2 * var(--jp-ui-font-size3)); 6 | } 7 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # ROADMAP 2 | 3 | - [ ] Adopt some well-known locations for more user-serviceable starters 4 | - [ ] Add generic `script` type 5 | - [ ] Add preview/dry-run 6 | - [ ] Add better error handling and logging 7 | - [ ] Explore [json-e](https://github.com/taskcluster/json-e) integration 8 | - [ ] Add starter builder with command explorer 9 | -------------------------------------------------------------------------------- /lite/jupyter-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter-config-data": { 3 | "appName": "JupyterLite Starters", 4 | "collaborative": true, 5 | "disabledExtensions": [ 6 | "@deathbeds/jupyterlab-starters:server-provider", 7 | "@deathbeds/jupyterlab-starters:server-runner" 8 | ] 9 | }, 10 | "jupyter-lite-schema-version": 0 11 | } 12 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [SIMILARITIES] 2 | 3 | # Minimum lines number of a similarity. 4 | min-similarity-lines=20 5 | 6 | # Ignore comments when computing similarities. 7 | ignore-comments=yes 8 | 9 | # Ignore docstrings when computing similarities. 10 | ignore-docstrings=yes 11 | 12 | # Ignore imports when computing similarities. 13 | ignore-imports=no 14 | -------------------------------------------------------------------------------- /atest/lab/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation All the tests 3 | 4 | Resource ../Keywords.resource 5 | 6 | Suite Setup Setup Server And Browser 7 | Suite Teardown Tear Down Everything 8 | Test Setup Reset Application State 9 | 10 | Test Tags os:${os.lower()} py:${py} ospy:${os.lower()}${py} 11 | -------------------------------------------------------------------------------- /.github/specs/lint.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - black 3 | - docformatter 4 | - isort 5 | - mypy 6 | - pydocstyle 7 | - pyflakes 8 | - pylint 9 | - pyproject-fmt 10 | - pytest 11 | - robotframework-robocop 12 | - robotframework-seleniumlibrary 13 | - robotframework-tidy 14 | - ruamel.yaml 15 | - ssort 16 | - types-jsonschema 17 | - types-ujson 18 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/markdown.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm .jp-RenderedMarkdown > *:first-child, 2 | .jp-SchemaForm .jp-RenderedMarkdown > p:first-child { 3 | margin-top: 0; 4 | padding-top: 0; 5 | } 6 | 7 | .jp-SchemaForm .jp-RenderedMarkdown > *:nth-last-child(2), 8 | .jp-SchemaForm .jp-RenderedMarkdown > p:nth-last-child(2) { 9 | margin-bottom: 0; 10 | padding-bottom: 0; 11 | } 12 | -------------------------------------------------------------------------------- /atest/lite/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Lite 3 | 4 | Library OperatingSystem 5 | Resource ./Keywords.resource 6 | Resource ../Keywords.resource 7 | 8 | Suite Setup Start Lite Suite 9 | Suite Teardown Clean Up Lite Suite 10 | Test Setup Start Lite Test 11 | Test Teardown Clean Up Lite Test 12 | 13 | Test Tags app:lite 14 | -------------------------------------------------------------------------------- /packages/_meta/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "rootDir": ".", 6 | "tsBuildInfoFile": "../.src.tsbuildinfo" 7 | }, 8 | "include": ["./**/*"], 9 | "references": [ 10 | { 11 | "path": "../../jupyterlab-starters/src" 12 | }, 13 | { 14 | "path": "../../jupyterlab-rjsf/src" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "rootDir": ".", 6 | "tsBuildInfoFile": "../.src.tsbuildinfo" 7 | }, 8 | "include": ["./**/*"], 9 | "files": ["./_schema.json"], 10 | "references": [ 11 | { "path": "../" }, 12 | { 13 | "path": "../../jupyterlab-rjsf" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/.condarc: -------------------------------------------------------------------------------- 1 | add_anaconda_token: False 2 | add_pip_as_python_dependency: False 3 | always_yes: True 4 | auto_update_conda: False 5 | local_repodata_ttl: 99999 6 | notify_outdated_conda: False 7 | remote_connect_timeout_secs: 600.0 8 | remote_max_retries: 10 9 | remote_read_timeout_secs: 600.0 10 | repodata_fns: 11 | - repodata.json 12 | show_channel_urls: True 13 | show_sources: True 14 | unsatisfiable_hints_check_depth: 2 15 | -------------------------------------------------------------------------------- /atest/lite/00_Smoke.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Check the vitals of a Lite site 3 | 4 | Resource ../Keywords.resource 5 | Resource ./Keywords.resource 6 | 7 | Suite Setup Setup Suite For Screenshots lite${/}smoke 8 | 9 | 10 | *** Test Cases *** 11 | Load Lite 12 | [Documentation] Can we load the JupyterLite site? 13 | Capture Page Screenshot 00-lite-smoke.png 14 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/dark.css: -------------------------------------------------------------------------------- 1 | [data-jp-theme-light='false'] 2 | .jp-Starters-FormPanel 3 | button.jp-mod-styled:not(.jp-mod-accept):not(.jp-mod-reject) { 4 | color: var(--jp-layout-color0); 5 | } 6 | 7 | [data-jp-theme-light='false'] 8 | .jp-Starters-FormPanel 9 | button.jp-mod-styled:not(.jp-mod-accept):not(.jp-mod-reject) 10 | [fill]:not([fill='none']) { 11 | fill: var(--jp-layout-color0); 12 | } 13 | -------------------------------------------------------------------------------- /src/jupyter_starters/serverextension.py: -------------------------------------------------------------------------------- 1 | """Serverextension for starters.""" 2 | from .handlers import add_handlers 3 | from .manager import StarterManager 4 | 5 | 6 | def load_jupyter_server_extension(nbapp): 7 | """Create a StarterManager and add handlers.""" 8 | manager = StarterManager(parent=nbapp) 9 | add_handlers(nbapp, manager) 10 | nbapp.log.info(f"""💡 starters: {", ".join(manager.starter_names)}""") 11 | -------------------------------------------------------------------------------- /src/jupyter_starters/types.py: -------------------------------------------------------------------------------- 1 | """Some types and constants.""" 2 | # pylint: disable=too-few-public-methods 3 | NS = "starters" 4 | 5 | 6 | class Status: 7 | """Pseudo-enum for managing statuses.""" 8 | 9 | # the starter isn't done yet, and more data is required 10 | CONTINUING = "continuing" 11 | 12 | # the starter is done, and should not continue 13 | DONE = "done" 14 | 15 | # something terrible has happened 16 | ERROR = "error" 17 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-20.04 5 | tools: 6 | python: mambaforge-4.10 7 | jobs: 8 | pre_build: 9 | - doit list --all --status 10 | - | 11 | doit dist || doit dist 12 | - doit dev 13 | - doit docs:schema 14 | - doit lite 15 | 16 | sphinx: 17 | builder: html 18 | configuration: docs/conf.py 19 | 20 | conda: 21 | environment: docs/rtd.yml 22 | 23 | formats: 24 | - htmlzip 25 | - epub 26 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/form/Form/Form.tsx: -------------------------------------------------------------------------------- 1 | import { asyncComponent } from '../../async-component'; 2 | 3 | /** 4 | * A themed JSON Schema form 5 | * 6 | * The actual form/theme will be loaded asynchronously the first time it is used 7 | */ 8 | const Form = asyncComponent(async () => { 9 | const { withTheme } = await import('@rjsf/core'); 10 | const Theme = (await import('../Theme')).default; 11 | return withTheme(Theme); 12 | }); 13 | 14 | export default Form; 15 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/icons.ts: -------------------------------------------------------------------------------- 1 | import { LabIcon } from '@jupyterlab/ui-components'; 2 | 3 | import { CSS } from './css'; 4 | import { DEFAULT_ICON_NAME, NS } from './tokens'; 5 | 6 | export namespace Icons { 7 | export const starter = new LabIcon({ 8 | name: DEFAULT_ICON_NAME, 9 | svgstr: CSS.SVG.DEFAULT_ICON, 10 | }); 11 | export const cookiecutter = new LabIcon({ 12 | name: `${NS}:cookiecutter`, 13 | svgstr: CSS.SVG.COOKIECUTTER, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/fields/xml/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { CodeMirrorField } from '../codemirror'; 4 | 5 | export function XMLField(props: Record): JSX.Element { 6 | const finalProps = { 7 | ...props, 8 | options: { 9 | cmOptions: { 10 | ...(props.options?.cmOptions || {}), 11 | mode: 'text/xml', 12 | }, 13 | }, 14 | }; 15 | return ; 16 | } 17 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/preview.css: -------------------------------------------------------------------------------- 1 | .jp-Starters-Preview { 2 | display: flex; 3 | flex: 0; 4 | flex-direction: column; 5 | align-items: center; 6 | border-top: solid 1px var(--jp-border-color0); 7 | max-height: calc( 8 | var(--jp-private-launcher-card-size) + 2 * var(--jp-notebook-padding) 9 | ); 10 | } 11 | 12 | .jp-Starters-Preview img { 13 | width: var(--jp-private-launcher-large-icon-size); 14 | height: var(--jp-private-launcher-large-icon-size); 15 | } 16 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | export MAMBA_NO_BANNER=1 6 | 7 | mamba create --yes \ 8 | --prefix "${NB_PYTHON_PREFIX}" \ 9 | --file ".github/locks/binder-linux-64-3.11.conda.lock" 10 | 11 | # do a proper activation 12 | source activate "${NB_PYTHON_PREFIX}" 13 | 14 | # some things we just don't need to do on binder 15 | export DEMO_IN_BINDER=1 16 | 17 | jlpm cache clean 18 | 19 | doit -n8 preflight || doit preflight 20 | doit -n8 docs || echo "no docs for you" 21 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const script: string; 3 | export default script; 4 | } 5 | 6 | declare module '@rjsf/core/lib/components/fields/ObjectField' { 7 | import * as React from 'react'; 8 | export default class ObjectField extends React.Component {} 9 | } 10 | 11 | declare module '@rjsf/core/lib/components/fields/StringField' { 12 | import * as React from 'react'; 13 | export default class StringField extends React.Component {} 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | _*.json 3 | _build 4 | .cache/ 5 | .coverage 6 | .envs/ 7 | .eslintcache 8 | .ipynb_checkpoints/ 9 | .mypy_cache/ 10 | .pabotsuitenames 11 | .pytest_cache/ 12 | .stylelintcache 13 | .vscode/ 14 | .yarn-packages/ 15 | *.doit* 16 | *.egg-info 17 | *.log 18 | *.tgz 19 | *.tsbuildinfo 20 | atest/output/ 21 | build/ 22 | dist/ 23 | docs/_static/_/ 24 | docs/_static/schema 25 | docs/schema 26 | envs/ 27 | lib/ 28 | node_modules/ 29 | src/**/labextension/ 30 | src/jupyter_starters/_d/share/ 31 | untitled* 32 | Untitled* 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/fields/markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { CodeMirrorField } from '../codemirror'; 4 | 5 | export function MarkdownField(props: Record): JSX.Element { 6 | const finalProps = { 7 | ...props, 8 | options: { 9 | ...(props.options || {}), 10 | cmOptions: { 11 | ...(props.options?.cmOptions || {}), 12 | mode: 'text/x-ipythongfm', 13 | }, 14 | }, 15 | }; 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /src/jupyter_starters/_version.py: -------------------------------------------------------------------------------- 1 | """Single source of truth for jupyter_starts version.""" 2 | import json 3 | import sys 4 | from pathlib import Path 5 | 6 | HERE = Path(__file__).parent 7 | _D = HERE / "_d" 8 | __ext__ = "@deathbeds/jupyterlab-starters" 9 | __package_json__ = ( 10 | _D if _D.exists() else Path(sys.prefix) 11 | ) / f"share/jupyter/labextensions/{ __ext__}/package.json" 12 | 13 | __js__ = json.loads(__package_json__.read_text(encoding="utf-8")) 14 | __version__ = __js__["version"].replace("-alpha", "a").replace("-beta", "b") 15 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { settingsProviderPlugin, browserRunnerPlugin } from './plugins/browser'; 2 | import { corePlugin } from './plugins/core'; 3 | import { routerPlugin } from './plugins/router'; 4 | import { serverRunnerPlugin, serverProviderPlugin } from './plugins/server'; 5 | 6 | import '../style/index.css'; 7 | 8 | const plugins = [ 9 | corePlugin, 10 | serverProviderPlugin, 11 | serverRunnerPlugin, 12 | settingsProviderPlugin, 13 | browserRunnerPlugin, 14 | routerPlugin, 15 | ]; 16 | 17 | export default plugins; 18 | -------------------------------------------------------------------------------- /atest/etc/jupyter_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyter_starters": true 5 | }, 6 | "tornado_settings": { 7 | "page_config_data": { 8 | "buildAvailable": false, 9 | "buildCheck": false 10 | } 11 | } 12 | }, 13 | "StarterManager": { 14 | "extra_starters": { 15 | "noop": { 16 | "description": "noop", 17 | "label": "noop", 18 | "src": "examples/No-Op Notebook.ipynb", 19 | "type": "notebook" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/jupyter_starters/schema/v3.py: -------------------------------------------------------------------------------- 1 | """Initial schema.""" 2 | from pathlib import Path 3 | 4 | from ..json_ import json_validator, loads 5 | 6 | VERSION = "3" 7 | 8 | HERE = Path(__file__).parent 9 | 10 | SCHEMA = loads((HERE / f"v{VERSION}.json").read_text()) 11 | ALL_STARTERS = json_validator(SCHEMA) 12 | 13 | _STARTER = dict(SCHEMA) 14 | _STARTER["anyOf"] = [{"$ref": "#/definitions/starter"}] 15 | 16 | STARTER = json_validator(_STARTER) 17 | 18 | _STARTERS = dict(SCHEMA) 19 | _STARTERS["anyOf"] = [{"$ref": "#/definitions/starters"}] 20 | STARTERS = json_validator(_STARTERS) 21 | -------------------------------------------------------------------------------- /atest/lab/01_Launcher.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Launcher 3 | 4 | Resource ../Keywords.resource 5 | 6 | Suite Setup Setup Suite For Screenshots lab${/}launcher 7 | 8 | Test Tags launcher 9 | 10 | 11 | *** Test Cases *** 12 | Launcher 13 | [Documentation] Does the launcher basically work? 14 | Wait Until Page Contains Element ${XP LAUNCH SECTION} 15 | Scroll Element Into View ${XP LAUNCH SECTION} 16 | Scroll Element Into View ${CSS LAUNCH CARD} 17 | Capture Page Screenshot 00-launcher-did-load.png 18 | -------------------------------------------------------------------------------- /docs/_static/copy-button.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/jupyter_starters/tests/test_meta.py: -------------------------------------------------------------------------------- 1 | """Is metadata reported properly.""" 2 | # pylint: disable=protected-access 3 | import jupyter_starters 4 | 5 | 6 | def test_labextenions_paths(): 7 | """does it report the right number of labextensions?""" 8 | paths = jupyter_starters._jupyter_labextension_paths() 9 | assert len(paths) == 1 10 | 11 | 12 | def test_serverextenions_paths(): 13 | """does it report the right number of serverextensions?""" 14 | paths = jupyter_starters._jupyter_server_extension_paths() 15 | assert len(paths) == 1 16 | 17 | 18 | def test_version(): 19 | """does it have a version?""" 20 | assert jupyter_starters.__version__ 21 | -------------------------------------------------------------------------------- /tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "composite": true, 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "incremental": true, 9 | "jsx": "react", 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmitOnError": true, 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "preserveWatchOutput": true, 16 | "resolveJsonModule": true, 17 | "skipLibCheck": true, 18 | "sourceMap": true, 19 | "strictNullChecks": true, 20 | "target": "es2018", 21 | "types": [] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const script: string; 3 | export default script; 4 | } 5 | 6 | declare module '@rjsf/core/lib/components/fields/ObjectField' { 7 | import * as React from 'react'; 8 | // eslint-disable-next-line 9 | import * as rjsf from '@rjsf/core'; 10 | 11 | export default class ObjectField extends React.Component {} 12 | } 13 | 14 | declare module '@rjsf/core/lib/components/fields/StringField' { 15 | import * as React from 'react'; 16 | // eslint-disable-next-line 17 | import * as rjsf from '@rjsf/core'; 18 | 19 | export default class StringField extends React.Component {} 20 | } 21 | -------------------------------------------------------------------------------- /atest/lab/00_Smoke.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Smoke Test 3 | 4 | Resource ../Keywords.resource 5 | 6 | Suite Setup Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}smoke 7 | 8 | Test Tags smoke 9 | 10 | 11 | *** Test Cases *** 12 | Lab Version 13 | [Documentation] JupyterLab Version 14 | Capture Page Screenshot 00-smoke-did-load.png 15 | ${script} = Get Element Attribute id:jupyter-config-data innerHTML 16 | ${config} = Evaluate __import__("json").loads(r"""${script}""") 17 | Set Global Variable ${PAGE CONFIG} ${config} 18 | Set Global Variable ${LAB VERSION} ${config["appVersion"]} 19 | -------------------------------------------------------------------------------- /atest/lab/08_Tree_URL.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Tree URL 3 | 4 | Resource ../Keywords.resource 5 | Library String 6 | 7 | Suite Setup Setup Suite For Screenshots lab${/}tree-url 8 | 9 | Test Tags example:tree-url 10 | 11 | 12 | *** Test Cases *** 13 | Starter Opens 14 | [Documentation] Does a URL-provided starter open? 15 | Go To ${URL}lab?starter=cookiecutter/examples 16 | Run Keyword And Ignore Error Wait For Splash 17 | ${template css} = Set Variable css:input[label\="Template"] 18 | Wait Until Page Contains Element ${template css} timeout=10s 19 | Capture Page Screenshot 00-tree-url-did-launch.png 20 | -------------------------------------------------------------------------------- /examples/No-Op Notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "> This notebook doesn't do anything" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/50 What are the risks.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### What are the risks?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/60 How much will it cost.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### How much will it cost?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/70 How long will it take.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### How long will it take?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/json.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm fieldset .jp-SchemaForm-JSONObject-buttons { 2 | display: flex; 3 | } 4 | 5 | .jp-SchemaForm fieldset .jp-SchemaForm-JSONObject-buttons button { 6 | border: solid var(--jp-border-width) transparent; 7 | flex: 1; 8 | margin: 0; 9 | background-color: transparent; 10 | } 11 | 12 | .jp-SchemaForm fieldset .jp-SchemaForm-JSONObject-buttons button[disabled] { 13 | opacity: 0.25; 14 | } 15 | 16 | .jp-SchemaForm fieldset .jp-SchemaForm-JSONObject-buttons button:hover:not([disabled]), 17 | .jp-SchemaForm fieldset .jp-SchemaForm-JSONObject-buttons button:focus:not([disabled]) { 18 | border: solid var(--jp-border-width) var(--jp-border-color1); 19 | background-color: var(jp-layout-color2); 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a Future Roadmap item 3 | about: Help us build future features 4 | --- 5 | 6 | 13 | 14 | ## Elevator Pitch 15 | 16 | 17 | 18 | ## Motivation 19 | 20 | 21 | 22 | ## Design Ideas 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/jupyter_starters/tests/test_notebook.py: -------------------------------------------------------------------------------- 1 | """Tests of exotic notebook starter behavior.""" 2 | import pytest 3 | 4 | from jupyter_starters.types import Status 5 | 6 | 7 | @pytest.mark.asyncio 8 | async def test_notebook_no_schema(starter_manager, tmp_notebook): 9 | """does a notebook without a schema still work? 10 | 11 | https://github.com/deathbeds/jupyterlab-starters/issues/26 12 | """ 13 | 14 | name = "tmp-notebook" 15 | starter_manager.extra_starters[name] = { 16 | "type": "notebook", 17 | "src": str(tmp_notebook), 18 | "description": "test", 19 | "label": "test", 20 | } 21 | 22 | response = await starter_manager.start(name, "", {}) 23 | assert response["status"] == Status.DONE, response 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/build 3 | **/lib 4 | **/node_modules 5 | **/mock_packages 6 | **/static 7 | **/typings 8 | **/schemas 9 | **/themes 10 | coverage 11 | *.map.js 12 | *.bundle.js 13 | _*.d.ts 14 | 15 | dev_mode/index.js 16 | !dev_mode/static/index.out.js 17 | dev_mode/workspaces 18 | docs/_build 19 | docs/api 20 | docs/_static 21 | docs/build 22 | examples/chrome-example-test.js 23 | jupyterlab/chrome-test.js 24 | jupyterlab/geckodriver 25 | packages/extensionmanager-extension/examples/listings 26 | packages/ui-components/src/icon/iconimports.ts 27 | jupyterlab/staging/yarn.js 28 | jupyterlab/staging/index.js 29 | 30 | # jetbrains IDE stuff 31 | .idea/ 32 | 33 | # ms IDE stuff 34 | .history/ 35 | .vscode/ 36 | 37 | envs/ 38 | atest/ 39 | .eslintrc.js 40 | -------------------------------------------------------------------------------- /src/jupyter_starters/__init__.py: -------------------------------------------------------------------------------- 1 | """Parametrized starter files and folders for Jupyter.""" 2 | from typing import Dict, List 3 | 4 | from ._version import __js__, __package_json__, __version__ 5 | from .serverextension import load_jupyter_server_extension 6 | 7 | 8 | def _jupyter_server_extension_paths() -> List[Dict[str, str]]: 9 | return [{"module": "jupyter_starters"}] 10 | 11 | 12 | def _jupyter_labextension_paths() -> List[Dict[str, str]]: 13 | """Fetch the paths to JupyterLab extensions.""" 14 | return [dict(src=(str(__package_json__.parent)), dest=__js__["name"])] 15 | 16 | 17 | __all__ = [ 18 | "__version__", 19 | "_jupyter_labextension_paths", 20 | "_jupyter_server_extension_paths", 21 | "load_jupyter_server_extension", 22 | ] 23 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/40 Who cares.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Who cares? If you are successful, what difference will it make?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/20 How is it done today.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### How is it done today, and what are the limits of current practice?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/80 What are the mid-term and final exams.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### What are the mid-term and final “exams” to check for success?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/30 What is new in your approach.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### What is new in your approach and why do you think it will be successful?" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3 (ipykernel)", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.10.6" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 4 32 | } 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/share.css: -------------------------------------------------------------------------------- 1 | .jp-Starters-ShareForm { 2 | max-width: 40em; 3 | padding: 0.5em; 4 | flex: 0; 5 | max-height: var(--jp-starters-share-height); 6 | border-bottom: solid 1px var(--jp-border-color0); 7 | } 8 | 9 | .jp-Starters-ShareForm-input { 10 | display: flex; 11 | flex-direction: row; 12 | margin-top: 0.5em; 13 | } 14 | 15 | .jp-Starters-ShareForm-input input { 16 | flex: 1; 17 | } 18 | 19 | .jp-Starters-ShareForm-input button { 20 | flex: 1; 21 | max-width: 6em; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | cursor: copy; 26 | height: unset; 27 | } 28 | 29 | .jp-Starters-ShareForm-input button > span { 30 | margin-top: 0.5em; 31 | } 32 | 33 | .jp-Starters-ShareForm-input button > * { 34 | flex: 1; 35 | cursor: copy; 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation request 3 | about: Ask for clarification in jupyter(lab)-starters documentation 4 | --- 5 | 6 | 12 | 13 | ## What I am trying to do... 14 | 15 | 16 | 17 | ## How I would like to learn how to do it... 18 | 19 | 20 | 21 | ## How the project might keep the docs accurate... 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | output: { 3 | clean: true, 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.js$/, 10 | use: process.env.WITH_JS_COV 11 | ? ['@ephesoft/webpack.istanbul.loader'] 12 | : [ 13 | { 14 | loader: 'source-map-loader', 15 | options: { 16 | filterSourceMappingUrl: (url, resourcePath) => { 17 | if (resourcePath.includes('@jupyterlab/rendermime')) { 18 | return false; 19 | } 20 | return true; 21 | }, 22 | }, 23 | }, 24 | ], 25 | }, 26 | { 27 | test: /\.html$/, 28 | type: 'asset/resource', 29 | }, 30 | ], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Python API 2 | ========== 3 | 4 | Starter Manager 5 | ------------------- 6 | 7 | .. automodule:: jupyter_starters.manager 8 | :members: 9 | :special-members: 10 | 11 | Starters 12 | ------------ 13 | 14 | Cookiecutter 15 | ^^^^^^^^^^^^ 16 | 17 | .. automodule:: jupyter_starters.py_starters.cookiecutter 18 | :members: 19 | :special-members: 20 | 21 | Notebook 22 | ^^^^^^^^ 23 | 24 | .. automodule:: jupyter_starters.py_starters.notebook 25 | :members: 26 | :special-members: 27 | 28 | Miscellaneous 29 | ------------- 30 | 31 | 32 | Types 33 | ^^^^^ 34 | 35 | .. automodule:: jupyter_starters.types 36 | :members: 37 | :special-members: 38 | 39 | Traits 40 | ^^^^^^ 41 | 42 | .. automodule:: jupyter_starters.trait_types 43 | :members: 44 | :special-members: 45 | 46 | Validators 47 | ^^^^^^^^^^ 48 | 49 | .. automodule:: jupyter_starters.json_ 50 | :members: 51 | :special-members: 52 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/10 What are you trying to do.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### What are you trying to do? Articulate your objectives using absolutely no jargon." 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "..." 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.10.6" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 4 39 | } 40 | -------------------------------------------------------------------------------- /src/jupyter_starters/trait_types.py: -------------------------------------------------------------------------------- 1 | """Some more traits. 2 | 3 | these are not typechecked yet, because of the impedance between 4 | traitlets, JSON Schema, and mypy. 5 | """ 6 | 7 | # pylint: disable=broad-except,unused-argument 8 | import traitlets 9 | 10 | from .json_ import JsonSchemaException 11 | 12 | 13 | class Schema(traitlets.Any): 14 | """any... 15 | 16 | but validated by a :func:`jupyter_starters.json_.json_validator` 17 | """ 18 | 19 | _validator = None 20 | 21 | def __init__(self, validator, *args, **kwargs): 22 | super().__init__(*args, **kwargs) 23 | self._validator = validator 24 | 25 | def validate(self, obj, value): 26 | """Applies a validator.""" 27 | try: 28 | self._validator(value) 29 | except JsonSchemaException as err: # pragma: no cover 30 | raise traitlets.TraitError(f"""schema errors: {err}""") 31 | return value 32 | -------------------------------------------------------------------------------- /packages/_meta/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deathbeds/metapackage-jupyterlab-starters", 3 | "version": "0.1.0", 4 | "description": "JupyterLab Starters - Meta Package", 5 | "license": "BSD-3-Clause", 6 | "author": "dead pixels collective", 7 | "homepage": "https://github.com/deathbeds/jupyterlab-starters", 8 | "bugs": { 9 | "url": "https://github.com/deathbeds/jupyterlab-starters" 10 | }, 11 | "main": "lib/index.js", 12 | "files": [ 13 | "lib/*.{d.ts,js,js.map}" 14 | ], 15 | "directories": { 16 | "lib": "lib/" 17 | }, 18 | "scripts": { 19 | "build": "tsc --build src || tsc --build src", 20 | "clean": "rimraf lib", 21 | "watch": "tsc --build src --watch --preserveWatchOutput" 22 | }, 23 | "sideEffects": false, 24 | "types": "lib/index.d.ts", 25 | "dependencies": { 26 | "@deathbeds/jupyterlab-rjsf": "file:../jupyterlab-rjsf", 27 | "@deathbeds/jupyterlab-starters": "file:../jupyterlab-starters" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/previewcard/model.ts: -------------------------------------------------------------------------------- 1 | import { VDomModel } from '@jupyterlab/apputils'; 2 | 3 | import * as SCHEMA from '../../_schema'; 4 | import { CSS } from '../../css'; 5 | 6 | export class PreviewCardModel extends VDomModel { 7 | private _starter: SCHEMA.Starter; 8 | 9 | constructor(options: PreviewCardModel.IOptions = {}) { 10 | super(); 11 | if (options.starter != null) { 12 | this._starter = options.starter; 13 | } 14 | } 15 | 16 | get iconURI(): string { 17 | const icon = this._starter?.icon || CSS.SVG.DEFAULT_ICON; 18 | return `data:image/svg+xml;base64,${btoa(icon)}`; 19 | } 20 | 21 | get starter(): SCHEMA.Starter { 22 | return this._starter; 23 | } 24 | 25 | set starter(starter: SCHEMA.Starter) { 26 | this._starter = starter; 27 | this.stateChanged.emit(void 0); 28 | } 29 | } 30 | 31 | export namespace PreviewCardModel { 32 | export interface IOptions { 33 | starter?: SCHEMA.Starter; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "tags": [] 7 | }, 8 | "source": [ 9 | "```{include} ../README.md\n", 10 | "\n", 11 | "```\n", 12 | "\n", 13 | "# Documentation Contents\n", 14 | "\n", 15 | "```{toctree}\n", 16 | "notebooks/Starters\n", 17 | "notebooks/Users\n", 18 | "notebooks/Developers\n", 19 | "notebooks/History\n", 20 | "```" 21 | ] 22 | } 23 | ], 24 | "metadata": { 25 | "kernelspec": { 26 | "display_name": "Python 3 (ipykernel)", 27 | "language": "python", 28 | "name": "python3" 29 | }, 30 | "language_info": { 31 | "codemirror_mode": { 32 | "name": "ipython", 33 | "version": 3 34 | }, 35 | "file_extension": ".py", 36 | "mimetype": "text/x-python", 37 | "name": "python", 38 | "nbconvert_exporter": "python", 39 | "pygments_lexer": "ipython3", 40 | "version": "3.10.6" 41 | } 42 | }, 43 | "nbformat": 4, 44 | "nbformat_minor": 4 45 | } 46 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/plugins/server.ts: -------------------------------------------------------------------------------- 1 | import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; 2 | 3 | import { ServerStarterProvider } from '../providers/server'; 4 | import { ServerStarterRunner } from '../runners/server'; 5 | import { IStarterManager, PKG, SERVER_NAME } from '../tokens'; 6 | 7 | export const serverProviderPlugin: JupyterFrontEndPlugin = { 8 | id: `${PKG.name}:server-provider`, 9 | requires: [IStarterManager], 10 | autoStart: true, 11 | activate: (app: JupyterFrontEnd, manager: IStarterManager) => { 12 | const provider = new ServerStarterProvider(); 13 | manager.addProvider(SERVER_NAME, provider); 14 | }, 15 | }; 16 | 17 | export const serverRunnerPlugin: JupyterFrontEndPlugin = { 18 | id: `${PKG.name}:server-runner`, 19 | requires: [IStarterManager], 20 | autoStart: true, 21 | activate: (app: JupyterFrontEnd, manager: IStarterManager) => { 22 | const runner = new ServerStarterRunner({ manager }); 23 | manager.addRunner(SERVER_NAME, runner); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/jupyter_starters/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | """Tests of CLI features.""" 2 | 3 | 4 | from .._version import __version__ 5 | from ..json_ import loads 6 | from ..schema.v3 import STARTERS 7 | 8 | try: 9 | from ruamel.yaml import safe_load 10 | 11 | HAS_YAML = True 12 | except ImportError: # pragma: no cover 13 | HAS_YAML = False 14 | 15 | 16 | def test_cli_version(script_runner): 17 | """does it report the version?""" 18 | ret = script_runner.run("jupyter", "starters", "--version") 19 | assert ret.success 20 | assert __version__ in ret.stdout 21 | 22 | 23 | def test_cli_json(script_runner): 24 | """does it emit valid json?""" 25 | ret = script_runner.run("jupyter", "starters", "list", "--json") 26 | assert ret.success 27 | assert STARTERS(loads(ret.stdout)) 28 | 29 | 30 | if HAS_YAML: 31 | 32 | def test_cli_yaml(script_runner): 33 | """is the default output valid yaml?""" 34 | 35 | ret = script_runner.run("jupyter", "starters", "list") 36 | assert ret.success 37 | assert safe_load(ret.stdout) 38 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/css.ts: -------------------------------------------------------------------------------- 1 | import COOKIECUTTER_SVG from '!!raw-loader!../style/icons/cookiecutter.svg'; 2 | import DEFAULT_ICON_SVG from '!!raw-loader!../style/icons/starter.svg'; 3 | 4 | export const CSS = { 5 | P: { 6 | hidden: 'p-mod-hidden', 7 | }, 8 | JP: { 9 | accept: 'jp-mod-accept', 10 | reject: 'jp-mod-reject', 11 | styled: 'jp-mod-styled', 12 | warn: 'jp-mod-warn', 13 | icon16: 'jp-MaterialIcon jp-Icon16', 14 | ICON_CLASS: { 15 | filledCircle: 'jp-FilledCircleIcon', 16 | close: 'jp-CloseIcon', 17 | }, 18 | }, 19 | SHARE_FORM: 'jp-Starters-ShareForm', 20 | BUILDER: 'jp-Starters-BodyBuilder', 21 | BUILDER_BUTTONS: 'jp-Starters-BodyBuilder-buttons', 22 | META: 'jp-Starters-NotebookMetadata', 23 | FORM_PANEL: 'jp-Starters-FormPanel', 24 | PREVIEW: 'jp-Starters-Preview', 25 | LAUNCHER: { 26 | CARD: 'jp-LauncherCard', 27 | ICON: 'jp-LauncherCard-icon', 28 | LABEL: 'jp-LauncherCard-label', 29 | }, 30 | SVG: { 31 | DEFAULT_ICON: DEFAULT_ICON_SVG, 32 | COOKIECUTTER: COOKIECUTTER_SVG, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## References 9 | 10 | 11 | 12 | 13 | 14 | ## Code changes 15 | 16 | 17 | 18 | ## User-facing changes 19 | 20 | 21 | 22 | 23 | 24 | ## Backwards-incompatible changes 25 | 26 | 27 | 28 | ## Chores 29 | 30 | - [ ] linted 31 | - [ ] tested 32 | - [ ] checked on binder 33 | - [ ] documented 34 | - [ ] changelog entry 35 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/previewcard/index.tsx: -------------------------------------------------------------------------------- 1 | import { VDomRenderer } from '@jupyterlab/apputils'; 2 | import * as React from 'react'; 3 | 4 | import { CSS } from '../../css'; 5 | 6 | import { PreviewCardModel } from './model'; 7 | 8 | export class PreviewCard extends VDomRenderer { 9 | constructor(options: PreviewCardModel.IOptions = {}) { 10 | super(new PreviewCardModel(options)); 11 | this.addClass(CSS.PREVIEW); 12 | } 13 | 14 | render(): JSX.Element { 15 | const m = this.model; 16 | 17 | const { starter, iconURI } = m; 18 | 19 | if (!starter) { 20 | return ( 21 |
22 |
23 |
24 | ); 25 | } 26 | 27 | return ( 28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 |

{starter.label}

36 |
37 |
38 |
39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/errors.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm .errors { 2 | order: 999; 3 | color: var(--jp-warn-color0); 4 | } 5 | 6 | .jp-SchemaForm .errors h3 { 7 | margin: 0; 8 | } 9 | 10 | .jp-SchemaForm .errors ul, 11 | .jp-SchemaForm .errors li { 12 | list-style: none; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .jp-SchemaForm .has-error > input[type='text'], 18 | .jp-SchemaForm .has-error > input[type='number'], 19 | .jp-SchemaForm .has-error > input[type='date'], 20 | .jp-SchemaForm .has-error > input[type='datetime-local'], 21 | .jp-SchemaForm .has-error > input[type='url'], 22 | .jp-SchemaForm .has-error > input[type='email'], 23 | .jp-SchemaForm .has-error > input[type='file'], 24 | .jp-SchemaForm .has-error > input[type='password'], 25 | .jp-SchemaForm .has-error > input[type='color'], 26 | .jp-SchemaForm .has-error > textarea { 27 | border: solid 1px var(--jp-warn-color1); 28 | } 29 | 30 | .jp-SchemaForm .field .error-detail { 31 | list-style: none; 32 | color: var(--jp-warn-color0); 33 | margin: 0; 34 | padding: 0; 35 | } 36 | 37 | .jp-SchemaForm .field .error-detail li { 38 | margin-top: calc(var(--jp-ui-font-size1) / 2); 39 | } 40 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/fields/codemirror/index.tsx: -------------------------------------------------------------------------------- 1 | import type CodeMirror from 'codemirror'; 2 | import * as React from 'react'; 3 | import { UnControlled } from 'react-codemirror2'; 4 | 5 | export function CodeMirrorField(props: Record): JSX.Element { 6 | const { options } = props; 7 | 8 | const cmOptions = { 9 | ...codeMirrorDefaults(), 10 | ...(options?.cmOptions || {}), 11 | }; 12 | 13 | const onChange = (editor: CodeMirror.Editor, data: any, value: string) => { 14 | props.onChange(value); 15 | }; 16 | 17 | return ( 18 | <> 19 |
20 | 27 |
28 | 29 | ); 30 | } 31 | 32 | export function codeMirrorDefaults(): Record { 33 | const isLight = !!document.querySelector('body[data-jp-theme-light="true"]'); 34 | 35 | return { 36 | theme: isLight ? 'default' : 'zenburn', 37 | matchBrackets: true, 38 | autoCloseBrackets: true, 39 | lineWrapping: true, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/notebookbutton.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 | export class NotebookStarter 10 | implements DocumentRegistry.IWidgetExtension 11 | { 12 | private _commands: CommandRegistry; 13 | 14 | constructor(options: NotebookStarter.IOptions) { 15 | this._commands = options.commands; 16 | } 17 | 18 | createNew( 19 | panel: NotebookPanel, 20 | _context: DocumentRegistry.IContext 21 | ): IDisposable { 22 | let button = new CommandToolbarButton({ 23 | commands: this._commands, 24 | id: CommandIDs.notebookMeta, 25 | }); 26 | 27 | panel.toolbar.insertItem(10, 'starter-notebook', button); 28 | 29 | return new DisposableDelegate(() => button.dispose()); 30 | } 31 | } 32 | 33 | export namespace NotebookStarter { 34 | export interface IOptions { 35 | commands: CommandRegistry; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/intent_to_implement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Intent to Implement 3 | about: Announce that you're going to do some work 4 | --- 5 | 6 | 13 | 14 | ## What are you trying to do? 15 | 16 | 17 | 18 | ## How is it done today, and what are the limits of current practice? 19 | 20 | 21 | 22 | ## What is new in your approach and why do you think it will be successful? 23 | 24 | ## Who cares? If you are successful, what difference will it make? 25 | 26 | ## What are the risks? 27 | 28 | ## How much will it cost? 29 | 30 | 31 | 32 | ## How long will it take? 33 | 34 | ## What are the mid-term and final “exams” to check for success? 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/jupyter_starters/json_.py: -------------------------------------------------------------------------------- 1 | """Some third-party preferred alternatives to stdlib/status quo for parsing and 2 | validating JSON.""" 3 | # pylint: disable=invalid-name,c-extension-no-member 4 | from typing import Any, Callable, Dict, Text 5 | 6 | try: 7 | import ujson 8 | 9 | loads = ujson.loads 10 | dumps = ujson.dumps 11 | except ImportError: # pragma: no cover 12 | import json 13 | 14 | loads = json.loads # type: ignore 15 | dumps = json.dumps # type: ignore 16 | 17 | try: 18 | import fastjsonschema.compile as json_validator 19 | from fastjsonschema import JsonSchemaException 20 | except ImportError: 21 | from jsonschema import validate 22 | from jsonschema.exceptions import ValidationError as JsonSchemaException 23 | from jsonschema.validators import validator_for 24 | 25 | def json_validator(schema: Dict[Text, Any]) -> Callable[[Dict[Text, Any]], Any]: 26 | """Implements that fastjsonschema.compile API with jsonschema.""" 27 | validator_cls = validator_for(schema) 28 | 29 | def _validate(instance: Dict[Text, Any]) -> Any: 30 | validate(instance, schema, cls=validator_cls) 31 | return instance 32 | 33 | return _validate 34 | 35 | 36 | __all__ = ["loads", "dumps", "json_validator", "JsonSchemaException"] 37 | -------------------------------------------------------------------------------- /atest/ports.py: -------------------------------------------------------------------------------- 1 | """Get a random port.""" 2 | 3 | import socket 4 | import time 5 | import urllib.request 6 | 7 | from robot.api import logger 8 | 9 | 10 | def get_unused_port(): 11 | """Get an unused port by trying to listen to any random port. 12 | 13 | Probably could introduce race conditions if inside a tight loop. 14 | """ 15 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | sock.bind(("localhost", 0)) 17 | sock.listen(1) 18 | port = sock.getsockname()[1] 19 | sock.close() 20 | return port 21 | 22 | 23 | def wait_for_url_status( 24 | url: str, status_code: int, interval_sec: float = 0.1, attempts: int = 100 25 | ) -> bool: 26 | """Attempt to fetch from the URL until, or raise an.""" 27 | response = None 28 | 29 | for i in range(attempts): 30 | try: 31 | response = urllib.request.urlopen(url) 32 | if response.status == status_code: 33 | break 34 | logger.debug(response.status) 35 | except Exception: 36 | logger.debug(Exception) 37 | time.sleep(interval_sec) 38 | 39 | if not response or response.status != status_code: 40 | raise RuntimeError( 41 | f"{url} did NOT return {status_code} within {interval_sec * attempts}s" 42 | ) 43 | 44 | return True 45 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deathbeds/jupyterlab-rjsf", 3 | "version": "2.0.0-alpha0", 4 | "description": "React JSON Schema Form for JupyterLab", 5 | "license": "BSD-3-Clause", 6 | "author": "dead pixels collective", 7 | "homepage": "https://github.com/deathbeds/jupyterlab-starters", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/deathbeds/jupyterlab-starters.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/deathbeds/jupyterlab-starters/issues" 14 | }, 15 | "main": "lib/index.js", 16 | "files": [ 17 | "{lib,style,src}/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,css,ts,tsx}" 18 | ], 19 | "scripts": { 20 | "bundle": "npm pack .", 21 | "clean": "rimraf lib", 22 | "upload": "jlpm publish ." 23 | }, 24 | "types": "lib/index.d.ts", 25 | "dependencies": { 26 | "@jupyterlab/apputils": "3", 27 | "@jupyterlab/rendermime": "3", 28 | "@rjsf/core": "^5.0.1", 29 | "@rjsf/utils": "^5.0.1", 30 | "@rjsf/validator-ajv8": "^5.0.1", 31 | "react-codemirror2": "^7.2.1" 32 | }, 33 | "devDependencies": { 34 | "@types/codemirror": "^0.0.97", 35 | "@types/react": "^17.0.0", 36 | "react": "^17.0.1" 37 | }, 38 | "keywords": [ 39 | "jupyter", 40 | "jupyterlab", 41 | "jupyterlab-extension" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/icons/cookiecutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/whitepaper-multiple/00 Introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# My Next Big Idea" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import pathlib\n", 17 | "import urllib.parse\n", 18 | "\n", 19 | "import IPython\n", 20 | "\n", 21 | "# generate a little table of contents\n", 22 | "IPython.display.Markdown(\n", 23 | " \"\".join(\n", 24 | " [\n", 25 | " f\"\"\"\n", 26 | "- [{path.name[3:-6]}](./{urllib.parse.quote(path.name)})\"\"\"\n", 27 | " for path in sorted(pathlib.Path().glob(\"*.ipynb\"))\n", 28 | " ]\n", 29 | " )\n", 30 | ")" 31 | ] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3 (ipykernel)", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.10.6" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 4 55 | } 56 | -------------------------------------------------------------------------------- /docs/notebooks/Developers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## For Developers\n", 8 | "\n", 9 | "> and other adventurous scientists" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": { 15 | "nbsphinx-toctree": { 16 | "maxdepth": 4 17 | } 18 | }, 19 | "source": [ 20 | "```{toctree}\n", 21 | "\n", 22 | "Use Cases.ipynb\n", 23 | "Packaging.ipynb\n", 24 | "JupyterLite.ipynb\n", 25 | "../api.rst\n", 26 | "REST API.ipynb\n", 27 | "```" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "```{toctree}\n", 35 | ":maxdepth: 1\n", 36 | "\n", 37 | "../schema/v3\n", 38 | "../schema/index\n", 39 | "```" 40 | ] 41 | } 42 | ], 43 | "metadata": { 44 | "kernelspec": { 45 | "display_name": "Python 3 (ipykernel)", 46 | "language": "python", 47 | "name": "python3" 48 | }, 49 | "language_info": { 50 | "codemirror_mode": { 51 | "name": "ipython", 52 | "version": 3 53 | }, 54 | "file_extension": ".py", 55 | "mimetype": "text/x-python", 56 | "name": "python", 57 | "nbconvert_exporter": "python", 58 | "pygments_lexer": "ipython3", 59 | "version": "3.10.6" 60 | } 61 | }, 62 | "nbformat": 4, 63 | "nbformat_minor": 4 64 | } 65 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/plugins/browser.ts: -------------------------------------------------------------------------------- 1 | import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; 2 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 3 | 4 | import { SettingsProvider } from '../providers/settings'; 5 | import { BrowserStarterRunner } from '../runners/browser'; 6 | import { IStarterManager, SETTINGS_PLUGIN_ID, PKG, SETTINGS_NAME } from '../tokens'; 7 | 8 | export const settingsProviderPlugin: JupyterFrontEndPlugin = { 9 | id: SETTINGS_PLUGIN_ID, 10 | requires: [IStarterManager, ISettingRegistry], 11 | autoStart: true, 12 | activate: ( 13 | app: JupyterFrontEnd, 14 | manager: IStarterManager, 15 | settingRegistry: ISettingRegistry 16 | ) => { 17 | const provider = new SettingsProvider({ 18 | settingsGetter: async () => { 19 | const { composite } = await settingRegistry.load(SETTINGS_PLUGIN_ID); 20 | return composite; 21 | }, 22 | }); 23 | manager.addProvider(SETTINGS_NAME, provider); 24 | }, 25 | }; 26 | 27 | export const browserRunnerPlugin: JupyterFrontEndPlugin = { 28 | id: `${PKG.name}:browser-runner`, 29 | requires: [IStarterManager], 30 | autoStart: true, 31 | activate: (app: JupyterFrontEnd, manager: IStarterManager) => { 32 | const { contents } = app.serviceManager; 33 | const runner = new BrowserStarterRunner({ manager, contents }); 34 | manager.addRunner('browser', runner); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/icons/form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/core.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm { 2 | color: var(--jp-ui-font-color1); 3 | } 4 | 5 | .jp-SchemaForm > .form-group { 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | .jp-SchemaForm > form { 11 | padding: var(--jp-ui-font-size1); 12 | } 13 | 14 | .jp-SchemaForm fieldset { 15 | border: 0; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | .jp-SchemaForm fieldset fieldset { 21 | padding-left: var(--jp-notebook-padding); 22 | border-left: solid 4px var(--jp-border-color3); 23 | } 24 | 25 | .jp-SchemaForm button[type='submit'] { 26 | display: none; 27 | } 28 | 29 | .jp-SchemaForm .control-label, 30 | .jp-SchemaForm legend { 31 | margin: 0; 32 | padding: 0; 33 | font-weight: bold; 34 | display: block; 35 | position: relative; 36 | } 37 | 38 | .jp-SchemaForm legend > :first-child, 39 | .jp-SchemaForm .control-label > :first-child { 40 | margin-top: 0; 41 | padding-top: 0; 42 | } 43 | 44 | .jp-SchemaForm .form-group .form-group { 45 | margin-bottom: var(--jp-ui-font-size1); 46 | } 47 | 48 | .jp-SchemaForm .form-group:last-child { 49 | margin-bottom: 0; 50 | } 51 | 52 | .jp-SchemaForm .field-description, 53 | .jp-SchemaForm .help-block { 54 | color: var(--jp-ui-font-color2); 55 | } 56 | 57 | .jp-SchemaForm .required { 58 | font-size: var(--jp-ui-font-size3); 59 | color: var(--jp-warn-color1); 60 | font-weight: 800; 61 | position: absolute; 62 | top: 0; 63 | right: 0; 64 | display: block; 65 | } 66 | -------------------------------------------------------------------------------- /src/jupyter_starters/tests/test_src.py: -------------------------------------------------------------------------------- 1 | """Test the sanity of the src/py_src behavior.""" 2 | # pylint: disable=too-many-arguments 3 | from pathlib import Path 4 | 5 | import pytest 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "name,starter", 10 | [ 11 | ["doesn't exist", {"src": "doesnt-exist"}], 12 | ["not importable", {"py_src": "not_a_module", "src": "doesnt-matter"}], 13 | ["no src", {"py_src": "won't matter"}], 14 | ["nothing", {}], 15 | ], 16 | ) 17 | def test_bad_src(starter_manager, name, starter): 18 | """These are bad sources.""" 19 | src = starter_manager.resolve_src(starter) 20 | assert src is None, f"{name} should have been None" 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "name,starter,py_path", 25 | [ 26 | ["local file", {"src": "README.md"}, False], 27 | ["local directory", {"src": "my_module/starter_content"}, False], 28 | ["py file", {"src": "__init__.py", "py_src": "my_module"}, True], 29 | ["py directory", {"src": "starter_content", "py_src": "my_module"}, True], 30 | ], 31 | ) 32 | def test_good_src( 33 | name, starter, py_path, monkeypatch, example_project, starter_manager 34 | ): 35 | """These are good sources.""" 36 | if py_path: 37 | monkeypatch.syspath_prepend(example_project) 38 | else: 39 | monkeypatch.chdir(example_project) 40 | 41 | src = starter_manager.resolve_src(starter) 42 | assert src and isinstance(src, Path) and src.exists(), f"{name} {src} is bad" 43 | -------------------------------------------------------------------------------- /docs/_static/copybutton.css: -------------------------------------------------------------------------------- 1 | /* Copy buttons */ 2 | a.copybtn { 3 | position: absolute; 4 | top: 0.5em; 5 | right: 0.5em; 6 | width: 1em; 7 | height: 1em; 8 | opacity: 0.8; 9 | transition: opacity 0.5s; 10 | } 11 | 12 | div.highlight { 13 | position: relative; 14 | } 15 | 16 | a.copybtn > img { 17 | vertical-align: top; 18 | } 19 | 20 | .highlight:hover .copybtn { 21 | opacity: 1; 22 | } 23 | 24 | /** 25 | * A minimal CSS-only tooltip copied from: 26 | * https://codepen.io/mildrenben/pen/rVBrpK 27 | * 28 | * To use, write HTML like the following: 29 | * 30 | *

Short

31 | */ 32 | .o-tooltip--left { 33 | position: relative; 34 | } 35 | 36 | .o-tooltip--left::after { 37 | opacity: 0; 38 | visibility: hidden; 39 | position: absolute; 40 | content: attr(data-tooltip); 41 | padding: 0; 42 | padding-right: 0.5em; 43 | top: 0; 44 | left: 0; 45 | background: transparent; 46 | font-size: 1rem; 47 | color: var(--deathbeds); 48 | white-space: nowrap; 49 | z-index: 2; 50 | transform: translateX(-102%) translateY(0); 51 | transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), 52 | transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 53 | } 54 | 55 | .o-tooltip--left:hover::after { 56 | display: block; 57 | opacity: 1; 58 | visibility: visible; 59 | transform: translateX(-100%) translateY(0); 60 | transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), 61 | transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 62 | } 63 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/providers/server.ts: -------------------------------------------------------------------------------- 1 | import { ServerConnection } from '@jupyterlab/services'; 2 | import { PromiseDelegate } from '@lumino/coreutils'; 3 | import { ISignal, Signal } from '@lumino/signaling'; 4 | 5 | import * as SCHEMA from '../_schema'; 6 | import { IStarterProvider, API } from '../tokens'; 7 | 8 | const { makeRequest, makeSettings } = ServerConnection; 9 | 10 | /** Discovers starters from the server. */ 11 | export class ServerStarterProvider implements IStarterProvider { 12 | private _starters: SCHEMA.NamedStarters = {}; 13 | private _changed: Signal; 14 | private _ready = new PromiseDelegate(); 15 | 16 | private _serverSettings = makeSettings(); 17 | 18 | constructor() { 19 | this._changed = new Signal(this); 20 | } 21 | 22 | get ready(): Promise { 23 | return this._ready.promise; 24 | } 25 | 26 | get changed(): ISignal { 27 | return this._changed; 28 | } 29 | 30 | async fetch(): Promise { 31 | const response = await makeRequest(API, {}, this._serverSettings); 32 | const content = (await response.json()) as SCHEMA.AResponseForAnStartersRequest; 33 | this._starters = content.starters; 34 | this._changed.emit(void 0); 35 | this._ready.resolve(void 0); 36 | } 37 | 38 | get starters(): SCHEMA.NamedStarters { 39 | return { ...this._starters }; 40 | } 41 | 42 | starter(name: string): SCHEMA.Starter { 43 | return this._starters[name]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/runners/_base.ts: -------------------------------------------------------------------------------- 1 | import { IRunningSessions } from '@jupyterlab/running'; 2 | import { PromiseDelegate } from '@lumino/coreutils'; 3 | import { ISignal, Signal } from '@lumino/signaling'; 4 | 5 | import * as SCHEMA from '../_schema'; 6 | import { IStarterRunner, IStarterManager } from '../tokens'; 7 | 8 | export class BaseStarterRunner implements Partial { 9 | protected _manager: IStarterManager; 10 | protected _ready = new PromiseDelegate(); 11 | protected _runningChanged: Signal; 12 | 13 | constructor(options: BaseStarterRunner.IOptions) { 14 | this._manager = options.manager; 15 | this._runningChanged = new Signal(this as any); 16 | } 17 | 18 | get ready(): Promise { 19 | return this._ready.promise; 20 | } 21 | get runningChanged(): ISignal { 22 | return this._runningChanged; 23 | } 24 | canStart(name: string, _starter: SCHEMA.Starter): boolean { 25 | return true; 26 | } 27 | 28 | async fetch(): Promise { 29 | // nothing yet 30 | } 31 | 32 | async stop(name: string): Promise { 33 | // nothing yets 34 | } 35 | 36 | refreshRunning(): void { 37 | this.fetch().catch(console.warn); 38 | } 39 | 40 | running(): IRunningSessions.IRunningItem[] { 41 | return []; 42 | } 43 | 44 | shutdownAll(): void { 45 | // nothing yet 46 | } 47 | } 48 | 49 | export namespace BaseStarterRunner { 50 | export interface IOptions { 51 | manager: IStarterManager; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /atest/lab/02_Simple.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Simple 3 | 4 | Resource ../Keywords.resource 5 | 6 | Suite Setup Setup Suite For Screenshots lab${/}simple 7 | 8 | Test Tags example:simple 9 | 10 | 11 | *** Test Cases *** 12 | Simple Notebook 13 | [Documentation] Can we start a single notebook? 14 | Click Element ${CSS LAUNCH CARD SINGLE} 15 | Wait Until Created ${HOME}${/}whitepaper-single.ipynb 16 | Capture Page Screenshot 00-notebook-did-create.png 17 | Wait Until Kernel 18 | Wait Until Page Contains Element id:My-Next-Big-Idea 19 | Save Notebook 20 | Capture Page Screenshot 01-notebook-did-open.png 21 | 22 | Simple Folder 23 | [Documentation] Can we start a folder? 24 | Click Element ${CSS LAUNCH CARD FOLDER} 25 | Wait Until Created ${HOME}${/}whitepaper-multiple 26 | Wait Until Page Contains Element ${XP FILE TREE ITEM}/span[text() = '00 Introduction.ipynb'] 27 | Capture Page Screenshot 10-folder-did-copy.png 28 | 29 | Folder Ignoring 30 | [Documentation] Will it ignore paths? 31 | Create File ${HOME}${/}examples${/}whitepaper-multiple${/}node_modules${/}foo.txt 32 | Click Element ${CSS LAUNCH CARD FOLDER} 33 | Wait Until Created ${HOME}${/}whitepaper-multiple 34 | Wait Until Page Contains Element ${XP FILE TREE ITEM}/span[text() = '00 Introduction.ipynb'] 35 | Page Should Not Contain Element ${XP FILE TREE ITEM}/span[text() = 'node_modules'] 36 | Capture Page Screenshot 20-files-were-ignored.png 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, dead pixels collective 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 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, dead pixels collective 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 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/providers/settings.ts: -------------------------------------------------------------------------------- 1 | import { PromiseDelegate, ReadonlyPartialJSONObject } from '@lumino/coreutils'; 2 | import { ISignal, Signal } from '@lumino/signaling'; 3 | 4 | import * as SCHEMA from '../_schema'; 5 | import { IStarterProvider } from '../tokens'; 6 | 7 | export class SettingsProvider implements IStarterProvider { 8 | private _starters: SCHEMA.NamedStarters = {}; 9 | private _changed: Signal; 10 | private _ready = new PromiseDelegate(); 11 | 12 | private _settingsGetter: SettingsProvider.ISettingsGetter; 13 | 14 | constructor(options: SettingsProvider.IOptions) { 15 | this._settingsGetter = options.settingsGetter; 16 | this._changed = new Signal(this); 17 | } 18 | 19 | get ready(): Promise { 20 | return this._ready.promise; 21 | } 22 | 23 | get changed(): ISignal { 24 | return this._changed; 25 | } 26 | 27 | async fetch(): Promise { 28 | const settings = await this._settingsGetter(); 29 | this._starters = (settings['starters'] as SCHEMA.NamedStarters) || {}; 30 | this._changed.emit(void 0); 31 | this._ready.resolve(void 0); 32 | } 33 | 34 | get starters(): SCHEMA.NamedStarters { 35 | return { ...this._starters }; 36 | } 37 | 38 | starter(name: string): SCHEMA.Starter { 39 | return this._starters[name]; 40 | } 41 | } 42 | 43 | export namespace SettingsProvider { 44 | export interface IOptions { 45 | settingsGetter: ISettingsGetter; 46 | } 47 | export interface ISettingsGetter { 48 | (): Promise; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, dead pixels collective 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 | -------------------------------------------------------------------------------- /scripts/preflight.py: -------------------------------------------------------------------------------- 1 | """Check preflight consistency.""" 2 | import pathlib 3 | 4 | # pylint: disable=redefined-outer-name,unused-variable 5 | import re 6 | import subprocess 7 | import sys 8 | import tempfile 9 | from importlib.util import find_spec 10 | 11 | import pytest 12 | 13 | ROOT = pathlib.Path(__file__).parent.parent 14 | 15 | # TS stuff 16 | NPM_NS = "@deathbeds" 17 | MAIN_NAME = f"{NPM_NS}/jupyterlab-starters" 18 | 19 | # py stuff 20 | PY_NAME = "jupyter_starters" 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "kind,expect", 25 | [ 26 | ["serverextension", f"{PY_NAME}.*ok"], 27 | ["labextension", f"{MAIN_NAME}.*enabled.*ok"], 28 | ], 29 | ) 30 | def test_extension_cli(kind, expect): 31 | """does (at least) the CLI think the extensions are installed?""" 32 | proc = subprocess.Popen( 33 | ["jupyter", kind, "list"], stderr=subprocess.PIPE, stdout=subprocess.PIPE 34 | ) 35 | out, err = proc.communicate() 36 | all_out = f"""{out.decode("utf-8")}{err.decode("utf-8")}""".lower() 37 | assert re.findall(expect, all_out), f"failed to find {expect} in:\n{all_out}" 38 | 39 | 40 | def preflight(): 41 | """Run the tests.""" 42 | with tempfile.TemporaryDirectory() as tmpd: 43 | ini = pathlib.Path(tmpd) / "pytest.ini" 44 | ini.write_text((ROOT / "scripts" / "fake_pytest.ini").read_text()) 45 | 46 | args = ["-c", str(ini), "-vv", __file__] 47 | try: 48 | if find_spec("pytest_azurepipelines"): 49 | args += ["--no-coverage-upload"] 50 | except ImportError: 51 | pass 52 | 53 | return pytest.main(args) 54 | 55 | 56 | if __name__ == "__main__": 57 | sys.exit(preflight()) 58 | -------------------------------------------------------------------------------- /atest/lab/04_Cookiecutter.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Cookiecutter 3 | 4 | Resource ../Keywords.resource 5 | Library String 6 | 7 | Suite Setup Setup Suite For Screenshots lab${/}cookiecutter 8 | 9 | Test Tags example:cookiecutter 10 | 11 | 12 | *** Test Cases *** 13 | Happy Path 14 | [Documentation] Can we use the cookiecutter? 15 | Click Element ${CSS LAUNCH CARD COOKIECUTTER} 16 | ${template css} = Set Variable css:input[label\="Template"] 17 | ${size css} = Set Variable css:.jp-SchemaForm select[id$\="idea_size"] 18 | ${index ipynb} = Set Variable ${XP FILE TREE ITEM}/span[text() = 'index.ipynb'] 19 | Wait For And Capture ${template css} 00-cookiecutter-did-launch.png 20 | Click Element ${template css} 21 | Really Input Text ${template css} ./examples/cookiecutter 22 | Advance Starter Form 23 | Capture Page Screenshot 01-cookiecutter-did-advance.png 24 | Wait Until Page Contains Element ${size css} 25 | Select From List By Label ${size css} Little 26 | Advance Starter Form 27 | Wait For And Capture ${index ipynb} 02-cookiecutter-advanced-again.png 28 | Double Click Element ${index ipynb} 29 | Wait Until Kernel 30 | Wait Until Page Contains Element id:My-Next-Little-Idea 31 | Save Notebook 32 | Capture Page Screenshot 03-cookiecutter-did-complete.png 33 | 34 | 35 | *** Keywords *** 36 | Wait For And Capture 37 | [Documentation] Shorthand to await and capture 38 | [Arguments] ${selector} ${screenshot} 39 | Wait Until Page Contains Element ${selector} 40 | Capture Page Screenshot ${screenshot} 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 12 | 13 | ## Description 14 | 15 | 16 | 17 | ## Reproduce 18 | 19 | 20 | 21 | 1. Go to '...' 22 | 2. Click on '...' 23 | 3. Scroll down to '...' 24 | 4. See error '...' 25 | 26 | 28 | 29 | ## Expected behavior 30 | 31 | 32 | 33 | ## Context 34 | 35 | 36 | 37 | - Operating System and version: 38 | - Browser and version: 39 | - JupyterLab version: 40 | - `jupyter-starters` version: 41 | - `@deathbeds/jupyterlab-starters` version: 42 | 43 |
Troubleshoot Output 44 |
45 | Paste the output from running `jupyter troubleshoot` from the command line here.
46 | You may want to sanitize the paths in the output.
47 | 
48 |
49 | 50 |
Command Line Output 51 |
52 | Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
53 | 
54 |
55 | 56 |
Browser Output 57 |
58 | Paste the output from your browser Javascript console here.
59 | 
60 |
61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to jupyter(lab)-starters 2 | 3 | `jupyter-starters` and `jupyterlab-starters` are [open source](./LICENSE) software, and 4 | all contributions conforming to good sense, good taste, and the [Jupyter Code of 5 | Conduct][code-of-conduct] are welcome, and will be reviewed by the contributors, 6 | time-permitting. 7 | 8 | [code-of-conduct]: 9 | https://github.com/jupyter/governance/blob/main/conduct/code_of_conduct.md 10 | 11 | ## setting up 12 | 13 | > _There are probably other ways, but I haven't tried them_ 14 | 15 | - Be on Linux/OSX 16 | - Get [Mambaforge](https://github.com/conda-forge/miniforge/releases) 17 | - Get [doit](https://pydoit.org) 18 | 19 | ```bash 20 | mamba install doit 21 | ``` 22 | 23 | ```bash 24 | doit 25 | ``` 26 | 27 | Now you should have a working Lab. 28 | 29 | ```bash 30 | doit lab 31 | ``` 32 | 33 | Try out some stuff. Make some whitepapers and cookiecutters. 34 | 35 | ## linting 36 | 37 | ```bash 38 | doit lint 39 | ``` 40 | 41 | ## testing 42 | 43 | ```bash 44 | doit test 45 | ``` 46 | 47 | > _You may want to run against a "clean" lab, e.g. `doit`_ 48 | 49 | ## hacking 50 | 51 | ```bash 52 | doit watch:lab 53 | ``` 54 | 55 | ...in another terminal 56 | 57 | ```bash 58 | doit lab 59 | ``` 60 | 61 | ## documenting 62 | 63 | ```bash 64 | doit docs 65 | ``` 66 | 67 | ...or watch for changes 68 | 69 | ```bash 70 | doit watch:docs 71 | ``` 72 | 73 | ## locking 74 | 75 | ``` 76 | mamba create -p .envs/_lock --file .github/locks/lock-linux-64-3.11.conda.lock 77 | source .envs/_lock/bin/activate 78 | doit lock 79 | ``` 80 | 81 | ## releasing 82 | 83 | - Download and unpack the artifacts from CI into `dist` 84 | - Make a GitHub release with all of the release artifacts 85 | - Upload the releases 86 | 87 | ```bash 88 | twine upload dist/*.whl dist/*.tar.gz 89 | npm publish dist/*.tgz 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/_templates/demo.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | Try Jupyter Starters Now 11 | 12 | 13 |
14 | Start with... 15 | 19 | 23 |
24 | 25 | Powered by JupyterLite 26 | 27 | 48 |
49 | -------------------------------------------------------------------------------- /docs/notebooks/History.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# For Posterity" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": { 14 | "tags": [ 15 | "hide-input" 16 | ] 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import pathlib\n", 21 | "\n", 22 | "import IPython\n", 23 | "\n", 24 | "HERE = pathlib.Path.cwd()" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "```{include} ../../CHANGELOG.md\n", 32 | "\n", 33 | "```" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": { 39 | "tags": [ 40 | "hide-input" 41 | ] 42 | }, 43 | "source": [ 44 | "```{include} ../../ROADMAP.md\n", 45 | "\n", 46 | "```" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "# License" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "tags": [ 61 | "hide-input" 62 | ] 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "IPython.display.Markdown(\n", 67 | " f\"\"\"```\n", 68 | "{(HERE / \"..\" / \"..\" / \"LICENSE\").read_text()}\n", 69 | "```\"\"\"\n", 70 | ")" 71 | ] 72 | } 73 | ], 74 | "metadata": { 75 | "kernelspec": { 76 | "display_name": "Python 3 (ipykernel)", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "codemirror_mode": { 82 | "name": "ipython", 83 | "version": 3 84 | }, 85 | "file_extension": ".py", 86 | "mimetype": "text/x-python", 87 | "name": "python", 88 | "nbconvert_exporter": "python", 89 | "pygments_lexer": "ipython3", 90 | "version": "3.10.6" 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 4 95 | } 96 | -------------------------------------------------------------------------------- /atest/CodeMirror.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Common behaviors for CodeMirror instances 3 | 4 | 5 | *** Keywords *** 6 | Set CodeMirror Value 7 | [Documentation] Set the value in the CodeMirror attached to the element 8 | ... that matches a ``css`` selector to be the given ``text``. 9 | [Arguments] ${css} ${code} 10 | Select All CodeMirror Text ${css} 11 | Replace CodeMirror Selection ${css} ${code} 12 | 13 | Select All CodeMirror Text 14 | [Documentation] Select all of the text in the CodeMirror attached to the element 15 | ... matched by a ``css`` selector. 16 | [Arguments] ${css} 17 | Execute CodeMirror Command ${css} selectAll 18 | 19 | Execute CodeMirror Command 20 | [Documentation] Run a CodeMirror [https://codemirror.net/doc/manual.html#commands:command] 21 | ... ``cmd`` for the editor attached to element that matches a ``css`` selector 22 | [Arguments] ${css} ${cmd} 23 | Call CodeMirror Method ${css} execCommand("${cmd}") 24 | 25 | Replace CodeMirror Selection 26 | [Documentation] Replace all of the text in the CodeMirror attached to the element 27 | ... that matches a ``css`` selector with the given ``text``. 28 | [Arguments] ${css} ${code} 29 | Call CodeMirror Method ${css} replaceSelection(`${code}`) 30 | 31 | Call CodeMirror Method 32 | [Documentation] Construct and a method call against in the CodeMirror attached to the element 33 | ... that matches a ``css`` selector with the given ``js`` code. 34 | [Arguments] ${css} ${js} 35 | Wait Until Page Contains Element css:${css} 36 | ${res} = Execute JavaScript return document.querySelector(`${css}`).CodeMirror.${js} 37 | RETURN ${res} 38 | 39 | CodeMirror Value Contains 40 | [Documentation] Handle the common case of checking for text in a CodeMirror 41 | [Arguments] ${css} ${text} 42 | ${value} = Call CodeMirror Method ${css} getValue() 43 | Should Contain ${value} ${text} 44 | -------------------------------------------------------------------------------- /docs/notebooks/Use Cases.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Use Cases" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### The Demo\n", 15 | "\n", 16 | "> I built a cool [Binder](https://mybinder.org) (and even included\n", 17 | "> `?urlpath=lab/tree/tutorial.ipynb` to open a notebook) to showcase my library, but\n", 18 | "> when visitors get to their Binder, they still have to _read a notebook_ and\n", 19 | "> `shift+enter` their way through my example to see the plot at the end.\n", 20 | "\n", 21 | "A starter can _increase engagement_ by:\n", 22 | "\n", 23 | "- creating personalized content\n", 24 | "- running\n", 25 | " [JupyterLab commands](https://jupyterlab.readthedocs.io/en/stable/user/commands.html)\n", 26 | " on the users behalf, including the mighty `Run All Cells`\n", 27 | "- launching a starter directly, with a\n", 28 | " [Starter Tree URL](Starters.ipynb#Starter-Tree-URL)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "### The Chore\n", 36 | "\n", 37 | "> When my scientists start a new experiment they hope will become reproducible research,\n", 38 | "> they start a new folder that contains a notebook, a spreadsheet and a few other\n", 39 | "> artifacts. Sometimes they don't do it... quite right.\n", 40 | "\n", 41 | "A starter can _enhance reproducibility_ by:\n", 42 | "\n", 43 | "- automating boring chores\n", 44 | "- standardizing file names" 45 | ] 46 | } 47 | ], 48 | "metadata": { 49 | "kernelspec": { 50 | "display_name": "Python 3 (ipykernel)", 51 | "language": "python", 52 | "name": "python3" 53 | }, 54 | "language_info": { 55 | "codemirror_mode": { 56 | "name": "ipython", 57 | "version": 3 58 | }, 59 | "file_extension": ".py", 60 | "mimetype": "text/x-python", 61 | "name": "python", 62 | "nbconvert_exporter": "python", 63 | "pygments_lexer": "ipython3", 64 | "version": "3.10.6" 65 | } 66 | }, 67 | "nbformat": 4, 68 | "nbformat_minor": 4 69 | } 70 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/array.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm .array-item-move-up::before { 2 | content: var(--jp-schema-form-up); 3 | } 4 | 5 | .jp-SchemaForm .array-item-move-down::before { 6 | content: var(--jp-schema-form-down); 7 | } 8 | 9 | .jp-SchemaForm .array-item-remove::before { 10 | content: var(--jp-schema-form-remove); 11 | } 12 | 13 | .jp-SchemaForm .array-item-remove { 14 | content: var(--jp-schema-form-remove); 15 | } 16 | 17 | .jp-SchemaForm .array-item-add button::before { 18 | content: var(--jp-schema-form-add); 19 | } 20 | 21 | .jp-SchemaForm .array-item-add button { 22 | background-color: var(--jp-brand-color2); 23 | color: var(--jp-ui-inverse-font-color0); 24 | } 25 | 26 | .jp-SchemaForm .array-item-add button:hover { 27 | background-color: var(--jp-brand-color1); 28 | color: var(--jp-ui-inverse-font-color0); 29 | } 30 | 31 | .jp-SchemaForm .array-item-toolbox { 32 | opacity: 0.5; 33 | flex: 0; 34 | padding-right: var(--jp-ui-font-size1); 35 | margin-bottom: calc(var(--jp-ui-font-size1) / 2); 36 | order: 1; 37 | } 38 | 39 | .jp-SchemaForm .array-item:hover > .array-item-toolbox { 40 | opacity: 1; 41 | } 42 | 43 | .jp-SchemaForm .array-item-toolbox .btn-group { 44 | display: flex; 45 | flex-direction: column; 46 | } 47 | 48 | .jp-SchemaForm .array-item-toolbox .btn { 49 | margin-bottom: calc(var(--jp-ui-font-size1) / 2); 50 | } 51 | 52 | .jp-SchemaForm .array-item-toolbox .btn:hover:not([disabled]) { 53 | background-color: var(--jp-layout-color3); 54 | } 55 | 56 | .jp-SchemaForm .array-item-toolbox .btn.array-item-remove:hover { 57 | background-color: var(--jp-warn-color2); 58 | color: var(--jp-ui-inverse-font-color0); 59 | } 60 | 61 | .jp-SchemaForm .array-item-toolbox .btn[disabled] { 62 | opacity: 0.1; 63 | cursor: default; 64 | } 65 | 66 | .jp-SchemaForm .array-item { 67 | display: flex; 68 | position: relative; 69 | } 70 | 71 | .jp-SchemaForm .array-item:not(:last-child) { 72 | border-bottom: var(--jp-border-width) solid var(--jp-border-color0); 73 | margin-bottom: var(--jp-ui-font-size1); 74 | padding-bottom: var(--jp-ui-font-size1); 75 | } 76 | 77 | .jp-SchemaForm .array-item div:first-child { 78 | flex: 1; 79 | order: 2; 80 | } 81 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "flit_core.buildapi" 3 | requires = [ 4 | "flit_core<4,>=3.7.1", 5 | ] 6 | 7 | [project] 8 | name = "jupyter_starters" 9 | version = "2.0.0a0" 10 | readme = "README.md" 11 | authors = [ 12 | {name = "jupyter-starters contributors", email = "deathbeds@googlegroups.com"}, 13 | ] 14 | requires-python = ">=3.8" 15 | dependencies = [ 16 | "jupyterlab >=3", 17 | "jupyter-client >=6.1.0", 18 | ] 19 | dynamic = [ 20 | "description", 21 | ] 22 | classifiers = [ 23 | "Framework :: Jupyter :: JupyterLab :: 3", 24 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 25 | "Framework :: Jupyter :: JupyterLab :: Extensions", 26 | "Framework :: Jupyter :: JupyterLab", 27 | "Framework :: Jupyter", 28 | "License :: OSI Approved :: BSD License", 29 | "Programming Language :: Python :: 3 :: Only", 30 | "Programming Language :: Python :: 3", 31 | ] 32 | 33 | [project.urls] 34 | "Bug Tracker" = "https://github.com/deathbeds/jupyterlab-starters/issues" 35 | "Changelog" = "https://github.com/deathbeds/jupyterlab-starters/blob/main/CHANGELOG.md" 36 | "Documentation" = "https://jupyterstarters.rtfd.io" 37 | "Source" = "https://github.com/deathbeds/jupyterlab-starters" 38 | 39 | [project.scripts] 40 | jupyter-starters = "jupyter_starters.app:main" 41 | 42 | [tool.flit.sdist] 43 | include = ["src/jupyter_starters/_d"] 44 | 45 | [tool.flit.external-data] 46 | directory = "src/jupyter_starters/_d" 47 | 48 | [tool.docformatter] 49 | recursive = true 50 | wrap-summaries = 88 51 | 52 | [tool.doit] 53 | backend = "sqlite3" 54 | verbosity = 2 55 | 56 | [tools.doit.commands.list] 57 | status = true 58 | subtasks = true 59 | 60 | [tool.mypy] 61 | cache_dir = "build/.mypy_cache" 62 | sqlite_cache = true 63 | # language settings 64 | python_version = "3.8" 65 | allow_redefinition = true 66 | # check_untyped_defs = true 67 | # disallow_untyped_defs = true 68 | no_implicit_optional = true 69 | show_error_codes = true 70 | warn_return_any = true 71 | warn_unused_ignores = true 72 | 73 | [[tool.mypy.overrides]] 74 | module = [ 75 | "fastjsonschema", 76 | "fastjsonschema.*", 77 | "cookiecutter", 78 | "cookiecutter.*", 79 | ] 80 | ignore_missing_imports = true 81 | -------------------------------------------------------------------------------- /src/jupyter_starters/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Common test stuff.""" 2 | # pylint: disable=redefined-outer-name 3 | import nbformat.v4 4 | import pytest 5 | import traitlets 6 | from jupyter_client.multikernelmanager import AsyncMultiKernelManager 7 | from jupyter_server.services.contents.filemanager import FileContentsManager 8 | from jupyter_server.services.contents.manager import ContentsManager 9 | from traitlets.config import LoggingConfigurable 10 | 11 | from jupyter_starters.manager import StarterManager 12 | 13 | 14 | class MockApp(LoggingConfigurable): 15 | """Not really a nbapp.""" 16 | 17 | kernel_manager = traitlets.Instance(AsyncMultiKernelManager) 18 | contents_manager = traitlets.Instance(ContentsManager) 19 | notebook_dir = traitlets.Unicode() 20 | 21 | @traitlets.default("kernel_manager") 22 | def _kernel_manager(self): 23 | """Simplest reasonable kernel manager.""" 24 | return AsyncMultiKernelManager(parent=self) 25 | 26 | @traitlets.default("contents_manager") 27 | def _contents_manager(self): 28 | """Simplest reasonable kernel manager.""" 29 | return FileContentsManager(root_dir=self.notebook_dir, parent=self) 30 | 31 | 32 | @pytest.fixture 33 | def starter_manager(mock_app): 34 | """An orphaned starter.""" 35 | return StarterManager(parent=mock_app) 36 | 37 | 38 | @pytest.fixture 39 | def mock_app(monkeypatch, tmp_path): 40 | """A fake notebook app in a tmpdir.""" 41 | monkeypatch.chdir(tmp_path) 42 | return MockApp(notebook_dir=str(tmp_path)) 43 | 44 | 45 | @pytest.fixture 46 | def example_project(tmp_path): 47 | """A minimal project.""" 48 | my_module = tmp_path / "my_module" 49 | starter_content = my_module / "starter_content" 50 | starter_content.mkdir(parents=True) 51 | 52 | (tmp_path / "README.md").write_text("# My Module\n") 53 | (my_module / "__init__.py").write_text("__version__ = '0.0.0\n") 54 | (starter_content / "example.txt").write_text("123") 55 | 56 | return tmp_path 57 | 58 | 59 | @pytest.fixture 60 | def tmp_notebook(tmp_path): 61 | """Make an empty python notebook on disk.""" 62 | notebook = nbformat.v4.new_notebook() 63 | notebook.metadata["kernelspec"] = {"name": "python3"} 64 | nb_path = tmp_path / "Untitled.ipynb" 65 | nb_path.write_text(nbformat.writes(notebook)) 66 | return nb_path 67 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/meta/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SchemaForm, 3 | ALL_CUSTOM_UI, 4 | AS_JSONOBJECT, 5 | AS_TEXTAREA, 6 | AS_XML, 7 | } from '@deathbeds/jupyterlab-rjsf'; 8 | import { JSONObject } from '@lumino/coreutils'; 9 | import { Widget, BoxLayout } from '@lumino/widgets'; 10 | 11 | import { CSS } from '../../css'; 12 | import { PreviewCard } from '../previewcard'; 13 | 14 | import { NotebookMetadataModel } from './model'; 15 | 16 | export class NotebookMetadata extends Widget { 17 | private _form: SchemaForm; 18 | private _preview: PreviewCard; 19 | 20 | model: NotebookMetadataModel; 21 | 22 | constructor(options: NotebookMetadataModel.IOptions) { 23 | super(options); 24 | this.model = new NotebookMetadataModel(options); 25 | this.layout = new BoxLayout(); 26 | this.id = Private.nextId(); 27 | this.addClass(CSS.META); 28 | this.addClass(CSS.FORM_PANEL); 29 | this.initForm().catch(console.error); 30 | } 31 | 32 | protected async initForm(): Promise { 33 | this._form = new SchemaForm( 34 | this.model.liveSchema, 35 | { 36 | liveValidate: true, 37 | uiSchema: { 38 | description: AS_TEXTAREA, 39 | icon: AS_XML, 40 | schema: AS_JSONOBJECT, 41 | uiSchema: AS_JSONOBJECT, 42 | commands: { 43 | items: { 44 | args: AS_JSONOBJECT, 45 | }, 46 | }, 47 | }, 48 | ...(await ALL_CUSTOM_UI()), 49 | }, 50 | { markdown: this.model.manager.markdown } 51 | ); 52 | this.model.form = this._form.model; 53 | 54 | this._preview = new PreviewCard(); 55 | this.model.stateChanged.connect(() => { 56 | this._preview.model.starter = (this.model.form.formData as any) || {}; 57 | this._form.setHidden(!this.model.notebook); 58 | }); 59 | 60 | this.boxLayout.addWidget(this._form); 61 | this.boxLayout.addWidget(this._preview); 62 | } 63 | 64 | get boxLayout(): BoxLayout { 65 | return this.layout as BoxLayout; 66 | } 67 | 68 | dispose(): void { 69 | super.dispose(); 70 | if (!this.isDisposed) { 71 | this.model.dispose(); 72 | } 73 | } 74 | } 75 | 76 | namespace Private { 77 | let _nextId = 0; 78 | export function nextId(): string { 79 | return `id-jp-starters-notebook-${_nextId++}`; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/fields/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An unfortunately very convoluted way to expose some custom react components 3 | * in the face of federated modules 4 | */ 5 | import { FormProps, getDefaultRegistry } from '@rjsf/core'; 6 | import { Widget, Field } from '@rjsf/utils'; 7 | import React from 'react'; 8 | 9 | export type TComponentFactory = (props: Record) => JSX.Element; 10 | export type TComponentMap = Record; 11 | 12 | /** 13 | * a subset of an RJSF `widgets` prop 14 | */ 15 | export const CUSTOM_UI_WIDGETS = async (): Promise['widgets']> => { 16 | const widgets = { 17 | 'codemirror-xml': await XMLField(), 18 | codemirror: await CodeMirrorField(), 19 | 'codemirror-markdown': await MarkdownField(), 20 | }; 21 | 22 | return widgets; 23 | }; 24 | 25 | /** 26 | * a subset of an RJSF `fields` prop 27 | */ 28 | export const CUSTOM_UI_FIELDS = async (): Promise['fields']> => { 29 | const fields = { 30 | 'codemirror-jsonobject': await JSONObjectField(), 31 | }; 32 | 33 | return fields as any; 34 | }; 35 | 36 | /** 37 | * a subset of an RJSF props with all custom elements available 38 | */ 39 | export const ALL_CUSTOM_UI = async (): Promise>> => { 40 | return { 41 | fields: await CUSTOM_UI_FIELDS(), 42 | widgets: await CUSTOM_UI_WIDGETS(), 43 | }; 44 | }; 45 | 46 | /** 47 | * janky lazy references to Field classes 48 | */ 49 | export const CodeMirrorField = async (): Promise => 50 | (await import('./codemirror')).CodeMirrorField; 51 | export const MarkdownField = async (): Promise => 52 | (await import('./markdown')).MarkdownField; 53 | export const XMLField = async (): Promise => (await import('./xml')).XMLField; 54 | export const JSONObjectField = async (): Promise => { 55 | const reg = getDefaultRegistry(); 56 | return (await import('./jsonobject')).makeJSONObjectField( 57 | reg.fields['ObjectField'] as any 58 | ); 59 | }; 60 | 61 | /** 62 | * the RJSF uiSchema for activating these components 63 | */ 64 | export const AS_JSONOBJECT = { 'ui:field': 'codemirror-jsonobject' }; 65 | export const AS_TEXTAREA = { 'ui:widget': 'textarea' }; 66 | export const AS_XML = { 'ui:widget': 'codemirror-xml' }; 67 | export const AS_MARKDOWN = { 'ui:widget': 'codemirror-markdown' }; 68 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-rjsf 2 | 3 | > [React JSON Schema Form][rjsf] for [JupyterLab][lab] 4 | 5 | [rjsf]: https://github.com/rjsf-team/react-jsonschema-form 6 | [lab]: https://github.com/jupyterlab/jupyterlab 7 | 8 | ## For users: Do I need to install this? 9 | 10 | For now, this project just provides some "glue" to be used by other JupyterLab 11 | extensions, and other labextensions that use it should specify it as a `dependency` so 12 | it will be installed for you, if needed. 13 | 14 | > A follow-on release will add more tightly-integrated Document (a la `Notebook`) with a 15 | > `MimeRenderer` for working with JSON instances, JSON Schema, and `rjsf''s UI Schema, 16 | > with support for alternate encodings like YAML and JSONL. See more in the 17 | > [roadmap](#Roadmap). 18 | 19 | ### Related Projects 20 | 21 | Things you can `jupyter labextension install` to use this component: 22 | 23 | - [@deathbeds/jupyterlab-starters](https://github.com/deathbeds/jupyterlab-starters) 24 | uses this component in a sidebar to render things like cookiecutters and notebooks 25 | before making templated files 26 | 27 | - the tests for this component are also mostly contained in this repo, for now 28 | 29 | - [@deathbeds/wxyz](https://github.com/deathbeds/wxyz) uses an earlier version of this 30 | component to connect a JSON Schema to the broader Jupyter Widgets ecosystem, but also 31 | works with other `wxyz` widgets without a "server" kernel. 32 | 33 | ## For Developers 34 | 35 | ### JupyterLab Extensions 36 | 37 | Use the `SchemaForm`, a `@lumino/widget` that you can put inside of any other widget 38 | (such as the `DockPanel`). It's `model` exposes the `schema`, `formData`, other `rjsf` 39 | specifics, and can be connected to with `model.stateChanged`. 40 | 41 | ### React 42 | 43 | Several underlying libraries are used from the broader React ecosystem. 44 | 45 | > `rjsf` in particular has a large pending release (2.x), so some APIs are subject to 46 | > change abruptly in the near future. 47 | 48 | > > TBD: more info on `async-component`, and `rjsf`-specifics like `jsonobject` and 49 | > > `codemirror`, `Form` and `Theme` 50 | 51 | ## Roadmap 52 | 53 | - [ ] `MimeRenderer` 54 | - [ ] `Document` 55 | - [ ] JSON Instance as form 56 | - [ ] JSON Schema as form? 57 | - [ ] `rjsf` UI schema 58 | - [ ] formatting 59 | - - [ ] `rjsf` 2.x 60 | - [ ] additional extension points 61 | - [ ] readers e.g. `YAML`, `JSONL`, `TOML` 62 | - [ ] explore additional form libraries, e.g. `formik` 63 | -------------------------------------------------------------------------------- /src/jupyter_starters/app.py: -------------------------------------------------------------------------------- 1 | """CLI for jupyter-starters.""" 2 | # pylint: disable=too-many-ancestors 3 | import textwrap 4 | 5 | import traitlets as T 6 | from jupyter_core.application import JupyterApp, base_aliases, base_flags 7 | 8 | from ._version import __version__ 9 | from .json_ import dumps 10 | from .manager import StarterManager 11 | 12 | 13 | class StartersBaseApp(JupyterApp): 14 | """A base class for CLI apps.""" 15 | 16 | version = T.Unicode(__version__) 17 | 18 | @property 19 | def description(self): # pragma: no cover 20 | """A human readable description.""" 21 | return self.__doc__.splitlines()[0].strip() 22 | 23 | 24 | class StartersListApp(StartersBaseApp): 25 | """List all installed starters.""" 26 | 27 | json = T.Bool(False, help="output JSON").tag(config=True) 28 | 29 | manager = T.Instance(StarterManager) 30 | 31 | flags = dict( 32 | **base_flags, 33 | **{ 34 | "json": ( 35 | {"StartersListApp": {"json": True}}, 36 | "List starters as JSON instead of YAML", 37 | ), 38 | }, 39 | ) # type: ignore 40 | 41 | aliases = dict(**base_aliases) # type: ignore 42 | 43 | def start(self): 44 | """List the installed starters.""" 45 | starters = self.manager.starters 46 | if self.json: 47 | print(dumps(starters, indent=2, sort_keys=True)) 48 | else: 49 | for name, starter in sorted(starters.items()): 50 | description = starter.get("description") 51 | label = starter.get("label", "(no label)") 52 | print(f"""{name}:""") 53 | print(" type:", starter.get("type", "(unknown)")) 54 | if label: 55 | print(f" label: {label}") 56 | if description: 57 | print(" description: |-") 58 | print(textwrap.indent("\n".join(textwrap.wrap(description)), " ")) 59 | 60 | @T.default("manager") 61 | def _default_manager(self): 62 | return StarterManager(parent=self) 63 | 64 | 65 | class StartersApp(StartersBaseApp): 66 | """Jupyter-starters utilities.""" 67 | 68 | name = "starters" 69 | 70 | subcommands = T.Dict( 71 | dict( 72 | list=(StartersListApp, f"{StartersListApp.__doc__}".splitlines()[0]), 73 | ) 74 | ) 75 | 76 | 77 | main = launch_instance = StartersApp.launch_instance 78 | 79 | if __name__ == "__main__": # pragma: no cover 80 | main() 81 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | commonjs: true, 6 | node: true, 7 | }, 8 | root: true, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:import/errors', 12 | 'plugin:import/warnings', 13 | 'plugin:import/typescript', 14 | 'plugin:@typescript-eslint/eslint-recommended', 15 | 'plugin:@typescript-eslint/recommended', 16 | 'plugin:react/recommended', 17 | ], 18 | globals: { 19 | JSX: 'readonly', 20 | }, 21 | parser: '@typescript-eslint/parser', 22 | parserOptions: { 23 | project: 'tsconfig.eslint.json', 24 | }, 25 | plugins: ['@typescript-eslint', 'import'], 26 | rules: { 27 | '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], 28 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | '@typescript-eslint/camelcase': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | '@typescript-eslint/no-non-null-assertion': 'off', 33 | '@typescript-eslint/no-namespace': 'off', 34 | '@typescript-eslint/explicit-function-return-type': 'off', 35 | '@typescript-eslint/no-var-requires': 'off', 36 | '@typescript-eslint/no-empty-interface': 'off', 37 | '@typescript-eslint/triple-slash-reference': 'warn', 38 | '@typescript-eslint/no-inferrable-types': 'off', 39 | 'import/export': 'off', // we do class/interface + NS pun exports _all over_ 40 | 'import/no-unresolved': 'off', 41 | 'import/order': [ 42 | 'warn', 43 | { 44 | groups: [ 45 | 'builtin', 46 | 'external', 47 | 'parent', 48 | 'sibling', 49 | 'index', 50 | 'object', 51 | 'unknown', 52 | ], 53 | pathGroups: [ 54 | { pattern: 'react/**', group: 'builtin', position: 'after' }, 55 | { pattern: 'codemirror/**', group: 'external', position: 'before' }, 56 | { pattern: '@lumino/**', group: 'external', position: 'before' }, 57 | { pattern: '@jupyterlab/**', group: 'external', position: 'after' }, 58 | ], 59 | 'newlines-between': 'always', 60 | alphabetize: { order: 'asc' }, 61 | }, 62 | ], 63 | 'no-inner-declarations': 'off', 64 | 'no-prototype-builtins': 'off', 65 | 'no-control-regex': 'warn', 66 | 'no-undef': 'warn', 67 | 'no-case-declarations': 'warn', 68 | 'no-useless-escape': 'off', 69 | 'prefer-const': 'off', 70 | }, 71 | settings: { 72 | react: { 73 | version: 'detect', 74 | }, 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /docs/notebooks/Users.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## For Users" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### Installing\n", 15 | "\n", 16 | "Get up and running fast with `pip`:\n", 17 | "\n", 18 | "```bash\n", 19 | "pip install jupyter-starters jupyterlab=3\n", 20 | "```\n", 21 | "\n", 22 | "or\n", 23 | "\n", 24 | "```bash\n", 25 | "mamba install -c conda-forge jupyter-starters jupyterlab-3\n", 26 | "```\n", 27 | "\n", 28 | "or\n", 29 | "\n", 30 | "```bash\n", 31 | "mamba install -c conda-forge jupyter-starters jupyterlab-3\n", 32 | "```" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### Using\n", 40 | "\n", 41 | "- When JupyterLab opens, and configured starters will appear in the _Launcher_." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "### Extras\n", 49 | "\n", 50 | "#### Cookiecutter\n", 51 | "\n", 52 | "`jupyter_starters` integrates with (but doesn't require)\n", 53 | "[cookiecutter](https://cookiecutter.readthedocs.io).\n", 54 | "\n", 55 | "```bash\n", 56 | "pip install cookiecutter\n", 57 | "```\n", 58 | "\n", 59 | "or `conda`...\n", 60 | "\n", 61 | "```bash\n", 62 | "conda install -c conda-forge cookiecutter\n", 63 | "```" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "## CLI\n", 71 | "\n", 72 | "You can list your available starters with the `jupyter starters list` CLI." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "!jupyter starters list --help" 82 | ] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "Python 3 (ipykernel)", 88 | "language": "python", 89 | "name": "python3" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.10.6" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 4 106 | } 107 | -------------------------------------------------------------------------------- /examples/whitepaper-single.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# My Next Big Idea" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## The Heilmeier Catechism\n", 15 | "\n", 16 | "> _George H. Heilmeier, a former DARPA director (1975-1977), crafted a set of questions\n", 17 | "> known as the \"Heilmeier Catechism\" to help Agency officials think through and evaluate\n", 18 | "> proposed research programs. ⸻\n", 19 | "> [DARPA](https://www.darpa.mil/work-with-us/heilmeier-catechism)_" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "### What are you trying to do? Articulate your objectives using absolutely no jargon." 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "### How is it done today, and what are the limits of current practice?" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "### What is new in your approach and why do you think it will be successful?" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "### Who cares? If you are successful, what difference will it make?" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "### What are the risks?" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "### How much will it cost?" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "### How long will it take?" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "### What are the mid-term and final “exams” to check for success?" 76 | ] 77 | } 78 | ], 79 | "metadata": { 80 | "kernelspec": { 81 | "display_name": "Python 3 (ipykernel)", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "codemirror_mode": { 87 | "name": "ipython", 88 | "version": 3 89 | }, 90 | "file_extension": ".py", 91 | "mimetype": "text/x-python", 92 | "name": "python", 93 | "nbconvert_exporter": "python", 94 | "pygments_lexer": "ipython3", 95 | "version": "3.10.6" 96 | } 97 | }, 98 | "nbformat": 4, 99 | "nbformat_minor": 4 100 | } 101 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/builder/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaForm, ALL_CUSTOM_UI } from '@deathbeds/jupyterlab-rjsf'; 2 | import { JSONObject } from '@lumino/coreutils'; 3 | import { Widget, BoxLayout } from '@lumino/widgets'; 4 | 5 | import { CSS } from '../../css'; 6 | import { IStartContext } from '../../tokens'; 7 | 8 | import { BuilderButtons } from './buttons'; 9 | import { BuilderModel } from './model'; 10 | import { ShareForm } from './share'; 11 | 12 | export class BodyBuilder extends Widget { 13 | private _form: SchemaForm; 14 | private _context: IStartContext; 15 | private _buttons: BuilderButtons; 16 | private _share: ShareForm; 17 | 18 | model: BuilderModel; 19 | 20 | constructor(options: BuilderModel.IOptions) { 21 | super(options); 22 | this.model = new BuilderModel(options); 23 | this.model.done = () => this.dispose(); 24 | this.layout = new BoxLayout(); 25 | this._context = options.context; 26 | this._share = new ShareForm(new ShareForm.Model(this.model)); 27 | const { label } = this._context.starter; 28 | this.id = Private.nextId(); 29 | this.addClass(CSS.BUILDER); 30 | this.addClass(CSS.FORM_PANEL); 31 | this.title.caption = label; 32 | if (this.model.icon) { 33 | this.title.icon = this.model.icon; 34 | } 35 | this.initForm().catch(console.warn); 36 | } 37 | 38 | protected async initForm(): Promise { 39 | this._form = new SchemaForm( 40 | this._context.starter.schema || {}, 41 | { 42 | liveValidate: true, 43 | formData: this._context.body, 44 | uiSchema: this._context.starter.uiSchema || {}, 45 | ...(await ALL_CUSTOM_UI()), 46 | }, 47 | { markdown: this.model.manager.markdown } 48 | ); 49 | this._form.addClass(`${CSS.BUILDER}-FormWrapper`); 50 | 51 | this._buttons = this.makeButtons(); 52 | this.boxLayout.addWidget(this._form); 53 | this.boxLayout.addWidget(this._buttons); 54 | this.boxLayout.addWidget(this._share); 55 | this.boxLayout.spacing = 0; 56 | } 57 | 58 | get boxLayout(): BoxLayout { 59 | return this.layout as BoxLayout; 60 | } 61 | 62 | dispose(): void { 63 | super.dispose(); 64 | if (!this.isDisposed) { 65 | this.model.dispose(); 66 | } 67 | } 68 | 69 | makeButtons(): BuilderButtons { 70 | const buttons = new BuilderButtons(this.model); 71 | 72 | buttons.model.form = this._form.model; 73 | 74 | return buttons; 75 | } 76 | } 77 | 78 | namespace Private { 79 | let _nextId = 0; 80 | export function nextId(): string { 81 | return `id-jp-starters-${_nextId++}`; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /atest/lab/07_Copy_and_Continue.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Multi-Stage Notebook 3 | 4 | Resource ../Keywords.resource 5 | Library String 6 | 7 | Suite Setup Setup Suite For Screenshots lab${/}notebook-multi 8 | 9 | Test Tags example:notebook-multi 10 | 11 | 12 | *** Variables *** 13 | ${MULTI TAB} css:.p-DockPanel .p-TabBar-tab [data-icon\="starters:multi-stage-notebook"] 14 | 15 | 16 | *** Test Cases *** 17 | Happy Path 18 | [Documentation] Can we use the multi-stage notebook? 19 | Click Element ${CSS LAUNCH CARD NOTEBOOK MULTI} 20 | ${name} = Change The Name Field 21 | Capture Page Screenshot 00-notebook-multi-accepted-name.png 22 | Advance Starter Form 23 | ${file1} = Wait For File Prompt ${name} 1 24 | Capture Page Screenshot 01-notebook-multi-updated-file-1-prompt.png 25 | Advance Starter Form 26 | Wait For File Tab ${file1} 27 | Capture Page Screenshot 02-notebook-multi-updated-file-1-prompt-file.png 28 | ${file2} = Wait For File Prompt ${name} 2 29 | Capture Page Screenshot 03-notebook-multi-updated-file-2-prompt.png 30 | Advance Starter Form 31 | Wait For File Tab ${file2} 32 | Capture Page Screenshot 04-notebook-multi-updated-file-2-prompt-file.png 33 | I Am Done 34 | Advance Starter Form 35 | Wait Until Page Does Not Contain Element ${MULTI TAB} 36 | Capture Page Screenshot 05-notebook-multi-updated-done.png 37 | 38 | 39 | *** Keywords *** 40 | Change The Name Field 41 | [Documentation] Set a random name on the name field 42 | [Arguments] ${previous}=${EMPTY} 43 | ${name css} = Set Variable css:input[label\="Name"] 44 | Wait Until Page Contains Element ${name css} timeout=30s 45 | ${name} = Generate Random String 46 | Click Element ${name css} 47 | Really Input Text ${name css} ${name} 48 | RETURN ${name} 49 | 50 | Wait For File Prompt 51 | [Documentation] Accept that a file will be made 52 | [Arguments] ${name} ${number} 53 | ${file} = Set Variable file for ${name} ${number}.txt 54 | Wait Until Page Contains Element xpath://code[text() = '${file}'] timeout=30s 55 | RETURN ${file} 56 | 57 | Wait For File Tab 58 | [Documentation] Wait until a file is opened 59 | [Arguments] ${file} 60 | Wait Until Page Contains Element xpath://div[contains(@class, 'p-TabBar-tabLabel')][text() = '${file}'] 61 | 62 | I Am Done 63 | [Documentation] Signal to the notebook that we are done 64 | Click Element xpath://p[text() = 'I am done'] 65 | -------------------------------------------------------------------------------- /src/jupyter_starters/handlers.py: -------------------------------------------------------------------------------- 1 | """Tornado handler for managing and communicating with language servers.""" 2 | # pylint: disable=abstract-method 3 | from typing import TYPE_CHECKING 4 | 5 | from jupyter_server.base.handlers import JupyterHandler 6 | from jupyter_server.utils import url_path_join as ujoin 7 | 8 | from .json_ import JsonSchemaException, loads 9 | from .schema.v3 import ALL_STARTERS, VERSION 10 | from .types import NS 11 | 12 | if TYPE_CHECKING: 13 | from .manager import StarterManager 14 | 15 | 16 | class BaseHandler(JupyterHandler): 17 | """Common base handlers.""" 18 | 19 | manager: "StarterManager" 20 | 21 | def initialize(self, manager) -> None: 22 | """Capture the manager.""" 23 | self.manager = manager 24 | 25 | 26 | class StartersHandler(BaseHandler): 27 | """Serves the available starters.""" 28 | 29 | async def get(self) -> None: 30 | """Return the starters.""" 31 | response = { 32 | "version": VERSION, 33 | "starters": self.manager.starters, 34 | "running": self.manager.running, 35 | } 36 | 37 | try: 38 | ALL_STARTERS(response) 39 | except JsonSchemaException as err: 40 | self.manager.log.warn(f"[starter] invalid response: {err}") 41 | 42 | self.finish(response) 43 | 44 | 45 | class StarterHandler(BaseHandler): 46 | """Acts on a single starters.""" 47 | 48 | async def post(self, starter, path) -> None: 49 | """Start a starter.""" 50 | body = None 51 | 52 | if self.request.body: 53 | body = loads(self.request.body) 54 | 55 | self.finish(await self.manager.start(starter, path, body)) 56 | 57 | # pylint: disable=unused-argument 58 | 59 | async def delete(self, starter, path=None) -> None: 60 | """Forcibly stop a starter.""" 61 | await self.manager.stop(starter) 62 | self.set_status(202) 63 | self.finish({}) 64 | 65 | 66 | def add_handlers(nbapp, manager) -> None: 67 | """Add starter routes to the notebook server web application.""" 68 | 69 | opts = {"manager": manager} 70 | 71 | url = ujoin(nbapp.base_url, NS) 72 | starter_url = ujoin(url, "(?P.*?)", "(?P.*?)", "?$") 73 | nbapp.log.debug("💡 starters will list under %s", url) 74 | nbapp.log.debug("💡 starters will run under %s", starter_url) 75 | 76 | nbapp.web_app.add_handlers( 77 | ".*", 78 | [ 79 | (url, StartersHandler, opts), 80 | ( 81 | starter_url, 82 | StarterHandler, 83 | opts, 84 | ), 85 | ], 86 | ) 87 | -------------------------------------------------------------------------------- /examples/cookiecutter/{{ cookiecutter.idea_size }} Idea/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# My Next {{ cookiecutter.idea_size }} Idea" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## The Heilmeier Catechism\n", 15 | "\n", 16 | "> _George H. Heilmeier, a former DARPA director (1975-1977), crafted a set of questions\n", 17 | "> known as the \"Heilmeier Catechism\" to help Agency officials think through and evaluate\n", 18 | "> proposed research programs. ⸻\n", 19 | "> [DARPA](https://www.darpa.mil/work-with-us/heilmeier-catechism)_" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "### What are you trying to do? Articulate your objectives using absolutely no jargon." 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "### How is it done today, and what are the limits of current practice?" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "### What is new in your approach and why do you think it will be successful?" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "### Who cares? If you are successful, what difference will it make?" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "### What are the risks?" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "### How much will it cost?" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "### How long will it take?" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "### What are the mid-term and final “exams” to check for success?" 76 | ] 77 | } 78 | ], 79 | "metadata": { 80 | "kernelspec": { 81 | "display_name": "Python 3", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "codemirror_mode": { 87 | "name": "ipython", 88 | "version": 3 89 | }, 90 | "file_extension": ".py", 91 | "mimetype": "text/x-python", 92 | "name": "python", 93 | "nbconvert_exporter": "python", 94 | "pygments_lexer": "ipython3", 95 | "version": "3.7.3" 96 | } 97 | }, 98 | "nbformat": 4, 99 | "nbformat_minor": 4 100 | } 101 | -------------------------------------------------------------------------------- /atest/lab/03_Parameters.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Parameters 3 | 4 | Resource ../Keywords.resource 5 | Library String 6 | 7 | Suite Setup Setup Suite For Screenshots lab${/}parameters 8 | 9 | Test Tags example:params 10 | 11 | 12 | *** Variables *** 13 | ${CSS TOPIC} css:input[label="## Topic"] 14 | 15 | 16 | *** Test Cases *** 17 | Cancel 18 | [Documentation] Does the cancel button work? 19 | Launch The Parameterized Starter 20 | Wait Until Page Contains Element ${CSS BODYBUILDER} 21 | Really Input Text ${CSS TOPIC} cancel 22 | Capture Page Screenshot 00-cancel-did-edit.png 23 | Wait Until Keyword Succeeds 3x 0.5s Cancel Starter Form 24 | Capture Page Screenshot 01-cancel-did-cancel.png 25 | 26 | Parameter Notebook 27 | [Documentation] Can we start a single notebook with parameters? 28 | Launch The Parameterized Starter 29 | ${topic} = Really Input A Random String ${CSS TOPIC} 30 | Capture Page Screenshot 10-notebook-topic-changed.png 31 | Starter Form Should Contain Markdown Elements 32 | Advance Starter Form 33 | Wait Until Page Contains Element 34 | ... ${XP FILE TREE ITEM}/span[contains(text(), '${topic} Whitepaper.ipynb')] 35 | Wait Until Kernel 36 | Capture Page Screenshot 11-notebook-accepted-parameter.png 37 | Wait Until Page Contains Element id:My-Next-Big-Idea 38 | Save Notebook 39 | Capture Page Screenshot 12-notebook-did-save.png 40 | 41 | 42 | *** Keywords *** 43 | Really Input A Random String 44 | [Documentation] Type and return a random string in an input 45 | [Arguments] ${selector} 46 | ${text} = Generate Random String 47 | Really Input Text ${selector} ${text} 48 | RETURN ${text} 49 | 50 | Launch The Parameterized Starter 51 | [Documentation] Use the launcher to start the parameterized example 52 | Wait Until Page Contains Element ${CSS LAUNCH CARD PARAM} timeout=10s 53 | Click Element ${CSS LAUNCH CARD PARAM} 54 | 55 | Starter Form Should Contain Markdown Elements 56 | [Documentation] Verify some fancy markdown rendered. 57 | Wait Until Page Contains Element ${CSS BODYBUILDER} legend.jp-RenderedMarkdown h1 58 | Wait Until Page Contains Element ${CSS BODYBUILDER} .jp-RenderedMarkdown.control-label h2 59 | Wait Until Page Contains Element ${CSS BODYBUILDER} .jp-RenderedMarkdown.field-description em 60 | Wait Until Page Contains Element ${CSS BODYBUILDER} .jp-RenderedMarkdown.field-description blockquote a 61 | Wait Until Page Contains Element ${CSS BODYBUILDER} .jp-RenderedMarkdown.help-block code 62 | -------------------------------------------------------------------------------- /lite/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@deathbeds/jupyterlab-starters:settings-provider": { 3 | "starters": { 4 | "hello-notebook": { 5 | "category": "Notebook", 6 | "content": { 7 | "content": { 8 | "cells": [ 9 | { 10 | "cell_type": "markdown", 11 | "source": "# Hello {{ world }}!" 12 | } 13 | ] 14 | }, 15 | "name": "hello-{{ world | lower }}.ipynb", 16 | "type": "notebook" 17 | }, 18 | "description": "A Hello World notebook.", 19 | "label": "Hello Notebook", 20 | "rank": 42, 21 | "schema": { 22 | "description": "An example of a _Hello World_ notebook.", 23 | "properties": { 24 | "world": { 25 | "default": "Jupyter", 26 | "description": "The world to greet.", 27 | "type": "string" 28 | } 29 | }, 30 | "title": "Hello Notebook", 31 | "type": "object" 32 | }, 33 | "type": "content" 34 | }, 35 | "todo": { 36 | "category": "Productivity", 37 | "content": { 38 | "content": "# {{ title }}\n{% for item in items %}\n- [{% if item.done %}x{% else %} {% endif %}] {{ item.description }}{% endfor %}", 39 | "name": "TODO.md" 40 | }, 41 | "description": "A todo list", 42 | "label": "TODO List", 43 | "schema": { 44 | "default": { 45 | "title": "Things To Do" 46 | }, 47 | "properties": { 48 | "items": { 49 | "items": { 50 | "properties": { 51 | "description": { 52 | "default": "a thing", 53 | "type": "string" 54 | }, 55 | "done": { 56 | "type": "boolean" 57 | } 58 | }, 59 | "type": "object" 60 | }, 61 | "type": "array" 62 | }, 63 | "title": { 64 | "type": "string" 65 | } 66 | }, 67 | "title": "A TODO List", 68 | "type": "object" 69 | }, 70 | "type": "content", 71 | "uiSchema": { 72 | "ui:order": ["title", "items"] 73 | } 74 | } 75 | } 76 | }, 77 | "@jupyterlab/apputils-extension:notification": { 78 | "doNotDisturbMode": true, 79 | "fetchNews": "false" 80 | }, 81 | "@jupyterlab/apputils-extension:palette": { 82 | "modal": false 83 | }, 84 | "@jupyterlab/extensionmanager-extension:plugin": { 85 | "enabled": false 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /.binder/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@deathbeds/jupyterlab-starters:settings-provider": { 3 | "starters": { 4 | "hello-notebook": { 5 | "category": "Notebook", 6 | "content": { 7 | "content": { 8 | "cells": [ 9 | { 10 | "cell_type": "markdown", 11 | "source": "# Hello {{ world }}!" 12 | } 13 | ] 14 | }, 15 | "name": "hello-{{ world | lower }}.ipynb", 16 | "type": "notebook" 17 | }, 18 | "description": "A Hello World notebook.", 19 | "label": "Hello Notebook", 20 | "rank": 42, 21 | "schema": { 22 | "description": "An example of a _Hello World_ notebook.", 23 | "properties": { 24 | "world": { 25 | "default": "Jupyter", 26 | "description": "The world to greet.", 27 | "type": "string" 28 | } 29 | }, 30 | "title": "Hello Notebook", 31 | "type": "object" 32 | }, 33 | "type": "content" 34 | }, 35 | "todo": { 36 | "category": "Productivity", 37 | "content": { 38 | "content": "# {{ title }}\n{% for item in items %}\n- [{% if item.done %}x{% else %} {% endif %}] {{ item.description }}{% endfor %}", 39 | "name": "TODO.md" 40 | }, 41 | "description": "A todo list", 42 | "label": "TODO List", 43 | "schema": { 44 | "default": { 45 | "title": "Things To Do" 46 | }, 47 | "properties": { 48 | "items": { 49 | "items": { 50 | "properties": { 51 | "description": { 52 | "default": "a thing", 53 | "type": "string" 54 | }, 55 | "done": { 56 | "type": "boolean" 57 | } 58 | }, 59 | "type": "object" 60 | }, 61 | "type": "array" 62 | }, 63 | "title": { 64 | "type": "string" 65 | } 66 | }, 67 | "title": "A TODO List", 68 | "type": "object" 69 | }, 70 | "type": "content", 71 | "uiSchema": { 72 | "ui:order": ["title", "items"] 73 | } 74 | } 75 | } 76 | }, 77 | "@jupyterlab/apputils-extension:notification": { 78 | "doNotDisturbMode": true, 79 | "fetchNews": "false" 80 | }, 81 | "@jupyterlab/apputils-extension:palette": { 82 | "modal": false 83 | }, 84 | "@jupyterlab/extensionmanager-extension:plugin": { 85 | "enabled": false 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /atest/lite/Keywords.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation A work-in-progress set of keywords for JupyterLite 3 | 4 | Library OperatingSystem 5 | Library Process 6 | Library ../ports.py 7 | 8 | 9 | *** Variables *** 10 | ${NEXT LITE LOG} ${0} 11 | 12 | 13 | *** Keywords *** 14 | Start JupyterLite Server 15 | [Documentation] Start _the_ `jupyter lite` server 16 | [Arguments] ${cwd} @{args} 17 | ${proc} = Ensure JupyterLite Process ${cwd} @{args} 18 | Close All Browsers 19 | Wait For URL Status ${LITE URL} ${200} 20 | Open Browser ${LITE URL} headlessfirefox 21 | ... service_log_path=${OUTPUT DIR}${/}geckodriver-lite.log 22 | RETURN ${proc} 23 | 24 | Start JupyterLite Process 25 | [Documentation] Start a `jupyter lite` process 26 | [Arguments] ${cwd} ${task} @{args} 27 | ${proc} = Start Process jupyter lite ${task} @{args} cwd=${cwd} 28 | ... stdout=${OUTPUT DIR}${/}lite-${NEXT LITE LOG}-${task}.log stderr=STDOUT 29 | Set Suite Variable ${NEXT LITE LOG} ${NEXT LITE LOG + 1} children=${TRUE} 30 | RETURN ${proc} 31 | 32 | Ensure JupyterLite Process 33 | [Documentation] Build and then start a JupyterLite server 34 | [Arguments] ${cwd} @{args} 35 | ${prefix} = Set Variable /@rf/ 36 | ${port} = Get Unused Port 37 | ${url} = Set Variable http://localhost:${port}${prefix}lab/index.html 38 | Set Suite Variable ${LITE URL} ${url} children=${TRUE} 39 | @{final args} = Set Variable @{args} --port ${port} --base-url ${prefix} --debug 40 | ${proc} = Start JupyterLite Process ${cwd} doit @{final args} -- -s serve 41 | Set Suite Variable ${LITE SERVER} ${proc} children=${TRUE} 42 | RETURN ${proc} 43 | 44 | Open JupyterLite 45 | [Documentation] Open the browser to JupyterLite with an optional URL fragment 46 | [Arguments] ${url}=${EMPTY} 47 | Go To ${LITE URL}${url} 48 | Wait For Splash 49 | 50 | Stop JupyterLite Server 51 | [Documentation] Stop _the_ `jupyter lite` server 52 | Close All Browsers 53 | Terminate Process ${LITE SERVER} 54 | 55 | Start Lite Test 56 | [Documentation] Start with a blank browser 57 | Open JupyterLite 58 | 59 | Clean Up Lite Test 60 | [Documentation] Clean up 61 | ... TODO: how might we clear the application cache? 62 | Log Nothing to see here 63 | 64 | Start Lite Suite 65 | [Documentation] Ensure lite assets are available 66 | Set Screenshot Directory ${OUTPUT DIR}${/}lite 67 | Start JupyterLite Server ${ROOT}${/}lite 68 | 69 | Clean Up Lite Suite 70 | [Documentation] Clean up after lite 71 | Stop JupyterLite Server 72 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deathbeds/jupyterlab-starters", 3 | "version": "2.0.0-alpha0", 4 | "description": "Parameterized files and folders for JupyterLab", 5 | "license": "BSD-3-Clause", 6 | "author": "dead pixels collective", 7 | "homepage": "https://github.com/deathbeds/jupyterlab-starters", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/deathbeds/jupyterlab-starters.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/deathbeds/jupyterlab-starters/issues" 14 | }, 15 | "main": "lib/index.js", 16 | "files": [ 17 | "{lib,style,src}/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,css,ts,tsx}" 18 | ], 19 | "scripts": { 20 | "build:copyschema": "python -c \"from pathlib import Path as P; s = P('../../src/jupyter_starters/schema/v3.json').read_text(); P('lib').mkdir(exist_ok=True); P('lib/_schema.json').write_text(s); P('src/_schema.json').write_text(s)\"", 21 | "build:ext": "jupyter labextension build .", 22 | "build:ext:dev": "jupyter labextension build --development True .", 23 | "build:json2ts": "json2ts ../../src/jupyter_starters/schema/v3.json --unreachableDefinitions | prettier --stdin-filepath _schema.d.ts > src/_schema.d.ts", 24 | "build:pre": "jlpm build:json2ts && jlpm build:copyschema", 25 | "bundle": "npm pack .", 26 | "clean": "rimraf lib ../../src/jupyter_starters/labextension", 27 | "upload": "jlpm publish .", 28 | "watch": "jupyter labextension watch ." 29 | }, 30 | "types": "lib/index.d.ts", 31 | "dependencies": { 32 | "@deathbeds/jupyterlab-rjsf": "~2.0.0-alpha0", 33 | "@jupyterlab/application": "3", 34 | "@jupyterlab/launcher": "3", 35 | "@jupyterlab/notebook": "3", 36 | "@jupyterlab/running": "3", 37 | "@rjsf/core": "^5.0.1", 38 | "@rjsf/utils": "^5.0.1", 39 | "@rjsf/validator-ajv8": "^5.0.1", 40 | "lodash.mergewith": "^4.6.2", 41 | "nunjucks": "^3.2.3" 42 | }, 43 | "devDependencies": { 44 | "@jupyterlab/builder": "^3.2.1", 45 | "@types/codemirror": "^0.0.97", 46 | "@types/lodash.mergewith": "^4.6.7", 47 | "@types/nunjucks": "^3.2.1", 48 | "@types/react": "^17.0.0", 49 | "react": "^17.0.1" 50 | }, 51 | "keywords": [ 52 | "jupyter", 53 | "jupyterlab", 54 | "jupyterlab-extension" 55 | ], 56 | "jupyterlab": { 57 | "webpackConfig": "./webpack.config.js", 58 | "extension": "lib/plugin.js", 59 | "outputDir": "../../src/jupyter_starters/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-starters", 60 | "schemaDir": "schema", 61 | "sharedPackages": { 62 | "@deathbeds/jupyterlab-rjsf": { 63 | "bundled": true 64 | }, 65 | "@rjsf/core": { 66 | "bundled": true 67 | }, 68 | "@rjsf/validator-ajv8": { 69 | "bundled": true 70 | }, 71 | "nunjucks": { 72 | "bundled": true 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /atest/lab/05_Notebook.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Notebook 3 | 4 | Resource ../Keywords.resource 5 | Library String 6 | 7 | Suite Setup Setup Suite For Screenshots lab${/}notebook 8 | 9 | Test Tags example:notebook 10 | 11 | 12 | *** Test Cases *** 13 | Happy Path 14 | [Documentation] Can we use the notebook? 15 | Click Element ${CSS LAUNCH CARD NOTEBOOK} 16 | ${index ipynb} = Set Variable ${XP FILE TREE ITEM}/span[text() = 'index.ipynb'] 17 | ${name} = Change The Name Field 18 | Capture Page Screenshot 00-notebook-accepted-name.png 19 | Advance Starter Form 20 | ${quest css} = Set Variable css:input[label\="So, ${name}, what is your quest?"] 21 | ${quest} = Change The Quest Field 22 | Capture Page Screenshot 01-notebook-accepted-quest.png 23 | Advance Starter Form 24 | Change The Answer Field 42 25 | Capture Page Screenshot 02-notebook-accepted-answet.png 26 | Advance Starter Form 27 | ${txt} = Set Variable ${XP FILE TREE ITEM}/span[text() = 'good job ${name}.txt'] 28 | Wait Until Page Contains Element ${txt} timeout=20s 29 | Double Click Element ${txt} 30 | Wait Until Page Contains fjords 31 | Capture Page Screenshot 03-notebook-created-file.png 32 | 33 | No-op 34 | [Documentation] Does a no-op do nothing? 35 | [Tags] issue:26 36 | Capture Page Screenshot 04-noop-before.png 37 | Click Element ${CSS LAUNCH CARD NOTEBOOK NOOP} 38 | Sleep 5s 39 | Page Should Not Contain Element ${CSS DIALOG} 40 | Capture Page Screenshot 04-noop-after.png 41 | 42 | 43 | *** Keywords *** 44 | Change The Name Field 45 | [Documentation] Set a random name on the name field 46 | [Arguments] ${previous}=${EMPTY} 47 | ${name css} = Set Variable If "${previous}" css:input[label\="Hi, ${previous}"] 48 | ... css:input[label\="Name"] 49 | Wait Until Page Contains Element ${name css} timeout=30s 50 | ${name} = Generate Random String 51 | Click Element ${name css} 52 | Really Input Text ${name css} ${name} 53 | RETURN ${name} 54 | 55 | Change The Quest Field 56 | [Documentation] Set a random value on the quest field 57 | ${quest css} = Set Variable css:input[label\="Quest"] 58 | Wait Until Page Contains Element ${quest css} timeout=30s 59 | ${quest} = Generate Random String 60 | Click Element ${quest css} 61 | Really Input Text ${quest css} ${quest} 62 | RETURN ${quest} 63 | 64 | Change The Answer Field 65 | [Documentation] Set the answer field 66 | [Arguments] ${value}=${EMPTY} 67 | ${answer css} = Set Variable css:input[label\="The Answer"] 68 | Wait Until Page Contains Element ${answer css} timeout=30s 69 | Click Element ${answer css} 70 | Really Input Text ${answer css} ${value} 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bootstrap": "jlpm --ignore-optional --prefer-offline --frozen-lockfile && lerna bootstrap && jlpm lint && jlpm clean && jlpm build", 5 | "build": "lerna run build:pre && lerna run build && lerna run build:ext", 6 | "bundle": "lerna run --parallel bundle", 7 | "clean": "lerna run --parallel clean", 8 | "deduplicate": "jlpm yarn-deduplicate -s fewer --fail", 9 | "eslint:check": "eslint --ext .js,.jsx,.ts,.tsx .", 10 | "eslint:fix": "eslint --ext .js,.jsx,.ts,.tsx --fix .", 11 | "lint": "jlpm --silent stylelint:fix && jlpm --silent prettier:fix && jlpm --silent eslint:fix", 12 | "lint:check": "jlpm --silent prettier:check && jlpm --silent eslint:check && jlpm --silent stylelint:check", 13 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml}\"", 14 | "prettier:check": "jlpm prettier:base --check", 15 | "prettier:fix": "jlpm prettier:base --list-different --write", 16 | "stylelint:check": "stylelint --cache \"{packages/*/style,docs/_static}/**/*.css\"", 17 | "stylelint:fix": "jlpm stylelint:check --fix", 18 | "test": "lerna run --stream --concurrency=1 test", 19 | "watch": "lerna run --parallel --stream watch" 20 | }, 21 | "workspaces": { 22 | "packages": [ 23 | "packages/*" 24 | ] 25 | }, 26 | "resolutions": { 27 | "glob-parent": "^5.1.2", 28 | "@rjsf/core": "^5.0.1", 29 | "@rjsf/utils": "^5.0.1", 30 | "@rjsf/validator-ajv8": "^5.0.1", 31 | "prettier": "^2.8.3", 32 | "loader-utils": "^2.0.0" 33 | }, 34 | "devDependencies": { 35 | "@adobe/jsonschema2md": "^7.1.5", 36 | "@ephesoft/webpack.istanbul.loader": "^2.2.0", 37 | "@typescript-eslint/eslint-plugin": "^5.50.0", 38 | "@typescript-eslint/parser": "^5.50.0", 39 | "eslint": "^8.33.0", 40 | "eslint-config-prettier": "^8.6.0", 41 | "eslint-plugin-import": "^2.27.5", 42 | "eslint-plugin-prettier": "^4.2.1", 43 | "eslint-plugin-react": "^7.32.2", 44 | "json-schema-to-typescript": "^8.0.0", 45 | "lerna": "^6.4.1", 46 | "prettier": "^2.8.3", 47 | "prettier-package-json": "^2.8.0", 48 | "prettier-plugin-sort-json": "^1.0.0", 49 | "rimraf": "^4.1.2", 50 | "source-map-loader": "^4.0.1", 51 | "stylelint": "^14.3.0", 52 | "stylelint-config-prettier": "^9.0.3", 53 | "stylelint-config-recommended": "^6.0.0", 54 | "stylelint-config-standard": "~24.0.0", 55 | "stylelint-prettier": "^2.0.0", 56 | "typescript": "~4.9.5", 57 | "yarn-deduplicate": "^6.0.1" 58 | }, 59 | "prettier": { 60 | "singleQuote": true, 61 | "printWidth": 88, 62 | "proseWrap": "always", 63 | "jsonRecursiveSort": true 64 | }, 65 | "stylelint": { 66 | "extends": [ 67 | "stylelint-config-recommended", 68 | "stylelint-config-standard", 69 | "stylelint-prettier/recommended" 70 | ], 71 | "rules": { 72 | "selector-class-pattern": null 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/style/input.css: -------------------------------------------------------------------------------- 1 | .jp-SchemaForm input[type='text'], 2 | .jp-SchemaForm input[type='number'], 3 | .jp-SchemaForm input[type='date'], 4 | .jp-SchemaForm input[type='datetime-local'], 5 | .jp-SchemaForm input[type='url'], 6 | .jp-SchemaForm input[type='email'], 7 | .jp-SchemaForm input[type='file'], 8 | .jp-SchemaForm input[type='password'], 9 | .jp-SchemaForm input[type='color'], 10 | .jp-SchemaForm textarea { 11 | box-sizing: border-box; 12 | border: var(--jp-border-width) solid var(--jp-border-color1); 13 | background-color: var(--jp-layout-color0); 14 | color: var(--jp-ui-font-color0); 15 | font-size: var(--jp-ui-font-size2); 16 | min-width: 0; 17 | outline: none !important; 18 | padding: 2px 7px; 19 | width: 100%; 20 | max-width: 100%; 21 | } 22 | 23 | .jp-SchemaForm input[type='text']:focus, 24 | .jp-SchemaForm input[type='number']:focus, 25 | .jp-SchemaForm input[type='date']:focus, 26 | .jp-SchemaForm input[type='datetime-local']:focus, 27 | .jp-SchemaForm input[type='url']:focus, 28 | .jp-SchemaForm input[type='email']:focus, 29 | .jp-SchemaForm input[type='file']:focus, 30 | .jp-SchemaForm input[type='password']:focus, 31 | .jp-SchemaForm input[type='color']:focus, 32 | .jp-SchemaForm textarea:focus { 33 | border-color: var(--md-blue-500); 34 | background-color: var(--jp-layout-color0); 35 | box-shadow: inset 0 0 4px var(--md-blue-300); 36 | } 37 | 38 | .jp-SchemaForm select { 39 | width: 100%; 40 | font-size: var(--jp-ui-font-size1); 41 | background: var(--jp-input-background); 42 | color: var(--jp-ui-font-color0); 43 | padding: calc(var(--jp-ui-font-size1) / 2); 44 | padding-right: var(--jp-ui-font-size3); 45 | background-image: url('~@jupyterlab/ui-components/style/icons/arrow/caret-down-empty-thin.svg'); 46 | background-repeat: no-repeat; 47 | background-position: 99% center; 48 | background-size: 18px; 49 | border: var(--jp-border-width) solid var(--jp-input-border-color); 50 | border-radius: 0; 51 | outline: none; 52 | appearance: none; 53 | } 54 | 55 | .jp-SchemaForm select[multiple] { 56 | padding: 0; 57 | background: none; 58 | } 59 | 60 | .jp-SchemaForm select:focus { 61 | border: var(--jp-border-width) solid var(--jp-brand-color1); 62 | } 63 | 64 | .jp-SchemaForm fieldset button { 65 | font-size: var(--jp-ui-font-size2); 66 | padding: calc(var(--jp-ui-font-size1) / 2); 67 | background-color: var(--jp-layout-color2); 68 | color: var(--jp-ui-font-color1); 69 | border: var(--jp-border-width) solid transparent; 70 | cursor: pointer; 71 | min-width: calc(var(--jp-ui-font-size1) * 1.6); 72 | font-weight: bold; 73 | } 74 | 75 | .jp-SchemaForm .radio > label > span, 76 | .jp-SchemaForm .field-boolean .checkbox > label { 77 | display: flex; 78 | } 79 | 80 | .jp-SchemaForm .radio > label > span > input, 81 | .jp-SchemaForm .field-boolean .checkbox > label > input { 82 | flex: 0; 83 | } 84 | 85 | .jp-SchemaForm .radio > label > span > span, 86 | .jp-SchemaForm .field-boolean .checkbox > label > span { 87 | flex: 1; 88 | } 89 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/async-component/index.tsx: -------------------------------------------------------------------------------- 1 | // adapted from: 2 | // - https://medium.com/front-end-weekly/loading-components-asynchronously-in-react-app-with-an-hoc-61ca27c4fda7 3 | // - https://gist.github.com/alecmerdler/1bbda1c26c05e7b64711e0ef2899c347 4 | 5 | import React from 'react'; 6 | 7 | /** 8 | * An async wrapper around a heavy-weight React dependency 9 | * 10 | * The component should only be loaded once. 11 | */ 12 | export function asyncComponent( 13 | doImport: asyncComponent.IImporter, 14 | onError: asyncComponent.IErrorHandler = console.error 15 | ): asyncComponent.TImportable { 16 | let COMPONENT: T; 17 | let PROMISE: Promise; 18 | 19 | return class extends React.Component { 20 | readonly displayName = 'AsyncComponent'; 21 | 22 | state: asyncComponent.IState = { 23 | __async_component: null, 24 | }; 25 | 26 | /** 27 | * React-specific overload 28 | */ 29 | componentDidMount() { 30 | if (COMPONENT != null) { 31 | this.componentWasCached(); 32 | return; 33 | } 34 | 35 | if (PROMISE == null) { 36 | PROMISE = doImport().then((component) => (COMPONENT = component)); 37 | } 38 | 39 | // the error may propagate multiple times 40 | PROMISE.then(() => this.componentWasCached()).catch(onError); 41 | } 42 | 43 | /** 44 | * Trigger a redraw when the component becomes available 45 | */ 46 | componentWasCached() { 47 | this.setState({ __async_component: COMPONENT }); 48 | } 49 | 50 | /** 51 | * Render the component (or a spinner, while loading) 52 | */ 53 | render(): JSX.Element { 54 | const Component = this.state.__async_component; 55 | if (Component == null) { 56 | return ( 57 |
58 |
59 |
60 | ); 61 | } else { 62 | return {this.props.children}; 63 | } 64 | } 65 | }; 66 | } 67 | 68 | /** 69 | * A namespace for relevant interfaces 70 | */ 71 | export namespace asyncComponent { 72 | /** 73 | * The type of thing that can be lazily loaded 74 | */ 75 | export type TImportable = React.ComponentClass | React.StatelessComponent; 76 | 77 | /** 78 | * A lazy loader that uses `await import` 79 | * 80 | * Should probably also specify a webpackChunkName 81 | * https://webpack.js.org/guides/code-splitting/#dynamic-imports 82 | */ 83 | export interface IImporter { 84 | (): Promise; 85 | } 86 | 87 | /** 88 | * An error handler (can't have any particular side effects 89 | */ 90 | export interface IErrorHandler { 91 | (err: any): void; 92 | } 93 | 94 | /** 95 | * The state of the wrapper component. 96 | * 97 | * Apparently this can't be yet... 98 | */ 99 | export interface IState { 100 | __async_component: TImportable | null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /docs/notebooks/REST API.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## REST API\n", 8 | "\n", 9 | "`jupyter_starters` adds two routes to the Notebook server, which are primarily consumed\n", 10 | "by the `@deathbeds/jupyterlab-starters` JupyterLab extension." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "### Starter List\n", 18 | "\n", 19 | "```\n", 20 | "GET http://localhost:8888/starters\n", 21 | "```\n", 22 | "\n", 23 | "Returns a [named map of starters](../schema/v3.html#definitions-group-all-starters), as\n", 24 | "loaded via `traitlets`. For example, the demo starters:" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": { 31 | "tags": [ 32 | "hide-input" 33 | ] 34 | }, 35 | "outputs": [], 36 | "source": [ 37 | "import json\n", 38 | "import pathlib\n", 39 | "\n", 40 | "import IPython\n", 41 | "\n", 42 | "from jupyter_starters.handlers import VERSION\n", 43 | "\n", 44 | "HERE = pathlib.Path.cwd()\n", 45 | "starters = json.loads((HERE.parent.parent / \"jupyter_server_config.json\").read_text())[\n", 46 | " \"StarterManager\"\n", 47 | "][\"extra_starters\"]\n", 48 | "IPython.display.Markdown(\n", 49 | " f\"\"\"```json\n", 50 | "{json.dumps({\n", 51 | " \"version\": VERSION,\n", 52 | " \"starters\": starters\n", 53 | "}, indent=2, sort_keys=True) }\n", 54 | "```\"\"\"\n", 55 | ")" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "### Starter\n", 63 | "\n", 64 | "```\n", 65 | "POST http://localhost:8888/starters/{:starter-name}/{:api-path}\n", 66 | "```\n", 67 | "\n", 68 | "Returns a [start response](../schema/v3.md#definitions-group-start-response), e.g.\n", 69 | "\n", 70 | "```json\n", 71 | "{\n", 72 | " \"body\": null,\n", 73 | " \"name\": \"whitepaper-single\",\n", 74 | " \"path\": \"whitepaper-single.ipynb\",\n", 75 | " \"starter\": {\n", 76 | " \"description\": \"A reusable notebook for proposing research\",\n", 77 | " \"label\": \"Whitepaper Notebook\",\n", 78 | " \"src\": \"examples/whitepaper-single.ipynb\",\n", 79 | " \"type\": \"copy\"\n", 80 | " },\n", 81 | " \"status\": \"done\"\n", 82 | "}\n", 83 | "```" 84 | ] 85 | } 86 | ], 87 | "metadata": { 88 | "kernelspec": { 89 | "display_name": "Python 3 (ipykernel)", 90 | "language": "python", 91 | "name": "python3" 92 | }, 93 | "language_info": { 94 | "codemirror_mode": { 95 | "name": "ipython", 96 | "version": 3 97 | }, 98 | "file_extension": ".py", 99 | "mimetype": "text/x-python", 100 | "name": "python", 101 | "nbconvert_exporter": "python", 102 | "pygments_lexer": "ipython3", 103 | "version": "3.10.6" 104 | } 105 | }, 106 | "nbformat": 4, 107 | "nbformat_minor": 4 108 | } 109 | -------------------------------------------------------------------------------- /jupyter_notebook_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerProxy": { 3 | "servers": { 4 | "docs": { 5 | "absolute_url": false, 6 | "command": [ 7 | "python3", 8 | "-m", 9 | "sphinx_autobuild", 10 | "/home/jovyan/docs", 11 | "/home/jovyan/build/docs/html", 12 | "--no-initial", 13 | "--port", 14 | "{port}" 15 | ], 16 | "launcher_entry": { 17 | "enabled": true, 18 | "icon_path": "/home/jovyan/packages/jupyterlab-starters/style/icons/starter.svg", 19 | "title": "Starters Documentation" 20 | }, 21 | "new_browser_tab": false, 22 | "timeout": 300 23 | } 24 | } 25 | }, 26 | "StarterManager": { 27 | "extra_jinja_env_extensions": { 28 | "jinja2_time.TimeExtension": true 29 | }, 30 | "extra_starters": { 31 | "multi-stage-notebook": { 32 | "description": "Build a directory one file at a time", 33 | "label": "Multi-Stage Starter Notebook", 34 | "src": "./examples/Multi-Stage Starter Notebook.ipynb", 35 | "type": "notebook" 36 | }, 37 | "notebook-starter": { 38 | "description": "A notebook that is also a starter", 39 | "label": "Starter Notebook", 40 | "src": "./examples/Starter Notebook.ipynb", 41 | "type": "notebook" 42 | }, 43 | "whitepaper-multiple": { 44 | "description": "Some reusable notebooks for proposing research", 45 | "icon": "", 46 | "label": "Whitepaper Folder", 47 | "src": "examples/whitepaper-multiple", 48 | "type": "copy" 49 | }, 50 | "whitepaper-named": { 51 | "description": "A renamed whitepaper", 52 | "dest": "{% now 'local' %} {{ dest }} Whitepaper.ipynb", 53 | "icon": "", 54 | "label": "Named Whitepaper", 55 | "schema": { 56 | "description": "> A whitepaper that already has a name, based on the [Heilmeier Catechism](https://www.darpa.mil/work-with-us/heilmeier-catechism).", 57 | "properties": { 58 | "dest": { 59 | "default": "Unimagined", 60 | "description": "the _topic_ of the whitepaper", 61 | "title": "## Topic", 62 | "type": "string" 63 | } 64 | }, 65 | "required": ["dest"], 66 | "title": "# A Named whitepaper", 67 | "type": "object" 68 | }, 69 | "src": "examples/whitepaper-single.ipynb", 70 | "type": "copy", 71 | "uiSchema": { 72 | "dest": { 73 | "ui:autofocus": true, 74 | "ui:help": "keep it short and simple: it will go in $1$ file named: ` Whitepaper.ipynb`" 75 | } 76 | } 77 | }, 78 | "whitepaper-single": { 79 | "description": "A reusable notebook for proposing research", 80 | "label": "Whitepaper Notebook", 81 | "src": "examples/whitepaper-single.ipynb", 82 | "type": "copy" 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /jupyter_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerProxy": { 3 | "servers": { 4 | "docs": { 5 | "absolute_url": false, 6 | "command": [ 7 | "python3", 8 | "-m", 9 | "sphinx_autobuild", 10 | "/home/jovyan/docs", 11 | "/home/jovyan/build/docs/html", 12 | "--no-initial", 13 | "--port", 14 | "{port}" 15 | ], 16 | "launcher_entry": { 17 | "enabled": true, 18 | "icon_path": "/home/jovyan/packages/jupyterlab-starters/style/icons/starter.svg", 19 | "title": "Starters Documentation" 20 | }, 21 | "new_browser_tab": false, 22 | "timeout": 300 23 | } 24 | } 25 | }, 26 | "StarterManager": { 27 | "extra_jinja_env_extensions": { 28 | "jinja2_time.TimeExtension": true 29 | }, 30 | "extra_starters": { 31 | "multi-stage-notebook": { 32 | "description": "Build a directory one file at a time", 33 | "label": "Multi-Stage Starter Notebook", 34 | "src": "./examples/Multi-Stage Starter Notebook.ipynb", 35 | "type": "notebook" 36 | }, 37 | "notebook-starter": { 38 | "description": "A notebook that is also a starter", 39 | "label": "Starter Notebook", 40 | "src": "./examples/Starter Notebook.ipynb", 41 | "type": "notebook" 42 | }, 43 | "whitepaper-multiple": { 44 | "description": "Some reusable notebooks for proposing research", 45 | "icon": "", 46 | "label": "Whitepaper Folder", 47 | "src": "examples/whitepaper-multiple", 48 | "type": "copy" 49 | }, 50 | "whitepaper-named": { 51 | "description": "A renamed whitepaper", 52 | "dest": "{% now 'local' %} {{ dest }} Whitepaper.ipynb", 53 | "icon": "", 54 | "label": "Named Whitepaper", 55 | "schema": { 56 | "description": "> A whitepaper that already has a name, based on the [Heilmeier Catechism](https://www.darpa.mil/work-with-us/heilmeier-catechism).", 57 | "properties": { 58 | "dest": { 59 | "default": "Unimagined", 60 | "description": "the _topic_ of the whitepaper", 61 | "title": "## Topic", 62 | "type": "string" 63 | } 64 | }, 65 | "required": ["dest"], 66 | "title": "# A Named whitepaper", 67 | "type": "object" 68 | }, 69 | "src": "examples/whitepaper-single.ipynb", 70 | "type": "copy", 71 | "uiSchema": { 72 | "dest": { 73 | "ui:autofocus": true, 74 | "ui:help": "keep it short and simple: it will go in $1$ file named: ` Whitepaper.ipynb`" 75 | } 76 | } 77 | }, 78 | "whitepaper-single": { 79 | "description": "A reusable notebook for proposing research", 80 | "label": "Whitepaper Notebook", 81 | "src": "examples/whitepaper-single.ipynb", 82 | "type": "copy" 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/_static/copybutton.js: -------------------------------------------------------------------------------- 1 | // Localization support 2 | const messages = { 3 | en: { 4 | copy: 'Copy', 5 | copy_to_clipboard: 'Copy to clipboard', 6 | copy_success: 'Copied!', 7 | copy_failure: 'Failed to copy', 8 | }, 9 | es: { 10 | copy: 'Copiar', 11 | copy_to_clipboard: 'Copiar al portapapeles', 12 | copy_success: '¡Copiado!', 13 | copy_failure: 'Error al copiar', 14 | }, 15 | de: { 16 | copy: 'Kopieren', 17 | copy_to_clipboard: 'In die Zwischenablage kopieren', 18 | copy_success: 'Kopiert!', 19 | copy_failure: 'Fehler beim Kopieren', 20 | }, 21 | }; 22 | 23 | let locale = 'en'; 24 | if ( 25 | document.documentElement.lang !== undefined && 26 | messages[document.documentElement.lang] !== undefined 27 | ) { 28 | locale = document.documentElement.lang; 29 | } 30 | 31 | /** 32 | * Set up copy/paste for code blocks 33 | */ 34 | 35 | const runWhenDOMLoaded = (cb) => { 36 | if (document.readyState != 'loading') { 37 | cb(); 38 | } else if (document.addEventListener) { 39 | document.addEventListener('DOMContentLoaded', cb); 40 | } else { 41 | document.attachEvent('onreadystatechange', function () { 42 | if (document.readyState == 'complete') cb(); 43 | }); 44 | } 45 | }; 46 | 47 | const codeCellId = (index) => `codecell${index}`; 48 | 49 | // Clears selected text since ClipboardJS will select the text when copying 50 | const clearSelection = () => { 51 | if (window.getSelection) { 52 | window.getSelection().removeAllRanges(); 53 | } else if (document.selection) { 54 | document.selection.empty(); 55 | } 56 | }; 57 | 58 | // Changes tooltip text for two seconds, then changes it back 59 | const temporarilyChangeTooltip = (el, newText) => { 60 | const oldText = el.getAttribute('data-tooltip'); 61 | el.setAttribute('data-tooltip', newText); 62 | setTimeout(() => el.setAttribute('data-tooltip', oldText), 2000); 63 | }; 64 | 65 | const addCopyButtonToCodeCells = () => { 66 | // If ClipboardJS hasn't loaded, wait a bit and try again. This 67 | // happens because we load ClipboardJS asynchronously. 68 | if (window.ClipboardJS === undefined) { 69 | setTimeout(addCopyButtonToCodeCells, 250); 70 | return; 71 | } 72 | 73 | const codeCells = document.querySelectorAll('div.highlight pre'); 74 | codeCells.forEach((codeCell, index) => { 75 | const id = codeCellId(index); 76 | codeCell.setAttribute('id', id); 77 | const pre_bg = getComputedStyle(codeCell).backgroundColor; 78 | 79 | const clipboardButton = (id) => 80 | ` 81 | ${messages[locale]['copy_to_clipboard']} 82 | `; 83 | codeCell.insertAdjacentHTML('afterend', clipboardButton(id)); 84 | }); 85 | 86 | const clipboard = new ClipboardJS('.copybtn'); 87 | clipboard.on('success', (event) => { 88 | clearSelection(); 89 | temporarilyChangeTooltip(event.trigger, messages[locale]['copy_success']); 90 | }); 91 | 92 | clipboard.on('error', (event) => { 93 | temporarilyChangeTooltip(event.trigger, messages[locale]['copy_failure']); 94 | }); 95 | }; 96 | 97 | runWhenDOMLoaded(addCopyButtonToCodeCells); 98 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/runners/server.ts: -------------------------------------------------------------------------------- 1 | import { URLExt } from '@jupyterlab/coreutils'; 2 | import { IRunningSessions } from '@jupyterlab/running'; 3 | import { ServerConnection } from '@jupyterlab/services'; 4 | import { LabIcon } from '@jupyterlab/ui-components'; 5 | import { JSONObject, JSONExt } from '@lumino/coreutils'; 6 | 7 | import * as SCHEMA from '../_schema'; 8 | import { IStarterRunner, API, IStarterManager, SERVER_NAME, EMOJI } from '../tokens'; 9 | 10 | import { BaseStarterRunner } from './_base'; 11 | 12 | const { makeRequest, makeSettings } = ServerConnection; 13 | 14 | /** A starter runner that executes on the server. */ 15 | export class ServerStarterRunner extends BaseStarterRunner implements IStarterRunner { 16 | readonly name = 'Server Starters'; 17 | private _serverSettings = makeSettings(); 18 | private _running: string[]; 19 | 20 | async fetch(): Promise { 21 | const response = await makeRequest(API, {}, this._serverSettings); 22 | const content = (await response.json()) as SCHEMA.AResponseForAnStartersRequest; 23 | this._ready.resolve(void 0); 24 | 25 | if (content.running != null && !JSONExt.deepEqual(this._running, content.running)) { 26 | this._running = content.running; 27 | this._runningChanged.emit(void 0); 28 | } 29 | } 30 | 31 | async start( 32 | name: string, 33 | _starter: SCHEMA.Starter, 34 | contentsPath: string, 35 | body?: JSONObject 36 | ): Promise { 37 | const init = { method: 'POST' } as RequestInit; 38 | if (body) { 39 | init.body = JSON.stringify(body); 40 | } 41 | const url = URLExt.join(API, name, contentsPath); 42 | const response = await makeRequest(`${url}/`, init, this._serverSettings); 43 | const result = (await response.json()) as SCHEMA.AResponseForStartRequest; 44 | return result; 45 | } 46 | 47 | async stop(name: string): Promise { 48 | const init: RequestInit = { method: 'DELETE' }; 49 | const url = URLExt.join(API, name); 50 | const response = await makeRequest(`${url}/`, init, this._serverSettings); 51 | if (response.status !== 202) { 52 | console.warn(EMOJI, 'Failed to stop', response); 53 | } 54 | await this.fetch(); 55 | } 56 | 57 | running(): IRunningSessions.IRunningItem[] { 58 | return (this._running || []).map((name) => { 59 | const starter = this._manager.starter(name); 60 | const icon = this._manager.icon(name, starter) as LabIcon; 61 | return { 62 | label: () => starter.label, 63 | open: () => void 0, 64 | shutdown: async () => this.stop(name).catch(console.warn), 65 | icon: () => icon, 66 | } as IRunningSessions.IRunningItem; 67 | }); 68 | } 69 | 70 | shutdownAll(): void { 71 | this.fetch() 72 | .then(() => this.running().map((runner) => runner.shutdown())) 73 | .catch(console.warn); 74 | } 75 | 76 | canStart(name: string, _starter: SCHEMA.Starter): boolean { 77 | const provider = this._manager.getProvider(SERVER_NAME); 78 | if (!provider) { 79 | return false; 80 | } 81 | if (provider.starters[name]) { 82 | return true; 83 | } 84 | return false; 85 | } 86 | } 87 | 88 | export namespace ServerStarterRunner { 89 | export interface IOptions { 90 | manager: IStarterManager; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/builder/buttons.tsx: -------------------------------------------------------------------------------- 1 | import { VDomRenderer } from '@jupyterlab/apputils'; 2 | import { 3 | folderIcon, 4 | runIcon, 5 | circleIcon, 6 | stopIcon, 7 | closeIcon, 8 | linkIcon, 9 | } from '@jupyterlab/ui-components'; 10 | import * as React from 'react'; 11 | 12 | import { CSS } from '../../css'; 13 | 14 | import { BuilderModel } from './model'; 15 | 16 | export class BuilderButtons extends VDomRenderer { 17 | constructor(model: BuilderModel) { 18 | super(model); 19 | this.addClass(CSS.BUILDER_BUTTONS); 20 | } 21 | 22 | protected render(): JSX.Element { 23 | const m = this.model; 24 | const { context } = m; 25 | let path = context.cwd; 26 | path = path.startsWith('/') ? path : `/${path}`; 27 | path = path.endsWith('/') ? path : `${path}/`; 28 | path = path.replace(/\//g, ' / '); 29 | 30 | return ( 31 | <> 32 |
33 | 37 | {context.starter.label} 38 |
39 |
40 | {this.renderShareButton()} 41 | {this.renderCancelButton()} 42 | {this.renderStartButton()} 43 |
44 | 45 | ); 46 | } 47 | 48 | protected renderShareButton(): JSX.Element { 49 | return ( 50 | 58 | ); 59 | } 60 | 61 | protected renderCancelButton(): JSX.Element { 62 | return ( 63 | 67 | ); 68 | } 69 | 70 | protected renderStartButton(): JSX.Element { 71 | const { status, startCount } = this.model; 72 | 73 | let icon = ; 74 | let label = 'FIXME'; 75 | let statusClass = CSS.JP.warn; 76 | 77 | switch (status) { 78 | case 'ready': 79 | icon = ; 80 | label = startCount ? 'CONTINUE' : 'START'; 81 | statusClass = CSS.JP.accept; 82 | break; 83 | case 'starting': 84 | icon = ; 85 | label = startCount > 1 ? 'CONTINUING' : 'STARTING'; 86 | break; 87 | default: 88 | break; 89 | } 90 | 91 | return ( 92 | 100 | ); 101 | } 102 | 103 | onStart: () => void = () => this.model.onStart(); 104 | onDone: () => void = () => this.model.onDone(); 105 | onShare: () => void = () => this.model.onShare(); 106 | } 107 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { PageConfig, URLExt } from '@jupyterlab/coreutils'; 2 | import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 3 | import { RenderedMarkdown } from '@jupyterlab/rendermime/lib/widgets'; 4 | import { IRunningSessions } from '@jupyterlab/running'; 5 | import { LabIcon } from '@jupyterlab/ui-components'; 6 | import { JSONObject, Token } from '@lumino/coreutils'; 7 | import { ISignal } from '@lumino/signaling'; 8 | 9 | import * as _PKG from '../package.json'; 10 | 11 | import * as SCHEMA from './_schema'; 12 | 13 | export const PKG = _PKG; 14 | 15 | export const NS = 'starters'; 16 | export const CORE_PLUGIN_ID = `${PKG.name}:core`; 17 | export const SETTINGS_PLUGIN_ID = `${PKG.name}:settings-provider`; 18 | export const API = URLExt.join(PageConfig.getBaseUrl(), 'starters'); 19 | 20 | export const DEFAULT_ICON_NAME = `${NS}:default`; 21 | export const DEFAULT_ICON_CLASS = `jp-StartersDefaultIcon`; 22 | export const CATEGORY = 'Starters'; 23 | 24 | export const SERVER_NAME = 'server'; 25 | export const BROWSER_NAME = 'browser'; 26 | export const SETTINGS_NAME = 'settings'; 27 | 28 | export const STARTER_PATTERN = new RegExp(`[\?&]starter=`); 29 | export const STARTER_NAME_PARAM = 'starter'; 30 | export const STARTER_BODY_PARAM = 'starter-body'; 31 | export const STARTER_FORM_PARAM = 'starter-form'; 32 | export const EMOJI = '🦋'; 33 | 34 | /** The token for the main extension, which can be used by other extensions */ 35 | export const IStarterManager = new Token(CORE_PLUGIN_ID); 36 | 37 | /** An interface for the starter manager. */ 38 | export interface IStarterManager extends IStarterProvider, IStarterRunner { 39 | markdown: RenderedMarkdown; 40 | icon(name: string, starter: SCHEMA.Starter): LabIcon.ILabIcon; 41 | addProvider(key: string, provider: IStarterProvider): void; 42 | addRunner(key: string, runner: IStarterRunner): void; 43 | providerNames: string[]; 44 | runnerNames: string[]; 45 | getRunner(key: string): IStarterRunner | null; 46 | getProvider(key: string): IStarterProvider | null; 47 | } 48 | 49 | /** An interface for a source of starters. */ 50 | export interface IStarterProvider { 51 | starters: SCHEMA.NamedStarters; 52 | starter(name: string): SCHEMA.Starter; 53 | fetch(): Promise; 54 | ready: Promise; 55 | changed: ISignal; 56 | } 57 | 58 | /** An interface for starter runners. */ 59 | export interface IStarterRunner extends IRunningSessions.IManager { 60 | ready: Promise; 61 | runningChanged: ISignal; 62 | fetch(): Promise; 63 | canStart(name: string, starter: SCHEMA.Starter): boolean; 64 | stop(name: string): Promise; 65 | start( 66 | name: string, 67 | starter: SCHEMA.Starter, 68 | path: string, 69 | body?: JSONObject 70 | ): Promise; 71 | } 72 | 73 | export namespace IStarterManager { 74 | export interface IOptions { 75 | rendermime: IRenderMimeRegistry; 76 | } 77 | } 78 | 79 | export namespace CommandIDs { 80 | export const start = `${NS}:start`; 81 | export const routerStart = `${NS}:router-starter`; 82 | export const notebookMeta = `${NS}:notebook-meta`; 83 | } 84 | 85 | export interface IStartContext { 86 | starter: SCHEMA.Starter; 87 | name: string; 88 | cwd: string; 89 | body: JSONObject; 90 | form?: boolean; 91 | doCommands?: boolean; 92 | } 93 | -------------------------------------------------------------------------------- /scripts/nblint.py: -------------------------------------------------------------------------------- 1 | """Linter and formatter of notebooks.""" 2 | import shutil 3 | import subprocess 4 | import sys 5 | from pathlib import Path 6 | 7 | import black 8 | import nbformat 9 | from isort.api import sort_code_string 10 | 11 | HERE = Path(__file__).parent 12 | ROOT = HERE.parent 13 | 14 | NODE = Path( 15 | shutil.which("node") or shutil.which("node.exe") or shutil.which("node.cmd") 16 | ).resolve() 17 | NODE_MODULES = ROOT / "node_modules" 18 | PRETTIER = [NODE, NODE_MODULES / ".bin/prettier"] 19 | 20 | NB_METADATA_KEYS = ["kernelspec", "language_info", "jupyter_starters"] 21 | 22 | 23 | def blacken(source): 24 | """Apply black to a source string.""" 25 | return black.format_str(source, mode=black.FileMode(line_length=88)) 26 | 27 | 28 | def nblint_one(nb_node): 29 | """Format/lint one notebook.""" 30 | changes = 0 31 | has_empty = 0 32 | nb_metadata_keys = list(nb_node.metadata.keys()) 33 | for key in nb_metadata_keys: 34 | if key not in NB_METADATA_KEYS: 35 | nb_node.metadata.pop(key) 36 | for cell in nb_node.cells: 37 | cell_type = cell["cell_type"] 38 | source = "".join(cell["source"]) 39 | if not source.strip(): 40 | has_empty += 1 41 | if cell_type == "markdown": 42 | args = [ 43 | *PRETTIER, 44 | "--stdin-filepath", 45 | "foo.md", 46 | "--prose-wrap", 47 | "always", 48 | ] 49 | prettier = subprocess.Popen( 50 | list(map(str, args)), 51 | stdin=subprocess.PIPE, 52 | stdout=subprocess.PIPE, 53 | ) 54 | out, _err = prettier.communicate(source.encode("utf-8")) 55 | new = out.decode("utf-8").rstrip() 56 | if new != source: 57 | cell["source"] = new.splitlines(True) 58 | changes += 1 59 | elif cell_type == "code": 60 | if cell["outputs"] or cell["execution_count"]: 61 | cell["outputs"] = [] 62 | cell["execution_count"] = None 63 | changes += 1 64 | if [line for line in source.splitlines() if line.strip().startswith("!")]: 65 | continue 66 | if source.startswith("%"): 67 | continue 68 | new = sort_code_string(source) 69 | new = blacken(new).rstrip() 70 | if new != source: 71 | cell["source"] = new.splitlines(True) 72 | changes += 1 73 | 74 | if has_empty: 75 | changes += 1 76 | nb_node.cells = [ 77 | cell for cell in nb_node.cells if "".join(cell["source"]).strip() 78 | ] 79 | 80 | return nb_node 81 | 82 | 83 | def nblint(nb_paths): 84 | """Lint a number of notebook paths.""" 85 | len_paths = len(nb_paths) 86 | 87 | for i, nb_path in enumerate(nb_paths): 88 | nb_text = nb_path.read_text(encoding="utf-8") 89 | 90 | if len_paths > 1: 91 | print(f"[{i + 1} of {len_paths}] {nb_path}", flush=True) 92 | 93 | nb_node = nblint_one(nbformat.reads(nb_text, 4)) 94 | 95 | with nb_path.open("w", encoding="utf-8") as fpt: 96 | nbformat.write(nb_node, fpt) 97 | 98 | return 0 99 | 100 | 101 | if __name__ == "__main__": 102 | sys.exit(nblint([Path(p) for p in sys.argv[1:]])) 103 | -------------------------------------------------------------------------------- /atest/Variables.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Variables for jupyterlab-starters 3 | 4 | 5 | *** Variables *** 6 | ${LAB CONF} jupyter_server_config.json 7 | ${LAB VERSION} ${EMPTY} 8 | ${SPLASH} id:jupyterlab-splash 9 | # to help catch hard-coded paths 10 | ${BASE} /@est/ 11 | ${MATHJAX} ${BASE}static/notebook/components/MathJax/MathJax.js 12 | # override with `python scripts/atest.py --variable HEADLESS:0` 13 | ${HEADLESS} 1 14 | ${CMD PALETTE INPUT} css:.p-CommandPalette-input 15 | ${CMD PALETTE ITEM ACTIVE} css:.p-CommandPalette-item.p-mod-active 16 | ${XP LAUNCH A SECTION} xpath://h2[contains(@class, 'jp-Launcher-sectionTitle')] 17 | ${XP LAUNCH SECTION} ${XP LAUNCH A SECTION}\[contains(text(), 'Starters')] 18 | ${CSS LAUNCH CARD} css:[data-category\="Starters"] 19 | ${CSS LAUNCH CARD SINGLE} ${CSS LAUNCH CARD}\[title\="A reusable notebook for proposing research"] 20 | ${CSS LAUNCH CARD NOTEBOOK} ${CSS LAUNCH CARD}\[title\="A notebook that is also a starter"] 21 | ${CSS LAUNCH CARD NOTEBOOK NOOP} ${CSS LAUNCH CARD}\[title\="noop"] 22 | ${CSS LAUNCH CARD NOTEBOOK MULTI} ${CSS LAUNCH CARD}\[title\="Build a directory one file at a time"] 23 | ${CSS LAUNCH CARD PARAM} ${CSS LAUNCH CARD}\[title\="A renamed whitepaper"] 24 | ${CSS LAUNCH CARD COOKIECUTTER} ${CSS LAUNCH CARD} [data-icon="starters:cookiecutter"] 25 | ${CSS LAUNCH CARD FOLDER} ${CSS LAUNCH CARD}\[title\="Some reusable notebooks for proposing research"] 26 | 27 | # in lab 3, text actually appears inside a nested `span` 28 | ${XP FILE TREE ITEM} xpath://span[contains(@class, 'jp-DirListing-itemText')] 29 | ${CSS BODYBUILDER} css:.jp-Starters-BodyBuilder 30 | ${CSS BODYBUILDER ACCEPT} css:.jp-Starters-BodyBuilder-buttons .jp-mod-accept 31 | ${CSS BODYBUILDER CANCEL} css:.jp-Starters-BodyBuilder-buttons .jp-mod-reject 32 | ${CSS NOTEBOOK SAVE} css:[data-icon="ui-components:save"] 33 | ${CSS DIALOG} css:.jp-Dialog 34 | ${CSS DIALOG OK} ${CSS DIALOG} .jp-mod-accept 35 | ${CSS NOTEBOOK TOOLBAR BUTTON} css:.jp-ToolbarButtonComponent[title^='Configure'][title$='as Starter'] 36 | ${CSS HOME FOLDER} css:.jp-FileBrowser-crumbs svg[data-icon="ui-components:folder"] 37 | ${CSS NOTEBOOK STARTER META} css:.jp-Starters-NotebookMetadata 38 | 39 | # simple content starter 40 | ${CSS LAUNCH CARD TODO} css:[data-category\="Productivity"][title\="A todo list"] 41 | ${CSS TODO BTN ADD} ${CSS BODYBUILDER} .btn-add 42 | ${CSS TODO TEXT TITLE} ${CSS BODYBUILDER} input[type=text][label=title] 43 | ${CSS TODO TEXT DESCRIPTION} ${CSS BODYBUILDER} input[type=text][label=description] 44 | 45 | ${XP MENU ITEM LABEL} xpath://div[contains(@class, 'lm-Menu-itemLabel')] 46 | ${XP MENU OPEN WITH} 47 | ... ${XP MENU ITEM LABEL}\[contains(text(), "Open With")] 48 | ${CSS FILE EDITOR} .jp-FileEditor 49 | ${XP FILE OPEN} xpath://li[@data-command="filebrowser:open"] 50 | ${XP FILE OPENER} ${XP FILE OPEN}//div[contains(@class, 'lm-Menu-itemLabel')] 51 | ${XP MENU MARKDOWN} ${XP FILE OPENER}\[contains(., "arkdown Preview")] 52 | -------------------------------------------------------------------------------- /scripts/integrity.py: -------------------------------------------------------------------------------- 1 | """Check internal version consistency. 2 | 3 | these should be quick to run (not invoke any other process) 4 | """ 5 | # pylint: disable=redefined-outer-name,unused-variable 6 | import json 7 | import pathlib 8 | import sys 9 | import tempfile 10 | from importlib.util import find_spec 11 | 12 | try: 13 | import tomllib 14 | except ImportError: 15 | import tomli as tomllib 16 | 17 | import jsonschema 18 | import pytest 19 | 20 | ROOT = pathlib.Path(__file__).parent.parent 21 | 22 | # docs 23 | MAIN_README = ROOT / "README.md" 24 | CHANGELOG = ROOT / "CHANGELOG.md" 25 | 26 | # TS stuff 27 | NPM_NS = "@deathbeds" 28 | PACKAGES = { 29 | package["name"]: [path.parent, package] 30 | for path, package in [ 31 | (path, json.loads(path.read_text())) 32 | for path in ROOT.glob("packages/*/package.json") 33 | ] 34 | } 35 | MAIN_NAME = f"{NPM_NS}/jupyterlab-starters" 36 | META_NAME = f"{NPM_NS}/metapackage-jupyterlab-starters" 37 | RJSF_NAME = f"{NPM_NS}/jupyterlab-rjsf" 38 | 39 | MAIN_EXT_VERSION = PACKAGES[MAIN_NAME][1]["version"] 40 | RJSF_EXT_VERSION = PACKAGES[RJSF_NAME][1]["version"] 41 | 42 | # py stuff 43 | PY_NAME = "jupyter_starters" 44 | PYPROJECT_TOML = ROOT / "pyproject.toml" 45 | PYPROJECT = tomllib.loads(PYPROJECT_TOML.read_text(encoding="utf-8")) 46 | PY_VERSION = PYPROJECT["project"]["version"] 47 | 48 | 49 | @pytest.fixture(scope="module") 50 | def the_meta_package(): 51 | """Load up the metapackage.""" 52 | meta_path, meta = PACKAGES[META_NAME] 53 | return ( 54 | meta_path, 55 | meta, 56 | json.loads((meta_path / "src/tsconfig.json").read_text()), 57 | (meta_path / "src" / "index.ts").read_text(), 58 | ) 59 | 60 | 61 | @pytest.mark.parametrize( 62 | "name,info", [p for p in PACKAGES.items() if p[0] != META_NAME] 63 | ) 64 | def test_ts_package_integrity(name, info, the_meta_package): 65 | """Are the typescript packages self-consistent.""" 66 | m_path, m_pkg, m_tsconfig, m_index = the_meta_package 67 | path, pkg = info 68 | 69 | assert ( 70 | name in m_pkg["dependencies"] 71 | ), f"{name} missing from metapackage/package.json" 72 | 73 | assert f"'{name}'" in m_index, f"{name} missing from metapackage/src/index.ts" 74 | 75 | assert [ 76 | ref 77 | for ref in m_tsconfig["references"] 78 | if ref["path"] == f"../../{path.name}/src" 79 | ], f"{name} missing from metapackage/tsconfig.json" 80 | 81 | schemas = list(path.glob("schema/*.json")) 82 | 83 | if schemas: 84 | for schema in schemas: 85 | schema_instance = json.loads(schema.read_text()) 86 | jsonschema.validators.Draft7Validator(schema_instance) 87 | 88 | 89 | @pytest.mark.parametrize( 90 | "pkg,version", 91 | [ 92 | [PY_NAME, PY_VERSION], 93 | [MAIN_NAME, MAIN_EXT_VERSION], 94 | [RJSF_NAME, RJSF_EXT_VERSION], 95 | ], 96 | ) 97 | def test_changelog_versions(pkg, version): 98 | """is the changelog up-to-date(ish)""" 99 | assert f"## `{pkg} {version}`" in CHANGELOG.read_text() 100 | 101 | 102 | def integrity(): 103 | """Run the integrity checks.""" 104 | with tempfile.TemporaryDirectory() as tmpd: 105 | ini = pathlib.Path(tmpd) / "pytest.ini" 106 | ini.write_text(pathlib.Path(ROOT / "scripts" / "fake_pytest.ini").read_text()) 107 | 108 | args = ["-c", str(ini), "-vv", __file__] 109 | try: 110 | if find_spec("pytest_azurepipelines"): 111 | args += ["--no-coverage-upload"] 112 | except ImportError: 113 | pass 114 | 115 | return pytest.main(args) 116 | 117 | 118 | if __name__ == "__main__": 119 | sys.exit(integrity()) 120 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/icons/starter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /atest/lite/01_Todo.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Check the the TODO starter 3 | 4 | Resource ../Keywords.resource 5 | Resource ../CodeMirror.resource 6 | Resource ./Keywords.resource 7 | Library String 8 | 9 | Suite Setup Setup Suite For Screenshots lite${/}todo 10 | 11 | Test Tags example:todo 12 | 13 | 14 | *** Test Cases *** 15 | Run TODO With Form 16 | [Documentation] Does the TODO example work? 17 | ${prefix} = Set Variable basic- 18 | Wait Until Page Contains Element ${CSS LAUNCH CARD TODO} 19 | Click Element ${CSS LAUNCH CARD TODO} 20 | ${title} = Fill TODO Starter Title ${prefix} 21 | ${task} = Add TODO Starter Item ${prefix} 22 | Click Element ${CSS BODYBUILDER ACCEPT} 23 | TODO Starter File Contains ${prefix} ${title} ${task} 24 | 25 | Run TODO With URL 26 | [Documentation] Does the TODO example work with a URL? 27 | ${prefix} = Set Variable url- 28 | Open JupyterLite ?starter=todo 29 | ${title} = Fill TODO Starter Title ${prefix} 30 | ${task} = Add TODO Starter Item ${prefix} 31 | Click Element ${CSS BODYBUILDER ACCEPT} 32 | TODO Starter File Contains ${prefix} ${title} ${task} 33 | 34 | Run TODO With URL Body 35 | [Documentation] Does the TODO example work with a URL containing a body? 36 | ${prefix} = Set Variable url-body- 37 | ${title} = Generate Random String 38 | ${body} = Set Variable \{"title":"${title}"} 39 | Open JupyterLite ?starter=todo&starter-body=${body} 40 | ${task} = Add TODO Starter Item ${prefix} 41 | Click Element ${CSS BODYBUILDER ACCEPT} 42 | TODO Starter File Contains ${prefix} ${title} ${task} 43 | 44 | Run TODO Without Form 45 | [Documentation] Does the TODO example work with a URL containing a body? 46 | ${prefix} = Set Variable url-no-form- 47 | ${title} = Generate Random String 48 | ${task} = Generate Random String 49 | ${body} = Set Variable 50 | ... \{"title":"${title}","items":[\{"description": "${task}"}]} 51 | Open JupyterLite ?starter=todo&starter-form=0&starter-body=${body} 52 | TODO Starter File Contains ${prefix} ${title} ${task} 53 | 54 | 55 | *** Keywords *** 56 | Add TODO Starter Item 57 | [Documentation] Add and fill in a task, generating a random description if needed. 58 | [Arguments] ${prefix} ${task}=${EMPTY} 59 | IF not "${task}" 60 | ${task} = Generate Random String 61 | END 62 | Wait Until Page Contains Element ${CSS TODO BTN ADD} 63 | Click Element ${CSS TODO BTN ADD} 64 | Wait Until Page Contains Element ${CSS TODO TEXT DESCRIPTION} 65 | Really Input Text ${CSS TODO TEXT DESCRIPTION} ${task} 66 | Capture Page Screenshot ${prefix}01-filled.png 67 | RETURN ${task} 68 | 69 | Fill TODO Starter Title 70 | [Documentation] Fill in the starter title, generating a random one if needed. 71 | [Arguments] ${prefix} ${title}=${EMPTY} 72 | IF not "${title}" 73 | ${title} = Generate Random String 74 | END 75 | Wait Until Page Contains Element ${CSS TODO TEXT TITLE} 76 | Capture Page Screenshot ${prefix}00-empty.png 77 | Really Input Text ${CSS TODO TEXT TITLE} ${title} 78 | RETURN ${title} 79 | 80 | TODO Starter File Contains 81 | [Documentation] Verify the text contains 82 | [Arguments] ${prefix} @{texts} 83 | Wait Until Page Contains ${CSS FILE EDITOR} 84 | Capture Page Screenshot ${prefix}02-editor.png 85 | Open In TODO.md ${XP MENU MARKDOWN} 86 | Capture Page Screenshot ${prefix}03-preview.png 87 | FOR ${text} IN @{texts} 88 | CodeMirror Value Contains ${CSS FILE EDITOR} .CodeMirror ${text} 89 | END 90 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/src/widgets/builder/share.tsx: -------------------------------------------------------------------------------- 1 | import { Clipboard, VDomRenderer, VDomModel } from '@jupyterlab/apputils'; 2 | import { copyIcon, LabIcon, checkIcon } from '@jupyterlab/ui-components'; 3 | import * as React from 'react'; 4 | 5 | import { CSS } from '../../css'; 6 | import { 7 | STARTER_BODY_PARAM, 8 | STARTER_FORM_PARAM, 9 | STARTER_NAME_PARAM, 10 | } from '../../tokens'; 11 | 12 | import { BuilderModel } from './model'; 13 | 14 | export class ShareForm extends VDomRenderer { 15 | constructor(model: ShareForm.Model) { 16 | super(model); 17 | this.addClass(CSS.SHARE_FORM); 18 | } 19 | protected render(): JSX.Element { 20 | const { builderModel, icon, buttonLabel } = this.model; 21 | if (!builderModel.showShare || !builderModel.form) { 22 | this.hide(); 23 | return <>; 24 | } 25 | 26 | const { url } = this.model; 27 | 28 | this.show(); 29 | return ( 30 | <> 31 |

32 | 37 |

38 |
39 | 47 |
48 |
49 | 50 | 58 |
59 | 60 | ); 61 | } 62 | 63 | onClick = (): void => this.model.copy(); 64 | onShowForm = (): void => { 65 | this.model.withForm = !this.model.withForm; 66 | }; 67 | } 68 | 69 | export namespace ShareForm { 70 | export class Model extends VDomModel { 71 | builderModel: BuilderModel; 72 | _icon: LabIcon = copyIcon; 73 | _withForm: boolean = true; 74 | 75 | constructor(builderModel: BuilderModel) { 76 | super(); 77 | this.builderModel = builderModel; 78 | this.builderModel.stateChanged.connect(() => this.stateChanged.emit(void 0)); 79 | } 80 | 81 | copy(): void { 82 | Clipboard.copyToSystem(this.url); 83 | this.icon = checkIcon; 84 | setTimeout(() => (this.icon = copyIcon), 500); 85 | } 86 | 87 | get url(): string { 88 | let rawParams: Record = { 89 | [STARTER_NAME_PARAM]: this.builderModel.context.name, 90 | [STARTER_BODY_PARAM]: JSON.stringify(this.builderModel.form.formData), 91 | }; 92 | 93 | if (!this._withForm) { 94 | rawParams = { 95 | ...rawParams, 96 | [STARTER_FORM_PARAM]: 0, 97 | }; 98 | } 99 | 100 | const params = new URLSearchParams(rawParams); 101 | 102 | return `?${params.toString()}`; 103 | } 104 | 105 | get icon(): LabIcon { 106 | return this._icon; 107 | } 108 | 109 | set icon(icon: LabIcon) { 110 | this._icon = icon; 111 | this.stateChanged.emit(void 0); 112 | } 113 | 114 | get withForm(): boolean { 115 | return this._withForm; 116 | } 117 | 118 | set withForm(withForm: boolean) { 119 | this._withForm = withForm; 120 | this.stateChanged.emit(void 0); 121 | } 122 | 123 | get buttonLabel(): string { 124 | return this.icon === copyIcon ? 'Copy' : 'OK'; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /packages/jupyterlab-starters/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./variables.css'); 2 | @import url('./share.css'); 3 | @import url('./preview.css'); 4 | @import url('./dark.css'); 5 | 6 | .jp-StartersDefaultIcon { 7 | background-image: url('./icons/starter.svg'); 8 | } 9 | 10 | #jp-left-stack > .p-Widget.jp-Starters-FormPanel, 11 | #jp-right-stack > .p-Widget.jp-Starters-FormPanel, 12 | .jp-Starters-FormPanel { 13 | background-color: var(--jp-layout-color2); 14 | color: var(--jp-ui-font-color1); 15 | display: flex; 16 | flex-direction: column; 17 | flex: 1; 18 | min-width: 350px !important; 19 | width: 350px; 20 | } 21 | 22 | .jp-Starters-FormPanel .jp-Starters-BodyBuilder-FormWrapper { 23 | overflow-y: scroll; 24 | } 25 | 26 | .jp-Starters-FormPanel .jp-SchemaForm-Widget { 27 | flex: 1; 28 | display: flex; 29 | flex-direction: column; 30 | } 31 | 32 | .jp-Starters-FormPanel .jp-SchemaForm { 33 | flex: 1; 34 | background-color: var(--jp-layout-color0); 35 | display: flex; 36 | flex-direction: column; 37 | max-height: 100%; 38 | } 39 | 40 | .jp-Starters-FormPanel .jp-SchemaForm > .form-group { 41 | padding: var(--jp-notebook-padding); 42 | overflow-y: auto; 43 | } 44 | 45 | .jp-Starters-FormPanel .jp-SchemaForm > .panel.panel-danger.errors { 46 | padding: var(--jp-notebook-padding); 47 | background-color: var(--jp-layout-color1); 48 | border-top: solid 1px var(--jp-warn-color0); 49 | max-height: 33%; 50 | flex: 0; 51 | } 52 | 53 | .jp-Starters-NotebookMetadata { 54 | min-width: 350px; 55 | } 56 | 57 | .jp-Starters-BodyBuilder-buttons { 58 | flex: 0; 59 | max-height: var(--jp-starters-btns-height); 60 | height: var(--jp-starters-btns-height); 61 | display: flex; 62 | flex-direction: column; 63 | border-top: solid 1px var(--jp-border-color0); 64 | border-bottom: solid 1px var(--jp-border-color0); 65 | padding: var(--jp-notebook-padding); 66 | } 67 | 68 | .jp-Starters-BodyBuilder-buttons > footer { 69 | flex: 0; 70 | min-height: var(--jp-ui-font-size3); 71 | color: var(--jp-ui-font-color2); 72 | text-overflow: ellipsis; 73 | overflow: hidden; 74 | display: flex; 75 | flex-direction: row; 76 | } 77 | 78 | .jp-Starters-BodyBuilder-buttons > footer > label { 79 | display: flex; 80 | flex: 1; 81 | flex-direction: row; 82 | align-items: center; 83 | white-space: nowrap; 84 | } 85 | 86 | .jp-Starters-BodyBuilder-buttons > footer > label [data-icon] { 87 | margin-right: 0.25em; 88 | } 89 | 90 | .jp-Starters-BodyBuilder-buttons > footer strong { 91 | flex: 1; 92 | font-weight: bold; 93 | white-space: nowrap; 94 | text-align: right; 95 | } 96 | 97 | .jp-Starters-BodyBuilder-buttons section { 98 | display: flex; 99 | flex: 1; 100 | flex-direction: row; 101 | max-height: var(--jp-starters-btn-height); 102 | } 103 | 104 | .jp-Starters-BodyBuilder-buttons button { 105 | margin: var(--jp-notebook-padding) 0 var(--jp-notebook-padding) 106 | var(--jp-notebook-padding); 107 | height: unset; 108 | display: flex; 109 | flex-direction: row; 110 | align-items: center; 111 | justify-content: center; 112 | flex: 1; 113 | cursor: pointer; 114 | } 115 | 116 | .jp-Starters-BodyBuilder-buttons button > label { 117 | cursor: pointer; 118 | } 119 | 120 | .jp-Starters-BodyBuilder-buttons button:first-child { 121 | margin-left: 0; 122 | } 123 | 124 | .jp-Starters-BodyBuilder-buttons button:disabled { 125 | opacity: 0.5; 126 | } 127 | 128 | .jp-Starters-BodyBuilder-buttons div[data-icon] { 129 | display: flex; 130 | flex-direction: column; 131 | } 132 | 133 | .jp-Starters-BodyBuilder-buttons button [fill]:not([fill='none']) { 134 | fill: var(--jp-ui-font-color0); 135 | } 136 | 137 | .jp-Starters-BodyBuilder-buttons button.jp-mod-warn [fill]:not([fill='none']), 138 | .jp-Starters-BodyBuilder-buttons button.jp-mod-accept [fill]:not([fill='none']) { 139 | fill: white; 140 | } 141 | 142 | .jp-Starters-FormPanel .jp-RenderedHTMLCommon { 143 | padding: 0; 144 | } 145 | -------------------------------------------------------------------------------- /packages/jupyterlab-rjsf/src/fields/jsonobject/index.tsx: -------------------------------------------------------------------------------- 1 | import { JSONExt } from '@lumino/coreutils'; 2 | import { getDefaultRegistry } from '@rjsf/core'; 3 | import _ObjectField from '@rjsf/core/lib/components/fields/ObjectField'; 4 | import { Field, retrieveSchema } from '@rjsf/utils'; 5 | import * as CodeMirror from 'codemirror'; 6 | import * as React from 'react'; 7 | import { UnControlled } from 'react-codemirror2'; 8 | 9 | /** 10 | * This is a pretty nasty way to deal with the ObjectField being very hard 11 | * to reach at import time 12 | * 13 | * We use the upstream ObjectField at runtime to construct a private class. 14 | * This could likely be improved with a namespace or something. 15 | */ 16 | export function makeJSONObjectField(ObjectField: typeof _ObjectField): Field { 17 | /** 18 | * A raw JSON Object which can be stored/edited inside an RJSF 19 | */ 20 | class JSONObjectField extends ObjectField { 21 | protected _editor: CodeMirror.Editor; 22 | 23 | render(): JSX.Element { 24 | const { 25 | uiSchema, 26 | formData, 27 | idSchema, 28 | name, 29 | registry = getDefaultRegistry(), 30 | } = this.props; 31 | const { definitions } = registry; 32 | const schema = retrieveSchema(this.props.schema, definitions, formData); 33 | 34 | let title; 35 | if (this.state.wasPropertyKeyModified) { 36 | title = name; 37 | } else { 38 | title = schema.title === undefined ? name : schema.title; 39 | } 40 | 41 | const isLight = !!document.querySelector('body[data-jp-theme-light="true"]'); 42 | 43 | const description = uiSchema['ui:description'] || schema.description; 44 | const { canSave } = this; 45 | 46 | const options = { 47 | mode: 'application/json', 48 | theme: isLight ? 'default' : 'zenburn', 49 | matchBrackets: true, 50 | autoCloseBrackets: true, 51 | }; 52 | 53 | return ( 54 | <> 55 | {title} 56 |

{description}

57 |
58 | (this._editor = editor)} 60 | value={JSON.stringify(formData, null, 2)} 61 | options={options} 62 | onChange={this.onChange} 63 | /> 64 |
65 |
66 | 74 | 83 |
84 | 85 | ); 86 | } 87 | 88 | get canSave() { 89 | return ( 90 | !this.state.editorError && 91 | this.state.editorValue && 92 | !JSONExt.deepEqual(this.state.editorValue, this.props.formData) 93 | ); 94 | } 95 | 96 | onReset = () => { 97 | this._editor.getDoc().setValue(JSON.stringify(this.props.formData, null, 2)); 98 | }; 99 | 100 | onSave = () => { 101 | this.props.onChange(this.state.editorValue); 102 | }; 103 | 104 | onChange = (editor: CodeMirror.Editor, data: any, value: string) => { 105 | try { 106 | const jsonValue = JSON.parse(value); 107 | this.setState({ editorValue: jsonValue, editorError: false }); 108 | } catch (err) { 109 | this.setState({ editorValue: null, editorError: true }); 110 | } 111 | }; 112 | } 113 | 114 | return JSONObjectField as any as Field; 115 | } 116 | --------------------------------------------------------------------------------