UNPKG

@jupyter-widgets/jupyterlab-manager

Version:

The JupyterLab extension providing Jupyter widgets.

465 lines 15.2 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { shims, } from '@jupyter-widgets/base'; import { ManagerBase, serialize_state, } from '@jupyter-widgets/base-manager'; import { Signal } from '@lumino/signaling'; import { valid } from 'semver'; import { SemVerCache } from './semvercache'; /** * The mime type for a widget view. */ export const WIDGET_VIEW_MIMETYPE = 'application/vnd.jupyter.widget-view+json'; /** * The mime type for widget state data. */ export const WIDGET_STATE_MIMETYPE = 'application/vnd.jupyter.widget-state+json'; /** * A widget manager that returns Lumino widgets. */ export class LabWidgetManager extends ManagerBase { constructor(rendermime) { super(); // _handleCommOpen is an attribute, not a method, so `this` is captured in a // single object that can be registered and removed this._handleCommOpen = async (comm, msg) => { const oldComm = new shims.services.Comm(comm); await this.handle_comm_open(oldComm, msg); }; this._restored = new Signal(this); this._restoredStatus = false; this._kernelRestoreInProgress = false; this._isDisposed = false; this._registry = new SemVerCache(); this._modelsSync = new Map(); this._onUnhandledIOPubMessage = new Signal(this); this._rendermime = rendermime; } /** * Default callback handler to emit unhandled kernel messages. */ callbacks(view) { return { iopub: { output: (msg) => { this._onUnhandledIOPubMessage.emit(msg); }, }, }; } /** * Register a new kernel */ _handleKernelChanged({ oldValue, newValue, }) { if (oldValue) { oldValue.removeCommTarget(this.comm_target_name, this._handleCommOpen); } if (newValue) { newValue.registerCommTarget(this.comm_target_name, this._handleCommOpen); } } /** * Disconnect the widget manager from the kernel, setting each model's comm * as dead. */ disconnect() { super.disconnect(); this._restoredStatus = false; } async _loadFromKernel() { var _a; if (!this.kernel) { throw new Error('Kernel not set'); } if (((_a = this.kernel) === null || _a === void 0 ? void 0 : _a.handleComms) === false) { // A "load" for a kernel that does not handle comms does nothing. return; } return super._loadFromKernel(); } /** * Create a comm. */ async _create_comm(target_name, model_id, data, metadata, buffers) { const kernel = this.kernel; if (!kernel) { throw new Error('No current kernel'); } const comm = kernel.createComm(target_name, model_id); if (data || metadata) { comm.open(data, metadata, buffers); } return new shims.services.Comm(comm); } /** * Get the currently-registered comms. */ async _get_comm_info() { const kernel = this.kernel; if (!kernel) { throw new Error('No current kernel'); } const reply = await kernel.requestCommInfo({ target_name: this.comm_target_name, }); if (reply.content.status === 'ok') { return reply.content.comms; } else { return {}; } } /** * Get whether the manager is disposed. * * #### Notes * This is a read-only property. */ get isDisposed() { return this._isDisposed; } /** * Dispose the resources held by the manager. */ dispose() { if (this.isDisposed) { return; } this._isDisposed = true; if (this._commRegistration) { this._commRegistration.dispose(); } } /** * Resolve a URL relative to the current notebook location. */ async resolveUrl(url) { return url; } /** * Load a class and return a promise to the loaded object. */ async loadClass(className, moduleName, moduleVersion) { // Special-case the Jupyter base and controls packages. If we have just a // plain version, with no indication of the compatible range, prepend a ^ to // get all compatible versions. We may eventually apply this logic to all // widget modules. See issues #2006 and #2017 for more discussion. if ((moduleName === '@jupyter-widgets/base' || moduleName === '@jupyter-widgets/controls') && valid(moduleVersion)) { moduleVersion = `^${moduleVersion}`; } const allVersions = this._registry.getAllVersions(moduleName); if (!allVersions) { throw new Error(`No version of module ${moduleName} is registered`); } const mod = this._registry.get(moduleName, moduleVersion); if (!mod) { const registeredVersionList = Object.keys(allVersions); throw new Error(`Module ${moduleName}, version ${moduleVersion} is not registered, however, \ ${registeredVersionList.join(',')} ${registeredVersionList.length > 1 ? 'are' : 'is'}`); } let module; if (typeof mod === 'function') { module = await mod(); } else { module = await mod; } const cls = module[className]; if (!cls) { throw new Error(`Class ${className} not found in module ${moduleName}`); } return cls; } get rendermime() { return this._rendermime; } /** * A signal emitted when state is restored to the widget manager. * * #### Notes * This indicates that previously-unavailable widget models might be available now. */ get restored() { return this._restored; } /** * Whether the state has been restored yet or not. */ get restoredStatus() { return this._restoredStatus; } /** * A signal emitted for unhandled iopub kernel messages. * */ get onUnhandledIOPubMessage() { return this._onUnhandledIOPubMessage; } register(data) { this._registry.set(data.name, data.version, data.exports); } /** * Register a widget model. */ register_model(model_id, modelPromise) { super.register_model(model_id, modelPromise); // Update the synchronous model map modelPromise.then((model) => { this._modelsSync.set(model_id, model); model.once('comm:close', () => { this._modelsSync.delete(model_id); }); }); } /** * Close all widgets and empty the widget state. * @return Promise that resolves when the widget state is cleared. */ async clear_state() { await super.clear_state(); this._modelsSync = new Map(); } /** * Synchronously get the state of the live widgets in the widget manager. * * This includes all of the live widget models, and follows the format given in * the @jupyter-widgets/schema package. * * @param options - The options for what state to return. * @returns A state dictionary */ get_state_sync(options = {}) { const models = []; for (const model of this._modelsSync.values()) { if (model.comm_live) { models.push(model); } } return serialize_state(models, options); } } /** * A widget manager that returns Lumino widgets. */ export class KernelWidgetManager extends LabWidgetManager { constructor(kernel, rendermime) { super(rendermime); this._kernel = kernel; kernel.statusChanged.connect((sender, args) => { this._handleKernelStatusChange(args); }); kernel.connectionStatusChanged.connect((sender, args) => { this._handleKernelConnectionStatusChange(args); }); this._handleKernelChanged({ name: 'kernel', oldValue: null, newValue: kernel, }); this.restoreWidgets(); } _handleKernelConnectionStatusChange(status) { 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) { this.restoreWidgets(); } } } _handleKernelStatusChange(status) { if (status === 'restarting') { this.disconnect(); } } /** * Restore widgets from kernel and saved state. */ async restoreWidgets() { try { this._kernelRestoreInProgress = true; await this._loadFromKernel(); this._restoredStatus = true; this._restored.emit(); } catch (err) { // Do nothing } this._kernelRestoreInProgress = false; } /** * Dispose the resources held by the manager. */ dispose() { if (this.isDisposed) { return; } this._kernel = null; super.dispose(); } get kernel() { return this._kernel; } } /** * A widget manager that returns phosphor widgets. */ export class WidgetManager extends LabWidgetManager { constructor(context, rendermime, settings) { var _a, _b; super(rendermime); 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 ((_a = context.sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel) { this._handleKernelChanged({ name: 'kernel', oldValue: null, newValue: (_b = context.sessionContext.session) === null || _b === void 0 ? void 0 : _b.kernel, }); } this.restoreWidgets(this._context.model); this._settings = settings; context.saveState.connect((sender, saveState) => { if (saveState === 'started' && settings.saveState) { this._saveState(); } }); } /** * Save the widget state to the context model. */ _saveState() { const state = this.get_state_sync({ drop_defaults: true }); if (this._context.model.setMetadata) { this._context.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, }); } } _handleKernelConnectionStatusChange(status) { 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, }); } } } _handleKernelStatusChange(status) { if (status === 'restarting') { this.disconnect(); } } /** * Restore widgets from kernel and saved state. */ async restoreWidgets(notebook, { loadKernel, loadNotebook } = { loadKernel: true, loadNotebook: true }) { 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 the restore worked above, then update our state. this._restoredStatus = true; this._restored.emit(); } catch (err) { // Do nothing if the restore did not work. } } /** * Load widget state from notebook metadata */ async _loadFromNotebook(notebook) { const widget_md = notebook.getMetadata ? notebook.getMetadata('widgets') : // 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); } } /** * Dispose the resources held by the manager. */ dispose() { if (this.isDisposed) { return; } this._context = null; super.dispose(); } /** * Resolve a URL relative to the current notebook location. */ async resolveUrl(url) { const partial = await this.context.urlResolver.resolveUrl(url); return this.context.urlResolver.getDownloadUrl(partial); } get context() { return this._context; } get kernel() { var _a, _b, _c; return (_c = (_b = (_a = this._context.sessionContext) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.kernel) !== null && _c !== void 0 ? _c : null; } /** * Register a widget model. */ register_model(model_id, modelPromise) { 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() { await super.clear_state(); this.setDirty(); } /** * Set the dirty state of the notebook model if applicable. * * TODO: perhaps should also set dirty when any model changes any data */ setDirty() { if (this._settings.saveState) { this._context.model.dirty = true; } } } //# sourceMappingURL=manager.js.map