From cac9c4c0bf00a27c4b29d94d406198def0d4a34d Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Sat, 16 Jul 2022 07:58:15 -0500 Subject: [PATCH 1/4] Export single plain amd modules from html manager so we can more easily load individual packages with requirejs. --- packages/html-manager/webpack.config.js | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/html-manager/webpack.config.js b/packages/html-manager/webpack.config.js index 7b8dc1d5bb..6e7bea8d7e 100644 --- a/packages/html-manager/webpack.config.js +++ b/packages/html-manager/webpack.config.js @@ -93,4 +93,49 @@ module.exports = [ externals: ['@jupyter-widgets/base', 'module'], ...options, }, + { + // @jupyter-widgets/base + entry: ['./amd-public-path.js', '@jupyter-widgets/base/lib/index'], + output: { + filename: '@jupyter-widgets/base.js', + path: path.resolve(__dirname, 'dist'), + library: { + type: '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, but versioned so we can load it beside other versions of controls + entry: ['./amd-public-path.js', '@jupyter-widgets/controls/lib/index'], + output: { + filename: '@jupyter-widgets/controls.js', + path: path.resolve(__dirname, 'dist'), + library: { + type: 'amd', + }, + publicPath: '', // Set in amd-public-path.js + }, + // 'module' is the magic requirejs dependency used to set the publicPath + externals: ['@jupyter-widgets/base', 'module'], + ...options, + }, + { + // @jupyter-widgets/html-manager + entry: ['./amd-public-path.js', './lib/index.js'], + output: { + filename: '@jupyter-widgets/html-manager.js', + path: path.resolve(__dirname, 'dist'), + library: { + type: 'amd', + }, + publicPath: '', // Set in amd-public-path.js + }, + // 'module' is the magic requirejs dependency used to set the publicPath + externals: ['@jupyter-widgets/base', '@jupyter-widgets/controls', 'module'], + ...options, + }, ]; From 8cfb68a2c45b2aa5039921ba20556cfcb6bd3499 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Sat, 16 Jul 2022 13:32:27 +0000 Subject: [PATCH 2/4] Make the standard controls optional for the HTML manager. This allows the HTML manager to instead load, for example, older versions of the core controls. The idea is that the HTML Manager can load either the ipywidgets 7 or ipywidgets 8 version of core controls, to support either during a transition time. It also allows the HTML manager to be used without loading all of the base controls, if desired, making it strictly more flexible. --- packages/html-manager/src/htmlmanager.ts | 23 +++++++++------------ packages/html-manager/src/libembed-amd.ts | 25 ++++++++++++++++++++++- packages/html-manager/src/libembed.ts | 1 + packages/html-manager/src/output.ts | 2 ++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/html-manager/src/htmlmanager.ts b/packages/html-manager/src/htmlmanager.ts index 50f5807da5..f4a548c259 100644 --- a/packages/html-manager/src/htmlmanager.ts +++ b/packages/html-manager/src/htmlmanager.ts @@ -1,9 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import * as widgets from '@jupyter-widgets/controls'; -import * as base from '@jupyter-widgets/base'; -import * as outputWidgets from './output'; +import { createErrorWidgetModel, ErrorWidgetView } from '@jupyter-widgets/base'; import { ManagerBase } from '@jupyter-widgets/base-manager'; import { MessageLoop } from '@lumino/messaging'; @@ -14,7 +12,12 @@ import { } from '@jupyterlab/rendermime'; import { WidgetRenderer, WIDGET_MIMETYPE } from './output_renderers'; -import { WidgetModel, WidgetView, DOMWidgetView } from '@jupyter-widgets/base'; + +import type { + WidgetModel, + WidgetView, + DOMWidgetView, +} from '@jupyter-widgets/base'; export class HTMLManager extends ManagerBase { constructor(options?: { @@ -58,9 +61,9 @@ export class HTMLManager extends ManagerBase { } catch (error) { const msg = `Could not create a view for ${view}`; console.error(msg); - const ModelCls = base.createErrorWidgetModel(error, msg); + const ModelCls = createErrorWidgetModel(error, msg); const errorModel = new ModelCls(); - v = new base.ErrorWidgetView({ + v = new ErrorWidgetView({ model: errorModel, }); v.render(); @@ -112,13 +115,7 @@ export class HTMLManager extends ManagerBase { moduleVersion: string ): Promise { return new Promise((resolve, reject) => { - if (moduleName === '@jupyter-widgets/base') { - resolve(base); - } else if (moduleName === '@jupyter-widgets/controls') { - resolve(widgets); - } else if (moduleName === '@jupyter-widgets/output') { - resolve(outputWidgets); - } else if (this.loader !== undefined) { + if (this.loader !== undefined) { resolve(this.loader(moduleName, moduleVersion)); } else { reject(`Could not load module ${moduleName}@${moduleVersion}`); diff --git a/packages/html-manager/src/libembed-amd.ts b/packages/html-manager/src/libembed-amd.ts index 6cf1868c60..f925d1860e 100644 --- a/packages/html-manager/src/libembed-amd.ts +++ b/packages/html-manager/src/libembed-amd.ts @@ -3,6 +3,10 @@ import * as libembed from './libembed'; +import * as controls from '@jupyter-widgets/controls'; +import * as base from '@jupyter-widgets/base'; +import * as outputWidgets from './output'; + let cdn = 'https://cdn.jsdelivr.net/npm/'; let onlyCDN = false; @@ -60,10 +64,29 @@ function moduleNameToCDNUrl(moduleName: string, moduleVersion: string): string { * * The semver range is only used with the CDN. */ -export function requireLoader( +export async function requireLoader( moduleName: string, moduleVersion: string ): Promise { + // First, try to load from the default packages if the version number matches + if ( + moduleName === '@jupyter-widgets/base' && + moduleVersion /* Some semver test??? */ === base.JUPYTER_WIDGETS_VERSION + ) { + return base; + } else if ( + moduleName === '@jupyter-widgets/controls' && + moduleVersion /* Some semver test??? */ === + controls.JUPYTER_CONTROLS_VERSION + ) { + return controls; + } else if ( + moduleName === '@jupyter-widgets/output' && + moduleVersion /* Some semver test??? */ === + outputWidgets.OUTPUT_WIDGET_VERSION + ) { + return outputWidgets; + } const require = (window as any).requirejs; if (require === undefined) { throw new Error( diff --git a/packages/html-manager/src/libembed.ts b/packages/html-manager/src/libembed.ts index 494734d6aa..8992f02dc8 100644 --- a/packages/html-manager/src/libembed.ts +++ b/packages/html-manager/src/libembed.ts @@ -67,6 +67,7 @@ export async function renderWidgets( * * @param element The DOM element to search for widget view state script tags * @param widgetState The widget manager state + * @param managerFactory A function that returns a new HTMLManager * * #### Notes * diff --git a/packages/html-manager/src/output.ts b/packages/html-manager/src/output.ts index 3a732c72b4..bdcc026769 100644 --- a/packages/html-manager/src/output.ts +++ b/packages/html-manager/src/output.ts @@ -13,6 +13,8 @@ import $ from 'jquery'; import '../css/output.css'; +export const OUTPUT_WIDGET_VERSION = outputBase.OUTPUT_WIDGET_VERSION; + export class OutputModel extends outputBase.OutputModel { defaults(): Backbone.ObjectHash { return { From 12476aabc0a323591a3fe43a5c4d447f4664ab93 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Sat, 16 Jul 2022 15:06:11 +0000 Subject: [PATCH 3/4] Make HTMLManager understand the basic widgets after all for compatibility, but at least check the versions. --- packages/html-manager/src/htmlmanager.ts | 52 ++++++++++++++++------- packages/html-manager/src/libembed-amd.ts | 23 ---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/html-manager/src/htmlmanager.ts b/packages/html-manager/src/htmlmanager.ts index f4a548c259..5da47c9c1f 100644 --- a/packages/html-manager/src/htmlmanager.ts +++ b/packages/html-manager/src/htmlmanager.ts @@ -1,6 +1,10 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. +import * as controls from '@jupyter-widgets/controls'; +import * as base from '@jupyter-widgets/base'; +import * as outputWidgets from './output'; + import { createErrorWidgetModel, ErrorWidgetView } from '@jupyter-widgets/base'; import { ManagerBase } from '@jupyter-widgets/base-manager'; import { MessageLoop } from '@lumino/messaging'; @@ -109,26 +113,42 @@ export class HTMLManager extends ManagerBase { /** * Load a class and return a promise to the loaded object. */ - protected loadClass( + protected async loadClass( className: string, moduleName: string, moduleVersion: string ): Promise { - return new Promise((resolve, reject) => { - if (this.loader !== undefined) { - resolve(this.loader(moduleName, moduleVersion)); - } else { - reject(`Could not load module ${moduleName}@${moduleVersion}`); - } - }).then((module) => { - if ((module as any)[className]) { - return (module as any)[className]; - } else { - return Promise.reject( - `Class ${className} not found in module ${moduleName}@${moduleVersion}` - ); - } - }); + let module: any; + if ( + moduleName === '@jupyter-widgets/base' && + moduleVersion /* Some semver test??? */ === base.JUPYTER_WIDGETS_VERSION + ) { + module = base; + } else if ( + moduleName === '@jupyter-widgets/controls' && + moduleVersion /* Some semver test??? */ === + controls.JUPYTER_CONTROLS_VERSION + ) { + module = controls; + } else if ( + moduleName === '@jupyter-widgets/output' && + moduleVersion /* Some semver test??? */ === + outputWidgets.OUTPUT_WIDGET_VERSION + ) { + module = outputWidgets; + } else if (this.loader !== undefined) { + module = await this.loader(moduleName, moduleVersion); + } else { + throw new Error(`Could not load module ${moduleName}@${moduleVersion}`); + } + + if ((module as any)[className]) { + return (module as any)[className]; + } else { + throw Error( + `Class ${className} not found in module ${moduleName}@${moduleVersion}` + ); + } } /** diff --git a/packages/html-manager/src/libembed-amd.ts b/packages/html-manager/src/libembed-amd.ts index f925d1860e..20e2ef1dfc 100644 --- a/packages/html-manager/src/libembed-amd.ts +++ b/packages/html-manager/src/libembed-amd.ts @@ -3,10 +3,6 @@ import * as libembed from './libembed'; -import * as controls from '@jupyter-widgets/controls'; -import * as base from '@jupyter-widgets/base'; -import * as outputWidgets from './output'; - let cdn = 'https://cdn.jsdelivr.net/npm/'; let onlyCDN = false; @@ -68,25 +64,6 @@ export async function requireLoader( moduleName: string, moduleVersion: string ): Promise { - // First, try to load from the default packages if the version number matches - if ( - moduleName === '@jupyter-widgets/base' && - moduleVersion /* Some semver test??? */ === base.JUPYTER_WIDGETS_VERSION - ) { - return base; - } else if ( - moduleName === '@jupyter-widgets/controls' && - moduleVersion /* Some semver test??? */ === - controls.JUPYTER_CONTROLS_VERSION - ) { - return controls; - } else if ( - moduleName === '@jupyter-widgets/output' && - moduleVersion /* Some semver test??? */ === - outputWidgets.OUTPUT_WIDGET_VERSION - ) { - return outputWidgets; - } const require = (window as any).requirejs; if (require === undefined) { throw new Error( From aada267c9cbe1a8797b2c5f3deab42b8457c65c8 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Sat, 16 Jul 2022 11:30:51 -0500 Subject: [PATCH 4/4] Add a control channel message to request registered widgets. This could be used to determine what widgets we can invoke from the frontend. We can also use this to query what version of the base widget is registered, which gives clues about what version of ipywidgets is installed. To determine what version of ipywidgets is installed, this is a rather indirect route, so it's not so satisfactory for that purpose. --- .../ipywidgets/ipywidgets/widgets/widget.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/python/ipywidgets/ipywidgets/widgets/widget.py b/python/ipywidgets/ipywidgets/widgets/widget.py index 703ac64a6d..9089c9ac9e 100644 --- a/python/ipywidgets/ipywidgets/widgets/widget.py +++ b/python/ipywidgets/ipywidgets/widgets/widget.py @@ -345,7 +345,32 @@ def _handle_control_comm_msg(cls, msg): states=full_state, buffer_paths=buffer_paths ), buffers=buffers) - + elif method == 'request_registered': + # Send back registered widgets + registered = [] + filter = data.get('filter', {}) + filter_list = ( + filter.get('model_module', None), + filter.get('model_version', None), + filter.get('model_name', None), + filter.get('view_module', None), + filter.get('view_version', None), + filter.get('view_name', None) + ) + for info, _ in cls._widget_types.items(): + if all(f is None or f == v for f, v in zip(filter, info)): + registered.append(dict( + model_module=info[0], + model_version=info[1], + model_name=info[2], + view_module=info[3], + view_version=info[4], + view_name=info[5] + )) + cls._control_comm.send(dict( + method='reply_registered', + registered=registered + )) else: raise RuntimeError('Unknown front-end to back-end widget control msg with method "%s"' % method)