Skip to content
Permalink
Browse files

feat: use jupyterlab-manager output widget

Most of the widget manager logic is duplicated from the
@jupyter-widgets/jupyterlab-manager package. This PR aims at using the
package directly.
The main benefit of this approach is support for output of "stream" messages.
It becomes possible to render and interact with interactive widgets.
An example of interactive widget has been added in
notebooks/interactive.ipynb

Fix #19
Fix #23
  • Loading branch information...
msuperina authored and SylvainCorlay committed Jan 22, 2019
1 parent 82b47b5 commit 30d09d0cdd5c98d94c9abf243ebd4c217d51ae6b
Showing with 2,405 additions and 1,047 deletions.
  1. +5 −6 js/WidgetApplication.js
  2. +1 −2 js/index.js
  3. +144 −149 js/manager.js
  4. +0 −156 js/output.js
  5. +2,185 −605 js/package-lock.json
  6. +1 −0 js/package.json
  7. +0 −23 js/renderMime.js
  8. +0 −104 js/services-shim.js
  9. +1 −1 js/webpack.config.js
  10. +67 −0 notebooks/interactive.ipynb
  11. +1 −1 voila/static/main.js
@@ -8,27 +8,26 @@

import { Kernel, ServerConnection } from '@jupyterlab/services'
import { PageConfig } from '@jupyterlab/coreutils';
import { WidgetManager } from './manager'

import { WidgetManager } from './manager';

import 'font-awesome/css/font-awesome.css'
import '@phosphor/widgets/style/index.css'
import './widgets.css'

