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

HTMLManager: Support both ipywidgets 7 and 8 models #3932

Merged
merged 7 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ ipyleaflet
jupyter-client
jupyter-packaging
jupyterlab >=4
jupyterlite-core >=0.3.0<0.4.0
jupyterlite-pyodide-kernel >=0.3.0<0.4.0
jupyterlite-core >=0.3.0,<0.4.0
jupyterlite-pyodide-kernel >=0.3.0,<0.4.0
matplotlib
myst-nb >=0.17,<0.18
numpy
Expand Down
5 changes: 4 additions & 1 deletion packages/html-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"@fortawesome/fontawesome-free": "^5.12.0",
"@jupyter-widgets/base": "^6.0.8",
"@jupyter-widgets/base-manager": "^1.0.9",
"@jupyter-widgets/base7": "npm:@jupyter-widgets/[email protected]",
"@jupyter-widgets/controls": "^5.0.9",
"@jupyter-widgets/controls7": "npm:@jupyter-widgets/[email protected]",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"@jupyter-widgets/output": "^6.0.8",
"@jupyter-widgets/schema": "^0.5.5",
"@jupyterlab/outputarea": "^3.0.0 || ^4.0.0",
Expand All @@ -47,7 +49,8 @@
"@lumino/messaging": "^1.10.1 || ^2.1",
"@lumino/widgets": "^1.30.0 || ^2.1",
"ajv": "^8.6.0",
"jquery": "^3.1.1"
"jquery": "^3.1.1",
"semver": "^7.3.5"
},
"devDependencies": {
"@types/jquery": "^3.5.16",
Expand Down
11 changes: 10 additions & 1 deletion packages/html-manager/scripts/concat-amd-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
var fs = require('fs');

// Make a script file that defines all of the relevant AMD modules
var files = ['base.js', 'controls.js', 'index.js', 'libembed-amd.js'];
var files = [
'base.js',
'controls.js',
'base7.js',
'controls7.js',
'index.js',
'libembed-amd.js',
];
var output = files
.map((f) => {
return fs.readFileSync('./dist/amd/' + f).toString();
Expand All @@ -17,6 +24,8 @@ fs.writeFileSync('./dist/libembed-amd.js', output);
files = [
'base.js',
'controls.js',
'base7.js',
'controls7.js',
'index.js',
'libembed-amd.js',
'embed-amd-render.js',
Expand Down
54 changes: 49 additions & 5 deletions packages/html-manager/src/htmlmanager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import * as widgets from '@jupyter-widgets/controls';
import { maxSatisfying } from 'semver';
import * as base from '@jupyter-widgets/base';
import * as outputWidgets from './output';
import { ManagerBase } from '@jupyter-widgets/base-manager';
Expand Down Expand Up @@ -38,7 +38,7 @@ export class HTMLManager extends ManagerBase {
window.addEventListener('resize', () => {
this._viewList.forEach((view) => {
MessageLoop.postMessage(
view.luminoWidget,
view.luminoWidget || view.pWidget,
LuminoWidget.Widget.ResizeMessage.UnknownSize
);
});
Expand Down Expand Up @@ -66,7 +66,7 @@ export class HTMLManager extends ManagerBase {
v.render();
}

LuminoWidget.Widget.attach(v.luminoWidget, el);
LuminoWidget.Widget.attach(v.luminoWidget || v.pWidget, el);
this._viewList.add(v);
v.once('remove', () => {
this._viewList.delete(v);
Expand Down Expand Up @@ -112,10 +112,54 @@ export class HTMLManager extends ManagerBase {
moduleVersion: string
): Promise<typeof WidgetModel | typeof WidgetView> {
return new Promise((resolve, reject) => {
if (
moduleName === '@jupyter-widgets/base' ||
moduleName === '@jupyter-widgets/controls'
) {
moduleVersion = `^${moduleVersion}`;
}

if (moduleName === '@jupyter-widgets/base') {
resolve(base);
const best = maxSatisfying(['1.2.0', '2.0.0'], moduleVersion);

if (best === '1.2.0') {
// ipywidgets 7 model
resolve(require('@jupyter-widgets/base7'));
} else {
// ipywidgets 8 model
resolve(require('@jupyter-widgets/base'));
}
} else if (moduleName === '@jupyter-widgets/controls') {
resolve(widgets);
const best = maxSatisfying(['1.5.0', '2.0.0'], moduleVersion);

if (best === '1.5.0') {
// ipywidgets 7 controls JS and CSS
require('@jupyter-widgets/controls7/css/widgets-base.css');

// If lab variables are not found, we set them (we don't want to reset the variables if they are already defined)
if (
getComputedStyle(document.documentElement).getPropertyValue(
'--jp-layout-color0'
) === ''
) {
require('@jupyter-widgets/controls7/css/labvariables.css');
}
resolve(require('@jupyter-widgets/controls7'));
} else {
// ipywidgets 8 controls JS and CSS
require('@jupyter-widgets/controls/css/widgets-base.css');

// If lab variables are not found, we set them (we don't want to reset the variables if they are already defined)
if (
getComputedStyle(document.documentElement).getPropertyValue(
'--jp-layout-color0'
) === ''
) {
require('@jupyter-widgets/controls/css/labvariables.css');
}

resolve(require('@jupyter-widgets/controls'));
}
} else if (moduleName === '@jupyter-widgets/output') {
resolve(outputWidgets);
} else if (this.loader !== undefined) {
Expand Down
20 changes: 10 additions & 10 deletions packages/html-manager/src/libembed-amd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,6 @@

import * as libembed from './libembed';

let cdn = 'https://cdn.jsdelivr.net/npm/';
let onlyCDN = false;

// find the data-cdn for any script tag, assuming it is only used for embed-amd.js
const scripts = document.getElementsByTagName('script');
Array.prototype.forEach.call(scripts, (script: HTMLScriptElement) => {
cdn = script.getAttribute('data-jupyter-widgets-cdn') || cdn;
onlyCDN = onlyCDN || script.hasAttribute('data-jupyter-widgets-cdn-only');
});

/**
* Load a package using requirejs and return a promise
*
Expand All @@ -29,6 +19,16 @@ const requirePromise = function (pkg: string | string[]): Promise<any> {
});
};

let cdn = 'https://cdn.jsdelivr.net/npm/';
let onlyCDN = false;

// find the data-cdn for any script tag, assuming it is only used for embed-amd.js
const scripts = document.getElementsByTagName('script');
Array.prototype.forEach.call(scripts, (script: HTMLScriptElement) => {
cdn = script.getAttribute('data-jupyter-widgets-cdn') || cdn;
onlyCDN = onlyCDN || script.hasAttribute('data-jupyter-widgets-cdn-only');
});

function moduleNameToCDNUrl(moduleName: string, moduleVersion: string): string {
let packageName = moduleName;
let fileName = 'index'; // default filename
Expand Down
10 changes: 0 additions & 10 deletions packages/html-manager/src/libembed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ import '@fortawesome/fontawesome-free/css/all.min.css';
import '@fortawesome/fontawesome-free/css/v4-shims.min.css';

import '@lumino/widgets/style/index.css';
import '@jupyter-widgets/controls/css/widgets-base.css';

// If lab variables are not found, we set them (we don't want to reset the variables if they are already defined)
if (
getComputedStyle(document.documentElement).getPropertyValue(
'--jp-layout-color0'
) === ''
) {
require('@jupyter-widgets/controls/css/labvariables.css');
}

// Used just for the typing. We must not import the javascript because we don't
// want to include it in the require embedding.
Expand Down
2 changes: 1 addition & 1 deletion packages/html-manager/src/output_renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class WidgetRenderer extends Widget implements IRenderMime.IRenderer {
try {
const wModel = await this._manager.get_model(source.model_id);
const wView = await this._manager.create_view(wModel);
Widget.attach(wView.luminoWidget, this.node);
Widget.attach(wView.luminoWidget || wView.pWidget, this.node);
} catch (err) {
console.log('Error displaying widget');
console.log(err);
Expand Down
83 changes: 80 additions & 3 deletions packages/html-manager/test/src/output_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,83 @@ describe('Output widget', function () {
expect(elt.querySelectorAll('table').length).to.equal(1);
});

it('renders widgets ipywidgets 7', async function () {
const modelState = {
_view_module: '@jupyter-widgets/output',
outputs: [
{
output_type: 'display_data',
data: {
'application/vnd.jupyter.widget-view+json': {
model_id: 'adffc4580a0944f6929c381463b0059b',
version_minor: 0,
version_major: 2,
},
'text/plain': 'A Jupyter Widget',
},
metadata: {},
},
],
};

const elt = document.createElement('div');
elt.className = 'widget-subarea';
document.body.appendChild(elt);
const manager = new HTMLManager();

// We need to seed the manager with the state of the widgets
const managerState = {
adffc4580a0944f6929c381463b0059b: {
model_name: 'IntSliderModel',
model_module: '@jupyter-widgets/controls',
model_module_version: '1.5.0',
state: {
style: 'IPY_MODEL_3b8780f457254737a83be48bc32b0613',
_view_module: '@jupyter-widgets/controls',
layout: 'IPY_MODEL_33cb011834fd4c9d9af512e5e98c9904',
value: 45,
_model_module: '@jupyter-widgets/controls',
},
},
'3b8780f457254737a83be48bc32b0613': {
model_name: 'SliderStyleModel',
model_module: '@jupyter-widgets/controls',
model_module_version: '1.5.0',
state: {
description_width: '',
_model_module: '@jupyter-widgets/controls',
},
},
'33cb011834fd4c9d9af512e5e98c9904': {
model_name: 'LayoutModel',
model_module: '@jupyter-widgets/base',
model_module_version: '1.2.0',
state: {},
},
};
await manager.set_state({
state: managerState,
version_major: 2,
version_minor: 0,
});
const modelId = 'u-u-i-d';
const modelCreate: base.IModelOptions = {
model_name: 'OutputModel',
model_id: modelId,
model_module: '@jupyter-widgets/output',
model_module_version: '*',
};
const model = await manager.new_model(modelCreate, modelState);
await manager.display_view(manager.create_view(model), elt);

// Give the widget time to render
await new Promise((resolve) => {
setTimeout(resolve, 20);
});

expect(elt.querySelectorAll('.slider').length).to.equal(1);
});

it('renders widgets', async function () {
const modelState = {
_view_module: '@jupyter-widgets/output',
Expand Down Expand Up @@ -92,7 +169,7 @@ describe('Output widget', function () {
adffc4580a0944f6929c381463b0059b: {
model_name: 'IntSliderModel',
model_module: '@jupyter-widgets/controls',
model_module_version: '1.0.0',
model_module_version: '2.0.0',
state: {
style: 'IPY_MODEL_3b8780f457254737a83be48bc32b0613',
_view_module: '@jupyter-widgets/controls',
Expand All @@ -104,7 +181,7 @@ describe('Output widget', function () {
'3b8780f457254737a83be48bc32b0613': {
model_name: 'SliderStyleModel',
model_module: '@jupyter-widgets/controls',
model_module_version: '1.0.0',
model_module_version: '2.0.0',
state: {
description_width: '',
_model_module: '@jupyter-widgets/controls',
Expand All @@ -113,7 +190,7 @@ describe('Output widget', function () {
'33cb011834fd4c9d9af512e5e98c9904': {
model_name: 'LayoutModel',
model_module: '@jupyter-widgets/base',
model_module_version: '1.0.0',
model_module_version: '2.0.0',
state: {},
},
};
Expand Down
28 changes: 28 additions & 0 deletions packages/html-manager/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,32 @@ module.exports = [
externals: ['@jupyter-widgets/base', 'module'],
...options,
},
{
// @jupyter-widgets/base ipywidgets 7
entry: ['./amd-public-path.js', '@jupyter-widgets/base7/lib/index'],
output: {
library: '@jupyter-widgets/base7',
filename: 'base7.js',
path: path.resolve(__dirname, 'dist', 'amd'),
libraryTarget: 'amd',
publicPath: '', // Set in amd-public-path.js
},
// 'module' is the magic requirejs dependency used to set the publicPath
externals: ['module'],
...options,
},
{
// @jupyter-widgets/controls
entry: ['./amd-public-path.js', '@jupyter-widgets/controls7/lib/index'],
output: {
library: '@jupyter-widgets/controls7',
filename: 'controls7.js',
path: path.resolve(__dirname, 'dist', 'amd'),
libraryTarget: 'amd',
publicPath: '', // Set in amd-public-path.js
},
// 'module' is the magic requirejs dependency used to set the publicPath
externals: ['@jupyter-widgets/base7', 'module'],
...options,
},
];
6 changes: 5 additions & 1 deletion scripts/package-integrity.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ function validate(dname) {
problems.push('Bad core version: ' + name + ' should be ' + desired);
}
}
if (names.indexOf(name) === -1) {
if (
names.indexOf(name) === -1 &&
!name.startsWith('@jupyter-widgets/base') &&
!name.startsWith('@jupyter-widgets/controls')
) {
problems.push('Unused package: ' + name);
}
});
Expand Down
Loading
Loading