diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index b99fed35e7..d11a8c2ad9 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -134,7 +134,7 @@ jobs: - name: Check the JupyterLab extension is installed if: matrix.dist != 'widgetsnbextension*.tar.gz' run: | - python -m pip install jupyterlab~=3.0 + python -m pip install jupyterlab~=4.0 jupyter labextension list jupyter labextension list 2>&1 | grep -ie "@jupyter-widgets/jupyterlab-manager.*enabled.*ok" - diff --git a/packages/base-manager/src/manager-base.ts b/packages/base-manager/src/manager-base.ts index c8e3ee9964..337820b912 100644 --- a/packages/base-manager/src/manager-base.ts +++ b/packages/base-manager/src/manager-base.ts @@ -214,10 +214,14 @@ export abstract class ManagerBase implements IWidgetManager { * * If you would like to synchronously test if a model exists, use .has_model(). */ - async get_model(model_id: string): Promise { + async get_model(model_id: string, delays = [500]): Promise { + let i = 0; + while (!this._models[model_id] && i < delays.length) { + await new Promise((resolve) => setTimeout(resolve, delays[i++])); + } const modelPromise = this._models[model_id]; if (modelPromise === undefined) { - throw new Error('widget model not found'); + throw new Error(`widget model '${model_id}' not found`); } return modelPromise; } @@ -383,6 +387,12 @@ export abstract class ManagerBase implements IWidgetManager { // Try fetching all widget states through the control comm let data: any; let buffers: any; + let timeoutID: number | undefined; + const comm_ids = await this._get_comm_info(); + if (Object.keys(comm_ids).length === 0) { + // There is nothing to load, either a new kernel or no widgets in the kernel. + return Promise.resolve(); + } try { const initComm = await this._create_comm( CONTROL_COMM_TARGET, @@ -390,8 +400,8 @@ export abstract class ManagerBase implements IWidgetManager { {}, { version: CONTROL_COMM_PROTOCOL_VERSION } ); - await new Promise((resolve, reject) => { + let succeeded = false; initComm.on_msg((msg: any) => { data = msg['content']['data']; @@ -409,22 +419,24 @@ export abstract class ManagerBase implements IWidgetManager { return new DataView(b instanceof ArrayBuffer ? b : b.buffer); } }); - + succeeded = true; + clearTimeout(timeoutID); resolve(null); }); - initComm.on_close(() => reject('Control comm was closed too early')); + initComm.on_close(() => { + if (!succeeded) reject('Control comm was closed too early'); + }); // Send a states request msg initComm.send({ method: 'request_states' }, {}); // Reject if we didn't get a response in time - setTimeout( + timeoutID = window.setTimeout( () => reject('Control comm did not respond in time'), CONTROL_COMM_TIMEOUT ); }); - initComm.close(); } catch (error) { // Fall back to the old implementation for old ipywidgets backend versions (ipywidgets<=7.6) @@ -487,6 +499,9 @@ export abstract class ManagerBase implements IWidgetManager { model.constructor as typeof WidgetModel )._deserialize_state(state.state, this); model!.set_state(deserializedState); + if (!model.comm_live) { + model.comm = await this._create_comm('jupyter.widget', widget_id); + } } } catch (error) { // Failed to create a widget model, we continue creating other models so that @@ -738,12 +753,12 @@ export abstract class ManagerBase implements IWidgetManager { /** * Disconnect the widget manager from the kernel, setting each model's comm - * as dead. + * as undefined. */ disconnect(): void { Object.keys(this._models).forEach((i) => { this._models[i].then((model) => { - model.comm_live = false; + model.comm = undefined; }); }); } diff --git a/packages/base/src/errorwidget.ts b/packages/base/src/errorwidget.ts index 1e6a837c93..818466acdf 100644 --- a/packages/base/src/errorwidget.ts +++ b/packages/base/src/errorwidget.ts @@ -24,7 +24,9 @@ export function createErrorWidgetModel( error: error, }; super(attributes, options); - this.comm_live = true; + } + get comm_live(): boolean { + return true; } } return ErrorWidget; diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index ecf886799f..03112e1cc6 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -160,7 +160,7 @@ export class WidgetModel extends Backbone.Model { // Attributes should be initialized here, since user initialization may depend on it this.widget_manager = options.widget_manager; this.model_id = options.model_id; - const comm = options.comm; + this.comm = options.comm; this.views = Object.create(null); this.state_change = Promise.resolve(); @@ -174,27 +174,23 @@ export class WidgetModel extends Backbone.Model { // _buffered_state_diff must be created *after* the super.initialize // call above. See the note in the set() method below. this._buffered_state_diff = {}; + } - if (comm) { - // Remember comm associated with the model. - this.comm = comm; + get comm() { + return this._comm; + } - // Hook comm messages up to model. + set comm(comm: IClassicComm | undefined) { + this._comm = comm; + if (comm) { comm.on_close(this._handle_comm_closed.bind(this)); comm.on_msg(this._handle_comm_msg.bind(this)); - - this.comm_live = true; - } else { - this.comm_live = false; } + this.trigger('comm_live_update'); } - get comm_live(): boolean { - return this._comm_live; - } - set comm_live(x) { - this._comm_live = x; - this.trigger('comm_live_update'); + get comm_live() { + return Boolean(this.comm); } /** @@ -218,39 +214,46 @@ export class WidgetModel extends Backbone.Model { * * @returns - a promise that is fulfilled when all the associated views have been removed. */ - close(comm_closed = false): Promise { + async close(comm_closed = false): Promise { // can only be closed once. if (this._closed) { - return Promise.resolve(); + return; } this._closed = true; - if (this.comm && !comm_closed) { - this.comm.close(); + if (this._comm && !comm_closed) { + try { + this._comm.close(); + } catch (err) { + // Do Nothing + } } this.stopListening(); this.trigger('destroy', this); - if (this.comm) { - delete this.comm; - } + delete this._comm; + // Delete all views of this model if (this.views) { const views = Object.keys(this.views).map((id: string) => { return this.views![id].then((view) => view.remove()); }); delete this.views; - return Promise.all(views).then(() => { - return; - }); + await Promise.all(views); + return; } - return Promise.resolve(); } /** * Handle when a widget comm is closed. */ _handle_comm_closed(msg: KernelMessage.ICommCloseMsg): void { + if (!this.comm) { + return; + } + this.comm = undefined; this.trigger('comm:close'); - this.close(true); + if (!this._closed) { + this.close(true); + } } /** @@ -635,7 +638,7 @@ export class WidgetModel extends Backbone.Model { * This invokes a Backbone.Sync. */ save_changes(callbacks?: {}): void { - if (this.comm_live) { + if (this.comm) { const options: any = { patch: true }; if (callbacks) { options.callbacks = callbacks; @@ -721,11 +724,9 @@ export class WidgetModel extends Backbone.Model { model_id: string; views?: { [key: string]: Promise }; state_change: Promise; - comm?: IClassicComm; name: string; module: string; - - private _comm_live: boolean; + private _comm?: IClassicComm; private _closed: boolean; private _state_lock: any; private _buffered_state_diff: any; diff --git a/packages/base/test/src/widget_test.ts b/packages/base/test/src/widget_test.ts index 911f130127..6f2a3492fb 100644 --- a/packages/base/test/src/widget_test.ts +++ b/packages/base/test/src/widget_test.ts @@ -194,15 +194,15 @@ describe('WidgetModel', function () { }); }); - it('sets the widget_manager, id, comm, and comm_live properties', function () { - const widgetDead = new WidgetModel({}, { - model_id: 'widgetDead', + it('sets the widget_manager, id, comm, properties', function () { + const widgetNoComm = new WidgetModel({}, { + model_id: 'noComm', widget_manager: this.manager, } as IBackboneModelOptions); - expect(widgetDead.model_id).to.equal('widgetDead'); - expect(widgetDead.widget_manager).to.equal(this.manager); - expect(widgetDead.comm).to.be.undefined; - expect(widgetDead.comm_live).to.be.false; + expect(widgetNoComm.model_id).to.equal('noComm'); + expect(widgetNoComm.widget_manager).to.equal(this.manager); + expect(widgetNoComm.comm).to.be.undefined; + expect(widgetNoComm.comm_live).to.be.false; const comm = new MockComm(); const widgetLive = new WidgetModel({}, { diff --git a/python/jupyterlab_widgets/package.json b/python/jupyterlab_widgets/package.json index 44d597afbe..3f571539f2 100644 --- a/python/jupyterlab_widgets/package.json +++ b/python/jupyterlab_widgets/package.json @@ -51,13 +51,13 @@ "@jupyter-widgets/controls": "^5.0.11", "@jupyter-widgets/output": "^6.0.10", "@jupyterlab/application": "^3.0.0 || ^4.0.0", - "@jupyterlab/apputils": "^3.0.0 || ^4.0.0", "@jupyterlab/console": "^3.0.0 || ^4.0.0", "@jupyterlab/docregistry": "^3.0.0 || ^4.0.0", "@jupyterlab/logconsole": "^3.0.0 || ^4.0.0", "@jupyterlab/mainmenu": "^3.0.0 || ^4.0.0", "@jupyterlab/nbformat": "^3.0.0 || ^4.0.0", "@jupyterlab/notebook": "^3.0.0 || ^4.0.0", + "@jupyterlab/observables": "^5.2.1", "@jupyterlab/outputarea": "^3.0.0 || ^4.0.0", "@jupyterlab/rendermime": "^3.0.0 || ^4.0.0", "@jupyterlab/rendermime-interfaces": "^3.0.0 || ^4.0.0", @@ -75,7 +75,6 @@ }, "devDependencies": { "@jupyterlab/builder": "^3.0.0 || ^4.0.0", - "@jupyterlab/cells": "^3.0.0 || ^4.0.0", "@types/jquery": "^3.5.16", "@types/semver": "^7.3.6", "@typescript-eslint/eslint-plugin": "^5.8.0", diff --git a/python/jupyterlab_widgets/src/index.ts b/python/jupyterlab_widgets/src/index.ts index eb40b5522f..e8e25c3af0 100644 --- a/python/jupyterlab_widgets/src/index.ts +++ b/python/jupyterlab_widgets/src/index.ts @@ -8,11 +8,7 @@ export default WidgetManagerProvider; export { registerWidgetManager } from './plugin'; -export { - KernelWidgetManager, - LabWidgetManager, - WidgetManager, -} from './manager'; +export { KernelWidgetManager, WidgetManager } from './manager'; export { WidgetRenderer } from './renderer'; diff --git a/python/jupyterlab_widgets/src/manager.ts b/python/jupyterlab_widgets/src/manager.ts index fec2cb3e4e..4bd1fa2096 100644 --- a/python/jupyterlab_widgets/src/manager.ts +++ b/python/jupyterlab_widgets/src/manager.ts @@ -2,30 +2,40 @@ // Distributed under the terms of the Modified BSD License. import { - shims, + ExportData, + ExportMap, + ICallbacks, IClassicComm, IWidgetRegistryData, - ExportMap, - ExportData, WidgetModel, WidgetView, - ICallbacks, + shims, } from '@jupyter-widgets/base'; import { + IStateOptions, ManagerBase, serialize_state, - IStateOptions, } from '@jupyter-widgets/base-manager'; import { IDisposable } from '@lumino/disposable'; +import { AttachedProperty } from '@lumino/properties'; + +import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; + import { ReadonlyPartialJSONValue } from '@lumino/coreutils'; -import { INotebookModel } from '@jupyterlab/notebook'; +import { INotebookModel, NotebookModel } from '@jupyterlab/notebook'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import { ObservableList } from '@jupyterlab/observables'; + +import * as nbformat from '@jupyterlab/nbformat'; + +import { ILoggerRegistry, LogLevel } from '@jupyterlab/logconsole'; + import { Kernel, KernelMessage, Session } from '@jupyterlab/services'; import { DocumentRegistry } from '@jupyterlab/docregistry'; @@ -36,6 +46,12 @@ import { valid } from 'semver'; import { SemVerCache } from './semvercache'; +import Backbone from 'backbone'; + +import { WidgetRenderer } from './renderer'; + +import * as base from '@jupyter-widgets/base'; + /** * The mime type for a widget view. */ @@ -50,15 +66,7 @@ export const WIDGET_STATE_MIMETYPE = /** * A widget manager that returns Lumino widgets. */ -export abstract class LabWidgetManager - extends ManagerBase - implements IDisposable -{ - constructor(rendermime: IRenderMimeRegistry) { - super(); - this._rendermime = rendermime; - } - +abstract class LabWidgetManager extends ManagerBase implements IDisposable { /** * Default callback handler to emit unhandled kernel messages. */ @@ -105,7 +113,6 @@ export abstract class LabWidgetManager // A "load" for a kernel that does not handle comms does nothing. return; } - return super._loadFromKernel(); } @@ -229,10 +236,6 @@ export abstract class LabWidgetManager abstract get kernel(): Kernel.IKernelConnection | null; - get rendermime(): IRenderMimeRegistry { - return this._rendermime; - } - /** * A signal emitted when state is restored to the widget manager. * @@ -282,6 +285,7 @@ export abstract class LabWidgetManager * @return Promise that resolves when the widget state is cleared. */ async clear_state(): Promise { + this._restoredStatus = false; await super.clear_state(); this._modelsSync = new Map(); } @@ -298,7 +302,7 @@ export abstract class LabWidgetManager get_state_sync(options: IStateOptions = {}): ReadonlyPartialJSONValue { const models = []; for (const model of this._modelsSync.values()) { - if (model.comm_live) { + if (model.comm) { models.push(model); } } @@ -315,13 +319,13 @@ export abstract class LabWidgetManager await this.handle_comm_open(oldComm, msg); }; + static rendermime: IRenderMimeRegistry; + protected _restored = new Signal(this); protected _restoredStatus = false; - protected _kernelRestoreInProgress = false; private _isDisposed = false; private _registry: SemVerCache = new SemVerCache(); - private _rendermime: IRenderMimeRegistry; private _commRegistration: IDisposable; @@ -330,65 +334,196 @@ export abstract class LabWidgetManager this, KernelMessage.IIOPubMessage >(this); + static WIDGET_REGISTRY = new ObservableList(); } /** - * A widget manager that returns Lumino widgets. + * KernelWidgetManager is singleton widget manager per kernel.id. + * This class should not be created directly or subclassed, instead use + * the class method `KernelWidgetManager.getManager(kernel)`. */ export class KernelWidgetManager extends LabWidgetManager { - constructor( - kernel: Kernel.IKernelConnection, - rendermime: IRenderMimeRegistry - ) { - super(rendermime); - this._kernel = kernel; - - kernel.statusChanged.connect((sender, args) => { - this._handleKernelStatusChange(args); - }); - kernel.connectionStatusChanged.connect((sender, args) => { - this._handleKernelConnectionStatusChange(args); - }); + constructor(kernel: Kernel.IKernelConnection) { + if (Private.managers.has(kernel.id)) { + throw new Error('A manager already exists!'); + } + if (!kernel.handleComms) { + throw new Error('Kernel does not have handleComms enabled'); + } + super(); + Private.managers.set(kernel.id, this); + this.loadCustomWidgetDefinitions(); + LabWidgetManager.WIDGET_REGISTRY.changed.connect(() => + this.loadCustomWidgetDefinitions() + ); + this._updateKernel(kernel); + } + private _updateKernel( + this: KernelWidgetManager, + kernel: Kernel.IKernelConnection + ) { + if (!kernel.handleComms || this._kernel === kernel) { + return; + } this._handleKernelChanged({ name: 'kernel', - oldValue: null, + oldValue: this._kernel, newValue: kernel, }); + if (this._kernel) { + this._kernel.statusChanged.disconnect( + this._handleKernelStatusChange, + this + ); + this._kernel.connectionStatusChanged.disconnect( + this._handleKernelConnectionStatusChange, + this + ); + this._kernel.disposed.disconnect(this._onKernelDisposed, this); + } + this._kernel = kernel; + kernel.statusChanged.connect(this._handleKernelStatusChange, this); + kernel.connectionStatusChanged.connect( + this._handleKernelConnectionStatusChange, + this + ); + kernel.disposed.connect(this._onKernelDisposed, this); this.restoreWidgets(); } - _handleKernelConnectionStatusChange(status: Kernel.ConnectionStatus): void { - if (status === 'connected') { - // Only restore if we aren't currently trying to restore from the kernel - // (for example, in our initial restore from the constructor). - if (!this._kernelRestoreInProgress) { + /** + * Configure a non-global rendermime. Passing the global rendermine will do + * nothing. + * + * @param rendermime + * @param manager The manager to use with WidgetRenderer. + * @param pendingManagerMessage A message that is displayed while the manager + * has not been provided. If manager is not provided here a non-empty string + * assumes the manager will be provided at some time in the future. + * + * The default will search for a manager once prior to waiting for a manager. + * @returns + */ + static configureRendermime( + rendermime?: IRenderMimeRegistry, + manager?: KernelWidgetManager, + pendingManagerMessage = '' + ) { + if (!rendermime || rendermime === LabWidgetManager.rendermime) { + return; + } + rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); + rendermime.addFactory( + { + safe: false, + mimeTypes: [WIDGET_VIEW_MIMETYPE], + createRenderer: (options: IRenderMime.IRendererOptions) => + new WidgetRenderer(options, manager, pendingManagerMessage), + }, + -10 + ); + } + _handleKernelConnectionStatusChange( + sender: Kernel.IKernelConnection, + status: Kernel.ConnectionStatus + ): void { + switch (status) { + case 'connected': this.restoreWidgets(); + break; + case 'disconnected': + this.disconnect(); + break; + } + } + + /** + * Find the KernelWidgetManager that owns the model. + */ + static async findManager( + model_id: string, + delays = [100, 1000] + ): Promise { + for (const sleepTime of delays) { + for (const wManager of Private.managers.values()) { + if (wManager.has_model(model_id)) { + return wManager; + } + } + await new Promise((resolve) => setTimeout(resolve, sleepTime)); + } + throw new Error( + `Failed to locate the KernelWidgetManager for model_id='${model_id}'` + ); + } + + /** + * The correct way to get a KernelWidgetManager + * @param kernel IKernelConnection + * @returns + */ + static async getManager( + kernel: Kernel.IKernelConnection + ): Promise { + let manager = Private.managers.get(kernel.id); + if (!manager) { + manager = new KernelWidgetManager(kernel); + } + if (kernel.handleComms) { + manager._updateKernel(kernel); + if (!manager.restoredStatus) { + const restored = manager.restored; + await new Promise((resolve) => restored.connect(resolve)); } } + return manager; + } + + _handleKernelStatusChange( + sender: Kernel.IKernelConnection, + status: Kernel.Status + ): void { + switch (status) { + case 'restarting': + case 'dead': + this.disconnect(); + this.clear_state(); + break; + } } - _handleKernelStatusChange(status: Kernel.Status): void { - if (status === 'restarting') { - this.disconnect(); + async _onKernelDisposed() { + const model = await KernelWidgetManager.kernels.findById(this.kernel?.id); + if (model) { + const kernel = KernelWidgetManager.kernels.connectTo({ model }); + this._updateKernel(kernel); } } /** - * Restore widgets from kernel and saved state. + * Restore widgets from kernel. */ async restoreWidgets(): Promise { + if (this._kernelRestoreInProgress) { + return; + } + this._restoredStatus = false; + this._kernelRestoreInProgress = true; try { - this._kernelRestoreInProgress = true; await this._loadFromKernel(); + } catch { + /* empty */ + } finally { this._restoredStatus = true; - this._restored.emit(); - } catch (err) { - // Do nothing + this._kernelRestoreInProgress = false; + this.triggerRestored(); } - this._kernelRestoreInProgress = false; } + triggerRestored() { + this._restored.emit(); + } /** * Dispose the resources held by the manager. */ @@ -396,122 +531,217 @@ export class KernelWidgetManager extends LabWidgetManager { if (this.isDisposed) { return; } - - this._kernel = null!; super.dispose(); + Private.managers.delete(this.kernel.id); + this._handleKernelChanged({ + name: 'kernel', + oldValue: this._kernel, + newValue: null, + }); + this._kernel = null!; + this.clear_state(); } get kernel(): Kernel.IKernelConnection { return this._kernel; } + loadCustomWidgetDefinitions() { + for (const data of LabWidgetManager.WIDGET_REGISTRY) { + this.register(data); + } + } + + filterModelState(serialized_state: any): any { + return this.filterExistingModelState(serialized_state); + } + static kernels: Kernel.IManager; private _kernel: Kernel.IKernelConnection; + private _kernelRestoreInProgress = false; } /** - * A widget manager that returns phosphor widgets. + * A single 'WidgetManager' per context. + * It monitors the kernel of the context swapping the kernel manager when the + * kernel is changed. + * A better name would be `WidgetManagerChanger'. TODO: change name and context. */ -export class WidgetManager extends LabWidgetManager { +export class WidgetManager extends Backbone.Model implements IDisposable { constructor( - context: DocumentRegistry.IContext, + context: DocumentRegistry.Context, rendermime: IRenderMimeRegistry, - settings: WidgetManager.Settings + settings?: WidgetManager.Settings ) { - super(rendermime); + const instance = Private.widgetManagerProperty.get(context); + if (instance) { + WidgetManager._rendermimeSetFactory(rendermime, instance); + return instance; + } + super(); + Private.widgetManagerProperty.set(context, this); this._context = context; - - context.sessionContext.kernelChanged.connect((sender, args) => { - this._handleKernelChanged(args); - }); - - context.sessionContext.statusChanged.connect((sender, args) => { - this._handleKernelStatusChange(args); - }); - - context.sessionContext.connectionStatusChanged.connect((sender, args) => { - this._handleKernelConnectionStatusChange(args); - }); - - if (context.sessionContext.session?.kernel) { - this._handleKernelChanged({ - name: 'kernel', - oldValue: null, - newValue: context.sessionContext.session?.kernel, + this._settings = settings; + this._renderers = new Set(); + WidgetManager._rendermimeSetFactory(rendermime, this); + + context.sessionContext.kernelChanged.connect( + this._handleKernelChange, + this + ); + + context.sessionContext.statusChanged.connect( + this._handleStatusChange, + this + ); + + context.sessionContext.connectionStatusChanged.connect( + this._handleConnectionStatusChange, + this + ); + if (context?.saveState) { + context.saveState.connect((sender, saveState) => { + if (saveState === 'started' && settings?.saveState) { + this._saveState(); + } }); } - - this.restoreWidgets(this._context!.model); - - this._settings = settings; - context.saveState.connect((sender, saveState) => { - if (saveState === 'started' && settings.saveState) { - this._saveState(); - } - }); + this.updateWidgetManager(); } /** * Save the widget state to the context model. */ private _saveState(): void { - const state = this.get_state_sync({ drop_defaults: true }); - if (this._context.model.setMetadata) { - this._context.model.setMetadata('widgets', { + if (!this.widgetManager) { + return; + } + const state = this.widgetManager.get_state_sync({ drop_defaults: true }); + const model = this._context.model; + if (model instanceof NotebookModel) { + model.setMetadata('widgets', { 'application/vnd.jupyter.widget-state+json': state, }); - } else { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore JupyterLab 3 support - this._context.model.metadata.set('widgets', { - 'application/vnd.jupyter.widget-state+json': state, + } + } + + static _rendermimeSetFactory( + rendermime: IRenderMimeRegistry, + manager: WidgetManager + ) { + if (rendermime === LabWidgetManager.rendermime) { + return; + } + rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); + rendermime.addFactory( + { + safe: false, + mimeTypes: [WIDGET_VIEW_MIMETYPE], + createRenderer: manager._newWidgetRenderer.bind(manager), + }, + -10 + ); + } + + async updateWidgetManager() { + let wManager: KernelWidgetManager | undefined; + await this.context.sessionContext.ready; + if (this.kernel) { + wManager = await KernelWidgetManager.getManager(this.kernel); + } + if (wManager === this._widgetManager) { + return; + } + if (this._widgetManager) { + this._widgetManager.onUnhandledIOPubMessage.disconnect( + this.onUnhandledIOPubMessage, + this + ); + } + this._widgetManager = wManager; + if (!wManager) { + return; + } + wManager.onUnhandledIOPubMessage.connect( + this.onUnhandledIOPubMessage, + this + ); + if (!wManager.restored) { + await new Promise((resolve) => { + this._widgetManager?.restored.connect(resolve); }); } + this._renderers.forEach( + (renderer: WidgetRenderer) => (renderer.manager = wManager) + ); + if (await this._restoreWidgets(this._context!.model)) { + wManager.triggerRestored(); + } + } + + _newWidgetRenderer(options: IRenderMime.IRendererOptions) { + const renderer = new WidgetRenderer( + options, + this.widgetManager, + this.widgetManager ? 'Loading widget ...' : 'No kernel' + ); + this._renderers.add(renderer); + renderer.disposed.connect((renderer_: WidgetRenderer) => + this._renderers.delete(renderer_) + ); + return renderer; } - _handleKernelConnectionStatusChange(status: Kernel.ConnectionStatus): void { - if (status === 'connected') { - // Only restore if we aren't currently trying to restore from the kernel - // (for example, in our initial restore from the constructor). - if (!this._kernelRestoreInProgress) { - // We only want to restore widgets from the kernel, not ones saved in the notebook. - this.restoreWidgets(this._context!.model, { - loadKernel: true, - loadNotebook: false, - }); + onUnhandledIOPubMessage( + sender: KernelWidgetManager, + msg: KernelMessage.IIOPubMessage + ) { + if (WidgetManager.loggerRegistry) { + const logger = WidgetManager.loggerRegistry.getLogger(this.context.path); + let level: LogLevel = 'warning'; + if ( + KernelMessage.isErrorMsg(msg) || + (KernelMessage.isStreamMsg(msg) && msg.content.name === 'stderr') + ) { + level = 'error'; } + const data: nbformat.IOutput = { + ...msg.content, + output_type: msg.header.msg_type, + }; + // logger.rendermime = this.content.rendermime; + logger.log({ type: 'output', data, level }); } } - _handleKernelStatusChange(status: Kernel.Status): void { - if (status === 'restarting') { - this.disconnect(); - } + _handleConnectionStatusChange( + sender: any, + status: Kernel.ConnectionStatus + ): void { + null; + } + + _handleKernelChange(sender: any, kernel: any): void { + this.updateWidgetManager(); + this.setDirty(); + } + _handleStatusChange(sender: any, status: Kernel.Status): void { + this.setDirty(); + } + + get widgetManager(): KernelWidgetManager | undefined { + return this._widgetManager; } /** - * Restore widgets from kernel and saved state. + * Restore widgets from model. */ - async restoreWidgets( - notebook: INotebookModel, - { loadKernel, loadNotebook } = { loadKernel: true, loadNotebook: true } - ): Promise { + async _restoreWidgets( + model: DocumentRegistry.IModel + ): Promise { try { - await this.context.sessionContext.ready; - if (loadKernel) { - try { - this._kernelRestoreInProgress = true; - await this._loadFromKernel(); - } finally { - this._kernelRestoreInProgress = false; - } - } - if (loadNotebook) { - await this._loadFromNotebook(notebook); + if (model instanceof NotebookModel) { + return await this._loadFromNotebook(model); } - - // If the restore worked above, then update our state. - this._restoredStatus = true; - this._restored.emit(); } catch (err) { // Do nothing if the restore did not work. } @@ -520,18 +750,35 @@ export class WidgetManager extends LabWidgetManager { /** * Load widget state from notebook metadata */ - async _loadFromNotebook(notebook: INotebookModel): Promise { - const widget_md = notebook.getMetadata - ? (notebook.getMetadata('widgets') as any) - : // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore JupyterLab 3 support - notebook.metadata.get('widgets'); - // Restore any widgets from saved state that are not live - if (widget_md && widget_md[WIDGET_STATE_MIMETYPE]) { - let state = widget_md[WIDGET_STATE_MIMETYPE]; - state = this.filterExistingModelState(state); - await this.set_state(state); + async _loadFromNotebook(notebook: INotebookModel): Promise { + if (this.widgetManager) { + const widget_md = notebook.getMetadata + ? (notebook.getMetadata('widgets') as any) + : // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore JupyterLab 3 support + notebook.metadata.get('widgets'); + if (widget_md && widget_md[WIDGET_STATE_MIMETYPE]) { + let state = widget_md[WIDGET_STATE_MIMETYPE]; + state = this.widgetManager.filterModelState(state); + const n = Object.keys(state?.state || {}).length; + if (n) { + // Restore any widgets from saved state that are not live + await this.widgetManager.set_state(state); + } + return n; + } } + return 0; + } + + /** + * Get whether the manager is disposed. + * + * #### Notes + * This is a read-only property. + */ + get isDisposed(): boolean { + return this._isDisposed; } /** @@ -541,9 +788,13 @@ export class WidgetManager extends LabWidgetManager { if (this.isDisposed) { return; } - + // Remove the custom factory from the rendermime. TODO: de-register the rendermime factory for this object + KernelWidgetManager.configureRendermime(this.rendermime); + this._renderers.forEach((renderer) => renderer.dispose()); + this._renderers = null!; this._context = null!; - super.dispose(); + this._context = null!; + this._settings = null!; } /** @@ -554,7 +805,7 @@ export class WidgetManager extends LabWidgetManager { return this.context.urlResolver.getDownloadUrl(partial); } - get context(): DocumentRegistry.IContext { + get context(): DocumentRegistry.Context { return this._context; } @@ -562,21 +813,8 @@ export class WidgetManager extends LabWidgetManager { return this._context.sessionContext?.session?.kernel ?? null; } - /** - * Register a widget model. - */ - register_model(model_id: string, modelPromise: Promise): void { - super.register_model(model_id, modelPromise); - this.setDirty(); - } - - /** - * Close all widgets and empty the widget state. - * @return Promise that resolves when the widget state is cleared. - */ - async clear_state(): Promise { - await super.clear_state(); - this.setDirty(); + get rendermime(): IRenderMimeRegistry { + return this._rendermime; } /** @@ -585,13 +823,17 @@ export class WidgetManager extends LabWidgetManager { * TODO: perhaps should also set dirty when any model changes any data */ setDirty(): void { - if (this._settings.saveState) { - this._context!.model.dirty = true; + if (this._settings?.saveState && this._context?.model) { + this._context.model.dirty = true; } } - - private _context: DocumentRegistry.IContext; - private _settings: WidgetManager.Settings; + static loggerRegistry: ILoggerRegistry | null; + private _isDisposed = false; + private _context: DocumentRegistry.Context; + private _rendermime: IRenderMimeRegistry; + private _settings: WidgetManager.Settings | undefined; + private _widgetManager: KernelWidgetManager | undefined; + _renderers: Set; } export namespace WidgetManager { @@ -599,3 +841,18 @@ export namespace WidgetManager { saveState: boolean; }; } + +/** + * A namespace for private data + */ +namespace Private { + export const managers = new Map(); + + export const widgetManagerProperty = new AttachedProperty< + DocumentRegistry.Context, + WidgetManager | undefined + >({ + name: 'widgetManager', + create: (owner: DocumentRegistry.Context): undefined => undefined, + }); +} diff --git a/python/jupyterlab_widgets/src/output.ts b/python/jupyterlab_widgets/src/output.ts index 37793bf193..a71032defe 100644 --- a/python/jupyterlab_widgets/src/output.ts +++ b/python/jupyterlab_widgets/src/output.ts @@ -7,13 +7,15 @@ import { JupyterLuminoPanelWidget } from '@jupyter-widgets/base'; import { Panel } from '@lumino/widgets'; -import { LabWidgetManager, WidgetManager } from './manager'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; + +import { KernelWidgetManager } from './manager'; import { OutputAreaModel, OutputArea } from '@jupyterlab/outputarea'; import * as nbformat from '@jupyterlab/nbformat'; -import { KernelMessage, Session } from '@jupyterlab/services'; +import { KernelMessage } from '@jupyterlab/services'; import $ from 'jquery'; @@ -33,37 +35,16 @@ export class OutputModel extends outputBase.OutputModel { return false; }; - // if the context is available, react on kernel changes - if (this.widget_manager instanceof WidgetManager) { - this.widget_manager.context.sessionContext.kernelChanged.connect( - (sender, args) => { - this._handleKernelChanged(args); - } - ); - } this.listenTo(this, 'change:msg_id', this.reset_msg_id); this.listenTo(this, 'change:outputs', this.setOutputs); this.setOutputs(); } - /** - * Register a new kernel - */ - _handleKernelChanged({ - oldValue, - }: Session.ISessionConnection.IKernelChangedArgs): void { - const msgId = this.get('msg_id'); - if (msgId && oldValue) { - oldValue.removeMessageHook(msgId, this._msgHook); - this.set('msg_id', null); - } - } - /** * Reset the message id. */ reset_msg_id(): void { - const kernel = this.widget_manager.kernel; + const kernel = (this.widget_manager as KernelWidgetManager).kernel; const msgId = this.get('msg_id'); const oldMsgId = this.previous('msg_id'); @@ -117,10 +98,9 @@ export class OutputModel extends outputBase.OutputModel { } } - widget_manager: LabWidgetManager; - private _msgHook: (msg: KernelMessage.IIOPubMessage) => boolean; private _outputs: OutputAreaModel; + static rendermime: IRenderMimeRegistry; } export class OutputView extends outputBase.OutputView { @@ -145,10 +125,11 @@ export class OutputView extends outputBase.OutputView { render(): void { super.render(); this._outputView = new OutputArea({ - rendermime: this.model.widget_manager.rendermime, + rendermime: OutputModel.rendermime, contentFactory: OutputArea.defaultContentFactory, model: this.model.outputs, }); + // TODO: why is this a readonly property now? // this._outputView.model = this.model.outputs; // TODO: why is this on the model now? diff --git a/python/jupyterlab_widgets/src/plugin.ts b/python/jupyterlab_widgets/src/plugin.ts index 8dd7d6c5d4..f0805871fc 100644 --- a/python/jupyterlab_widgets/src/plugin.ts +++ b/python/jupyterlab_widgets/src/plugin.ts @@ -5,47 +5,32 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { DocumentRegistry } from '@jupyterlab/docregistry'; -import * as nbformat from '@jupyterlab/nbformat'; +import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console'; -import { - IConsoleTracker, - CodeConsole, - ConsolePanel, -} from '@jupyterlab/console'; - -import { - INotebookModel, - INotebookTracker, - Notebook, - NotebookPanel, -} from '@jupyterlab/notebook'; +import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; import { - JupyterFrontEndPlugin, JupyterFrontEnd, + JupyterFrontEndPlugin, } from '@jupyterlab/application'; import { IMainMenu } from '@jupyterlab/mainmenu'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; -import { ILoggerRegistry, LogLevel } from '@jupyterlab/logconsole'; - -import { CodeCell } from '@jupyterlab/cells'; - -import { filter } from '@lumino/algorithm'; +import { ILoggerRegistry } from '@jupyterlab/logconsole'; import { DisposableDelegate } from '@lumino/disposable'; import { WidgetRenderer } from './renderer'; import { - WidgetManager, - WIDGET_VIEW_MIMETYPE, KernelWidgetManager, + WIDGET_VIEW_MIMETYPE, + WidgetManager, } from './manager'; -import { OutputModel, OutputView, OUTPUT_WIDGET_VERSION } from './output'; +import { OUTPUT_WIDGET_VERSION, OutputModel, OutputView } from './output'; import * as base from '@jupyter-widgets/base'; @@ -55,262 +40,27 @@ import { JUPYTER_CONTROLS_VERSION } from '@jupyter-widgets/controls/lib/version' import '@jupyter-widgets/base/css/index.css'; import '@jupyter-widgets/controls/css/widgets-base.css'; -import { KernelMessage } from '@jupyterlab/services'; import { ITranslator, nullTranslator } from '@jupyterlab/translation'; -import { ISessionContext } from '@jupyterlab/apputils'; - -const WIDGET_REGISTRY: base.IWidgetRegistryData[] = []; /** * The cached settings. */ const SETTINGS: WidgetManager.Settings = { saveState: false }; -/** - * Iterate through all widget renderers in a notebook. - */ -function* notebookWidgetRenderers( - nb: Notebook -): Generator { - for (const cell of nb.widgets) { - if (cell.model.type === 'code') { - for (const codecell of (cell as CodeCell).outputArea.widgets) { - // We use Array.from instead of using Lumino 2 (JLab 4) iterator - // This is to support Lumino 1 (JLab 3) as well - for (const output of Array.from(codecell.children())) { - if (output instanceof WidgetRenderer) { - yield output; - } - } - } - } - } -} - -/** - * Iterate through all widget renderers in a console. - */ -function* consoleWidgetRenderers( - console: CodeConsole -): Generator { - for (const cell of Array.from(console.cells)) { - if (cell.model.type === 'code') { - for (const codecell of (cell as unknown as CodeCell).outputArea.widgets) { - for (const output of Array.from(codecell.children())) { - if (output instanceof WidgetRenderer) { - yield output; - } - } - } - } - } -} - -/** - * Iterate through all matching linked output views - */ -function* outputViews( - app: JupyterFrontEnd, - path: string -): Generator { - const linkedViews = filter( - app.shell.widgets(), - (w) => w.id.startsWith('LinkedOutputView-') && (w as any).path === path - ); - // We use Array.from instead of using Lumino 2 (JLab 4) iterator - // This is to support Lumino 1 (JLab 3) as well - for (const view of Array.from(linkedViews)) { - for (const outputs of Array.from(view.children())) { - for (const output of Array.from(outputs.children())) { - if (output instanceof WidgetRenderer) { - yield output; - } - } - } - } -} - -function* chain( - ...args: IterableIterator[] -): Generator { - for (const it of args) { - yield* it; - } -} - -/** - * Get the kernel id of current notebook or console panel, this value - * is used as key for `Private.widgetManagerProperty` to store the widget - * manager of current notebook or console panel. - * - * @param {ISessionContext} sessionContext The session context of notebook or - * console panel. - */ -async function getWidgetManagerOwner( - sessionContext: ISessionContext -): Promise { - await sessionContext.ready; - return sessionContext.session!.kernel!.id; -} - -/** - * Common handler for registering both notebook and console - * `WidgetManager` - * - * @param {(Notebook | CodeConsole)} content Context of panel. - * @param {ISessionContext} sessionContext Session context of panel. - * @param {IRenderMimeRegistry} rendermime Rendermime of panel. - * @param {IterableIterator} renderers Iterator of - * `WidgetRenderer` inside panel - * @param {(() => WidgetManager | KernelWidgetManager)} widgetManagerFactory - * function to create widget manager. - */ -async function registerWidgetHandler( - content: Notebook | CodeConsole, - sessionContext: ISessionContext, - rendermime: IRenderMimeRegistry, - renderers: IterableIterator, - widgetManagerFactory: () => WidgetManager | KernelWidgetManager -): Promise { - const wManagerOwner = await getWidgetManagerOwner(sessionContext); - let wManager = Private.widgetManagerProperty.get(wManagerOwner); - let currentOwner: string; - - if (!wManager) { - wManager = widgetManagerFactory(); - WIDGET_REGISTRY.forEach((data) => wManager!.register(data)); - Private.widgetManagerProperty.set(wManagerOwner, wManager); - currentOwner = wManagerOwner; - content.disposed.connect((_) => { - const currentwManager = Private.widgetManagerProperty.get(currentOwner); - if (currentwManager) { - Private.widgetManagerProperty.delete(currentOwner); - } - }); - - sessionContext.kernelChanged.connect((_, args) => { - const { newValue } = args; - if (newValue) { - const newKernelId = newValue.id; - const oldwManager = Private.widgetManagerProperty.get(currentOwner); - - if (oldwManager) { - Private.widgetManagerProperty.delete(currentOwner); - Private.widgetManagerProperty.set(newKernelId, oldwManager); - } - currentOwner = newKernelId; - } - }); - } - - for (const r of renderers) { - r.manager = wManager; - } - - // Replace the placeholder widget renderer with one bound to this widget - // manager. - rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); - rendermime.addFactory( - { - safe: false, - mimeTypes: [WIDGET_VIEW_MIMETYPE], - createRenderer: (options) => new WidgetRenderer(options, wManager), - }, - -10 - ); - - return new DisposableDelegate(() => { - if (rendermime) { - rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); - } - wManager!.dispose(); - }); -} - -// Kept for backward compat ipywidgets<=8, but not used here anymore export function registerWidgetManager( - context: DocumentRegistry.IContext, - rendermime: IRenderMimeRegistry, - renderers: IterableIterator + context: DocumentRegistry.Context, + rendermime: IRenderMimeRegistry ): DisposableDelegate { - let wManager: WidgetManager; - const managerReady = getWidgetManagerOwner(context.sessionContext).then( - (wManagerOwner) => { - const currentManager = Private.widgetManagerProperty.get( - wManagerOwner - ) as WidgetManager; - if (!currentManager) { - wManager = new WidgetManager(context, rendermime, SETTINGS); - WIDGET_REGISTRY.forEach((data) => wManager!.register(data)); - Private.widgetManagerProperty.set(wManagerOwner, wManager); - } else { - wManager = currentManager; - } - - for (const r of renderers) { - r.manager = wManager; - } - - // Replace the placeholder widget renderer with one bound to this widget - // manager. - rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); - rendermime.addFactory( - { - safe: false, - mimeTypes: [WIDGET_VIEW_MIMETYPE], - createRenderer: (options) => new WidgetRenderer(options, wManager), - }, - -10 - ); - } - ); - - return new DisposableDelegate(async () => { - await managerReady; - if (rendermime) { - rendermime.removeMimeType(WIDGET_VIEW_MIMETYPE); - } - wManager!.dispose(); - }); + const wManager = new WidgetManager(context, rendermime, SETTINGS); + return new DisposableDelegate(() => wManager!.dispose()); } -export async function registerNotebookWidgetManager( - panel: NotebookPanel, - renderers: IterableIterator -): Promise { - const content = panel.content; - const context = panel.context; - const sessionContext = context.sessionContext; - const rendermime = content.rendermime; - const widgetManagerFactory = () => - new WidgetManager(context, rendermime, SETTINGS); - - return registerWidgetHandler( - content, - sessionContext, - rendermime, - renderers, - widgetManagerFactory - ); -} - -export async function registerConsoleWidgetManager( - panel: ConsolePanel, - renderers: IterableIterator -): Promise { - const content = panel.console; - const sessionContext = content.sessionContext; - const rendermime = content.rendermime; - const widgetManagerFactory = () => - new KernelWidgetManager(sessionContext.session!.kernel!, rendermime); - - return registerWidgetHandler( - content, - sessionContext, - rendermime, - renderers, - widgetManagerFactory - ); +function attachWidgetManager(panel: NotebookPanel | ConsolePanel) { + if (panel instanceof NotebookPanel) { + new WidgetManager(panel.context, panel.content.rendermime, SETTINGS); + } else if (panel instanceof ConsolePanel) { + new WidgetManager(panel.console as any, panel.console.rendermime); + } } /** @@ -343,7 +93,7 @@ function updateSettings(settings: ISettingRegistry.ISettings): void { function activateWidgetExtension( app: JupyterFrontEnd, rendermime: IRenderMimeRegistry, - tracker: INotebookTracker | null, + widgetTracker: INotebookTracker | null, consoleTracker: IConsoleTracker | null, settingRegistry: ISettingRegistry | null, menu: IMainMenu | null, @@ -352,42 +102,7 @@ function activateWidgetExtension( ): base.IJupyterWidgetRegistry { const { commands } = app; const trans = (translator ?? nullTranslator).load('jupyterlab_widgets'); - - const bindUnhandledIOPubMessageSignal = async ( - nb: NotebookPanel - ): Promise => { - if (!loggerRegistry) { - return; - } - const wManagerOwner = await getWidgetManagerOwner( - nb.context.sessionContext - ); - const wManager = Private.widgetManagerProperty.get(wManagerOwner); - - if (wManager) { - wManager.onUnhandledIOPubMessage.connect( - ( - sender: WidgetManager | KernelWidgetManager, - msg: KernelMessage.IIOPubMessage - ) => { - const logger = loggerRegistry.getLogger(nb.context.path); - let level: LogLevel = 'warning'; - if ( - KernelMessage.isErrorMsg(msg) || - (KernelMessage.isStreamMsg(msg) && msg.content.name === 'stderr') - ) { - level = 'error'; - } - const data: nbformat.IOutput = { - ...msg.content, - output_type: msg.header.msg_type, - }; - logger.rendermime = nb.content.rendermime; - logger.log({ type: 'output', data, level }); - } - ); - } - }; + KernelWidgetManager.kernels = app.serviceManager.kernels; if (settingRegistry !== null) { settingRegistry .load(managerPlugin.id) @@ -399,8 +114,9 @@ function activateWidgetExtension( console.error(reason.message); }); } - - // Add a placeholder widget renderer. + WidgetManager.loggerRegistry = loggerRegistry; + KernelWidgetManager.rendermime = rendermime; + // Add a default widget renderer. rendermime.addFactory( { safe: false, @@ -409,33 +125,13 @@ function activateWidgetExtension( }, -10 ); - - if (tracker !== null) { - const rendererIterator = (panel: NotebookPanel) => - chain( - notebookWidgetRenderers(panel.content), - outputViews(app, panel.context.path) + for (const tracker of [widgetTracker, consoleTracker]) { + if (tracker !== null) { + tracker.forEach((panel) => attachWidgetManager(panel)); + tracker.widgetAdded.connect((sender, panel) => + attachWidgetManager(panel) ); - tracker.forEach(async (panel) => { - await registerNotebookWidgetManager(panel, rendererIterator(panel)); - bindUnhandledIOPubMessageSignal(panel); - }); - tracker.widgetAdded.connect(async (sender, panel) => { - await registerNotebookWidgetManager(panel, rendererIterator(panel)); - bindUnhandledIOPubMessageSignal(panel); - }); - } - - if (consoleTracker !== null) { - const rendererIterator = (panel: ConsolePanel) => - chain(consoleWidgetRenderers(panel.console)); - - consoleTracker.forEach(async (panel) => { - await registerConsoleWidgetManager(panel, rendererIterator(panel)); - }); - consoleTracker.widgetAdded.connect(async (sender, panel) => { - await registerConsoleWidgetManager(panel, rendererIterator(panel)); - }); + } } if (settingRegistry !== null) { // Add a command for automatically saving (jupyter-)widget state. @@ -462,7 +158,7 @@ function activateWidgetExtension( return { registerWidget(data: base.IWidgetRegistryData): void { - WIDGET_REGISTRY.push(data); + KernelWidgetManager.WIDGET_REGISTRY.push(data); }, }; } @@ -534,17 +230,19 @@ export const controlWidgetsPlugin: JupyterFrontEndPlugin = { */ export const outputWidgetPlugin: JupyterFrontEndPlugin = { id: `@jupyter-widgets/jupyterlab-manager:output-${OUTPUT_WIDGET_VERSION}`, - requires: [base.IJupyterWidgetRegistry], + requires: [base.IJupyterWidgetRegistry, IRenderMimeRegistry], autoStart: true, activate: ( app: JupyterFrontEnd, - registry: base.IJupyterWidgetRegistry + registry: base.IJupyterWidgetRegistry, + rendermime: IRenderMimeRegistry ): void => { - registry.registerWidget({ - name: '@jupyter-widgets/output', - version: OUTPUT_WIDGET_VERSION, - exports: { OutputModel, OutputView }, - }); + (OutputModel.rendermime = rendermime), + registry.registerWidget({ + name: '@jupyter-widgets/output', + version: OUTPUT_WIDGET_VERSION, + exports: { OutputModel, OutputView }, + }); }, }; @@ -554,25 +252,3 @@ export default [ controlWidgetsPlugin, outputWidgetPlugin, ]; -namespace Private { - /** - * A type alias for keys of `widgetManagerProperty` . - */ - export type IWidgetManagerOwner = string; - - /** - * A type alias for values of `widgetManagerProperty` . - */ - export type IWidgetManagerValue = - | WidgetManager - | KernelWidgetManager - | undefined; - - /** - * A private map for a widget manager. - */ - export const widgetManagerProperty = new Map< - IWidgetManagerOwner, - IWidgetManagerValue - >(); -} diff --git a/python/jupyterlab_widgets/src/renderer.ts b/python/jupyterlab_widgets/src/renderer.ts index ce0d257625..258b11b5ea 100644 --- a/python/jupyterlab_widgets/src/renderer.ts +++ b/python/jupyterlab_widgets/src/renderer.ts @@ -5,15 +5,29 @@ import { PromiseDelegate } from '@lumino/coreutils'; import { IDisposable } from '@lumino/disposable'; -import { Panel, Widget as LuminoWidget } from '@lumino/widgets'; +import { Widget as LuminoWidget, Panel } from '@lumino/widgets'; import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; -import { LabWidgetManager } from './manager'; import { DOMWidgetModel } from '@jupyter-widgets/base'; +import { KernelWidgetManager } from './manager'; + /** * A renderer for widgets. + * + * Default behavior is to search for the manager, unless the manager + * has already been set. + * + * If `pendingManagerMessage` is a non-empty string, no attempt will + * be made to search for the wiget manager, and the manager must be + * waiting for a manager to be set. + * + * pendingManagerMessage: A message to post when rendering whilst + * awaiting the when manager. + * + * Omitting a message means a manager will be searched for. + * */ export class WidgetRenderer extends Panel @@ -21,57 +35,71 @@ export class WidgetRenderer { constructor( options: IRenderMime.IRendererOptions, - manager?: LabWidgetManager + manager?: KernelWidgetManager, + pendingManagerMessage = '' ) { super(); this.mimeType = options.mimeType; - if (manager) { - this.manager = manager; - } + this._pendingManagerMessage = pendingManagerMessage; + this.manager = manager; } /** * The widget manager. + * + * Will accept the first non-null manager and ignore anything afterwards. */ - set manager(value: LabWidgetManager) { - value.restored.connect(this._rerender, this); - this._manager.resolve(value); + + set manager(value: KernelWidgetManager | undefined) { + if (value && !this._managerIsSet) { + // Can only set the manager once + this._manager.resolve(value); + this._managerIsSet = true; + value.restored.connect(this.rerender, this); + this.disposed.connect(() => + value.restored.disconnect(this.rerender, this) + ); + } } async renderModel(model: IRenderMime.IMimeModel): Promise { const source: any = model.data[this.mimeType]; - // Let's be optimistic, and hope the widget state will come later. - this.node.textContent = 'Loading widget...'; - - const manager = await this._manager.promise; // If there is no model id, the view was removed, so hide the node. if (source.model_id === '') { this.hide(); - return Promise.resolve(); + return; } + if (!this._pendingManagerMessage && !this._managerIsSet) { + try { + this.manager = await KernelWidgetManager.findManager(source.model_id); + } catch { + this.node.textContent = `KernelWidgetManager not found for model: ${model.data['text/plain']}`; + return; + } + } + this.node.textContent = `${ + this._pendingManagerMessage || model.data['text/plain'] + }`; + const manager = await this._manager.promise; + this._rerenderMimeModel = model; let wModel: DOMWidgetModel; try { // Presume we have a DOMWidgetModel. Should we check for sure? wModel = (await manager.get_model(source.model_id)) as DOMWidgetModel; } catch (err) { - if (manager.restoredStatus) { + if (this._pendingManagerMessage === 'No kernel') { + this.node.textContent = `Model not found: ${model.data['text/plain']}`; + } else if (manager.restoredStatus) { // The manager has been restored, so this error won't be going away. this.node.textContent = 'Error displaying widget: model not found'; this.addClass('jupyter-widgets'); console.error(err); - return; } - // Store the model for a possible rerender - this._rerenderMimeModel = model; return; } - - // Successful getting the model, so we don't need to try to rerender. - this._rerenderMimeModel = null; - let widget: LuminoWidget; try { const view = await manager.create_view(wModel); @@ -86,12 +114,12 @@ export class WidgetRenderer // Clear any previous loading message. this.node.textContent = ''; this.addWidget(widget); + this.show(); // When the widget is disposed, hide this container and make sure we // change the output model to reflect the view was closed. widget.disposed.connect(() => { this.hide(); - source.model_id = ''; }); } @@ -103,11 +131,13 @@ export class WidgetRenderer return; } this._manager = null!; + this._rerenderMimeModel = null; super.dispose(); } - private _rerender(): void { - if (this._rerenderMimeModel) { + rerender(): void { + // TODO: Add conditions for when re-rendering should occur. + if (this._rerenderMimeModel && !this.children.length) { // Clear the error message this.node.textContent = ''; this.removeClass('jupyter-widgets'); @@ -121,6 +151,8 @@ export class WidgetRenderer * The mimetype being rendered. */ readonly mimeType: string; - private _manager = new PromiseDelegate(); + private _manager = new PromiseDelegate(); + private _managerIsSet = false; + private _pendingManagerMessage: string; private _rerenderMimeModel: IRenderMime.IMimeModel | null = null; } diff --git a/yarn.lock b/yarn.lock index b2e6fde943..e019a222b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -980,15 +980,14 @@ __metadata: "@jupyter-widgets/controls": ^5.0.11 "@jupyter-widgets/output": ^6.0.10 "@jupyterlab/application": ^3.0.0 || ^4.0.0 - "@jupyterlab/apputils": ^3.0.0 || ^4.0.0 "@jupyterlab/builder": ^3.0.0 || ^4.0.0 - "@jupyterlab/cells": ^3.0.0 || ^4.0.0 "@jupyterlab/console": ^3.0.0 || ^4.0.0 "@jupyterlab/docregistry": ^3.0.0 || ^4.0.0 "@jupyterlab/logconsole": ^3.0.0 || ^4.0.0 "@jupyterlab/mainmenu": ^3.0.0 || ^4.0.0 "@jupyterlab/nbformat": ^3.0.0 || ^4.0.0 "@jupyterlab/notebook": ^3.0.0 || ^4.0.0 + "@jupyterlab/observables": ^5.2.1 "@jupyterlab/outputarea": ^3.0.0 || ^4.0.0 "@jupyterlab/rendermime": ^3.0.0 || ^4.0.0 "@jupyterlab/rendermime-interfaces": ^3.0.0 || ^4.0.0 @@ -1121,7 +1120,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/apputils@npm:^3.0.0 || ^4.0.0, @jupyterlab/apputils@npm:^4.3.5": +"@jupyterlab/apputils@npm:^4.3.5": version: 4.3.5 resolution: "@jupyterlab/apputils@npm:4.3.5" dependencies: @@ -1238,7 +1237,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/cells@npm:^3.0.0 || ^4.0.0, @jupyterlab/cells@npm:^4.2.5": +"@jupyterlab/cells@npm:^4.2.5": version: 4.2.5 resolution: "@jupyterlab/cells@npm:4.2.5" dependencies: @@ -1581,16 +1580,16 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/observables@npm:^5.2.5": - version: 5.2.5 - resolution: "@jupyterlab/observables@npm:5.2.5" +"@jupyterlab/observables@npm:^5.2.1, @jupyterlab/observables@npm:^5.2.5": + version: 5.3.0 + resolution: "@jupyterlab/observables@npm:5.3.0" dependencies: - "@lumino/algorithm": ^2.0.1 - "@lumino/coreutils": ^2.1.2 - "@lumino/disposable": ^2.1.2 - "@lumino/messaging": ^2.0.1 - "@lumino/signaling": ^2.1.2 - checksum: 21fd2828463c08a770714692ff44aeca500f8ea8f3a743ad203a61fbf04cfa81921a47b432d8e65f4935fb45c08fce2b8858cb7e2198cc9bf0fa51f482ec37bd + "@lumino/algorithm": ^2.0.2 + "@lumino/coreutils": ^2.2.0 + "@lumino/disposable": ^2.1.3 + "@lumino/messaging": ^2.0.2 + "@lumino/signaling": ^2.1.3 + checksum: 8d1c5e6eebeebe8fe45098531c9be9b3f0f0f3ec153203746fba233fe74db028f93261f11e0897a020ac0ae6872e7c3e03c4365678663bbbe4f0125b89174f37 languageName: node linkType: hard @@ -3531,7 +3530,7 @@ __metadata: languageName: node linkType: hard -"@types/backbone@npm:1.4.14": +"@types/backbone@npm:1.4.14, @types/backbone@npm:^1.4.1": version: 1.4.14 resolution: "@types/backbone@npm:1.4.14" dependencies: @@ -3541,16 +3540,6 @@ __metadata: languageName: node linkType: hard -"@types/backbone@npm:^1.4.1": - version: 1.4.22 - resolution: "@types/backbone@npm:1.4.22" - dependencies: - "@types/jquery": "*" - "@types/underscore": "*" - checksum: 095959d89abfdbf01071c8389f8638cf941e235c7ba7b03d2500cea34c8c313830605c3c87b2bb8630760bb9ed8ca54114344c1721947ce6992016482b295c6c - languageName: node - linkType: hard - "@types/base64-js@npm:^1.2.5": version: 1.3.2 resolution: "@types/base64-js@npm:1.3.2" @@ -3579,14 +3568,7 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:*": - version: 5.0.0 - resolution: "@types/chai@npm:5.0.0" - checksum: ae3d63d8e84b4fc7fce5b0e68d0000834e709e19378e569c9ab97503a1b38f582ff69db6299528a849ec336954690a905225cb0dd9648d823c291f53ae93b458 - languageName: node - linkType: hard - -"@types/chai@npm:^4.1.7": +"@types/chai@npm:*, @types/chai@npm:^4.1.7": version: 4.3.20 resolution: "@types/chai@npm:4.3.20" checksum: 7c5b0c9148f1a844a8d16cb1e16c64f2e7749cab2b8284155b9e494a6b34054846e22fb2b38df6b290f9bf57e6beebb2e121940c5896bc086ad7bab7ed429f06 @@ -3709,14 +3691,7 @@ __metadata: languageName: node linkType: hard -"@types/minimatch@npm:*": - version: 5.1.2 - resolution: "@types/minimatch@npm:5.1.2" - checksum: 0391a282860c7cb6fe262c12b99564732401bdaa5e395bee9ca323c312c1a0f45efbf34dce974682036e857db59a5c9b1da522f3d6055aeead7097264c8705a8 - languageName: node - linkType: hard - -"@types/minimatch@npm:^3.0.3": +"@types/minimatch@npm:*, @types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 @@ -3737,16 +3712,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=10.0.0": - version: 22.7.8 - resolution: "@types/node@npm:22.7.8" - dependencies: - undici-types: ~6.19.2 - checksum: c1dd36bd0bf82588e61f82edb29a792f21ce902f90cc5485591f9fd60cec3ea9172e044bf7b1c0849e7cf3a5a01da39516db260cb65cb0b94904010e00634a1c - languageName: node - linkType: hard - -"@types/node@npm:^17.0.2": +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:^17.0.2": version: 17.0.45 resolution: "@types/node@npm:17.0.45" checksum: aa04366b9103b7d6cfd6b2ef64182e0eaa7d4462c3f817618486ea0422984c51fc69fd0d436eae6c9e696ddfdbec9ccaa27a917f7c2e8c75c5d57827fe3d95e8 @@ -3819,16 +3785,7 @@ __metadata: languageName: node linkType: hard -"@types/sinon@npm:*": - version: 17.0.3 - resolution: "@types/sinon@npm:17.0.3" - dependencies: - "@types/sinonjs__fake-timers": "*" - checksum: c8e9956d9c90fe1ec1cc43085ae48897f93f9ea86e909ab47f255ea71f5229651faa070393950fb6923aef426c84e92b375503f9f8886ef44668b82a8ee49e9a - languageName: node - linkType: hard - -"@types/sinon@npm:^10.0.2": +"@types/sinon@npm:*, @types/sinon@npm:^10.0.2": version: 10.0.20 resolution: "@types/sinon@npm:10.0.20" dependencies: @@ -4796,20 +4753,13 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.1": +"ansi-colors@npm:4.1.1, ansi-colors@npm:^4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" checksum: 138d04a51076cb085da0a7e2d000c5c0bb09f6e772ed5c65c53cb118d37f6c5f1637506d7155fb5f330f0abcf6f12fa2e489ac3f8cdab9da393bf1bb4f9a32b0 languageName: node linkType: hard -"ansi-colors@npm:^4.1.1": - version: 4.1.3 - resolution: "ansi-colors@npm:4.1.3" - checksum: a9c2ec842038a1fabc7db9ece7d3177e2fe1c5dc6f0c51ecfbf5f39911427b89c00b5dc6b8bd95f82a26e9b16aaae2e83d45f060e98070ce4d1333038edceb0e - languageName: node - linkType: hard - "ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -5055,7 +5005,7 @@ __metadata: languageName: node linkType: hard -"async@npm:3.2.5": +"async@npm:3.2.5, async@npm:^3.2.3": version: 3.2.5 resolution: "async@npm:3.2.5" checksum: 5ec77f1312301dee02d62140a6b1f7ee0edd2a0f983b6fd2b0849b969f245225b990b47b8243e7b9ad16451a53e7f68e753700385b706198ced888beedba3af4 @@ -5071,13 +5021,6 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.3": - version: 3.2.6 - resolution: "async@npm:3.2.6" - checksum: ee6eb8cd8a0ab1b58bd2a3ed6c415e93e773573a91d31df9d5ef559baafa9dab37d3b096fa7993e84585cac3697b2af6ddb9086f45d3ac8cae821bb2aab65682 - languageName: node - linkType: hard - "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -5770,7 +5713,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3": +"chokidar@npm:3.5.3, chokidar@npm:^3.3.0, chokidar@npm:^3.5.1": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -5789,25 +5732,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.3.0, chokidar@npm:^3.5.1": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: ~3.1.2 - braces: ~3.0.2 - fsevents: ~2.3.2 - glob-parent: ~5.1.2 - is-binary-path: ~2.1.0 - is-glob: ~4.0.1 - normalize-path: ~3.0.0 - readdirp: ~3.6.0 - dependenciesMeta: - fsevents: - optional: true - checksum: d2f29f499705dcd4f6f3bbed79a9ce2388cf530460122eed3b9c48efeab7a4e28739c6551fd15bec9245c6b9eeca7a32baa64694d64d9b6faeb74ddb8c4a413d - languageName: node - linkType: hard - "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -5852,20 +5776,13 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:2.6.1": +"cli-spinners@npm:2.6.1, cli-spinners@npm:^2.5.0": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" checksum: 423409baaa7a58e5104b46ca1745fbfc5888bbd0b0c5a626e052ae1387060839c8efd512fb127e25769b3dc9562db1dc1b5add6e0b93b7ef64f477feb6416a45 languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": - version: 2.9.2 - resolution: "cli-spinners@npm:2.9.2" - checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c - languageName: node - linkType: hard - "cli-truncate@npm:^2.1.0": version: 2.1.0 resolution: "cli-truncate@npm:2.1.0" @@ -5900,7 +5817,7 @@ __metadata: languageName: node linkType: hard -"clipanion@npm:4.0.0-rc.3": +"clipanion@npm:4.0.0-rc.3, clipanion@npm:^4.0.0-rc.2": version: 4.0.0-rc.3 resolution: "clipanion@npm:4.0.0-rc.3" dependencies: @@ -5911,17 +5828,6 @@ __metadata: languageName: node linkType: hard -"clipanion@npm:^4.0.0-rc.2": - version: 4.0.0-rc.4 - resolution: "clipanion@npm:4.0.0-rc.4" - dependencies: - typanion: ^3.8.0 - peerDependencies: - typanion: "*" - checksum: a92aa03b24eb89292b7bda570973c164fff16a1c5ba4c4abdd1b0dd6110a57651752114ec9f5cfc29e2040213e514b3220142a2316c4fc4e659ba423caa296c7 - languageName: node - linkType: hard - "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -6427,20 +6333,13 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:1.0.2": +"core-util-is@npm:1.0.2, core-util-is@npm:~1.0.0": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" checksum: 7a4c925b497a2c91421e25bf76d6d8190f0b2359a9200dbeed136e63b2931d6294d3b1893eda378883ed363cd950f44a12a401384c609839ea616befb7927dab languageName: node linkType: hard -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 - languageName: node - linkType: hard - "cors@npm:2.8.5, cors@npm:~2.8.5": version: 2.8.5 resolution: "cors@npm:2.8.5" @@ -6561,20 +6460,13 @@ __metadata: languageName: node linkType: hard -"csstype@npm:3.0.10": +"csstype@npm:3.0.10, csstype@npm:^3.0.2": version: 3.0.10 resolution: "csstype@npm:3.0.10" checksum: 20a8fa324f2b33ddf94aa7507d1b6ab3daa6f3cc308888dc50126585d7952f2471de69b2dbe0635d1fdc31223fef8e070842691877e725caf456e2378685a631 languageName: node linkType: hard -"csstype@npm:^3.0.2": - version: 3.1.3 - resolution: "csstype@npm:3.1.3" - checksum: 8db785cc92d259102725b3c694ec0c823f5619a84741b5c7991b8ad135dfaa66093038a1cc63e03361a6cd28d122be48f2106ae72334e067dd619a51f49eddf7 - languageName: node - linkType: hard - "custom-event@npm:~1.0.0": version: 1.0.1 resolution: "custom-event@npm:1.0.1" @@ -7291,7 +7183,7 @@ __metadata: languageName: node linkType: hard -"envinfo@npm:7.13.0": +"envinfo@npm:7.13.0, envinfo@npm:^7.7.3, envinfo@npm:^7.7.4": version: 7.13.0 resolution: "envinfo@npm:7.13.0" bin: @@ -7300,15 +7192,6 @@ __metadata: languageName: node linkType: hard -"envinfo@npm:^7.7.3, envinfo@npm:^7.7.4": - version: 7.14.0 - resolution: "envinfo@npm:7.14.0" - bin: - envinfo: dist/cli.js - checksum: 137c1dd9a4d5781c4a6cdc6b695454ba3c4ba1829f73927198aa4122f11b35b59d7b2cb7e1ceea1364925a30278897548511d22f860c14253a33797d0bebd551 - languageName: node - linkType: hard - "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -7763,20 +7646,13 @@ __metadata: languageName: node linkType: hard -"extsprintf@npm:1.3.0": +"extsprintf@npm:1.3.0, extsprintf@npm:^1.2.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" checksum: cee7a4a1e34cffeeec18559109de92c27517e5641991ec6bab849aa64e3081022903dd53084f2080d0d2530803aa5ee84f1e9de642c365452f9e67be8f958ce2 languageName: node linkType: hard -"extsprintf@npm:^1.2.0": - version: 1.4.1 - resolution: "extsprintf@npm:1.4.1" - checksum: a2f29b241914a8d2bad64363de684821b6b1609d06ae68d5b539e4de6b28659715b5bea94a7265201603713b7027d35399d10b0548f09071c5513e65e8323d33 - languageName: node - linkType: hard - "fast-deep-equal@npm:^1.0.0": version: 1.1.0 resolution: "fast-deep-equal@npm:1.1.0" @@ -8458,7 +8334,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.2.0": +"glob@npm:7.2.0, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.7": version: 7.2.0 resolution: "glob@npm:7.2.0" dependencies: @@ -8501,20 +8377,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.7": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^3.1.1 - once: ^1.3.0 - path-is-absolute: ^1.0.0 - checksum: 29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133 - languageName: node - linkType: hard - "glob@npm:^8.0.1": version: 8.1.0 resolution: "glob@npm:8.1.0" @@ -10021,20 +9883,13 @@ __metadata: languageName: node linkType: hard -"jsonc-parser@npm:3.2.0": +"jsonc-parser@npm:3.2.0, jsonc-parser@npm:^3.2.0": version: 3.2.0 resolution: "jsonc-parser@npm:3.2.0" checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 languageName: node linkType: hard -"jsonc-parser@npm:^3.2.0": - version: 3.3.1 - resolution: "jsonc-parser@npm:3.3.1" - checksum: 81ef19d98d9c6bd6e4a37a95e2753c51c21705cbeffd895e177f4b542cca9cda5fda12fb942a71a2e824a9132cf119dc2e642e9286386055e1365b5478f49a47 - languageName: node - linkType: hard - "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -10428,20 +10283,13 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:2.0.5": +"lilconfig@npm:2.0.5, lilconfig@npm:^2.0.5": version: 2.0.5 resolution: "lilconfig@npm:2.0.5" checksum: f7bb9e42656f06930ad04e583026f087508ae408d3526b8b54895e934eb2a966b7aafae569656f2c79a29fe6d779b3ec44ba577e80814734c8655d6f71cdf2d1 languageName: node linkType: hard -"lilconfig@npm:^2.0.5": - version: 2.1.0 - resolution: "lilconfig@npm:2.1.0" - checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117 - languageName: node - linkType: hard - "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -11034,20 +10882,13 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:1.52.0": +"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f languageName: node linkType: hard -"mime-db@npm:>= 1.43.0 < 2": - version: 1.53.0 - resolution: "mime-db@npm:1.53.0" - checksum: 3fd9380bdc0b085d0b56b580e4f89ca4fc3b823722310d795c248f0806b9a80afd5d8f4347f015ad943b9ecfa7cc0b71dffa0db96fa776d01a13474821a2c7fb - languageName: node - linkType: hard - "mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" @@ -11133,7 +10974,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -11142,7 +10983,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.0.5": +"minimatch@npm:3.0.5, minimatch@npm:~3.0.4": version: 3.0.5 resolution: "minimatch@npm:3.0.5" dependencies: @@ -11187,15 +11028,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:~3.0.4": - version: 3.0.8 - resolution: "minimatch@npm:3.0.8" - dependencies: - brace-expansion: ^1.1.7 - checksum: 850cca179cad715133132693e6963b0db64ab0988c4d211415b087fc23a3e46321e2c5376a01bf5623d8782aba8bdf43c571e2e902e51fdce7175c7215c29f8b - languageName: node - linkType: hard - "minimist-options@npm:4.1.0": version: 4.1.0 resolution: "minimist-options@npm:4.1.0" @@ -11517,20 +11349,13 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 languageName: node linkType: hard -"negotiator@npm:^0.6.3": - version: 0.6.4 - resolution: "negotiator@npm:0.6.4" - checksum: 7ded10aa02a0707d1d12a9973fdb5954f98547ca7beb60e31cb3a403cc6e8f11138db7a3b0128425cf836fc85d145ec4ce983b2bdf83dca436af879c2d683510 - languageName: node - linkType: hard - "neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" @@ -14176,21 +14001,7 @@ __metadata: languageName: node linkType: hard -"sanitize-html@npm:^2.3": - version: 2.13.1 - resolution: "sanitize-html@npm:2.13.1" - dependencies: - deepmerge: ^4.2.2 - escape-string-regexp: ^4.0.0 - htmlparser2: ^8.0.0 - is-plain-object: ^5.0.0 - parse-srcset: ^1.0.2 - postcss: ^8.3.11 - checksum: 645381375fcb9c70070644b02538f1d43d35db6c9761eba32ec5f0ea919fdc70aa19e186ae949e6e21de767cbf11ced3e6a31b322a6c705593dc9a902a257d7a - languageName: node - linkType: hard - -"sanitize-html@npm:~2.12.1": +"sanitize-html@npm:^2.3, sanitize-html@npm:~2.12.1": version: 2.12.1 resolution: "sanitize-html@npm:2.12.1" dependencies: @@ -14638,7 +14449,7 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:3.8.0": +"sonic-boom@npm:3.8.0, sonic-boom@npm:^3.7.0": version: 3.8.0 resolution: "sonic-boom@npm:3.8.0" dependencies: @@ -14656,15 +14467,6 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:^3.7.0": - version: 3.8.1 - resolution: "sonic-boom@npm:3.8.1" - dependencies: - atomic-sleep: ^1.0.0 - checksum: 79c90d7a2f928489fd3d4b68d8f8d747a426ca6ccf83c3b102b36f899d4524463dd310982ab7ab6d6bcfd34b7c7c281ad25e495ad71fbff8fd6fa86d6273fc6b - languageName: node - linkType: hard - "sort-keys@npm:^2.0.0": version: 2.0.0 resolution: "sort-keys@npm:2.0.0" @@ -15775,13 +15577,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 - languageName: node - linkType: hard - "union@npm:~0.5.0": version: 0.5.0 resolution: "union@npm:0.5.0" @@ -16147,7 +15942,7 @@ __metadata: languageName: node linkType: hard -"vscode-jsonrpc@npm:8.2.0": +"vscode-jsonrpc@npm:8.2.0, vscode-jsonrpc@npm:^8.0.2": version: 8.2.0 resolution: "vscode-jsonrpc@npm:8.2.0" checksum: f302a01e59272adc1ae6494581fa31c15499f9278df76366e3b97b2236c7c53ebfc71efbace9041cfd2caa7f91675b9e56f2407871a1b3c7f760a2e2ee61484a @@ -16161,13 +15956,6 @@ __metadata: languageName: node linkType: hard -"vscode-jsonrpc@npm:^8.0.2": - version: 8.2.1 - resolution: "vscode-jsonrpc@npm:8.2.1" - checksum: 2af2c333d73f6587896a7077978b8d4b430e55c674d5dbb90597a84a6647057c1655a3bff398a9b08f1f8ba57dbd2deabf05164315829c297b0debba3b8bc19e - languageName: node - linkType: hard - "vscode-languageserver-protocol@npm:^3.17.0": version: 3.17.5 resolution: "vscode-languageserver-protocol@npm:3.17.5" @@ -16661,22 +16449,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.11.0": - version: 8.18.0 - resolution: "ws@npm:8.18.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975 - languageName: node - linkType: hard - -"ws@npm:~8.17.1": +"ws@npm:^8.11.0, ws@npm:~8.17.1": version: 8.17.1 resolution: "ws@npm:8.17.1" peerDependencies: @@ -16737,7 +16510,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:20.2.4": +"yargs-parser@npm:20.2.4, yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3": version: 20.2.4 resolution: "yargs-parser@npm:20.2.4" checksum: d251998a374b2743a20271c2fd752b9fbef24eb881d53a3b99a7caa5e8227fcafd9abf1f345ac5de46435821be25ec12189a11030c12ee6481fef6863ed8b924 @@ -16751,13 +16524,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3": - version: 20.2.9 - resolution: "yargs-parser@npm:20.2.9" - checksum: 8bb69015f2b0ff9e17b2c8e6bfe224ab463dd00ca211eece72a4cd8a906224d2703fb8a326d36fdd0e68701e201b2a60ed7cf81ce0fd9b3799f9fe7745977ae3 - languageName: node - linkType: hard - "yargs-unparser@npm:2.0.0": version: 2.0.0 resolution: "yargs-unparser@npm:2.0.0"