├── .bumpversion.cfg ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── actions │ └── build-dist │ │ └── action.yml └── workflows │ ├── binder.yml │ ├── build.yml │ ├── buildutils.yml │ ├── check-release.yml │ ├── enforce-labels.yml │ └── ui-tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── RELEASE.md ├── app ├── index.js ├── package.json ├── publicpath.js ├── style.js ├── webpack.config.js └── webpack.config.watch.js ├── binder ├── environment.yml ├── example.ipynb └── postBuild ├── buildutils ├── package.json ├── src │ ├── develop.ts │ ├── ensure-repo.ts │ ├── release-bump.ts │ ├── release-patch.ts │ └── utils.ts └── tsconfig.json ├── jupyter-config ├── jupyter_notebook_config.d │ └── retrolab.json └── jupyter_server_config.d │ └── retrolab.json ├── jupyter_config.json ├── lerna.json ├── lint-staged.config.js ├── logo.png ├── logo.svg ├── package.json ├── packages ├── _metapackage │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── application-extension │ ├── package.json │ ├── schema │ │ ├── menus.json │ │ └── top.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── application │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── app.ts │ │ ├── index.ts │ │ └── shell.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ ├── test │ │ └── shell.spec.ts │ ├── tsconfig.json │ └── tsconfig.test.json ├── console-extension │ ├── package.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── docmanager-extension │ ├── package.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── documentsearch-extension │ ├── package.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── help-extension │ ├── package.json │ ├── src │ │ └── index.tsx │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── lab-extension │ ├── package.json │ ├── schema │ │ └── interface-switcher.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── notebook-extension │ ├── package.json │ ├── schema │ │ └── scroll-output.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ ├── index.js │ │ └── variables.css │ └── tsconfig.json ├── terminal-extension │ ├── package.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── tree-extension │ ├── package.json │ ├── src │ │ └── index.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json └── ui-components │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── icon │ │ ├── iconimports.ts │ │ └── index.ts │ ├── index.ts │ └── svg.d.ts │ ├── style │ ├── base.css │ ├── icons │ │ ├── jupyter.svg │ │ ├── retrolab.svg │ │ ├── retrolabInline.svg │ │ └── retrolabSun.svg │ ├── index.css │ └── index.js │ ├── test │ └── foo.spec.ts │ ├── tsconfig.json │ └── tsconfig.test.json ├── pyproject.toml ├── retrolab ├── __init__.py ├── __main__.py ├── _version.py ├── app.py ├── serverextension.py ├── static │ └── favicons │ │ └── favicon-console.ico └── templates │ ├── consoles.html │ ├── edit.html │ ├── error.html │ ├── notebooks.html │ ├── terminals.html │ └── tree.html ├── setup.cfg ├── setup.py ├── tsconfig.eslint.json ├── tsconfig.test.json ├── tsconfigbase.json ├── tsconfigbase.test.json ├── ui-tests ├── package.json ├── playwright.config.ts ├── test │ ├── editor.spec.ts │ ├── fixtures.ts │ ├── jupyter_server_config.py │ ├── menus.spec.ts │ ├── menus.spec.ts-snapshots │ │ ├── opened-menu-edit-chromium-linux.png │ │ ├── opened-menu-edit-firefox-linux.png │ │ ├── opened-menu-file-chromium-linux.png │ │ ├── opened-menu-file-firefox-linux.png │ │ ├── opened-menu-file-new-chromium-linux.png │ │ ├── opened-menu-file-new-firefox-linux.png │ │ ├── opened-menu-help-chromium-linux.png │ │ ├── opened-menu-help-firefox-linux.png │ │ ├── opened-menu-kernel-chromium-linux.png │ │ ├── opened-menu-kernel-firefox-linux.png │ │ ├── opened-menu-run-chromium-linux.png │ │ ├── opened-menu-run-firefox-linux.png │ │ ├── opened-menu-settings-chromium-linux.png │ │ ├── opened-menu-settings-firefox-linux.png │ │ ├── opened-menu-settings-theme-chromium-linux.png │ │ ├── opened-menu-settings-theme-firefox-linux.png │ │ ├── opened-menu-view-chromium-linux.png │ │ └── opened-menu-view-firefox-linux.png │ ├── mobile.spec.ts │ ├── mobile.spec.ts-snapshots │ │ ├── notebook-chromium-linux.png │ │ ├── notebook-firefox-linux.png │ │ ├── tree-chromium-linux.png │ │ └── tree-firefox-linux.png │ ├── notebook.spec.ts │ ├── notebooks │ │ ├── autoscroll.ipynb │ │ └── empty.ipynb │ ├── settings.spec.ts │ ├── settings.spec.ts-snapshots │ │ ├── top-hidden-chromium-linux.png │ │ ├── top-hidden-firefox-linux.png │ │ ├── top-visible-chromium-linux.png │ │ └── top-visible-firefox-linux.png │ ├── smoke.spec.ts │ ├── tree.spec.ts │ └── utils.ts ├── tsconfig.test.json └── yarn.lock └── yarn.lock /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0, 4, 0, 'alpha', 1 3 | commit = False 4 | tag = False 5 | parse = (?P\d+)\,\ (?P\d+)\,\ (?P\d+)\,\ \'(?P\S+)\'\,\ (?P\d+) 6 | serialize = 7 | {major}, {minor}, {patch}, '{release}', {build} 8 | 9 | [bumpversion:part:release] 10 | optional_value = final 11 | values = 12 | alpha 13 | beta 14 | candidate 15 | final 16 | 17 | [bumpversion:part:build] 18 | 19 | [bumpversion:file:retrolab/_version.py] 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lint-staged.config.js 2 | .eslintrc.js 3 | 4 | node_modules 5 | **/build 6 | **/lib 7 | **/node_modules 8 | **/mock_packages 9 | **/static 10 | **/typings 11 | **/schemas 12 | **/themes 13 | coverage 14 | *.map.js 15 | *.bundle.js 16 | 17 | # jetbrains IDE stuff 18 | .idea/ 19 | 20 | # ms IDE stuff 21 | .history/ 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | commonjs: true, 6 | node: true, 7 | 'jest/globals': true 8 | }, 9 | root: true, 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:prettier/recommended', 15 | 'plugin:react/recommended', 16 | 'plugin:jest/recommended' 17 | ], 18 | parser: '@typescript-eslint/parser', 19 | parserOptions: { 20 | project: 'tsconfig.eslint.json', 21 | sourceType: 'module' 22 | }, 23 | plugins: ['@typescript-eslint', 'jest'], 24 | rules: { 25 | '@typescript-eslint/naming-convention': [ 26 | 'error', 27 | { 28 | selector: 'interface', 29 | format: ['PascalCase'], 30 | custom: { 31 | regex: '^I[A-Z]', 32 | match: true 33 | } 34 | } 35 | ], 36 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 37 | '@typescript-eslint/no-explicit-any': 'off', 38 | '@typescript-eslint/no-namespace': 'off', 39 | '@typescript-eslint/no-var-requires': 'off', 40 | '@typescript-eslint/no-use-before-define': 'off', 41 | '@typescript-eslint/no-empty-interface': 'off', 42 | '@typescript-eslint/quotes': [ 43 | 'error', 44 | 'single', 45 | { avoidEscape: true, allowTemplateLiterals: false } 46 | ], 47 | 'jest/no-done-callback': 'off', 48 | curly: ['error', 'all'], 49 | eqeqeq: 'error', 50 | 'prefer-arrow-callback': 'error' 51 | }, 52 | settings: { 53 | react: { 54 | version: 'detect' 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Do you have a feature request? See Notebook 7. 4 | url: https://github.com/jupyter/notebook 5 | about: The RetroLab code base has now been integrated in the Jupyter Notebook GitHub repository. Development is now happening in the Notebook repository. 6 | -------------------------------------------------------------------------------- /.github/actions/build-dist/action.yml: -------------------------------------------------------------------------------- 1 | name: "Build RetroLab" 2 | description: "Build RetroLab fron source" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Base Setup 7 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 8 | 9 | - name: Install dependencies 10 | shell: bash 11 | run: | 12 | python -m pip install --upgrade jupyter_packaging~=0.10 "jupyterlab>=3,<4" build 13 | 14 | - name: Build pypi distributions 15 | shell: bash 16 | run: | 17 | python -m build 18 | 19 | - name: Build npm distributions 20 | shell: bash 21 | run: | 22 | mkdir pkgs 23 | jlpm lerna exec -- npm pack 24 | cp packages/*/*.tgz pkgs 25 | 26 | - name: Build checksum file 27 | shell: bash 28 | run: | 29 | cd dist 30 | sha256sum * | tee SHA256SUMS 31 | cd ../pkgs 32 | sha256sum * | tee SHA256SUMS 33 | 34 | - name: Upload distributions 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: retrolab-dist-${{ github.run_number }} 38 | path: ./dist 39 | 40 | - name: Upload distributions 41 | uses: actions/upload-artifact@v2 42 | with: 43 | name: retrolab-pkgs-${{ github.run_number }} 44 | path: ./pkgs 45 | -------------------------------------------------------------------------------- /.github/workflows/binder.yml: -------------------------------------------------------------------------------- 1 | name: Binder Badge 2 | on: 3 | pull_request_target: 4 | types: [opened] 5 | 6 | jobs: 7 | binder: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - uses: jupyterlab/maintainer-tools/.github/actions/binder-link@v1 13 | with: 14 | github_token: ${{ secrets.github_token }} 15 | url_path: retro 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | permissions: 12 | contents: 13 | write 14 | 15 | env: 16 | PIP_DISABLE_PIP_VERSION_CHECK: 1 17 | 18 | defaults: 19 | run: 20 | shell: bash -l {0} 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | 29 | - name: Build 30 | uses: ./.github/actions/build-dist 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v2 37 | 38 | - name: Base Setup 39 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 40 | 41 | - name: Install dependencies 42 | run: | 43 | python -m pip install -U jupyter_packaging~=0.10 44 | 45 | - name: Install the package 46 | run: | 47 | python -m pip install . 48 | jupyter labextension list 2>&1 | grep -ie "@retrolab/lab-extension.*enabled.*ok" - 49 | jupyter server extension list 2>&1 | grep -ie "retrolab.*enabled" - 50 | python -m jupyterlab.browser_check 51 | 52 | - name: Lint 53 | run: | 54 | jlpm 55 | jlpm run eslint:check 56 | jlpm run prettier:check 57 | 58 | - name: Test 59 | run: | 60 | jlpm run build:test 61 | jlpm run test 62 | 63 | install: 64 | needs: [build] 65 | runs-on: ${{ matrix.os }}-latest 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | os: [ubuntu, macos, windows] 70 | python: ['3.7', '3.10'] 71 | include: 72 | - python: '3.7' 73 | dist: 'retrolab*.tar.gz' 74 | - python: '3.10' 75 | dist: 'retrolab*.whl' 76 | - os: windows 77 | py_cmd: python 78 | - os: macos 79 | py_cmd: python3 80 | - os: ubuntu 81 | py_cmd: python 82 | steps: 83 | - name: Install Python 84 | uses: actions/setup-python@v2 85 | with: 86 | python-version: ${{ matrix.python }} 87 | architecture: 'x64' 88 | - uses: actions/download-artifact@v2 89 | with: 90 | name: retrolab-dist-${{ github.run_number }} 91 | path: ./dist 92 | - name: Install the prerequisites 93 | run: | 94 | ${{ matrix.py_cmd }} -m pip install pip wheel 95 | - name: Install the package 96 | run: | 97 | cd dist 98 | ${{ matrix.py_cmd }} -m pip install -vv ${{ matrix.dist }} 99 | - name: Validate environment 100 | run: | 101 | ${{ matrix.py_cmd }} -m pip freeze 102 | ${{ matrix.py_cmd }} -m pip check 103 | - name: Validate the install 104 | run: | 105 | jupyter labextension list 106 | jupyter labextension list 2>&1 | grep -ie "@retrolab/lab-extension.*enabled.*ok" - 107 | jupyter server extension list 108 | jupyter server extension list 2>&1 | grep -ie "retrolab.*enabled" - 109 | jupyter retro --version 110 | jupyter retro --help 111 | -------------------------------------------------------------------------------- /.github/workflows/buildutils.yml: -------------------------------------------------------------------------------- 1 | name: Build Utilities 2 | 3 | on: 4 | push: 5 | branches: '*' 6 | pull_request: 7 | branches: '*' 8 | 9 | defaults: 10 | run: 11 | shell: bash -l {0} 12 | 13 | jobs: 14 | versioning: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Base Setup 21 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 22 | 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install -U "jupyterlab>=4.0.0a18,<5" jupyter_packaging~=0.10 26 | jlpm 27 | jlpm run build 28 | 29 | - name: Configure git identity to commit 30 | run: | 31 | git config --global user.email "you@example.com" 32 | git config --global user.name "Your Name" 33 | 34 | - name: Reset version 35 | run: | 36 | # TODO: improve this with a mock package? 37 | # This step is to ensure the workflow always starts with a final version 38 | sed -i -E "s/VersionInfo\(.*\)/VersionInfo\(9, 8, 7, 'final', 0\)/" retrolab/_version.py 39 | sed -i -E "s/current_version = .*/current_version = 9, 8, 7, 'final', 0/" .bumpversion.cfg 40 | jlpm run lerna version 9.8.7 --no-push --force-publish --no-git-tag-version --yes 41 | git commit -am "Release 9.8.7" 42 | 43 | - name: Patch Release 44 | run: | 45 | jlpm release:patch --force 46 | 47 | - name: Minor Release 48 | run: | 49 | jlpm release:bump minor --force 50 | 51 | - name: Release Cycle 52 | run: | 53 | # beta 54 | jlpm release:bump release --force 55 | # rc 56 | jlpm release:bump release --force 57 | # final 58 | jlpm release:bump release --force 59 | 60 | - name: Major Release 61 | run: | 62 | jlpm release:bump major --force 63 | 64 | npm: 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v2 69 | 70 | - name: Install Python 71 | uses: actions/setup-python@v2 72 | with: 73 | python-version: '3.9' 74 | architecture: 'x64' 75 | 76 | - name: Install dependencies 77 | run: | 78 | python -m pip install -U "jupyterlab>=4.0.0a18,<5" jupyter_packaging~=0.10 pip 79 | jlpm 80 | jlpm run build 81 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: 5 | - "*" 6 | pull_request: 7 | branches: 8 | - "*" 9 | 10 | permissions: 11 | contents: 12 | write 13 | 14 | jobs: 15 | check_release: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Base Setup 22 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - name: Upgrade packaging dependencies 25 | run: | 26 | pip install --upgrade jupyter-packaging~=0.10 --user 27 | 28 | - name: Install Dependencies 29 | run: | 30 | pip install . 31 | 32 | - name: Check Release 33 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | version_spec: next 37 | -------------------------------------------------------------------------------- /.github/workflows/enforce-labels.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: enforce-triage-label 11 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 12 | -------------------------------------------------------------------------------- /.github/workflows/ui-tests.yml: -------------------------------------------------------------------------------- 1 | name: UI Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Build 15 | uses: ./.github/actions/build-dist 16 | 17 | ui-tests: 18 | needs: [build] 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | browser: [firefox, chromium] 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Python 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: '3.9' 32 | architecture: 'x64' 33 | 34 | - uses: actions/download-artifact@v2 35 | with: 36 | name: retrolab-dist-${{ github.run_number }} 37 | path: ./dist 38 | 39 | - name: Install the prerequisites 40 | run: | 41 | python -m pip install pip wheel 42 | 43 | - name: Install the package 44 | run: | 45 | cd dist 46 | python -m pip install -vv retrolab*.whl 47 | 48 | - name: Install the test dependencies 49 | run: | 50 | cd ui-tests 51 | jlpm --frozen-lockfile 52 | jlpm playwright install 53 | 54 | - name: Start RetroLab 55 | run: | 56 | cd ui-tests 57 | jlpm start:detached 58 | 59 | - name: Wait for RetroLab 60 | uses: ifaxity/wait-on-action@v1 61 | with: 62 | resource: http-get://127.0.0.1:8888/ 63 | timeout: 360000 64 | 65 | - name: Test 66 | run: | 67 | cd ui-tests 68 | jlpm test --browser ${{ matrix.browser }} 69 | 70 | - name: Upload Playwright Test assets 71 | if: always() 72 | uses: actions/upload-artifact@v2 73 | with: 74 | name: retrolab-${{ matrix.browser }}-test-assets 75 | path: | 76 | ui-tests/test-results 77 | 78 | - name: Upload Playwright Test report 79 | if: always() 80 | uses: actions/upload-artifact@v2 81 | with: 82 | name: retrolab-${{ matrix.browser }}-test-report 83 | path: | 84 | ui-tests/playwright-report 85 | 86 | - name: Update snapshots 87 | if: failure() 88 | run: | 89 | cd ui-tests 90 | # remove previous snapshots from other browser 91 | jlpm rimraf "test/**/*-snapshots/*.png" 92 | # generate new snapshots 93 | jlpm run test:update --browser ${{ matrix.browser }} 94 | 95 | - name: Upload updated snapshots 96 | if: failure() 97 | uses: actions/upload-artifact@v2 98 | with: 99 | name: retrolab-${{ matrix.browser }}-updated-snapshots 100 | path: ui-tests/test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.egg-info/ 5 | .ipynb_checkpoints 6 | *.tsbuildinfo 7 | 8 | # Created by https://www.gitignore.io/api/python 9 | # Edit at https://www.gitignore.io/?templates=python 10 | 11 | ### Python ### 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | pip-wheel-metadata/ 35 | share/python-wheels/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | .spyproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # Mr Developer 93 | .mr.developer.cfg 94 | .project 95 | .pydevproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | .dmypy.json 103 | dmypy.json 104 | 105 | # Pyre type checker 106 | .pyre/ 107 | 108 | # OS X stuff 109 | *.DS_Store 110 | 111 | # End of https://www.gitignore.io/api/python 112 | 113 | _temp_extension 114 | junit.xml 115 | [uU]ntitled* 116 | retrolab/static/* 117 | !retrolab/static/favicons 118 | retrolab/labextension 119 | retrolab/schemas 120 | 121 | # playwright 122 | ui-tests/test-results 123 | ui-tests/playwright-report 124 | 125 | # VSCode 126 | .vscode 127 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | **/static 6 | **/labextension 7 | build 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RetroLab 2 | 3 | Thanks for contributing to RetroLab! 4 | 5 | Make sure to follow [Project Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md) 6 | for a friendly and welcoming collaborative environment. 7 | 8 | ## Setting up a development environment 9 | 10 | Note: You will need NodeJS to build the extension package. 11 | 12 | The `jlpm` command is JupyterLab's pinned version of [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 13 | `yarn` or `npm` in lieu of `jlpm` below. 14 | 15 | **Note**: we recomment using `mamba` to speed the creating of the environment. 16 | 17 | ```bash 18 | # create a new environment 19 | mamba create -n retrolab -c conda-forge python nodejs -y 20 | 21 | # activate the environment 22 | conda activate retrolab 23 | 24 | # Install package in development mode 25 | pip install -e . 26 | 27 | # Link the RetroLab JupyterLab extension and RetroLab schemas 28 | jlpm develop 29 | 30 | # Enable the server extension 31 | jupyter server extension enable retrolab 32 | ``` 33 | 34 | `retrolab` follows a monorepo structure. To build all the packages at once: 35 | 36 | ```bash 37 | jlpm build 38 | ``` 39 | 40 | There is also a `watch` script to watch for changes and rebuild the app automatically: 41 | 42 | ```bash 43 | jlpm watch 44 | ``` 45 | 46 | To make sure the `retrolab` server extension is installed: 47 | 48 | ```bash 49 | $ jupyter server extension list 50 | Config dir: /home/username/.jupyter 51 | 52 | Config dir: /home/username/miniforge3/envs/retrolab/etc/jupyter 53 | jupyterlab enabled 54 | - Validating jupyterlab... 55 | jupyterlab 3.0.0 OK 56 | retrolab enabled 57 | - Validating retrolab... 58 | retrolab 0.1.0rc2 OK 59 | nbclassic enabled 60 | - Validating nbclassic... 61 | nbclassic OK 62 | 63 | Config dir: /usr/local/etc/jupyter 64 | ``` 65 | 66 | Then start RetroLab with: 67 | 68 | ```bash 69 | jupyter retro 70 | ``` 71 | 72 | ## Running Tests 73 | 74 | To run the tests: 75 | 76 | ```bash 77 | jlpm run build:test 78 | jlpm run test 79 | ``` 80 | 81 | There are also end to end tests to cover higher level user interactions, located in the [`ui-tests`](./ui-tests) folder. To run these tests: 82 | 83 | ```bash 84 | cd ui-tests 85 | # start a new Jupyter server in a terminal 86 | jlpm start 87 | 88 | # in a new terminal, run the tests 89 | jlpm test 90 | ``` 91 | 92 | The `test` script calls the Playwright test runner. You can pass additional arguments to `playwright` by appending parameters to the command. For example to run the test in headed mode, `jlpm test --headed`. 93 | 94 | Checkout the [Playwright Command Line Reference](https://playwright.dev/docs/test-cli/) for more information about the available command line options. 95 | 96 | Running the end to end tests in headful mode will trigger something like the following: 97 | 98 | ![playwight-headed-demo](https://user-images.githubusercontent.com/591645/141274633-ca9f9c2f-eef6-430e-9228-a35827f8133d.gif) 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Project Jupyter 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include *.md 3 | include pyproject.toml 4 | include setup.py 5 | include jupyter-config/retrolab.json 6 | 7 | include package.json 8 | include install.json 9 | include ts*.json 10 | 11 | graft retrolab/labextension 12 | graft retrolab/static 13 | graft retrolab/templates 14 | 15 | # Javascript files 16 | graft src 17 | graft style 18 | prune **/node_modules 19 | prune lib 20 | 21 | # Patterns to exclude from any directory 22 | global-exclude *~ 23 | global-exclude *.pyc 24 | global-exclude *.pyo 25 | global-exclude .git 26 | global-exclude .ipynb_checkpoints 27 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing RetroLab 2 | 3 | ## Automated releases 4 | 5 | The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). 6 | 7 | We follow a similar bump strategy as in JupyterLab: https://github.com/jupyterlab/jupyterlab/blob/master/RELEASE.md#bump-version 8 | 9 | If you would still like to do the release manually instead, read below. 10 | 11 | ## Making a manual new release of RetroLab 12 | 13 | This process is still a bit manual and consists of running a couple of commands. 14 | 15 | ## Getting a clean environment 16 | 17 | Creating a new environment can help avoid pushing local changes and any extra tag. 18 | 19 | ```bash 20 | mamba create -q -y -n retrolab-release -c conda-forge twine nodejs jupyter-packaging jupyterlab -y 21 | conda activate retrolab-release 22 | ``` 23 | 24 | Alternatively, the local repository can be cleaned with: 25 | 26 | ```bash 27 | git clean -fdx 28 | ``` 29 | 30 | ## Releasing on PyPI 31 | 32 | Make sure the `dist/` folder is empty. 33 | 34 | 1. Update [retrolab/\_version.py](./retrolab/_version.py) with the new version number 35 | 2. Commit the changes 36 | 37 | - `git add retrolab/_version.py` 38 | - `git commit -m "Release x.y.z"` 39 | 40 | 3. Bump the frontend packages: 41 | 42 | - `jlpm` 43 | - `jlpm run lerna version x.y.z --no-push --amend --force-publish` 44 | 45 | 4. Run: `python -m pip install build && python -m build` 46 | 5. Double check the size of the bundles in the `dist/` folder 47 | 6. Test the release by installing the wheel or sdist: `python -m pip install ./dist/retrolab-x.y.z-py3-none-any.whl 48 | 7. `export TWINE_USERNAME=mypypi_username` 49 | 8. `twine upload dist/*` 50 | 51 | ## Releasing on conda-forge 52 | 53 | The simplest is to wait for the bot to automatically open the PR. 54 | 55 | Alternatively, to do the update manually: 56 | 57 | 1. Open a new PR on https://github.com/conda-forge/retrolab-feedstock to update the `version` and the `sha256` hash 58 | 2. Wait for the tests 59 | 3. Merge the PR 60 | 61 | The new version will be available on `conda-forge` soon after. 62 | 63 | ## Publish the packages to npm 64 | 65 | 1. Publish the packages: `jlpm run lerna publish from-package` 66 | 67 | ## Committing and tagging 68 | 69 | Push the release commit to the `main` branch: 70 | 71 | ```bash 72 | git push origin main 73 | ``` 74 | 75 | Then create a new release from the GitHub interface. 76 | -------------------------------------------------------------------------------- /app/publicpath.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | // We dynamically set the webpack public path based on the page config 5 | // settings from the JupyterLab app. We copy some of the pageconfig parsing 6 | // logic in @jupyterlab/coreutils below, since this must run before any other 7 | // files are loaded (including @jupyterlab/coreutils). 8 | 9 | /** 10 | * Get global configuration data for the Jupyter application. 11 | * 12 | * @param name - The name of the configuration option. 13 | * 14 | * @returns The config value or an empty string if not found. 15 | * 16 | * #### Notes 17 | * All values are treated as strings. 18 | * For browser based applications, it is assumed that the page HTML 19 | * includes a script tag with the id `jupyter-config-data` containing the 20 | * configuration as valid JSON. In order to support the classic Notebook, 21 | * we fall back on checking for `body` data of the given `name`. 22 | */ 23 | function getOption(name) { 24 | let configData = Object.create(null); 25 | // Use script tag if available. 26 | if (typeof document !== 'undefined' && document) { 27 | const el = document.getElementById('jupyter-config-data'); 28 | 29 | if (el) { 30 | configData = JSON.parse(el.textContent || '{}'); 31 | } 32 | } 33 | return configData[name] || ''; 34 | } 35 | 36 | // eslint-disable-next-line no-undef 37 | __webpack_public_path__ = getOption('fullStaticUrl') + '/'; 38 | -------------------------------------------------------------------------------- /app/style.js: -------------------------------------------------------------------------------- 1 | import '@jupyterlab/celltags/style/index.js'; 2 | -------------------------------------------------------------------------------- /app/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | // Heavily inspired (and slightly tweaked) from: 5 | // https://github.com/jupyterlab/jupyterlab/blob/master/examples/federated/core_package/webpack.config.js 6 | 7 | const fs = require('fs-extra'); 8 | const path = require('path'); 9 | const webpack = require('webpack'); 10 | const merge = require('webpack-merge').default; 11 | const { ModuleFederationPlugin } = webpack.container; 12 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 13 | .BundleAnalyzerPlugin; 14 | 15 | const Build = require('@jupyterlab/builder').Build; 16 | const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base'); 17 | 18 | const data = require('./package.json'); 19 | 20 | const names = Object.keys(data.dependencies).filter(name => { 21 | const packageData = require(path.join(name, 'package.json')); 22 | return packageData.jupyterlab !== undefined; 23 | }); 24 | 25 | // Ensure a clear build directory. 26 | const buildDir = path.resolve(__dirname, 'build'); 27 | if (fs.existsSync(buildDir)) { 28 | fs.removeSync(buildDir); 29 | } 30 | fs.ensureDirSync(buildDir); 31 | 32 | // Copy extra files 33 | const index = path.resolve(__dirname, 'index.js'); 34 | const cssImports = path.resolve(__dirname, 'style.js'); 35 | fs.copySync(index, path.resolve(buildDir, 'index.js')); 36 | fs.copySync(cssImports, path.resolve(buildDir, 'extraStyle.js')); 37 | 38 | const extras = Build.ensureAssets({ 39 | packageNames: names, 40 | output: buildDir, 41 | schemaOutput: path.resolve(__dirname, '..', 'retrolab') 42 | }); 43 | 44 | /** 45 | * Create the webpack ``shared`` configuration 46 | */ 47 | function createShared(packageData) { 48 | // Set up module federation sharing config 49 | const shared = {}; 50 | const extensionPackages = packageData.jupyterlab.extensions; 51 | 52 | // Make sure any resolutions are shared 53 | for (let [pkg, requiredVersion] of Object.entries(packageData.resolutions)) { 54 | shared[pkg] = { requiredVersion }; 55 | } 56 | 57 | // Add any extension packages that are not in resolutions (i.e., installed from npm) 58 | for (let pkg of extensionPackages) { 59 | if (!shared[pkg]) { 60 | shared[pkg] = { 61 | requiredVersion: require(`${pkg}/package.json`).version 62 | }; 63 | } 64 | } 65 | 66 | // Add dependencies and sharedPackage config from extension packages if they 67 | // are not already in the shared config. This means that if there is a 68 | // conflict, the resolutions package version is the one that is shared. 69 | const extraShared = []; 70 | for (let pkg of extensionPackages) { 71 | let pkgShared = {}; 72 | let { 73 | dependencies = {}, 74 | jupyterlab: { sharedPackages = {} } = {} 75 | } = require(`${pkg}/package.json`); 76 | for (let [dep, requiredVersion] of Object.entries(dependencies)) { 77 | if (!shared[dep]) { 78 | pkgShared[dep] = { requiredVersion }; 79 | } 80 | } 81 | 82 | // Overwrite automatic dependency sharing with custom sharing config 83 | for (let [dep, config] of Object.entries(sharedPackages)) { 84 | if (config === false) { 85 | delete pkgShared[dep]; 86 | } else { 87 | if ('bundled' in config) { 88 | config.import = config.bundled; 89 | delete config.bundled; 90 | } 91 | pkgShared[dep] = config; 92 | } 93 | } 94 | extraShared.push(pkgShared); 95 | } 96 | 97 | // Now merge the extra shared config 98 | const mergedShare = {}; 99 | for (let sharedConfig of extraShared) { 100 | for (let [pkg, config] of Object.entries(sharedConfig)) { 101 | // Do not override the basic share config from resolutions 102 | if (shared[pkg]) { 103 | continue; 104 | } 105 | 106 | // Add if we haven't seen the config before 107 | if (!mergedShare[pkg]) { 108 | mergedShare[pkg] = config; 109 | continue; 110 | } 111 | 112 | // Choose between the existing config and this new config. We do not try 113 | // to merge configs, which may yield a config no one wants 114 | let oldConfig = mergedShare[pkg]; 115 | 116 | // if the old one has import: false, use the new one 117 | if (oldConfig.import === false) { 118 | mergedShare[pkg] = config; 119 | } 120 | } 121 | } 122 | 123 | Object.assign(shared, mergedShare); 124 | 125 | // Transform any file:// requiredVersion to the version number from the 126 | // imported package. This assumes (for simplicity) that the version we get 127 | // importing was installed from the file. 128 | for (let [pkg, { requiredVersion }] of Object.entries(shared)) { 129 | if (requiredVersion && requiredVersion.startsWith('file:')) { 130 | shared[pkg].requiredVersion = require(`${pkg}/package.json`).version; 131 | } 132 | } 133 | 134 | // Add singleton package information 135 | for (let pkg of packageData.jupyterlab.singletonPackages) { 136 | if (shared[pkg]) { 137 | shared[pkg].singleton = true; 138 | } 139 | } 140 | 141 | return shared; 142 | } 143 | 144 | // Make a bootstrap entrypoint 145 | const entryPoint = path.join(buildDir, 'bootstrap.js'); 146 | const bootstrap = 'import("./index.js");'; 147 | fs.writeFileSync(entryPoint, bootstrap); 148 | 149 | if (process.env.NODE_ENV === 'production') { 150 | baseConfig.mode = 'production'; 151 | } 152 | 153 | if (process.argv.includes('--analyze')) { 154 | extras.push(new BundleAnalyzerPlugin()); 155 | } 156 | 157 | module.exports = [ 158 | merge(baseConfig, { 159 | mode: 'development', 160 | entry: ['./publicpath.js', './' + path.relative(__dirname, entryPoint)], 161 | output: { 162 | path: path.resolve(__dirname, '..', 'retrolab/static/'), 163 | library: { 164 | type: 'var', 165 | name: ['_JUPYTERLAB', 'CORE_OUTPUT'] 166 | }, 167 | filename: 'bundle.js' 168 | }, 169 | plugins: [ 170 | new ModuleFederationPlugin({ 171 | library: { 172 | type: 'var', 173 | name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION'] 174 | }, 175 | name: 'CORE_FEDERATION', 176 | shared: createShared(data) 177 | }) 178 | ] 179 | }) 180 | ].concat(extras); 181 | -------------------------------------------------------------------------------- /app/webpack.config.watch.js: -------------------------------------------------------------------------------- 1 | const base = require('./webpack.config'); 2 | const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin'); 3 | 4 | module.exports = [ 5 | { 6 | ...base[0], 7 | bail: false, 8 | watch: true, 9 | plugins: [ 10 | ...base[0].plugins, 11 | new ExtraWatchWebpackPlugin({ 12 | files: ['../packages/_metapackage/tsconfig.tsbuildinfo'] 13 | }) 14 | ] 15 | }, 16 | ...base.slice(1) 17 | ]; 18 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: retrolab 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - ipywidgets=7.6 6 | - jupyterlab=3 7 | - jupyterlab-language-pack-fr-FR 8 | - jupyterlab-link-share>=0.2 9 | - matplotlib 10 | - numpy 11 | - nodejs 12 | - python >=3.9,<3.10 13 | - xeus-python 14 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | python -m pip install -e . 5 | jlpm && jlpm run build 6 | jlpm run develop 7 | jupyter server extension enable retrolab 8 | jupyter serverextension enable retrolab -------------------------------------------------------------------------------- /buildutils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/buildutils", 3 | "version": "0.4.0-alpha.1", 4 | "private": true, 5 | "description": "RetroLab - Build Utilities", 6 | "homepage": "https://github.com/jupyterlab/retrolab", 7 | "bugs": { 8 | "url": "https://github.com/jupyterlab/retrolab/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/jupyterlab/retrolab.git" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Project Jupyter", 16 | "main": "lib/index.js", 17 | "types": "lib/index.d.ts", 18 | "directories": { 19 | "lib": "lib/" 20 | }, 21 | "files": [ 22 | "lib/*.d.ts", 23 | "lib/*.js.map", 24 | "lib/*.js" 25 | ], 26 | "scripts": { 27 | "build": "tsc", 28 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 29 | "prepublishOnly": "npm run build", 30 | "watch": "tsc -w --listEmittedFiles" 31 | }, 32 | "dependencies": { 33 | "@jupyterlab/buildutils": "^4.0.0-alpha.5", 34 | "commander": "^6.2.0", 35 | "fs-extra": "^9.1.0", 36 | "typescript": "~4.1.3" 37 | }, 38 | "devDependencies": { 39 | "@types/fs-extra": "^9.0.10", 40 | "@types/node": "^14.6.1", 41 | "rimraf": "~3.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /buildutils/src/develop.ts: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | import commander from 'commander'; 7 | 8 | import fs from 'fs-extra'; 9 | 10 | import path from 'path'; 11 | 12 | import process from 'process'; 13 | 14 | import { run } from '@jupyterlab/buildutils'; 15 | 16 | commander 17 | .description('Setup the repository for develop mode') 18 | .option('--overwrite', 'Force linking the RetroLab schemas') 19 | .option('--source', 'The path to the retrolab package') 20 | .action((options: any) => { 21 | const { overwrite } = options; 22 | const prefix = run( 23 | 'python -c "import sys; print(sys.prefix)"', 24 | { 25 | stdio: 'pipe' 26 | }, 27 | true 28 | ); 29 | const source = path.resolve(options.source ?? process.cwd()); 30 | const sourceDir = path.join(source, 'retrolab', 'schemas', '@retrolab'); 31 | const destDir = path.join( 32 | prefix, 33 | 'share', 34 | 'jupyter', 35 | 'lab', 36 | 'schemas', 37 | '@retrolab' 38 | ); 39 | if (overwrite) { 40 | try { 41 | fs.unlinkSync(destDir); 42 | console.log('Removed previous symlink:', destDir); 43 | } catch (e) { 44 | console.info('Skip unlinkink', destDir); 45 | } 46 | } 47 | console.log('Symlinking:', sourceDir, destDir); 48 | fs.symlinkSync(sourceDir, destDir, 'dir'); 49 | }); 50 | 51 | commander.parse(process.argv); 52 | -------------------------------------------------------------------------------- /buildutils/src/ensure-repo.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import * as fs from 'fs-extra'; 4 | 5 | import { writePackageData } from '@jupyterlab/buildutils'; 6 | 7 | /** 8 | * Ensure the application package resolutions. 9 | */ 10 | function ensureResolutions(): string[] { 11 | const basePath = path.resolve('.'); 12 | const corePath = path.join(basePath, 'app', 'package.json'); 13 | const corePackage = fs.readJSONSync(corePath); 14 | 15 | corePackage.jupyterlab.mimeExtensions = {}; 16 | corePackage.jupyterlab.linkedPackages = {}; 17 | corePackage.resolutions = {}; 18 | 19 | const packages = Object.keys(corePackage.dependencies).concat( 20 | corePackage.jupyterlab.singletonPackages 21 | ); 22 | 23 | packages.forEach(name => { 24 | const data = require(`${name}/package.json`); 25 | // Insist on a restricted version in the yarn resolution. 26 | corePackage.resolutions[name] = `~${data.version}`; 27 | }); 28 | 29 | // Write the package.json back to disk. 30 | if (writePackageData(corePath, corePackage)) { 31 | return ['Updated dev mode']; 32 | } 33 | return []; 34 | } 35 | 36 | if (require.main === module) { 37 | void ensureResolutions(); 38 | } 39 | -------------------------------------------------------------------------------- /buildutils/src/release-bump.ts: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * Inspired by: https://github.com/jupyterlab/jupyterlab/blob/master/buildutils/src/bumpversion.ts 8 | */ 9 | 10 | import * as utils from '@jupyterlab/buildutils'; 11 | 12 | import commander from 'commander'; 13 | 14 | import { getPythonVersion, postbump } from './utils'; 15 | 16 | // Specify the program signature. 17 | commander 18 | .description('Update the version') 19 | .option('--dry-run', 'Dry run') 20 | .option('--force', 'Force the upgrade') 21 | .option('--skip-commit', 'Whether to skip commit changes') 22 | .arguments('') 23 | .action((spec: any, opts: any) => { 24 | // Get the previous version. 25 | const prev = getPythonVersion(); 26 | const isFinal = /\d+\.\d+\.\d+$/.test(prev); 27 | 28 | // Whether to commit after bumping 29 | const commit = opts.skipCommit !== true; 30 | 31 | // for "next", determine whether to use "patch" or "build" 32 | if (spec === 'next') { 33 | spec = isFinal ? 'patch' : 'build'; 34 | } 35 | 36 | // For patch, defer to `patch:release` command 37 | if (spec === 'patch') { 38 | let cmd = 'jlpm run release:patch'; 39 | if (opts.force) { 40 | cmd += ' --force'; 41 | } 42 | if (!commit) { 43 | cmd += ' --skip-commit'; 44 | } 45 | utils.run(cmd); 46 | process.exit(0); 47 | } 48 | 49 | // Make sure we have a valid version spec. 50 | const options = ['major', 'minor', 'release', 'build']; 51 | if (options.indexOf(spec) === -1) { 52 | throw new Error(`Version spec must be one of: ${options}`); 53 | } 54 | if (isFinal && spec === 'release') { 55 | throw new Error('Use "major" or "minor" to switch back to alpha release'); 56 | } 57 | if (isFinal && spec === 'build') { 58 | throw new Error('Cannot increment a build on a final release'); 59 | } 60 | 61 | // Run pre-bump script. 62 | utils.prebump(); 63 | 64 | // Handle dry runs. 65 | if (opts.dryRun) { 66 | utils.run(`bumpversion --dry-run --verbose ${spec}`); 67 | return; 68 | } 69 | 70 | // If this is a major release during the alpha cycle, bump 71 | // just the Python version. 72 | if (prev.indexOf('a') !== -1 && spec === 'major') { 73 | // Bump the version. 74 | utils.run(`bumpversion ${spec}`); 75 | 76 | // Run the post-bump script. 77 | postbump(commit); 78 | 79 | return; 80 | } 81 | 82 | // Determine the version spec to use for lerna. 83 | let lernaVersion = 'preminor'; 84 | if (spec === 'build') { 85 | lernaVersion = 'prerelease'; 86 | // a -> b 87 | } else if (spec === 'release' && prev.indexOf('a') !== -1) { 88 | lernaVersion = 'prerelease --preid=beta'; 89 | // b -> rc 90 | } else if (spec === 'release' && prev.indexOf('b') !== -1) { 91 | lernaVersion = 'prerelease --preid=rc'; 92 | // rc -> final 93 | } else if (spec === 'release' && prev.indexOf('rc') !== -1) { 94 | lernaVersion = 'patch'; 95 | } 96 | if (lernaVersion === 'preminor') { 97 | lernaVersion += ' --preid=alpha'; 98 | } 99 | 100 | let cmd = `jlpm run lerna version --force-publish --no-push --no-git-tag-version ${lernaVersion}`; 101 | if (opts.force) { 102 | cmd += ' --yes'; 103 | } 104 | // For a preminor release, we bump 10 minor versions so that we do 105 | // not conflict with versions during minor releases of the top 106 | // level package. 107 | if (lernaVersion === 'preminor') { 108 | for (let i = 0; i < 10; i++) { 109 | utils.run(cmd); 110 | } 111 | } else { 112 | utils.run(cmd); 113 | } 114 | 115 | // Bump the version. 116 | utils.run(`bumpversion ${spec} --allow-dirty`); 117 | 118 | // Run the post-bump script. 119 | postbump(commit); 120 | }); 121 | 122 | commander.parse(process.argv); 123 | -------------------------------------------------------------------------------- /buildutils/src/release-patch.ts: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * Inspired by: https://github.com/jupyterlab/jupyterlab/blob/master/buildutils/src/patch-release.ts 8 | */ 9 | 10 | import * as utils from '@jupyterlab/buildutils'; 11 | 12 | import commander from 'commander'; 13 | 14 | import { getPythonVersion, postbump } from './utils'; 15 | 16 | // Specify the program signature. 17 | commander 18 | .description('Create a patch release') 19 | .option('--force', 'Force the upgrade') 20 | .option('--skip-commit', 'Whether to skip commit changes') 21 | .action((options: any) => { 22 | // Make sure we can patch release. 23 | const pyVersion = getPythonVersion(); 24 | if ( 25 | pyVersion.includes('a') || 26 | pyVersion.includes('b') || 27 | pyVersion.includes('rc') 28 | ) { 29 | throw new Error('Can only make a patch release from a final version'); 30 | } 31 | 32 | // Run pre-bump actions. 33 | utils.prebump(); 34 | 35 | // Patch the python version 36 | utils.run('bumpversion patch'); // switches to alpha 37 | utils.run('bumpversion release --allow-dirty'); // switches to beta 38 | utils.run('bumpversion release --allow-dirty'); // switches to rc. 39 | utils.run('bumpversion release --allow-dirty'); // switches to final. 40 | 41 | // Version the changed 42 | let cmd = 43 | 'jlpm run lerna version patch --no-push --force-publish --no-git-tag-version'; 44 | if (options.force) { 45 | cmd += ' --yes'; 46 | } 47 | utils.run(cmd); 48 | 49 | // Whether to commit after bumping 50 | const commit = options.skipCommit !== true; 51 | postbump(commit); 52 | }); 53 | 54 | commander.parse(process.argv); 55 | -------------------------------------------------------------------------------- /buildutils/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { run } from '@jupyterlab/buildutils'; 2 | 3 | /** 4 | * Get the current version of RetroLab 5 | */ 6 | export function getPythonVersion(): string { 7 | const cmd = 'python setup.py --version'; 8 | const lines = run(cmd, { stdio: 'pipe' }, true).split('\n'); 9 | return lines[lines.length - 1]; 10 | } 11 | 12 | export function postbump(commit = true): void { 13 | // run the integrity 14 | run('jlpm integrity'); 15 | 16 | const newPyVersion = getPythonVersion(); 17 | 18 | // Commit changes. 19 | if (commit) { 20 | run(`git commit -am "Release ${newPyVersion}"`); 21 | run(`git tag ${newPyVersion}`); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /buildutils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "module": "commonjs" 7 | }, 8 | "include": ["src/*"], 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.d/retrolab.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "retrolab": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_server_config.d/retrolab.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "retrolab": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "RetroApp": { "collaborative": true, "expose_app_in_browser": true }, 3 | "LabApp": { "collaborative": true, "expose_app_in_browser": true } 4 | } 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "version": "independent", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | const escape = require('shell-quote').quote; 2 | const fs = require('fs'); 3 | const isWin = process.platform === 'win32'; 4 | 5 | const escapeFileNames = filenames => 6 | filenames 7 | .filter(filename => fs.existsSync(filename)) 8 | .map(filename => `"${isWin ? filename : escape([filename])}"`) 9 | .join(' '); 10 | 11 | module.exports = { 12 | '**/*{.css,.json,.md}': filenames => { 13 | const escapedFileNames = escapeFileNames(filenames); 14 | return [ 15 | `prettier --write ${escapedFileNames}`, 16 | `git add -f ${escapedFileNames}` 17 | ]; 18 | }, 19 | '**/*{.ts,.tsx,.js,.jsx}': filenames => { 20 | const escapedFileNames = escapeFileNames(filenames); 21 | return [ 22 | `prettier --write ${escapedFileNames}`, 23 | `eslint --fix ${escapedFileNames}`, 24 | `git add -f ${escapedFileNames}` 25 | ]; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/logo.png -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 54 | 64 | 67 | 71 | 75 | 76 | 86 | 96 | 97 | 121 | 123 | 124 | 126 | image/svg+xml 127 | 129 | 130 | 131 | 132 | 133 | 138 | 141 | 145 | 150 | 152 | 156 | 157 | 163 | 169 | 175 | 176 | RETROLAB 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/root", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "workspaces": { 16 | "packages": [ 17 | "app", 18 | "buildutils", 19 | "packages/*" 20 | ] 21 | }, 22 | "scripts": { 23 | "build": "lerna run build", 24 | "build:prod": "lerna run build:prod", 25 | "build:test": "lerna run build:test", 26 | "clean": "lerna run clean", 27 | "develop": "jupyter labextension develop . --overwrite && node ./buildutils/lib/develop.js --overwrite", 28 | "eslint": "eslint . --ext .ts,.tsx --fix", 29 | "eslint:check": "eslint . --ext .ts,.tsx", 30 | "install": "lerna bootstrap", 31 | "integrity": "node buildutils/lib/ensure-repo.js", 32 | "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 33 | "prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 34 | "release:bump": "node ./buildutils/lib/release-bump.js", 35 | "release:patch": "node ./buildutils/lib/release-patch.js", 36 | "test": "lerna run test", 37 | "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna", 38 | "watch": "run-p watch:lib watch:app", 39 | "watch:app": "lerna exec --stream --scope \"@retrolab/app\" jlpm watch", 40 | "watch:lib": "lerna exec --stream --scope @retrolab/metapackage jlpm watch" 41 | }, 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "lint-staged" 45 | } 46 | }, 47 | "devDependencies": { 48 | "@jupyterlab/buildutils": "^4.0.0-alpha.5", 49 | "@typescript-eslint/eslint-plugin": "^4.2.0", 50 | "@typescript-eslint/parser": "^4.2.0", 51 | "eslint": "^7.10.0", 52 | "eslint-config-prettier": "^6.15.0", 53 | "eslint-plugin-jest": "^24.1.3", 54 | "eslint-plugin-prettier": "^3.1.4", 55 | "eslint-plugin-react": "^7.21.5", 56 | "extra-watch-webpack-plugin": "^1.0.3", 57 | "husky": "^3", 58 | "jest": "^26.4.2", 59 | "jest-junit": "^11.1.0", 60 | "jest-raw-loader": "^1.0.1", 61 | "jest-summary-reporter": "^0.0.2", 62 | "lerna": "^3.22.1", 63 | "lint-staged": "^10.4.0", 64 | "npm-run-all": "^4.1.5", 65 | "prettier": "^1.19.0", 66 | "rimraf": "^3.0.2", 67 | "shell-quote": "^1.7.2", 68 | "typescript": "~4.1.3" 69 | }, 70 | "jupyter-releaser": { 71 | "hooks": { 72 | "before-build-npm": [ 73 | "jlpm clean", 74 | "jlpm build:prod" 75 | ], 76 | "before-build-python": [ 77 | "jlpm clean" 78 | ], 79 | "before-bump-version": "python -m pip install bump2version" 80 | }, 81 | "options": { 82 | "version-cmd": [ 83 | "jlpm run release:bump --force --skip-commit" 84 | ] 85 | }, 86 | "skip": [ 87 | "check-links" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /packages/_metapackage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/metapackage", 3 | "private": true, 4 | "version": "0.4.0-alpha.1", 5 | "description": "RetroLab - Metapackage", 6 | "homepage": "https://github.com/jupyterlab/retrolab", 7 | "bugs": { 8 | "url": "https://github.com/jupyterlab/retrolab/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/jupyterlab/retrolab.git" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Project Jupyter", 16 | "main": "lib/index.js", 17 | "types": "lib/index.d.ts", 18 | "scripts": { 19 | "build": "tsc -b", 20 | "watch": "tsc -b -w --preserveWatchOutput" 21 | }, 22 | "dependencies": { 23 | "@retrolab/application": "file:../application", 24 | "@retrolab/application-extension": "file:../application-extension", 25 | "@retrolab/console-extension": "file:../console-extension", 26 | "@retrolab/docmanager-extension": "file:../docmanager-extension", 27 | "@retrolab/documentsearch-extension": "file:../documentsearch-extension", 28 | "@retrolab/help-extension": "file:../help-extension", 29 | "@retrolab/lab-extension": "file:../lab-extension", 30 | "@retrolab/notebook-extension": "file:../notebook-extension", 31 | "@retrolab/terminal-extension": "file:../terminal-extension", 32 | "@retrolab/tree-extension": "file:../tree-extension", 33 | "@retrolab/ui-components": "file:../ui-components" 34 | }, 35 | "devDependencies": { 36 | "typescript": "~4.1.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/_metapackage/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@retrolab/application'; 2 | import '@retrolab/application-extension'; 3 | import '@retrolab/console-extension'; 4 | import '@retrolab/docmanager-extension'; 5 | import '@retrolab/documentsearch-extension'; 6 | import '@retrolab/help-extension'; 7 | import '@retrolab/lab-extension'; 8 | import '@retrolab/notebook-extension'; 9 | import '@retrolab/terminal-extension'; 10 | import '@retrolab/tree-extension'; 11 | import '@retrolab/ui-components'; 12 | -------------------------------------------------------------------------------- /packages/_metapackage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { "path": "../application" }, 10 | { "path": "../application-extension" }, 11 | { "path": "../console-extension" }, 12 | { "path": "../docmanager-extension" }, 13 | { "path": "../documentsearch-extension" }, 14 | { "path": "../help-extension" }, 15 | { "path": "../lab-extension" }, 16 | { "path": "../notebook-extension" }, 17 | { "path": "../terminal-extension" }, 18 | { "path": "../tree-extension" }, 19 | { "path": "../ui-components" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/application-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/application-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Application Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/apputils": "^4.0.0-alpha.5", 44 | "@jupyterlab/celltags": "^4.0.0-alpha.5", 45 | "@jupyterlab/codeeditor": "^4.0.0-alpha.5", 46 | "@jupyterlab/codemirror": "^4.0.0-alpha.5", 47 | "@jupyterlab/console": "^4.0.0-alpha.5", 48 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 49 | "@jupyterlab/docmanager": "^4.0.0-alpha.5", 50 | "@jupyterlab/docregistry": "^4.0.0-alpha.5", 51 | "@jupyterlab/mainmenu": "^4.0.0-alpha.5", 52 | "@jupyterlab/settingregistry": "^4.0.0-alpha.5", 53 | "@jupyterlab/translation": "^4.0.0-alpha.4", 54 | "@lumino/coreutils": "^1.12.0", 55 | "@lumino/disposable": "^1.10.1", 56 | "@lumino/widgets": "^1.31.1", 57 | "@retrolab/application": "^0.4.0-alpha.1", 58 | "@retrolab/ui-components": "^0.4.0-alpha.1" 59 | }, 60 | "devDependencies": { 61 | "rimraf": "~3.0.0", 62 | "typescript": "~4.1.3" 63 | }, 64 | "publishConfig": { 65 | "access": "public" 66 | }, 67 | "jupyterlab": { 68 | "extension": true, 69 | "schemaDir": "schema" 70 | }, 71 | "styleModule": "style/index.js" 72 | } 73 | -------------------------------------------------------------------------------- /packages/application-extension/schema/menus.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "RetroLab Menu Entries", 3 | "description": "RetroLab Menu Entries", 4 | "jupyter.lab.menus": { 5 | "main": [ 6 | { 7 | "id": "jp-mainmenu-file", 8 | "items": [ 9 | { 10 | "command": "application:rename", 11 | "rank": 4 12 | }, 13 | { 14 | "command": "notebook:trust", 15 | "rank": 20 16 | } 17 | ] 18 | } 19 | ] 20 | }, 21 | "properties": {}, 22 | "additionalProperties": false, 23 | "type": "object" 24 | } 25 | -------------------------------------------------------------------------------- /packages/application-extension/schema/top.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon": "retro-ui-components:retroSun", 3 | "jupyter.lab.setting-icon-label": "RetroLab Top Area", 4 | "title": "RetroLab Top Area", 5 | "description": "RetroLab Top Area settings", 6 | "properties": { 7 | "visible": { 8 | "type": "boolean", 9 | "title": "Top Bar Visibility", 10 | "description": "Whether to show the top bar or not", 11 | "default": true 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | } 17 | -------------------------------------------------------------------------------- /packages/application-extension/style/base.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | 4 | | Distributed under the terms of the Modified BSD License. 5 | |----------------------------------------------------------------------------*/ 6 | 7 | .jp-RetroSpacer { 8 | flex-grow: 1; 9 | flex-shrink: 1; 10 | } 11 | 12 | .jp-MainAreaWidget { 13 | height: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /packages/application-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('~@retrolab/application/style/index.css'); 2 | @import url('~@lumino/widgets/style/index.css'); 3 | 4 | @import url('./base.css'); 5 | -------------------------------------------------------------------------------- /packages/application-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import '@retrolab/application/style/index.js'; 2 | import '@lumino/widgets/style/index.js'; 3 | 4 | import './base.css'; 5 | -------------------------------------------------------------------------------- /packages/application-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../application" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/application/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /packages/application/jest.config.js: -------------------------------------------------------------------------------- 1 | const func = require('@jupyterlab/testutils/lib/jest-config'); 2 | const upstream = func(__dirname); 3 | 4 | const esModules = ['lib0', 'y-protocols'].join('|'); 5 | 6 | let local = { 7 | preset: 'ts-jest/presets/js-with-babel', 8 | transformIgnorePatterns: [ 9 | `/node_modules/(?!${esModules}).+\\.js/(?!(@jupyterlab/.*)/)` 10 | ], 11 | globals: { 12 | 'ts-jest': { 13 | tsconfig: './tsconfig.test.json' 14 | } 15 | }, 16 | transform: { 17 | '\\.(ts|tsx)?$': 'ts-jest', 18 | '\\.svg$': 'jest-raw-loader' 19 | } 20 | }; 21 | 22 | Object.keys(local).forEach(option => { 23 | upstream[option] = local[option]; 24 | }); 25 | 26 | module.exports = upstream; 27 | -------------------------------------------------------------------------------- /packages/application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/application", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Application", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "style/*.css", 30 | "style/index.js" 31 | ], 32 | "scripts": { 33 | "build": "tsc -b", 34 | "build:prod": "tsc -b", 35 | "build:test": "tsc --build tsconfig.test.json", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "test": "jest", 40 | "test:cov": "jest --collect-coverage", 41 | "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", 42 | "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", 43 | "watch": "tsc -b --watch" 44 | }, 45 | "dependencies": { 46 | "@jupyterlab/application": "^4.0.0-alpha.5", 47 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 48 | "@jupyterlab/docregistry": "^4.0.0-alpha.5", 49 | "@jupyterlab/rendermime-interfaces": "^4.0.0-alpha.5", 50 | "@jupyterlab/ui-components": "^4.0.0-alpha.19", 51 | "@lumino/algorithm": "^1.9.1", 52 | "@lumino/coreutils": "^1.12.0", 53 | "@lumino/messaging": "^1.10.1", 54 | "@lumino/polling": "^1.10.0", 55 | "@lumino/signaling": "^1.10.1", 56 | "@lumino/widgets": "^1.31.1" 57 | }, 58 | "devDependencies": { 59 | "@babel/core": "^7.11.6", 60 | "@babel/preset-env": "^7.12.1", 61 | "@jupyterlab/testutils": "^4.0.0-alpha.5", 62 | "@types/jest": "^26.0.10", 63 | "jest": "^26.4.2", 64 | "rimraf": "~3.0.0", 65 | "ts-jest": "^26.3.0", 66 | "typescript": "~4.1.3" 67 | }, 68 | "publishConfig": { 69 | "access": "public" 70 | }, 71 | "jupyterlab": { 72 | "coreDependency": true 73 | }, 74 | "styleModule": "style/index.js" 75 | } 76 | -------------------------------------------------------------------------------- /packages/application/src/app.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | JupyterFrontEnd, 6 | JupyterFrontEndPlugin 7 | } from '@jupyterlab/application'; 8 | 9 | import { createRendermimePlugins } from '@jupyterlab/application/lib/mimerenderers'; 10 | 11 | import { LabStatus } from '@jupyterlab/application/lib/status'; 12 | 13 | import { PageConfig } from '@jupyterlab/coreutils'; 14 | 15 | import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; 16 | 17 | import { Throttler } from '@lumino/polling'; 18 | 19 | import { IRetroShell, RetroShell } from './shell'; 20 | 21 | /** 22 | * App is the main application class. It is instantiated once and shared. 23 | */ 24 | export class RetroApp extends JupyterFrontEnd { 25 | /** 26 | * Construct a new RetroApp object. 27 | * 28 | * @param options The instantiation options for an application. 29 | */ 30 | constructor(options: RetroApp.IOptions = { shell: new RetroShell() }) { 31 | super({ 32 | ...options, 33 | shell: options.shell ?? new RetroShell() 34 | }); 35 | if (options.mimeExtensions) { 36 | for (const plugin of createRendermimePlugins(options.mimeExtensions)) { 37 | this.registerPlugin(plugin); 38 | } 39 | } 40 | this.restored.then(() => this._formatter.invoke()); 41 | } 42 | 43 | /** 44 | * The name of the application. 45 | */ 46 | readonly name = 'RetroLab'; 47 | 48 | /** 49 | * A namespace/prefix plugins may use to denote their provenance. 50 | */ 51 | readonly namespace = this.name; 52 | 53 | /** 54 | * The application busy and dirty status signals and flags. 55 | */ 56 | readonly status = new LabStatus(this); 57 | 58 | /** 59 | * The version of the application. 60 | */ 61 | 62 | readonly version = PageConfig.getOption('appVersion') ?? 'unknown'; 63 | 64 | /** 65 | * The JupyterLab application paths dictionary. 66 | */ 67 | get paths(): JupyterFrontEnd.IPaths { 68 | return { 69 | urls: { 70 | base: PageConfig.getOption('baseUrl'), 71 | notFound: PageConfig.getOption('notFoundUrl'), 72 | app: PageConfig.getOption('appUrl'), 73 | static: PageConfig.getOption('staticUrl'), 74 | settings: PageConfig.getOption('settingsUrl'), 75 | themes: PageConfig.getOption('themesUrl'), 76 | doc: PageConfig.getOption('docUrl'), 77 | translations: PageConfig.getOption('translationsApiUrl'), 78 | hubHost: PageConfig.getOption('hubHost') || undefined, 79 | hubPrefix: PageConfig.getOption('hubPrefix') || undefined, 80 | hubUser: PageConfig.getOption('hubUser') || undefined, 81 | hubServerName: PageConfig.getOption('hubServerName') || undefined 82 | }, 83 | directories: { 84 | appSettings: PageConfig.getOption('appSettingsDir'), 85 | schemas: PageConfig.getOption('schemasDir'), 86 | static: PageConfig.getOption('staticDir'), 87 | templates: PageConfig.getOption('templatesDir'), 88 | themes: PageConfig.getOption('themesDir'), 89 | userSettings: PageConfig.getOption('userSettingsDir'), 90 | serverRoot: PageConfig.getOption('serverRoot'), 91 | workspaces: PageConfig.getOption('workspacesDir') 92 | } 93 | }; 94 | } 95 | 96 | /** 97 | * Handle the DOM events for the application. 98 | * 99 | * @param event - The DOM event sent to the application. 100 | */ 101 | handleEvent(event: Event): void { 102 | super.handleEvent(event); 103 | if (event.type === 'resize') { 104 | void this._formatter.invoke(); 105 | } 106 | } 107 | 108 | /** 109 | * Register plugins from a plugin module. 110 | * 111 | * @param mod - The plugin module to register. 112 | */ 113 | registerPluginModule(mod: RetroApp.IPluginModule): void { 114 | let data = mod.default; 115 | // Handle commonjs exports. 116 | if (!Object.prototype.hasOwnProperty.call(mod, '__esModule')) { 117 | data = mod as any; 118 | } 119 | if (!Array.isArray(data)) { 120 | data = [data]; 121 | } 122 | data.forEach(item => { 123 | try { 124 | this.registerPlugin(item); 125 | } catch (error) { 126 | console.error(error); 127 | } 128 | }); 129 | } 130 | 131 | /** 132 | * Register the plugins from multiple plugin modules. 133 | * 134 | * @param mods - The plugin modules to register. 135 | */ 136 | registerPluginModules(mods: RetroApp.IPluginModule[]): void { 137 | mods.forEach(mod => { 138 | this.registerPluginModule(mod); 139 | }); 140 | } 141 | 142 | private _formatter = new Throttler(() => { 143 | Private.setFormat(this); 144 | }, 250); 145 | } 146 | 147 | /** 148 | * A namespace for App statics. 149 | */ 150 | export namespace RetroApp { 151 | /** 152 | * The instantiation options for an App application. 153 | */ 154 | export interface IOptions 155 | extends JupyterFrontEnd.IOptions, 156 | Partial {} 157 | 158 | /** 159 | * The information about a RetroLab application. 160 | */ 161 | export interface IInfo { 162 | /** 163 | * The mime renderer extensions. 164 | */ 165 | readonly mimeExtensions: IRenderMime.IExtensionModule[]; 166 | } 167 | 168 | /** 169 | * The interface for a module that exports a plugin or plugins as 170 | * the default value. 171 | */ 172 | export interface IPluginModule { 173 | /** 174 | * The default export. 175 | */ 176 | default: JupyterFrontEndPlugin | JupyterFrontEndPlugin[]; 177 | } 178 | } 179 | 180 | /** 181 | * A namespace for module-private functionality. 182 | */ 183 | namespace Private { 184 | /** 185 | * Media query for mobile devices. 186 | */ 187 | const MOBILE_QUERY = 'only screen and (max-width: 760px)'; 188 | 189 | /** 190 | * Sets the `format` of a Jupyter front-end application. 191 | * 192 | * @param app The front-end application whose format is set. 193 | */ 194 | export function setFormat(app: RetroApp): void { 195 | app.format = window.matchMedia(MOBILE_QUERY).matches ? 'mobile' : 'desktop'; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /packages/application/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './app'; 5 | export * from './shell'; 6 | -------------------------------------------------------------------------------- /packages/application/style/base.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | :root { 7 | --jp-private-topbar-height: 28px; 8 | /* Override the layout-2 color for the dark theme */ 9 | --md-grey-800: #323232; 10 | --jp-notebook-max-width: 1200px; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | background: var(--jp-layout-color2); 17 | } 18 | 19 | #main { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | bottom: 0; 25 | } 26 | 27 | #top-panel-wrapper { 28 | min-height: calc(1.5 * var(--jp-private-topbar-height)); 29 | border-bottom: var(--jp-border-width) solid var(--jp-border-color0); 30 | background: var(--jp-layout-color1); 31 | } 32 | 33 | #top-panel { 34 | display: flex; 35 | min-height: calc(1.5 * var(--jp-private-topbar-height)); 36 | padding-left: 5px; 37 | padding-right: 5px; 38 | margin-left: auto; 39 | margin-right: auto; 40 | max-width: 1200px; 41 | } 42 | 43 | #menu-panel-wrapper { 44 | min-height: var(--jp-private-topbar-height); 45 | background: var(--jp-layout-color1); 46 | border-bottom: var(--jp-border-width) solid var(--jp-border-color0); 47 | box-shadow: var(--jp-elevation-z1); 48 | } 49 | 50 | #menu-panel { 51 | display: flex; 52 | min-height: var(--jp-private-topbar-height); 53 | background: var(--jp-layout-color1); 54 | padding-left: 5px; 55 | padding-right: 5px; 56 | margin-left: auto; 57 | margin-right: auto; 58 | max-width: var(--jp-notebook-max-width); 59 | } 60 | 61 | #main-panel { 62 | box-shadow: var(--jp-elevation-z4); 63 | margin-left: auto; 64 | margin-right: auto; 65 | max-width: var(--jp-notebook-max-width); 66 | } 67 | 68 | #spacer-widget { 69 | min-height: 16px; 70 | } 71 | 72 | /* Special case notebooks as document oriented pages */ 73 | 74 | body[data-retro='notebooks'] #main-panel { 75 | margin-left: unset; 76 | margin-right: unset; 77 | max-width: unset; 78 | } 79 | 80 | body[data-retro='notebooks'] #spacer-widget { 81 | min-height: unset; 82 | } 83 | -------------------------------------------------------------------------------- /packages/application/style/index.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | @import url('~@jupyterlab/application/style/index.css'); 7 | @import url('~@jupyterlab/mainmenu/style/index.css'); 8 | @import url('~@jupyterlab/ui-components/style/index.css'); 9 | 10 | @import url('./base.css'); 11 | -------------------------------------------------------------------------------- /packages/application/style/index.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | import '@jupyterlab/application/style/index.js'; 7 | import '@jupyterlab/mainmenu/style/index.js'; 8 | import '@jupyterlab/ui-components/style/index.js'; 9 | 10 | import './base.css'; 11 | -------------------------------------------------------------------------------- /packages/application/test/shell.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { RetroShell, IRetroShell } from '@retrolab/application'; 5 | 6 | import { JupyterFrontEnd } from '@jupyterlab/application'; 7 | 8 | import { toArray } from '@lumino/algorithm'; 9 | 10 | import { Widget } from '@lumino/widgets'; 11 | 12 | describe('Shell', () => { 13 | let shell: IRetroShell; 14 | 15 | beforeEach(() => { 16 | shell = new RetroShell(); 17 | Widget.attach(shell, document.body); 18 | }); 19 | 20 | afterEach(() => { 21 | shell.dispose(); 22 | }); 23 | 24 | describe('#constructor()', () => { 25 | it('should create a LabShell instance', () => { 26 | expect(shell).toBeInstanceOf(RetroShell); 27 | }); 28 | }); 29 | 30 | describe('#widgets()', () => { 31 | it('should add widgets to existing areas', () => { 32 | const widget = new Widget(); 33 | shell.add(widget, 'main'); 34 | const widgets = toArray(shell.widgets('main')); 35 | expect(widgets).toEqual([widget]); 36 | }); 37 | 38 | it('should throw an exception if the area does not exist', () => { 39 | const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell; 40 | expect(() => { 41 | jupyterFrontEndShell.widgets('left'); 42 | }).toThrow('Invalid area: left'); 43 | }); 44 | }); 45 | 46 | describe('#currentWidget', () => { 47 | it('should be the current widget in the shell main area', () => { 48 | expect(shell.currentWidget).toBe(null); 49 | const widget = new Widget(); 50 | widget.node.tabIndex = -1; 51 | widget.id = 'foo'; 52 | expect(shell.currentWidget).toBe(null); 53 | shell.add(widget, 'main'); 54 | expect(shell.currentWidget).toBe(widget); 55 | widget.parent = null; 56 | expect(shell.currentWidget).toBe(null); 57 | }); 58 | }); 59 | 60 | describe('#add(widget, "top")', () => { 61 | it('should add a widget to the top area', () => { 62 | const widget = new Widget(); 63 | widget.id = 'foo'; 64 | shell.add(widget, 'top'); 65 | const widgets = toArray(shell.widgets('top')); 66 | expect(widgets.length).toBeGreaterThan(0); 67 | }); 68 | 69 | it('should accept options', () => { 70 | const widget = new Widget(); 71 | widget.id = 'foo'; 72 | shell.add(widget, 'top', { rank: 10 }); 73 | const widgets = toArray(shell.widgets('top')); 74 | expect(widgets.length).toBeGreaterThan(0); 75 | }); 76 | }); 77 | 78 | describe('#add(widget, "main")', () => { 79 | it('should add a widget to the main area', () => { 80 | const widget = new Widget(); 81 | widget.id = 'foo'; 82 | shell.add(widget, 'main'); 83 | const widgets = toArray(shell.widgets('main')); 84 | expect(widgets.length).toBeGreaterThan(0); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /packages/application/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/application/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase.test", 3 | "include": ["src/**/*", "test/**/*"], 4 | "references": [ 5 | { 6 | "path": "." 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/console-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/console-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Console Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/console": "^4.0.0-alpha.5", 44 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 45 | "@lumino/algorithm": "^1.9.1" 46 | }, 47 | "devDependencies": { 48 | "rimraf": "~3.0.0", 49 | "typescript": "~4.1.3" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | }, 54 | "jupyterlab": { 55 | "extension": true 56 | }, 57 | "styleModule": "style/index.js" 58 | } 59 | -------------------------------------------------------------------------------- /packages/console-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | IRouter, 6 | JupyterFrontEnd, 7 | JupyterFrontEndPlugin 8 | } from '@jupyterlab/application'; 9 | 10 | import { IConsoleTracker } from '@jupyterlab/console'; 11 | 12 | import { PageConfig } from '@jupyterlab/coreutils'; 13 | 14 | import { find } from '@lumino/algorithm'; 15 | 16 | /** 17 | * A plugin to open consoles in a new tab 18 | */ 19 | const opener: JupyterFrontEndPlugin = { 20 | id: '@retrolab/console-extension:opener', 21 | requires: [IRouter], 22 | autoStart: true, 23 | activate: (app: JupyterFrontEnd, router: IRouter) => { 24 | const { commands } = app; 25 | const consolePattern = new RegExp('/consoles/(.*)'); 26 | 27 | const command = 'router:console'; 28 | commands.addCommand(command, { 29 | execute: (args: any) => { 30 | const parsed = args as IRouter.ILocation; 31 | const matches = parsed.path.match(consolePattern); 32 | if (!matches) { 33 | return; 34 | } 35 | const [, match] = matches; 36 | if (!match) { 37 | return; 38 | } 39 | 40 | const path = decodeURIComponent(match); 41 | commands.execute('console:create', { path }); 42 | } 43 | }); 44 | 45 | router.register({ command, pattern: consolePattern }); 46 | } 47 | }; 48 | 49 | /** 50 | * Open consoles in a new tab. 51 | */ 52 | const redirect: JupyterFrontEndPlugin = { 53 | id: '@retrolab/console-extension:redirect', 54 | requires: [IConsoleTracker], 55 | autoStart: true, 56 | activate: (app: JupyterFrontEnd, tracker: IConsoleTracker) => { 57 | const baseUrl = PageConfig.getBaseUrl(); 58 | tracker.widgetAdded.connect(async (send, console) => { 59 | const { sessionContext } = console; 60 | await sessionContext.ready; 61 | const widget = find(app.shell.widgets('main'), w => w.id === console.id); 62 | if (widget) { 63 | // bail if the console is already added to the main area 64 | return; 65 | } 66 | window.open(`${baseUrl}retro/consoles/${sessionContext.path}`, '_blank'); 67 | 68 | // the widget is not needed anymore 69 | console.dispose(); 70 | }); 71 | } 72 | }; 73 | 74 | /** 75 | * Export the plugins as default. 76 | */ 77 | const plugins: JupyterFrontEndPlugin[] = [opener, redirect]; 78 | 79 | export default plugins; 80 | -------------------------------------------------------------------------------- /packages/console-extension/style/base.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/packages/console-extension/style/base.css -------------------------------------------------------------------------------- /packages/console-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/console-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/console-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/docmanager-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/docmanager-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Document Manager Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 44 | "@jupyterlab/docmanager": "^4.0.0-alpha.5", 45 | "@jupyterlab/docregistry": "^4.0.0-alpha.5", 46 | "@jupyterlab/services": "^7.0.0-alpha.5", 47 | "@lumino/algorithm": "^1.9.1" 48 | }, 49 | "devDependencies": { 50 | "rimraf": "~3.0.0", 51 | "typescript": "~4.1.3" 52 | }, 53 | "publishConfig": { 54 | "access": "public" 55 | }, 56 | "jupyterlab": { 57 | "extension": true 58 | }, 59 | "styleModule": "style/index.js" 60 | } 61 | -------------------------------------------------------------------------------- /packages/docmanager-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | JupyterFrontEnd, 6 | JupyterFrontEndPlugin 7 | } from '@jupyterlab/application'; 8 | 9 | import { PageConfig, PathExt } from '@jupyterlab/coreutils'; 10 | 11 | import { IDocumentManager } from '@jupyterlab/docmanager'; 12 | 13 | import { IDocumentWidget, DocumentRegistry } from '@jupyterlab/docregistry'; 14 | 15 | import { Kernel } from '@jupyterlab/services'; 16 | 17 | /** 18 | * A plugin to open document in a new browser tab. 19 | * 20 | * TODO: remove and use a custom doc manager? 21 | */ 22 | const opener: JupyterFrontEndPlugin = { 23 | id: '@retrolab/docmanager-extension:opener', 24 | requires: [IDocumentManager], 25 | autoStart: true, 26 | activate: (app: JupyterFrontEnd, docManager: IDocumentManager) => { 27 | const baseUrl = PageConfig.getBaseUrl(); 28 | 29 | // patch the `docManager.open` option to prevent the default behavior 30 | const docOpen = docManager.open; 31 | docManager.open = ( 32 | path: string, 33 | widgetName = 'default', 34 | kernel?: Partial, 35 | options?: DocumentRegistry.IOpenOptions 36 | ): IDocumentWidget | undefined => { 37 | const ref = options?.ref; 38 | if (ref === '_noref') { 39 | docOpen.call(docManager, path, widgetName, kernel, options); 40 | return; 41 | } 42 | const ext = PathExt.extname(path); 43 | const route = ext === '.ipynb' ? 'notebooks' : 'edit'; 44 | window.open(`${baseUrl}retro/${route}/${path}`); 45 | return undefined; 46 | }; 47 | } 48 | }; 49 | 50 | /** 51 | * Export the plugins as default. 52 | */ 53 | const plugins: JupyterFrontEndPlugin[] = [opener]; 54 | 55 | export default plugins; 56 | -------------------------------------------------------------------------------- /packages/docmanager-extension/style/base.css: -------------------------------------------------------------------------------- 1 | .jp-Document { 2 | height: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /packages/docmanager-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/docmanager-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/docmanager-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/documentsearch-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/documentsearch-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Document Search Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/documentsearch": "^4.0.0-alpha.5", 44 | "@lumino/widgets": "^1.31.1", 45 | "@retrolab/application": "^0.4.0-alpha.1" 46 | }, 47 | "devDependencies": { 48 | "rimraf": "~3.0.0", 49 | "typescript": "~4.1.3" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | }, 54 | "jupyterlab": { 55 | "extension": true 56 | }, 57 | "styleModule": "style/index.js" 58 | } 59 | -------------------------------------------------------------------------------- /packages/documentsearch-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | 6 | import { ISearchProviderRegistry } from '@jupyterlab/documentsearch'; 7 | 8 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 9 | 10 | import { Widget } from '@lumino/widgets'; 11 | 12 | import { IRetroShell } from '@retrolab/application'; 13 | 14 | const SEARCHABLE_CLASS = 'jp-mod-searchable'; 15 | 16 | /** 17 | * A plugin to programmatically disable the Crtl-F shortcut in RetroLab 18 | * See https://github.com/jupyterlab/retrolab/pull/294 and 19 | * https://github.com/jupyterlab/jupyterlab/issues/11754 for more context. 20 | */ 21 | const disableShortcut: JupyterFrontEndPlugin = { 22 | id: '@retrolab/documentsearch-extension:disableShortcut', 23 | requires: [ISettingRegistry], 24 | autoStart: true, 25 | activate: async (app: JupyterFrontEnd, registry: ISettingRegistry) => { 26 | const docSearchShortcut = registry.plugins[ 27 | '@jupyterlab/documentsearch-extension:plugin' 28 | ]?.schema['jupyter.lab.shortcuts']?.find( 29 | shortcut => shortcut.command === 'documentsearch:start' 30 | ); 31 | 32 | if (docSearchShortcut) { 33 | docSearchShortcut.disabled = true; 34 | docSearchShortcut.keys = []; 35 | } 36 | } 37 | }; 38 | 39 | /** 40 | * A plugin to add document search functionalities. 41 | */ 42 | const retroShellWidgetListener: JupyterFrontEndPlugin = { 43 | id: '@retrolab/documentsearch-extension:retroShellWidgetListener', 44 | requires: [IRetroShell, ISearchProviderRegistry], 45 | autoStart: true, 46 | activate: ( 47 | app: JupyterFrontEnd, 48 | retroShell: IRetroShell, 49 | registry: ISearchProviderRegistry 50 | ) => { 51 | // If a given widget is searchable, apply the searchable class. 52 | // If it's not searchable, remove the class. 53 | const transformWidgetSearchability = (widget: Widget | null) => { 54 | if (!widget) { 55 | return; 56 | } 57 | const providerForWidget = registry.getProviderForWidget(widget); 58 | if (providerForWidget) { 59 | widget.addClass(SEARCHABLE_CLASS); 60 | } 61 | if (!providerForWidget) { 62 | widget.removeClass(SEARCHABLE_CLASS); 63 | } 64 | }; 65 | 66 | // Update searchability of the active widget when the registry 67 | // changes, in case a provider for the current widget was added 68 | // or removed 69 | registry.changed.connect(() => 70 | transformWidgetSearchability(retroShell.currentWidget) 71 | ); 72 | 73 | // Apply the searchable class only to the active widget if it is actually 74 | // searchable. Remove the searchable class from a widget when it's 75 | // no longer active. 76 | retroShell.currentChanged.connect((_, args) => { 77 | if (retroShell.currentWidget) { 78 | transformWidgetSearchability(retroShell.currentWidget); 79 | } 80 | }); 81 | } 82 | }; 83 | 84 | /** 85 | * Export the plugins as default. 86 | */ 87 | const plugins: JupyterFrontEndPlugin[] = [ 88 | disableShortcut, 89 | retroShellWidgetListener 90 | ]; 91 | 92 | export default plugins; 93 | -------------------------------------------------------------------------------- /packages/documentsearch-extension/style/base.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/packages/documentsearch-extension/style/base.css -------------------------------------------------------------------------------- /packages/documentsearch-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/documentsearch-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/documentsearch-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/help-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/help-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Help Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/apputils": "^4.0.0-alpha.5", 44 | "@jupyterlab/mainmenu": "^4.0.0-alpha.5", 45 | "@jupyterlab/translation": "^4.0.0-alpha.4", 46 | "@retrolab/ui-components": "^0.4.0-alpha.1" 47 | }, 48 | "devDependencies": { 49 | "rimraf": "~3.0.0", 50 | "typescript": "~4.1.3" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "jupyterlab": { 56 | "extension": true 57 | }, 58 | "styleModule": "style/index.js" 59 | } 60 | -------------------------------------------------------------------------------- /packages/help-extension/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | JupyterFrontEnd, 6 | JupyterFrontEndPlugin 7 | } from '@jupyterlab/application'; 8 | 9 | import { showDialog, Dialog } from '@jupyterlab/apputils'; 10 | 11 | import { IMainMenu } from '@jupyterlab/mainmenu'; 12 | 13 | import { ITranslator } from '@jupyterlab/translation'; 14 | 15 | import { retroIcon } from '@retrolab/ui-components'; 16 | 17 | import * as React from 'react'; 18 | 19 | /** 20 | * A list of resources to show in the help menu. 21 | */ 22 | const RESOURCES = [ 23 | { 24 | text: 'About Jupyter', 25 | url: 'https://jupyter.org' 26 | }, 27 | { 28 | text: 'Markdown Reference', 29 | url: 'https://commonmark.org/help/' 30 | } 31 | ]; 32 | 33 | /** 34 | * The command IDs used by the help plugin. 35 | */ 36 | namespace CommandIDs { 37 | export const open = 'help:open'; 38 | 39 | export const shortcuts = 'help:shortcuts'; 40 | 41 | export const about = 'help:about'; 42 | } 43 | 44 | /** 45 | * The help plugin. 46 | */ 47 | const plugin: JupyterFrontEndPlugin = { 48 | id: '@retrolab/help-extension:plugin', 49 | autoStart: true, 50 | requires: [ITranslator], 51 | optional: [IMainMenu], 52 | activate: ( 53 | app: JupyterFrontEnd, 54 | translator: ITranslator, 55 | menu: IMainMenu | null 56 | ): void => { 57 | const { commands } = app; 58 | const trans = translator.load('retrolab'); 59 | 60 | commands.addCommand(CommandIDs.open, { 61 | label: args => args['text'] as string, 62 | execute: args => { 63 | const url = args['url'] as string; 64 | window.open(url); 65 | } 66 | }); 67 | 68 | commands.addCommand(CommandIDs.shortcuts, { 69 | label: trans.__('Keyboard Shortcuts'), 70 | execute: () => { 71 | const title = ( 72 | 73 |
74 | {trans.__('Keyboard Shortcuts')} 75 |
76 |
77 | ); 78 | 79 | const body = ( 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {commands.keyBindings 89 | .filter(binding => commands.isEnabled(binding.command)) 90 | .map((binding, i) => ( 91 | 92 | 93 | 96 | 97 | ))} 98 | 99 |
{trans.__('Name')}{trans.__('Shortcut')}
{commands.label(binding.command)} 94 |
{binding.keys.join(', ')}
95 |
100 | ); 101 | 102 | return showDialog({ 103 | title, 104 | body, 105 | buttons: [ 106 | Dialog.createButton({ 107 | label: trans.__('Dismiss'), 108 | className: 109 | 'jp-AboutRetro-about-button jp-mod-reject jp-mod-styled' 110 | }) 111 | ] 112 | }); 113 | } 114 | }); 115 | 116 | commands.addCommand(CommandIDs.about, { 117 | label: trans.__('About %1', app.name), 118 | execute: () => { 119 | const title = ( 120 | <> 121 | 122 | 123 | 124 | 125 | ); 126 | 127 | const retroNotebookURL = 'https://github.com/jupyterlab/retrolab'; 128 | const linkLabel = trans.__('RETROLAB ON GITHUB'); 129 | const externalLinks = ( 130 | 131 | 137 | {linkLabel} 138 | 139 | 140 | ); 141 | const version = trans.__('Version: %1', app.version); 142 | const body = ( 143 | <> 144 | {version} 145 |
{externalLinks}
146 | 147 | ); 148 | 149 | const dialog = new Dialog({ 150 | title, 151 | body, 152 | buttons: [ 153 | Dialog.createButton({ 154 | label: trans.__('Dismiss'), 155 | className: 156 | 'jp-AboutRetro-about-button jp-mod-reject jp-mod-styled' 157 | }) 158 | ] 159 | }); 160 | dialog.addClass('jp-AboutRetro'); 161 | void dialog.launch(); 162 | } 163 | }); 164 | 165 | const resourcesGroup = RESOURCES.map(args => ({ 166 | args, 167 | command: CommandIDs.open 168 | })); 169 | 170 | if (menu) { 171 | menu.helpMenu.addGroup([{ command: CommandIDs.about }]); 172 | menu.helpMenu.addGroup([{ command: CommandIDs.shortcuts }]); 173 | menu.helpMenu.addGroup(resourcesGroup); 174 | } 175 | } 176 | }; 177 | 178 | export default plugin; 179 | -------------------------------------------------------------------------------- /packages/help-extension/style/base.css: -------------------------------------------------------------------------------- 1 | .jp-AboutRetro .jp-Dialog-header { 2 | justify-content: center; 3 | } 4 | 5 | .jp-AboutRetro-header { 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | padding: var(--jp-flat-button-padding); 10 | } 11 | 12 | .jp-AboutRetro-header-text { 13 | margin-left: 16px; 14 | } 15 | 16 | .jp-AboutRetro-body { 17 | display: flex; 18 | font-size: var(--jp-ui-font-size2); 19 | padding: var(--jp-flat-button-padding); 20 | color: var(--jp-ui-font-color1); 21 | text-align: left; 22 | flex-direction: column; 23 | min-width: 360px; 24 | overflow: hidden; 25 | } 26 | 27 | .jp-AboutRetro-about-body pre { 28 | white-space: pre-wrap; 29 | } 30 | 31 | .jp-AboutRetro-about-externalLinks { 32 | display: flex; 33 | flex-direction: column; 34 | justify-content: flex-start; 35 | align-items: flex-start; 36 | padding-top: 12px; 37 | color: var(--jp-warn-color0); 38 | } 39 | 40 | .jp-AboutRetro-shortcuts { 41 | padding: 10px; 42 | } 43 | 44 | .jp-AboutRetro-shortcuts pre { 45 | padding: 5px; 46 | margin: 0 0 10px; 47 | background: var(--jp-layout-color2); 48 | border: 1px solid var(--jp-border-color0); 49 | border-radius: 2px; 50 | word-break: break-all; 51 | } 52 | -------------------------------------------------------------------------------- /packages/help-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/help-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/help-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/lab-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/lab-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Lab Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/index.js" 31 | ], 32 | "scripts": { 33 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 34 | "build:labextension": "jupyter labextension build .", 35 | "build:labextension:dev": "jupyter labextension build --development True .", 36 | "build:lib": "tsc", 37 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 38 | "clean": "jlpm run clean:lib && jlpm run clean:labextension", 39 | "clean:labextension": "rimraf ../../retrolab/labextension", 40 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 41 | "watch": "run-p watch:src watch:labextension", 42 | "watch:labextension": "jupyter labextension watch .", 43 | "watch:src": "tsc -w" 44 | }, 45 | "dependencies": { 46 | "@jupyterlab/application": "^4.0.0-alpha.5", 47 | "@jupyterlab/apputils": "^4.0.0-alpha.5", 48 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 49 | "@jupyterlab/docregistry": "^4.0.0-alpha.5", 50 | "@jupyterlab/mainmenu": "^4.0.0-alpha.5", 51 | "@jupyterlab/notebook": "^4.0.0-alpha.5", 52 | "@jupyterlab/translation": "^4.0.0-alpha.4", 53 | "@lumino/commands": "^1.20.0", 54 | "@lumino/disposable": "^1.10.1", 55 | "@retrolab/application": "^0.4.0-alpha.1" 56 | }, 57 | "devDependencies": { 58 | "@jupyterlab/builder": "^4.0.0-alpha.5", 59 | "rimraf": "~3.0.0", 60 | "typescript": "~4.1.3" 61 | }, 62 | "publishConfig": { 63 | "access": "public" 64 | }, 65 | "jupyterlab": { 66 | "extension": true, 67 | "outputDir": "../../retrolab/labextension", 68 | "schemaDir": "schema" 69 | }, 70 | "styleModule": "style/index.js" 71 | } 72 | -------------------------------------------------------------------------------- /packages/lab-extension/schema/interface-switcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Interface Switcher", 3 | "description": "Interface Switcher settings.", 4 | "jupyter.lab.toolbars": { 5 | "Notebook": [{ "name": "interfaceSwitcher", "rank": 990 }] 6 | }, 7 | "properties": {}, 8 | "additionalProperties": false, 9 | "type": "object" 10 | } 11 | -------------------------------------------------------------------------------- /packages/lab-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | ILabShell, 6 | JupyterFrontEnd, 7 | JupyterFrontEndPlugin 8 | } from '@jupyterlab/application'; 9 | 10 | import { ICommandPalette, IToolbarWidgetRegistry } from '@jupyterlab/apputils'; 11 | 12 | import { PageConfig } from '@jupyterlab/coreutils'; 13 | 14 | import { IMainMenu } from '@jupyterlab/mainmenu'; 15 | 16 | import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; 17 | 18 | import { ITranslator } from '@jupyterlab/translation'; 19 | 20 | import { Menu, MenuBar } from '@lumino/widgets'; 21 | 22 | import { IRetroShell } from '@retrolab/application'; 23 | 24 | /** 25 | * The command IDs used by the application plugin. 26 | */ 27 | namespace CommandIDs { 28 | /** 29 | * Launch RetroLab Tree 30 | */ 31 | export const launchRetroTree = 'retrolab:launch-tree'; 32 | 33 | /** 34 | * Open RetroLab 35 | */ 36 | export const openRetro = 'retrolab:open-retro'; 37 | 38 | /** 39 | * Open in Classic Notebook 40 | */ 41 | export const openClassic = 'retrolab:open-classic'; 42 | 43 | /** 44 | * Open in JupyterLab 45 | */ 46 | export const openLab = 'retrolab:open-lab'; 47 | } 48 | 49 | interface ISwitcherChoice { 50 | command: string; 51 | commandLabel: string; 52 | buttonLabel: string; 53 | urlPrefix: string; 54 | } 55 | 56 | /** 57 | * A plugin to add custom toolbar items to the notebook page 58 | */ 59 | const launchButtons: JupyterFrontEndPlugin = { 60 | id: '@retrolab/lab-extension:interface-switcher', 61 | autoStart: true, 62 | requires: [ITranslator], 63 | optional: [ 64 | INotebookTracker, 65 | ICommandPalette, 66 | IMainMenu, 67 | IRetroShell, 68 | ILabShell, 69 | IToolbarWidgetRegistry 70 | ], 71 | activate: ( 72 | app: JupyterFrontEnd, 73 | translator: ITranslator, 74 | notebookTracker: INotebookTracker | null, 75 | palette: ICommandPalette | null, 76 | menu: IMainMenu | null, 77 | retroShell: IRetroShell | null, 78 | labShell: ILabShell | null, 79 | toolbarRegistry: IToolbarWidgetRegistry | null 80 | ) => { 81 | if (!notebookTracker) { 82 | // to prevent showing the toolbar button in non-notebook pages 83 | return; 84 | } 85 | 86 | const { commands, shell } = app; 87 | const baseUrl = PageConfig.getBaseUrl(); 88 | const trans = translator.load('retrolab'); 89 | const menubar = new MenuBar(); 90 | const switcher = new Menu({ commands }); 91 | switcher.title.label = trans.__('Interface'); 92 | menubar.addMenu(switcher); 93 | 94 | const isEnabled = () => { 95 | return ( 96 | notebookTracker.currentWidget !== null && 97 | notebookTracker.currentWidget === shell.currentWidget 98 | ); 99 | }; 100 | 101 | const addInterface = (option: ISwitcherChoice) => { 102 | const { command, commandLabel, urlPrefix } = option; 103 | commands.addCommand(command, { 104 | label: args => (args.noLabel ? '' : commandLabel), 105 | caption: commandLabel, 106 | execute: () => { 107 | const current = notebookTracker.currentWidget; 108 | if (!current) { 109 | return; 110 | } 111 | window.open(`${urlPrefix}${current.context.path}`); 112 | }, 113 | isEnabled 114 | }); 115 | 116 | if (palette) { 117 | palette.addItem({ command, category: 'Other' }); 118 | } 119 | 120 | if (menu) { 121 | menu.viewMenu.addGroup([{ command }], 1); 122 | } 123 | 124 | switcher.addItem({ command }); 125 | }; 126 | 127 | // always add Classic 128 | addInterface({ 129 | command: 'retrolab:open-classic', 130 | commandLabel: trans.__('Open With %1', 'Classic Notebook'), 131 | buttonLabel: 'openClassic', 132 | urlPrefix: `${baseUrl}tree/` 133 | }); 134 | 135 | if (!retroShell) { 136 | addInterface({ 137 | command: 'retrolab:open-retro', 138 | commandLabel: trans.__('Open With %1', 'RetroLab'), 139 | buttonLabel: 'openRetro', 140 | urlPrefix: `${baseUrl}retro/tree/` 141 | }); 142 | } 143 | 144 | if (!labShell) { 145 | addInterface({ 146 | command: 'retrolab:open-lab', 147 | commandLabel: trans.__('Open With %1', 'JupyterLab'), 148 | buttonLabel: 'openLab', 149 | urlPrefix: `${baseUrl}doc/tree/` 150 | }); 151 | } 152 | 153 | if (toolbarRegistry) { 154 | toolbarRegistry.registerFactory( 155 | 'Notebook', 156 | 'interfaceSwitcher', 157 | panel => { 158 | const menubar = new MenuBar(); 159 | menubar.addMenu(switcher); 160 | menubar.addClass('jp-InterfaceSwitcher'); 161 | return menubar; 162 | } 163 | ); 164 | } 165 | } 166 | }; 167 | 168 | /** 169 | * A plugin to add a command to open the RetroLab Tree. 170 | */ 171 | const launchRetroTree: JupyterFrontEndPlugin = { 172 | id: '@retrolab/lab-extension:launch-retrotree', 173 | autoStart: true, 174 | requires: [ITranslator], 175 | optional: [IMainMenu, ICommandPalette], 176 | activate: ( 177 | app: JupyterFrontEnd, 178 | translator: ITranslator, 179 | menu: IMainMenu | null, 180 | palette: ICommandPalette | null 181 | ): void => { 182 | const { commands } = app; 183 | const trans = translator.load('retrolab'); 184 | const category = trans.__('Help'); 185 | 186 | commands.addCommand(CommandIDs.launchRetroTree, { 187 | label: trans.__('Launch RetroLab File Browser'), 188 | execute: () => { 189 | window.open(PageConfig.getBaseUrl() + 'retro/tree'); 190 | } 191 | }); 192 | 193 | if (menu) { 194 | const helpMenu = menu.helpMenu; 195 | helpMenu.addGroup([{ command: CommandIDs.launchRetroTree }], 1); 196 | } 197 | 198 | if (palette) { 199 | palette.addItem({ command: CommandIDs.launchRetroTree, category }); 200 | } 201 | } 202 | }; 203 | 204 | /** 205 | * Export the plugins as default. 206 | */ 207 | const plugins: JupyterFrontEndPlugin[] = [launchRetroTree, launchButtons]; 208 | 209 | export default plugins; 210 | -------------------------------------------------------------------------------- /packages/lab-extension/style/base.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | .jp-InterfaceSwitcher { 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | } 12 | -------------------------------------------------------------------------------- /packages/lab-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/lab-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/lab-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../application" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/notebook-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/notebook-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Notebook Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/apputils": "^4.0.0-alpha.5", 44 | "@jupyterlab/cells": "^4.0.0-alpha.5", 45 | "@jupyterlab/docmanager": "^4.0.0-alpha.5", 46 | "@jupyterlab/notebook": "^4.0.0-alpha.5", 47 | "@jupyterlab/settingregistry": "^4.0.0-alpha.5", 48 | "@jupyterlab/translation": "^4.0.0-alpha.4", 49 | "@lumino/polling": "^1.10.0", 50 | "@lumino/widgets": "^1.31.1", 51 | "@retrolab/application": "^0.4.0-alpha.1" 52 | }, 53 | "devDependencies": { 54 | "rimraf": "~3.0.0", 55 | "typescript": "~4.1.3" 56 | }, 57 | "publishConfig": { 58 | "access": "public" 59 | }, 60 | "jupyterlab": { 61 | "extension": true, 62 | "schemaDir": "schema" 63 | }, 64 | "styleModule": "style/index.js" 65 | } 66 | -------------------------------------------------------------------------------- /packages/notebook-extension/schema/scroll-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon": "retro-ui-components:retroSun", 3 | "jupyter.lab.setting-icon-label": "RetroLab Notebook", 4 | "title": "RetroLab Notebook", 5 | "description": "RetroLab Notebook settings", 6 | "properties": { 7 | "autoScrollOutputs": { 8 | "type": "boolean", 9 | "title": "Auto Scroll Outputs", 10 | "description": "Whether to auto scroll the output area when the outputs become too long", 11 | "default": true 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | } 17 | -------------------------------------------------------------------------------- /packages/notebook-extension/style/base.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | 4 | | Distributed under the terms of the Modified BSD License. 5 | |----------------------------------------------------------------------------*/ 6 | 7 | @import './variables.css'; 8 | 9 | /* Document oriented look for the notebook (scrollbar to the right of the page) */ 10 | 11 | body[data-retro='notebooks'] .jp-NotebookPanel-toolbar { 12 | padding-left: calc(calc(100% - var(--jp-notebook-max-width)) * 0.5); 13 | padding-right: calc(calc(100% - var(--jp-notebook-max-width)) * 0.5); 14 | } 15 | 16 | body[data-retro='notebooks'] .jp-Notebook > * { 17 | background: var(--jp-layout-color0); 18 | padding: var(--jp-notebook-padding); 19 | } 20 | 21 | body[data-retro='notebooks'] 22 | .jp-Notebook.jp-mod-commandMode 23 | .jp-Cell.jp-mod-active.jp-mod-selected:not(.jp-mod-multiSelected) { 24 | background: var(--jp-layout-color0) !important; 25 | } 26 | 27 | body[data-retro='notebooks'] .jp-Notebook > *:first-child { 28 | padding-top: var(--jp-notebook-padding-offset); 29 | margin-top: var(--jp-notebook-toolbar-margin-bottom); 30 | } 31 | 32 | body[data-retro='notebooks'] .jp-Notebook { 33 | padding-top: unset; 34 | padding-bottom: unset; 35 | padding-left: calc(calc(100% - var(--jp-notebook-max-width)) * 0.5); 36 | padding-right: calc( 37 | calc( 38 | 100% - var(--jp-notebook-max-width) - var(--jp-notebook-padding-offset) 39 | ) * 0.5 40 | ); 41 | background: var(--jp-layout-color2); 42 | } 43 | 44 | body[data-retro='notebooks'] .jp-Notebook.jp-mod-scrollPastEnd::after { 45 | background: var(--jp-layout-color0); 46 | } 47 | 48 | /* ---- */ 49 | 50 | .jp-RetroKernelLogo { 51 | flex: 0 0 auto; 52 | display: flex; 53 | align-items: center; 54 | text-align: center; 55 | margin-right: 8px; 56 | } 57 | 58 | .jp-RetroKernelLogo img { 59 | max-width: 28px; 60 | max-height: 28px; 61 | display: flex; 62 | } 63 | 64 | .jp-RetroKernelStatus { 65 | margin: 0; 66 | font-weight: normal; 67 | font-size: var(--jp-ui-font-size1); 68 | color: var(--jp-ui-font-color0); 69 | font-family: var(--jp-ui-font-family); 70 | line-height: var(--jp-private-title-panel-height); 71 | padding-left: var(--jp-kernel-status-padding); 72 | padding-right: var(--jp-kernel-status-padding); 73 | } 74 | 75 | .jp-RetroKernelStatus-error { 76 | background-color: var(--jp-error-color0); 77 | } 78 | 79 | .jp-RetroKernelStatus-warn { 80 | background-color: var(--jp-warn-color0); 81 | } 82 | 83 | .jp-RetroKernelStatus-info { 84 | background-color: var(--jp-info-color0); 85 | } 86 | 87 | .jp-RetroKernelStatus-fade { 88 | animation: 0.5s fade-out forwards; 89 | } 90 | 91 | @keyframes fade-out { 92 | 0% { 93 | opacity: 1; 94 | } 95 | 100% { 96 | opacity: 0; 97 | } 98 | } 99 | 100 | #jp-title h1 { 101 | cursor: pointer; 102 | font-size: 18px; 103 | margin: 0; 104 | font-weight: normal; 105 | color: var(--jp-ui-font-color0); 106 | font-family: var(--jp-ui-font-family); 107 | line-height: calc(1.5 * var(--jp-private-title-panel-height)); 108 | text-overflow: ellipsis; 109 | overflow: hidden; 110 | white-space: nowrap; 111 | } 112 | 113 | #jp-title h1:hover { 114 | background: var(--jp-layout-color2); 115 | } 116 | 117 | .jp-RetroCheckpoint { 118 | font-size: 14px; 119 | margin-left: 5px; 120 | margin-right: 5px; 121 | font-weight: normal; 122 | color: var(--jp-ui-font-color0); 123 | font-family: var(--jp-ui-font-family); 124 | line-height: calc(1.5 * var(--jp-private-title-panel-height)); 125 | text-overflow: ellipsis; 126 | overflow: hidden; 127 | white-space: nowrap; 128 | } 129 | 130 | /* Mobile View */ 131 | 132 | body[data-format='mobile'] .jp-RetroCheckpoint { 133 | display: none; 134 | } 135 | 136 | body[data-format='mobile'] .jp-Notebook > *:first-child { 137 | margin-top: 0; 138 | } 139 | -------------------------------------------------------------------------------- /packages/notebook-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/notebook-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/notebook-extension/style/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jp-notebook-toolbar-margin-bottom: 20px; 3 | --jp-notebook-padding-offset: 20px; 4 | 5 | --jp-kernel-status-padding: 5px; 6 | } 7 | -------------------------------------------------------------------------------- /packages/notebook-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/terminal-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/terminal-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Terminal Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 44 | "@jupyterlab/terminal": "^4.0.0-alpha.5", 45 | "@lumino/algorithm": "^1.9.1" 46 | }, 47 | "devDependencies": { 48 | "rimraf": "~3.0.0", 49 | "typescript": "~4.1.3" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | }, 54 | "jupyterlab": { 55 | "extension": true 56 | }, 57 | "styleModule": "style/index.js" 58 | } 59 | -------------------------------------------------------------------------------- /packages/terminal-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | IRouter, 6 | JupyterFrontEnd, 7 | JupyterFrontEndPlugin 8 | } from '@jupyterlab/application'; 9 | 10 | import { PageConfig } from '@jupyterlab/coreutils'; 11 | 12 | import { ITerminalTracker } from '@jupyterlab/terminal'; 13 | 14 | import { find } from '@lumino/algorithm'; 15 | 16 | /** 17 | * A plugin to open terminals in a new tab 18 | */ 19 | const opener: JupyterFrontEndPlugin = { 20 | id: '@retrolab/terminal-extension:opener', 21 | requires: [IRouter, ITerminalTracker], 22 | autoStart: true, 23 | activate: ( 24 | app: JupyterFrontEnd, 25 | router: IRouter, 26 | tracker: ITerminalTracker 27 | ) => { 28 | const { commands } = app; 29 | const terminalPattern = new RegExp('/terminals/(.*)'); 30 | 31 | const command = 'router:terminal'; 32 | commands.addCommand(command, { 33 | execute: (args: any) => { 34 | const parsed = args as IRouter.ILocation; 35 | const matches = parsed.path.match(terminalPattern); 36 | if (!matches) { 37 | return; 38 | } 39 | const [, name] = matches; 40 | if (!name) { 41 | return; 42 | } 43 | 44 | tracker.widgetAdded.connect((send, terminal) => { 45 | terminal.content.setOption('closeOnExit', false); 46 | }); 47 | commands.execute('terminal:open', { name }); 48 | } 49 | }); 50 | 51 | router.register({ command, pattern: terminalPattern }); 52 | } 53 | }; 54 | 55 | /** 56 | * Open terminals in a new tab. 57 | */ 58 | const redirect: JupyterFrontEndPlugin = { 59 | id: '@retrolab/terminal-extension:redirect', 60 | requires: [ITerminalTracker], 61 | autoStart: true, 62 | activate: (app: JupyterFrontEnd, tracker: ITerminalTracker) => { 63 | const baseUrl = PageConfig.getBaseUrl(); 64 | tracker.widgetAdded.connect((send, terminal) => { 65 | const widget = find(app.shell.widgets('main'), w => w.id === terminal.id); 66 | if (widget) { 67 | // bail if the terminal is already added to the main area 68 | return; 69 | } 70 | const name = terminal.content.session.name; 71 | window.open(`${baseUrl}retro/terminals/${name}`, '_blank'); 72 | 73 | // dispose the widget since it is not used on this page 74 | terminal.dispose(); 75 | }); 76 | } 77 | }; 78 | 79 | /** 80 | * Export the plugins as default. 81 | */ 82 | const plugins: JupyterFrontEndPlugin[] = [opener, redirect]; 83 | 84 | export default plugins; 85 | -------------------------------------------------------------------------------- /packages/terminal-extension/style/base.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/packages/terminal-extension/style/base.css -------------------------------------------------------------------------------- /packages/terminal-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | -------------------------------------------------------------------------------- /packages/terminal-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/terminal-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/tree-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/tree-extension", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - Tree Extension", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*.css", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/*.d.ts", 27 | "lib/*.js.map", 28 | "lib/*.js", 29 | "schema/*.json", 30 | "style/**/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "tsc -b", 35 | "build:prod": "tsc -b", 36 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 37 | "docs": "typedoc src", 38 | "prepublishOnly": "npm run build", 39 | "watch": "tsc -b --watch" 40 | }, 41 | "dependencies": { 42 | "@jupyterlab/application": "^4.0.0-alpha.5", 43 | "@jupyterlab/apputils": "^4.0.0-alpha.5", 44 | "@jupyterlab/coreutils": "^6.0.0-alpha.5", 45 | "@jupyterlab/docmanager": "^4.0.0-alpha.5", 46 | "@jupyterlab/filebrowser": "^4.0.0-alpha.5", 47 | "@jupyterlab/mainmenu": "^4.0.0-alpha.5", 48 | "@jupyterlab/services": "^7.0.0-alpha.5", 49 | "@jupyterlab/settingregistry": "^4.0.0-alpha.5", 50 | "@jupyterlab/statedb": "^4.0.0-alpha.5", 51 | "@jupyterlab/translation": "^4.0.0-alpha.4", 52 | "@jupyterlab/ui-components": "^4.0.0-alpha.19", 53 | "@lumino/algorithm": "^1.9.1", 54 | "@lumino/commands": "^1.20.0", 55 | "@lumino/widgets": "^1.31.1", 56 | "@retrolab/application": "^0.4.0-alpha.1" 57 | }, 58 | "devDependencies": { 59 | "rimraf": "~3.0.0", 60 | "typescript": "~4.1.3" 61 | }, 62 | "publishConfig": { 63 | "access": "public" 64 | }, 65 | "jupyterlab": { 66 | "extension": true 67 | }, 68 | "styleModule": "style/index.js" 69 | } 70 | -------------------------------------------------------------------------------- /packages/tree-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { 5 | JupyterFrontEnd, 6 | JupyterFrontEndPlugin 7 | } from '@jupyterlab/application'; 8 | import { CommandToolbarButton } from '@jupyterlab/apputils'; 9 | 10 | import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; 11 | 12 | import { IRunningSessionManagers, RunningSessions } from '@jupyterlab/running'; 13 | 14 | import { ITranslator } from '@jupyterlab/translation'; 15 | 16 | import { 17 | consoleIcon, 18 | folderIcon, 19 | notebookIcon, 20 | runningIcon, 21 | terminalIcon 22 | } from '@jupyterlab/ui-components'; 23 | 24 | import { TabPanel } from '@lumino/widgets'; 25 | 26 | /** 27 | * Plugin to add extra buttons to the file browser to create new notebooks and files 28 | */ 29 | const newFiles: JupyterFrontEndPlugin = { 30 | id: '@retrolab/tree-extension:buttons', 31 | requires: [IFileBrowserFactory, ITranslator], 32 | autoStart: true, 33 | activate: ( 34 | app: JupyterFrontEnd, 35 | filebrowser: IFileBrowserFactory, 36 | translator: ITranslator 37 | ) => { 38 | const { commands } = app; 39 | const browser = filebrowser.defaultBrowser; 40 | const trans = translator.load('retrolab'); 41 | 42 | // wrapper commands to be able to override the label 43 | const newNotebookCommand = 'tree:new-notebook'; 44 | commands.addCommand(newNotebookCommand, { 45 | label: trans.__('New Notebook'), 46 | icon: notebookIcon, 47 | execute: () => { 48 | return commands.execute('notebook:create-new'); 49 | } 50 | }); 51 | 52 | const newNotebook = new CommandToolbarButton({ 53 | commands, 54 | id: newNotebookCommand 55 | }); 56 | 57 | const newFile = new CommandToolbarButton({ 58 | commands, 59 | id: 'filebrowser:create-new-file' 60 | }); 61 | 62 | browser.toolbar.insertItem(0, 'new-notebook', newNotebook); 63 | browser.toolbar.insertItem(1, 'new-file', newFile); 64 | } 65 | }; 66 | 67 | /** 68 | * Plugin to add a "New Console" button to the file browser toolbar. 69 | */ 70 | const newConsole: JupyterFrontEndPlugin = { 71 | id: '@retrolab/tree-extension:new-console', 72 | requires: [IFileBrowserFactory, ITranslator], 73 | autoStart: true, 74 | activate: ( 75 | app: JupyterFrontEnd, 76 | filebrowser: IFileBrowserFactory, 77 | translator: ITranslator 78 | ) => { 79 | const { commands } = app; 80 | const browser = filebrowser.defaultBrowser; 81 | const trans = translator.load('retrolab'); 82 | 83 | const newConsoleCommand = 'tree:new-console'; 84 | commands.addCommand(newConsoleCommand, { 85 | label: trans.__('New Console'), 86 | icon: consoleIcon, 87 | execute: () => { 88 | return commands.execute('console:create'); 89 | } 90 | }); 91 | 92 | const newConsole = new CommandToolbarButton({ 93 | commands, 94 | id: newConsoleCommand 95 | }); 96 | 97 | browser.toolbar.insertItem(2, 'new-console', newConsole); 98 | } 99 | }; 100 | 101 | /** 102 | * Plugin to add a "New Terminal" button to the file browser toolbar. 103 | */ 104 | const newTerminal: JupyterFrontEndPlugin = { 105 | id: '@retrolab/tree-extension:new-terminal', 106 | requires: [IFileBrowserFactory, ITranslator], 107 | autoStart: true, 108 | activate: ( 109 | app: JupyterFrontEnd, 110 | filebrowser: IFileBrowserFactory, 111 | translator: ITranslator 112 | ) => { 113 | const { commands } = app; 114 | const browser = filebrowser.defaultBrowser; 115 | const trans = translator.load('retrolab'); 116 | 117 | const newTerminalCommand = 'tree:new-terminal'; 118 | commands.addCommand(newTerminalCommand, { 119 | label: trans.__('New Terminal'), 120 | icon: terminalIcon, 121 | execute: () => { 122 | return commands.execute('terminal:create-new'); 123 | } 124 | }); 125 | 126 | const newTerminal = new CommandToolbarButton({ 127 | commands, 128 | id: newTerminalCommand 129 | }); 130 | 131 | browser.toolbar.insertItem(3, 'new-terminal', newTerminal); 132 | } 133 | }; 134 | 135 | /** 136 | * A plugin to add the file browser widget to an ILabShell 137 | */ 138 | const browserWidget: JupyterFrontEndPlugin = { 139 | id: '@retrolab/tree-extension:widget', 140 | requires: [IFileBrowserFactory, ITranslator], 141 | optional: [IRunningSessionManagers], 142 | autoStart: true, 143 | activate: ( 144 | app: JupyterFrontEnd, 145 | factory: IFileBrowserFactory, 146 | translator: ITranslator, 147 | manager: IRunningSessionManagers | null 148 | ): void => { 149 | const tabPanel = new TabPanel({ tabPlacement: 'top', tabsMovable: true }); 150 | tabPanel.addClass('jp-TreePanel'); 151 | 152 | const trans = translator.load('retrolab'); 153 | 154 | const { defaultBrowser: browser } = factory; 155 | browser.title.label = trans.__('Files'); 156 | browser.node.setAttribute('role', 'region'); 157 | browser.node.setAttribute('aria-label', trans.__('File Browser Section')); 158 | browser.title.icon = folderIcon; 159 | 160 | tabPanel.addWidget(browser); 161 | tabPanel.tabBar.addTab(browser.title); 162 | 163 | if (manager) { 164 | const running = new RunningSessions(manager, translator); 165 | running.id = 'jp-running-sessions'; 166 | running.title.label = trans.__('Running'); 167 | running.title.icon = runningIcon; 168 | tabPanel.addWidget(running); 169 | tabPanel.tabBar.addTab(running.title); 170 | } 171 | 172 | app.shell.add(tabPanel, 'main', { rank: 100 }); 173 | } 174 | }; 175 | 176 | /** 177 | * Export the plugins as default. 178 | */ 179 | const plugins: JupyterFrontEndPlugin[] = [ 180 | newFiles, 181 | newConsole, 182 | newTerminal, 183 | browserWidget 184 | ]; 185 | export default plugins; 186 | -------------------------------------------------------------------------------- /packages/tree-extension/style/base.css: -------------------------------------------------------------------------------- 1 | .jp-FileBrowser { 2 | height: 100%; 3 | } 4 | 5 | .lm-TabPanel { 6 | height: 100%; 7 | } 8 | 9 | .jp-TreePanel .lm-TabPanel-tabBar { 10 | overflow: visible; 11 | min-height: 32px; 12 | border-bottom: unset; 13 | height: var(--jp-private-toolbar-height); 14 | } 15 | 16 | .jp-TreePanel .lm-TabBar-content { 17 | height: 100%; 18 | } 19 | 20 | .jp-TreePanel .lm-TabBar-tab { 21 | color: var(--jp-ui-font-color0); 22 | font-size: var(--jp-ui-font-size1); 23 | padding-top: 6px; 24 | height: 100%; 25 | } 26 | 27 | .jp-TreePanel .lm-TabBar-tabLabel { 28 | padding-left: 5px; 29 | padding-right: 5px; 30 | } 31 | 32 | /* Override the style from upstream JupyterLab */ 33 | .jp-FileBrowser-toolbar.jp-Toolbar 34 | .jp-Toolbar-item:first-child 35 | .jp-ToolbarButtonComponent { 36 | width: auto; 37 | background: unset; 38 | padding-left: 5px; 39 | padding-right: 5px; 40 | } 41 | 42 | .jp-FileBrowser-toolbar.jp-Toolbar 43 | .jp-Toolbar-item:first-child 44 | .jp-ToolbarButtonComponent:hover { 45 | background-color: var(--jp-layout-color2); 46 | } 47 | 48 | .jp-FileBrowser-toolbar.jp-Toolbar .jp-ToolbarButtonComponent { 49 | width: unset; 50 | } 51 | -------------------------------------------------------------------------------- /packages/tree-extension/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('~@jupyterlab/filebrowser/style/index.css'); 2 | 3 | @import url('./base.css'); 4 | -------------------------------------------------------------------------------- /packages/tree-extension/style/index.js: -------------------------------------------------------------------------------- 1 | import '@jupyterlab/filebrowser/style/index.js'; 2 | 3 | import './base.css'; 4 | -------------------------------------------------------------------------------- /packages/tree-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../application" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ui-components/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /packages/ui-components/jest.config.js: -------------------------------------------------------------------------------- 1 | const func = require('@jupyterlab/testutils/lib/jest-config'); 2 | module.exports = func(__dirname); 3 | -------------------------------------------------------------------------------- /packages/ui-components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/ui-components", 3 | "version": "0.4.0-alpha.1", 4 | "description": "RetroLab - UI components", 5 | "homepage": "https://github.com/jupyterlab/retrolab", 6 | "bugs": { 7 | "url": "https://github.com/jupyterlab/retrolab/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jupyterlab/retrolab.git" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Project Jupyter", 15 | "sideEffects": [ 16 | "style/**/*", 17 | "style/index.js" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "style": "style/index.css", 22 | "directories": { 23 | "lib": "lib/" 24 | }, 25 | "files": [ 26 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 27 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 28 | "style/index.js" 29 | ], 30 | "scripts": { 31 | "build": "tsc -b", 32 | "build:prod": "tsc -b", 33 | "build:test": "tsc --build tsconfig.test.json", 34 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 35 | "cleansvg": "svgo --config svgo.yaml", 36 | "docs": "typedoc src", 37 | "docs:init": "bash docs/build.sh", 38 | "prepublishOnly": "npm run build", 39 | "test": "jest", 40 | "test:cov": "jest --collect-coverage", 41 | "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", 42 | "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", 43 | "watch": "tsc -b --watch" 44 | }, 45 | "dependencies": { 46 | "@jupyterlab/ui-components": "^4.0.0-alpha.19", 47 | "react": "^17.0.1", 48 | "react-dom": "^17.0.1" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.10.2", 52 | "@babel/preset-env": "^7.10.2", 53 | "@jupyterlab/testutils": "^4.0.0-alpha.5", 54 | "@types/jest": "^26.0.10", 55 | "babel-loader": "^8.0.6", 56 | "jest": "^26.4.2", 57 | "rimraf": "~3.0.0", 58 | "ts-jest": "^26.3.0", 59 | "typescript": "~4.1.3" 60 | }, 61 | "publishConfig": { 62 | "access": "public" 63 | }, 64 | "jupyterlab": { 65 | "coreDependency": true 66 | }, 67 | "styleModule": "style/index.js" 68 | } 69 | -------------------------------------------------------------------------------- /packages/ui-components/src/icon/iconimports.ts: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | import { LabIcon } from '@jupyterlab/ui-components'; 7 | 8 | import jupyterSvgstr from '../../style/icons/jupyter.svg'; 9 | 10 | import retroSvgstr from '../../style/icons/retrolab.svg'; 11 | 12 | import retroInlineSvgstr from '../../style/icons/retrolabInline.svg'; 13 | 14 | import retroSunSvgstr from '../../style/icons/retrolabSun.svg'; 15 | 16 | export const jupyterIcon = new LabIcon({ 17 | name: 'retro-ui-components:jupyter', 18 | svgstr: jupyterSvgstr 19 | }); 20 | 21 | export const retroIcon = new LabIcon({ 22 | name: 'retro-ui-components:retrolab', 23 | svgstr: retroSvgstr 24 | }); 25 | 26 | export const retroInlineIcon = new LabIcon({ 27 | name: 'retro-ui-components:retrolabInline', 28 | svgstr: retroInlineSvgstr 29 | }); 30 | 31 | export const retroSunIcon = new LabIcon({ 32 | name: 'retro-ui-components:retroSun', 33 | svgstr: retroSunSvgstr 34 | }); 35 | -------------------------------------------------------------------------------- /packages/ui-components/src/icon/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './iconimports'; 5 | -------------------------------------------------------------------------------- /packages/ui-components/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './icon'; 5 | -------------------------------------------------------------------------------- /packages/ui-components/src/svg.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | declare module '*.svg' { 5 | const value: string; 6 | export default value; 7 | } 8 | -------------------------------------------------------------------------------- /packages/ui-components/style/base.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/packages/ui-components/style/base.css -------------------------------------------------------------------------------- /packages/ui-components/style/icons/jupyter.svg: -------------------------------------------------------------------------------- 1 | 2 | Jupyter 3 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /packages/ui-components/style/icons/retrolab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/ui-components/style/icons/retrolabInline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/ui-components/style/icons/retrolabSun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/ui-components/style/index.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | @import url('./base.css'); 7 | -------------------------------------------------------------------------------- /packages/ui-components/style/index.js: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) Jupyter Development Team. 3 | | Distributed under the terms of the Modified BSD License. 4 | |----------------------------------------------------------------------------*/ 5 | 6 | import './base.css'; 7 | -------------------------------------------------------------------------------- /packages/ui-components/test/foo.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | describe('foo', () => { 5 | describe('bar', () => { 6 | it('should pass', () => { 7 | expect(true).toBe(true); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/ui-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/ui-components/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase.test", 3 | "include": ["src/**/*", "test/**/*"], 4 | "references": [ 5 | { 6 | "path": "." 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.10", "jupyterlab>=4.0.0a20,<5"] 3 | build-backend = "jupyter_packaging.build_api" 4 | 5 | [license] 6 | file="LICENSE" 7 | 8 | [tool.jupyter-packaging.options] 9 | skip-if-exists = ["retrolab/labextension/static/style.js", "retrolab/static/bundle.js"] 10 | ensured-targets = ["retrolab/labextension/static/style.js", "retrolab/static/bundle.js"] 11 | 12 | [tool.jupyter-packaging.builder] 13 | factory = "jupyter_packaging.npm_builder" 14 | 15 | [tool.jupyter-packaging.build-args] 16 | build_cmd = "build:prod" 17 | npm = ["jlpm"] 18 | 19 | [tool.check-manifest] 20 | ignore = ["app/**", "binder/**", "buildutils/**", "packages/**", "*.json", "yarn.lock", "readthedocs.yml", ".bumpversion.cfg", ".*", "lint-staged.config.js", "logo.*", "retrolab/labextension/**", "retrolab/schemas/**", "retrolab/static/**", "retrolab/template/**", "ui-tests/**"] 21 | -------------------------------------------------------------------------------- /retrolab/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | from .serverextension import load_jupyter_server_extension 3 | 4 | 5 | def _jupyter_server_extension_paths(): 6 | return [ 7 | { 8 | 'module': 'retrolab' 9 | } 10 | ] 11 | 12 | 13 | def _jupyter_server_extension_points(): 14 | from .app import RetroApp 15 | return [{"module": "retrolab", "app": RetroApp}] 16 | 17 | 18 | def _jupyter_labextension_paths(): 19 | return [{ 20 | 'src': 'labextension', 21 | 'dest': '@retrolab/lab-extension' 22 | }] -------------------------------------------------------------------------------- /retrolab/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from retrolab.app import main 4 | 5 | sys.exit(main()) 6 | -------------------------------------------------------------------------------- /retrolab/_version.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Jupyter Development Team. 2 | # Distributed under the terms of the Modified BSD License. 3 | 4 | from collections import namedtuple 5 | 6 | VersionInfo = namedtuple('VersionInfo', [ 7 | 'major', 8 | 'minor', 9 | 'micro', 10 | 'releaselevel', 11 | 'serial' 12 | ]) 13 | 14 | # DO NOT EDIT THIS DIRECTLY! It is managed by bumpversion 15 | version_info = VersionInfo(0, 4, 0, 'alpha', 1) 16 | 17 | _specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''} 18 | 19 | __version__ = '{}.{}.{}{}'.format( 20 | version_info.major, 21 | version_info.minor, 22 | version_info.micro, 23 | ('' 24 | if version_info.releaselevel == 'final' 25 | else _specifier_[version_info.releaselevel] + str(version_info.serial))) 26 | -------------------------------------------------------------------------------- /retrolab/serverextension.py: -------------------------------------------------------------------------------- 1 | def load_jupyter_server_extension(serverapp): 2 | from .app import RetroApp 3 | extension = RetroApp() 4 | extension.serverapp = serverapp 5 | extension.load_config_file() 6 | extension.update_config(serverapp.config) 7 | extension.parse_command_line(serverapp.extra_args) 8 | extension.initialize() 9 | -------------------------------------------------------------------------------- /retrolab/static/favicons/favicon-console.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/retrolab/static/favicons/favicon-console.ico -------------------------------------------------------------------------------- /retrolab/templates/consoles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{page_config['appName'] | e}} - Console 7 | {% block favicon %} 8 | 9 | {% endblock %} 10 | 11 | 12 | 13 | {# Copy so we do not modify the page_config with updates. #} 14 | {% set page_config_full = page_config.copy() %} 15 | 16 | {# Set a dummy variable - we just want the side effect of the update. #} 17 | {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} 18 | 19 | {# Sentinel value to say that we are on the tree page #} 20 | {% set _ = page_config_full.update(retroPage='consoles') %} 21 | 22 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /retrolab/templates/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{page_config['appName'] | e}} - Edit 7 | {% block favicon %} 8 | 9 | {% endblock %} 10 | 11 | 12 | 13 | {# Copy so we do not modify the page_config with updates. #} 14 | {% set page_config_full = page_config.copy() %} 15 | 16 | {# Set a dummy variable - we just want the side effect of the update. #} 17 | {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} 18 | 19 | {# Sentinel value to say that we are on the tree page #} 20 | {% set _ = page_config_full.update(retroPage='edit') %} 21 | 22 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /retrolab/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{{page_title | e}}{% endblock %} 12 | 13 | {% block favicon %}{% endblock %} 14 | 15 | 16 | 17 | 18 | 19 | {% block stylesheet %} 20 | 26 | {% endblock %} 27 | {% block site %} 28 | 29 |
30 | {% block h1_error %} 31 |

{{status_code | e}} : {{status_message | e}}

32 | {% endblock h1_error %} 33 | {% block error_detail %} 34 | {% if message %} 35 |

The error was:

36 |
37 |
{{message | e}}
38 |
39 | {% endif %} 40 | {% endblock %} 41 | 42 | 43 | {% endblock %} 44 | 45 | {% block script %} 46 | 55 | {% endblock script %} 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /retrolab/templates/notebooks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{page_config['appName'] | e}} - Notebook 7 | {% block favicon %} 8 | 9 | {% endblock %} 10 | 11 | 12 | 13 | {# Copy so we do not modify the page_config with updates. #} 14 | {% set page_config_full = page_config.copy() %} 15 | 16 | {# Set a dummy variable - we just want the side effect of the update. #} 17 | {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} 18 | 19 | {# Sentinel value to say that we are on the tree page #} 20 | {% set _ = page_config_full.update(retroPage='notebooks') %} 21 | 22 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /retrolab/templates/terminals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{page_config['appName'] | e}} - Terminal 7 | {% block favicon %} 8 | 9 | {% endblock %} 10 | 11 | 12 | 13 | {# Copy so we do not modify the page_config with updates. #} 14 | {% set page_config_full = page_config.copy() %} 15 | 16 | {# Set a dummy variable - we just want the side effect of the update. #} 17 | {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} 18 | 19 | {# Sentinel value to say that we are on the tree page #} 20 | {% set _ = page_config_full.update(retroPage='terminals') %} 21 | 22 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /retrolab/templates/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{page_config['appName'] | e}} - Tree 7 | 8 | 9 | 10 | {# Copy so we do not modify the page_config with updates. #} 11 | {% set page_config_full = page_config.copy() %} 12 | 13 | {# Set a dummy variable - we just want the side effect of the update. #} 14 | {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} 15 | 16 | {# Sentinel value to say that we are on the tree page #} 17 | {% set _ = page_config_full.update(retroPage='tree') %} 18 | 19 | 22 | 23 | 24 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = retrolab 3 | version = attr: retrolab._version.__version__ 4 | description = JupyterLab Distribution with a retro look and feel 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | license_file = LICENSE 8 | author = Jupyter Development Team 9 | author_email = jupyter@googlegroups.com 10 | url = https://github.com/jupyterlab/retrolab 11 | platforms = Linux, Mac OS X, Windows 12 | keywords = Jupyter, JupyterLab, Notebook 13 | classifiers = 14 | Intended Audience :: Developers 15 | Intended Audience :: System Administrators 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: BSD License 18 | Programming Language :: Python 19 | Programming Language :: Python :: 3.7 20 | Programming Language :: Python :: 3.8 21 | Programming Language :: Python :: 3.9 22 | Programming Language :: Python :: 3.10 23 | Framework :: Jupyter 24 | 25 | [options] 26 | zip_safe = False 27 | include_package_data = True 28 | packages = find: 29 | python_requires = >=3.7 30 | install_requires = 31 | jupyterlab>=4.0.0a20,<5 32 | jupyterlab_server~=2.3 33 | jupyter_server~=1.4 34 | nbclassic~=0.2 35 | tornado>=6.1.0 36 | 37 | [options.entry_points] 38 | console_scripts = 39 | jupyter-retro = retrolab.app:main 40 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Jupyter Development Team. 2 | # Distributed under the terms of the Modified BSD License. 3 | 4 | from pathlib import Path 5 | 6 | import setuptools 7 | 8 | HERE = Path(__file__).parent.resolve() 9 | 10 | # The name of the project 11 | NAME = "retrolab" 12 | 13 | labext_name = "@retrolab/lab-extension" 14 | lab_extension_dest = HERE / NAME / "labextension" 15 | main_bundle_dest = HERE / NAME / "static" 16 | 17 | # Representative files that should exist after a successful build 18 | ensured_targets = [ 19 | str(lab_extension_dest / "static" / "style.js"), 20 | str(main_bundle_dest / "bundle.js"), 21 | str(HERE / NAME / "schemas/@retrolab/application-extension/package.json.orig"), 22 | ] 23 | 24 | data_files_spec = [ 25 | ("share/jupyter/labextensions/%s" % labext_name, str(lab_extension_dest), "**"), 26 | ("share/jupyter/labextensions/%s" % labext_name, str(HERE), "install.json"), 27 | ("share/jupyter/lab/schemas", f"{NAME}/schemas", "@retrolab/**/*"), 28 | ( 29 | "etc/jupyter/jupyter_server_config.d", 30 | "jupyter-config/jupyter_server_config.d", 31 | "retrolab.json", 32 | ), 33 | ( 34 | "etc/jupyter/jupyter_notebook_config.d", 35 | "jupyter-config/jupyter_notebook_config.d", 36 | "retrolab.json", 37 | ), 38 | ] 39 | 40 | try: 41 | from jupyter_packaging import wrap_installers, npm_builder, get_data_files 42 | 43 | # In develop mode, just run yarn 44 | builder = npm_builder(build_cmd="build", npm="jlpm", force=True) 45 | cmdclass = wrap_installers(post_develop=builder, ensured_targets=ensured_targets) 46 | 47 | setup_args = dict(cmdclass=cmdclass, data_files=get_data_files(data_files_spec)) 48 | except ImportError: 49 | setup_args = dict() 50 | 51 | 52 | if __name__ == "__main__": 53 | setuptools.setup(**setup_args) 54 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": ["packages/**/*", "app/**/*", "buildutils/**/*", "ui-tests/**/*"], 4 | "types": ["jest"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "noEmitOnError": true, 5 | "noUnusedLocals": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "target": "es2015", 9 | "lib": [ 10 | "es2015", 11 | "es2015.collection", 12 | "dom", 13 | "es2015.iterable", 14 | "es2017.object" 15 | ], 16 | "types": ["jest"], 17 | "jsx": "react", 18 | "resolveJsonModule": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "strict": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "target": "es2017", 20 | "types": [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfigbase.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "noImplicitAny": true, 5 | "noEmitOnError": true, 6 | "noUnusedLocals": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "target": "es2015", 10 | "outDir": "lib", 11 | "lib": [ 12 | "es2015", 13 | "es2015.collection", 14 | "dom", 15 | "es2015.iterable", 16 | "es2017.object" 17 | ], 18 | "types": ["jest", "node"], 19 | "jsx": "react", 20 | "resolveJsonModule": true, 21 | "esModuleInterop": true, 22 | "strictNullChecks": true, 23 | "skipLibCheck": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@retrolab/ui-tests", 3 | "private": true, 4 | "version": "0.1.0", 5 | "author": "Project Jupyter", 6 | "license": "BSD-3-Clause", 7 | "description": "RetroLab UI Tests", 8 | "scripts": { 9 | "start": "jupyter retro --config test/jupyter_server_config.py", 10 | "start:detached": "yarn run start&", 11 | "test": "playwright test", 12 | "test:debug": "PWDEBUG=1 playwright test", 13 | "test:report": "http-server ./playwright-report -a localhost -o", 14 | "test:update": "playwright test --update-snapshots" 15 | }, 16 | "dependencies": { 17 | "@jupyterlab/galata": "~5.0.0-alpha.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import baseConfig from '@jupyterlab/galata/lib/playwright-config'; 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | timeout: 240000, 6 | use: { 7 | appPath: '/retro' 8 | }, 9 | retries: 1 10 | }; 11 | -------------------------------------------------------------------------------- /ui-tests/test/editor.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import path from 'path'; 5 | 6 | import { test } from './fixtures'; 7 | 8 | import { expect } from '@playwright/test'; 9 | 10 | const FILE = 'environment.yml'; 11 | 12 | test.use({ autoGoto: false }); 13 | 14 | const processRenameDialog = async (page, prevName: string, newName: string) => { 15 | // Rename in the input dialog 16 | await page.fill( 17 | `//div[normalize-space(.)='File Path${prevName}New Name']/input`, 18 | newName 19 | ); 20 | 21 | await Promise.all([ 22 | await page.click('text="Rename"'), 23 | // wait until the URL is updated 24 | await page.waitForNavigation() 25 | ]); 26 | }; 27 | 28 | test.describe('Editor', () => { 29 | test.beforeEach(async ({ page, tmpPath }) => { 30 | await page.contents.uploadFile( 31 | path.resolve(__dirname, `../../binder/${FILE}`), 32 | `${tmpPath}/${FILE}` 33 | ); 34 | }); 35 | 36 | test('Renaming the file by clicking on the title', async ({ 37 | page, 38 | tmpPath 39 | }) => { 40 | const file = `${tmpPath}/${FILE}`; 41 | await page.goto(`edit/${file}`); 42 | 43 | // Click on the title 44 | await page.click(`text="${FILE}"`); 45 | 46 | const newName = 'test.yml'; 47 | await processRenameDialog(page, file, newName); 48 | 49 | // Check the URL contains the new name 50 | const url = page.url(); 51 | expect(url).toContain(newName); 52 | }); 53 | 54 | test('Renaming the file via the menu entry', async ({ page, tmpPath }) => { 55 | const file = `${tmpPath}/${FILE}`; 56 | await page.goto(`edit/${file}`); 57 | 58 | // Click on the title 59 | await page.menu.clickMenuItem('File>Rename…'); 60 | 61 | // Rename in the input dialog 62 | const newName = 'test.yml'; 63 | 64 | await processRenameDialog(page, file, newName); 65 | 66 | // Check the URL contains the new name 67 | const url = page.url(); 68 | expect(url).toContain(newName); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /ui-tests/test/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { test as base } from '@jupyterlab/galata'; 2 | 3 | export const test = base.extend({ 4 | waitForApplication: async ({ baseURL }, use, testInfo) => { 5 | const waitIsReady = async (page): Promise => { 6 | await page.waitForSelector('#main-panel'); 7 | }; 8 | await use(waitIsReady); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /ui-tests/test/jupyter_server_config.py: -------------------------------------------------------------------------------- 1 | from tempfile import mkdtemp 2 | 3 | c.ServerApp.port = 8888 4 | c.ServerApp.port_retries = 0 5 | c.ServerApp.open_browser = False 6 | 7 | c.ServerApp.root_dir = mkdtemp(prefix="galata-test-") 8 | c.ServerApp.token = "" 9 | c.ServerApp.password = "" 10 | c.ServerApp.disable_check_xsrf = True 11 | c.RetroApp.expose_app_in_browser = True 12 | -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import path from 'path'; 5 | 6 | import { test } from './fixtures'; 7 | 8 | import { expect } from '@playwright/test'; 9 | 10 | const NOTEBOOK = 'empty.ipynb'; 11 | 12 | const MENU_PATHS = [ 13 | 'File', 14 | 'File>New', 15 | 'Edit', 16 | 'View', 17 | 'Run', 18 | 'Kernel', 19 | 'Settings', 20 | 'Settings>Theme', 21 | 'Help' 22 | ]; 23 | 24 | test.use({ autoGoto: false }); 25 | 26 | test.describe('Notebook Menus', () => { 27 | test.beforeEach(async ({ page, tmpPath }) => { 28 | await page.contents.uploadFile( 29 | path.resolve(__dirname, `./notebooks/${NOTEBOOK}`), 30 | `${tmpPath}/${NOTEBOOK}` 31 | ); 32 | }); 33 | 34 | test.afterEach(async ({ page, tmpPath }) => { 35 | await page.kernel.shutdownAll(); 36 | }); 37 | 38 | MENU_PATHS.forEach(menuPath => { 39 | test(`Open menu item ${menuPath}`, async ({ page, tmpPath }) => { 40 | await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`); 41 | await page.menu.open(menuPath); 42 | expect(await page.menu.isOpen(menuPath)).toBeTruthy(); 43 | 44 | const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`; 45 | const menu = await page.menu.getOpenMenu(); 46 | expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase()); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/mobile.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import path from 'path'; 5 | 6 | import { expect } from '@playwright/test'; 7 | 8 | import { test } from './fixtures'; 9 | import { waitForKernelReady } from './utils'; 10 | 11 | test.use({ autoGoto: false, viewport: { width: 512, height: 768 } }); 12 | 13 | test.describe('Mobile', () => { 14 | test('The layout should be more compact on the file browser page', async ({ 15 | page, 16 | tmpPath 17 | }) => { 18 | await page.goto(`tree/${tmpPath}`); 19 | await page.waitForSelector('#top-panel-wrapper', { state: 'hidden' }); 20 | expect(await page.screenshot()).toMatchSnapshot('tree.png'); 21 | }); 22 | 23 | test('The layout should be more compact on the notebook page', async ({ 24 | page, 25 | tmpPath 26 | }) => { 27 | const notebook = 'empty.ipynb'; 28 | await page.contents.uploadFile( 29 | path.resolve(__dirname, `./notebooks/${notebook}`), 30 | `${tmpPath}/${notebook}` 31 | ); 32 | await page.goto(`notebooks/${tmpPath}/${notebook}`); 33 | // TODO: investigate why this does not run the cells in RetroLab 34 | // await page.notebook.run(); 35 | 36 | // wait for the kernel status animations to be finished 37 | await waitForKernelReady(page); 38 | 39 | expect(await page.screenshot()).toMatchSnapshot('notebook.png'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/mobile.spec.ts-snapshots/notebook-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/mobile.spec.ts-snapshots/notebook-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/mobile.spec.ts-snapshots/tree-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/mobile.spec.ts-snapshots/tree-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/mobile.spec.ts-snapshots/tree-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/mobile.spec.ts-snapshots/tree-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/notebook.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import path from 'path'; 5 | 6 | import { expect } from '@playwright/test'; 7 | 8 | import { test } from './fixtures'; 9 | 10 | import { runAndAdvance, waitForKernelReady } from './utils'; 11 | 12 | const NOTEBOOK = 'example.ipynb'; 13 | 14 | test.use({ autoGoto: false }); 15 | 16 | test.describe('Notebook', () => { 17 | test.beforeEach(async ({ page, tmpPath }) => { 18 | await page.contents.uploadFile( 19 | path.resolve(__dirname, `../../binder/${NOTEBOOK}`), 20 | `${tmpPath}/${NOTEBOOK}` 21 | ); 22 | }); 23 | 24 | test('Title should be rendered', async ({ page, tmpPath }) => { 25 | await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`); 26 | const href = await page.evaluate(() => { 27 | return document.querySelector('#jp-RetroLogo')?.getAttribute('href'); 28 | }); 29 | expect(href).toContain('/retro/tree'); 30 | }); 31 | 32 | test('Renaming the notebook should be possible', async ({ 33 | page, 34 | tmpPath 35 | }) => { 36 | const notebook = `${tmpPath}/${NOTEBOOK}`; 37 | await page.goto(`notebooks/${notebook}`); 38 | 39 | // Click on the title (with .ipynb extension stripped) 40 | await page.click('text="example"'); 41 | 42 | // Rename in the input dialog 43 | const newName = 'test.ipynb'; 44 | const newNameStripped = 'test'; 45 | await page.fill( 46 | `//div[normalize-space(.)='File Path${notebook}New Name']/input`, 47 | newName 48 | ); 49 | 50 | await Promise.all([ 51 | await page.click('text="Rename"'), 52 | // wait until the URL is updated 53 | await page.waitForNavigation() 54 | ]); 55 | 56 | // Check the URL contains the new name 57 | const url = page.url(); 58 | expect(url).toContain(newNameStripped); 59 | }); 60 | 61 | // TODO: rewrite with page.notebook when fixed upstream in Galata 62 | // and usable in RetroLab without active tabs 63 | test('Outputs should be scrolled automatically', async ({ 64 | page, 65 | tmpPath 66 | }) => { 67 | const notebook = 'autoscroll.ipynb'; 68 | await page.contents.uploadFile( 69 | path.resolve(__dirname, `./notebooks/${notebook}`), 70 | `${tmpPath}/${notebook}` 71 | ); 72 | await page.goto(`notebooks/${tmpPath}/${notebook}`); 73 | 74 | await waitForKernelReady(page); 75 | // run the two cells 76 | await runAndAdvance(page); 77 | await runAndAdvance(page); 78 | 79 | await page.waitForSelector('.jp-Cell-outputArea pre'); 80 | 81 | const checkCell = async (n: number): Promise => { 82 | const scrolled = await page.$eval(`.jp-Notebook-cell >> nth=${n}`, el => 83 | el.classList.contains('jp-mod-outputsScrolled') 84 | ); 85 | return scrolled; 86 | }; 87 | 88 | // check the long output area is auto scrolled 89 | expect(await checkCell(0)).toBe(true); 90 | 91 | // check the short output area is not auto scrolled 92 | expect(await checkCell(1)).toBe(false); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /ui-tests/test/notebooks/autoscroll.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "6f7028b9-4d2c-4fa2-96ee-bfa77bbee434", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": ["print('1\\n' * 200)"] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "6f7028b9-4d2c-4fa2-96ee-bfa77bbee434", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": ["print('1\\n' * 20)"] 18 | } 19 | ], 20 | "metadata": { 21 | "kernelspec": { 22 | "display_name": "Python 3 (ipykernel)", 23 | "language": "python", 24 | "name": "python3" 25 | }, 26 | "language_info": { 27 | "codemirror_mode": { 28 | "name": "ipython", 29 | "version": 3 30 | }, 31 | "file_extension": ".py", 32 | "mimetype": "text/x-python", 33 | "name": "python", 34 | "nbconvert_exporter": "python", 35 | "pygments_lexer": "ipython3", 36 | "version": "3.9.7" 37 | } 38 | }, 39 | "nbformat": 4, 40 | "nbformat_minor": 5 41 | } 42 | -------------------------------------------------------------------------------- /ui-tests/test/notebooks/empty.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "6f7028b9-4d2c-4fa2-96ee-bfa77bbee434", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [] 10 | } 11 | ], 12 | "metadata": { 13 | "kernelspec": { 14 | "display_name": "Python 3 (ipykernel)", 15 | "language": "python", 16 | "name": "python3" 17 | }, 18 | "language_info": { 19 | "codemirror_mode": { 20 | "name": "ipython", 21 | "version": 3 22 | }, 23 | "file_extension": ".py", 24 | "mimetype": "text/x-python", 25 | "name": "python", 26 | "nbconvert_exporter": "python", 27 | "pygments_lexer": "ipython3", 28 | "version": "3.9.7" 29 | } 30 | }, 31 | "nbformat": 4, 32 | "nbformat_minor": 5 33 | } 34 | -------------------------------------------------------------------------------- /ui-tests/test/settings.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { test } from './fixtures'; 5 | 6 | import { expect } from '@playwright/test'; 7 | 8 | test.use({ autoGoto: false }); 9 | 10 | test.describe('Settings', () => { 11 | test('Should be persisted after reloading the page', async ({ 12 | page, 13 | tmpPath 14 | }) => { 15 | const showHeaderPath = 'View>Show Header'; 16 | 17 | await page.goto(`tree/${tmpPath}`); 18 | 19 | await page.waitForSelector('#top-panel', { state: 'visible' }); 20 | await page.menu.clickMenuItem(showHeaderPath); 21 | await page.waitForSelector('#top-panel', { state: 'hidden' }); 22 | await page.reload({ waitUntil: 'networkidle' }); 23 | await page.menu.getMenuItem(showHeaderPath); 24 | expect(await page.screenshot()).toMatchSnapshot('top-hidden.png'); 25 | 26 | await page.waitForSelector('#top-panel', { state: 'hidden' }); 27 | await page.menu.clickMenuItem(showHeaderPath); 28 | await page.waitForSelector('#top-panel', { state: 'visible' }); 29 | await page.reload({ waitUntil: 'networkidle' }); 30 | await page.menu.getMenuItem(showHeaderPath); 31 | expect(await page.screenshot()).toMatchSnapshot('top-visible.png'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png -------------------------------------------------------------------------------- /ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/retrolab/7286f5e19288c58c2d1d0d4131308dda980b42b6/ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png -------------------------------------------------------------------------------- /ui-tests/test/smoke.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@playwright/test'; 2 | 3 | import { test } from './fixtures'; 4 | 5 | import { runAndAdvance } from './utils'; 6 | 7 | test.use({ autoGoto: false }); 8 | 9 | test.describe('Smoke', () => { 10 | test('Tour', async ({ page, tmpPath }) => { 11 | // Open the tree page 12 | await page.goto(`tree/${tmpPath}`); 13 | await page.click('text="Running"'); 14 | await page.click('text="Files"'); 15 | 16 | // Create a new console 17 | await page.click('text="New Console"'); 18 | // Choose the kernel 19 | const [console] = await Promise.all([ 20 | page.waitForEvent('popup'), 21 | page.click('text="Select"') 22 | ]); 23 | await console.waitForLoadState(); 24 | await console.waitForSelector('.jp-CodeConsole'); 25 | 26 | // Create a new notebook 27 | const [notebook] = await Promise.all([ 28 | page.waitForEvent('popup'), 29 | page.click('text="New Notebook"') 30 | ]); 31 | 32 | // Enter code in the first cell 33 | await notebook.fill('//textarea', 'import math'); 34 | await notebook.press('//textarea', 'Enter'); 35 | await notebook.press('//textarea', 'Enter'); 36 | await notebook.fill('//textarea', 'math.pi'); 37 | 38 | // Run the cell 39 | runAndAdvance(notebook); 40 | 41 | // Enter code in the next cell 42 | await notebook.fill( 43 | "//div[normalize-space(.)=' ​']/div[1]/textarea", 44 | 'import this' 45 | ); 46 | 47 | // Run the cell 48 | runAndAdvance(notebook); 49 | 50 | // Save the notebook 51 | // TODO: re-enable after fixing the name on save dialog? 52 | // await notebook.click('//span/*[local-name()="svg"]'); 53 | 54 | // Click on the Jupyter logo to open the tree page 55 | const [tree2] = await Promise.all([ 56 | notebook.waitForEvent('popup'), 57 | notebook.click( 58 | '//*[local-name()="svg" and normalize-space(.)=\'Jupyter\']' 59 | ) 60 | ]); 61 | 62 | // Shut down the kernels 63 | await tree2.click('text="Running"'); 64 | await tree2.click('text="Shut Down All"'); 65 | await tree2.click("//div[normalize-space(.)='Shut Down All']"); 66 | 67 | // Close the pages 68 | await tree2.close(); 69 | await notebook.close(); 70 | await console.close(); 71 | await page.close(); 72 | 73 | expect(true).toBe(true); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /ui-tests/test/tree.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { test } from './fixtures'; 5 | 6 | import { expect } from '@playwright/test'; 7 | 8 | const SUBFOLDER = 'subfolder'; 9 | 10 | test('Tree', async ({ page }) => { 11 | await page.goto('tree'); 12 | const button = await page.$('text="New Notebook"'); 13 | expect(button).toBeDefined(); 14 | }); 15 | 16 | test('should go to subfolder', async ({ page, tmpPath }) => { 17 | const dir = `${tmpPath}/${SUBFOLDER}`; 18 | await page.contents.createDirectory(dir); 19 | await page.goto(`tree/${dir}`); 20 | 21 | expect( 22 | await page.waitForSelector(`.jp-FileBrowser-crumbs >> text=/${SUBFOLDER}/`) 23 | ).toBeTruthy(); 24 | }); 25 | 26 | test('should update url when navigating in filebrowser', async ({ 27 | page, 28 | tmpPath 29 | }) => { 30 | await page.contents.createDirectory(`${tmpPath}/${SUBFOLDER}`); 31 | 32 | await page.dblclick(`.jp-FileBrowser-listing >> text=${SUBFOLDER}`); 33 | 34 | await page.waitForSelector(`.jp-FileBrowser-crumbs >> text=/${SUBFOLDER}/`); 35 | 36 | const url = new URL(page.url()); 37 | expect(url.pathname).toEqual(`/retro/tree/${tmpPath}/${SUBFOLDER}`); 38 | }); 39 | -------------------------------------------------------------------------------- /ui-tests/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { IJupyterLabPageFixture } from '@jupyterlab/galata'; 2 | 3 | import { Page } from '@playwright/test'; 4 | 5 | /** 6 | * Run the selected cell and advance. 7 | */ 8 | export async function runAndAdvance( 9 | page: IJupyterLabPageFixture | Page 10 | ): Promise { 11 | await page.click(".jp-Toolbar-item [data-icon='ui-components:run']"); 12 | } 13 | 14 | /** 15 | * Wait for the kernel to be ready 16 | */ 17 | export async function waitForKernelReady( 18 | page: IJupyterLabPageFixture 19 | ): Promise { 20 | await page.waitForSelector('.jp-RetroKernelStatus-fade'); 21 | await page.waitForFunction(() => { 22 | const status = window.document.getElementsByClassName( 23 | 'jp-RetroKernelStatus' 24 | )[0]; 25 | 26 | if (!status) { 27 | return false; 28 | } 29 | 30 | const finished = status?.getAnimations().reduce((prev, curr) => { 31 | return prev && curr.playState === 'finished'; 32 | }, true); 33 | return finished; 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /ui-tests/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfigbase.test", 3 | "include": ["test/**/*"] 4 | } 5 | --------------------------------------------------------------------------------