-
Notifications
You must be signed in to change notification settings - Fork 950
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves: issues#1542
- Loading branch information
Showing
6 changed files
with
260 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright (c) Jupyter Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||
|
||
from unittest import TestCase | ||
|
||
from traitlets import TraitError | ||
|
||
from ipywidgets import FileUpload | ||
|
||
|
||
class TestFileUpload(TestCase): | ||
|
||
def test_construction(self): | ||
FileUpload() | ||
# Defaults | ||
assert uploader.accept == '' | ||
assert uploader.multiple == False | ||
|
||
|
||
def test_construction_accept(self): | ||
uploader = FileUpload(accept='.txt) | ||
assert uploader.accept == '.txt' | ||
|
||
def test_construction_multiple(self): | ||
uploader = FileUpload(accept='.txt) | ||
assert uploader.multiple == True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Copyright (c) Jupyter Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||
|
||
from .widget_core import CoreWidget | ||
from .domwidget import DOMWidget | ||
from .widget import register | ||
from traitlets import Unicode, Bool, List, observe | ||
import base64 | ||
import copy | ||
from datetime import datetime | ||
|
||
@register() | ||
class FileUpload(DOMWidget, CoreWidget): | ||
"""Upload files from a browser to python | ||
Parameters | ||
---------- | ||
values : List | ||
List of file objects | ||
accept : string | ||
Limit accepted file types. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept | ||
multiple : bool | ||
Allow for multiple files to be uploaded | ||
""" | ||
_view_name = Unicode('UploadView').tag(sync=True) | ||
_model_name = Unicode('UploadModel').tag(sync=True) | ||
|
||
disabled = Bool(False, help="Enable or disable user changes.").tag(sync=True) | ||
values = List().tag(sync=True) | ||
_values_base64 = List(help='File content, base64 encoded.').tag(trait=Unicode()).tag(sync=True) | ||
accept = Unicode(help='Type of files the input accepts. None for all. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept').tag(sync=True) | ||
multiple = Bool(help='If true, allow for multiple input files. Else only accept one').tag(sync=True) | ||
|
||
|
||
def __init__(self, multiple=False, accept='', **kwargs): | ||
self.accept = accept | ||
self.multiple = multiple | ||
super(FileUpload, self).__init__(**kwargs) | ||
|
||
|
||
@observe('_values_base64') | ||
def _on_files_changed(self, *args): | ||
""" | ||
Parse data from JS and put into python friendly form | ||
""" | ||
values = [] | ||
for v in self._values_base64: | ||
f = copy.copy(v) | ||
if 'contents' in f: | ||
f['contents'] = base64.b64decode(f['contents'].split(',', 1)[1]) | ||
if 'lastModified' in f: | ||
f['lastModified'] = datetime.fromtimestamp(f['lastModified']/1000.0) | ||
values.append(f) | ||
self.values = values |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
|
||
import { | ||
DOMWidgetView | ||
} from '@jupyter-widgets/base'; | ||
|
||
import { | ||
CoreDOMWidgetModel | ||
} from './widget_core'; | ||
|
||
import * as _ from 'underscore'; | ||
|
||
export | ||
class UploadModel extends CoreDOMWidgetModel { | ||
defaults() { | ||
return _.extend(super.defaults(), { | ||
_model_name: 'UploadModel', | ||
_view_name: 'UploadView', | ||
values: [], | ||
_values_base64: [], | ||
accept: '', | ||
multiple: false | ||
}); | ||
} | ||
} | ||
|
||
export | ||
class UploadView extends DOMWidgetView { | ||
render() { | ||
/** | ||
* Called when view is rendered. | ||
*/ | ||
super.render(); | ||
this.pWidget.addClass('jupyter-widgets'); | ||
this.el.classList.add('widget-inline-hbox'); | ||
this.pWidget.addClass('widget-upload'); | ||
|
||
this.upload_container = document.createElement('input'); | ||
this.upload_container.setAttribute('type', 'file'); | ||
this.handleUploadChanged = this.handleUploadChanged.bind(this); | ||
this.upload_container.onchange = this.handleUploadChanged; | ||
|
||
this.el.appendChild(this.upload_container); | ||
|
||
|
||
this.listenTo(this.model, 'change:values', this._update_values); | ||
this._update_values(); | ||
this.update(); // Set defaults. | ||
} | ||
|
||
_update_values() { | ||
// Only allow for value to clear. This is a rule enforced by browsers | ||
const values = this.model.get('values'); | ||
if (values.length === 0) { | ||
this.upload_container.value = ''; | ||
} | ||
} | ||
|
||
update(options?) { | ||
/** | ||
* Update the contents of this view | ||
* | ||
* Called when the model is changed. The model may have been | ||
* changed by another view or by a state update from the back-end. | ||
*/ | ||
if (options === undefined || options.updated_view !== this) { | ||
this.upload_container.disabled = this.model.get('disabled'); | ||
this.upload_container.setAttribute('accept', this.model.get('accept')); | ||
if (this.model.get('multiple')) { | ||
this.upload_container.setAttribute('multiple', 'true'); | ||
} else { | ||
this.upload_container.removeAttribute('multiple'); | ||
} | ||
} | ||
return super.update(options); | ||
} | ||
|
||
handleUploadChanged(event) { | ||
const that = this; | ||
const {files} = event.target; | ||
if (!files || files.length === 0) { | ||
that.model.set('_values_base64', []); | ||
that.touch(); | ||
return; | ||
} | ||
|
||
const fileContentsPromises = []; | ||
for (let file of files) { // files it NOT an array | ||
fileContentsPromises.push(new Promise((resolve, reject) => { | ||
const fileReader = new FileReader(); | ||
fileReader.onload = function fileReaderOnload() { | ||
resolve({ | ||
name: file.name, | ||
contents: fileReader.result, | ||
type: file.type, | ||
lastModified: file.lastModified | ||
}); | ||
}; | ||
fileReader.readAsDataURL(file); | ||
})); | ||
} | ||
Promise.all(fileContentsPromises) | ||
.then((contents) => { | ||
that.model.set('_values_base64', contents); | ||
that.touch(); | ||
}) | ||
.catch((err) => { | ||
console.error('FileUploadView Error loading files: %o', err); | ||
// FIXME: Set state? | ||
}); | ||
// FIXME: Add an icon that shows it loading? | ||
} | ||
|
||
upload_container: HTMLInputElement; | ||
model: UploadModel; | ||
} |
Oops, something went wrong.