@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