From 2960be0d8616eb9b7a6a63c6d3285b9cb2b01082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 31 Aug 2017 11:23:31 +0200 Subject: [PATCH 01/25] xhrupload: Use error messages from the endpoint, closes #303 --- src/core/Core.js | 6 +++++- src/plugins/XHRUpload.js | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/Core.js b/src/core/Core.js index 60e2682d4..4e3b121ce 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -444,7 +444,11 @@ class Uppy { this.on('core:upload-error', (fileID, error) => { const fileName = this.state.files[fileID].name - this.info(`Failed to upload: ${fileName}`, 'error', 5000) + let message = `Failed to upload ${fileName}` + if (typeof error === 'object' && error.message) { + message = `${message}: ${error.message}` + } + this.info(message, 'error', 5000) }) this.on('core:upload', () => { diff --git a/src/plugins/XHRUpload.js b/src/plugins/XHRUpload.js index 3da48dd0d..9f706bc91 100644 --- a/src/plugins/XHRUpload.js +++ b/src/plugins/XHRUpload.js @@ -20,6 +20,9 @@ module.exports = class XHRUpload extends Plugin { headers: {}, getResponseData (xhr) { return JSON.parse(xhr.response) + }, + getResponseError (xhr) { + return new Error('Upload error') } } @@ -88,8 +91,10 @@ module.exports = class XHRUpload extends Plugin { return resolve(file) } else { - this.core.emit('core:upload-error', file.id, xhr) - return reject('Upload error') + const error = opts.getResponseError(xhr) || new Error('Upload error') + error.request = xhr + this.core.emit('core:upload-error', file.id, error) + return reject(error) } // var upload = {} @@ -103,7 +108,7 @@ module.exports = class XHRUpload extends Plugin { xhr.addEventListener('error', (ev) => { this.core.emit('core:upload-error', file.id) - return reject('Upload error') + return reject(new Error('Upload error')) }) xhr.open(opts.method.toUpperCase(), opts.endpoint, true) From 515902d8369b23c8c9e4b4695277ae9c02079d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 31 Aug 2017 11:24:01 +0200 Subject: [PATCH 02/25] s3: Use AWS-returned error message --- src/plugins/AwsS3/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/AwsS3/index.js b/src/plugins/AwsS3/index.js index f396daa23..998dde642 100644 --- a/src/plugins/AwsS3/index.js +++ b/src/plugins/AwsS3/index.js @@ -66,6 +66,14 @@ module.exports = class AwsS3 extends Plugin { key: getValue('Key'), etag: getValue('ETag') } + }, + getResponseError (xhr) { + // If no response, we don't have a specific error message, use the default. + if (!xhr.responseXML) { + return + } + const error = xhr.responseXML.querySelector('Error > Message') + return new Error(error.textContent) } }) }) From c28c0c55511abd72584877746946664f999ba809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 31 Aug 2017 11:38:35 +0200 Subject: [PATCH 03/25] dashboard: Prevent submitting outer form while editing metadata Fixes #286 Using a `data-` attribute instead of the `name` attribute. Inputs with `name` attributes submit the form they belong to when `enter` is pressed, but inputs without do not: https://stackoverflow.com/questions/3008035/stop-an-input-field-in-a-form-from-being-submitted --- src/plugins/Dashboard/FileCard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index 5e7fd3775..4ff7daf29 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -8,7 +8,7 @@ module.exports = function fileCard (props) { function tempStoreMeta (ev) { const value = ev.target.value - const name = ev.target.attributes.name.value + const name = ev.target.dataset.name meta[name] = value } @@ -18,8 +18,8 @@ module.exports = function fileCard (props) { return html`
` From c05ea5bbd8d616e2036abc970fce8d7b25392341 Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Thu, 31 Aug 2017 21:50:31 +0300 Subject: [PATCH 04/25] when FileCard loads, set up an event listener for enter key and call props.done --- src/plugins/Dashboard/FileCard.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index 4ff7daf29..20515f6ac 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -1,12 +1,22 @@ const html = require('yo-yo') const getFileTypeIcon = require('./getFileTypeIcon') +const onload = require('on-load') const { checkIcon } = require('./icons') -module.exports = function fileCard (props) { - const file = props.fileCardFor ? props.files[props.fileCardFor] : false - const meta = {} +let file +const meta = {} - function tempStoreMeta (ev) { +module.exports = function fileCard (props) { + file = props.fileCardFor ? props.files[props.fileCardFor] : false + // const meta = {} + + const handleEnterKey = (ev) => { + if (event.keyCode === 13) { + props.done(meta, file.id) + } + } + + const tempStoreMeta = (ev) => { const value = ev.target.value const name = ev.target.dataset.name meta[name] = value @@ -26,7 +36,7 @@ module.exports = function fileCard (props) { }) } - return html`
+ const fileCardEl = html`

Editing ${file.meta ? file.meta.name : file.name}

` + + return onload(html`
${fileCardEl}
`, + () => document.body.addEventListener('keyup', handleEnterKey), + () => document.body.removeEventListener('keyup', handleEnterKey) + ) } From cb62a1e623bbf9b28059e22cacd3f731d647e433 Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Thu, 31 Aug 2017 21:52:08 +0300 Subject: [PATCH 05/25] minor refactoring --- src/core/Core.js | 1 + src/plugins/Dashboard/index.js | 16 ++++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/core/Core.js b/src/core/Core.js index 60e2682d4..4bc3e1f21 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -72,6 +72,7 @@ class Uppy { this.initSocket = this.initSocket.bind(this) this.log = this.log.bind(this) this.addFile = this.addFile.bind(this) + this.removeFile = this.removeFile.bind(this) this.calculateProgress = this.calculateProgress.bind(this) this.resetProgress = this.resetProgress.bind(this) diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 086918eb6..6dbb5c1a8 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -249,7 +249,7 @@ module.exports = class DashboardUI extends Plugin { } handleFileCard (fileId) { - const modal = this.core.getState().modal + const modal = this.core.state.modal this.core.setState({ modal: Object.assign({}, modal, { @@ -318,18 +318,10 @@ module.exports = class DashboardUI extends Plugin { return target.type === 'progressindicator' }) - // const addFile = (file) => { - // this.core.emitter.emit('core:file-add', file) - // } - - const removeFile = (fileID) => { - this.core.emitter.emit('core:file-remove', fileID) - } - const startUpload = (ev) => { this.core.upload().catch((err) => { // Log error. - console.error(err.stack || err.message || err) + this.core.log(err.stack || err.message || err) }) } @@ -355,7 +347,7 @@ module.exports = class DashboardUI extends Plugin { this.core.info(text, type, duration) } - const resumableUploads = this.core.getState().capabilities.resumableUploads || false + const resumableUploads = this.core.state.capabilities.resumableUploads || false return Dashboard({ state: state, @@ -382,7 +374,7 @@ module.exports = class DashboardUI extends Plugin { pauseAll: this.pauseAll, resumeAll: this.resumeAll, addFile: this.core.addFile, - removeFile: removeFile, + removeFile: this.core.removeFile, info: info, note: this.opts.note, metaFields: state.metaFields, From 3b7dbc39e38e6f03c5a3ac53cc867f9894c0ce2d Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Fri, 1 Sep 2017 01:47:20 +0300 Subject: [PATCH 06/25] pass unique ID to onload; data-name="name" for name input --- src/plugins/Dashboard/FileCard.js | 11 ++++++----- src/plugins/Dashboard/index.js | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index 20515f6ac..305779668 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -36,7 +36,7 @@ module.exports = function fileCard (props) { }) } - const fileCardEl = html`
+ const fileCardEl = () => html`

Editing ${file.meta ? file.meta.name : file.name}

-
` +
` - return onload(html`
${fileCardEl}
`, + return onload(html`
${fileCardEl()}
`, () => document.body.addEventListener('keyup', handleEnterKey), - () => document.body.removeEventListener('keyup', handleEnterKey) + () => document.body.removeEventListener('keyup', handleEnterKey), + `uppy${file.id}` ) } diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 6dbb5c1a8..30c101311 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -326,21 +326,22 @@ module.exports = class DashboardUI extends Plugin { } const pauseUpload = (fileID) => { - this.core.emitter.emit('core:upload-pause', fileID) + this.core.emit.emit('core:upload-pause', fileID) } const cancelUpload = (fileID) => { - this.core.emitter.emit('core:upload-cancel', fileID) - this.core.emitter.emit('core:file-remove', fileID) + this.core.emit('core:upload-cancel', fileID) + this.core.emit('core:file-remove', fileID) } const showFileCard = (fileID) => { - this.core.emitter.emit('dashboard:file-card', fileID) + this.core.emit('dashboard:file-card', fileID) } const fileCardDone = (meta, fileID) => { - this.core.emitter.emit('core:update-meta', meta, fileID) - this.core.emitter.emit('dashboard:file-card') + console.log(meta) + this.core.emit('core:update-meta', meta, fileID) + this.core.emit('dashboard:file-card') } const info = (text, type, duration) => { From 2f0d1b85812d406ee0773ea8670c89e16e9eaa51 Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Fri, 1 Sep 2017 02:06:23 +0300 Subject: [PATCH 07/25] =?UTF-8?q?don=E2=80=99t=20use=20onload,=20because?= =?UTF-8?q?=20fileCard=20is=20actually=20always=20in=20DOM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @goto-bus-stop could you help a bit here? I’m confused as to how to remove an event listener when FileCard is closed, given that data needed for that event listener lives in closure. Now it seems to me that every time FileCard is open, a new listener is added --- src/plugins/Dashboard/FileCard.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index 305779668..a9bea5e8d 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -8,14 +8,19 @@ const meta = {} module.exports = function fileCard (props) { file = props.fileCardFor ? props.files[props.fileCardFor] : false - // const meta = {} const handleEnterKey = (ev) => { - if (event.keyCode === 13) { + if (file && event.keyCode === 13) { props.done(meta, file.id) } } + if (file) { + document.body.addEventListener('keyup', handleEnterKey) + } else { + document.body.removeEventListener('keyup', handleEnterKey) + } + const tempStoreMeta = (ev) => { const value = ev.target.value const name = ev.target.dataset.name @@ -73,8 +78,8 @@ module.exports = function fileCard (props) {
` return onload(html`
${fileCardEl()}
`, - () => document.body.addEventListener('keyup', handleEnterKey), - () => document.body.removeEventListener('keyup', handleEnterKey), + null, + null, `uppy${file.id}` ) } From 6731b82905bcfbc5c3d0a55ea8d68ff45e99bdef Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Fri, 1 Sep 2017 12:12:36 +0300 Subject: [PATCH 08/25] add event listeners to input elements instead --- src/plugins/Dashboard/FileCard.js | 35 ++++++++++++++++++------------- src/plugins/Dashboard/index.js | 1 - 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index a9bea5e8d..7876d97c6 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -3,25 +3,30 @@ const getFileTypeIcon = require('./getFileTypeIcon') const onload = require('on-load') const { checkIcon } = require('./icons') -let file -const meta = {} +// let file +// const meta = {} module.exports = function fileCard (props) { - file = props.fileCardFor ? props.files[props.fileCardFor] : false + const file = props.fileCardFor ? props.files[props.fileCardFor] : false + const meta = {} - const handleEnterKey = (ev) => { - if (file && event.keyCode === 13) { + // const handleEnterKey = (ev) => { + // if (event.keyCode === 13) { + // props.done(meta, file.id) + // } + // } + + // if (file) { + // document.body.addEventListener('keyup', handleEnterKey) + // } else { + // document.body.removeEventListener('keyup', handleEnterKey) + // } + + const tempStoreMetaOrSubmit = (ev) => { + if (ev.keyCode === 13) { props.done(meta, file.id) } - } - if (file) { - document.body.addEventListener('keyup', handleEnterKey) - } else { - document.body.removeEventListener('keyup', handleEnterKey) - } - - const tempStoreMeta = (ev) => { const value = ev.target.value const name = ev.target.dataset.name meta[name] = value @@ -37,7 +42,7 @@ module.exports = function fileCard (props) { data-name="${field.id}" value="${file.meta[field.id]}" placeholder="${field.placeholder || ''}" - onkeyup=${tempStoreMeta} />` + onkeyup=${tempStoreMetaOrSubmit} />` }) } @@ -62,7 +67,7 @@ module.exports = function fileCard (props) {
+ onkeyup=${tempStoreMetaOrSubmit} />
${renderMetaFields(file)} diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 30c101311..afe8eb09e 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -339,7 +339,6 @@ module.exports = class DashboardUI extends Plugin { } const fileCardDone = (meta, fileID) => { - console.log(meta) this.core.emit('core:update-meta', meta, fileID) this.core.emit('dashboard:file-card') } From c75e80fed2091a5a0b0d3fc3b0b009cb6603e9ba Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Fri, 1 Sep 2017 12:38:03 +0300 Subject: [PATCH 09/25] cleanup --- src/plugins/Dashboard/FileCard.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/plugins/Dashboard/FileCard.js b/src/plugins/Dashboard/FileCard.js index 7876d97c6..51602b8b4 100644 --- a/src/plugins/Dashboard/FileCard.js +++ b/src/plugins/Dashboard/FileCard.js @@ -1,27 +1,11 @@ const html = require('yo-yo') const getFileTypeIcon = require('./getFileTypeIcon') -const onload = require('on-load') const { checkIcon } = require('./icons') -// let file -// const meta = {} - module.exports = function fileCard (props) { const file = props.fileCardFor ? props.files[props.fileCardFor] : false const meta = {} - // const handleEnterKey = (ev) => { - // if (event.keyCode === 13) { - // props.done(meta, file.id) - // } - // } - - // if (file) { - // document.body.addEventListener('keyup', handleEnterKey) - // } else { - // document.body.removeEventListener('keyup', handleEnterKey) - // } - const tempStoreMetaOrSubmit = (ev) => { if (ev.keyCode === 13) { props.done(meta, file.id) @@ -46,7 +30,7 @@ module.exports = function fileCard (props) { }) } - const fileCardEl = () => html`
+ return html`

Editing ${file.meta ? file.meta.name : file.name}

` - - return onload(html`
${fileCardEl()}
`, - null, - null, - `uppy${file.id}` - ) } From 617c5e525cce82be985e4fe93602b1692784f628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 24 Aug 2017 14:40:43 +0200 Subject: [PATCH 10/25] transloadit: Return assembly information from `createAssembly`. We don't actually use this right now but this is what I would've expected a method called `createAssembly` to do. --- src/plugins/Transloadit/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index ed6dcc1f8..3c4c9584c 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -167,8 +167,10 @@ module.exports = class Transloadit extends Plugin { this.core.emit('transloadit:assembly-created', assembly, fileIDs) return this.connectSocket(assembly) - }).then(() => { + .then(() => assembly) + }).then((assembly) => { this.core.log('Transloadit: Created assembly') + return assembly }).catch((err) => { this.core.info(this.opts.locale.strings.creatingAssemblyFailed, 'error', 0) From cde56fa491fdc61df6b477f5a7667a85f42f7027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 7 Jul 2017 15:19:34 +0200 Subject: [PATCH 11/25] transloadit: Begin work on importing uploaded URLs into assembly. Support uploading to elsewhere, then importing URLs into assembly. --- src/plugins/Transloadit/Client.js | 18 ++++++++++++++++++ src/plugins/Transloadit/index.js | 31 +++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/plugins/Transloadit/Client.js b/src/plugins/Transloadit/Client.js index 95323f938..ae93c1c19 100644 --- a/src/plugins/Transloadit/Client.js +++ b/src/plugins/Transloadit/Client.js @@ -47,6 +47,24 @@ module.exports = class Client { }) } + reserveFile (assembly, file) { + const size = encodeURIComponent(file.size) + return fetch(`${assembly.assembly_ssl_url}/reserve_file?size=${size}`) + .then((response) => response.json()) + } + + addFile (assembly, file) { + if (!file.uploadURL) { + return Promise.reject(new Error('File does not have an `uploadURL`.')) + } + const size = encodeURIComponent(file.size) + const url = encodeURIComponent(file.uploadURL) + const filename = encodeURIComponent(file.name) + const fieldname = 'file' + return fetch(`${assembly.assembly_ssl_url}/add_file?size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${url}`) + .then((response) => response.json()) + } + /** * Get the current status for an assembly. * diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index 3c4c9584c..4ada7c41d 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -24,6 +24,7 @@ module.exports = class Transloadit extends Plugin { waitForEncoding: false, waitForMetadata: false, alwaysRunAssembly: false, // TODO name + importFromUploadURLs: false, signature: null, params: null, fields: {}, @@ -44,6 +45,7 @@ module.exports = class Transloadit extends Plugin { this.prepareUpload = this.prepareUpload.bind(this) this.afterUpload = this.afterUpload.bind(this) + this.onFileUploaded = this.onFileUploaded.bind(this) if (this.opts.params) { this.validateParams(this.opts.params) @@ -145,7 +147,8 @@ module.exports = class Transloadit extends Plugin { }) // Add assembly-specific Tus endpoint. const tus = Object.assign({}, file.tus, { - endpoint: assembly.tus_url + endpoint: assembly.tus_url, + metaFields: ['assembly_url', 'filename', 'fieldname'] }) const transloadit = { assembly: assembly.assembly_id @@ -183,6 +186,19 @@ module.exports = class Transloadit extends Plugin { return this.opts.waitForEncoding || this.opts.waitForMetadata } + onFileUploaded (fileID) { + const file = this.core.getState().files[fileID] + if (!file || !file.transloadit || !file.transloadit.assembly) { + return + } + + const assembly = this.state.assemblies[file.transloadit.assembly] + + this.client.addFile(assembly, file).catch((err) => { + console.error('ignoring', err) + }) + } + findFile (uploadedFile) { const files = this.core.state.files for (const id in files) { @@ -274,7 +290,14 @@ module.exports = class Transloadit extends Plugin { }) const createAssembly = ({ fileIDs, options }) => { - return this.createAssembly(fileIDs, uploadID, options).then(() => { + return this.createAssembly(fileIDs, uploadID, options).then((assembly) => { + if (this.opts.importFromUploadURLs) { + return Promise.all(fileIDs.map((fileID) => { + const file = this.core.getFile(fileID) + return this.client.reserveFile(assembly, file) + })) + } + }).then(() => { fileIDs.forEach((fileID) => { this.core.emit('core:preprocess-complete', fileID) }) @@ -403,6 +426,10 @@ module.exports = class Transloadit extends Plugin { this.core.addPreProcessor(this.prepareUpload) this.core.addPostProcessor(this.afterUpload) + if (this.opts.importFromUploadURLs) { + this.core.on('file:upload-success', this.onFileUploaded) + } + this.updateState({ // Contains assembly status objects, indexed by their ID. assemblies: {}, From cebc0ee58115537c0733b378ea3c27caae4719f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 14 Jul 2017 19:54:16 +0200 Subject: [PATCH 12/25] transloadit: First working URL import --- src/plugins/Transloadit/Client.js | 8 +++++--- src/plugins/Transloadit/index.js | 14 ++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/plugins/Transloadit/Client.js b/src/plugins/Transloadit/Client.js index ae93c1c19..088afe1a1 100644 --- a/src/plugins/Transloadit/Client.js +++ b/src/plugins/Transloadit/Client.js @@ -30,7 +30,7 @@ module.exports = class Client { Object.keys(fields).forEach((key) => { data.append(key, fields[key]) }) - data.append('tus_num_expected_upload_files', expectedFiles) + data.append('num_expected_upload_files', expectedFiles) return fetch(`${this.apiUrl}/assemblies`, { method: 'post', @@ -49,7 +49,7 @@ module.exports = class Client { reserveFile (assembly, file) { const size = encodeURIComponent(file.size) - return fetch(`${assembly.assembly_ssl_url}/reserve_file?size=${size}`) + return fetch(`${assembly.assembly_ssl_url}/reserve_file?size=${size}`, { method: 'post' }) .then((response) => response.json()) } @@ -61,7 +61,9 @@ module.exports = class Client { const url = encodeURIComponent(file.uploadURL) const filename = encodeURIComponent(file.name) const fieldname = 'file' - return fetch(`${assembly.assembly_ssl_url}/add_file?size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${url}`) + + const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${url}` + return fetch(`${assembly.assembly_ssl_url}/add_file?${qs}`, { method: 'post' }) .then((response) => response.json()) } diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index 4ada7c41d..c06c629ea 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -153,11 +153,13 @@ module.exports = class Transloadit extends Plugin { const transloadit = { assembly: assembly.assembly_id } - return Object.assign( - {}, - file, - { meta, tus, transloadit } - ) + + const newFile = Object.assign({}, file, { transloadit }) + // Only configure the Tus plugin if we are uploading straight to Transloadit (the default). + if (!opts.importFromUploadURLs) { + Object.assign(newFile, { meta, tus }) + } + return newFile } const files = Object.assign({}, this.core.state.files) @@ -427,7 +429,7 @@ module.exports = class Transloadit extends Plugin { this.core.addPostProcessor(this.afterUpload) if (this.opts.importFromUploadURLs) { - this.core.on('file:upload-success', this.onFileUploaded) + this.core.on('core:upload-success', this.onFileUploaded) } this.updateState({ From d8560232175f983576f08120e21f8319d8d8b3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 21 Aug 2017 17:23:24 +0200 Subject: [PATCH 13/25] transloadit: Fix undefined `opts` --- src/plugins/Transloadit/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index c06c629ea..e44d0e4a7 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -113,6 +113,8 @@ module.exports = class Transloadit extends Plugin { } createAssembly (fileIDs, uploadID, options) { + const pluginOptions = this.opts + this.core.log('Transloadit: create assembly') return this.client.createAssembly({ @@ -156,7 +158,7 @@ module.exports = class Transloadit extends Plugin { const newFile = Object.assign({}, file, { transloadit }) // Only configure the Tus plugin if we are uploading straight to Transloadit (the default). - if (!opts.importFromUploadURLs) { + if (!pluginOptions.importFromUploadURLs) { Object.assign(newFile, { meta, tus }) } return newFile @@ -177,7 +179,7 @@ module.exports = class Transloadit extends Plugin { this.core.log('Transloadit: Created assembly') return assembly }).catch((err) => { - this.core.info(this.opts.locale.strings.creatingAssemblyFailed, 'error', 0) + this.core.info(pluginOptions.locale.strings.creatingAssemblyFailed, 'error', 0) // Reject the promise. throw err From 13ac3350a4617c53ded4155ddde26ebfb2af6d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 21 Aug 2017 17:25:25 +0200 Subject: [PATCH 14/25] transloadit: Emit `core:upload-error` if file cannot be added to assembly --- src/plugins/Transloadit/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index e44d0e4a7..618d602c4 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -199,7 +199,8 @@ module.exports = class Transloadit extends Plugin { const assembly = this.state.assemblies[file.transloadit.assembly] this.client.addFile(assembly, file).catch((err) => { - console.error('ignoring', err) + this.core.log(err) + this.core.emit('core:upload-error', file.id, err) }) } From 0e479497e23ea5770963b48b9521e8dc80b39f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 21 Aug 2017 17:29:03 +0200 Subject: [PATCH 15/25] transloadit: Clarify how only :tl: metadata is sent to tus --- src/plugins/Transloadit/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index 618d602c4..d6eee2307 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -140,17 +140,17 @@ module.exports = class Transloadit extends Plugin { // Attach meta parameters for the Tus plugin. See: // https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus // TODO Should this `meta` be moved to a `tus.meta` property instead? - // If the MetaData plugin can add eg. resize parameters, it doesn't - // make much sense to set those as upload-metadata for tus. - const meta = Object.assign({}, file.meta, { + const tlMeta = { assembly_url: assembly.assembly_url, filename: file.name, fieldname: 'file' - }) + } + const meta = Object.assign({}, file.meta, tlMeta) // Add assembly-specific Tus endpoint. const tus = Object.assign({}, file.tus, { endpoint: assembly.tus_url, - metaFields: ['assembly_url', 'filename', 'fieldname'] + // Only send assembly metadata to the tus endpoint. + metaFields: Object.keys(tlMeta) }) const transloadit = { assembly: assembly.assembly_id From 727c4ee14585956a5598c6afc26394bec0ba8447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 21 Aug 2017 17:49:14 +0200 Subject: [PATCH 16/25] transloadit: Name some things. --- src/plugins/Transloadit/index.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index d6eee2307..dc3930703 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -45,7 +45,7 @@ module.exports = class Transloadit extends Plugin { this.prepareUpload = this.prepareUpload.bind(this) this.afterUpload = this.afterUpload.bind(this) - this.onFileUploaded = this.onFileUploaded.bind(this) + this.onFileUploadURLAvailable = this.onFileUploadURLAvailable.bind(this) if (this.opts.params) { this.validateParams(this.opts.params) @@ -190,7 +190,22 @@ module.exports = class Transloadit extends Plugin { return this.opts.waitForEncoding || this.opts.waitForMetadata } - onFileUploaded (fileID) { + /** + * Used when `importFromUploadURLs` is enabled: reserves all files in + * the assembly. + */ + reserveFiles (assembly, fileIDs) { + return Promise.all(fileIDs.map((fileID) => { + const file = this.core.getFile(fileID) + return this.client.reserveFile(assembly, file) + })) + } + + /** + * Used when `importFromUploadURLs` is enabled: adds files to the assembly + * once they have been fully uploaded. + */ + onFileUploadURLAvailable (fileID) { const file = this.core.getState().files[fileID] if (!file || !file.transloadit || !file.transloadit.assembly) { return @@ -297,10 +312,7 @@ module.exports = class Transloadit extends Plugin { const createAssembly = ({ fileIDs, options }) => { return this.createAssembly(fileIDs, uploadID, options).then((assembly) => { if (this.opts.importFromUploadURLs) { - return Promise.all(fileIDs.map((fileID) => { - const file = this.core.getFile(fileID) - return this.client.reserveFile(assembly, file) - })) + return this.reserveFiles(assembly, fileIDs) } }).then(() => { fileIDs.forEach((fileID) => { @@ -432,7 +444,7 @@ module.exports = class Transloadit extends Plugin { this.core.addPostProcessor(this.afterUpload) if (this.opts.importFromUploadURLs) { - this.core.on('core:upload-success', this.onFileUploaded) + this.core.on('core:upload-success', this.onFileUploadURLAvailable) } this.updateState({ From 762df4518c106c91eb27b02181e9cfcc0022f638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 21 Aug 2017 18:00:43 +0200 Subject: [PATCH 17/25] docs: Transloadit `importFromUploadURLs` option --- website/src/docs/transloadit.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/website/src/docs/transloadit.md b/website/src/docs/transloadit.md index 04e7da47c..4b013083c 100644 --- a/website/src/docs/transloadit.md +++ b/website/src/docs/transloadit.md @@ -20,6 +20,8 @@ uppy.use(Transloadit, { }) ``` +NB: It is not required to use the `Tus10` plugin if [importFromUploadURLs](#importFromUploadURLs) is enabled. + ## Options ### `waitForEncoding` @@ -31,6 +33,35 @@ Whether to wait for all assemblies to complete before completing the upload. Whether to wait for metadata to be extracted from uploaded files before completing the upload. If `waitForEncoding` is enabled, this has no effect. +### `importFromUploadURLs` + +Instead of uploading to Transloadit's servers directly, allow another plugin to upload files, and then import those files into the Transloadit assembly. +Default `false`. + +When enabling this option, Transloadit will *not* configure the Tus plugin to upload to Transloadit. +Instead, a separate upload plugin must be used. +Once the upload completes, the Transloadit plugin adds the uploaded file to the assembly. + +For example, to upload files to an S3 bucket and then transcode them: + +```js +uppy.use(AwsS3, { + getUploadParameters (file) { + return { /* upload parameters */ } + } +}) +uppy.use(Transloadit, { + importFromUploadURLs: true, + params: { + auth: { key: /* secret */ }, + template_id: /* secret */ + } +}) +``` + +In order for this to work, the upload plugin must assign a publically accessible `uploadURL` property to the uploaded file object. +The Tus and S3 plugins both do this—for the XHRUpload plugin, you may have to specify a custom `getUploadResponse` function. + ### `params` The assembly parameters to use for the upload. From 191a1de41fd9295f1ecf008c1bcd445d7911bb10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 28 Aug 2017 14:31:02 +0200 Subject: [PATCH 18/25] transloadit: Fail the postprocessing step when /add_file fails. --- src/plugins/Transloadit/index.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index dc3930703..bc9ba8805 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -206,7 +206,7 @@ module.exports = class Transloadit extends Plugin { * once they have been fully uploaded. */ onFileUploadURLAvailable (fileID) { - const file = this.core.getState().files[fileID] + const file = this.core.getFile(fileID) if (!file || !file.transloadit || !file.transloadit.assembly) { return } @@ -215,7 +215,7 @@ module.exports = class Transloadit extends Plugin { this.client.addFile(assembly, file).catch((err) => { this.core.log(err) - this.core.emit('core:upload-error', file.id, err) + this.core.emit('transloadit:import-error', assembly, file.id, err) }) } @@ -424,13 +424,28 @@ module.exports = class Transloadit extends Plugin { reject(error) } + const onImportError = (assembly, fileID, error) => { + if (assemblyIDs.indexOf(assembly.assembly_id) === -1) { + return + } + + // Not sure if we should be doing something when it's just one file failing. + // ATM, the only options are 1) ignoring or 2) failing the entire upload. + // I think failing the upload is better than silently ignoring. + // In the future we should maybe have a way to resolve uploads with some failures, + // like returning an object with `{ successful, failed }` uploads. + onAssemblyError(assembly, error) + } + const removeListeners = () => { this.core.off('transloadit:complete', onAssemblyFinished) this.core.off('transloadit:assembly-error', onAssemblyError) + this.core.off('transloadit:import-error', onImportError) } this.core.on('transloadit:complete', onAssemblyFinished) this.core.on('transloadit:assembly-error', onAssemblyError) + this.core.on('transloadit:import-error', onImportError) }).then(() => { // Clean up uploadID → assemblyIDs, they're no longer going to be used anywhere. const uploadsAssemblies = Object.assign({}, this.state.uploadsAssemblies) From 11b820e68cc1715b50b02bd307371966da7eb792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 28 Aug 2017 14:38:59 +0200 Subject: [PATCH 19/25] transloadit: Remove event listener correctly on uninstall --- src/plugins/Transloadit/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index bc9ba8805..7f636529f 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -477,6 +477,10 @@ module.exports = class Transloadit extends Plugin { uninstall () { this.core.removePreProcessor(this.prepareUpload) this.core.removePostProcessor(this.afterUpload) + + if (this.opts.importFromUploadURLs) { + this.core.off('core:upload-success', this.onFileUploadURLAvailable) + } } getAssembly (id) { From b0eb6c0a7e764c366923fb1c5837119ec6b9e21e Mon Sep 17 00:00:00 2001 From: Richard Willars Date: Fri, 1 Sep 2017 11:38:11 +0100 Subject: [PATCH 20/25] Checks for plugin type and plugin id rather than name --- src/core/Core.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/Core.js b/src/core/Core.js index 60e2682d4..aa5b5f693 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -609,21 +609,21 @@ class Uppy { // Instantiate const plugin = new Plugin(this, opts) - const pluginName = plugin.id + const pluginId = plugin.id this.plugins[plugin.type] = this.plugins[plugin.type] || [] - if (!pluginName) { - throw new Error('Your plugin must have a name') + if (!pluginId) { + throw new Error('Your plugin must have an id') } - if (!plugin.type) { + if (!plugin.type || plugin.type === 'none') { throw new Error('Your plugin must have a type') } - let existsPluginAlready = this.getPlugin(pluginName) + let existsPluginAlready = this.getPlugin(pluginId) if (existsPluginAlready) { - let msg = `Already found a plugin named '${existsPluginAlready.name}'. - Tried to use: '${pluginName}'. + let msg = `Already found a plugin named '${existsPluginAlready.id}'. + Tried to use: '${pluginId}'. Uppy is currently limited to running one of every plugin. Share your use case with us over at https://github.com/transloadit/uppy/issues/ From bd6379dd42131020f8c89c27303d58638684b1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 1 Sep 2017 12:44:19 +0200 Subject: [PATCH 21/25] changelog: tick off s3+tl interop --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200be45ca..ec5aee3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,7 @@ What we need to do to release Uppy 1.0 - [x] feature: restrictions: by size, number of files, file type - [x] feature: beta file recovering after closed tab / browser crash - [ ] feature: improved UI for Provider, Google Drive and Instagram, grid/list views -- [ ] feature: finish the direct-to-s3 upload plugin and test it with the flow to then upload to :transloadit: afterwards. This is because this might influence the inner flow of the plugin architecture quite a bit +- [x] feature: finish the direct-to-s3 upload plugin and test it with the flow to then upload to :transloadit: afterwards. This is because this might influence the inner flow of the plugin architecture quite a bit - [ ] feature: Uppy should work well with React/Redux and React Native - [ ] feature: preset for Transloadit that mimics jQuery SDK - [ ] QA: test how everything works together: user experience from `npm install` to production build with Webpack, using in React/Redux environment (npm pack) @@ -97,7 +97,7 @@ Theme: React and Retry - [ ] core: retry or show error when upload can’t start / fails (offline, wrong endpoint) — now it just sits there (@arturi @goto-bus-stop) - [ ] core: calling `upload` immediately after `addFile` does not upload all files (#249 @goto-bus-stop) - [ ] core: React / Redux PRs (@arturi @goto-bus-stop) -- [ ] transloadit: upload to S3, then import into :tl: assembly using `/add_file?s3url=${url}` (@goto-bus-stop) +- [x] transloadit: upload to S3, then import into :tl: assembly using `/add_file?s3url=${url}` (@goto-bus-stop) - [ ] goldenretriver: add “ghost” files (@arturi @goto-bus-stop) - [ ] dashboard: cancel button for any kind of uploads? currently resume/pause only for tus, and cancel for XHR (@arturi @goto-bus-stop) - [x] informer: support “explanations”, a (?) button that shows more info on hover / click From 5a75c0de70bf5b07e83de0c1e4e3b9f71b8b02a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 1 Sep 2017 12:51:33 +0200 Subject: [PATCH 22/25] changelog: punting on #249 for now --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5aee3e6..9e3bca3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Ideas that will be planned and find their way into a release at one point - [ ] possibility to edit/delete more than one file at once #118, #97 - [ ] optimize problematic filenames #72 - [ ] an uploader plugin to receive files in a callback instead of uploading them +- [ ] core: calling `upload` immediately after `addFile` does not upload all files (#249 @goto-bus-stop) ## 1.0 Goals @@ -95,7 +96,6 @@ Theme: React and Retry - [ ] core: add error in file progress state? error UI, question mark button, `core:error` (@arturi) - [ ] core: retry or show error when upload can’t start / fails (offline, wrong endpoint) — now it just sits there (@arturi @goto-bus-stop) -- [ ] core: calling `upload` immediately after `addFile` does not upload all files (#249 @goto-bus-stop) - [ ] core: React / Redux PRs (@arturi @goto-bus-stop) - [x] transloadit: upload to S3, then import into :tl: assembly using `/add_file?s3url=${url}` (@goto-bus-stop) - [ ] goldenretriver: add “ghost” files (@arturi @goto-bus-stop) From cfc21fd179df9ee335e860314f6060bf3323e65e Mon Sep 17 00:00:00 2001 From: Richard Willars Date: Fri, 1 Sep 2017 13:31:39 +0100 Subject: [PATCH 23/25] updateAll method uses iteratePlugins method --- src/core/Core.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/Core.js b/src/core/Core.js index 60e2682d4..06f031c7e 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -115,10 +115,8 @@ class Uppy { * */ updateAll (state) { - Object.keys(this.plugins).forEach((pluginType) => { - this.plugins[pluginType].forEach((plugin) => { - plugin.update(state) - }) + this.iteratePlugins(plugin => { + plugin.update(state) }) } From b7145e83f44b24fa8e1cb4d40f68fee8b9f2f3c6 Mon Sep 17 00:00:00 2001 From: Richard Willars Date: Fri, 1 Sep 2017 14:11:43 +0100 Subject: [PATCH 24/25] Removes 'none' string check on plugin type --- src/core/Core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Core.js b/src/core/Core.js index aa5b5f693..8183ec092 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -616,7 +616,7 @@ class Uppy { throw new Error('Your plugin must have an id') } - if (!plugin.type || plugin.type === 'none') { + if (!plugin.type) { throw new Error('Your plugin must have a type') } From 11ae9e332a6d22a614caf15ee13eb6889205b612 Mon Sep 17 00:00:00 2001 From: Richard Willars Date: Fri, 1 Sep 2017 14:13:47 +0100 Subject: [PATCH 25/25] Removes type property (should be added by the actual plugin) --- src/plugins/Plugin.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/Plugin.js b/src/plugins/Plugin.js index a29f32526..102a59ce2 100644 --- a/src/plugins/Plugin.js +++ b/src/plugins/Plugin.js @@ -17,7 +17,6 @@ module.exports = class Plugin { constructor (core, opts) { this.core = core this.opts = opts || {} - this.type = 'none' // clear everything inside the target selector this.opts.replaceTargetContent === this.opts.replaceTargetContent || true