From cc15bb0a64bbad8ddf45eb9ad9cd1155117c5e38 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Mon, 6 Jan 2020 17:27:18 +0100 Subject: [PATCH 1/6] Fix Upload widget counter value --- ipywidgets/widgets/widget_upload.py | 3 --- packages/controls/src/widget_upload.ts | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ipywidgets/widgets/widget_upload.py b/ipywidgets/widgets/widget_upload.py index 67b0f84ed0..0349d4d84f 100644 --- a/ipywidgets/widgets/widget_upload.py +++ b/ipywidgets/widgets/widget_upload.py @@ -52,9 +52,6 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): @observe('_counter') def on_incr_counter(self, change): - """ - counter increment triggers the update of trait value - """ res = {} msg = 'Error: length of metadata and data must be equal' assert len(self.metadata) == len(self.data), msg diff --git a/packages/controls/src/widget_upload.ts b/packages/controls/src/widget_upload.ts index bdfba88b21..d082acbf25 100644 --- a/packages/controls/src/widget_upload.ts +++ b/packages/controls/src/widget_upload.ts @@ -13,6 +13,7 @@ export class FileUploadModel extends CoreDOMWidgetModel { _view_name: 'FileUploadView', _counter: 0, + _file_count: 0, accept: '', description: 'Upload', disabled: false, @@ -104,6 +105,7 @@ export class FileUploadView extends DOMWidgetView { const counter = this.model.get('_counter'); this.model.set({ _counter: counter + contents.length, + _file_count: contents.length, metadata, data: li_buffer, error: '', @@ -128,8 +130,9 @@ export class FileUploadView extends DOMWidgetView { this.el.disabled = this.model.get('disabled'); this.el.setAttribute('title', this.model.get('tooltip')); - const description = `${this.model.get('description')} (${this.model.get('_counter')})` + const description = `${this.model.get('description')} (${this.model.get('_file_count')})`; const icon = this.model.get('icon'); + if (description.length || icon.length) { this.el.textContent = ''; if (icon.length) { From 73427ba81263ea4a2e8d021526c0977fa6e4b4a1 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Tue, 7 Jan 2020 18:06:55 +0100 Subject: [PATCH 2/6] Make FileUpload _counter, data, metadata read-only --- ipywidgets/widgets/widget_upload.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipywidgets/widgets/widget_upload.py b/ipywidgets/widgets/widget_upload.py index 0349d4d84f..ba71044d8e 100644 --- a/ipywidgets/widgets/widget_upload.py +++ b/ipywidgets/widgets/widget_upload.py @@ -33,7 +33,7 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): """ _model_name = Unicode('FileUploadModel').tag(sync=True) _view_name = Unicode('FileUploadView').tag(sync=True) - _counter = Int().tag(sync=True) + _counter = Int(read_only=True).tag(sync=True) accept = Unicode(help='File types to accept, empty string for all').tag(sync=True) multiple = Bool(help='If True, allow for multiple files upload').tag(sync=True) @@ -43,8 +43,8 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): values=['primary', 'success', 'info', 'warning', 'danger', ''], default_value='', help="""Use a predefined styling for the button.""").tag(sync=True) style = InstanceDict(ButtonStyle).tag(sync=True, **widget_serialization) - metadata = List(Dict(), help='List of file metadata').tag(sync=True) - data = List(Bytes(), help='List of file content (bytes)').tag( + metadata = List(Dict(), read_only=True, help='List of file metadata').tag(sync=True) + data = List(Bytes(), read_only=True, help='List of file content (bytes)').tag( sync=True, from_json=content_from_json ) error = Unicode(help='Error message').tag(sync=True) From 5ed9dc59aa696fb82f89b833c21312486e8fdedb Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Tue, 7 Jan 2020 18:22:05 +0100 Subject: [PATCH 3/6] Make FileUpload data and metadata private --- ipywidgets/widgets/widget_upload.py | 12 ++++++------ packages/controls/src/widget_upload.ts | 10 +++++----- packages/schema/jupyterwidgetmodels.latest.md | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ipywidgets/widgets/widget_upload.py b/ipywidgets/widgets/widget_upload.py index ba71044d8e..3ec06f5ea5 100644 --- a/ipywidgets/widgets/widget_upload.py +++ b/ipywidgets/widgets/widget_upload.py @@ -34,6 +34,10 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): _model_name = Unicode('FileUploadModel').tag(sync=True) _view_name = Unicode('FileUploadView').tag(sync=True) _counter = Int(read_only=True).tag(sync=True) + _metadata = List(Dict(), read_only=True, help='List of file metadata').tag(sync=True) + _data = List(Bytes(), read_only=True, help='List of file content (bytes)').tag( + sync=True, from_json=content_from_json + ) accept = Unicode(help='File types to accept, empty string for all').tag(sync=True) multiple = Bool(help='If True, allow for multiple files upload').tag(sync=True) @@ -43,10 +47,6 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): values=['primary', 'success', 'info', 'warning', 'danger', ''], default_value='', help="""Use a predefined styling for the button.""").tag(sync=True) style = InstanceDict(ButtonStyle).tag(sync=True, **widget_serialization) - metadata = List(Dict(), read_only=True, help='List of file metadata').tag(sync=True) - data = List(Bytes(), read_only=True, help='List of file content (bytes)').tag( - sync=True, from_json=content_from_json - ) error = Unicode(help='Error message').tag(sync=True) value = Dict(read_only=True) @@ -54,8 +54,8 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): def on_incr_counter(self, change): res = {} msg = 'Error: length of metadata and data must be equal' - assert len(self.metadata) == len(self.data), msg - for metadata, content in zip(self.metadata, self.data): + assert len(self._metadata) == len(self._data), msg + for metadata, content in zip(self._metadata, self._data): name = metadata['name'] res[name] = {'metadata': metadata, 'content': content} self.set_trait('value', res) diff --git a/packages/controls/src/widget_upload.ts b/packages/controls/src/widget_upload.ts index d082acbf25..54a38e1862 100644 --- a/packages/controls/src/widget_upload.ts +++ b/packages/controls/src/widget_upload.ts @@ -14,14 +14,14 @@ export class FileUploadModel extends CoreDOMWidgetModel { _counter: 0, _file_count: 0, + _data: [], + _metadata: [], accept: '', description: 'Upload', disabled: false, icon: 'upload', button_style: '', multiple: false, - metadata: [], - data: [], error: '', style: null }); @@ -29,7 +29,7 @@ export class FileUploadModel extends CoreDOMWidgetModel { static serializers = { ...CoreDOMWidgetModel.serializers, - data: { serialize: (buffers: any): any[] => { return [...buffers]; } }, + _data: { serialize: (buffers: any) => { return [...buffers]; } }, }; } @@ -106,8 +106,8 @@ export class FileUploadView extends DOMWidgetView { this.model.set({ _counter: counter + contents.length, _file_count: contents.length, - metadata, - data: li_buffer, + _metadata: metadata, + _data: li_buffer, error: '', }); this.touch(); diff --git a/packages/schema/jupyterwidgetmodels.latest.md b/packages/schema/jupyterwidgetmodels.latest.md index f7a210bafb..4c8c483723 100644 --- a/packages/schema/jupyterwidgetmodels.latest.md +++ b/packages/schema/jupyterwidgetmodels.latest.md @@ -394,7 +394,9 @@ Attribute | Type | Default | Help Attribute | Type | Default | Help -----------------|------------------|------------------|---- `_counter` | number (integer) | `0` | +`_data` | array | `[]` | List of file content (bytes) `_dom_classes` | array of string | `[]` | CSS classes applied to widget DOM element +`_metadata` | array | `[]` | List of file metadata `_model_module` | string | `'@jupyter-widgets/controls'` | `_model_module_version` | string | `'1.5.0'` | `_model_name` | string | `'FileUploadModel'` | @@ -403,13 +405,11 @@ Attribute | Type | Default | Help `_view_name` | string | `'FileUploadView'` | `accept` | string | `''` | File types to accept, empty string for all `button_style` | string (one of `'primary'`, `'success'`, `'info'`, `'warning'`, `'danger'`, `''`) | `''` | Use a predefined styling for the button. -`data` | array | `[]` | List of file content (bytes) `description` | string | `''` | Description of the control. `disabled` | boolean | `false` | Enable or disable button `error` | string | `''` | Error message `icon` | string | `'upload'` | Font-awesome icon name, without the 'fa-' prefix. `layout` | reference to Layout widget | reference to new instance | -`metadata` | array | `[]` | List of file metadata `multiple` | boolean | `false` | If True, allow for multiple files upload `style` | reference to ButtonStyle widget | reference to new instance | `tabbable` | `null` or boolean | `null` | Is widget tabbable? From c51018007cd487970b61a304f1ceadb0a3fa63d7 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 8 Jan 2020 14:06:41 +0100 Subject: [PATCH 4/6] Compute FileUpload value on the frontend --- ipywidgets/widgets/widget_upload.py | 27 ++----------------- packages/controls/src/widget_upload.ts | 26 +++++++----------- packages/schema/jupyterwidgetmodels.latest.md | 4 +-- 3 files changed, 13 insertions(+), 44 deletions(-) diff --git a/ipywidgets/widgets/widget_upload.py b/ipywidgets/widgets/widget_upload.py index 3ec06f5ea5..965c16a4f2 100644 --- a/ipywidgets/widgets/widget_upload.py +++ b/ipywidgets/widgets/widget_upload.py @@ -15,15 +15,7 @@ from .widget_core import CoreWidget from .widget_button import ButtonStyle from .widget import register, widget_serialization -from .trait_types import bytes_serialization, InstanceDict - -def content_from_json(value, widget): - """ - deserialize file content - """ - from_json = bytes_serialization['from_json'] - output = [from_json(e, None) for e in value] - return output +from .trait_types import InstanceDict @register @@ -33,11 +25,6 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): """ _model_name = Unicode('FileUploadModel').tag(sync=True) _view_name = Unicode('FileUploadView').tag(sync=True) - _counter = Int(read_only=True).tag(sync=True) - _metadata = List(Dict(), read_only=True, help='List of file metadata').tag(sync=True) - _data = List(Bytes(), read_only=True, help='List of file content (bytes)').tag( - sync=True, from_json=content_from_json - ) accept = Unicode(help='File types to accept, empty string for all').tag(sync=True) multiple = Bool(help='If True, allow for multiple files upload').tag(sync=True) @@ -48,17 +35,7 @@ class FileUpload(DescriptionWidget, ValueWidget, CoreWidget): help="""Use a predefined styling for the button.""").tag(sync=True) style = InstanceDict(ButtonStyle).tag(sync=True, **widget_serialization) error = Unicode(help='Error message').tag(sync=True) - value = Dict(read_only=True) - - @observe('_counter') - def on_incr_counter(self, change): - res = {} - msg = 'Error: length of metadata and data must be equal' - assert len(self._metadata) == len(self._data), msg - for metadata, content in zip(self._metadata, self._data): - name = metadata['name'] - res[name] = {'metadata': metadata, 'content': content} - self.set_trait('value', res) + value = List(Dict(), help="The file upload value").tag(sync=True) @default('description') def _default_description(self): diff --git a/packages/controls/src/widget_upload.ts b/packages/controls/src/widget_upload.ts index 54a38e1862..0784b9403b 100644 --- a/packages/controls/src/widget_upload.ts +++ b/packages/controls/src/widget_upload.ts @@ -12,16 +12,13 @@ export class FileUploadModel extends CoreDOMWidgetModel { _model_name: 'FileUploadModel', _view_name: 'FileUploadView', - _counter: 0, - _file_count: 0, - _data: [], - _metadata: [], accept: '', description: 'Upload', disabled: false, icon: 'upload', button_style: '', multiple: false, + value: [], error: '', style: null }); @@ -29,7 +26,7 @@ export class FileUploadModel extends CoreDOMWidgetModel { static serializers = { ...CoreDOMWidgetModel.serializers, - _data: { serialize: (buffers: any) => { return [...buffers]; } }, + value: { serialize: (x: any) => x }, }; } @@ -96,18 +93,14 @@ export class FileUploadView extends DOMWidgetView { Promise.all(promisesFile) .then(contents => { - const metadata: any[] = []; - const li_buffer: any[] = []; - contents.forEach(c => { - metadata.push(c.metadata); - li_buffer.push(c.buffer); + const value = contents.map(c => { + return { + metadata: c.metadata, + content: c.buffer + }; }); - const counter = this.model.get('_counter'); this.model.set({ - _counter: counter + contents.length, - _file_count: contents.length, - _metadata: metadata, - _data: li_buffer, + value, error: '', }); this.touch(); @@ -130,7 +123,8 @@ export class FileUploadView extends DOMWidgetView { this.el.disabled = this.model.get('disabled'); this.el.setAttribute('title', this.model.get('tooltip')); - const description = `${this.model.get('description')} (${this.model.get('_file_count')})`; + const value: [] = this.model.get('value'); + const description = `${this.model.get('description')} (${value.length})`; const icon = this.model.get('icon'); if (description.length || icon.length) { diff --git a/packages/schema/jupyterwidgetmodels.latest.md b/packages/schema/jupyterwidgetmodels.latest.md index 4c8c483723..df25ff0080 100644 --- a/packages/schema/jupyterwidgetmodels.latest.md +++ b/packages/schema/jupyterwidgetmodels.latest.md @@ -393,10 +393,7 @@ Attribute | Type | Default | Help Attribute | Type | Default | Help -----------------|------------------|------------------|---- -`_counter` | number (integer) | `0` | -`_data` | array | `[]` | List of file content (bytes) `_dom_classes` | array of string | `[]` | CSS classes applied to widget DOM element -`_metadata` | array | `[]` | List of file metadata `_model_module` | string | `'@jupyter-widgets/controls'` | `_model_module_version` | string | `'1.5.0'` | `_model_name` | string | `'FileUploadModel'` | @@ -414,6 +411,7 @@ Attribute | Type | Default | Help `style` | reference to ButtonStyle widget | reference to new instance | `tabbable` | `null` or boolean | `null` | Is widget tabbable? `tooltip` | `null` or string | `null` | A tooltip caption. +`value` | array | `[]` | The file upload value ### FloatLogSliderModel (@jupyter-widgets/controls, 1.5.0); FloatLogSliderView (@jupyter-widgets/controls, 1.5.0) From 656e2ed16aadd30b1d09a2ad25b54d91ee1ccb1e Mon Sep 17 00:00:00 2001 From: Pascal Bugnion Date: Wed, 8 Jan 2020 17:05:29 +0000 Subject: [PATCH 5/6] Add comment explaining use of dummy serializer --- packages/controls/src/widget_upload.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/controls/src/widget_upload.ts b/packages/controls/src/widget_upload.ts index 0784b9403b..51db5fa75d 100644 --- a/packages/controls/src/widget_upload.ts +++ b/packages/controls/src/widget_upload.ts @@ -26,6 +26,7 @@ export class FileUploadModel extends CoreDOMWidgetModel { static serializers = { ...CoreDOMWidgetModel.serializers, + // use a dummy serializer for value to circumvent the default serializer. value: { serialize: (x: any) => x }, }; } From 308564ba1e5ff3d3324c0c9a511a8171ce4e02aa Mon Sep 17 00:00:00 2001 From: Pascal Bugnion Date: Wed, 8 Jan 2020 17:17:11 +0000 Subject: [PATCH 6/6] Tighten type on identity function --- packages/controls/src/widget_upload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/controls/src/widget_upload.ts b/packages/controls/src/widget_upload.ts index 51db5fa75d..1d37ecba37 100644 --- a/packages/controls/src/widget_upload.ts +++ b/packages/controls/src/widget_upload.ts @@ -27,7 +27,7 @@ export class FileUploadModel extends CoreDOMWidgetModel { static serializers = { ...CoreDOMWidgetModel.serializers, // use a dummy serializer for value to circumvent the default serializer. - value: { serialize: (x: any) => x }, + value: { serialize: (x: T): T => x }, }; }