Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to a lab-based app for the Voila frontend #846

Merged
merged 9 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .binder/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ dependencies:
- bqplot
- scipy
- ipympl
- ipympl
- xleaflet=0.16.0
- xeus-cling=0.13.0
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ node_modules
coverage
*.map.js
*.bundle.js
*.voila.js

# jetbrains IDE stuff
.idea/
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ config.rst
package-lock.json

share/jupyter/voila/templates/base/static/*voila.js
share/jupyter/voila/templates/base/static/*[woff|woff2|eot|svg]
share/jupyter/voila/templates/base/static/*.[woff|woff2|eot|svg]

share/jupyter/voila/templates/classic/static/labvariables.css
share/jupyter/voila/templates/classic/static/materialcolors.css
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ node_modules
build
notebooks/
.vscode/
.pytest_cache

ui-tests/playwright-report
ui-tests/playwright-report
17 changes: 17 additions & 0 deletions docs/source/contribute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ To lint the packages:
# run prettier
jlpm run prettier

About the Voila Frontend
========================

The Voila frontend is built as a JupyterLab-based application using JupyterLab components.

This makes it possible to reuse existing plugins and extensions for Jupyterlab such as core JupyterLab plugins like the JSON viewer,
as well as third-party mime renderers like the `FASTA viewer <https://github.com/jupyterlab/jupyter-renderers>`_.

The Voila frontend is able to load existing JupyterLab extensions installed as prebuilt extensions under ``${PREFIX}/share/labextensions``,
similar to the way it works in JupyterLab.

These extensions are typically distributed via ``pip`` and ``conda`` packages and can easily be installed by end users without requiring Node.js.
Widget packages usually now include a prebuilt extension for JupyterLab 3.0 by default, which should automatically work in Voila too.

Check out the `JupyterLab Documentation on prebuilt extensions <https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#prebuilt-extensions>`_ for more info.

The code for the frontend is located under ``packages/voila``, with support for loading federated extensions in ``packages/voila/index.js``.

Tests
=====
Expand Down
22 changes: 0 additions & 22 deletions docs/source/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,25 +132,3 @@ The examples can then be served with:

cd notebooks/
voila


Using third-party Widgets with Voilà
====================================

By default, Voilà doesn't serve Jupyter Widgets installed as a classic notebook extension (nbextension).

Instead, it fallbacks to fetching the files from a CDN. This might result in an error (404) in case the
custom widget has not been published to ``npm``, or when Voilà runs in an environment without an Internet
connection.

To let the Voilà standalone app serve the nbextensions, use the ``enable_nbextensions`` flag as follows:

.. code-block:: bash

voila --enable_nbextensions=True

When using Voilà as a server extension:

.. code-block:: bash

jupyter notebook --VoilaConfiguration.enable_nbextensions=True
159 changes: 159 additions & 0 deletions notebooks/mimerenderers.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# JSON"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ipywidgets import Button, Output\n",
"from IPython import display\n",
"\n",
"button = Button(description='Output JSON')\n",
"output = Output()\n",
"obj = {\n",
" \"abcde\": 1234,\n",
" \"nested\": list(range(10))\n",
"}\n",
"\n",
"@output.capture()\n",
"def on_click(change):\n",
" display.display(display.JSON(obj))\n",
" \n",
" \n",
"button.on_click(on_click)\n",
"display.display(button)\n",
"output"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"display.JSON(obj)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Fasta"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fasta_button = Button(description='Output FASTA')\n",
"fasta_output = Output()\n",
"\n",
"\n",
"def Fasta(data=''):\n",
" bundle = {}\n",
" bundle['application/vnd.fasta.fasta'] = data\n",
" bundle['text/plain'] = data\n",
" display.display(bundle, raw=True)\n",
" \n",
" \n",
"@fasta_output.capture()\n",
"def on_click(change):\n",
" Fasta(\"\"\">SEQUENCE_1\n",
"MTEITAAMVKELRESTGAGMMDCKNALSETNGDFDKAVQLLREKGLGKAAKKADRLAAEG\n",
"LVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYKALVAELEKENEERRRLKDPNKPEHK\n",
"IPQFASRKQLSDAILKEAEEKIKEELKAQGKPEKIWDNIIPGKMNSFIADNSQLDSKLTL\n",
"MGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAAEVAAQL\n",
">SEQUENCE_2\n",
"SATVSEINSETDFVAKNDQFIALTKDTTAHIQSNSLQSVEELHSSTINGVKFEEYLKSQI\n",
"ATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH\"\"\")\n",
"\n",
"\n",
"fasta_button.on_click(on_click)\n",
"display.display(fasta_button)\n",
"fasta_output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# GeoJSON"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import GeoJSON\n",
"\n",
"\n",
"geojson_button = Button(description='Output GeoJSON')\n",
"geojson_output = Output()\n",
"\n",
" \n",
"@geojson_output.capture()\n",
"def on_click(change):\n",
" obj = GeoJSON({\n",
" \"type\": \"Feature\",\n",
" \"geometry\": {\n",
" \"type\": \"Point\",\n",
" \"coordinates\": [-118.4563712, 34.0163116]\n",
" }\n",
" })\n",
" display.display(obj)\n",
" \n",
"\n",
"geojson_button.on_click(on_click)\n",
"display.display(geojson_button)\n",
"geojson_output"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
4 changes: 2 additions & 2 deletions packages/jupyterlab-preview/src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export class VoilaPreview extends DocumentWidget<IFrame, INotebookModel> {
* Reload the preview.
*/
reload(): void {
const iframe = this.content.node.querySelector('iframe')!;
if (iframe.contentWindow) {
const iframe = this.content.node.querySelector('iframe');
if (iframe && iframe.contentWindow) {
iframe.contentWindow.location.reload();
}
}
Expand Down
62 changes: 43 additions & 19 deletions packages/voila/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "@voila-dashboards/voila",
"private": true,
"version": "0.4.0",
"description": "The Voilà Frontend",
"author": "Voilà contributors",
Expand All @@ -11,44 +10,69 @@
"@jupyter-widgets/base": "^6.0.1",
"@jupyter-widgets/controls": "^5.0.1",
"@jupyter-widgets/jupyterlab-manager": "^5.0.3",
"@jupyter-widgets/output": "^6.0.1",
"@jupyterlab/application": "^3.0.0",
"@jupyterlab/apputils": "^3.0.0",
"@jupyterlab/coreutils": "^5.0.0",
"@jupyterlab/docregistry": "^3.0.0",
"@jupyterlab/javascript-extension": "~3.0.0",
"@jupyterlab/json-extension": "^3.0.0",
"@jupyterlab/logconsole": "^3.0.0",
"@jupyterlab/mainmenu": "^3.0.0",
"@jupyterlab/markdownviewer-extension": "^3.0.0",
"@jupyterlab/mathjax2-extension": "^3.0.0",
"@jupyterlab/nbformat": "^3.0.0",
"@jupyterlab/notebook": "^3.0.0",
"@jupyterlab/outputarea": "^3.0.0",
"@jupyterlab/rendermime": "^3.0.0",
"@jupyterlab/rendermime-extension": "^3.0.0",
"@jupyterlab/services": "^6.1.8",
"@lumino/algorithm": "^1.3.3",
"@lumino/commands": "^1.12.0",
"@lumino/domutils": "^1.2.3",
"@lumino/messaging": "^1.4.3",
"@lumino/signaling": "^1.4.3",
"@lumino/virtualdom": "^1.8.0",
"@lumino/widgets": "^1.18.0",
"mathjax-full": "^3.0.0"
"@jupyterlab/settingregistry": "^3.0.0",
"@jupyterlab/translation": "^3.0.0",
"@jupyterlab/ui-components": "^3.0.0",
"@lumino/algorithm": "^1.6.2",
"@lumino/commands": "^1.15.2",
"@lumino/coreutils": "^1.8.2",
"@lumino/disposable": "^1.7.2",
"@lumino/domutils": "^1.5.2",
"@lumino/dragdrop": "^1.10.2",
"@lumino/messaging": "^1.7.2",
"@lumino/properties": "^1.5.2",
"@lumino/signaling": "^1.7.2",
"@lumino/virtualdom": "^1.11.2",
"@lumino/widgets": "^1.26.2",
"react": "^17.0.1"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@jupyterlab/builder": "^3.0.0",
"@types/node": "^18.8.3",
"babel-loader": "^8.0.5",
"css-loader": "^5.0.0",
"file-loader": "^4.0.0",
"css-loader": "~5.0.2",
"file-loader": "^6.2.0",
"fs-extra": "^9.1.0",
"glob": "~7.1.6",
"mini-css-extract-plugin": "~0.9.0",
"npm-run-all": "^4.1.5",
"p-limit": "^2.2.2",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"style-loader": "^2.0.0",
"svg-url-loader": "^7.1.1",
"typescript": "~4.1.3",
"url-loader": "^1.0.0",
"webpack": "^4.29.3",
"webpack-cli": "^3.2.3"
"url-loader": "^4.1.1",
"watch": "^1.0.2",
"webpack": "^5.24.1",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.5.0",
"webpack-merge": "^5.7.3",
"whatwg-fetch": "^3.0.0"
},
"style": "style/index.css",
"scripts": {
"build": "npm run build:lib && webpack",
"build": "npm run build:lib && webpack --mode=development",
"build:lib": "tsc",
"build:prod": "npm run build:lib && webpack --production",
"clean": "jlpm run clean:lib",
"build:prod": "npm run build:lib && webpack --mode=production",
"clean": "jlpm run clean:lib && rimraf build",
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "npm-run-all -p watch:*",
Expand Down
37 changes: 37 additions & 0 deletions packages/voila/publicpath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// We dynamically set the webpack public path based on the page config
// settings from the JupyterLab app. We copy some of the pageconfig parsing
// logic in @jupyterlab/coreutils below, since this must run before any other
// files are loaded (including @jupyterlab/coreutils).

/**
* Get global configuration data for the Jupyter application.
*
* @param name - The name of the configuration option.
*
* @returns The config value or an empty string if not found.
*
* #### Notes
* All values are treated as strings.
* For browser based applications, it is assumed that the page HTML
* includes a script tag with the id `jupyter-config-data` containing the
* configuration as valid JSON. In order to support the classic Notebook,
* we fall back on checking for `body` data of the given `name`.
*/
function getOption(name) {
let configData = Object.create(null);
// Use script tag if available.
if (typeof document !== 'undefined' && document) {
const el = document.getElementById('jupyter-config-data');

if (el) {
configData = JSON.parse(el.textContent || '{}');
}
}
return configData[name] || '';
}

// eslint-disable-next-line no-undef
__webpack_public_path__ = getOption('fullStaticUrl') + '/';
Loading