From 0bda16b0977a8d33ace80a6d063961e4084ebf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 30 Apr 2018 14:55:59 +0200 Subject: [PATCH 01/40] core: Debounce render calls again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This saves a lot of time diffing, mostly—every `setState()` call is currently telling preact to update the vdom, which is cheap but not THAT cheap :P I used a Promise resolution for the debounce here, instead of nanoraf, because it is 1) very small and 2) the same behaviour that Preact has for `setState()` calls inside components. This time, it comes with a sync `this.rerender()` method for plugins that need to handle focus. I think this is best viewed as a hack, and we should try to use the `componentDidMount()` lifecycle method instead for this sort of thing. --- src/core/Plugin.js | 32 ++++++++++++++++++++++++++++---- src/plugins/Dashboard/index.js | 2 +- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/core/Plugin.js b/src/core/Plugin.js index 46e918296..ec8976b01 100644 --- a/src/core/Plugin.js +++ b/src/core/Plugin.js @@ -1,6 +1,28 @@ const preact = require('preact') const { findDOMElement } = require('../core/Utils') +/** + * Defer a frequent call to the microtask queue. + */ +function debounce (fn) { + let calling = null + let latestArgs = null + return (...args) => { + latestArgs = args + if (!calling) { + calling = Promise.resolve().then(() => { + calling = null + // At this point `args` may be different from the most + // recent state, if multiple calls happened since this task + // was queued. So we use the `latestArgs`, which definitely + // is the most recent call. + return fn(...latestArgs) + }) + } + return calling + } +} + /** * Boilerplate that all Plugins share - and should not be used * directly. It also shows which methods final plugins should implement/override, @@ -39,8 +61,8 @@ module.exports = class Plugin { return } - if (this.updateUI) { - this.updateUI(state) + if (this._updateUI) { + this._updateUI(state) } } @@ -60,9 +82,11 @@ module.exports = class Plugin { if (targetElement) { this.isTargetDOMEl = true - this.updateUI = (state) => { + // API for plugins that require a synchronous rerender. + this.rerender = (state) => { this.el = preact.render(this.render(state), targetElement, this.el) } + this._updateUI = debounce(this.rerender) this.uppy.log(`Installing ${callerPluginName} to a DOM element`) @@ -71,7 +95,7 @@ module.exports = class Plugin { targetElement.innerHTML = '' } - this.el = preact.render(this.render(this.uppy.state), targetElement) + this.el = preact.render(this.render(this.uppy.getState()), targetElement) return this.el } diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 4949acda1..f2186c488 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -240,8 +240,8 @@ module.exports = class Dashboard extends Plugin { document.body.classList.add('uppy-Dashboard-isOpen') } + this.rerender() this.updateDashboardElWidth() - // this.setFocusToFirstNode() this.setFocusToBrowse() } From 892a19e10114a77f86d57b5af6d11024911e0dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 30 Apr 2018 15:01:07 +0200 Subject: [PATCH 02/40] dashboard: focus "Connect" button on auth view. --- src/views/ProviderView/AuthView.js | 41 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/views/ProviderView/AuthView.js b/src/views/ProviderView/AuthView.js index 1e107ca5b..5986d408e 100644 --- a/src/views/ProviderView/AuthView.js +++ b/src/views/ProviderView/AuthView.js @@ -1,28 +1,41 @@ const LoaderView = require('./Loader') const { h, Component } = require('preact') +class AuthBlock extends Component { + componentDidMount () { + this.connectButton.focus() + } + + render () { + return
+
{this.props.pluginIcon()}
+

Please authenticate with {this.props.pluginName}
to select files

+ + {this.props.demo && + + } +
+ } +} + class AuthView extends Component { componentDidMount () { this.props.checkAuth() } render () { - const AuthBlock = () => { - return
-
{this.props.pluginIcon()}
-

Please authenticate with {this.props.pluginName}
to select files

- - {this.props.demo && - - } -
- } - return ( -
+
{this.props.checkAuthInProgress - ? LoaderView() - : AuthBlock() + ? + : }
) From 307dbde55719c49bc2fb48c60f48545b55e3cab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 3 May 2018 13:47:35 +0200 Subject: [PATCH 03/40] transloadit: Remove startWith/endsWith use Not supported in all browsers. --- src/plugins/Transloadit/index.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index f8821853e..8156e93fe 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -185,18 +185,15 @@ module.exports = class Transloadit extends Plugin { // this is the criteria to identify remote files. If we add it without // the check, then the file automatically becomes a remote file. // @TODO: this is quite hacky. Please fix this later - let remote + let remote = file.remote if (file.remote) { let newHost = assembly.uppyserver_url - // remove tailing slash - if (newHost.endsWith('/')) { - newHost = newHost.slice(0, -1) - } let path = file.remote.url.replace(file.remote.host, '') + // remove tailing slash + newHost = newHost.replace(/\/$/, '') // remove leading slash - if (path.startsWith('/')) { - path = path.slice(1) - } + path = path.replace(/^\//, '') + remote = Object.assign({}, file.remote, { host: newHost, url: `${newHost}/${path}` From 7e8d3a5f38d845cdbf764e88ca523935583ad362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 3 May 2018 13:56:18 +0200 Subject: [PATCH 04/40] transloadit: Only rewrite Uppy Server URLs if uppy is configured to use TL's hosted options. --- src/plugins/Transloadit/index.js | 17 +++++++++++------ website/src/docs/transloadit.md | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/plugins/Transloadit/index.js b/src/plugins/Transloadit/index.js index 8156e93fe..edb1bb696 100644 --- a/src/plugins/Transloadit/index.js +++ b/src/plugins/Transloadit/index.js @@ -12,6 +12,10 @@ function defaultGetAssemblyOptions (file, options) { } } +const UPPY_SERVER = 'https://api2.transloadit.com/uppy-server' +// Regex used to check if an uppy-server address is run by Transloadit. +const TL_UPPY_SERVER = /https?:\/\/api2(?:-\w+)?\.transloadit\.com\/uppy-server/ + /** * Upload files to Transloadit using Tus. */ @@ -180,13 +184,12 @@ module.exports = class Transloadit extends Plugin { endpoint: assembly.tus_url }) - // Set uppy server location. - // we only add this, if 'file' has the attribute remote, because - // this is the criteria to identify remote files. If we add it without - // the check, then the file automatically becomes a remote file. - // @TODO: this is quite hacky. Please fix this later + // Set uppy server location. We only add this, if 'file' has the attribute + // remote, because this is the criteria to identify remote files. + // We only replace the hostname for Transloadit's uppy-servers, so that + // people can self-host them while still using Transloadit for encoding. let remote = file.remote - if (file.remote) { + if (file.remote && TL_UPPY_SERVER.test(file.remote)) { let newHost = assembly.uppyserver_url let path = file.remote.url.replace(file.remote.host, '') // remove tailing slash @@ -823,3 +826,5 @@ module.exports = class Transloadit extends Plugin { }) } } + +module.exports.UPPY_SERVER = UPPY_SERVER diff --git a/website/src/docs/transloadit.md b/website/src/docs/transloadit.md index 631b2f29d..5d561cbee 100644 --- a/website/src/docs/transloadit.md +++ b/website/src/docs/transloadit.md @@ -24,6 +24,29 @@ uppy.use(Transloadit, { As of Uppy 0.24 the Transloadit plugin includes the [Tus](/docs/tus) plugin to handle the uploading, so you no longer have to add it manually. +## Properties + +### `Transloadit.UPPY_SERVER` + +The main endpoint for Transloadit's hosted uppy-servers. You can use this constant in remote provider options, like so: + +```js +const Dropbox = require('uppy/lib/plugins/Dropbox') +const Transloadit = require('uppy/lib/plugins/Transloadit') + +uppy.use(Dropbox, { + host: Transloadit.UPPY_SERVER +}) +``` + +The value of this constant is `https://api2.transloadit.com/uppy-server`. If you are using a custom [`service`](#service) option, you should also set a custom host option in your provider plugins, by taking a Transloadit API url and appending `/uppy-server`: + +```js +uppy.use(Dropbox, { + host: 'https://api2-us-east-1.transloadit.com/uppy-server' +}) +``` + ## Options ### `service` From 7c2515fa0158642eb87586b94420fe1abcc33a6f Mon Sep 17 00:00:00 2001 From: Artur Paikin Date: Mon, 7 May 2018 17:32:20 -0400 Subject: [PATCH 05/40] Pass allowedFileTypes and maxNumberOfFiles to input[type=file] --- src/core/Core.js | 13 ++++++++++++- src/plugins/Dashboard/ActionBrowseTagline.js | 4 +++- src/plugins/Dashboard/FileList.js | 4 +++- src/plugins/Dashboard/Tabs.js | 3 ++- src/plugins/Dashboard/index.js | 4 +++- src/plugins/DragDrop/index.js | 17 ++++++++++++++--- src/plugins/FileInput.js | 6 +++++- src/scss/_dragdrop.scss | 12 +----------- 8 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/core/Core.js b/src/core/Core.js index 5a499fb39..2abd63edd 100644 --- a/src/core/Core.js +++ b/src/core/Core.js @@ -323,7 +323,18 @@ class Uppy { if (allowedFileTypes) { const isCorrectFileType = allowedFileTypes.filter((type) => { if (!file.type) return false - return match(file.type, type) + + // is this is a mime-type + if (type.indexOf('/') > -1) { + return match(file.type, type) + } + + // otherwise this is likely an extension + if (type.startsWith('.') > -1) { + if (file.extension === type.substr(1)) { + return file.extension + } + } }).length > 0 if (!isCorrectFileType) { diff --git a/src/plugins/Dashboard/ActionBrowseTagline.js b/src/plugins/Dashboard/ActionBrowseTagline.js index c2526d5e6..62b8b0a57 100644 --- a/src/plugins/Dashboard/ActionBrowseTagline.js +++ b/src/plugins/Dashboard/ActionBrowseTagline.js @@ -13,6 +13,7 @@ class ActionBrowseTagline extends Component { render () { // empty value="" on file input, so we can select same file // after removing it from Uppy — otherwise OS thinks it’s selected + return ( {this.props.acquirers.length === 0 @@ -27,8 +28,9 @@ class ActionBrowseTagline extends Component { tabindex="-1" type="file" name="files[]" - multiple="true" + multiple={this.props.maxNumberOfFiles !== 1 || !this.props.maxNumberOfFiles} onchange={this.props.handleInputChange} + accept={this.props.allowedFileTypes} value="" ref={(input) => { this.input = input diff --git a/src/plugins/Dashboard/FileList.js b/src/plugins/Dashboard/FileList.js index 392604b68..6fd996ee3 100644 --- a/src/plugins/Dashboard/FileList.js +++ b/src/plugins/Dashboard/FileList.js @@ -18,7 +18,9 @@ module.exports = (props) => { {h(ActionBrowseTagline, { acquirers: props.acquirers, handleInputChange: props.handleInputChange, - i18n: props.i18n + i18n: props.i18n, + allowedFileTypes: props.allowedFileTypes, + maxNumberOfFiles: props.maxNumberOfFiles })}
{ props.note &&
{props.note}
} diff --git a/src/plugins/Dashboard/Tabs.js b/src/plugins/Dashboard/Tabs.js index bb6b550e7..234c46f3d 100644 --- a/src/plugins/Dashboard/Tabs.js +++ b/src/plugins/Dashboard/Tabs.js @@ -49,7 +49,8 @@ class Tabs extends Component { tabindex="-1" type="file" name="files[]" - multiple="true" + multiple={this.props.maxNumberOfFiles !== 1 || !this.props.maxNumberOfFiles} + accept={this.props.allowedFileTypes} onchange={this.props.handleInputChange} value="" ref={(input) => { this.input = input }} /> diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 4949acda1..ed0d7c2fe 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -486,7 +486,9 @@ module.exports = class Dashboard extends Plugin { proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy, currentWidth: pluginState.containerWidth, isWide: pluginState.containerWidth > 400, - isTargetDOMEl: this.isTargetDOMEl + isTargetDOMEl: this.isTargetDOMEl, + allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes, + maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles }) } diff --git a/src/plugins/DragDrop/index.js b/src/plugins/DragDrop/index.js index 7433de771..1617a247c 100644 --- a/src/plugins/DragDrop/index.js +++ b/src/plugins/DragDrop/index.js @@ -102,11 +102,21 @@ module.exports = class DragDrop extends Plugin { } render (state) { - const DragDropClass = `uppy uppy-DragDrop-container ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}` + /* http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ */ + const hiddenInputStyle = { + width: '0.1px', + height: '0.1px', + opacity: 0, + overflow: 'hidden', + position: 'absolute', + zIndex: -1 + } + const DragDropClass = `uppy uppy-DragDrop-container ${this.isDragDropSupported ? 'uppy-DragDrop--is-dragdrop-supported' : ''}` const DragDropStyle = { width: this.opts.width, height: this.opts.height } + const restrictions = this.uppy.opts.restrictions return (
@@ -114,10 +124,11 @@ module.exports = class DragDrop extends Plugin {