diff --git a/packages/voila/src/main.ts b/packages/voila/src/main.ts index 876fafbc9..3ffcfa7fb 100644 --- a/packages/voila/src/main.ts +++ b/packages/voila/src/main.ts @@ -20,7 +20,6 @@ import { IFederatedExtensionData, activePlugins, createModule, - isIpywidgets7extension, loadComponent, shouldUseMathJax2 } from './tools'; @@ -75,52 +74,7 @@ async function main() { }) ); - console.log('lab extensions!', extensions); - - // Extract out @voila-dashboards/widget-manager packages - // we'll include them back later depending on the requested version - const widgetsManager7Extension = extensions.splice( - extensions.findIndex( - (ext) => - ext.status === 'fulfilled' && - ext.value.name === '@voila-dashboards/widgets-manager7' - ), - 1 - )[0]; - const widgetsManager8Extension = extensions.splice( - extensions.findIndex( - (ext) => - ext.status === 'fulfilled' && - ext.value.name === '@voila-dashboards/widgets-manager8' - ), - 1 - )[0]; - const officialWidgetsManagerExtension = extensions.splice( - extensions.findIndex( - (ext) => - ext.status === 'fulfilled' && - ext.value.name === '@jupyter-widgets/jupyterlab-manager' - ), - 1 - )[0]; - // Load @jupyter-widgets/jupyterlab-manager if it's there, and spot if it's widgets 7 or 8 - if ( - officialWidgetsManagerExtension && - officialWidgetsManagerExtension.status === 'fulfilled' - ) { - const ext = officialWidgetsManagerExtension.value; - - if (ext.extension) { - const module = await createModule(ext.name, ext.extension); - if (isIpywidgets7extension(module)) { - extensions.push(widgetsManager7Extension); - } else { - extensions.push(widgetsManager8Extension); - // Also bring back the official extension which registers the widgets - extensions.push(officialWidgetsManagerExtension); - } - } - } + console.log('labextensions data', extensionData, extensions); extensions.forEach((p) => { if (p.status === 'rejected') { @@ -187,6 +141,7 @@ async function main() { }); app.registerPluginModules(mods); await app.start(); + console.log('__webpack_share_scopes__.default', __webpack_share_scopes__.default); window.jupyterapp = app; } diff --git a/packages/voila/src/plugins/outputs/plugins.ts b/packages/voila/src/plugins/outputs/plugins.ts index f8918bdeb..9a5a8e722 100644 --- a/packages/voila/src/plugins/outputs/plugins.ts +++ b/packages/voila/src/plugins/outputs/plugins.ts @@ -35,56 +35,60 @@ export const renderOutputsPlugin: JupyterFrontEndPlugin = { app: JupyterFrontEnd, rendermime: IRenderMimeRegistry ): Promise => { - // TODO: Typeset a fake element to get MathJax loaded, remove this hack once - // MathJax 2 is removed. - await rendermime.latexTypesetter?.typeset(document.createElement('div')); - - // Render latex in markdown cells - const mdOutput = document.body.querySelectorAll('div.jp-MarkdownOutput'); - mdOutput.forEach((md) => { - rendermime.latexTypesetter?.typeset(md as HTMLElement); - }); - // Render code cell - const cellOutputs = document.body.querySelectorAll( - 'script[type="application/vnd.voila.cell-output+json"]' - ); - cellOutputs.forEach(async (cellOutput) => { - const model = JSON.parse(cellOutput.innerHTML); - - const mimeType = rendermime.preferredMimeType(model.data, 'any'); - - if (!mimeType) { - return null; - } - const output = rendermime.createRenderer(mimeType); - output.renderModel(model).catch((error) => { - // Manually append error message to output - const pre = document.createElement('pre'); - pre.textContent = `Javascript Error: ${error.message}`; - output.node.appendChild(pre); - - // Remove mime-type-specific CSS classes - pre.className = 'lm-Widget jp-RenderedText'; - pre.setAttribute('data-mime-type', 'application/vnd.jupyter.stderr'); + app.started.then(() => { + // TODO: Typeset a fake element to get MathJax loaded, remove this hack once + // MathJax 2 is removed. + rendermime.latexTypesetter?.typeset(document.createElement('div')); + + // Render latex in markdown cells + const mdOutput = document.body.querySelectorAll('div.jp-MarkdownOutput'); + mdOutput.forEach((md) => { + rendermime.latexTypesetter?.typeset(md as HTMLElement); }); + // Render code cell + const cellOutputs = document.body.querySelectorAll( + 'script[type="application/vnd.voila.cell-output+json"]' + ); + cellOutputs.forEach(async (cellOutput) => { + const model = JSON.parse(cellOutput.innerHTML); + + const mimeType = rendermime.preferredMimeType(model.data, 'any'); + console.log('mimetype', mimeType, rendermime); - output.addClass('jp-OutputArea-output'); + if (!mimeType) { + return null; + } + const output = rendermime.createRenderer(mimeType); + console.log('rendering model', model); + output.renderModel(model).catch((error) => { + // Manually append error message to output + const pre = document.createElement('pre'); + pre.textContent = `Javascript Error: ${error.message}`; + output.node.appendChild(pre); - if (cellOutput.parentElement) { - const container = cellOutput.parentElement; + // Remove mime-type-specific CSS classes + pre.className = 'lm-Widget jp-RenderedText'; + pre.setAttribute('data-mime-type', 'application/vnd.jupyter.stderr'); + }); - container.removeChild(cellOutput); + output.addClass('jp-OutputArea-output'); - // Attach output - Widget.attach(output, container); - } - }); + if (cellOutput.parentElement) { + const container = cellOutput.parentElement; - const node = document.getElementById('rendered_cells'); - if (node) { - const cells = new RenderedCells({ node }); - app.shell.add(cells, 'main'); - } + container.removeChild(cellOutput); + + // Attach output + Widget.attach(output, container); + } + }); + + const node = document.getElementById('rendered_cells'); + if (node) { + const cells = new RenderedCells({ node }); + app.shell.add(cells, 'main'); + } + }) } }; diff --git a/packages/voila/src/tools.ts b/packages/voila/src/tools.ts index 70af2314f..733bbb5dc 100644 --- a/packages/voila/src/tools.ts +++ b/packages/voila/src/tools.ts @@ -38,27 +38,6 @@ export async function createModule( } } -export function isIpywidgets7extension(extension: any) { - // Handle commonjs or es2015 modules - let exports; - if (Object.prototype.hasOwnProperty.call(extension, '__esModule')) { - exports = extension.default; - } else { - // CommonJS exports. - exports = extension; - } - - const plugins = Array.isArray(exports) ? exports : [exports]; - const pluginIds = plugins.map((plugin) => { - return plugin.id; - }); - - return ( - pluginIds.includes('@jupyter-widgets/jupyterlab-manager:plugin') && - pluginIds.length === 1 - ); -} - /** * Iterate over active plugins in an extension. * diff --git a/packages/widgets_manager7/src/index.ts b/packages/widgets_manager7/src/index.ts index 5bc84ed9e..d418835e6 100644 --- a/packages/widgets_manager7/src/index.ts +++ b/packages/widgets_manager7/src/index.ts @@ -99,6 +99,7 @@ const widgetManager: JupyterFrontEndPlugin = { ); (app as any).widgetManager = manager; + console.log('manager promise!', manager); rendermime.removeMimeType(WIDGET_MIMETYPE); rendermime.addFactory( { diff --git a/packages/widgets_manager7/src/manager.ts b/packages/widgets_manager7/src/manager.ts index 557bb3372..cdb9c9c4b 100644 --- a/packages/widgets_manager7/src/manager.ts +++ b/packages/widgets_manager7/src/manager.ts @@ -1,6 +1,7 @@ import { WidgetModel } from '@jupyter-widgets/base'; import { WidgetManager } from '@jupyter-widgets/jupyterlab-manager'; import { ISignal, Signal } from '@lumino/signaling'; +import { INotebookModel } from '@jupyterlab/notebook'; export class VoilaWidgetManager extends WidgetManager { register_model(model_id: string, modelPromise: Promise): void { @@ -21,6 +22,10 @@ export class VoilaWidgetManager extends WidgetManager { this._registeredModels.delete(modelId); } + restoreWidgets(notebook: INotebookModel): Promise { + return Promise.resolve(); + } + private _modelRegistered = new Signal(this); private _registeredModels = new Set(); } diff --git a/voila/utils.py b/voila/utils.py index 97c84cefb..2ee1d1313 100644 --- a/voila/utils.py +++ b/voila/utils.py @@ -16,6 +16,7 @@ from copy import deepcopy from functools import partial from pathlib import Path +from packaging.version import Version from typing import Awaitable, Dict, List import websockets @@ -23,6 +24,7 @@ from jupyter_server.config_manager import recursive_update from jupyter_server.utils import url_path_join from jupyterlab_server.config import get_page_config as gpc +from jupyterlab_server.config import get_federated_extensions from markupsafe import Markup from ._version import __version__ @@ -137,16 +139,45 @@ def get_page_config(base_url, settings, log, voila_configuration: VoilaConfigura required_extensions = [] federated_extensions = deepcopy(page_config["federated_extensions"]) - page_config["federated_extensions"] = filter_extension( + filtered_extensions = filter_extension( federated_extensions=federated_extensions, disabled_extensions=disabled_extensions, required_extensions=required_extensions, extension_allowlist=voila_configuration.extension_allowlist, extension_denylist=voila_configuration.extension_denylist, ) + + extensions = maybe_inject_widgets_manager_extension(filtered_extensions, labextensions_path) + + page_config["federated_extensions"] = extensions return page_config +def maybe_inject_widgets_manager_extension(federated_extensions: List[Dict], labextensions_path: List[str]): + """If the @jupyter-widgets/jupyterlab-manager is installed on the server. Inject our own manager.""" + labextensions = get_federated_extensions(labextensions_path) + + if '@jupyter-widgets/jupyterlab-manager' not in labextensions: + return federated_extensions + + widgets_version = labextensions['@jupyter-widgets/jupyterlab-manager']['version'] + + if Version(widgets_version) >= Version('5.0.0'): + # ipywidgets 8 or more, remove widgets-manager7 + return [ + x + for x in federated_extensions + if x["name"] != '@voila-dashboards/widgets-manager7' + ] + else: + # ipywidgets 7, remove widgets-manager8 + return [ + x + for x in federated_extensions + if x["name"] != '@voila-dashboards/widgets-manager8' + ] + + def filter_extension( federated_extensions: List[Dict], disabled_extensions: List[str] = [],