@jupyter-widgets/jupyterlab-manager
Version:
The JupyterLab extension providing Jupyter widgets.
465 lines • 15.2 kB
JavaScript
// 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