├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ ├── check-release.yml │ ├── fix-license-header.yml │ └── update-integration-tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── .yarnrc.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── RELEASE.md ├── babel.config.js ├── conftest.py ├── dev ├── README.md ├── config │ ├── README.md │ └── jupyter_server_config.py ├── content │ ├── README.md │ ├── ping.ipynb │ ├── subfolder-1 │ │ ├── README.md │ │ ├── subfolder-1-1 │ │ │ ├── README.md │ │ │ └── test-1-1.ipynb │ │ └── test-1.ipynb │ ├── subfolder-2 │ │ ├── README.md │ │ ├── test-2.ipynb │ │ └── untitled.txt │ ├── test.ipynb │ ├── tmp.ipynb │ └── untitled.txt └── sh │ ├── README.md │ ├── kill.sh │ └── start-jupyter-server.sh ├── docs ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── babel.config.js ├── docs │ └── index.mdx ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ ├── HomepageFeatures.js │ │ ├── HomepageFeatures.module.css │ │ ├── HomepageProducts.js │ │ └── HomepageProducts.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ ├── markdown-page.md │ │ └── testimonials.tsx └── static │ └── img │ ├── datalayer │ ├── logo.png │ └── logo.svg │ ├── favicon.ico │ ├── feature_1.svg │ ├── feature_2.svg │ ├── feature_3.svg │ ├── product_1.svg │ ├── product_2.svg │ └── product_3.svg ├── environment.yml ├── hatch_build.py ├── install.json ├── jest-playwright.config.js ├── jest.config.js ├── jupyter-config ├── nb-config │ └── jupyter_viewer.json └── server-config │ └── jupyter_viewer.json ├── jupyter_viewer ├── __init__.py ├── __main__.py ├── handlers │ ├── __init__.py │ ├── base.py │ ├── config │ │ ├── __init__.py │ │ └── handler.py │ └── index │ │ ├── __init__.py │ │ └── handler.py ├── serverapplication.py ├── static │ └── README.md ├── templates │ └── index.html └── tests │ ├── __init__.py │ └── test_handlers.py ├── package.json ├── public └── index.html ├── pyproject.toml ├── schema ├── notebook.json └── plugin.json ├── setup.py ├── src ├── Viewer.tsx ├── ViewerApp.tsx ├── ViewerJupyterLab.tsx ├── ViewerJupyterLabApp.tsx ├── ViewerJupyterLabHeadless.tsx ├── ViewerRoutes.tsx ├── __tests__ │ ├── browser.spec.ts │ └── datalayer.spec.ts ├── index.ts ├── jupyterlab │ ├── handler.ts │ ├── index.ts │ ├── viewer │ │ ├── Viewer.tsx │ │ ├── ViewerButton.ts │ │ ├── ViewerDocument.tsx │ │ ├── ViewerWidget.tsx │ │ ├── plugin.ts │ │ └── token.ts │ └── widget.tsx ├── state │ └── zustand.ts ├── typings.d.ts └── views │ ├── ViewerAbout.tsx │ ├── ViewerExamples.tsx │ ├── ViewerForm.tsx │ ├── ViewerGitHub.tsx │ ├── index.ts │ └── notebooks │ └── Examples.tsx ├── style ├── base.css ├── index.css └── index.js ├── tsconfig.json ├── ui-tests ├── README.md ├── jupyter_server_test_config.py ├── package.json ├── playwright.config.js └── tests │ └── datalayer.spec.ts ├── webpack.config.js └── webpack.lab-config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | __pycache__ 3 | build 4 | dist 5 | lib 6 | node_modules 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | 7 | **/__tests__ 8 | ui-tests 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/naming-convention': [ 16 | 'error', 17 | { 18 | selector: 'interface', 19 | format: ['PascalCase'], 20 | custom: { 21 | regex: '^I[A-Z]', 22 | match: true 23 | } 24 | } 25 | ], 26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-namespace': 'off', 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | '@typescript-eslint/quotes': [ 31 | 'error', 32 | 'single', 33 | { avoidEscape: true, allowTemplateLiterals: false } 34 | ], 35 | curly: ['error', 'all'], 36 | eqeqeq: 'error', 37 | 'prefer-arrow-callback': 'error' 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: ['*'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Base Setup 18 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 19 | 20 | - name: Install dependencies 21 | run: python -m pip install -U jupyterlab==4.1.0b0 22 | 23 | - name: Lint the extension 24 | run: | 25 | set -eux 26 | jlpm 27 | jlpm run lint:check 28 | 29 | - name: Test the extension 30 | run: | 31 | set -eux 32 | jlpm run test 33 | 34 | - name: Build the extension 35 | run: | 36 | set -eux 37 | python -m pip install .[test] 38 | 39 | pytest -vv -r ap --cov jupyter_viewer 40 | jupyter server extension list 41 | jupyter server extension list 2>&1 | grep -ie "jupyter_viewer.*OK" 42 | 43 | jupyter labextension list 44 | jupyter labextension list 2>&1 | grep -ie "@datalayer/jupyter_viewer.*OK" 45 | python -m jupyterlab.browser_check 46 | 47 | - name: Package the extension 48 | run: | 49 | set -eux 50 | 51 | pip install build 52 | python -m build 53 | pip uninstall -y "jupyter_viewer" jupyterlab 54 | 55 | - name: Upload extension packages 56 | uses: actions/upload-artifact@v2 57 | with: 58 | name: extension-artifacts 59 | path: dist/jupyter_viewer* 60 | if-no-files-found: error 61 | 62 | test_isolated: 63 | needs: build 64 | runs-on: ubuntu-latest 65 | 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v2 69 | - name: Install Python 70 | uses: actions/setup-python@v2 71 | with: 72 | python-version: '3.9' 73 | architecture: 'x64' 74 | - uses: actions/download-artifact@v2 75 | with: 76 | name: extension-artifacts 77 | - name: Install and Test 78 | run: | 79 | set -eux 80 | # Remove NodeJS, twice to take care of system and locally installed node versions. 81 | sudo rm -rf $(which node) 82 | sudo rm -rf $(which node) 83 | 84 | pip install "jupyterlab==4.1.0b0" jupyter_viewer*.whl 85 | 86 | 87 | jupyter server extension list 88 | jupyter server extension list 2>&1 | grep -ie "jupyter_viewer.*OK" 89 | 90 | jupyter labextension list 91 | jupyter labextension list 2>&1 | grep -ie "@datalayer/jupyter_viewer.*OK" 92 | python -m jupyterlab.browser_check --no-chrome-test 93 | 94 | integration-tests: 95 | name: Integration tests 96 | needs: build 97 | runs-on: ubuntu-latest 98 | 99 | env: 100 | PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers 101 | 102 | steps: 103 | - name: Checkout 104 | uses: actions/checkout@v2 105 | 106 | - name: Base Setup 107 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 108 | 109 | - name: Download extension package 110 | uses: actions/download-artifact@v2 111 | with: 112 | name: extension-artifacts 113 | 114 | - name: Install the extension 115 | run: | 116 | set -eux 117 | python -m pip install "jupyterlab==4.1.0b0" jupyter_viewer*.whl 118 | 119 | - name: Install dependencies 120 | working-directory: ui-tests 121 | env: 122 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 123 | run: jlpm install 124 | 125 | - name: Set up browser cache 126 | uses: actions/cache@v2 127 | with: 128 | path: | 129 | ${{ github.workspace }}/pw-browsers 130 | key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }} 131 | 132 | - name: Install browser 133 | run: jlpm playwright install chromium 134 | working-directory: ui-tests 135 | 136 | - name: Execute integration tests 137 | working-directory: ui-tests 138 | run: | 139 | jlpm playwright test 140 | 141 | - name: Upload Playwright Test report 142 | if: always() 143 | uses: actions/upload-artifact@v2 144 | with: 145 | name: jupyter_viewer-playwright-tests 146 | path: | 147 | ui-tests/test-results 148 | ui-tests/playwright-report 149 | 150 | check_links: 151 | name: Check Links 152 | runs-on: ubuntu-latest 153 | timeout-minutes: 15 154 | steps: 155 | - uses: actions/checkout@v2 156 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 157 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 158 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | jobs: 9 | check_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Base Setup 15 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 16 | - name: Install Dependencies 17 | run: | 18 | pip install -e . 19 | - name: Check Release 20 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 21 | with: 22 | 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Upload Distributions 26 | uses: actions/upload-artifact@v2 27 | with: 28 | name: jupyter-viewer-releaser-dist-${{ github.run_number }} 29 | path: .jupyter_releaser_checkout/dist -------------------------------------------------------------------------------- /.github/workflows/fix-license-header.yml: -------------------------------------------------------------------------------- 1 | name: Fix License Headers 2 | 3 | on: 4 | pull_request_target: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | header-license-fix: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Checkout the branch from the PR that triggered the job 25 | run: gh pr checkout ${{ github.event.pull_request.number }} 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: Fix License Header 30 | # pin to include https://github.com/apache/skywalking-eyes/pull/168 31 | uses: apache/skywalking-eyes/header@e19b828cea6a6027cceae78f05d81317347d21be 32 | with: 33 | mode: fix 34 | 35 | - name: List files changed 36 | id: files-changed 37 | shell: bash -l {0} 38 | run: | 39 | set -ex 40 | export CHANGES=$(git status --porcelain | tee /tmp/modified.log | wc -l) 41 | cat /tmp/modified.log 42 | 43 | echo "N_CHANGES=${CHANGES}" >> $GITHUB_OUTPUT 44 | 45 | git diff 46 | 47 | - name: Commit any changes 48 | if: steps.files-changed.outputs.N_CHANGES != '0' 49 | shell: bash -l {0} 50 | run: | 51 | git config user.name "github-actions[bot]" 52 | git config user.email "github-actions[bot]@users.noreply.github.com" 53 | 54 | git pull --no-tags 55 | 56 | git add * 57 | git commit -m "Automatic application of license header" 58 | 59 | git config push.default upstream 60 | git push 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /.github/workflows/update-integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Update Playwright Snapshots 2 | 3 | on: 4 | issue_comment: 5 | types: [created, edited] 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | 13 | 14 | update-snapshots: 15 | if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update playwright snapshots') }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Configure git to use https 25 | run: git config --global hub.protocol https 26 | 27 | - name: Checkout the branch from the PR that triggered the job 28 | run: hub pr checkout ${{ github.event.issue.number }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | set -eux 35 | jlpm 36 | python -m pip install . 37 | 38 | - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | # Playwright knows how to start JupyterLab server 42 | start_server_script: 'null' 43 | test_folder: ui-tests 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | yarn.lock 3 | *.bundle.* 4 | lib/ 5 | node_modules/ 6 | .eslintcache 7 | .stylelintcache 8 | *.egg-info/ 9 | .ipynb_checkpoints 10 | *.tsbuildinfo 11 | jupyter_viewer/labextension 12 | 13 | # Version file is handled by hatchling 14 | jupyter_viewer/_version.py 15 | 16 | jupyter_viewer/static/*.js 17 | jupyter_viewer/static/*.css 18 | 19 | coverage 20 | 21 | # Integration tests 22 | ui-tests/test-results/ 23 | ui-tests/playwright-report/ 24 | 25 | # Created by https://www.gitignore.io/api/python 26 | # Edit at https://www.gitignore.io/?templates=python 27 | 28 | ### Python ### 29 | # Byte-compiled / optimized / DLL files 30 | __pycache__/ 31 | *.py[cod] 32 | *$py.class 33 | 34 | # C extensions 35 | *.so 36 | 37 | # Distribution / packaging 38 | .Python 39 | build/ 40 | develop-eggs/ 41 | dist/ 42 | downloads/ 43 | eggs/ 44 | .eggs/ 45 | lib/ 46 | lib64/ 47 | parts/ 48 | sdist/ 49 | var/ 50 | wheels/ 51 | pip-wheel-metadata/ 52 | share/python-wheels/ 53 | .installed.cfg 54 | *.egg 55 | MANIFEST 56 | 57 | # PyInstaller 58 | # Usually these files are written by a python script from a template 59 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 60 | *.manifest 61 | *.spec 62 | 63 | # Installer logs 64 | pip-log.txt 65 | pip-delete-this-directory.txt 66 | 67 | # Unit test / coverage reports 68 | htmlcov/ 69 | .tox/ 70 | .nox/ 71 | .coverage 72 | .coverage.* 73 | .cache 74 | nosetests.xml 75 | coverage.xml 76 | *.cover 77 | .hypothesis/ 78 | .pytest_cache/ 79 | 80 | # Translations 81 | *.mo 82 | *.pot 83 | 84 | # Scrapy stuff: 85 | .scrapy 86 | 87 | # Sphinx documentation 88 | docs/_build/ 89 | 90 | # PyBuilder 91 | target/ 92 | 93 | # pyenv 94 | .python-version 95 | 96 | # celery beat schedule file 97 | celerybeat-schedule 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Spyder project settings 103 | .spyderproject 104 | .spyproject 105 | 106 | # Rope project settings 107 | .ropeproject 108 | 109 | # Mr Developer 110 | .mr.developer.cfg 111 | .project 112 | .pydevproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # End of https://www.gitignore.io/api/python 126 | 127 | *creds.sh 128 | 129 | *ystore.db 130 | 131 | # Include 132 | !**/.*ignore 133 | !**/.*rc 134 | !**/.*rc.js 135 | !**/.*rc.json 136 | !**/.*rc.yml 137 | !**/.*config 138 | !*.*rc.json 139 | !**/.env 140 | !.github 141 | !.devcontainer 142 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | jupyter_viewer 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-standard", 5 | "stylelint-prettier/recommended" 6 | ], 7 | "rules": { 8 | "property-no-vendor-prefix": null, 9 | "selector-no-vendor-prefix": null, 10 | "value-no-vendor-prefix": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Datalayer, Inc. https://datalayer.io 2 | # Distributed under the terms of the MIT License. 3 | 4 | enableImmutableInstalls: false 5 | enableInlineBuilds: false 6 | enableTelemetry: false 7 | httpTimeout: 60000 8 | nodeLinker: node-modules 9 | npmRegistryServer: "https://registry.yarnpkg.com" 10 | checksumBehavior: update 11 | 12 | # This will fix the build error with @lerna/legacy-package-management 13 | # See https://github.com/lerna/repro/pull/11 14 | packageExtensions: 15 | "@lerna/legacy-package-management@*": 16 | dependencies: 17 | "@lerna/child-process": "*" 18 | "js-yaml": "*" 19 | "rimraf": "*" 20 | peerDependencies: 21 | "nx": "*" 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Datalayer, Inc. https://datalayer.io 2 | # Distributed under the terms of the MIT License. 3 | 4 | FROM python:3.11 5 | 6 | RUN pip install jupyter-viewer==0.0.8 7 | 8 | EXPOSE 8888 9 | 10 | CMD ["jupyter", "viewer", "--ip", "0.0.0.0", "--IdentityProvider.token", "", "--allow-root", "--no-browser"] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Datalayer 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 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. 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 | 3. 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Datalayer, Inc. https://datalayer.io 2 | # Distributed under the terms of the MIT License. 3 | 4 | SHELL=/bin/bash 5 | 6 | ENV_NAME=datalayer 7 | 8 | .DEFAULT_GOAL := default 9 | 10 | .SILENT: init 11 | 12 | help: ## display this help 13 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 14 | 15 | default: help ## default target is help 16 | 17 | env: warning ## env 18 | micromamba env create -y -n ${ENV_NAME} -f environment.yml 19 | @exec echo "-------------------------------------------------------" 20 | @exec echo "micromamba activate ${ENV_NAME}" 21 | @exec echo "-------------------------------------------------------" 22 | 23 | env-rm: warning ## env-rm 24 | micromamba deactivate && \ 25 | micromamba remove -y --name ${ENV_NAME} --all || true 26 | 27 | kill: 28 | yarn kill 29 | 30 | warning: 31 | echo "\x1b[34m\x1b[43mEnsure you have run \x1b[1;37m\x1b[41m conda deactivate \x1b[22m\x1b[34m\x1b[43m before invoking this.\x1b[0m" 32 | 33 | clean: ## clean 34 | yarn clean 35 | 36 | build: ## build 37 | yarn build 38 | 39 | build-webpack: ## build-webpack 40 | yarn build:webpack 41 | 42 | build-webpack-prod: ## build-webpack-prod 43 | rm -fr ./dist 44 | yarn build:webpack:prod 45 | 46 | build-prod: ## build-prod 47 | git clean -fdx 48 | python -m build 49 | 50 | publish-npm: clean build ## publish 51 | npm publish 52 | echo open https://www.npmjs.com/package/@datalayer/jupyter-viewer 53 | 54 | build-docker: ## build the image. 55 | docker build \ 56 | -t datalayer/jupyter-viewer:0.0.1 \ 57 | -f Dockerfile \ 58 | . 59 | 60 | push-docker: ## push the image. 61 | docker push \ 62 | datalayer/jupyter-viewer:0.0.1 63 | 64 | start-docker: ## start the container. 65 | echo open http://localhost:8888/jupyter_viewer 66 | docker run \ 67 | -it \ 68 | --rm \ 69 | --name jupyter-viewer \ 70 | -p 8888:8888 \ 71 | datalayer/jupyter-viewer:0.0.1 72 | 73 | connect-docker: ## connect to the container. 74 | docker exec -it jupyter-viewer bash 75 | 76 | logs-docker: ## show container logs. 77 | docker logs jupyter-viewer -f 78 | 79 | stop-docker: ## stop the container. 80 | docker stop jupyter-viewer 81 | 82 | rm-docker: ## remove the container. 83 | docker rm -f jupyter-viewer 84 | 85 | publish: build-webpack-prod ## publish 86 | ( sed -i.bu "s|http://localhost:3063||g" ./dist/index.html && \ 87 | aws s3 rm \ 88 | s3://datalayer-viewer/ \ 89 | --recursive \ 90 | --profile datalayer && \ 91 | aws s3 cp \ 92 | ./dist \ 93 | s3://datalayer-viewer/ \ 94 | --recursive \ 95 | --profile datalayer && \ 96 | aws cloudfront create-invalidation \ 97 | --distribution-id E1BK7NGENPR3RM \ 98 | --paths "/*" \ 99 | --profile datalayer && \ 100 | echo open ✨ https://viewer.datalayer.tech ) 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # 🪐 👀 Jupyter Viewer 6 | 7 | > A revisited [NbViewer](https://nbviewer.org) as a modern Web application to view Jupyter notebooks. 8 | 9 | [NbViewer](https://nbviewer.org) is built on top of the [GitHub nbviewer repository](https://github.com/jupyter/nbviewer) and has been useful for many data scientists since years. 10 | 11 | Jupyter Viewer takes over the concept of easy visualisation and brings it to the Web application developers with React.js components to render Notebooks without any Kernel. It is also available as JupyterLab extension. 12 | 13 | You can try on https://viewer.datalayer.tech. 14 | 15 | The documentation lives on https://jupyter-viewer.datalayer.tech. 16 | 17 | If needed, Users can connect the static view to a Kernel and make it executable. 18 | 19 | ## Usage 20 | 21 | This repository packages the [Jupyter UI Viewer](https://jupyter-ui.datalayer.tech/docs/components/viewer) component as a JupyterLab extension. Install and launch with the following commands (you will need Python installed on your machine). 22 | 23 | ```bash 24 | pip install jupyter_viewer 25 | jupyter viewer 26 | ``` 27 | 28 | After launching, enjoy the 👀 views on http://localhost:8888/jupyter_viewer (served by Jupyter). 29 | 30 |
31 | Jupyter Viewer 32 |
33 | 34 | You can view a Notebook hosted on GitHub using the following pattern: `http://localhost:8888/github/{account}/{repo}/{branch}/{path}` 35 | 36 | ```bash 37 | # For example... 38 | open http://localhost:8888/jupyter_viewer/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 39 | ``` 40 | 41 | ## Develop 42 | 43 | Develop the Web application. 44 | 45 | ```bash 46 | yarn 47 | yarn start 48 | ``` 49 | 50 | ```bash 51 | # Browse the Webpack server. 52 | open http://localhost:3063 53 | open http://localhost:3063/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 54 | ``` 55 | 56 | Develop the Jupyter Server 57 | 58 | ```bash 59 | pip install -e .[test] 60 | jupyter viewer 61 | ``` 62 | 63 | Browse the Jupyter Server. 64 | 65 | ```bash 66 | open http://localhost:8888/jupyter_viewer 67 | open http://localhost:8888/jupyter_viewer/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 68 | ``` 69 | 70 | Develop the JupyterLab extension. 71 | 72 | ```bash 73 | pip install -e .[test] 74 | jupyter labextension develop . --overwrite 75 | jupyter labextension list 76 | jupyter server extension list 77 | yarn jupyterlab 78 | ``` 79 | 80 | ```bash 81 | # Browse JupyterLab. 82 | open http://localhost:8888 83 | ``` 84 | 85 | ## Publish 86 | 87 | ```bash 88 | make publish 89 | open https://viewer.datalayer.tech 90 | open https://viewer.datalayer.tech/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 91 | ``` 92 | 93 | ## Releases 94 | 95 | Jupyter Viewer is released as a python package in [PyPI](https://pypi.org/project/jupyter-viewer). 96 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of Jupyter Viewer 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python 10 | packages. All of the Python 11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a 12 | Python package. Before generating a package, we first need to install `build`. 13 | 14 | ```bash 15 | pip install build twine hatch 16 | ``` 17 | 18 | Bump the version using `hatch`. By default this will create a tag. 19 | See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. 20 | 21 | ```bash 22 | hatch version 23 | ``` 24 | 25 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 26 | 27 | ```bash 28 | python -m build 29 | ``` 30 | 31 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 32 | 33 | Then to upload the package to PyPI, do: 34 | 35 | ```bash 36 | twine upload dist/* 37 | ``` 38 | 39 | ### NPM package 40 | 41 | To publish the frontend part of the extension as a NPM package, do: 42 | 43 | ```bash 44 | npm login 45 | npm publish --access public 46 | ``` 47 | 48 | ## Automated releases with the Jupyter Releaser 49 | 50 | The extension repository should already be compatible with the Jupyter Releaser. 51 | 52 | Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. 53 | 54 | Here is a summary of the steps to cut a new release: 55 | 56 | - Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) 57 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork 58 | - Go to the Actions panel 59 | - Run the "Draft Changelog" workflow 60 | - Merge the Changelog PR 61 | - Run the "Draft Release" workflow 62 | - Run the "Publish Release" workflow 63 | 64 | ## Publishing to `conda-forge` 65 | 66 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 67 | 68 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 69 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | pytest_plugins = ("jupyter_server.pytest_plugin", ) 4 | 5 | 6 | @pytest.fixture 7 | def jp_server_config(jp_server_config): 8 | return {"ServerApp": {"jpserver_extensions": {"jupyter_viewer": True}}} 9 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # Jupyter Viewer Development 6 | -------------------------------------------------------------------------------- /dev/config/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # Jupyter Viewer Development Configuration 6 | -------------------------------------------------------------------------------- /dev/config/jupyter_server_config.py: -------------------------------------------------------------------------------- 1 | """Configuration for the Jupyter development server.""" 2 | 3 | import os 4 | 5 | ################# 6 | # Logging 7 | ################# 8 | 9 | c.ServerApp.log_level = 'INFO' 10 | 11 | ################# 12 | # Network 13 | ################# 14 | 15 | c.ServerApp.ip = '0.0.0.0' 16 | c.ServerApp.port = 8888 17 | c.ServerApp.port_retries = 0 18 | 19 | ################# 20 | # Browser 21 | ################# 22 | 23 | c.ServerApp.open_browser = False 24 | 25 | ################# 26 | # Terminal 27 | ################# 28 | 29 | c.ServerApp.terminals_enabled = True 30 | 31 | ################# 32 | # Authentication 33 | ################# 34 | 35 | c.IdentityProvider.token = '' 36 | 37 | ################# 38 | # Security 39 | ################# 40 | 41 | c.ServerApp.disable_check_xsrf = False 42 | # ORIGIN = 'http://localhost:3208' 43 | ORIGIN = '*' 44 | # c.ServerApp.allow_origin = ORIGIN 45 | c.ServerApp.allow_origin_pat = '.*' 46 | c.ServerApp.allow_credentials = True 47 | c.ServerApp.tornado_settings = { 48 | 'headers': { 49 | # 'Access-Control-Allow-Origin': ORIGIN, 50 | 'Access-Control-Allow-Methods': '*', 51 | 'Access-Control-Allow-Headers': 'Accept, Accept-Encoding, Accept-Language, Authorization, Cache-Control, Connection, Content-Type, Host, Origin, Pragma, Referer, sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Upgrade, User-Agent, X-XSRFToken, X-Datalayer, Expires', 52 | 'Access-Control-Allow-Credentials': 'true', 53 | 'Content-Security-Policy': f"frame-ancestors 'self' {ORIGIN} ", 54 | }, 55 | 'cookie_options': { 56 | 'SameSite': 'None', 57 | 'Secure': True 58 | } 59 | } 60 | c.IdentityProvider.cookie_options = { 61 | "SameSite": "None", 62 | "Secure": True, 63 | } 64 | 65 | ################# 66 | # Server Extensions 67 | ################# 68 | 69 | c.ServerApp.jpserver_extensions = { 70 | 'jupyterlab': True, 71 | 'jupyter_viewer': True, 72 | } 73 | 74 | ################# 75 | # Content 76 | ################# 77 | 78 | # c.FileContentsManager.delete_to_trash = False 79 | content_dir = os.path.dirname(os.path.realpath(__file__)) + '/../content' 80 | c.ServerApp.root_dir = content_dir 81 | c.ServerApp.preferred_dir = content_dir 82 | 83 | ################# 84 | # URLs 85 | ################# 86 | 87 | c.ServerApp.base_url = '/' 88 | c.ServerApp.default_url = '/lab' 89 | 90 | ################# 91 | # Kernel 92 | ################# 93 | 94 | # See 95 | # https://github.com/jupyterlab/jupyterlab/pull/11841 96 | # https://github.com/jupyter-server/jupyter_server/pull/657 97 | c.ZMQChannelsWebsocketConnection.kernel_ws_protocol = None # None or '' 98 | 99 | ################# 100 | # JupyterLab 101 | ################# 102 | 103 | c.LabApp.collaborative = False 104 | -------------------------------------------------------------------------------- /dev/content/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # Jupyter React Development Notebooks 6 | -------------------------------------------------------------------------------- /dev/content/ping.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "jupyter-viewer", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "name": "python", 19 | "version": "3.10.4 | packaged by conda-forge | (main, Mar 24 2022, 17:43:32) [Clang 12.0.1 ]" 20 | } 21 | }, 22 | "nbformat": 4, 23 | "nbformat_minor": 4 24 | } 25 | -------------------------------------------------------------------------------- /dev/content/subfolder-1/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # 🪐 👀 Jupyter Viewer Source Dev 6 | -------------------------------------------------------------------------------- /dev/content/subfolder-1/subfolder-1-1/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # 🪐 👀 Jupyter Viewer Source Dev 6 | -------------------------------------------------------------------------------- /dev/content/subfolder-1/subfolder-1-1/test-1-1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/plain": [ 11 | "2" 12 | ] 13 | }, 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "output_type": "execute_result" 17 | } 18 | ], 19 | "source": [ 20 | "1+1" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [] 29 | } 30 | ], 31 | "metadata": { 32 | "kernelspec": { 33 | "display_name": "Python 3 (ipykernel)", 34 | "language": "python", 35 | "name": "python3" 36 | }, 37 | "language_info": { 38 | "codemirror_mode": { 39 | "name": "ipython", 40 | "version": 3 41 | }, 42 | "file_extension": ".py", 43 | "mimetype": "text/x-python", 44 | "name": "python", 45 | "nbconvert_exporter": "python", 46 | "pygments_lexer": "ipython3", 47 | "version": "3.8.10" 48 | } 49 | }, 50 | "nbformat": 4, 51 | "nbformat_minor": 4 52 | } 53 | -------------------------------------------------------------------------------- /dev/content/subfolder-1/test-1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/plain": [ 11 | "2" 12 | ] 13 | }, 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "output_type": "execute_result" 17 | } 18 | ], 19 | "source": [ 20 | "1+1" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [] 29 | } 30 | ], 31 | "metadata": { 32 | "kernelspec": { 33 | "display_name": "Python 3 (ipykernel)", 34 | "language": "python", 35 | "name": "python3" 36 | }, 37 | "language_info": { 38 | "codemirror_mode": { 39 | "name": "ipython", 40 | "version": 3 41 | }, 42 | "file_extension": ".py", 43 | "mimetype": "text/x-python", 44 | "name": "python", 45 | "nbconvert_exporter": "python", 46 | "pygments_lexer": "ipython3", 47 | "version": "3.8.10" 48 | } 49 | }, 50 | "nbformat": 4, 51 | "nbformat_minor": 4 52 | } 53 | -------------------------------------------------------------------------------- /dev/content/subfolder-2/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # 🪐 👀 Jupyter Viewer Source Dev 6 | -------------------------------------------------------------------------------- /dev/content/subfolder-2/test-2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/plain": [ 11 | "2" 12 | ] 13 | }, 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "output_type": "execute_result" 17 | } 18 | ], 19 | "source": [ 20 | "1+1" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [] 29 | } 30 | ], 31 | "metadata": { 32 | "kernelspec": { 33 | "display_name": "Python 3 (ipykernel)", 34 | "language": "python", 35 | "name": "python3" 36 | }, 37 | "language_info": { 38 | "codemirror_mode": { 39 | "name": "ipython", 40 | "version": 3 41 | }, 42 | "file_extension": ".py", 43 | "mimetype": "text/x-python", 44 | "name": "python", 45 | "nbconvert_exporter": "python", 46 | "pygments_lexer": "ipython3", 47 | "version": "3.8.10" 48 | } 49 | }, 50 | "nbformat": 4, 51 | "nbformat_minor": 4 52 | } 53 | -------------------------------------------------------------------------------- /dev/content/subfolder-2/untitled.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/dev/content/subfolder-2/untitled.txt -------------------------------------------------------------------------------- /dev/content/test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/plain": [ 11 | "2" 12 | ] 13 | }, 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "output_type": "execute_result" 17 | } 18 | ], 19 | "source": [ 20 | "1+1" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 3 (ipykernel)", 106 | "language": "python", 107 | "name": "python3" 108 | }, 109 | "language_info": { 110 | "codemirror_mode": { 111 | "name": "ipython", 112 | "version": 3 113 | }, 114 | "file_extension": ".py", 115 | "mimetype": "text/x-python", 116 | "name": "python", 117 | "nbconvert_exporter": "python", 118 | "pygments_lexer": "ipython3", 119 | "version": "3.10.4" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 4 124 | } 125 | -------------------------------------------------------------------------------- /dev/content/tmp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "4cb07156-8003-4cf7-8ab8-f9de6f8c9698", 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.8.12" 29 | } 30 | }, 31 | "nbformat": 4, 32 | "nbformat_minor": 5 33 | } 34 | -------------------------------------------------------------------------------- /dev/content/untitled.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/dev/content/untitled.txt -------------------------------------------------------------------------------- /dev/sh/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # Jupyter Viewer Development Shell 6 | -------------------------------------------------------------------------------- /dev/sh/kill.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright (c) Datalayer, Inc. https://datalayer.io 4 | # Distributed under the terms of the MIT License. 5 | 6 | uname_out="$(uname -s)" 7 | 8 | case "${uname_out}" in 9 | Linux*) export OS=LINUX;; 10 | Darwin*) export OS=MACOS;; 11 | # CYGWIN*) OS=CYGWIND;; 12 | # MINGW*) OS=MINGW;; 13 | *) export OS="UNSUPPORTED:${unameOut}" 14 | esac 15 | 16 | function kill_port() { 17 | case "${OS}" in 18 | LINUX) fuser -k $1/tcp;; 19 | MACOS) lsof -i TCP:$1 | grep LISTEN | awk '{print $2}' | xargs kill -9;; 20 | *) echo "Unsupported operating system ${OS}" 21 | esac 22 | } 23 | 24 | kill_port 3208 25 | kill_port 8080 26 | kill_port 8686 27 | kill_port 8888 28 | -------------------------------------------------------------------------------- /dev/sh/start-jupyter-server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright (c) Datalayer, Inc. https://datalayer.io 4 | # Distributed under the terms of the MIT License. 5 | 6 | echo -e "\x1b[34m\x1b[43mStarting Jupyter Server\x1b[0m" 7 | echo 8 | echo ✨ open http://localhost:8888/api/jupyter/lab?token=60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6 9 | echo ✨ open http://localhost:8888/api/jupyter/api/kernelspecs?token=60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6 10 | echo ✨ open http://localhost:8888/api/jupyter/jupyter_viewer/config?token=60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6 11 | echo 12 | 13 | export CURR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | 15 | $CURR_DIR/kill.sh EXIT 16 | 17 | trap $CURR_DIR/kill.sh EXIT 18 | 19 | jupyter server \ 20 | --config=${CURR_DIR}/../config/jupyter_server_config.py \ 21 | --autoreload 22 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | *.lock 23 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2023 Datalayer, Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Datalayer, Inc. https://datalayer.io 2 | # Distributed under the terms of the MIT License. 3 | 4 | SHELL=/bin/bash 5 | 6 | .DEFAULT_GOAL := default 7 | 8 | CONDA_ACTIVATE=source $$(conda info --base)/etc/profile.d/conda.sh ; conda activate 9 | CONDA_DEACTIVATE=source $$(conda info --base)/etc/profile.d/conda.sh ; conda deactivate 10 | CONDA_REMOVE=source $$(conda info --base)/etc/profile.d/conda.sh ; conda remove -y --all -n 11 | 12 | ENV_NAME=datalayer 13 | 14 | .SILENT: init install 15 | 16 | .PHONY: build publish 17 | 18 | help: ## display this help 19 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 20 | 21 | default: help ## default target is help 22 | 23 | env-rm: 24 | -conda remove -y --all -n ${ENV_NAME} 25 | 26 | env: 27 | -conda env create -f environment.yml 28 | @echo 29 | @echo -------------------------------------------------- 30 | @echo ✨ Datalayer environment is created. 31 | @echo -------------------------------------------------- 32 | @echo 33 | 34 | clean: ## clear 35 | ($(CONDA_ACTIVATE) ${ENV_NAME}; \ 36 | yarn clear ) 37 | 38 | install: ## install 39 | ($(CONDA_ACTIVATE) ${ENV_NAME}; \ 40 | yarn install ) 41 | 42 | start: ## start 43 | ($(CONDA_ACTIVATE) ${ENV_NAME}; \ 44 | yarn start ) 45 | 46 | build: ## build 47 | ($(CONDA_ACTIVATE) ${ENV_NAME}; \ 48 | yarn build ) 49 | 50 | publish: build ## publish 51 | ($(CONDA_ACTIVATE) ${ENV_NAME}; \ 52 | aws s3 rm \ 53 | s3://datalayer-jupyter-viewer/ \ 54 | --recursive \ 55 | --profile datalayer && \ 56 | aws s3 cp \ 57 | ./build \ 58 | s3://datalayer-jupyter-viewer/ \ 59 | --recursive \ 60 | --profile datalayer && \ 61 | aws cloudfront create-invalidation \ 62 | --distribution-id E2SY0NZENZ4YQ2 \ 63 | --paths "/*" \ 64 | --profile datalayer && \ 65 | echo open ✨ https://jupyter-viewer.datalayer.tech ) 66 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | 3 | [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer) 4 | 5 | # Jupyter Viewer Docs 6 | 7 | > Source code for the [Jupyter Viewer Documentation](https://datalayer.io), built with [Docusaurus](https://docusaurus.io). 8 | 9 | ```bash 10 | # Install the dependencies. 11 | make install 12 | ``` 13 | 14 | ```bash 15 | # Local Development: This command starts a local development server and opens up a browser window. 16 | # Most changes are reflected live without having to restart the server. 17 | echo open http://localhost:3000 18 | make start 19 | ``` 20 | 21 | ```bash 22 | # Build: This command generates static content into the `build` directory 23 | # and can be served using any static contents hosting service. 24 | make build 25 | ``` 26 | 27 | ```bash 28 | # Publish if you have karma for. 29 | make publish 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Jupyter Viewer 6 | 7 | > A revisited [NbViewer](https://nbviewer.org) as a modern Web application to view Jupyter notebooks. 8 | 9 | [NbViewer](https://nbviewer.org) is built on top of the [GitHub nbviewer repository](https://github.com/jupyter/nbviewer) and has been useful for many data scientists since years. 10 | 11 | Jupyter Viewer takes over the concept of easy visualisation and brings it to the Web application developers with React.js components to render Notebooks without any Kernel. It is also available as JupyterLab extension. 12 | 13 | You can try on https://viewer.datalayer.tech. 14 | 15 | The documentation lives on https://jupyter-viewer.datalayer.tech. 16 | 17 | If needed, Users can connect the static view to a Kernel and make it executable. 18 | 19 | ## Usage 20 | 21 | This repository packages the [Jupyter UI Viewer](https://jupyter-ui.datalayer.tech/docs/components/viewer) component as a JupyterLab extension. Install and launch with the following commands (you will need Python installed on your machine). 22 | 23 | ```bash 24 | pip install jupyter_viewer 25 | jupyter viewer 26 | ``` 27 | 28 | After launching, enjoy the 👀 views on http://localhost:8888/jupyter_viewer (served by Jupyter). 29 | 30 |
31 | Jupyter Viewer 32 |
33 | 34 | You can view a Notebook hosted on GitHub using the following pattern: `http://localhost:8888/github/account/repo/branch/path` 35 | 36 | ```bash 37 | # For example... 38 | open http://localhost:8888/jupyter_viewer/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 39 | ``` 40 | 41 | ## Develop 42 | 43 | Develop the Web application. 44 | 45 | ```bash 46 | yarn 47 | yarn start 48 | ``` 49 | 50 | ```bash 51 | # Browse the Webpack server. 52 | open http://localhost:3063 53 | open http://localhost:3063/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 54 | ``` 55 | 56 | Develop the Jupyter Server 57 | 58 | ```bash 59 | pip install -e .[test] 60 | jupyter viewer 61 | ``` 62 | 63 | ```bash 64 | # Browse the Jupyter Server. 65 | open http://localhost:8888/jupyter_viewer 66 | open http://localhost:8888/jupyter_viewer/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 67 | ``` 68 | 69 | Develop the JupyterLab extension. 70 | 71 | ```bash 72 | pip install -e .[test] 73 | jupyter labextension develop . --overwrite 74 | jupyter labextension list 75 | jupyter server extension list 76 | yarn jupyterlab 77 | ``` 78 | 79 | ```bash 80 | # Browse JupyterLab. 81 | open http://localhost:8888 82 | ``` 83 | 84 | ## Publish 85 | 86 | ```bash 87 | make publish 88 | open https://viewer.datalayer.tech 89 | open https://viewer.datalayer.tech/github/datalayer/examples/main/pytorch-gpu/pytorch-gpu-example.ipynb 90 | ``` 91 | 92 | ## Releases 93 | 94 | Jupyter Viewer is released as a python package in [PyPI](https://pypi.org/project/jupyter-viewer). 95 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 2 | module.exports = { 3 | title: '🪐 👀 Jupyter Viewer documentation', 4 | tagline: 'Jupyter Viewer documentation', 5 | url: 'https://datalayer.io', 6 | baseUrl: '/', 7 | onBrokenLinks: 'throw', 8 | onBrokenMarkdownLinks: 'warn', 9 | favicon: 'img/favicon.ico', 10 | organizationName: 'datalayer', // Usually your GitHub org/user name. 11 | projectName: 'datalayer', // Usually your repo name. 12 | themeConfig: { 13 | colorMode: { 14 | defaultMode: 'light', 15 | disableSwitch: true, 16 | }, 17 | navbar: { 18 | title: 'Jupyter Viewer Docs', 19 | logo: { 20 | alt: 'Datalayer Logo', 21 | src: 'img/datalayer/logo.svg', 22 | }, 23 | items: [ 24 | { 25 | type: 'doc', 26 | docId: 'index', 27 | position: 'left', 28 | label: 'Jupyter Viewer', 29 | }, 30 | { 31 | href: 'https://www.linkedin.com/company/datalayer', 32 | position: 'right', 33 | className: 'header-linkedin-link', 34 | 'aria-label': 'Linkedin', 35 | }, 36 | { 37 | href: 'https://x.com/DatalayerIO', 38 | position: 'right', 39 | className: 'header-x-link', 40 | 'aria-label': 'X', 41 | }, 42 | { 43 | href: 'https://github.com/datalayer/jupyter-viewer', 44 | position: 'right', 45 | className: 'header-github-link', 46 | 'aria-label': 'GitHub repository', 47 | }, 48 | { 49 | href: 'https://datalayer.io', 50 | position: 'right', 51 | className: 'header-datalayer-io-link', 52 | 'aria-label': 'Datalayer IO', 53 | }, 54 | ], 55 | }, 56 | footer: { 57 | style: 'dark', 58 | links: [ 59 | { 60 | title: 'Docs', 61 | items: [ 62 | { 63 | label: 'Jupyter Viewer', 64 | to: '/docs', 65 | }, 66 | ], 67 | }, 68 | { 69 | title: 'Community', 70 | items: [ 71 | { 72 | label: 'GitHub', 73 | href: 'https://github.com/datalayer', 74 | }, 75 | { 76 | label: 'X', 77 | href: 'https://x.com/datalayerio', 78 | }, 79 | { 80 | label: 'Linkedin', 81 | href: 'https://www.linkedin.com/company/datalayer', 82 | }, 83 | ], 84 | }, 85 | { 86 | title: 'More', 87 | items: [ 88 | { 89 | label: 'Datalayer IO', 90 | href: 'https://datalayer.io', 91 | }, 92 | { 93 | label: 'Datalayer IO', 94 | href: 'https://datalayer.io', 95 | }, 96 | { 97 | label: 'Datalayer Run', 98 | href: 'https://datalayer.run', 99 | }, 100 | { 101 | label: 'Datalayer Tech', 102 | href: 'https://datalayer.tech', 103 | }, 104 | { 105 | label: 'Clouder', 106 | href: 'https://clouder.sh', 107 | }, 108 | { 109 | label: 'Datalayer Blog', 110 | href: 'https://datalayer.blog', 111 | }, 112 | ], 113 | }, 114 | ], 115 | copyright: `Copyright © ${new Date().getFullYear()} Datalayer, Inc.`, 116 | }, 117 | }, 118 | presets: [ 119 | [ 120 | '@docusaurus/preset-classic', 121 | { 122 | docs: { 123 | sidebarPath: require.resolve('./sidebars.js'), 124 | // Please change this to your repo. 125 | editUrl: 126 | 'https://github.com/datalayer/jupyter-viewer/edit/main/', 127 | }, 128 | theme: { 129 | customCss: require.resolve('./src/css/custom.css'), 130 | }, 131 | }, 132 | ], 133 | ], 134 | }; 135 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datalayer/jupyter-viewer-docs", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.4.0", 18 | "@docusaurus/preset-classic": "2.4.0", 19 | "@mdx-js/react": "^1.6.22", 20 | "clsx": "^1.2.1", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "react-modal-image": "2.5.0", 24 | "react-router-dom": "6.22.3" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.5%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | datalayerSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | datalayerSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './HomepageFeatures.module.css'; 4 | 5 | const FeatureList = [ 6 | /* 7 | { 8 | title: 'Easy to Use', 9 | Svg: require('../../static/img/feature_1.svg').default, 10 | description: ( 11 | <> 12 | Datalayer was designed from the ground up to be easily installed and 13 | used to get your data analysis up and running quickly. 14 | 15 | ), 16 | }, 17 | { 18 | title: 'Focus on What Matters', 19 | Svg: require('../../static/img/feature_2.svg').default, 20 | description: ( 21 | <> 22 | Datalayer lets you focus on your work, and we'll do the chores. 23 | 24 | ), 25 | }, 26 | { 27 | title: 'Powered by Open Source', 28 | Svg: require('../../static/img/feature_3.svg').default, 29 | description: ( 30 | <> 31 | Extend or customize your platform to your needs. 32 | 33 | ), 34 | }, 35 | */ 36 | ]; 37 | 38 | function Feature({Svg, title, description}) { 39 | return ( 40 |
41 |
42 | 43 |
44 |
45 |

{title}

46 |

{description}

47 |
48 |
49 | ); 50 | } 51 | 52 | export default function HomepageFeatures() { 53 | return ( 54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => ( 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .features { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .featureSvg { 11 | height: 200px; 12 | width: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/components/HomepageProducts.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './HomepageProducts.module.css'; 4 | 5 | const ProductList = [ 6 | /* 7 | { 8 | title: 'Jupyter Viewer', 9 | Svg: require('../../static/img/product_1.svg').default, 10 | description: ( 11 | <> 12 | Get started by creating a Jupyter platform in the cloud with Jupyter Viewer. You will get Jupyter on Kubernetes with a cloud database and storage bucket to persist your notebooks and datasets. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Jupyter', 18 | Svg: require('../../static/img/product_2.svg').default, 19 | description: ( 20 | <> 21 | If you need more batteries for Jupyter, have a look to our Jupyter components. The components allow you to get the best of Jupyter notebooks, with features like authentication, authorization, React.js user interface, server and kernel instant start, administration... 22 | 23 | ), 24 | }, 25 | { 26 | title: 'Sharebook', 27 | Svg: require('../../static/img/product_3.svg').default, 28 | description: ( 29 | <> 30 | For a truly collaborative and accessible notebook, try Sharebook, a better better literate notebook, with built-in collaboration, accessibility... 31 | 32 | ), 33 | }, 34 | */ 35 | ]; 36 | 37 | function Product({Svg, title, description}) { 38 | return ( 39 |
40 |
41 | 42 |
43 |
44 |

{title}

45 |

{description}

46 |
47 |
48 | ); 49 | } 50 | 51 | export default function HomepageProducts() { 52 | return ( 53 |
54 |
55 |
56 | {ProductList.map((props, idx) => ( 57 | 58 | ))} 59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/components/HomepageProducts.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .product { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .productSvg { 11 | height: 200px; 12 | width: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #25c2a0; 11 | --ifm-color-primary-dark: rgb(33, 175, 144); 12 | --ifm-color-primary-darker: rgb(31, 165, 136); 13 | --ifm-color-primary-darkest: rgb(26, 136, 112); 14 | --ifm-color-primary-light: rgb(70, 203, 174); 15 | --ifm-color-primary-lighter: rgb(102, 212, 189); 16 | --ifm-color-primary-lightest: rgb(146, 224, 208); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | 27 | .header-datalayer-io-link::before { 28 | content: ''; 29 | width: 24px; 30 | height: 24px; 31 | display: flex; 32 | background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' aria-hidden='true' viewBox='0 0 20 20'%3E%3Cpath fill='%232ECC71' d='M0 0h20v4H0zm0 0'/%3E%3Cpath fill='%231ABC9C' d='M0 8h20v4H0zm0 0'/%3E%3Cpath fill='%2316A085' d='M0 16h20v4H0zm0 0'/%3E%3C/svg%3E%0A") 33 | no-repeat; 34 | } 35 | 36 | .header-datalayer-io-link:hover { 37 | opacity: 0.6; 38 | } 39 | 40 | [data-theme='dark'] .header-datalayer-io-link::before { 41 | background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' aria-hidden='true' viewBox='0 0 20 20'%3E%3Cpath fill='%232ECC71' d='M0 0h20v4H0zm0 0'/%3E%3Cpath fill='%231ABC9C' d='M0 8h20v4H0zm0 0'/%3E%3Cpath fill='%2316A085' d='M0 16h20v4H0zm0 0'/%3E%3C/svg%3E%0A") 42 | no-repeat; 43 | } 44 | 45 | .header-github-link::before { 46 | content: ''; 47 | width: 24px; 48 | height: 24px; 49 | display: flex; 50 | background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg viewBox='0 0 80 80' version='1.1' id='svg4' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs id='defs8' /%3E%3Cpath fill='%23959da5' d='M 40,0 C 17.9,0 0,17.900001 0,40 c 0,17.7 11.45,32.65 27.35,37.950001 2,0.35 2.75,-0.85 2.75,-1.9 0,-0.95 -0.05,-4.1 -0.05,-7.45 C 20,70.45 17.4,66.15 16.6,63.9 16.15,62.75 14.2,59.2 12.5,58.25 11.1,57.5 9.1,55.65 12.45,55.600001 c 3.15,-0.05 5.4,2.899999 6.15,4.1 3.6,6.05 9.35,4.35 11.65,3.3 0.35,-2.6 1.4,-4.35 2.55,-5.35 -8.9,-1 -18.2,-4.45 -18.2,-19.75 0,-4.35 1.55,-7.95 4.1,-10.75 -0.4,-1 -1.8,-5.1 0.4,-10.6 0,0 3.35,-1.05 11,4.1 3.2,-0.9 6.6,-1.35 10,-1.35 3.4,0 6.8,0.45 10,1.35 7.65,-5.2 11,-4.1 11,-4.1 2.2,5.5 0.8,9.6 0.4,10.6 2.55,2.8 4.1,6.35 4.1,10.75 0,15.35 -9.35,18.75 -18.25,19.75 1.45,1.25 2.7,3.65 2.7,7.4 0,5.349999 -0.05,9.65 -0.05,11 0,1.05 0.75,2.3 2.75,1.9 A 40.065,40.065 0 0 0 80,40 C 80,17.900001 62.1,0 40,0 Z' id='path2' style='stroke-width:5' /%3E%3C/svg%3E%0A") 51 | no-repeat; 52 | } 53 | 54 | .header-github-link:hover { 55 | opacity: 0.6; 56 | } 57 | 58 | [data-theme='dark'] .header-github-link::before { 59 | background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg viewBox='0 0 80 80' version='1.1' id='svg4' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs id='defs8' /%3E%3Cpath fill='%23959da5' d='M 40,0 C 17.9,0 0,17.900001 0,40 c 0,17.7 11.45,32.65 27.35,37.950001 2,0.35 2.75,-0.85 2.75,-1.9 0,-0.95 -0.05,-4.1 -0.05,-7.45 C 20,70.45 17.4,66.15 16.6,63.9 16.15,62.75 14.2,59.2 12.5,58.25 11.1,57.5 9.1,55.65 12.45,55.600001 c 3.15,-0.05 5.4,2.899999 6.15,4.1 3.6,6.05 9.35,4.35 11.65,3.3 0.35,-2.6 1.4,-4.35 2.55,-5.35 -8.9,-1 -18.2,-4.45 -18.2,-19.75 0,-4.35 1.55,-7.95 4.1,-10.75 -0.4,-1 -1.8,-5.1 0.4,-10.6 0,0 3.35,-1.05 11,4.1 3.2,-0.9 6.6,-1.35 10,-1.35 3.4,0 6.8,0.45 10,1.35 7.65,-5.2 11,-4.1 11,-4.1 2.2,5.5 0.8,9.6 0.4,10.6 2.55,2.8 4.1,6.35 4.1,10.75 0,15.35 -9.35,18.75 -18.25,19.75 1.45,1.25 2.7,3.65 2.7,7.4 0,5.349999 -0.05,9.65 -0.05,11 0,1.05 0.75,2.3 2.75,1.9 A 40.065,40.065 0 0 0 80,40 C 80,17.900001 62.1,0 40,0 Z' id='path2' style='stroke-width:5' /%3E%3C/svg%3E%0A") 60 | no-repeat; 61 | } 62 | 63 | .header-x-link::before { 64 | content: ''; 65 | width: 24px; 66 | height: 24px; 67 | display: flex; 68 | background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 1227' fill='rgb(149, 157, 165)'%3E%3Cpath d='M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z' /%3E%3C/svg%3E%0A") 69 | no-repeat; 70 | } 71 | 72 | .header-x-link:hover { 73 | opacity: 0.6; 74 | } 75 | 76 | [data-theme='dark'] .header-x-link::before { 77 | background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 1227' fill='rgb(149, 157, 165)'%3E%3Cpath d='M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z' /%3E%3C/svg%3E%0A") 78 | no-repeat; 79 | } 80 | 81 | .header-linkedin-link::before { 82 | content: ''; 83 | width: 24px; 84 | height: 24px; 85 | display: flex; 86 | background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 19 18'%3E%3Cpath d='M3.94 2A2 2 0 1 1 2 0a2 2 0 0 1 1.94 2zM4 5.48H0V18h4zm6.32 0H6.34V18h3.94v-6.57c0-3.66 4.77-4 4.77 0V18H19v-7.93c0-6.17-7.06-5.94-8.72-2.91z' fill='rgb(149, 157, 165)'/%3E%3C/svg%3E") 87 | no-repeat; 88 | } 89 | 90 | .header-linkedin-link:hover { 91 | opacity: 0.6; 92 | } 93 | 94 | [data-theme='dark'] .header-linkedin-link::before { 95 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 19 18'%3E%3Cpath d='M3.94 2A2 2 0 1 1 2 0a2 2 0 0 1 1.94 2zM4 5.48H0V18h4zm6.32 0H6.34V18h3.94v-6.57c0-3.66 4.77-4 4.77 0V18H19v-7.93c0-6.17-7.06-5.94-8.72-2.91z' fill='rgb(149, 157, 165)'/%3E%3C/svg%3E") 96 | no-repeat; 97 | } 98 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '../components/HomepageFeatures'; 8 | import HomepageProducts from '../components/HomepageProducts'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

{siteConfig.title} alpha

16 |

{siteConfig.tagline}

17 |
18 | 21 | Get Started 22 | 23 |
24 |
25 |
26 | ); 27 | } 28 | 29 | export default function Home() { 30 | const {siteConfig} = useDocusaurusContext(); 31 | return ( 32 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | 27 | .tag { 28 | font-size: small; 29 | padding: 4px; 30 | border-radius: 5px; 31 | border-width: thick; 32 | border-color: red; 33 | background: orange; 34 | } 35 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/src/pages/testimonials.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from '@theme/Layout'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import HomepageFeatures from '../components/HomepageFeatures'; 5 | 6 | export default function Home() { 7 | const {siteConfig} = useDocusaurusContext(); 8 | return ( 9 | 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /docs/static/img/datalayer/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/docs/static/img/datalayer/logo.png -------------------------------------------------------------------------------- /docs/static/img/datalayer/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/feature_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | Startup_SVG 26 | 27 | 28 | 29 | 54 | 56 | 58 | 59 | Startup_SVG 61 | 68 | 75 | 82 | 89 | 93 | 98 | 103 | 110 | 115 | 122 | 129 | 133 | 139 | 143 | 149 | 153 | 157 | 161 | 165 | 169 | 176 | 177 | -------------------------------------------------------------------------------- /docs/static/img/feature_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | Marketing_strategy_SVG 26 | 27 | 28 | 29 | 55 | 57 | 59 | 60 | Marketing_strategy_SVG 62 | 66 | 70 | 74 | 79 | 83 | 91 | 95 | 99 | 103 | 108 | 116 | 124 | 129 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/static/img/feature_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | Web_SVG 26 | 27 | 28 | 29 | 54 | 56 | 58 | 65 | 69 | 74 | 75 | 77 | 83 | 84 | 85 | Web_SVG 87 | 91 | 95 | 99 | 103 | 107 | 113 | 119 | 124 | 128 | 129 | 133 | 140 | 141 | -------------------------------------------------------------------------------- /docs/static/img/product_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | Cloud_database_SVG 26 | 27 | 28 | 29 | 54 | 56 | 58 | 61 | 68 | 69 | 72 | 79 | 80 | 83 | 90 | 91 | 94 | 101 | 102 | 103 | Cloud_database_SVG 105 | 110 | 115 | 120 | 125 | 126 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Datalayer, Inc. https://datalayer.io 2 | # Distributed under the terms of the MIT License. 3 | 4 | name: datalayer 5 | channels: 6 | - conda-forge 7 | dependencies: 8 | - jq 9 | - nodejs=18.15.0 10 | - pip 11 | - python=3.11 12 | - yarn=3.5.0 13 | - pip: 14 | - build 15 | - hatch 16 | - jupyter-packaging 17 | - jupyterlab==4.1.0b0 18 | -------------------------------------------------------------------------------- /hatch_build.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | 4 | from subprocess import check_call 5 | 6 | import shutil 7 | 8 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 9 | 10 | 11 | here = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | 14 | def build_javascript(): 15 | check_call( 16 | ['yarn', 'install'], 17 | cwd=here, 18 | ) 19 | check_call( 20 | ['yarn', 'build:webpack', '--mode=production'], 21 | cwd=here, 22 | ) 23 | for file in glob.glob(r'./dist/*.js'): 24 | shutil.copy( 25 | file, 26 | './jupyter_viewer/static/' 27 | ) 28 | 29 | 30 | class JupyterBuildHook(BuildHookInterface): 31 | def initialize(self, version, build_data): 32 | if self.target_name == 'editable': 33 | build_javascript() 34 | elif self.target_name == 'wheel': 35 | build_javascript() 36 | elif self.target_name == 'sdist': 37 | build_javascript() 38 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "jupyter_viewer", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyter_viewer" 5 | } 6 | -------------------------------------------------------------------------------- /jest-playwright.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launchOptions: { 3 | headless: true 4 | }, 5 | contextOptions: { 6 | ignoreHTTPSErrors: true, 7 | viewport: { 8 | width: 1920, 9 | height: 1080 10 | } 11 | }, 12 | browsers: [ 13 | "chromium", 14 | // "firefox", 15 | // "webkit" 16 | ], 17 | devices: [] 18 | } 19 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Jupyter Development Team. 3 | * Distributed under the terms of the Modified BSD License. 4 | */ 5 | 6 | const func = require('@jupyterlab/testutils/lib/jest-config'); 7 | const jlabConfig = func(__dirname); 8 | 9 | const esModules = [ 10 | '@codemirror', 11 | '@jupyterlab', 12 | '@jupyter', 13 | 'lib0', 14 | 'lib0/websocket', 15 | 'nanoid', 16 | 'vscode\\-ws\\-jsonrpc', 17 | 'y\\-protocols', 18 | 'y\\-websocket', 19 | 'y\\-webrtc', 20 | 'yjs' 21 | ].join('|'); 22 | /* 23 | const { 24 | moduleFileExtensions, 25 | moduleNameMapper, 26 | preset, 27 | setupFilesAfterEnv, 28 | setupFiles, 29 | testPathIgnorePatterns, 30 | transform 31 | } = jlabConfig; 32 | */ 33 | module.exports = { 34 | /* 35 | ...jlabConfig, 36 | moduleFileExtensions, 37 | moduleNameMapper, 38 | preset, 39 | setupFilesAfterEnv, 40 | setupFiles, 41 | testPathIgnorePatterns, 42 | transform, 43 | automock: false, 44 | */ 45 | // collectCoverageFrom: [ 46 | // 'src/**/*.{ts,tsx}', 47 | // '!src/**/*.d.ts', 48 | // '!src/**/.ipynb_checkpoints/*' 49 | // ], 50 | /* 51 | coverageDirectory: 'coverage', 52 | coverageReporters: ['lcov', 'text'], 53 | globals: { 54 | 'ts-jest': { 55 | tsconfig: 'tsconfig.json' 56 | } 57 | }, 58 | */ 59 | testRegex: "(/src/__tests__/.*(test|spec))\\.[jt]sx?$", 60 | transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`], 61 | transform: { 62 | "^.+\\.(ts)$": "ts-jest", 63 | }, 64 | preset: "jest-playwright-preset", 65 | } 66 | -------------------------------------------------------------------------------- /jupyter-config/nb-config/jupyter_viewer.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "jupyter_viewer": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/server-config/jupyter_viewer.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyter_viewer": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | 3 | from ._version import __version__ 4 | from .serverapplication import JupyterViewerExtensionApp 5 | 6 | 7 | def _jupyter_server_extension_points() -> List[Dict[str, Any]]: 8 | return [{ 9 | "module": "jupyter_viewer", 10 | "app": JupyterViewerExtensionApp, 11 | }] 12 | 13 | 14 | def _jupyter_labextension_paths() -> List[Dict[str, str]]: 15 | return [{ 16 | "src": "labextension", 17 | "dest": "@datalayer/jupyter-viewer" 18 | }] 19 | -------------------------------------------------------------------------------- /jupyter_viewer/__main__.py: -------------------------------------------------------------------------------- 1 | """The main for Jupyter Viewer.""" 2 | 3 | from .serverapplication import main 4 | 5 | if __name__ == "__main__": 6 | main() 7 | -------------------------------------------------------------------------------- /jupyter_viewer/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/jupyter_viewer/handlers/__init__.py -------------------------------------------------------------------------------- /jupyter_viewer/handlers/base.py: -------------------------------------------------------------------------------- 1 | """Base handler.""" 2 | 3 | from jupyter_server.base.handlers import JupyterHandler 4 | from jupyter_server.extension.handler import ExtensionHandlerMixin, ExtensionHandlerJinjaMixin 5 | 6 | 7 | # pylint: disable=W0223 8 | class BaseTemplateHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): 9 | """The Base handler for the templates.""" 10 | -------------------------------------------------------------------------------- /jupyter_viewer/handlers/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/jupyter_viewer/handlers/config/__init__.py -------------------------------------------------------------------------------- /jupyter_viewer/handlers/config/handler.py: -------------------------------------------------------------------------------- 1 | """Config handler.""" 2 | 3 | import json 4 | 5 | import tornado 6 | 7 | from jupyter_server.base.handlers import APIHandler 8 | from jupyter_server.extension.handler import ExtensionHandlerMixin 9 | 10 | from ..._version import __version__ 11 | 12 | 13 | # pylint: disable=W0223 14 | class ConfigHandler(ExtensionHandlerMixin, APIHandler): 15 | """The handler for configurations.""" 16 | 17 | @tornado.web.authenticated 18 | def get(self): 19 | """Returns the configurations of the server extensions.""" 20 | res = json.dumps({ 21 | "extension": "jupyter_viewer", 22 | "version": __version__ 23 | }) 24 | self.finish(res) 25 | -------------------------------------------------------------------------------- /jupyter_viewer/handlers/index/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalayer/jupyter-viewer/949706460070f5bfe88c778f4efcf93659a4f265/jupyter_viewer/handlers/index/__init__.py -------------------------------------------------------------------------------- /jupyter_viewer/handlers/index/handler.py: -------------------------------------------------------------------------------- 1 | """Index handler.""" 2 | 3 | import tornado 4 | 5 | from ..base import BaseTemplateHandler 6 | 7 | 8 | # pylint: disable=W0223 9 | class IndexHandler(BaseTemplateHandler): 10 | """The handler for the index.""" 11 | 12 | @tornado.web.authenticated 13 | def get(self, path = ""): 14 | """The index page.""" 15 | self.write(self.render_template("index.html")) 16 | -------------------------------------------------------------------------------- /jupyter_viewer/serverapplication.py: -------------------------------------------------------------------------------- 1 | """The Jupyter Viewer Server application.""" 2 | 3 | import os 4 | 5 | from traitlets import Unicode 6 | 7 | from jupyter_server.utils import url_path_join 8 | from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin 9 | 10 | from ._version import __version__ 11 | 12 | from .handlers.index.handler import IndexHandler 13 | from .handlers.config.handler import ConfigHandler 14 | 15 | 16 | DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "./static") 17 | 18 | DEFAULT_TEMPLATE_FILES_PATH = os.path.join(os.path.dirname(__file__), "./templates") 19 | 20 | 21 | class JupyterViewerExtensionApp(ExtensionAppJinjaMixin, ExtensionApp): 22 | """The Jupyter Viewer Server extension.""" 23 | 24 | name = "jupyter_viewer" 25 | 26 | extension_url = "/jupyter_viewer" 27 | 28 | load_other_extensions = True 29 | 30 | static_paths = [DEFAULT_STATIC_FILES_PATH] 31 | template_paths = [DEFAULT_TEMPLATE_FILES_PATH] 32 | 33 | config_a = Unicode("", config=True, help="Config A example.") 34 | config_b = Unicode("", config=True, help="Config B example.") 35 | config_c = Unicode("", config=True, help="Config C example.") 36 | 37 | def initialize_settings(self): 38 | self.log.debug("Jupyter Viewer Config {}".format(self.config)) 39 | 40 | def initialize_templates(self): 41 | self.serverapp.jinja_template_vars.update({"jupyter_viewer_version" : __version__}) 42 | 43 | def initialize_handlers(self): 44 | self.log.debug("Jupyter Viewer Config {}".format(self.settings['jupyter_viewer_jinja2_env'])) 45 | handlers = [ 46 | (url_path_join(self.name, "config"), ConfigHandler), 47 | (r"/jupyter_viewer/(.+)$", IndexHandler), 48 | (r"/jupyter_viewer/?", IndexHandler), 49 | ] 50 | self.handlers.extend(handlers) 51 | 52 | 53 | # ----------------------------------------------------------------------------- 54 | # Main entry point 55 | # ----------------------------------------------------------------------------- 56 | 57 | main = launch_new_instance = JupyterViewerExtensionApp.launch_instance 58 | -------------------------------------------------------------------------------- /jupyter_viewer/static/README.md: -------------------------------------------------------------------------------- 1 | [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) 2 | -------------------------------------------------------------------------------- /jupyter_viewer/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 🪐 👀 Jupyter Viewer 6 | 12 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /jupyter_viewer/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Python unit tests for jupyter_viewer.""" 2 | -------------------------------------------------------------------------------- /jupyter_viewer/tests/test_handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .._version import __version__ 4 | 5 | 6 | async def test_config(jp_fetch): 7 | # When 8 | response = await jp_fetch("jupyter_viewer", "config") 9 | # Then 10 | assert response.code == 200 11 | payload = json.loads(response.body) 12 | assert payload == { 13 | "extension": "jupyter_viewer", 14 | "version": __version__ 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datalayer/jupyter-viewer", 3 | "version": "0.0.8", 4 | "description": "🪐 👀 Jupyter Viewer.", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension", 9 | "nbviewer", 10 | "viewer", 11 | "datalayer" 12 | ], 13 | "homepage": "https://github.com/datalayer/jupyter-viewer", 14 | "bugs": { 15 | "url": "https://github.com/datalayer/jupyter-viewer/issues" 16 | }, 17 | "license": "BSD-3-Clause", 18 | "author": { 19 | "name": "Datalayer", 20 | "email": "info@datalayer.io" 21 | }, 22 | "files": [ 23 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 24 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 25 | "schema/*.json" 26 | ], 27 | "main": "lib/index.js", 28 | "types": "lib/index.d.ts", 29 | "style": "style/index.css", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/datalayer/jupyter-viewer.git" 33 | }, 34 | "scripts": { 35 | "build": "jlpm build:lib && jlpm build:labextension:dev", 36 | "build:labextension": "jupyter labextension build .", 37 | "build:labextension:dev": "jupyter labextension build --development True .", 38 | "build:lib": "tsc", 39 | "build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension", 40 | "build:webpack": "webpack-cli build", 41 | "build:webpack:prod": "cross-env IS_WEBPACK_PROD=true webpack-cli build", 42 | "clean": "jlpm clean:lib", 43 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 44 | "clean:labextension": "rimraf jupyter_viewer/labextension", 45 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 46 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 47 | "eslint": "jlpm eslint:check --fix", 48 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 49 | "install:extension": "jlpm build", 50 | "jupyterlab": "run-p -c 'jupyterlab:*'", 51 | "jupyterlab:server": "jupyter server --config=./dev/config/jupyter_server_config.py", 52 | "jupyterlab:webpack": "webpack serve", 53 | "kill": "./dev/sh/kill.sh || true", 54 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 55 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 56 | "prettier": "jlpm prettier:base --write --list-different", 57 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 58 | "prettier:check": "jlpm prettier:base --check", 59 | "start": "run-p -c 'start:*'", 60 | "start:jupyter-server": "cd ./dev/sh && ./start-jupyter-server.sh", 61 | "start:webpack": "webpack serve", 62 | "stylelint": "jlpm stylelint:check --fix", 63 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 64 | "test": "jest --coverage", 65 | "typedoc": "typedoc ./src", 66 | "watch": "run-p watch:src watch:labextension", 67 | "watch:labextension": "jupyter labextension watch .", 68 | "watch:src": "tsc -w" 69 | }, 70 | "dependencies": { 71 | "@datalayer/icons-react": "^0.3.2", 72 | "@datalayer/jupyter-react": "^0.15.0", 73 | "@datalayer/primer-addons": "^0.3.1", 74 | "@jupyterlab/application": "^4.0.0", 75 | "@jupyterlab/coreutils": "^6.0.0", 76 | "@jupyterlab/services": "^7.0.0", 77 | "@jupyterlab/settingregistry": "^4.0.0", 78 | "react": "^18.2.0", 79 | "react-dom": "^18.2.0", 80 | "react-is": "^18.2.0", 81 | "react-layout-masonry": "^1.1.0", 82 | "react-router-dom": "6.22.3", 83 | "styled-components": "^5.3.10", 84 | "zustand": "^4.4.1" 85 | }, 86 | "devDependencies": { 87 | "@babel/core": "^7.21.0", 88 | "@babel/plugin-proposal-class-properties": "^7.18.6", 89 | "@babel/preset-env": "^7.20.2", 90 | "@babel/preset-react": "^7.18.6", 91 | "@babel/preset-typescript": "^7.21.0", 92 | "@jupyterlab/builder": "^4.0.0", 93 | "@jupyterlab/launcher": "^4.0.0", 94 | "@jupyterlab/testutils": "^4.0.0", 95 | "@types/jest": "^29.4.0", 96 | "@types/node": "^18.15.3", 97 | "@types/react": "^18.2.12", 98 | "@types/react-dom": "^18.2.5", 99 | "@types/styled-components": "^5.1.26", 100 | "@types/webpack-env": "^1.18.2", 101 | "@typescript-eslint/eslint-plugin": "^4.8.1", 102 | "@typescript-eslint/parser": "^4.8.1", 103 | "babel-loader": "^9.1.2", 104 | "cross-env": "^7.0.3", 105 | "css-loader": "^6.9.1", 106 | "eslint": "^7.14.0", 107 | "eslint-config-prettier": "^6.15.0", 108 | "eslint-plugin-prettier": "^3.1.4", 109 | "html-webpack-plugin": "^5.3.1", 110 | "jest": "^29.4.3", 111 | "jest-playwright-preset": "^3.0.1", 112 | "mkdirp": "^1.0.3", 113 | "npm-run-all": "^4.1.5", 114 | "playwright": "^1.31.1", 115 | "prettier": "^2.1.1", 116 | "raw-loader": "^4.0.2", 117 | "rimraf": "^3.0.2", 118 | "stream": "^0.0.2", 119 | "stream-browserify": "^2.0.2", 120 | "style-loader": "^2.0.0", 121 | "stylelint": "^14.3.0", 122 | "stylelint-config-prettier": "^9.0.4", 123 | "stylelint-config-recommended": "^6.0.0", 124 | "stylelint-config-standard": "^24.0.0", 125 | "stylelint-prettier": "^2.0.0", 126 | "svg-url-loader": "^7.1.1", 127 | "ts-jest": "^29.0.5", 128 | "typescript": "~5.0.3", 129 | "webpack": "^5.74.0", 130 | "webpack-bundle-analyzer": "^4.5.0", 131 | "webpack-cli": "^4.10.0", 132 | "webpack-dev-server": "^4.9.3" 133 | }, 134 | "resolutions": { 135 | "@jupyter-widgets/base": "6.0.6", 136 | "@jupyter-widgets/controls": "5.0.7", 137 | "@jupyter-widgets/html-manager": "1.0.9", 138 | "@jupyter-widgets/jupyterlab-manager": "5.0.9", 139 | "@jupyter-widgets/output": "6.0.6", 140 | "@jupyterlab/application": "4.1.0", 141 | "@jupyterlab/apputils": "4.2.0", 142 | "@jupyterlab/attachments": "4.1.0", 143 | "@jupyterlab/cells": "4.1.0", 144 | "@jupyterlab/codeeditor": "4.1.0", 145 | "@jupyterlab/codemirror": "4.1.0", 146 | "@jupyterlab/completer": "4.1.0", 147 | "@jupyterlab/console": "4.1.0", 148 | "@jupyterlab/coreutils": "6.1.0", 149 | "@jupyterlab/docmanager": "4.1.0", 150 | "@jupyterlab/docregistry": "4.1.0", 151 | "@jupyterlab/documentsearch": "4.1.0", 152 | "@jupyterlab/filebrowser": "4.1.0", 153 | "@jupyterlab/fileeditor": "4.1.0", 154 | "@jupyterlab/inspector": "4.1.0", 155 | "@jupyterlab/javascript-extension": "4.1.0", 156 | "@jupyterlab/json-extension": "4.1.0", 157 | "@jupyterlab/launcher": "4.1.0", 158 | "@jupyterlab/lsp": "4.1.0", 159 | "@jupyterlab/mainmenu": "4.1.0", 160 | "@jupyterlab/markdownviewer": "4.1.0", 161 | "@jupyterlab/markedparser-extension": "4.1.0", 162 | "@jupyterlab/mathjax-extension": "4.1.0", 163 | "@jupyterlab/nbconvert-css": "4.1.0", 164 | "@jupyterlab/nbformat": "4.1.0", 165 | "@jupyterlab/notebook": "4.1.0", 166 | "@jupyterlab/observables": "5.1.0", 167 | "@jupyterlab/outputarea": "4.1.0", 168 | "@jupyterlab/rendermime": "4.1.0", 169 | "@jupyterlab/rendermime-extension": "4.1.0", 170 | "@jupyterlab/rendermime-interfaces": "3.9.0", 171 | "@jupyterlab/services": "7.1.0", 172 | "@jupyterlab/settingregistry": "4.1.0", 173 | "@jupyterlab/statedb": "4.1.0", 174 | "@jupyterlab/terminal": "4.1.0", 175 | "@jupyterlab/theme-dark-extension": "4.1.0", 176 | "@jupyterlab/theme-light-extension": "4.1.0", 177 | "@jupyterlab/translation": "4.1.0", 178 | "@jupyterlab/ui-components": "4.1.0", 179 | "@jupyterlite/server": "^0.4.0", 180 | "@jupyterlite/server-extension": "^0.4.0", 181 | "@jupyter/ydoc": "1.1.1", 182 | "@lumino/algorithm": "2.0.1", 183 | "@lumino/application": "2.2.0", 184 | "@lumino/collections": "2.0.1", 185 | "@lumino/commands": "2.2.0", 186 | "@lumino/coreutils": "2.1.1", 187 | "@lumino/default-theme": "2.1.2", 188 | "@lumino/disposable": "2.1.1", 189 | "@lumino/domutils": "2.0.1", 190 | "@lumino/dragdrop": "2.1.2", 191 | "@lumino/keyboard": "2.0.1", 192 | "@lumino/messaging": "2.0.1", 193 | "@lumino/polling": "2.1.1", 194 | "@lumino/properties": "2.0.1", 195 | "@lumino/signaling": "2.1.1", 196 | "@lumino/virtualdom": "2.0.1", 197 | "@lumino/widgets": "2.3.1", 198 | "@rjsf/core": "5.3.0", 199 | "@rjsf/utils": "5.3.0", 200 | "@rjsf/validator-ajv6": "5.3.0", 201 | "@rjsf/validator-ajv8": "5.3.0", 202 | "@types/react": "18.2.12", 203 | "@types/react-dom": "18.2.5", 204 | "@jest/core": "29.4.3", 205 | "@jest/transform": "29.4.3", 206 | "jest": "29.4.3", 207 | "jest-environment-jsdom": "29.4.3", 208 | "ts-jest": "29.0.5", 209 | "html-webpack-plugin": "5.3.1", 210 | "htmlparser2": "8.0.1", 211 | "react": "18.2.0", 212 | "react-dom": "18.2.0", 213 | "styled-components": "5.3.10", 214 | "typescript": "5.0.3", 215 | "webpack": "5.74.0", 216 | "webpack-cli": "4.10.0", 217 | "webpack-dev-server": "4.9.3", 218 | "y-websocket": "1.4.5", 219 | "yjs": "13.5.47", 220 | "zustand": "4.4.1" 221 | }, 222 | "sideEffects": [ 223 | "style/*.css", 224 | "style/index.js" 225 | ], 226 | "styleModule": "style/index.js", 227 | "publishConfig": { 228 | "access": "public" 229 | }, 230 | "jupyterlab": { 231 | "discovery": { 232 | "server": { 233 | "managers": [ 234 | "pip" 235 | ], 236 | "base": { 237 | "name": "jupyter-viewer" 238 | } 239 | } 240 | }, 241 | "extension": "lib/jupyterlab/index.js", 242 | "outputDir": "jupyter_viewer/labextension", 243 | "schemaDir": "schema", 244 | "webpackConfig": "./webpack.lab-config.js" 245 | }, 246 | "jupyter-releaser": { 247 | "hooks": { 248 | "before-build-npm": [ 249 | "python -m pip install jupyterlab==4.1.0b0", 250 | "jlpm" 251 | ], 252 | "before-build-python": [ 253 | "jlpm clean:all" 254 | ] 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 🪐 👀 Jupyter Viewer 6 | 12 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling==1.21.1", "jupyterlab==4.1.0b0", "hatch-nodejs-version"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "jupyter_viewer" 7 | readme = "README.md" 8 | license = { file = "LICENSE" } 9 | requires-python = ">=3.9" 10 | classifiers = [ 11 | "Framework :: Jupyter", 12 | "Framework :: Jupyter :: JupyterLab", 13 | "Framework :: Jupyter :: JupyterLab :: 4", 14 | "Framework :: Jupyter :: JupyterLab :: Extensions", 15 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 16 | "Intended Audience :: Developers", 17 | "Intended Audience :: System Administrators", 18 | "Intended Audience :: Science/Research", 19 | "License :: OSI Approved :: BSD License", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.9", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | ] 26 | 27 | dependencies = [ 28 | "jupyter_server>=2,<3" 29 | ] 30 | dynamic = ["version", "description", "authors", "urls", "keywords"] 31 | 32 | [project.optional-dependencies] 33 | test = [ 34 | "coverage", 35 | "pytest", 36 | "pytest-asyncio", 37 | "pytest-cov", 38 | "pytest-jupyter", 39 | "pytest-tornasync", 40 | ] 41 | 42 | [project.scripts] 43 | jupyter-viewer = "jupyter_viewer.serverapplication:main" 44 | 45 | [tool.hatch.version] 46 | source = "nodejs" 47 | 48 | [tool.hatch.build] 49 | artifacts = [ 50 | "jupyter_viewer/static", 51 | "jupyter_viewer/templates" 52 | ] 53 | 54 | [tool.hatch.build.hooks.custom] 55 | 56 | [tool.hatch.metadata.hooks.nodejs] 57 | fields = ["description", "authors", "urls"] 58 | 59 | [tool.hatch.build.targets.sdist] 60 | artifacts = ["jupyter_viewer/labextension"] 61 | exclude = [".github", "binder", ".yarn"] 62 | 63 | [tool.hatch.build.targets.wheel.shared-data] 64 | "jupyter_viewer/labextension" = "share/jupyter/labextensions/@datalayer/jupyter-viewer" 65 | "install.json" = "share/jupyter/labextensions/@datalayer/jupyter-viewer/install.json" 66 | "jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d" 67 | "jupyter-config/nb-config" = "etc/jupyter/jupyter_notebook_config.d" 68 | 69 | [tool.hatch.build.hooks.version] 70 | path = "jupyter_viewer/_version.py" 71 | 72 | [tool.hatch.build.hooks.jupyter-builder] 73 | dependencies = ["hatch-jupyter-builder>=0.5"] 74 | build-function = "hatch_jupyter_builder.npm_builder" 75 | ensured-targets = [ 76 | "jupyter_viewer/labextension/static/style.js", 77 | "jupyter_viewer/labextension/package.json", 78 | ] 79 | skip-if-exists = ["jupyter_viewer/labextension/static/style.js"] 80 | 81 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 82 | build_cmd = "build:prod" 83 | npm = ["jlpm"] 84 | 85 | [tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] 86 | build_cmd = "install:extension" 87 | npm = ["jlpm"] 88 | source_dir = "src" 89 | build_dir = "jupyter_viewer/labextension" 90 | 91 | [tool.jupyter-releaser.options] 92 | version_cmd = "hatch version" 93 | -------------------------------------------------------------------------------- /schema/notebook.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.shortcuts": [], 3 | "title": "@datalayer/jupyter-viewer:notebook", 4 | "description": "@datalayer/jupyter-viewer notebook settings.", 5 | "type": "object", 6 | "properties": {}, 7 | "additionalProperties": false 8 | } 9 | -------------------------------------------------------------------------------- /schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Jupyter Viewer", 3 | "description": "Jupyter Viewer settings.", 4 | "type": "object", 5 | "properties": { 6 | "showInLauncher": { 7 | "title": "Show Jupyter Viewer in the launcher", 8 | "description": "Show Jupyter Viewer in the launcher", 9 | "default": true, 10 | "type": "boolean" 11 | } 12 | }, 13 | "additionalProperties": false, 14 | "jupyter.lab.shortcuts": [] 15 | } 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __import__('setuptools').setup() 2 | -------------------------------------------------------------------------------- /src/Viewer.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useNavigate } from "react-router-dom"; 3 | import { ThemeProvider, BaseStyles, Box, UnderlineNav, Pagehead, Heading, Link } from '@primer/react'; 4 | import { JupyterLabAppAdapter } from '@datalayer/jupyter-react'; 5 | import { DatalayerGreenIcon } from '@datalayer/icons-react'; 6 | import { ViewerForm, ViewerExamples, ViewerAbout } from './views'; 7 | import { requestAPI } from './jupyterlab/handler'; 8 | 9 | export type JupyterViewerProps = { 10 | adapter?: JupyterLabAppAdapter; 11 | } 12 | 13 | const JupyterViewer = (props: JupyterViewerProps) => { 14 | const [tab, setTab] = useState(1); 15 | const [version, setVersion] = useState(''); 16 | const navigate = useNavigate(); 17 | useEffect(() => { 18 | requestAPI('config') 19 | .then(data => { 20 | setVersion(data.version); 21 | }) 22 | .catch(reason => { 23 | console.error( 24 | `Error while accessing the jupyter server jupyter_viewer extension.\n${reason}` 25 | ); 26 | }); 27 | }); 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | navigate('/')}> 36 | 37 | 38 | 39 | 40 | Jupyter Viewer 41 | 42 | 43 | 44 | 45 | 46 | 47 | {e.preventDefault(); setTab(1);}}> 48 | Viewer 49 | 50 | {e.preventDefault(); setTab(2);}}> 51 | Examples 52 | 53 | {e.preventDefault(); setTab(3);}}> 54 | About 55 | 56 | 57 | 58 | 59 | {tab === 1 && } 60 | {tab === 2 && } 61 | {tab === 3 && } 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | 70 | export default JupyterViewer; 71 | -------------------------------------------------------------------------------- /src/ViewerApp.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { createRoot } from 'react-dom/client'; 4 | import ViewerRoutes from './ViewerRoutes'; 5 | 6 | import "./../style/index.css"; 7 | 8 | const div = document.createElement('div'); 9 | document.body.appendChild(div); 10 | const root = createRoot(div) 11 | 12 | if (module.hot) { 13 | module.hot.accept('./ViewerRoutes', () => { 14 | const ViewerRoutes = require('./ViewerRoutes').default; 15 | root.render(); 16 | }) 17 | } 18 | 19 | root.render(); 20 | -------------------------------------------------------------------------------- /src/ViewerJupyterLab.tsx: -------------------------------------------------------------------------------- 1 | import { Jupyter, JupyterLabApp } from '@datalayer/jupyter-react'; 2 | 3 | import * as lightThemeExtension from '@jupyterlab/theme-light-extension'; 4 | import * as collaborationExtension from '@jupyter/collaboration-extension'; 5 | import * as viewerExtension from './jupyterlab/index'; 6 | 7 | const JupyterLabComponent = () => ( 8 | 17 | ) 18 | 19 | export const JupyterViewerJupyterLab = () => ( 20 | 21 | 22 | 23 | ) 24 | 25 | export default JupyterViewerJupyterLab; 26 | -------------------------------------------------------------------------------- /src/ViewerJupyterLabApp.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { createRoot } from 'react-dom/client'; 4 | import JupyterViewerJupyterLabHeadless from './ViewerJupyterLabHeadless'; 5 | 6 | import "./../style/index.css"; 7 | 8 | const div = document.createElement('div'); 9 | document.body.appendChild(div); 10 | const root = createRoot(div) 11 | 12 | if (module.hot) { 13 | module.hot.accept('./ViewerJupyterLabHeadless', () => { 14 | const ViewerJupyterLabHeadless = require('./ViewerJupyterLabHeadless').default; 15 | root.render(); 16 | }) 17 | } 18 | 19 | root.render(); 20 | -------------------------------------------------------------------------------- /src/ViewerJupyterLabHeadless.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { createGlobalStyle } from 'styled-components'; 3 | import { Jupyter, JupyterLabApp, JupyterLabAppAdapter } from '@datalayer/jupyter-react'; 4 | import Viewer from './Viewer'; 5 | 6 | import * as lightThemeExtension from '@jupyterlab/theme-light-extension'; 7 | import * as collaborationExtension from '@jupyter/collaboration-extension'; 8 | import * as viewerExtension from './jupyterlab/index'; 9 | 10 | const ThemeGlobalStyle = createGlobalStyle` 11 | body { 12 | background-color: white !important; 13 | overflow-y: auto; 14 | } 15 | ` 16 | 17 | const JupyterLabHeadless = () => { 18 | const [jupyterLabAppAdapter, setJupyterLabAppAdapter] = useState(); 19 | const onJupyterLab = (jupyterLabAppAdapter: JupyterLabAppAdapter) => { 20 | setJupyterLabAppAdapter(jupyterLabAppAdapter); 21 | } 22 | return ( 23 | <> 24 | {jupyterLabAppAdapter && } 25 | 34 | 35 | ) 36 | } 37 | 38 | export const JupyterViewerJupyterLabHeadless = () => ( 39 | 40 | 41 | 42 | 43 | ) 44 | 45 | export default JupyterViewerJupyterLabHeadless; 46 | -------------------------------------------------------------------------------- /src/ViewerRoutes.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 2 | import JupyterViewer from './Viewer'; 3 | import ViewerGitHub from './views/ViewerGitHub'; 4 | 5 | const JupyterViewerRoutes = () => { 6 | return ( 7 | <> 8 | 9 | 10 | }/> 11 | }/> 12 | }/> 13 | }/> 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | export default JupyterViewerRoutes; 21 | -------------------------------------------------------------------------------- /src/__tests__/browser.spec.ts: -------------------------------------------------------------------------------- 1 | import { chromium, Browser, Page } from "playwright"; 2 | 3 | let browser: Browser; 4 | let page: Page; 5 | 6 | beforeAll(async () => { 7 | browser = await chromium.launch(); 8 | }); 9 | afterAll(async () => { 10 | await browser.close(); 11 | }); 12 | 13 | beforeEach(async () => { 14 | page = await browser.newPage(); 15 | await page.goto('https://whatismybrowser.com') 16 | }); 17 | afterEach(async () => { 18 | await page.close(); 19 | }); 20 | 21 | test('should display correct browser', async () => { 22 | const browser = await page.$eval('.string-major', (el) => el.innerHTML) 23 | expect(browser).toContain('Chrome') 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/datalayer.spec.ts: -------------------------------------------------------------------------------- 1 | describe('@datalayer/jupyter-viewer', () => { 2 | it('should be tested', () => { 3 | expect(1 + 1).toEqual(2); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./views"; 2 | export * from "./Viewer"; 3 | export * from "./ViewerRoutes"; 4 | export * from "./ViewerJupyterLab"; 5 | export * from "./ViewerJupyterLabHeadless"; 6 | -------------------------------------------------------------------------------- /src/jupyterlab/handler.ts: -------------------------------------------------------------------------------- 1 | import { URLExt } from '@jupyterlab/coreutils'; 2 | import { ServerConnection } from '@jupyterlab/services'; 3 | 4 | /** 5 | * Call the API extension 6 | * 7 | * @param endPoint API REST end point for the extension 8 | * @param init Initial values for the request 9 | * @returns The response body interpreted as JSON 10 | */ 11 | export async function requestAPI( 12 | endPoint = '', 13 | init: RequestInit = {} 14 | ): Promise { 15 | // Make request to Jupyter API 16 | const settings = ServerConnection.makeSettings(); 17 | const requestUrl = URLExt.join( 18 | settings.baseUrl, 19 | 'jupyter_viewer', // API Namespace 20 | endPoint 21 | ); 22 | let response: Response; 23 | try { 24 | response = await ServerConnection.makeRequest(requestUrl, init, settings); 25 | } catch (error: any) { 26 | throw new ServerConnection.NetworkError(error); 27 | } 28 | let data: any = await response.text(); 29 | if (data.length > 0) { 30 | try { 31 | data = JSON.parse(data); 32 | } catch (error) { 33 | console.log('Not a JSON response body.', response); 34 | } 35 | } 36 | if (!response.ok) { 37 | throw new ServerConnection.ResponseError(response, data.message || data); 38 | } 39 | return data; 40 | } 41 | -------------------------------------------------------------------------------- /src/jupyterlab/index.ts: -------------------------------------------------------------------------------- 1 | import { JupyterFrontEnd, JupyterFrontEndPlugin, ILayoutRestorer, JupyterLab } from '@jupyterlab/application'; 2 | import { MainAreaWidget, ICommandPalette, WidgetTracker } from '@jupyterlab/apputils'; 3 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 4 | import { ILauncher } from '@jupyterlab/launcher'; 5 | import icon from '@datalayer/icons-react/data2/EyesIconJupyterLab'; 6 | import { requestAPI } from './handler'; 7 | import { JupyterViewerWidget } from './widget'; 8 | import viewerPlugin from './viewer/plugin'; 9 | 10 | import '../../style/index.css'; 11 | 12 | /** 13 | * The command IDs used by the plugin. 14 | */ 15 | namespace CommandIDs { 16 | export const create = 'create-jupyter-viewer-widget'; 17 | } 18 | 19 | /** 20 | * Initialization data for the @datalayer/jupyter-viewer extension. 21 | */ 22 | const plugin: JupyterFrontEndPlugin = { 23 | id: '@datalayer/jupyter-viewer:plugin', 24 | autoStart: true, 25 | requires: [ICommandPalette], 26 | optional: [ISettingRegistry, ILauncher, ILayoutRestorer], 27 | activate: ( 28 | app: JupyterFrontEnd, 29 | palette: ICommandPalette, 30 | settingRegistry?: ISettingRegistry, 31 | launcher?: ILauncher, 32 | restorer?: ILayoutRestorer, 33 | ) => { 34 | const { commands } = app; 35 | const command = CommandIDs.create; 36 | const tracker = new WidgetTracker>({ 37 | namespace: 'jupyter-viewer', 38 | }); 39 | if (restorer) { 40 | void restorer.restore(tracker, { 41 | command, 42 | name: () => 'jupyter-viewer', 43 | }); 44 | } 45 | commands.addCommand(command, { 46 | caption: 'Show Jupyter Viewer', 47 | label: 'Jupyter Viewer', 48 | icon, 49 | execute: () => { 50 | const content = new JupyterViewerWidget(app as JupyterLab); 51 | const widget = new MainAreaWidget({ content }); 52 | widget.title.label = 'Jupyter Viewer'; 53 | widget.title.icon = icon; 54 | app.shell.add(widget, 'main'); 55 | tracker.add(widget); 56 | } 57 | }); 58 | const category = 'Datalayer'; 59 | palette.addItem({ command, category }); 60 | const settingsUpdated = (settings: ISettingRegistry.ISettings) => { 61 | const showInLauncher = settings.get('showInLauncher').composite as boolean; 62 | if (launcher && showInLauncher) { 63 | launcher.add({ 64 | command, 65 | category, 66 | rank: 2.5, 67 | }); 68 | } 69 | }; 70 | if (settingRegistry) { 71 | settingRegistry 72 | .load(plugin.id) 73 | .then(settings => { 74 | console.log(`${plugin.id} settings loaded:`, settings.composite); 75 | settingsUpdated(settings); 76 | settings.changed.connect(settingsUpdated); 77 | }) 78 | .catch(reason => { 79 | console.error(`Failed to load settings for ${plugin.id}`, reason); 80 | }); 81 | } 82 | requestAPI('config') 83 | .then(data => { 84 | console.log(data); 85 | }) 86 | .catch(reason => { 87 | console.error( 88 | `Error while accessing the jupyter server jupyter_viewer extension.\n${reason}` 89 | ); 90 | } 91 | ); 92 | console.log('JupyterLab plugin @datalayer/jupyter-viewer:plugin is activated.'); 93 | } 94 | }; 95 | 96 | export default [ 97 | plugin, 98 | viewerPlugin, 99 | ]; 100 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/Viewer.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { INotebookModel } from '@jupyterlab/notebook'; 4 | import { Viewer as JupyterReactViewer} from '@datalayer/jupyter-react/lib/components/viewer/Viewer'; 5 | 6 | type Props = { 7 | context: DocumentRegistry.IContext, 8 | } 9 | 10 | const Viewer = (props: Props) => { 11 | const { context } = props; 12 | const [model, setModel] = useState(context.model.sharedModel.toJSON()); 13 | context.model.contentChanged.connect((model, _) => { 14 | setModel(model.sharedModel.toJSON()); 15 | }); 16 | return ( 17 | <> 18 | 19 | 20 | ) 21 | } 22 | 23 | export default Viewer; 24 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/ViewerButton.ts: -------------------------------------------------------------------------------- 1 | import { CommandRegistry } from '@lumino/commands'; 2 | import { IDisposable } from '@lumino/disposable'; 3 | import { ToolbarButton } from '@jupyterlab/apputils'; 4 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 5 | import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook'; 6 | import icon from '@datalayer/icons-react/data2/EyesIconJupyterLab'; 7 | import { CommandIDs } from './plugin'; 8 | 9 | class ViewerButton implements DocumentRegistry.IWidgetExtension { 10 | private _commands: CommandRegistry; 11 | 12 | constructor(commands: CommandRegistry) { 13 | this._commands = commands; 14 | } 15 | 16 | createNew(panel: NotebookPanel): IDisposable { 17 | const button = new ToolbarButton({ 18 | className: 'viewerRender', 19 | tooltip: 'Viewer', 20 | icon, 21 | onClick: () => { this._commands.execute(CommandIDs.viewerRender); } 22 | }); 23 | panel.toolbar.insertAfter('cellType', 'viewer', button); 24 | return button; 25 | } 26 | 27 | } 28 | 29 | export default ViewerButton; 30 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/ViewerDocument.tsx: -------------------------------------------------------------------------------- 1 | import { ToolbarButton, ReactWidget } from '@jupyterlab/apputils'; 2 | import { ABCWidgetFactory, DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry'; 3 | import { INotebookModel } from '@jupyterlab/notebook'; 4 | import { Signal } from '@lumino/signaling'; 5 | import ViewerWidget from './ViewerWidget'; 6 | 7 | export const VIEWER_ICON_CLASS = 'jp-MaterialIcon jp-NotebookIcon'; 8 | 9 | export class Viewer extends DocumentWidget { 10 | private _renderOnSave: boolean = false; 11 | 12 | constructor(options: Viewer.IOptions) { 13 | super({ 14 | ...options, 15 | content: new ViewerWidget(options.context), 16 | }); 17 | const { context, renderOnSave } = options; 18 | this.content.title.iconClass = VIEWER_ICON_CLASS; 19 | this.renderOnSave = renderOnSave ? true : false; 20 | context.pathChanged.connect(() => { 21 | // this.content.url = getClassicUrl(context.path); 22 | }); 23 | const reloadButton = new ToolbarButton({ 24 | iconClass: 'jp-RefreshIcon', 25 | tooltip: 'Reload Dashboard', 26 | onClick: () => { 27 | this.reload(); 28 | } 29 | }); 30 | const renderOnSaveCheckbox = ReactWidget.create( 31 | 43 | ); 44 | this.toolbar.addItem('reload', reloadButton); 45 | if (context) { 46 | this.toolbar.addItem('renderOnSave', renderOnSaveCheckbox); 47 | void context.ready.then(() => { 48 | context.fileChanged.connect(() => { 49 | if (this.renderOnSave) { 50 | this.reload(); 51 | } 52 | }); 53 | }); 54 | } 55 | } 56 | 57 | dispose(): void { 58 | if (this.isDisposed) { 59 | return; 60 | } 61 | super.dispose(); 62 | Signal.clearData(this); 63 | } 64 | 65 | reload(): void { 66 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 67 | const iframe = this.content.node.querySelector('iframe')!; 68 | if (iframe.contentWindow) { 69 | iframe.contentWindow.location.reload(); 70 | } 71 | } 72 | 73 | get renderOnSave(): boolean { 74 | return this._renderOnSave; 75 | } 76 | 77 | set renderOnSave(renderOnSave: boolean) { 78 | this._renderOnSave = renderOnSave; 79 | } 80 | 81 | } 82 | 83 | export namespace Viewer { 84 | export interface IOptions extends DocumentWidget.IOptionsOptionalContent { 85 | renderOnSave?: boolean; 86 | } 87 | } 88 | 89 | export class ViewerFactory extends ABCWidgetFactory { 90 | public defaultRenderOnSave = false; 91 | 92 | constructor(options: DocumentRegistry.IWidgetFactoryOptions) { 93 | super(options); 94 | } 95 | 96 | protected createNewWidget(context: DocumentRegistry.IContext): Viewer { 97 | return new Viewer({ 98 | context, 99 | renderOnSave: this.defaultRenderOnSave 100 | }); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/ViewerWidget.tsx: -------------------------------------------------------------------------------- 1 | import { ReactWidget } from '@jupyterlab/apputils'; 2 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { INotebookModel } from '@jupyterlab/notebook'; 4 | import { ThemeProvider, BaseStyles, Box } from '@primer/react'; 5 | import Viewer from './Viewer'; 6 | 7 | class ViewerWidget extends ReactWidget { 8 | private _context: DocumentRegistry.IContext 9 | 10 | constructor(context: DocumentRegistry.IContext) { 11 | super(); 12 | this._context = context; 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | } 27 | 28 | export default ViewerWidget; 29 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/plugin.ts: -------------------------------------------------------------------------------- 1 | import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; 2 | import { JupyterFrontEnd, JupyterFrontEndPlugin, ILayoutRestorer } from '@jupyterlab/application'; 3 | import { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils'; 4 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 5 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 6 | import { IMainMenu } from '@jupyterlab/mainmenu'; 7 | import { INotebookTracker, NotebookPanel, INotebookModel } from '@jupyterlab/notebook'; 8 | import { IViewerTracker } from './token'; 9 | import { Viewer, ViewerFactory } from './ViewerDocument'; 10 | import ViewerButton from './ViewerButton'; 11 | 12 | export namespace CommandIDs { 13 | export const viewerRender = 'notebook:render-with-viewer'; 14 | export const viewerOpen = 'notebook:open-with-viewer'; 15 | } 16 | 17 | export const NOTEBOOK_VIEWER_WIDGET_FACTORY = 'Notebook Viewer'; 18 | 19 | const viewerPlugin: JupyterFrontEndPlugin = { 20 | id: '@datalayer/jupyter-viewer:notebook', 21 | autoStart: true, 22 | requires: [INotebookTracker], 23 | optional: [ICommandPalette, ILayoutRestorer, IMainMenu, ISettingRegistry], 24 | provides: IViewerTracker, 25 | activate: ( 26 | app: JupyterFrontEnd, 27 | notebookTracker: INotebookTracker, 28 | palette: ICommandPalette | null, 29 | restorer: ILayoutRestorer | null, 30 | menu: IMainMenu | null, 31 | settingRegistry: ISettingRegistry | null 32 | ) => { 33 | const { commands, docRegistry } = app; 34 | const viewerTracker = new WidgetTracker({ 35 | namespace: 'viewer' 36 | }); 37 | if (restorer) { 38 | restorer.restore(viewerTracker, { 39 | command: 'docmanager:open', 40 | args: panel => ({ 41 | path: panel.context.path, 42 | factory: viwerRenderFactory.name 43 | }), 44 | name: panel => panel.context.path, 45 | when: app.serviceManager.ready 46 | }); 47 | } 48 | function getCurrentNotebookPanel(args: ReadonlyPartialJSONObject): NotebookPanel | null { 49 | const notebookPanel = notebookTracker.currentWidget; 50 | const activate = args['activate'] !== false; 51 | if (activate && notebookPanel) { 52 | app.shell.activateById(notebookPanel.id); 53 | } 54 | return notebookPanel; 55 | } 56 | function isEnabled(): boolean { 57 | return ( 58 | notebookTracker.currentWidget !== null && 59 | notebookTracker.currentWidget === app.shell.currentWidget 60 | ); 61 | } 62 | const viwerRenderFactory = new ViewerFactory({ 63 | name: NOTEBOOK_VIEWER_WIDGET_FACTORY, 64 | fileTypes: ['notebook'], 65 | modelName: 'notebook', 66 | }); 67 | viwerRenderFactory.widgetCreated.connect((sender, viewer) => { 68 | viewer.context.pathChanged.connect(() => { 69 | void viewerTracker.save(viewer); 70 | }); 71 | void viewerTracker.add(viewer); 72 | }); 73 | const updateSettings = (settings: ISettingRegistry.ISettings): void => { 74 | viwerRenderFactory.defaultRenderOnSave = settings.get('renderOnSave') 75 | .composite as boolean; 76 | }; 77 | if (settingRegistry) { 78 | Promise.all([settingRegistry.load(viewerPlugin.id), app.restored]) 79 | .then(([settings]) => { 80 | updateSettings(settings); 81 | settings.changed.connect(updateSettings); 82 | }) 83 | .catch((reason: Error) => { 84 | console.error(reason.message); 85 | }); 86 | } 87 | commands.addCommand(CommandIDs.viewerRender, { 88 | label: 'Viewer', 89 | execute: async args => { 90 | const notebookPanel = getCurrentNotebookPanel(args); 91 | let context: DocumentRegistry.IContext; 92 | if (notebookPanel) { 93 | context = notebookPanel.context; 94 | await context.save(); 95 | commands.execute('docmanager:open', { 96 | path: context.path, 97 | factory: NOTEBOOK_VIEWER_WIDGET_FACTORY, 98 | options: { 99 | mode: 'split-right' 100 | } 101 | }); 102 | } 103 | }, 104 | isEnabled 105 | }); 106 | commands.addCommand(CommandIDs.viewerOpen, { 107 | label: 'Open with Viewer', 108 | execute: async args => { 109 | const notebookPanel = getCurrentNotebookPanel(args); 110 | if (!notebookPanel) { 111 | return; 112 | } 113 | await notebookPanel.context.save(); 114 | }, 115 | isEnabled 116 | }); 117 | if (palette) { 118 | const category = 'Notebook Operations'; 119 | [CommandIDs.viewerRender, CommandIDs.viewerOpen].forEach(command => { 120 | palette.addItem({ command, category }); 121 | }); 122 | } 123 | if (menu) { 124 | menu.viewMenu.addGroup( 125 | [ 126 | { 127 | command: CommandIDs.viewerRender 128 | }, 129 | { 130 | command: CommandIDs.viewerOpen 131 | } 132 | ], 133 | 1000 134 | ); 135 | } 136 | docRegistry.addWidgetFactory(viwerRenderFactory); 137 | const viewerButton = new ViewerButton(commands); 138 | docRegistry.addWidgetExtension('Notebook', viewerButton); 139 | console.log('JupyterLab plugin @datalayer/jupyter-viewer:notebook is activated.'); 140 | return viewerTracker; 141 | } 142 | 143 | } 144 | 145 | export default viewerPlugin; 146 | -------------------------------------------------------------------------------- /src/jupyterlab/viewer/token.ts: -------------------------------------------------------------------------------- 1 | import { IWidgetTracker } from '@jupyterlab/apputils'; 2 | import { Token } from '@lumino/coreutils'; 3 | import { Viewer } from './ViewerDocument'; 4 | 5 | export type IViewerTracker = IWidgetTracker 6 | 7 | export const IViewerTracker = new Token( 8 | '@datalayer/jupyter-dashboard:IViewerTracker' 9 | ); 10 | -------------------------------------------------------------------------------- /src/jupyterlab/widget.tsx: -------------------------------------------------------------------------------- 1 | import { JupyterLab } from '@jupyterlab/application'; 2 | import { ReactWidget } from '@jupyterlab/apputils'; 3 | import { JupyterLabAppAdapter } from '@datalayer/jupyter-react'; 4 | import Viewer from '../Viewer'; 5 | 6 | export class JupyterViewerWidget extends ReactWidget { 7 | private _app: JupyterLab; 8 | constructor(app: JupyterLab) { 9 | super(); 10 | this._app = app; 11 | this.addClass('dla-Container'); 12 | } 13 | 14 | render(): JSX.Element { 15 | return ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/state/zustand.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | export class Timer { 4 | _secondsPassed = 0; 5 | constructor() { 6 | } 7 | reset() { 8 | this._secondsPassed = 0 9 | } 10 | increaseTimer() { 11 | this._secondsPassed += 1; 12 | } 13 | get secondsPassed() { 14 | return this._secondsPassed; 15 | } 16 | } 17 | 18 | export type JupyterViewerState = { 19 | tab: number; 20 | getIntTab: () => number; 21 | setTab: (tab: number) => void; 22 | timer: Timer; 23 | increaseTimer: () => void; 24 | secondsPassed: number; 25 | } 26 | 27 | export const useJupyterViewerStore = create((set, get) => ({ 28 | tab: 0.0, 29 | getIntTab: () => Math.floor(get().tab), 30 | setTab: (tab: number) => set((state) => ({ tab })), 31 | timer: new Timer(), 32 | increaseTimer: () => { 33 | get().timer.increaseTimer(); 34 | set((state) => ({ secondsPassed: get().timer.secondsPassed })); 35 | }, 36 | secondsPassed: 0, 37 | })); 38 | 39 | export default useJupyterViewerStore; 40 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.jpg" { 4 | const value: any; 5 | export default value; 6 | } 7 | declare module "*.jpeg" { 8 | const value: any; 9 | export default value; 10 | } 11 | declare module "*.png" { 12 | const value: any; 13 | export default value; 14 | } 15 | declare module "*.svg" { 16 | const value: any; 17 | export default value; 18 | } 19 | -------------------------------------------------------------------------------- /src/views/ViewerAbout.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Pagehead, Label, Text, Box } from '@primer/react'; 3 | import { ECharlesIcon } from '@datalayer/icons-react/eggs'; 4 | 5 | type Props = { 6 | version: string; 7 | } 8 | 9 | export const ViewerAbout = (props: Props): JSX.Element => { 10 | const { version } = props; 11 | const [egg, setEgg] = useState(false); 12 | return ( 13 | <> 14 | 🪐 👀 Jupyter Viewer{ version && } 15 | 16 | A revisited NbViewer as a modern Web application to view Jupyter notebooks. 17 | 18 | 19 | {!egg ? ( 20 | setEgg(true)} 23 | style={{ width: 300 }} 24 | /> 25 | ) : ( 26 | setEgg(false)} /> 27 | )} 28 | 29 | 30 | ); 31 | } 32 | 33 | export default ViewerAbout; 34 | -------------------------------------------------------------------------------- /src/views/ViewerExamples.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Box, ActionMenu, ActionList } from '@primer/react'; 3 | import { NetworkIcon, JupyterBaseIcon, JupiterIcon, ScientistIcon } from '@datalayer/icons-react'; 4 | import { JupyterReactTheme, Viewer } from '@datalayer/jupyter-react'; 5 | import { visualisations, astronomies, dataSciences, MenuLine, NotebookExample } from './notebooks/Examples'; 6 | 7 | export const ViewerExamples = () => { 8 | const [notebookExample, setNotebookExample] = useState(visualisations[0]); 9 | return ( 10 | <> 11 | 12 | 13 | 14 | }> 15 | Notebooks 16 | 17 | 18 | 19 | 20 | Visualisations 21 | 22 | 23 | {visualisations.map(visualisation => 24 | } setNotebookExample={setNotebookExample} />) 25 | } 26 | 27 | 28 | Data Science 29 | 30 | 31 | {dataSciences.map(dataScience => 32 | } setNotebookExample={setNotebookExample} />) 33 | } 34 | 35 | 36 | Astronomy 37 | 38 | 39 | {astronomies.map(astronomy => 40 | } setNotebookExample={setNotebookExample} />) 41 | } 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | export default ViewerExamples; 54 | -------------------------------------------------------------------------------- /src/views/ViewerForm.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useNavigate } from "react-router-dom"; 3 | import { INotebookContent } from '@jupyterlab/nbformat'; 4 | import { Box, Button, FormControl, TextInput, Spinner, ButtonGroup, Text, Link, Label } from '@primer/react'; 5 | import { Card } from '@datalayer/primer-addons'; 6 | // import { ThreeBarsIcon } from '@primer/octicons-react'; 7 | import { FourLeafCloverIcon, JupyterIcon } from '@datalayer/icons-react'; 8 | import { Jupyter } from '@datalayer/jupyter-react/lib/jupyter/Jupyter'; 9 | import { Viewer } from '@datalayer/jupyter-react/lib/components/viewer/Viewer'; 10 | import Masonry from 'react-layout-masonry'; 11 | import { visualisations, NotebookExample } from './notebooks/Examples'; 12 | 13 | type CardContent = { 14 | category: string; 15 | title: string; 16 | description: string; 17 | notebookUrl: string; 18 | imageUrl: string; 19 | viewRoute: string; 20 | } 21 | 22 | const cards: CardContent[] = [ 23 | { 24 | category: 'Language', 25 | title: 'IPython Kernel', 26 | description: 'IPython provides extensions to the Python programming language that make working interactively convenient and efficient. These extensions are implemented in the IPython Kernel and are available in all of the IPython Frontends (Notebook, Terminal, Console and Qt Console) when running this kernel.', 27 | notebookUrl: 'https://github.com/ipython/ipython/blob/6.x/examples/IPython%20Kernel/Index.ipynb', 28 | imageUrl: 'https://nbviewer.org/static/img/example-nb/ipython-thumb.png', 29 | viewRoute: '/github/ipython/ipython/6.x/examples/IPython%20Kernel/Index.ipynb', 30 | }, 31 | { 32 | category: 'Visualization', 33 | title: 'XKCD plots in Matplotlib', 34 | description: 'This notebook originally appeared as a blog post at Pythonic Perambulations by Jake Vanderplas.', 35 | notebookUrl: 'https://github.com/jakevdp/jakevdp.github.io-source/blob/master/content/downloads/notebooks/XKCD_plots.ipynb', 36 | imageUrl: 'https://nbviewer.org/static/img/example-nb/XKCD-Matplotlib.png', 37 | viewRoute: '/github/jakevdp/jakevdp.github.io-source/master/content/downloads/notebooks/XKCD_plots.ipynb', 38 | }, 39 | { 40 | category: 'Book', 41 | title: 'Mining the Social Web', 42 | description: 'Mining the Social Web (3rd Edition)! This collection of Jupyter Notebooks provides an interactive way to follow along with and explore the numbered examples from the book.', 43 | notebookUrl: 'https://github.com/mikhailklassen/Mining-the-Social-Web-3rd-Edition/blob/master/notebooks/Chapter%201%20-%20Mining%20Twitter.ipynb', 44 | imageUrl: 'https://nbviewer.org/static/img/example-nb/mining-slice.png', 45 | viewRoute: '/github/mikhailklassen/Mining-the-Social-Web-3rd-Edition/master/notebooks/Chapter%201%20-%20Mining%20Twitter.ipynb', 46 | }, 47 | ] 48 | 49 | export const ViewerForm = () => { 50 | const navigate = useNavigate(); 51 | const [input, setInput] = useState(''); 52 | const [loading, setLoading] = useState(false); 53 | const [notebook, setNotebook] = useState(); 54 | const [nbformat, setNbformat] = useState(); 55 | const randomNotebook = () => { 56 | return visualisations[Math.floor(Math.random() * visualisations.length)]; 57 | } 58 | useEffect(() => { 59 | setLoading(true); 60 | setNbformat(undefined); 61 | if (notebook) { 62 | fetch(notebook.url) 63 | .then(response => { 64 | return response.text(); 65 | }) 66 | .then(nb => { 67 | setNbformat(JSON.parse(nb)); 68 | }) 69 | .catch(() => { 70 | fetch(notebook.url.replace('/blob/', '/ref/heads/')) 71 | .then(response => { 72 | return response.text(); 73 | }) 74 | .then(nb => { 75 | setNbformat(JSON.parse(nb)); 76 | }) 77 | }); 78 | } 79 | setLoading(false); 80 | }, [notebook]); 81 | /* 82 | const onSelectedChange = (item: any) => { 83 | console.log('---', item); 84 | } 85 | */ 86 | return ( 87 | <> 88 | 89 | 90 | Enter the location of a Jupyter Notebook to have it rendered here 91 | setInput(e.target.value)} 95 | onSubmit={e => setNotebook({ title: '', url: input })} 96 | /> 97 | {/* 98 | 99 | 100 | 101 | 112 | 113 | 114 | */} 115 | 116 | 117 | 118 | 121 | 124 | 125 | 126 | {loading && } 127 | {notebook && nbformat && 128 | <> 129 | 130 | 131 | 132 | 133 | } 134 | 135 | 136 | 137 | {cards.map((card) => { 138 | return ( 139 |
140 | 141 | alert("Menu")} icon={ThreeBarsIcon} />} 147 | */ 148 | /> 149 | navigate(card.viewRoute)}> 150 | 151 | 152 | 153 | {/* 154 | Paella 155 | */} 156 | 157 | {card.description} 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 |
171 | ) 172 | })} 173 |
174 |
175 | 176 | ) 177 | } 178 | 179 | export default ViewerForm; 180 | -------------------------------------------------------------------------------- /src/views/ViewerGitHub.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useNavigate } from "react-router-dom"; 3 | import { useParams, useLocation } from "react-router-dom"; 4 | import { Box, Spinner, Pagehead, Breadcrumbs, Link } from '@primer/react'; 5 | import { MarkGithubIcon } from '@primer/octicons-react'; 6 | import { INotebookContent } from '@jupyterlab/nbformat'; 7 | import { DatalayerGreenIcon } from '@datalayer/icons-react'; 8 | import { URLExt } from '@jupyterlab/coreutils'; 9 | import { Jupyter } from '@datalayer/jupyter-react/lib/jupyter/Jupyter'; 10 | import { Viewer } from '@datalayer/jupyter-react/lib/components/viewer/Viewer'; 11 | 12 | export const ViewerGitHub = () => { 13 | const { account, repo, branch } = useParams(); 14 | if (!account || !repo || !branch) { 15 | return <> 16 | } 17 | const location = useLocation(); 18 | const navigate = useNavigate(); 19 | const [notebookPath, setNotebookPath] = useState(); 20 | const [loading, setLoading] = useState(false); 21 | const [nbformat, setNbformat] = useState(); 22 | useEffect(() => { 23 | setLoading(true); 24 | setNbformat(undefined); 25 | const notebookPath = location.pathname.replace("/jupyter_viewer", "").replace(`/github/${account}/${repo}/${branch}`, ''); 26 | setNotebookPath(notebookPath); 27 | const notebook = URLExt.join('https://raw.githubusercontent.com', account, repo, branch, notebookPath); 28 | if (notebook) { 29 | fetch(notebook) 30 | .then(response => { 31 | return response.text(); 32 | }) 33 | .then(nb => { 34 | setNbformat(JSON.parse(nb)); 35 | }); 36 | } 37 | setLoading(false); 38 | }, []); 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | navigate('/')}> 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {account} 57 | 58 | 59 | {repo} 60 | 61 | 62 | {branch} 63 | 64 | 65 | {notebookPath?.replace('/', '')} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {loading && } 74 | {nbformat && 75 | } 76 | 77 | 78 | 79 | ) 80 | } 81 | 82 | export default ViewerGitHub; 83 | -------------------------------------------------------------------------------- /src/views/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ViewerGitHub"; 2 | export * from "./ViewerAbout"; 3 | export * from "./ViewerExamples"; 4 | export * from "./ViewerForm"; 5 | -------------------------------------------------------------------------------- /src/views/notebooks/Examples.tsx: -------------------------------------------------------------------------------- 1 | import { ActionList } from '@primer/react'; 2 | 3 | export type NotebookExample = { 4 | title: string; 5 | url: string; 6 | } 7 | 8 | export const visualisations: NotebookExample[] = [ 9 | { 10 | title: "Plotly Daily Stocks", 11 | url: "https://raw.githubusercontent.com/datalayer-examples/notebooks/main/daily-stock.ipynb", 12 | }, 13 | { 14 | title: "Matplotlib", 15 | url: "https://raw.githubusercontent.com/anissa111/matplotlib-tutorial/main/notebooks/01-basic-matplotlib-tutorial.ipynb", 16 | }, 17 | /*{ 18 | title: "Bicycle Control", 19 | url: "https://raw.githubusercontent.com/plotly/IPython-plotly/master/notebooks/bicycle_control/bicycle_control.ipynb", 20 | },*/ 21 | { 22 | title: "IPyWidgets Example", 23 | url: " https://raw.githubusercontent.com/jupyter-widgets/ipywidgets/main/docs/source/examples/Widget%20Basics.ipynb", 24 | }, 25 | ] 26 | 27 | export const dataSciences: NotebookExample[] = [ 28 | { 29 | title: "Fair Experiment", 30 | url: "https://raw.githubusercontent.com/datalayer-courses/foundations-of-data-science-with-python/main/04-probability1/fair-experiments.ipynb", 31 | }, 32 | { 33 | title: "Text Vectorization", 34 | url: "https://raw.githubusercontent.com/datalayer-courses/python-text-mining-intro/main/4-text-vectorization.ipynb", 35 | }, 36 | /*{ 37 | title: "Survival Analysis", 38 | url: "https://raw.githubusercontent.com/plotly/IPython-plotly/master/notebooks/survival_analysis/survival_analysis.ipynb", 39 | },*/ 40 | ] 41 | 42 | export const astronomies: NotebookExample[] = [ 43 | { 44 | title: "Center of Mass", 45 | url: "https://raw.githubusercontent.com/JuanCab/AstroInteractives/master/Interactives/Center_of_Mass.ipynb", 46 | }, 47 | /* 48 | { 49 | title: "Propagation Effects", 50 | url: "https://raw.githubusercontent.com/ratt-ru/fundamentals_of_interferometry/master/7_Observing_Systems/7_7_propagation_effects.ipynb", 51 | }, 52 | */ 53 | ] 54 | 55 | type MenuLineProps = { 56 | notebookExample: NotebookExample, 57 | setNotebookExample: React.Dispatch>, 58 | icon: JSX.Element, 59 | } 60 | 61 | export const MenuLine = (props: MenuLineProps) => { 62 | const { notebookExample, setNotebookExample, icon } = props; 63 | return ( 64 | setNotebookExample(notebookExample)}> 65 | 66 | {icon} 67 | 68 | 69 | {notebookExample.url} 70 | 71 | {notebookExample.title} 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-y: visible; 3 | } 4 | 5 | .dla-Container { 6 | overflow-y: auto !important; 7 | } 8 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "include": ["./src/**/*", "./src/**/*.json"], 4 | "exclude": ["node_modules", "src/example"], 5 | "compilerOptions": { 6 | "forceConsistentCasingInFileNames": true, 7 | "baseUrl": "/", 8 | "rootDir": "./src", 9 | "allowSyntheticDefaultImports": true, 10 | "composite": true, 11 | "declaration": true, 12 | "esModuleInterop": true, 13 | "incremental": true, 14 | "jsx": "react-jsx", 15 | "jsxImportSource": "react", 16 | "module": "ES2022", 17 | "moduleResolution": "node", 18 | "noEmitOnError": true, 19 | "noImplicitAny": true, 20 | "noUnusedLocals": true, 21 | "outDir": "./lib", 22 | "preserveWatchOutput": true, 23 | "resolveJsonModule": true, 24 | "sourceMap": true, 25 | "skipLibCheck": true, 26 | "strictNullChecks": true, 27 | "target": "ESNext", 28 | "types": ["jest", "node"], 29 | "lib": ["ESNext", "DOM"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui-tests/README.md: -------------------------------------------------------------------------------- 1 | # Integration Testing 2 | 3 | This folder contains the integration tests of the extension. 4 | 5 | They are defined using [Playwright](https://playwright.dev/docs/intro) test runner 6 | and [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) helper. 7 | 8 | The Playwright configuration is defined in [playwright.config.js](./playwright.config.js). 9 | 10 | The JupyterLab server configuration to use for the integration test is defined 11 | in [jupyter_server_test_config.py](./jupyter_server_test_config.py). 12 | 13 | The default configuration will produce video for failing tests and an HTML report. 14 | 15 | ## Run the tests 16 | 17 | > All commands are assumed to be executed from the root directory 18 | 19 | To run the tests, you need to: 20 | 21 | 1. Compile the extension: 22 | 23 | ```sh 24 | jlpm install 25 | jlpm build:prod 26 | ``` 27 | 28 | > Check the extension is installed in JupyterLab. 29 | 30 | 2. Install test dependencies (needed only once): 31 | 32 | ```sh 33 | cd ./ui-tests 34 | jlpm install 35 | jlpm playwright install 36 | cd .. 37 | ``` 38 | 39 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) tests: 40 | 41 | ```sh 42 | cd ./ui-tests 43 | jlpm playwright test 44 | ``` 45 | 46 | Test results will be shown in the terminal. In case of any test failures, the test report 47 | will be opened in your browser at the end of the tests execution; see 48 | [Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter) 49 | for configuring that behavior. 50 | 51 | ## Update the tests snapshots 52 | 53 | > All commands are assumed to be executed from the root directory 54 | 55 | If you are comparing snapshots to validate your tests, you may need to update 56 | the reference snapshots stored in the repository. To do that, you need to: 57 | 58 | 1. Compile the extension: 59 | 60 | ```sh 61 | jlpm install 62 | jlpm build:prod 63 | ``` 64 | 65 | > Check the extension is installed in JupyterLab. 66 | 67 | 2. Install test dependencies (needed only once): 68 | 69 | ```sh 70 | cd ./ui-tests 71 | jlpm install 72 | jlpm playwright install 73 | cd .. 74 | ``` 75 | 76 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) command: 77 | 78 | ```sh 79 | cd ./ui-tests 80 | jlpm playwright test -u 81 | ``` 82 | 83 | > Some discrepancy may occurs between the snapshots generated on your computer and 84 | > the one generated on the CI. To ease updating the snapshots on a PR, you can 85 | > type `please update playwright snapshots` to trigger the update by a bot on the CI. 86 | > Once the bot has computed new snapshots, it will commit them to the PR branch. 87 | 88 | ## Create tests 89 | 90 | > All commands are assumed to be executed from the root directory 91 | 92 | To create tests, the easiest way is to use the code generator tool of playwright: 93 | 94 | 1. Compile the extension: 95 | 96 | ```sh 97 | jlpm install 98 | jlpm build:prod 99 | ``` 100 | 101 | > Check the extension is installed in JupyterLab. 102 | 103 | 2. Install test dependencies (needed only once): 104 | 105 | ```sh 106 | cd ./ui-tests 107 | jlpm install 108 | jlpm playwright install 109 | cd .. 110 | ``` 111 | 112 | 3. Execute the [Playwright code generator](https://playwright.dev/docs/codegen): 113 | 114 | ```sh 115 | cd ./ui-tests 116 | jlpm playwright codegen localhost:8888 117 | ``` 118 | 119 | ## Debug tests 120 | 121 | > All commands are assumed to be executed from the root directory 122 | 123 | To debug tests, a good way is to use the inspector tool of playwright: 124 | 125 | 1. Compile the extension: 126 | 127 | ```sh 128 | jlpm install 129 | jlpm build:prod 130 | ``` 131 | 132 | > Check the extension is installed in JupyterLab. 133 | 134 | 2. Install test dependencies (needed only once): 135 | 136 | ```sh 137 | cd ./ui-tests 138 | jlpm install 139 | jlpm playwright install 140 | cd .. 141 | ``` 142 | 143 | 3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug): 144 | 145 | ```sh 146 | cd ./ui-tests 147 | PWDEBUG=1 jlpm playwright test 148 | ``` 149 | -------------------------------------------------------------------------------- /ui-tests/jupyter_server_test_config.py: -------------------------------------------------------------------------------- 1 | """Server configuration for integration tests. 2 | 3 | !! Never use this configuration in production because it 4 | opens the server to the world and provide access to JupyterLab 5 | JavaScript objects through the global window variable. 6 | """ 7 | from tempfile import mkdtemp 8 | 9 | c.ServerApp.port = 8888 10 | c.ServerApp.port_retries = 0 11 | c.ServerApp.open_browser = False 12 | 13 | c.ServerApp.root_dir = mkdtemp(prefix='galata-test-') 14 | c.IdentityProvider.token = "" 15 | c.ServerApp.password = "" 16 | c.ServerApp.disable_check_xsrf = True 17 | c.LabApp.expose_app_in_browser = True 18 | 19 | # Uncomment to set server log level to debug level 20 | # c.ServerApp.log_level = "DEBUG" 21 | -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datalayer/jupyter-viewer-ui-tests", 3 | "version": "1.0.0", 4 | "description": "JupyterLab @datalayer/jupyter-viewer Integration Tests", 5 | "private": true, 6 | "scripts": { 7 | "start": "jupyter lab --config jupyter_server_test_config.py", 8 | "test": "jlpm playwright test" 9 | }, 10 | "devDependencies": { 11 | "@jupyterlab/galata": "5.0.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for Playwright using default from @jupyterlab/galata 3 | */ 4 | const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); 5 | 6 | module.exports = { 7 | ...baseConfig, 8 | webServer: { 9 | command: 'jlpm start', 10 | url: 'http://localhost:8888/lab', 11 | timeout: 120 * 1000, 12 | reuseExistingServer: !process.env.CI 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /ui-tests/tests/datalayer.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@jupyterlab/galata'; 2 | 3 | /** 4 | * Don't load JupyterLab webpage before running the tests. 5 | * This is required to ensure we capture all log messages. 6 | */ 7 | test.use({ autoGoto: false }); 8 | 9 | test('should emit an activation console message', async ({ page }) => { 10 | const logs: string[] = []; 11 | 12 | page.on('console', message => { 13 | logs.push(message.text()); 14 | }); 15 | 16 | await page.goto(); 17 | 18 | expect( 19 | logs.filter(s => s === 'JupyterLab extension @datalayer/jupyter-viewer is activated!') 20 | ).toHaveLength(1); 21 | }); 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 6 | 7 | const IS_WEBPACK_PROD = process.env.IS_WEBPACK_PROD; 8 | const IS_PRODUCTION = process.argv.indexOf('--mode=productin') > -1 || IS_WEBPACK_PROD === "true"; 9 | const MODE = IS_PRODUCTION ? "production" : "development"; 10 | const DEVTOOL = IS_PRODUCTION ? false : "inline-cheap-source-map"; 11 | const MINIMIZE = IS_PRODUCTION ? true : false; 12 | const PUBLIC_PATH = (process.argv.indexOf('--mode=production') > -1) ? 13 | "/static/jupyter_viewer/" 14 | : 15 | (IS_WEBPACK_PROD === "true") ? "/" : "http://localhost:3063/"; 16 | 17 | module.exports = { 18 | entry: "./src/ViewerApp", 19 | mode: MODE, 20 | devServer: { 21 | port: 3063, 22 | client: { overlay: false }, 23 | historyApiFallback: { 24 | disableDotRule: true, 25 | }, 26 | hot: !IS_PRODUCTION, 27 | }, 28 | watchOptions: { 29 | aggregateTimeout: 300, 30 | poll: 2000, // Seems to stabilise HMR file change detection 31 | ignored: "/node_modules/" 32 | }, 33 | devtool: DEVTOOL, 34 | optimization: { 35 | minimize: MINIMIZE, 36 | // usedExports: true, 37 | }, 38 | output: { 39 | publicPath: PUBLIC_PATH, 40 | // filename: '[name].[contenthash].jupyter-viewer.js', 41 | filename: '[name].jupyter-viewer.js', 42 | }, 43 | resolve: { 44 | extensions: [".ts", ".tsx", ".js", ".jsx"], 45 | alias: { 46 | path: "path-browserify", 47 | stream: "stream-browserify", 48 | }, 49 | }, 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.tsx?$/, 54 | loader: "babel-loader", 55 | options: { 56 | plugins: [ 57 | "@babel/plugin-proposal-class-properties", 58 | ], 59 | presets: [ 60 | ["@babel/preset-react", { 61 | runtime: 'automatic', 62 | }, 63 | ], 64 | "@babel/preset-typescript", 65 | ], 66 | cacheDirectory: true 67 | }, 68 | exclude: /node_modules/, 69 | }, 70 | { 71 | test: /\.m?js$/, 72 | resolve: { 73 | fullySpecified: false, 74 | }, 75 | }, 76 | { 77 | test: /\.jsx?$/, 78 | loader: "babel-loader", 79 | options: { 80 | presets: ["@babel/preset-react"], 81 | cacheDirectory: true 82 | } 83 | }, 84 | { 85 | test: /\.css?$/i, 86 | use: ['style-loader', 'css-loader'], 87 | }, 88 | { 89 | // In .css files, svg is loaded as a data URI. 90 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 91 | issuer: /\.css$/, 92 | use: { 93 | loader: 'svg-url-loader', 94 | options: { encoding: 'none', limit: 10000 } 95 | } 96 | }, 97 | { 98 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 99 | issuer: /\.tsx$/, 100 | use: [ 101 | '@svgr/webpack' 102 | ], 103 | }, 104 | { 105 | // In .ts and .tsx files (both of which compile to .js), svg files 106 | // must be loaded as a raw string instead of data URIs. 107 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 108 | issuer: /\.js$/, 109 | use: { 110 | loader: 'raw-loader' 111 | } 112 | }, 113 | { 114 | test: /\.(png|jpg|jpeg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 115 | use: [{ loader: 'url-loader', options: { limit: 10000 } }], 116 | }, 117 | // Ship the JupyterLite service worker. 118 | { 119 | resourceQuery: /text/, 120 | type: 'asset/resource', 121 | generator: { 122 | filename: '[name][ext]', 123 | }, 124 | }, 125 | // Rule for pyodide kernel 126 | { 127 | test: /pypi\/.*/, 128 | type: 'asset/resource', 129 | generator: { 130 | filename: 'pypi/[name][ext][query]', 131 | }, 132 | }, 133 | { 134 | test: /pyodide-kernel-extension\/schema\/.*/, 135 | type: 'asset/resource', 136 | generator: { 137 | filename: 'schema/[name][ext][query]', 138 | }, 139 | } 140 | ] 141 | }, 142 | plugins: [ 143 | !IS_PRODUCTION ? 144 | new webpack.ProvidePlugin({ 145 | process: 'process/browser' 146 | }) 147 | : 148 | new webpack.ProvidePlugin({ 149 | process: 'process/browser' 150 | }), 151 | new BundleAnalyzerPlugin({ 152 | analyzerMode: IS_PRODUCTION ? "static" : "disabled", // server, static, json, disabled. 153 | openAnalyzer: false, 154 | generateStatsFile: false, 155 | }), 156 | new HtmlWebpackPlugin({ 157 | template: "./public/index.html", 158 | }), 159 | ], 160 | }; 161 | -------------------------------------------------------------------------------- /webpack.lab-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 Datalayer, Inc. 3 | * 4 | * MIT License 5 | */ 6 | 7 | module.exports = { 8 | module: { 9 | rules: [ 10 | // Rule to deal with the service-worker.ts file 11 | // It will include the transpiled file as a text file named `[name][ext]` 12 | // That file is available from the static folder of this extension. That 13 | // requires to overwrite the `workerUrl` in '@datalayer/jupyter-kernels:browser-service-worker' 14 | // see https://github.com/jupyterlite/jupyterlite/blob/1a1bbcaab83f3c56fde6747a8c9b83d3c2a9eb97/packages/server/src/tokens.ts#L5 15 | { 16 | resourceQuery: /text/, 17 | type: 'asset/resource', 18 | generator: { 19 | filename: 'lite-[name][ext]', 20 | }, 21 | }, 22 | // Rules for pyodide kernel assets 23 | { 24 | test: /pypi\/.*/, 25 | type: 'asset/resource', 26 | generator: { 27 | filename: 'pypi/[name][ext][query]', 28 | }, 29 | }, 30 | { 31 | test: /pyodide-kernel-extension\/schema\/.*/, 32 | type: 'asset/resource', 33 | generator: { 34 | filename: 'schema/[name][ext][query]', 35 | }, 36 | }, 37 | ], 38 | }, 39 | }; 40 | --------------------------------------------------------------------------------