diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 77fa9fe..df164e7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,6 +18,9 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.10' + - name: Setup extension + run: | + bash ./setup.sh - name: Install the dependencies run: | python -m pip install -r requirements.txt diff --git a/.gitignore b/.gitignore index b661992..4936b85 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,6 @@ dmypy.json # jupyterlite *.doit.db _output +.idea +.venv +.yarn diff --git a/README.md b/README.md index f8d0424..a876f56 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,12 @@ For more info, keep an eye on the JupyterLite documentation: - How-to Guides: https://jupyterlite.readthedocs.io/en/latest/howto/index.html - Reference: https://jupyterlite.readthedocs.io/en/latest/reference/index.html + +## Additional Notes + +From Team Mat3ra: + +- `data_bridge` extensions is built using the `setup.sh` +- pass `INSTALL=1 BUILD=1` to also build and install the jupyter lite with extension +- `requirements.txt` is updated as part of the above to include the extension +- requires `pyenv` and `npm` installed diff --git a/extensions/src/data_bridge/index.ts b/extensions/src/data_bridge/index.ts new file mode 100644 index 0000000..d54f59a --- /dev/null +++ b/extensions/src/data_bridge/index.ts @@ -0,0 +1,71 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin, +} from "@jupyterlab/application"; + +import { NotebookPanel } from "@jupyterlab/notebook"; + +/** + * Initialization data for the data-bridge extension. + * Similar to https://jupyterlite.readthedocs.io/en/latest/howto/configure/advanced/iframe.html + */ +const plugin: JupyterFrontEndPlugin = { + id: "data-bridge:plugin", + description: + "Extension to pass JSON data between host page and Jupyter Lite instance", + autoStart: true, + activate: async (app: JupyterFrontEnd) => { + console.log("JupyterLab extension data-bridge is activated!"); + + // @ts-ignore + window.sendDataToHost = (data: any) => { + window.parent.postMessage( + { + type: "from-iframe-to-host", + data: data, + }, + "*" + ); + }; + + // @ts-ignore + window.requestDataFromHost = () => { + window.parent.postMessage( + { + type: "from-iframe-to-host", + requestData: true, + }, + "*" + ); + }; + + // TODO: set type for data + window.addEventListener("message", async (event) => { + if (event.data.type === "from-host-to-iframe") { + let data = event.data.data; + const dataJson = JSON.stringify(data); + const code = ` + import json + data = json.loads('${dataJson}') + `; + // Similar to https://jupyterlab.readthedocs.io/en/stable/api/classes/application.LabShell.html#currentWidget + // https://jupyterlite.readthedocs.io/en/latest/reference/api/ts/interfaces/jupyterlite_application.ISingleWidgetShell.html#currentwidget + const currentWidget = app.shell.currentWidget; + + if (currentWidget instanceof NotebookPanel) { + const notebookPanel = currentWidget; + const kernel = notebookPanel.sessionContext.session?.kernel; + if (kernel) { + kernel.requestExecute({ code: code }); + } else { + console.error("No active kernel found"); + } + } else { + console.error("Current active widget is not a notebook"); + } + } + }); + }, +}; + +export default plugin; diff --git a/requirements.txt b/requirements.txt index 678147c..d220149 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Core modules (mandatory) jupyterlite-core==0.1.3 -jupyterlab~=3.5.1 +jupyterlab~=4.0.6 # Python kernel (optional) jupyterlite-pyodide-kernel==0.1.3 diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..f6d1d29 --- /dev/null +++ b/setup.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# This script creates a JupyterLab extension using the cookiecutter template +# and updates the requirements.txt file to make it installable in the current +# JupyterLab environment. +# It assumes that pyenv and nvm are installed and configured correctly. + +PYTHON_VERSION="3.10" +NODE_VERSION="18" +EXTENSION_NAME="data_bridge" +COOKIECUTTER_TEMPLATE_PATH="$HOME/.cookiecutters/extension-cookiecutter-ts" +GITHUB_TEMPLATE_URL="https://github.com/jupyterlab/extension-cookiecutter-ts" + +kind="frontend" +author_name="Mat3ra" +author_email="info@mat3ra.com" +labextension_name=$EXTENSION_NAME +python_name=$EXTENSION_NAME +project_short_description="A JupyterLab extension that allows you to send data between notebook and host page" +has_settings=n +has_binder=n +test=n +repository="https://github.com/exabyte-io/jupyter-lite" + +COOKIECUTTER_OPTIONS=( + "$GITHUB_TEMPLATE_URL" + "--no-input" + "kind=$kind" + "author_name=$author_name" + "author_email=$author_email" + "labextension_name=$labextension_name" + "python_name=$python_name" + "project_short_description=$project_short_description" + "has_settings=$has_settings" + "has_binder=$has_binder" + "test=$test" + "repository=$repository" +) + +# Ensure Python and Node.js are installed and switch to the correct versions +if [ ! -d "$HOME/.pyenv/versions/$PYTHON_VERSION" ]; then + pyenv install $PYTHON_VERSION +fi +pyenv local $PYTHON_VERSION + +python -m venv .venv +source .venv/bin/activate + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +nvm install $NODE_VERSION +nvm use $NODE_VERSION + +pip install cookiecutter jupyterlab==4 jupyterlite-core + +# Create directory if it doesn't exist +if [ ! -d "extensions/dist" ]; then + mkdir -p extensions/dist +fi +cd extensions/dist + +# Use cookiecutter with the template path if it exists, otherwise use the URL +if [ ! -d "$COOKIECUTTER_TEMPLATE_PATH" ]; then + cookiecutter "${COOKIECUTTER_OPTIONS[@]}" + echo "Created extension using cookiecutter template." +else + # COOKIECUTTER_OPTIONS[0]="$COOKIECUTTER_TEMPLATE_PATH" + cookiecutter "${COOKIECUTTER_OPTIONS[@]}" + echo "Created extension using cached cookiecutter template." +fi + +# Copy the index.ts file if both source and destination directories exist +SRC_FILE="../src/$EXTENSION_NAME/index.ts" +DEST_DIR="./$EXTENSION_NAME/src" +if [ -f "$SRC_FILE" ] && [ -d "$DEST_DIR" ]; then + cp "$SRC_FILE" "$DEST_DIR/index.ts" +else + echo "Source file or destination directory not found. Skipping copy." +fi + +cd $EXTENSION_NAME +pip install -ve . +jupyter labextension develop --overwrite . + +# Install dependencies +jlpm add @jupyterlab/application +jlpm add @jupyterlab/notebook +jlpm add @exabyte-io/code.js + +# Build the extension +jlpm run build + +cd ../../../ + +# add to requirements.txt +LINE="./extensions/dist/$EXTENSION_NAME" +FILE='requirements.txt' +grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE" + +# Install extension +[[ ! -z $INSTALL ]] && python -m pip install -r requirements.txt + +# Build JupyterLite +[[ ! -z $BUILD ]] && jupyter lite build --contents content --output-dir dist +