export class WidgetApplication {
constructor (loader) {
this._loader = loader;
}

async renderWidgets() {
const baseUrl = PageConfig.getBaseUrl();
const kernelId = PageConfig.getOption('kernelId');
const connectionInfo = ServerConnection.makeSettings({baseUrl});
const connectionInfo = ServerConnection.makeSettings({ baseUrl });

let model = await Kernel.findById(kernelId, connectionInfo);
let kernel = await Kernel.connectTo(model, connectionInfo);
this._kernel = kernel;

const manager = new WidgetManager(kernel, this._loader);
const widgetManager = new WidgetManager(kernel);
widgetManager.build_widgets();
}

async cleanWidgets() {
@@ -6,5 +6,4 @@
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

export { WidgetApplication } from './WidgetApplication';
export { requireLoader } from '@jupyter-widgets/html-manager';
export { WidgetApplication } from './WidgetApplication';
@@ -1,149 +1,144 @@
/***************************************************************************
* Copyright (c) 2018, Voila contributors *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

import * as base from '@jupyter-widgets/base'
import * as controls from '@jupyter-widgets/controls';
import * as pWidget from '@phosphor/widgets';
import { Signal } from '@phosphor/signaling';

import { HTMLManager } from '@jupyter-widgets/html-manager';

import * as outputWidgets from './output';
import { ShimmedComm } from './services-shim';
import { createRenderMimeRegistryWithWidgets } from './renderMime';

if (typeof window !== "undefined" && typeof window.define !== "undefined") {
window.define("@jupyter-widgets/base", base);
window.define("@jupyter-widgets/controls", controls);
}

export class WidgetManager extends HTMLManager {
constructor(kernel, loader) {
super();
this.kernel = kernel;
this.registerWithKernel(kernel)
this.loader = loader;
this.renderMime = createRenderMimeRegistryWithWidgets(this);
this._onError = new Signal(this)
this.build_widgets()
}

async build_widgets() {
let models = await this.build_models()
window.models = models
let element = document.body;
let tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (let i=0; i!=tags.length; ++i) {
let viewtag = tags[i];
let widgetViewObject = JSON.parse(viewtag.innerHTML);
let model_id = widgetViewObject.model_id;
let model = models[model_id]
let prev = viewtag.previousElementSibling;
let widgetTag = document.createElement('div');
widgetTag.className = 'widget-subarea';
viewtag.parentElement.insertBefore(widgetTag, viewtag);
this.display_model(undefined, model, { el : widgetTag });
}
}
async build_models() {
let comm_ids = await this._get_comm_info()
let models = {};
let widgets_info = await Promise.all(Object.keys(comm_ids).map(async (comm_id) => {
var comm = await this._create_comm(this.comm_target_name, comm_id);
return this._update_comm(comm);
}));

await Promise.all(widgets_info.map(async (widget_info) => {
let promise = this.new_model({
model_name: widget_info.msg.content.data.state._model_name,
model_module: widget_info.msg.content.data.state._model_module,
model_module_version: widget_info.msg.content.data.state._model_module_version,
comm: widget_info.comm,
}, widget_info.msg.content.data.state);
let model = await promise;
models[model.model_id] = model;
return promise;
}));
return models
}

async _update_comm(comm) {
return new Promise(function(resolve, reject) {
comm.on_msg(async (msg) => {
base.put_buffers(msg.content.data.state, msg.content.data.buffer_paths, msg.buffers);
if (msg.content.data.method === 'update') {
resolve({comm: comm, msg: msg})
}
});
comm.send({method: 'request_state'}, {})
})
}

get onError() {
return this._onError
}

registerWithKernel(kernel) {
if (this._commRegistration) {
this._commRegistration.dispose();
}
this._commRegistration = kernel.registerCommTarget(
this.comm_target_name,
(comm, message) =>
this.handle_comm_open(new ShimmedComm(comm), message)
);
}

display_view(msg, view, options) {
const el = options.el || this.el;
return Promise.resolve(view).then(view => {
pWidget.Widget.attach(view.pWidget, el);
view.on('remove', function() {
console.log('view removed', view);
});
return view;
});
}

loadClass(className, moduleName, moduleVersion) {
if (moduleName === '@jupyter-widgets/output') {
return Promise.resolve(outputWidgets).then(module => {
if (module[className]) {
return module[className];
} else {
return Promise.reject(
`Class ${className} not found in module ${moduleName}`
);
}
})
} else {
return super.loadClass(className, moduleName, moduleVersion)
}
}

callbacks(view) {
const baseCallbacks = super.callbacks(view)
return {
...baseCallbacks,
iopub: { output: (msg) => this._onError.emit(msg) }
}
}

_create_comm(target_name, model_id, data, metadata) {
const comm = this.kernel.connectToComm(target_name, model_id)
if (data || metadata ) {
comm.open(data, metadata)
}
return Promise.resolve(new ShimmedComm(comm))
}

_get_comm_info() {
return this.kernel.requestCommInfo({ target: this.comm_target_name})
.then(reply => reply.content.comms)
}
}
/***************************************************************************
* Copyright (c) 2018, Voila contributors *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

import { RenderMimeRegistry, standardRendererFactories } from '@jupyterlab/rendermime';
import { WidgetManager as JupyterLabManager } from '@jupyter-widgets/jupyterlab-manager/lib/manager';
import { HTMLManager, requireLoader } from '@jupyter-widgets/html-manager';
import { WidgetRenderer } from '@jupyter-widgets/jupyterlab-manager/lib/renderer';
import * as output from '@jupyter-widgets/jupyterlab-manager/lib/output';
import * as base from '@jupyter-widgets/base';
import * as controls from '@jupyter-widgets/controls';

if (typeof window !== "undefined" && typeof window.define !== "undefined") {
window.define("@jupyter-widgets/base", base);
window.define("@jupyter-widgets/controls", controls);
window.define("@jupyter-widgets/output", output);
}

const WIDGET_MIMETYPE = 'application/vnd.jupyter.widget-view+json';
const htmlManager = new HTMLManager({ loader: requireLoader });

export class WidgetManager extends JupyterLabManager {

constructor(kernel) {
const context = createContext(kernel);
const rendermime = createRenderMimeRegistry();
super(context, rendermime);
this._registerWidgets();
this.loader = requireLoader;
}

async build_widgets() {
const models = await this._build_models();
const tags = document.body.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]');
for (let i=0; i!=tags.length; ++i) {
const viewtag = tags[i];
const widgetViewObject = JSON.parse(viewtag.innerHTML);
const { model_id } = widgetViewObject;
const model = models[model_id];
const widgetTag = document.createElement('div');
widgetTag.className = 'widget-subarea';
viewtag.parentElement.insertBefore(widgetTag, viewtag);
const view = await this.display_model(undefined, model, { el : widgetTag });
widgetTag.appendChild(view.node);
}
}

async loadClass(className, moduleName, moduleVersion) {
if (
moduleName === '@jupyter-widgets/base' ||
moduleName === '@jupyter-widgets/controls' ||
moduleName === '@jupyter-widgets/output'
) {
return super.loadClass(className, moduleName, moduleVersion);
}
else {
return htmlManager.loadClass(className, moduleName, moduleVersion);
}
}

_registerWidgets() {
this.register({
name: '@jupyter-widgets/base',
version: base.JUPYTER_WIDGETS_VERSION,
exports: base
});
this.register({
name: '@jupyter-widgets/controls',
version: controls.JUPYTER_CONTROLS_VERSION,
exports: controls
});
this.register({
name: '@jupyter-widgets/output',
version: output.OUTPUT_WIDGET_VERSION,
exports: output
});
}

async _build_models() {
const comm_ids = await this._get_comm_info();
const models = {};
const widgets_info = await Promise.all(Object.keys(comm_ids).map(async (comm_id) => {
const comm = await this._create_comm(this.comm_target_name, comm_id);
return this._update_comm(comm);
}));

await Promise.all(widgets_info.map(async (widget_info) => {
const state = widget_info.msg.content.data.state;
const modelPromise = this.new_model(
{
model_name: state._model_name,
model_module: state._model_module,
model_module_version: state._model_module_version,
comm: widget_info.comm,
},
state
);
const model = await modelPromise;
models[model.model_id] = model;
return modelPromise;
}));
return models;
}

async _update_comm(comm) {
return new Promise(function(resolve, reject) {
comm.on_msg(async (msg) => {
base.put_buffers(msg.content.data.state, msg.content.data.buffer_paths, msg.buffers);
if (msg.content.data.method === 'update') {
resolve({comm: comm, msg: msg});
}
});
comm.send({method: 'request_state'}, {});
});
}

}

function createContext(kernel) {
return {
session: {
kernel,
kernelChanged: {
connect: () => {}
}
}
};
}

function createRenderMimeRegistry() {
const rendermime = new RenderMimeRegistry({
initialFactories: standardRendererFactories
});
rendermime.addFactory({
safe: false,
mimeTypes: [WIDGET_MIMETYPE],
createRenderer: options => new WidgetRenderer(options, manager)
}, 1);
return rendermime;
}

0 comments on commit 30d09d0

Please sign in to comment.
You can’t perform that action at this time.