From f63da1431649e6e0af6fa23c40de41c71c811bab Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 19 Apr 2017 13:20:23 -0400 Subject: [PATCH 01/23] Handle binary state values when serializing/deserializing the widget manager state. Fixes #1282 --- jupyter-js-widgets/package.json | 1 + jupyter-js-widgets/src/manager-base.ts | 64 +++++++++++++++----------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/jupyter-js-widgets/package.json b/jupyter-js-widgets/package.json index 965b2672ad..5dc9bea0e0 100644 --- a/jupyter-js-widgets/package.json +++ b/jupyter-js-widgets/package.json @@ -91,6 +91,7 @@ "@types/semver": "^5.3.30", "ajv": "^4.9.0", "backbone": "1.2.0", + "base64-js": "1.2.0", "d3-format": "^0.5.1", "font-awesome": "^4.5.0", "jquery": "^3.1.1", diff --git a/jupyter-js-widgets/src/manager-base.ts b/jupyter-js-widgets/src/manager-base.ts index 04a6be326d..1283dea9fd 100644 --- a/jupyter-js-widgets/src/manager-base.ts +++ b/jupyter-js-widgets/src/manager-base.ts @@ -7,6 +7,10 @@ import * as semver from 'semver'; import * as Backbone from 'backbone'; import * as services from '@jupyterlab/services'; +import { + toByteArray, fromByteArray +} from 'base64-js'; + import { WidgetModel } from './widget'; @@ -375,21 +379,22 @@ abstract class ManagerBase { * @returns Promise for a state dictionary */ get_state(options: StateOptions): Promise { - var that = this; - return utils.resolvePromisesDict(this._models).then(function(models) { - var state = {}; - for (var model_id in models) { - if (models.hasOwnProperty(model_id)) { - var model = models[model_id]; - state[model_id] = utils.resolvePromisesDict({ - model_name: model.name, - model_module: model.module, - model_module_version: model.get('_model_module_version'), - state: model.constructor._serialize_state(model.get_state(options.drop_defaults), that) - }); - } - } - return utils.resolvePromisesDict(state); + return utils.resolvePromisesDict(this._models).then((models) => { + let state = {}; + Object.keys(models).forEach(model_id => { + let model = models[model_id]; + let split = utils.remove_buffers(model.serialize(model.get_state(options.drop_defaults))); + let base64Buffers = split.buffers.map(fromByteArray); + state[model_id] = { + model_name: model.name, + model_module: model.module, + model_module_version: model.get('_model_module_version'), + state: split.state, + buffer_paths: split.buffer_paths, + buffers: base64Buffers + }; + }); + return state; }).catch(utils.reject('Could not get state of widget manager', true)); }; @@ -401,17 +406,22 @@ abstract class ManagerBase { * state. */ set_state(state, displayOptions) { - var that = this; - // Recreate all the widget models for the given widget manager state. - var all_models = that._get_comm_info().then(function(live_comms) { - return Promise.all(_.map(Object.keys(state), function (model_id) { + let all_models = this._get_comm_info().then(live_comms => { + return Promise.all(Object.keys(state).map(model_id => { + + // First put back the binary buffers + let modelState = state[model_id].state; + let buffer_paths = modelState.buffer_paths || []; + let buffers = (modelState.buffers || []).map(toByteArray); + utils.put_buffers(modelState, buffer_paths, buffers); - // If the model has already been created, set it's state and then + // If the model has already been created, set its state and then // return it. - if (that._models[model_id]) { - return that._models[model_id].then(function(model) { - return model.constructor._deserialize_state(state[model_id].state || {}, that).then(function(attributes) { + if (this._models[model_id]) { + return this._models[model_id].then(model => { + // deserialize state + return model.constructor._deserialize_state(modelState || {}, this).then(attributes => { model.set_state(attributes); return model; }); @@ -419,8 +429,8 @@ abstract class ManagerBase { } if (live_comms.hasOwnProperty(model_id)) { // live comm - return that._create_comm(that.comm_target_name, model_id).then(function(new_comm) { - return that.new_model({ + return this._create_comm(this.comm_target_name, model_id).then(new_comm => { + return this.new_model({ comm: new_comm, model_name: state[model_id].model_name, model_module: state[model_id].model_module, @@ -428,12 +438,12 @@ abstract class ManagerBase { }); }); } else { // dead comm - return that.new_model({ + return this.new_model({ model_id: model_id, model_name: state[model_id].model_name, model_module: state[model_id].model_module, model_module_version: state[model_id].model_module_version - }, state[model_id].state); + }, modelState); } })); }); From ad04dc975eb87540775d2042cfa8f4fc48dc88a2 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 19 Apr 2017 13:58:09 -0400 Subject: [PATCH 02/23] Add version 2 of the state and view schema definitions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. We add optional buffer_paths and buffers keys to a model’s state dictionary. 2. We remove the requirement that each schema defines *all* of the properties an object may have, so that we can have backwards-compatible updates by adding optional properties. In other words, removed a number of additionalProperties: false entries in the schema. --- jupyter-js-widgets/package.json | 2 +- jupyter-js-widgets/src-embed/embed-webpack.ts | 4 +- jupyter-widgets-schema/package.json | 2 +- jupyter-widgets-schema/v2/state.schema.json | 64 +++++++++++++++++++ jupyter-widgets-schema/v2/view.schema.json | 22 +++++++ widgetsnbextension/src/embed_widgets.js | 2 +- widgetsnbextension/src/manager.js | 2 +- 7 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 jupyter-widgets-schema/v2/state.schema.json create mode 100644 jupyter-widgets-schema/v2/view.schema.json diff --git a/jupyter-js-widgets/package.json b/jupyter-js-widgets/package.json index 5dc9bea0e0..1b8613a877 100644 --- a/jupyter-js-widgets/package.json +++ b/jupyter-js-widgets/package.json @@ -96,7 +96,7 @@ "font-awesome": "^4.5.0", "jquery": "^3.1.1", "jquery-ui": "^1.12.1", - "jupyter-widgets-schema": "^0.1.1", + "jupyter-widgets-schema": "^0.2.0", "lolex": "^1.4.0", "scriptjs": "^2.5.8", "semver": "^5.1.0", diff --git a/jupyter-js-widgets/src-embed/embed-webpack.ts b/jupyter-js-widgets/src-embed/embed-webpack.ts index ce5f4416e9..e2a3b2c3f5 100644 --- a/jupyter-js-widgets/src-embed/embed-webpack.ts +++ b/jupyter-js-widgets/src-embed/embed-webpack.ts @@ -19,8 +19,8 @@ require('../css/widgets.css'); // Load json schema validator var Ajv = require('ajv'); -var widget_state_schema = require('jupyter-widgets-schema').v1.state; -var widget_view_schema = require('jupyter-widgets-schema').v1.view; +var widget_state_schema = require('jupyter-widgets-schema').v2.state; +var widget_view_schema = require('jupyter-widgets-schema').v2.view; // Magic global widget rendering function: import * as widgets from '../../jupyter-js-widgets/lib/index'; diff --git a/jupyter-widgets-schema/package.json b/jupyter-widgets-schema/package.json index 9a47ac1bd0..55c749b139 100644 --- a/jupyter-widgets-schema/package.json +++ b/jupyter-widgets-schema/package.json @@ -1,6 +1,6 @@ { "name": "jupyter-widgets-schema", - "version": "0.1.1", + "version": "0.2.0", "description": "Schemas for the Jupyter interactive Widgets", "main": "index.js", "scripts": { diff --git a/jupyter-widgets-schema/v2/state.schema.json b/jupyter-widgets-schema/v2/state.schema.json new file mode 100644 index 0000000000..9791328984 --- /dev/null +++ b/jupyter-widgets-schema/v2/state.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Jupyter Interactive Widget State JSON schema.", + "type": "object", + "properties" : { + "version_major" : { + "description": "Format version (major)", + "type": "number", + "minimum": 2, + "maximum": 2 + }, + "version_minor" : { + "description": "Format version (minor)", + "type": "number" + }, + "state": { + "description": "Model State for All Widget Models", + "type": "object", + "additionalProperties" : { + "type": "object", + "properties": { + "model_name": { + "description" : "Name of the JavaScript class holding the model implementation", + "type": "string" + }, + "model_module": { + "description" : "Name of the JavaScript module holding the model implementation", + "type": "string" + }, + "model_module_version": { + "description" : "Semver range for the JavaScript module holding the model implementation", + "type": "string" + }, + "state": { + "description" : "Serialized state of the model", + "type": "object" + }, + "buffer_paths": { + "description" : "Array of paths in the state for the corresponding buffers", + "type": "array", + "items": { + "description": "A path for a binary buffer value.", + "type": "array", + "items": { + "description": "An object key or array index", + "anyOf": [{"type": "string"}, {"type": "number"}] + } + } + }, + "buffers": { + "description" : "Array of base64-encoded binary buffers", + "type": "array", + "items": { + "description": "A base64-encoded binary buffer", + "type": "string" + } + } + }, + "required": [ "model_name", "model_module", "state" ] + } + } + }, + "required": [ "version_major", "version_minor", "state" ] +} diff --git a/jupyter-widgets-schema/v2/view.schema.json b/jupyter-widgets-schema/v2/view.schema.json new file mode 100644 index 0000000000..66ae922296 --- /dev/null +++ b/jupyter-widgets-schema/v2/view.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Jupyter Interactive Widget View JSON schema.", + "type": "object", + "properties" : { + "version_major" : { + "description": "Format version (major)", + "type": "number", + "minimum": 2, + "maximum": 2 + }, + "version_minor" : { + "description": "Format version (minor)", + "type": "number" + }, + "model_id": { + "description": "Unique identifier of the widget model to be displayed", + "type": "string" + } + }, + "required": [ "model_id" ] +} diff --git a/widgetsnbextension/src/embed_widgets.js b/widgetsnbextension/src/embed_widgets.js index 1ea2a71dbc..b887afa37d 100644 --- a/widgetsnbextension/src/embed_widgets.js +++ b/widgetsnbextension/src/embed_widgets.js @@ -13,7 +13,7 @@ var embed_widgets = function() { 'drop_defaults': true }).then(function(state) { var data = JSON.stringify({ - version_major: 1, + version_major: 2, version_minor: 0, state: state }, null, ' '); diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index 402ef74936..ed34b6d033 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -164,7 +164,7 @@ WidgetManager.prototype._init_actions = function() { }).then(function(state) { Jupyter.notebook.metadata.widgets = { 'application/vnd.jupyter.widget-state+json' : { - version_major: 1, + version_major: 2, version_minor: 0, state: state } From 66c6033357d5946a79ad44c42b6fafa379230f13 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 20 Apr 2017 11:33:17 -0400 Subject: [PATCH 03/23] Consolidate the buffer information into a single object, and add a new encoding attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We initially have the ‘hex’ and ‘base64’ encodings. We unpublished the 0.2.0 package so we could replace version 2 of the spec with these updates, so we bump the package number to 0.3.0-beta.0 --- jupyter-js-widgets/package.json | 2 +- jupyter-widgets-schema/package.json | 2 +- jupyter-widgets-schema/v2/state.schema.json | 50 +++++++++++++-------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/jupyter-js-widgets/package.json b/jupyter-js-widgets/package.json index 1b8613a877..eaf30dc92e 100644 --- a/jupyter-js-widgets/package.json +++ b/jupyter-js-widgets/package.json @@ -96,7 +96,7 @@ "font-awesome": "^4.5.0", "jquery": "^3.1.1", "jquery-ui": "^1.12.1", - "jupyter-widgets-schema": "^0.2.0", + "jupyter-widgets-schema": "^0.3.0-beta.0", "lolex": "^1.4.0", "scriptjs": "^2.5.8", "semver": "^5.1.0", diff --git a/jupyter-widgets-schema/package.json b/jupyter-widgets-schema/package.json index 55c749b139..d8dc7d7011 100644 --- a/jupyter-widgets-schema/package.json +++ b/jupyter-widgets-schema/package.json @@ -1,6 +1,6 @@ { "name": "jupyter-widgets-schema", - "version": "0.2.0", + "version": "0.3.0-beta.0", "description": "Schemas for the Jupyter interactive Widgets", "main": "index.js", "scripts": { diff --git a/jupyter-widgets-schema/v2/state.schema.json b/jupyter-widgets-schema/v2/state.schema.json index 9791328984..998fe87d0f 100644 --- a/jupyter-widgets-schema/v2/state.schema.json +++ b/jupyter-widgets-schema/v2/state.schema.json @@ -14,7 +14,7 @@ "type": "number" }, "state": { - "description": "Model State for All Widget Models", + "description": "Model State for All Widget Models - keys are model ids, values are model state", "type": "object", "additionalProperties" : { "type": "object", @@ -35,25 +35,37 @@ "description" : "Serialized state of the model", "type": "object" }, - "buffer_paths": { - "description" : "Array of paths in the state for the corresponding buffers", - "type": "array", - "items": { - "description": "A path for a binary buffer value.", - "type": "array", - "items": { - "description": "An object key or array index", - "anyOf": [{"type": "string"}, {"type": "number"}] - } - } - }, "buffers": { - "description" : "Array of base64-encoded binary buffers", - "type": "array", - "items": { - "description": "A base64-encoded binary buffer", - "type": "string" - } + "description": "Binary buffers in the state", + "type": "object", + "properties": { + "paths": { + "description" : "Array of paths in the state for the corresponding buffers", + "type": "array", + "items": { + "description": "A path for a binary buffer value.", + "type": "array", + "items": { + "description": "An object key or array index", + "type": ["string", "number"] + } + } + }, + "data": { + "description" : "Array of encoded binary buffers, encoded as specified in the 'encoding' property", + "type": "array" + }, + "encoding": { + "description": "The encoding of each item in the data property", + "type": "string", + "oneOf": [ + {"enum": ["hex"], "description": "Base 16 encoding, as specified in RFC 4648, section 8 (https://tools.ietf.org/html/rfc4648#section-8)"}, + {"enum": ["base64"], "description": "Base 64 encoding, as specified in RFC 4648, section 4 (https://tools.ietf.org/html/rfc4648#section-4)"} + + ] + } + }, + "required": ["paths", "data", "encoding"] } }, "required": [ "model_name", "model_module", "state" ] From 2416a6dd33083678059d35450f8dcdd3ef2190ac Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 20 Apr 2017 15:21:54 -0400 Subject: [PATCH 04/23] zip the buffer paths and data attributes to ease debugging Now a model state can have structure like: ``` state: ... buffers: { buffers: [ {path: ["a", 1, "b"], data: "AFE0139AB"}, {path: ["x", 0], data: "45E01FE"} ], encoding: "hex" } ``` --- jupyter-widgets-schema/v2/state.schema.json | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/jupyter-widgets-schema/v2/state.schema.json b/jupyter-widgets-schema/v2/state.schema.json index 998fe87d0f..a6ae4f893a 100644 --- a/jupyter-widgets-schema/v2/state.schema.json +++ b/jupyter-widgets-schema/v2/state.schema.json @@ -39,24 +39,29 @@ "description": "Binary buffers in the state", "type": "object", "properties": { - "paths": { - "description" : "Array of paths in the state for the corresponding buffers", + "buffers": { "type": "array", "items": { - "description": "A path for a binary buffer value.", - "type": "array", - "items": { - "description": "An object key or array index", - "type": ["string", "number"] - } + "type": "object", + "properties": { + "path": { + "description": "A path for a binary buffer value.", + "type": "array", + "items": { + "description": "An object key or array index", + "type": ["string", "number"] + } + }, + "data": { + "description" : "A binary buffer encoded as specified in the 'encoding' property", + "type": "string" + } + }, + "required": ["path", "data"] } }, - "data": { - "description" : "Array of encoded binary buffers, encoded as specified in the 'encoding' property", - "type": "array" - }, "encoding": { - "description": "The encoding of each item in the data property", + "description": "The encoding of each buffer data property", "type": "string", "oneOf": [ {"enum": ["hex"], "description": "Base 16 encoding, as specified in RFC 4648, section 8 (https://tools.ietf.org/html/rfc4648#section-8)"}, @@ -65,7 +70,7 @@ ] } }, - "required": ["paths", "data", "encoding"] + "required": ["buffers", "encoding"] } }, "required": [ "model_name", "model_module", "state" ] From 964eeb19e4194f649432493801a795a749736d18 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 20 Apr 2017 17:11:48 -0400 Subject: [PATCH 05/23] Make the embedding docs refer to the jupiter-widgets-schema package. --- docs/source/embedding.md | 81 +--------------------------------------- 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/docs/source/embedding.md b/docs/source/embedding.md index d2936b5437..e21fc9845f 100644 --- a/docs/source/embedding.md +++ b/docs/source/embedding.md @@ -30,58 +30,7 @@ This HTML snippet is composed of multiple `\n' + ''; From baaf28b72cbd8d8a8e84dcb86f39fad24e79deea Mon Sep 17 00:00:00 2001 From: maartenbreddels Date: Tue, 25 Apr 2017 13:25:25 +0200 Subject: [PATCH 18/23] some minor fixes to get embedding half-working --- jupyter-js-widgets/src-embed/embed-webpack.ts | 2 +- jupyter-js-widgets/src/manager-base.ts | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/jupyter-js-widgets/src-embed/embed-webpack.ts b/jupyter-js-widgets/src-embed/embed-webpack.ts index e2a3b2c3f5..2351920cb3 100644 --- a/jupyter-js-widgets/src-embed/embed-webpack.ts +++ b/jupyter-js-widgets/src-embed/embed-webpack.ts @@ -90,7 +90,7 @@ function renderManager(element, tag) { console.log(model_validate.errors); } var manager = new embed.EmbedManager(); - manager.set_state(widgetStateObject.state, {}).then(function(models) { + manager.set_state(widgetStateObject, {}).then(function(models) { var tags = element.querySelectorAll('script[type="application/vnd.jupyter.widget-view+json"]'); for (var i=0; i!=tags.length; ++i) { // TODO: validate view schema diff --git a/jupyter-js-widgets/src/manager-base.ts b/jupyter-js-widgets/src/manager-base.ts index 510acdf0c7..c640ad5ec5 100644 --- a/jupyter-js-widgets/src/manager-base.ts +++ b/jupyter-js-widgets/src/manager-base.ts @@ -385,7 +385,8 @@ abstract class ManagerBase { let model = models[model_id]; let split = utils.remove_buffers(model.serialize(model.get_state(options.drop_defaults))); let buffers = split.buffers.map((buffer, index) => { - return {data: fromByteArray(buffer), path: split.buffer_paths[index], encoding: 'base64'}; + // fromByteArray expects bytes, while our buffer may be a typed array (e.g. Float32Array) + return {data: fromByteArray(new Uint8Array(buffer.buffer)), path: split.buffer_paths[index], encoding: 'base64'}; }); state[model_id] = { model_name: model.name, @@ -424,11 +425,13 @@ abstract class ManagerBase { // First put back the binary buffers let decode = {'base64': toByteArray, 'hex': utils.hexToBuffer}; - let modelState = models[model_id].state; - if (modelState.buffers) { - let bufferPaths = modelState.buffers.map(b => b.path); - let buffers = modelState.buffers.map(b => decode[b.encoding](b.data)); - utils.put_buffers(modelState.state, bufferPaths, buffers); + let model = models[model_id]; + let modelState = model.state; + if (model.buffers) { + let bufferPaths = model.buffers.map(b => b.path); + // similas as what comes off the wire, we put the data into a DataView object + let buffers = model.buffers.map(b => new DataView(decode[b.encoding](b.data).buffer)); + utils.put_buffers(model.state, bufferPaths, buffers); } // If the model has already been created, set its state and then From 896d31be6b37d0e9ffb1fecabe940de121507092 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Apr 2017 11:15:04 -0400 Subject: [PATCH 19/23] Handle buffers more carefully --- jupyter-js-widgets/src/manager-base.ts | 13 +++++++++++-- jupyter-js-widgets/src/utils.ts | 4 ++-- jupyter-js-widgets/src/widget.ts | 14 +++++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/jupyter-js-widgets/src/manager-base.ts b/jupyter-js-widgets/src/manager-base.ts index 510acdf0c7..d682019358 100644 --- a/jupyter-js-widgets/src/manager-base.ts +++ b/jupyter-js-widgets/src/manager-base.ts @@ -189,8 +189,17 @@ abstract class ManagerBase { * Handle when a comm is opened. */ handle_comm_open(comm: shims.services.Comm, msg: services.KernelMessage.ICommOpenMsg): Promise { - var data = (msg.content.data as any); - utils.put_buffers(data.state, data.buffer_paths, msg.buffers) + let data = (msg.content.data as any); + let buffer_paths = data.buffer_paths || []; + // Make sure the buffers are DataViews + let buffers = (msg.buffers || []).map(b => { + if (b instanceof DataView) { + return b; + } else { + return new DataView(b instanceof ArrayBuffer ? b : b.buffer); + } + }); + utils.put_buffers(data.state, buffer_paths, buffers); return this.new_model({ model_name: data.state['_model_name'], model_module: data.state['_model_module'], diff --git a/jupyter-js-widgets/src/utils.ts b/jupyter-js-widgets/src/utils.ts index 0523628799..4e61074652 100644 --- a/jupyter-js-widgets/src/utils.ts +++ b/jupyter-js-widgets/src/utils.ts @@ -182,7 +182,7 @@ function escape_html(text: string): string { * Will lead to {a: 1, b: {data: array1}, c: [0, array2]} */ export -function put_buffers(state, buffer_paths, buffers) { +function put_buffers(state, buffer_paths: (string | number)[][], buffers: DataView[]) { for (let i=0; i { // see Widget.open/_split_state_buffers about why we need state_with_buffers - var state = msg.content.data.state; - var buffer_paths = msg.content.data.buffer_paths || []; - var buffers = msg.buffers || []; + let state = msg.content.data.state; + let buffer_paths = msg.content.data.buffer_paths || []; + // Make sure the buffers are DataViews + let buffers = (msg.buffers || []).map(b => { + if (b instanceof DataView) { + return b; + } else { + return new DataView(b instanceof ArrayBuffer ? b : b.buffer); + } + }); + utils.put_buffers(state, buffer_paths, buffers); return (this.constructor as typeof WidgetModel)._deserialize_state(state, this.widget_manager); }).then((state) => { From 9a94142fe87782f1c82fa9d1a75266478f3ec774 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Apr 2017 11:28:40 -0400 Subject: [PATCH 20/23] For simplicity, make remove_buffers convert all buffers to plain ArrayBuffers. --- jupyter-js-widgets/src/manager-base.ts | 4 ++-- jupyter-js-widgets/src/utils.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jupyter-js-widgets/src/manager-base.ts b/jupyter-js-widgets/src/manager-base.ts index 98f2c53149..6a698a57de 100644 --- a/jupyter-js-widgets/src/manager-base.ts +++ b/jupyter-js-widgets/src/manager-base.ts @@ -394,8 +394,8 @@ abstract class ManagerBase { let model = models[model_id]; let split = utils.remove_buffers(model.serialize(model.get_state(options.drop_defaults))); let buffers = split.buffers.map((buffer, index) => { - // fromByteArray expects bytes, while our buffer may be a typed array (e.g. Float32Array) - return {data: fromByteArray(new Uint8Array(buffer.buffer)), path: split.buffer_paths[index], encoding: 'base64'}; + // fromByteArray expects Uint8Array + return {data: fromByteArray(new Uint8Array(buffer)), path: split.buffer_paths[index], encoding: 'base64'}; }); state[model_id] = { model_name: model.name, diff --git a/jupyter-js-widgets/src/utils.ts b/jupyter-js-widgets/src/utils.ts index 4e61074652..f0cdcd3374 100644 --- a/jupyter-js-widgets/src/utils.ts +++ b/jupyter-js-widgets/src/utils.ts @@ -204,9 +204,9 @@ function put_buffers(state, buffer_paths: (string | number)[][], buffers: DataVi * and the buffers associated to those paths (.buffers). */ export -function remove_buffers(state): {state: any, buffers: (ArrayBuffer | ArrayBufferView)[], buffer_paths: (string | number)[][]} { - let buffers = []; - let buffer_paths = []; +function remove_buffers(state): {state: any, buffers: ArrayBuffer[], buffer_paths: (string | number)[][]} { + let buffers: ArrayBuffer[] = []; + let buffer_paths: (string | number)[][] = []; // if we need to remove an object from a list, we need to clone that list, otherwise we may modify // the internal state of the widget model // however, we do not want to clone everything, for performance @@ -226,7 +226,7 @@ function remove_buffers(state): {state: any, buffers: (ArrayBuffer | ArrayBuffer obj = _.clone(obj); is_cloned = true; } - buffers.push(value); + buffers.push(value instanceof ArrayBuffer ? value : value.buffer); buffer_paths.push(path.concat([i])); // easier to just keep the array, but clear the entry, otherwise we have to think // about array length, much easier this way @@ -255,7 +255,7 @@ function remove_buffers(state): {state: any, buffers: (ArrayBuffer | ArrayBuffer obj = _.clone(obj); is_cloned = true; } - buffers.push(value); + buffers.push(value instanceof ArrayBuffer ? value : value.buffer); buffer_paths.push(path.concat([key])); delete obj[key]; // for objects/dicts we just delete them } From c3b3c5d32382256d820320f17cecdb39ef38d9d0 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Apr 2017 11:49:54 -0400 Subject: [PATCH 21/23] Abstract out the base64 encoding to utils. --- jupyter-js-widgets/src/manager-base.ts | 13 ++++--------- jupyter-js-widgets/src/utils.ts | 23 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/jupyter-js-widgets/src/manager-base.ts b/jupyter-js-widgets/src/manager-base.ts index 6a698a57de..c05595815c 100644 --- a/jupyter-js-widgets/src/manager-base.ts +++ b/jupyter-js-widgets/src/manager-base.ts @@ -7,10 +7,6 @@ import * as semver from 'semver'; import * as Backbone from 'backbone'; import * as services from '@jupyterlab/services'; -import { - toByteArray, fromByteArray -} from 'base64-js'; - import { WidgetModel } from './widget'; @@ -394,8 +390,7 @@ abstract class ManagerBase { let model = models[model_id]; let split = utils.remove_buffers(model.serialize(model.get_state(options.drop_defaults))); let buffers = split.buffers.map((buffer, index) => { - // fromByteArray expects Uint8Array - return {data: fromByteArray(new Uint8Array(buffer)), path: split.buffer_paths[index], encoding: 'base64'}; + return {data: utils.bufferToBase64(buffer), path: split.buffer_paths[index], encoding: 'base64'}; }); state[model_id] = { model_name: model.name, @@ -433,13 +428,13 @@ abstract class ManagerBase { return Promise.all(Object.keys(models).map(model_id => { // First put back the binary buffers - let decode = {'base64': toByteArray, 'hex': utils.hexToBuffer}; + let decode: { [s: string]: (s: string) => ArrayBuffer; } = {'base64': utils.base64ToBuffer, 'hex': utils.hexToBuffer}; let model = models[model_id]; let modelState = model.state; if (model.buffers) { let bufferPaths = model.buffers.map(b => b.path); - // similas as what comes off the wire, we put the data into a DataView object - let buffers = model.buffers.map(b => new DataView(decode[b.encoding](b.data).buffer)); + // put_buffers expects buffers to be DataViews + let buffers = model.buffers.map(b => new DataView(decode[b.encoding](b.data))); utils.put_buffers(model.state, bufferPaths, buffers); } diff --git a/jupyter-js-widgets/src/utils.ts b/jupyter-js-widgets/src/utils.ts index f0cdcd3374..a42fdee45c 100644 --- a/jupyter-js-widgets/src/utils.ts +++ b/jupyter-js-widgets/src/utils.ts @@ -3,8 +3,9 @@ import * as _ from 'underscore'; -// TODO: ATTEMPT TO KILL THIS MODULE USING THIRD PARTY LIBRARIES WHEN IPYWIDGETS -// IS CONVERTED TO NODE COMMONJS. +import { + toByteArray, fromByteArray +} from 'base64-js'; /** * http://www.ietf.org/rfc/rfc4122.txt @@ -322,4 +323,20 @@ function hexToBuffer(hex: string): ArrayBuffer { x[i / 2] = parseInt(hex.slice(i, i + 2), 16); } return x.buffer; -} \ No newline at end of file +} + +/** + * Convert an ArrayBuffer to a base64 string. + */ +export +function bufferToBase64(buffer: ArrayBuffer): string { + return fromByteArray(new Uint8Array(buffer)); +} + +/** + * Convert a base64 string to an ArrayBuffer. + */ +export +function base64ToBuffer(base64: string): ArrayBuffer { + return toByteArray(base64).buffer; +} From 8f0a7266a26547e3fda962dec5c69f77128d825e Mon Sep 17 00:00:00 2001 From: maartenbreddels Date: Tue, 25 Apr 2017 21:41:44 +0200 Subject: [PATCH 22/23] small fix for embedding --- widgetsnbextension/src/embed_widgets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgetsnbextension/src/embed_widgets.js b/widgetsnbextension/src/embed_widgets.js index a1d7f42f44..22d8bacf7f 100644 --- a/widgetsnbextension/src/embed_widgets.js +++ b/widgetsnbextension/src/embed_widgets.js @@ -23,7 +23,7 @@ var embed_widgets = function() { cell.output_area.outputs.forEach(function (output) { if (output.data && output.data[VIEW_MIME_TYPE] - && state[output.data[VIEW_MIME_TYPE].model_id]) { + && state.state[output.data[VIEW_MIME_TYPE].model_id]) { views.push(('\n')); From 0ad54c8c06e9215825b57aacd6c0b0ab22c83d7b Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Apr 2017 15:51:05 -0400 Subject: [PATCH 23/23] Make sure widget views get included in the embed display. Also, make sure the display_data type actually conforms to the mimetype spec. --- ipywidgets/widgets/widget.py | 2 ++ widgetsnbextension/src/embed_widgets.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ipywidgets/widgets/widget.py b/ipywidgets/widgets/widget.py index 2ec1a0b673..030ffc55ee 100644 --- a/ipywidgets/widgets/widget.py +++ b/ipywidgets/widgets/widget.py @@ -630,6 +630,8 @@ def _ipython_display_(self, **kwargs): data = { 'text/plain': "A Jupyter Widget", 'application/vnd.jupyter.widget-view+json': { + 'version_major': '2', + 'version_minor': '0', 'model_id': self._model_id } } diff --git a/widgetsnbextension/src/embed_widgets.js b/widgetsnbextension/src/embed_widgets.js index a1d7f42f44..22d8bacf7f 100644 --- a/widgetsnbextension/src/embed_widgets.js +++ b/widgetsnbextension/src/embed_widgets.js @@ -23,7 +23,7 @@ var embed_widgets = function() { cell.output_area.outputs.forEach(function (output) { if (output.data && output.data[VIEW_MIME_TYPE] - && state[output.data[VIEW_MIME_TYPE].model_id]) { + && state.state[output.data[VIEW_MIME_TYPE].model_id]) { views.push(('\n'));