Skip to content

Commit f38e2b9

Browse files
committed
Figure out from the server the widgets version that should be injected
This has the downside of not letting the kernel decide the widgets version, which is a regression from Voila 0.4.x. But that should be fine.
1 parent 23fed69 commit f38e2b9

File tree

6 files changed

+88
-113
lines changed

6 files changed

+88
-113
lines changed

packages/voila/src/main.ts

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
IFederatedExtensionData,
2121
activePlugins,
2222
createModule,
23-
isIpywidgets7extension,
2423
loadComponent,
2524
shouldUseMathJax2
2625
} from './tools';
@@ -75,52 +74,7 @@ async function main() {
7574
})
7675
);
7776

78-
console.log('lab extensions!', extensions);
79-
80-
// Extract out @voila-dashboards/widget-manager packages
81-
// we'll include them back later depending on the requested version
82-
const widgetsManager7Extension = extensions.splice(
83-
extensions.findIndex(
84-
(ext) =>
85-
ext.status === 'fulfilled' &&
86-
ext.value.name === '@voila-dashboards/widgets-manager7'
87-
),
88-
1
89-
)[0];
90-
const widgetsManager8Extension = extensions.splice(
91-
extensions.findIndex(
92-
(ext) =>
93-
ext.status === 'fulfilled' &&
94-
ext.value.name === '@voila-dashboards/widgets-manager8'
95-
),
96-
1
97-
)[0];
98-
const officialWidgetsManagerExtension = extensions.splice(
99-
extensions.findIndex(
100-
(ext) =>
101-
ext.status === 'fulfilled' &&
102-
ext.value.name === '@jupyter-widgets/jupyterlab-manager'
103-
),
104-
1
105-
)[0];
106-
// Load @jupyter-widgets/jupyterlab-manager if it's there, and spot if it's widgets 7 or 8
107-
if (
108-
officialWidgetsManagerExtension &&
109-
officialWidgetsManagerExtension.status === 'fulfilled'
110-
) {
111-
const ext = officialWidgetsManagerExtension.value;
112-
113-
if (ext.extension) {
114-
const module = await createModule(ext.name, ext.extension);
115-
if (isIpywidgets7extension(module)) {
116-
extensions.push(widgetsManager7Extension);
117-
} else {
118-
extensions.push(widgetsManager8Extension);
119-
// Also bring back the official extension which registers the widgets
120-
extensions.push(officialWidgetsManagerExtension);
121-
}
122-
}
123-
}
77+
console.log('labextensions data', extensionData, extensions);
12478

