From 4cd3674fadb06f4797abad2611331aa87a05518b Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 16 Aug 2017 17:28:33 -0400 Subject: [PATCH 1/6] Convert the image to use a binary value and binary sync. We do this primarily so that we have a basic widget that exercises the binary messages. --- ipywidgets/widgets/widget_image.py | 8 +------- packages/controls/src/widget_image.ts | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ipywidgets/widgets/widget_image.py b/ipywidgets/widgets/widget_image.py index e1a282d2ab..a403dcafd9 100644 --- a/ipywidgets/widgets/widget_image.py +++ b/ipywidgets/widgets/widget_image.py @@ -31,10 +31,4 @@ class Image(DOMWidget, ValueWidget, CoreWidget): format = Unicode('png', help="The format of the image.").tag(sync=True) width = CUnicode(help="Width of the image in pixels.").tag(sync=True) height = CUnicode(help="Height of the image in pixels.").tag(sync=True) - _b64value = Unicode(help="The base64 encoded image data.").tag(sync=True) - - value = Bytes(help="The image data as a byte string.") - - @observe('value') - def _value_changed(self, change): - self._b64value = base64.b64encode(change['new']) + value = Bytes(help="The image data as a byte string.").tag(sync=True) diff --git a/packages/controls/src/widget_image.ts b/packages/controls/src/widget_image.ts index 4053aef625..0828acec8e 100644 --- a/packages/controls/src/widget_image.ts +++ b/packages/controls/src/widget_image.ts @@ -20,7 +20,7 @@ class ImageModel extends CoreDOMWidgetModel { format: 'png', width: '', height: '', - _b64value: '' + value: new Uint8Array(0) }); } } @@ -31,6 +31,7 @@ class ImageView extends DOMWidgetView { /** * Called when view is rendered. */ + super.render(); this.pWidget.addClass('jupyter-widgets'); this.pWidget.addClass('widget-image'); this.update(); // Set defaults. @@ -43,9 +44,13 @@ class ImageView extends DOMWidgetView { * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ - var image_src = 'data:image/' + this.model.get('format') + ';base64,' + this.model.get('_b64value'); - this.el.setAttribute('src', image_src); - + var blob = new Blob([this.model.get('value')], {type: `image/${this.model.get('format')}`}); + var url = URL.createObjectURL(blob); + var oldurl = this.el.src; + this.el.src = url; + if (oldurl) { + URL.revokeObjectURL(oldurl); + } var width = this.model.get('width'); if (width !== undefined && width.length > 0) { this.el.setAttribute('width', width); @@ -61,7 +66,14 @@ class ImageView extends DOMWidgetView { } return super.update(); } - + + remove() { + if (this.el.src) { + URL.revokeObjectURL(this.el.src); + } + super.remove() + } + /** * The default tag name. * From 495f6d76fac3657dc475e309f9b88ab31bf9e523 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 17 Aug 2017 11:55:40 -0400 Subject: [PATCH 2/6] Replace underscore with lodash --- packages/base/package.json | 4 +-- packages/base/src/manager-base.ts | 7 +++-- packages/base/src/services-shim.ts | 1 - packages/base/src/utils.ts | 38 ++++++++++++++++++----- packages/base/src/widget.ts | 37 +++++++++++----------- packages/base/src/widget_layout.ts | 9 +++--- packages/base/src/widget_style.ts | 11 +++---- packages/controls/src/widget_int.ts | 2 +- packages/controls/src/widget_selection.ts | 24 +++++++------- 9 files changed, 77 insertions(+), 56 deletions(-) diff --git a/packages/base/package.json b/packages/base/package.json index 8250008592..8c37dfc6ae 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -65,7 +65,7 @@ "@types/semver": "^5.3.30", "backbone": "1.2.3", "jquery": "^3.1.1", - "semver": "^5.1.0", - "underscore": "^1.8.3" + "lodash": "^4.17.4", + "semver": "^5.1.0" } } diff --git a/packages/base/src/manager-base.ts b/packages/base/src/manager-base.ts index b879088cd9..6f7118def8 100644 --- a/packages/base/src/manager-base.ts +++ b/packages/base/src/manager-base.ts @@ -1,7 +1,6 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import * as _ from 'underscore'; import * as utils from './utils'; import * as semver from 'semver'; import * as Backbone from 'backbone'; @@ -248,7 +247,9 @@ abstract class ManagerBase { let commPromise; // we check to make sure the view information is provided, to help catch // backwards incompatibility errors. - if (_.contains([options.view_name, options.view_module, options.view_module_version], undefined)) { + if (options.view_name === undefined + || options.view_module === undefined + || options.view_module_version === undefined) { return Promise.reject("new_widget(...) must be given view information in the options."); } // If no comm is provided, a new comm is opened for the jupyter.widget @@ -273,7 +274,7 @@ abstract class ManagerBase { ); } // The options dictionary is copied since data will be added to it. - let options_clone = _.clone(options); + let options_clone = {...options}; // Create the model. In the case where the comm promise is rejected a // comm-less model is still created with the required model id. return commPromise.then((comm) => { diff --git a/packages/base/src/services-shim.ts b/packages/base/src/services-shim.ts index f6e87c0e36..75c44d7df1 100644 --- a/packages/base/src/services-shim.ts +++ b/packages/base/src/services-shim.ts @@ -7,7 +7,6 @@ * embed live widgets in a context outside of the notebook. */ -import * as _ from 'underscore'; import * as utils from './utils'; import { Kernel diff --git a/packages/base/src/utils.ts b/packages/base/src/utils.ts index 718fea5798..fcb6a1198e 100644 --- a/packages/base/src/utils.ts +++ b/packages/base/src/utils.ts @@ -1,12 +1,33 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import * as _ from 'underscore'; +export { + isEqual, difference +} from 'lodash'; + +import { + isPlainObject +} from 'lodash'; import { toByteArray, fromByteArray } from 'base64-js'; +/** + * A polyfill for Object.assign + * + * This is from code that Typescript 2.4 generates for a polyfill. + */ +export +let assign = (Object as any).assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; +}; + /** * http://www.ietf.org/rfc/rfc4122.txt */ @@ -103,6 +124,8 @@ function put_buffers(state, buffer_paths: (string | number)[][], buffers: DataVi } } + + /** * The inverse of put_buffers, return an objects with the new state where all buffers(ArrayBuffer) * are removed. If a buffer is a member of an object, that object is cloned, and the key removed. If a buffer @@ -131,7 +154,7 @@ function remove_buffers(state): {state: any, buffers: ArrayBuffer[], buffer_path if(value) { if (value.buffer instanceof ArrayBuffer || value instanceof ArrayBuffer) { if(!is_cloned) { - obj = _.clone(obj); + obj = obj.slice(); is_cloned = true; } buffers.push(value instanceof ArrayBuffer ? value : value.buffer); @@ -144,7 +167,7 @@ function remove_buffers(state): {state: any, buffers: ArrayBuffer[], buffer_path // only assigned when the value changes, we may serialize objects that don't support assignment if(new_value !== value) { if(!is_cloned) { - obj = _.clone(obj); + obj = obj.slice(); is_cloned = true; } obj[i] = new_value; @@ -152,7 +175,7 @@ function remove_buffers(state): {state: any, buffers: ArrayBuffer[], buffer_path } } } - } else if(_.isObject(obj)) { + } else if(isPlainObject(obj)) { for (let key in obj) { let is_cloned = false; if (obj.hasOwnProperty(key)) { @@ -160,19 +183,18 @@ function remove_buffers(state): {state: any, buffers: ArrayBuffer[], buffer_path if(value) { if (value.buffer instanceof ArrayBuffer || value instanceof ArrayBuffer) { if(!is_cloned) { - obj = _.clone(obj); + obj = {...obj}; is_cloned = true; } buffers.push(value instanceof ArrayBuffer ? value : value.buffer); buffer_paths.push(path.concat([key])); delete obj[key]; // for objects/dicts we just delete them - } - else { + } else { let new_value = remove(value, path.concat([key])); // only assigned when the value changes, we may serialize objects that don't support assignment if(new_value !== value) { if(!is_cloned) { - obj = _.clone(obj); + obj = {...obj}; is_cloned = true; } obj[key] = new_value; diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index ae3955596a..d6833706e2 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -2,9 +2,8 @@ // Distributed under the terms of the Modified BSD License. import * as managerBase from './manager-base'; -import * as Backbone from 'backbone'; -import * as _ from 'underscore'; import * as utils from './utils'; +import * as Backbone from 'backbone'; import * as $ from 'jquery'; import { @@ -28,17 +27,17 @@ const JUPYTER_WIDGETS_VERSION = '1.0.0'; export function unpack_models(value, manager): Promise { let unpacked; - if (_.isArray(value)) { + if (Array.isArray(value)) { unpacked = []; - _.each(value, (sub_value, key) => { + value.forEach((sub_value, key) => { unpacked.push(unpack_models(sub_value, manager)); }); return Promise.all(unpacked); } else if (value instanceof Object) { unpacked = {}; - _.each(value, (sub_value, key) => { - unpacked[key] = unpack_models(sub_value, manager); - }); + Object.keys(value).forEach((key) => { + unpacked[key] = unpack_models(value[key], manager); + }) return utils.resolvePromisesDict(unpacked); } else if (typeof value === 'string' && value.slice(0,10) === 'IPY_MODEL_') { // get_model returns a promise already @@ -116,8 +115,8 @@ class WidgetModel extends Backbone.Model { this.comm = comm; // Hook comm messages up to model. - comm.on_close(_.bind(this._handle_comm_closed, this)); - comm.on_msg(_.bind(this._handle_comm_msg, this)); + comm.on_close(this._handle_comm_closed.bind(this)); + comm.on_msg(this._handle_comm_msg.bind(this)); this.comm_live = true; } else { @@ -229,10 +228,12 @@ class WidgetModel extends Backbone.Model { get_state(drop_defaults) { let fullState = this.attributes; if (drop_defaults) { - let defaults = _.result(this, 'defaults'); + // if defaults is a function, call it + let d = this.defaults; + let defaults = (typeof d === "function") ? d.call(this) : d; let state = {}; Object.keys(fullState).forEach(key => { - if (!_.isEqual(fullState[key], defaults[key])) { + if (!(utils.isEqual(fullState[key], defaults[key]))) { state[key] = fullState[key]; } }); @@ -302,7 +303,7 @@ class WidgetModel extends Backbone.Model { } } - this._buffered_state_diff = _.extend(this._buffered_state_diff, attrs); + this._buffered_state_diff = utils.assign(this._buffered_state_diff, attrs); } return return_value; } @@ -365,7 +366,7 @@ class WidgetModel extends Backbone.Model { // Combine updates if it is a 'patch' sync, otherwise replace updates switch (method) { case 'patch': - this._msg_buffer = _.extend(this._msg_buffer || {}, msgState); + this._msg_buffer = utils.assign(this._msg_buffer || {}, msgState); break; case 'update': case 'create': @@ -543,7 +544,7 @@ class DOMWidgetModel extends WidgetModel { } defaults() { - return _.extend(super.defaults(), { + return utils.assign(super.defaults(), { _dom_classes: [] // We do not declare defaults for the layout and style attributes. // Those defaults are constructed on the kernel side and synced here @@ -611,9 +612,9 @@ abstract class WidgetView extends NativeView { /** * Create and promise that resolves to a child view of a given model */ - create_child_view(child_model, options?) { + create_child_view(child_model, options = {}) { let that = this; - options = _.extend({ parent: this }, options || {}); + options = { parent: this, ...options}; return this.model.widget_manager.create_view(child_model, options) .catch(utils.reject('Could not create child view', true)); } @@ -787,14 +788,14 @@ class DOMWidgetView extends WidgetView { if (el===undefined) { el = this.el; } - _.difference(old_classes, new_classes).map(function(c) { + utils.difference(old_classes, new_classes).map(function(c) { if (el.classList) { // classList is not supported by IE for svg elements el.classList.remove(c); } else { el.setAttribute('class', el.getAttribute('class').replace(c, '')); } }); - _.difference(new_classes, old_classes).map(function(c) { + utils.difference(new_classes, old_classes).map(function(c) { if (el.classList) { // classList is not supported by IE for svg elements el.classList.add(c); } else { diff --git a/packages/base/src/widget_layout.ts b/packages/base/src/widget_layout.ts index 53eae5d0fa..e8f2e0a8ca 100644 --- a/packages/base/src/widget_layout.ts +++ b/packages/base/src/widget_layout.ts @@ -2,14 +2,13 @@ // Distributed under the terms of the Modified BSD License. import { - WidgetView, DOMWidgetView -} from './widget'; + assign +} from './utils'; import { - WidgetModel + WidgetModel, WidgetView, DOMWidgetView } from './widget'; -import * as _ from 'underscore'; /** * css properties exposed by the layout widget with their default values. @@ -45,7 +44,7 @@ let css_properties = { export class LayoutModel extends WidgetModel { defaults() { - return _.extend(super.defaults(), { + return assign(super.defaults(), { _model_name: 'LayoutModel', _view_name: 'LayoutView' }, css_properties); diff --git a/packages/base/src/widget_style.ts b/packages/base/src/widget_style.ts index de8b59ff75..dff6e14c11 100644 --- a/packages/base/src/widget_style.ts +++ b/packages/base/src/widget_style.ts @@ -2,23 +2,22 @@ // Distributed under the terms of the Modified BSD License. import { - WidgetView, DOMWidgetView -} from './widget'; + assign +} from './utils'; import { - WidgetModel + WidgetModel, WidgetView, DOMWidgetView } from './widget'; -import * as _ from 'underscore'; export class StyleModel extends WidgetModel { defaults() { let Derived = this.constructor as typeof StyleModel; - return _.extend(super.defaults(), { + return assign(super.defaults(), { _model_name: 'StyleModel', _view_name: 'StyleView', - }, _.reduce(Object.keys(Derived.styleProperties), (obj: any, key: string) => { + }, Object.keys(Derived.styleProperties).reduce((obj: any, key: string) => { obj[key] = Derived.styleProperties[key].default; return obj; }, {})); diff --git a/packages/controls/src/widget_int.ts b/packages/controls/src/widget_int.ts index 91d8fba138..907c4bee0f 100644 --- a/packages/controls/src/widget_int.ts +++ b/packages/controls/src/widget_int.ts @@ -138,7 +138,7 @@ abstract class BaseIntSliderView extends DescriptionView { let that = this; that.$slider.slider({}); - _.each(jquery_slider_keys, function(key, i) { + jquery_slider_keys.forEach(function(key) { let model_value = that.model.get(key); if (model_value !== undefined) { that.$slider.slider('option', key, model_value); diff --git a/packages/controls/src/widget_selection.ts b/packages/controls/src/widget_selection.ts index 5ed150d53a..a9521908f4 100644 --- a/packages/controls/src/widget_selection.ts +++ b/packages/controls/src/widget_selection.ts @@ -259,7 +259,7 @@ class RadioButtonsView extends DescriptionView { */ update(options?) { var view = this; - var items = this.model.get('_options_labels'); + var items: string[] = this.model.get('_options_labels'); var radios = _.pluck( this.container.querySelectorAll('input[type="radio"]'), 'value' @@ -278,7 +278,7 @@ class RadioButtonsView extends DescriptionView { if (stale && (options === undefined || options.updated_view !== this)) { // Add items to the DOM. this.container.textContent = ''; - _.each(items, function(item: any, index: number) { + items.forEach(function(item: any, index: number) { var label = document.createElement('label'); label.textContent = item; view.container.appendChild(label); @@ -290,7 +290,7 @@ class RadioButtonsView extends DescriptionView { label.appendChild(radio); }); } - _.each(items, function(item: any, index: number) { + items.forEach(function(item: any, index: number) { var item_query = 'input[data-value="' + encodeURIComponent(item) + '"]'; var radio = view.container.querySelectorAll(item_query); @@ -420,7 +420,7 @@ class ToggleButtonsView extends DescriptionView { */ update(options?) { var view = this; - var items = this.model.get('_options_labels'); + var items: string[] = this.model.get('_options_labels'); var icons = this.model.get('icons') || []; var previous_icons = this.model.previous('icons') || []; var previous_bstyle = ToggleButtonsView.classMap[this.model.previous('button_style')] || ''; @@ -440,7 +440,7 @@ class ToggleButtonsView extends DescriptionView { if (stale && options === undefined || options.updated_view !== this) { // Add items to the DOM. this.buttongroup.textContent = ''; - _.each(items, (item: any, index: number) => { + items.forEach((item: any, index: number) => { var item_html; var empty = item.trim().length === 0 && (!icons[index] || icons[index].trim().length === 0); @@ -474,7 +474,7 @@ class ToggleButtonsView extends DescriptionView { } // Select active button. - _.each(items, function(item: any, index: number) { + items.forEach(function(item: any, index: number) { var item_query = '[data-value="' + encodeURIComponent(item) + '"]'; var button = view.buttongroup.querySelector(item_query); if (view.model.get('index') === index) { @@ -514,16 +514,16 @@ class ToggleButtonsView extends DescriptionView { update_button_style() { var buttons = this.buttongroup.querySelectorAll('button'); - _.each(buttons, (button) => { - this.update_mapped_classes(ToggleButtonsView.classMap, 'button_style', button); - }); + for (let i = 0; i < buttons.length; i++) { + this.update_mapped_classes(ToggleButtonsView.classMap, 'button_style', buttons[i]); + } } set_button_style() { var buttons = this.buttongroup.querySelectorAll('button'); - _.each(buttons, (button) => { - this.set_mapped_classes(ToggleButtonsView.classMap, 'button_style', button); - }); + for (let i = 0; i < buttons.length; i++) { + this.set_mapped_classes(ToggleButtonsView.classMap, 'button_style', buttons[i]); + } } events(): {[e: string]: string} { From 617a3f3b488a9613e52f40e58a3d7703f8d0005a Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 17 Aug 2017 12:03:40 -0400 Subject: [PATCH 3/6] =?UTF-8?q?Patch=20Backbone=E2=80=99s=20set=20method?= =?UTF-8?q?=20to=20use=20our=20isEqual=20(from=20lodash).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Underscore 1.8.3’s _.eq assumes all DataView and ArrayBuffers are the same, so backbone will not trigger a change event when attributes are set (see https://github.com/jashkenas/underscore/issues/2692). Lodash 4 does distinguish between different dataviews or arraybuffers, so we'll use the Lodash isEqual. --- packages/base/src/backbone-patch.ts | 117 ++++++++++++++++++++++++++++ packages/base/src/widget.ts | 5 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 packages/base/src/backbone-patch.ts diff --git a/packages/base/src/backbone-patch.ts b/packages/base/src/backbone-patch.ts new file mode 100644 index 0000000000..c490c9e50e --- /dev/null +++ b/packages/base/src/backbone-patch.ts @@ -0,0 +1,117 @@ +// This file contains a modified version of the set function from the Backbone +// (see +// https://github.com/jashkenas/backbone/blob/05fde9e201f7e2137796663081105cd6dad12a98/backbone.js#L460, +// with changes below marked with an EDIT comment). This file in Backbone has the following license. + +// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +// Backbone's full license is below (from https://github.com/jashkenas/backbone/blob/05fde9e201f7e2137796663081105cd6dad12a98/LICENSE) + +/* +Copyright (c) 2010-2015 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + + +import * as utils from './utils'; + +// Set a hash of model attributes on the object, firing `"change"`. This is +// the core primitive operation of a model, updating the data and notifying +// anyone who needs to know about the change in state. The heart of the beast. +// This *MUST* be called with the model as the `this` context. +export +function set(key, val, options) { + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + var unset = options.unset; + var silent = options.silent; + var changes = []; + var changing = this._changing; + this._changing = true; + + if (!changing) { + // EDIT: changed to use object spread instead of _.clone + this._previousAttributes = {...this.attributes}; + this.changed = {}; + } + + var current = this.attributes; + var changed = this.changed; + var prev = this._previousAttributes; + + // For each `set` attribute, update or delete the current value. + for (var attr in attrs) { + val = attrs[attr]; + // EDIT: the following two lines use our isEqual instead of _.isEqual + if (!utils.isEqual(current[attr], val)) changes.push(attr); + if (!utils.isEqual(prev[attr], val)) { + changed[attr] = val; + } else { + delete changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Update the `id`. + this.id = this.get(this.idAttribute); + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0; i < changes.length; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; +} \ No newline at end of file diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index d6833706e2..c823dc74c1 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -3,6 +3,8 @@ import * as managerBase from './manager-base'; import * as utils from './utils'; +import * as backbonePatch from './backbone-patch' + import * as Backbone from 'backbone'; import * as $ from 'jquery'; @@ -277,7 +279,8 @@ class WidgetModel extends Backbone.Model { * Handles both "key", value and {key: value} -style arguments. */ set(key: any, val?: any, options?: any) { - let return_value = super.set(key, val, options); + // Call our patched backbone set. See #1642 and #1643. + let return_value = backbonePatch.set.call(this, key, val, options); // Backbone only remembers the diff of the most recent set() // operation. Calling set multiple times in a row results in a From b566bc1a1e8638dc84aefee08512ecae1111ae4f Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 16 Aug 2017 17:43:55 -0400 Subject: [PATCH 4/6] Add changelog notes --- docs/source/changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/changelog.md b/docs/source/changelog.md index 247f981328..59b769fde8 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -44,11 +44,11 @@ If you are developing a custom widget or widget manager, here are some major cha - Widgets will now need correct `_model_module` and `_view_module` Unicode traits defined. - Selection widgets now sync the index of the selected item, rather than the label. ([#1262](https://github.com/jupyter-widgets/ipywidgets/pull/1262)) - The Python `ipywidget.domwidget.LabeledWidget` is now `ipywidget.widget_description.DescriptionWidget`, and there is a new `ipywidget.widget_description.DescriptionStyle` that lets the user set the CSS width of the description. - - Custom serializers can now return a structure that contains binary objects (`memoryview`, `bytearray`, or Python 3 `bytes` object). In this case, the sync message will be a binary message, which is much more efficient for binary data than base64-encoding. ([#1194](https://github.com/jupyter-widgets/ipywidgets/pull/1194), [#1595](https://github.com/jupyter-widgets/ipywidgets/pull/1595)) + - Custom serializers can now return a structure that contains binary objects (`memoryview`, `bytearray`, or Python 3 `bytes` object). In this case, the sync message will be a binary message, which is much more efficient for binary data than base64-encoding. The Image widget now uses this binary synchronization. ([#1194](https://github.com/jupyter-widgets/ipywidgets/pull/1194), [#1595](https://github.com/jupyter-widgets/ipywidgets/pull/1595), [#1643](https://github.com/jupyter-widgets/ipywidgets/pull/1643)) - On the Javascript side: - The `jupyter-js-widgets` Javascript package has been split into `@jupyter-widgets/base` package (containing base widget classes, the DOM widget, and the associated layout and style classes), and the `@jupyter-widgets/controls` package (containing the rest of the Jupyter widgets controls). Authors of custom widgets will need to update to depend on `@jupyter-widgets/base` instead of `jupyter-js-widgets` (if you use a class from the controls package, you will also need to depend on `@jupyter-widgets/controls`). See the [cookie cutter](https://github.com/jupyter-widgets/widget-cookiecutter) to generate a simple example custom widget using the new packages. - Custom serializers in Javascript are now synchronous, and should return a snapshot of the widget state. The default serializer makes a copy of JSONable objects. ([#1270](https://github.com/jupyter-widgets/ipywidgets/pull/1270)) - - Custom serializers can now return a structure that contains binary objects (`ArrayBuffer`, `DataView`, or a typed array such as `Int8Array`, `Float64Array`, etc.). In this case, the sync message will be a binary message, which is much more efficient for binary data than base64-encoding. ([#1194](https://github.com/jupyter-widgets/ipywidgets/pull/1194)) + - Custom serializers can now return a structure that contains binary objects (`ArrayBuffer`, `DataView`, or a typed array such as `Int8Array`, `Float64Array`, etc.). In this case, the sync message will be a binary message, which is much more efficient for binary data than base64-encoding. The Image widget now uses this binary synchronization. ([#1194](https://github.com/jupyter-widgets/ipywidgets/pull/1194), [#1643](https://github.com/jupyter-widgets/ipywidgets/pull/1643)) - A custom serializer is given the widget instance as its second argument, and a custom deserializer is given the widget manager as its second argument. - The Javascript model `.id` attribute has been renamed to `.model_id` to avoid conflicting with the Backbone `.id` attribute. ([#1410](https://github.com/jupyter-widgets/ipywidgets/pull/1410)) - Regarding widget managers and the syncing message protocol: From b2876e27201b21e9ddc3fbc2b7361e9e494a0332 Mon Sep 17 00:00:00 2001 From: maartenbreddels Date: Thu, 17 Aug 2017 19:02:15 +0200 Subject: [PATCH 5/6] put buffers in place after a request_state --- widgetsnbextension/src/manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index f9b916156e..a7f8d7aea5 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -49,6 +49,7 @@ var WidgetManager = function (comm_manager, notebook) { return Promise.all(comms.map(function(comm) { var update_promise = new Promise(function(resolve, reject) { comm.on_msg(function (msg) { + base.put_buffers(msg.content.data.state, msg.content.data.buffer_paths, msg.buffers); // A suspected response was received, check to see if // it's a state update. If so, resolve. if (msg.content.data.method === 'update') { From 54fdce3db10952831a83fe385b89fe3a56d49634 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 17 Aug 2017 13:26:53 -0400 Subject: [PATCH 6/6] Increase browserNoActivityTimeout for testing for travis failures. --- packages/base/test/karma.conf.js | 3 ++- packages/controls/test/karma.conf.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/base/test/karma.conf.js b/packages/base/test/karma.conf.js index f814d1f31d..4098dfbdd8 100644 --- a/packages/base/test/karma.conf.js +++ b/packages/base/test/karma.conf.js @@ -7,6 +7,7 @@ module.exports = function (config) { port: 9876, colors: true, singleRun: true, - logLevel: config.LOG_INFO + logLevel: config.LOG_INFO, + browserNoActivityTimeout: 30000 }); }; diff --git a/packages/controls/test/karma.conf.js b/packages/controls/test/karma.conf.js index f814d1f31d..4098dfbdd8 100644 --- a/packages/controls/test/karma.conf.js +++ b/packages/controls/test/karma.conf.js @@ -7,6 +7,7 @@ module.exports = function (config) { port: 9876, colors: true, singleRun: true, - logLevel: config.LOG_INFO + logLevel: config.LOG_INFO, + browserNoActivityTimeout: 30000 }); };