12579
extensions.forEach((p) => {
12680
if (p.status === 'rejected') {
@@ -187,6 +141,7 @@ async function main() {
187141
});
188142
app.registerPluginModules(mods);
189143
await app.start();
144+
console.log('__webpack_share_scopes__.default', __webpack_share_scopes__.default);
190145
window.jupyterapp = app;
191146
}
192147

packages/voila/src/plugins/outputs/plugins.ts

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,56 +35,60 @@ export const renderOutputsPlugin: JupyterFrontEndPlugin<void> = {
3535
app: JupyterFrontEnd,
3636
rendermime: IRenderMimeRegistry
3737
): Promise<void> => {
38-
// TODO: Typeset a fake element to get MathJax loaded, remove this hack once
39-
// MathJax 2 is removed.
40-
await rendermime.latexTypesetter?.typeset(document.createElement('div'));
41-
42-
// Render latex in markdown cells
43-
const mdOutput = document.body.querySelectorAll('div.jp-MarkdownOutput');
44-
mdOutput.forEach((md) => {
45-
rendermime.latexTypesetter?.typeset(md as HTMLElement);
46-
});
47-
// Render code cell
48-
const cellOutputs = document.body.querySelectorAll(
49-
'script[type="application/vnd.voila.cell-output+json"]'
50-
);
51-
cellOutputs.forEach(async (cellOutput) => {
52-
const model = JSON.parse(cellOutput.innerHTML);
53-
54-
const mimeType = rendermime.preferredMimeType(model.data, 'any');
55-
56-
if (!mimeType) {
57-
return null;
58-
}
59-
const output = rendermime.createRenderer(mimeType);
60-
output.renderModel(model).catch((error) => {
61-
// Manually append error message to output
62-
const pre = document.createElement('pre');
63-
pre.textContent = `Javascript Error: ${error.message}`;
64-
output.node.appendChild(pre);
65-
66-
// Remove mime-type-specific CSS classes
67-
pre.className = 'lm-Widget jp-RenderedText';
68-
pre.setAttribute('data-mime-type', 'application/vnd.jupyter.stderr');
38+
app.started.then(() => {
39+
// TODO: Typeset a fake element to get MathJax loaded, remove this hack once
40+
// MathJax 2 is removed.
41+
rendermime.latexTypesetter?.typeset(document.createElement('div'));
42+
43+
// Render latex in markdown cells
44+
const mdOutput = document.body.querySelectorAll('div.jp-MarkdownOutput');
45+
mdOutput.forEach((md) => {
46+
rendermime.latexTypesetter?.typeset(md as HTMLElement);
6947
});
48+
// Render code cell
49+
const cellOutputs = document.body.querySelectorAll(
50+
'script[type="application/vnd.voila.cell-output+json"]'
51+
);
52+
cellOutputs.forEach(async (cellOutput) => {
53+
const model = JSON.parse(cellOutput.innerHTML);
54+
55+
const mimeType = rendermime.preferredMimeType(model.data, 'any');
56+
console.log('mimetype', mimeType, rendermime);
7057

71-
output.addClass('jp-OutputArea-output');
58+
if (!mimeType) {
59+
return null;
60+
}
61+
const output = rendermime.createRenderer(mimeType);
62+
console.log('rendering model', model);
63+
output.renderModel(model).catch((error) => {
64+
// Manually append error message to output
65+
const pre = document.createElement('pre');
66+
pre.textContent = `Javascript Error: ${error.message}`;
67+
output.node.appendChild(pre);
7268

73-
if (cellOutput.parentElement) {
74-
const container = cellOutput.parentElement;
69+
// Remove mime-type-specific CSS classes
70+
pre.className = 'lm-Widget jp-RenderedText';
71+
pre.setAttribute('data-mime-type', 'application/vnd.jupyter.stderr');
72+
});
7573

76-
container.removeChild(cellOutput);
74+
output.addClass('jp-OutputArea-output');
7775

78-
// Attach output
79-
Widget.attach(output, container);
80-
}
81-
});
76+
if (cellOutput.parentElement) {
77+
const container = cellOutput.parentElement;
8278

83-
const node = document.getElementById('rendered_cells');
84-
if (node) {
85-
const cells = new RenderedCells({ node });
86-
app.shell.add(cells, 'main');
87-
}
79+
container.removeChild(cellOutput);
80+
81+
// Attach output
82+
Widget.attach(output, container);
83+
}
84+
});
85+
86+
const node = document.getElementById('rendered_cells');
87+
if (node) {
88+
const cells = new RenderedCells({ node });
89+
app.shell.add(cells, 'main');
90+
}
91+
})
8892
}
8993
};
9094

packages/voila/src/tools.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,6 @@ export async function createModule(
3838
}
3939
}
4040

41-
export function isIpywidgets7extension(extension: any) {
42-
// Handle commonjs or es2015 modules
43-
let exports;
44-
if (Object.prototype.hasOwnProperty.call(extension, '__esModule')) {
45-
exports = extension.default;
46-
} else {
47-
// CommonJS exports.
48-
exports = extension;
49-
}
50-
51-
const plugins = Array.isArray(exports) ? exports : [exports];
52-
const pluginIds = plugins.map((plugin) => {
53-
return plugin.id;
54-
});
55-
56-
return (
57-
pluginIds.includes('@jupyter-widgets/jupyterlab-manager:plugin') &&
58-
pluginIds.length === 1
59-
);
60-
}
61-
6241
/**
6342
* Iterate over active plugins in an extension.
6443
*

packages/widgets_manager7/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const widgetManager: JupyterFrontEndPlugin<IJupyterWidgetRegistry> = {
9999
);
100100
(app as any).widgetManager = manager;
101101

102+
console.log('manager promise!', manager);
102103
rendermime.removeMimeType(WIDGET_MIMETYPE);
103104
rendermime.addFactory(
104105
{

packages/widgets_manager7/src/manager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { WidgetModel } from '@jupyter-widgets/base';
22
import { WidgetManager } from '@jupyter-widgets/jupyterlab-manager';
33
import { ISignal, Signal } from '@lumino/signaling';
4+
import { INotebookModel } from '@jupyterlab/notebook';
45

56
export class VoilaWidgetManager extends WidgetManager {
67
register_model(model_id: string, modelPromise: Promise<WidgetModel>): void {
@@ -21,6 +22,10 @@ export class VoilaWidgetManager extends WidgetManager {
2122
this._registeredModels.delete(modelId);
2223
}
2324

25+
restoreWidgets(notebook: INotebookModel): Promise<void> {
26+
return Promise.resolve();
27+
}
28+
2429
private _modelRegistered = new Signal<VoilaWidgetManager, string>(this);
2530
private _registeredModels = new Set<string>();
2631
}

voila/utils.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
from copy import deepcopy
1717
from functools import partial
1818
from pathlib import Path
19+
from packaging.version import Version
1920
from typing import Awaitable, Dict, List
2021

2122
import websockets
2223
from jupyter_core.paths import jupyter_path
2324
from jupyter_server.config_manager import recursive_update
2425
from jupyter_server.utils import url_path_join
2526
from jupyterlab_server.config import get_page_config as gpc
27+
from jupyterlab_server.config import get_federated_extensions
2628
from markupsafe import Markup
2729

2830
from ._version import __version__
@@ -137,16 +139,45 @@ def get_page_config(base_url, settings, log, voila_configuration: VoilaConfigura
137139
required_extensions = []
138140
federated_extensions = deepcopy(page_config["federated_extensions"])
139141

140-
page_config["federated_extensions"] = filter_extension(
142+
filtered_extensions = filter_extension(
141143
federated_extensions=federated_extensions,
142144
disabled_extensions=disabled_extensions,
143145
required_extensions=required_extensions,
144146
extension_allowlist=voila_configuration.extension_allowlist,
145147
extension_denylist=voila_configuration.extension_denylist,
146148
)
149+
150+
extensions = maybe_inject_widgets_manager_extension(filtered_extensions, labextensions_path)
151+
152+
page_config["federated_extensions"] = extensions
147153
return page_config
148154

149155

156+
def maybe_inject_widgets_manager_extension(federated_extensions: List[Dict], labextensions_path: List[str]):
157+
"""If the @jupyter-widgets/jupyterlab-manager is installed on the server. Inject our own manager."""
158+
labextensions = get_federated_extensions(labextensions_path)
159+
160+
if '@jupyter-widgets/jupyterlab-manager' not in labextensions:
161+
return federated_extensions
162+
163+
widgets_version = labextensions['@jupyter-widgets/jupyterlab-manager']['version']
164+
165+
if Version(widgets_version) >= Version('5.0.0'):
166+
# ipywidgets 8 or more, remove widgets-manager7
167+
return [
168+
x
169+
for x in federated_extensions
170+
if x["name"] != '@voila-dashboards/widgets-manager7'
171+
]
172+
else:
173+
# ipywidgets 7, remove widgets-manager8
174+
return [
175+
x
176+
for x in federated_extensions
177+
if x["name"] != '@voila-dashboards/widgets-manager8'
178+
]
179+
180+
150181
def filter_extension(
151182
federated_extensions: List[Dict],
152183
disabled_extensions: List[str] = [],

0 commit comments

Comments
 (0)