diff --git a/package-lock.json b/package-lock.json
index d7e785cc7..fb0f0d0b5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14652,6 +14652,11 @@
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
"integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ="
},
+ "is-shallow-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-shallow-equal/-/is-shallow-equal-1.0.1.tgz",
+ "integrity": "sha512-lq5RvK+85Hs5J3p4oA4256M1FEffzmI533ikeDHvJd42nouRRx5wBzt36JuviiGe5dIPyHON/d0/Up+PBo6XkQ=="
+ },
"is-ssh": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
@@ -18771,6 +18776,11 @@
}
}
},
+ "memoize-one": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz",
+ "integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA=="
+ },
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
diff --git a/packages/@uppy/dashboard/package.json b/packages/@uppy/dashboard/package.json
index aa3522529..3118bbadf 100644
--- a/packages/@uppy/dashboard/package.json
+++ b/packages/@uppy/dashboard/package.json
@@ -29,8 +29,10 @@
"@uppy/utils": "1.1.0",
"classnames": "^2.2.6",
"cuid": "^2.1.1",
+ "is-shallow-equal": "^1.0.1",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
+ "memoize-one": "^5.0.4",
"preact": "8.2.9",
"preact-css-transition-group": "^1.3.0",
"prettier-bytes": "^1.0.4",
diff --git a/packages/@uppy/dashboard/src/components/FileItem/index.js b/packages/@uppy/dashboard/src/components/FileItem/index.js
index 71d984f40..05456337a 100644
--- a/packages/@uppy/dashboard/src/components/FileItem/index.js
+++ b/packages/@uppy/dashboard/src/components/FileItem/index.js
@@ -1,12 +1,12 @@
const { h } = require('preact')
const classNames = require('classnames')
-
+const pure = require('../../utils/pure')
const FilePreviewAndLink = require('./FilePreviewAndLink')
const FileProgress = require('./FileProgress')
const FileInfo = require('./FileInfo')
const Buttons = require('./Buttons')
-module.exports = function FileItem (props) {
+module.exports = pure(function FileItem (props) {
const file = props.file
const isProcessing = file.progress.preprocess || file.progress.postprocess
@@ -72,4 +72,4 @@ module.exports = function FileItem (props) {
)
-}
+})
diff --git a/packages/@uppy/dashboard/src/components/FileList.js b/packages/@uppy/dashboard/src/components/FileList.js
index 85324b2f0..ac16ed6f7 100644
--- a/packages/@uppy/dashboard/src/components/FileList.js
+++ b/packages/@uppy/dashboard/src/components/FileList.js
@@ -9,6 +9,32 @@ module.exports = (props) => {
{ 'uppy-Dashboard-files--noFiles': noFiles }
)
+ const fileProps = {
+ // FIXME This is confusing, it's actually the Dashboard's plugin ID
+ id: props.id,
+ error: props.error,
+ // TODO move this to context
+ i18n: props.i18n,
+ log: props.log,
+ info: props.info,
+ // features
+ acquirers: props.acquirers,
+ resumableUploads: props.resumableUploads,
+ individualCancellation: props.individualCancellation,
+ // visual options
+ hideRetryButton: props.hideRetryButton,
+ hidePauseResumeCancelButtons: props.hidePauseResumeCancelButtons,
+ showLinkToFileUploadResult: props.showLinkToFileUploadResult,
+ isWide: props.isWide,
+ metaFields: props.metaFields,
+ // callbacks
+ retryUpload: props.retryUpload,
+ pauseUpload: props.pauseUpload,
+ cancelUpload: props.cancelUpload,
+ toggleFileCard: props.toggleFileCard,
+ removeFile: props.removeFile
+ }
+
return (
{
tabindex="-1">
{Object.keys(props.files).map((fileID) => (
))}
diff --git a/packages/@uppy/dashboard/src/index.js b/packages/@uppy/dashboard/src/index.js
index c896438fe..a89b4286f 100644
--- a/packages/@uppy/dashboard/src/index.js
+++ b/packages/@uppy/dashboard/src/index.js
@@ -12,6 +12,7 @@ const cuid = require('cuid')
const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
const { defaultPickerIcon } = require('./components/icons')
const createSuperFocus = require('./utils/createSuperFocus')
+const memoize = require('memoize-one').default || require('memoize-one')
const TAB_KEY = 9
const ESC_KEY = 27
@@ -659,6 +660,52 @@ module.exports = class Dashboard extends Plugin {
this.superFocusOnEachUpdate()
}
+ startUpload = (ev) => {
+ this.uppy.upload().catch((err) => {
+ // Log error.
+ this.uppy.log(err.stack || err.message || err)
+ })
+ }
+
+ cancelUpload = (fileID) => {
+ this.uppy.removeFile(fileID)
+ }
+
+ saveFileCard = (meta, fileID) => {
+ this.uppy.setFileMeta(fileID, meta)
+ this.toggleFileCard()
+ }
+
+ _attachRenderFunctionToTarget = (target) => {
+ const plugin = this.uppy.getPlugin(target.id)
+ return {
+ ...target,
+ icon: plugin.icon || this.opts.defaultPickerIcon,
+ render: plugin.render
+ }
+ }
+
+ _isTargetSupported = (target) => {
+ const plugin = this.uppy.getPlugin(target.id)
+ // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
+ if (typeof plugin.isSupported !== 'function') {
+ return true
+ }
+ return plugin.isSupported()
+ }
+
+ _getAcquirers = memoize((targets) => {
+ return targets
+ .filter(target => target.type === 'acquirer' && this._isTargetSupported(target))
+ .map(this._attachRenderFunctionToTarget)
+ })
+
+ _getProgressIndicators = memoize((targets) => {
+ return targets
+ .filter(target => target.type === 'progressindicator')
+ .map(this._attachRenderFunctionToTarget)
+ })
+
render (state) {
const pluginState = this.getPluginState()
const { files, capabilities, allowNewUpload } = state
@@ -710,46 +757,8 @@ module.exports = class Dashboard extends Plugin {
const isAllPaused = inProgressFiles.length !== 0 &&
pausedFiles.length === inProgressFiles.length
- const attachRenderFunctionToTarget = (target) => {
- const plugin = this.uppy.getPlugin(target.id)
- return Object.assign({}, target, {
- icon: plugin.icon || this.opts.defaultPickerIcon,
- render: plugin.render
- })
- }
-
- const isSupported = (target) => {
- const plugin = this.uppy.getPlugin(target.id)
- // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
- if (typeof plugin.isSupported !== 'function') {
- return true
- }
- return plugin.isSupported()
- }
-
- const acquirers = pluginState.targets
- .filter(target => target.type === 'acquirer' && isSupported(target))
- .map(attachRenderFunctionToTarget)
-
- const progressindicators = pluginState.targets
- .filter(target => target.type === 'progressindicator')
- .map(attachRenderFunctionToTarget)
-
- const startUpload = (ev) => {
- this.uppy.upload().catch((err) => {
- // Log error.
- this.uppy.log(err.stack || err.message || err)
- })
- }
-
- const cancelUpload = (fileID) => {
- this.uppy.removeFile(fileID)
- }
-
- const saveFileCard = (meta, fileID) => {
- this.uppy.setFileMeta(fileID, meta)
- this.toggleFileCard()
- }
+ const acquirers = this._getAcquirers(pluginState.targets)
+ const progressindicators = this._getProgressIndicators(pluginState.targets)
return DashboardUI({
state,
@@ -794,16 +803,16 @@ module.exports = class Dashboard extends Plugin {
metaFields: pluginState.metaFields,
resumableUploads: capabilities.resumableUploads || false,
individualCancellation: capabilities.individualCancellation,
- startUpload,
+ startUpload: this.startUpload,
pauseUpload: this.uppy.pauseResume,
retryUpload: this.uppy.retryUpload,
- cancelUpload,
+ cancelUpload: this.cancelUpload,
cancelAll: this.uppy.cancelAll,
fileCardFor: pluginState.fileCardFor,
toggleFileCard: this.toggleFileCard,
toggleAddFilesPanel: this.toggleAddFilesPanel,
showAddFilesPanel: pluginState.showAddFilesPanel,
- saveFileCard,
+ saveFileCard: this.saveFileCard,
width: this.opts.width,
height: this.opts.height,
showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
diff --git a/packages/@uppy/dashboard/src/utils/pure.js b/packages/@uppy/dashboard/src/utils/pure.js
new file mode 100644
index 000000000..577f507f5
--- /dev/null
+++ b/packages/@uppy/dashboard/src/utils/pure.js
@@ -0,0 +1,21 @@
+const shallowEqual = require('is-shallow-equal')
+const { h, Component } = require('preact')
+
+/**
+ * Higher order component that doesn't rerender an element if its props didn't change.
+ */
+module.exports = function pure (Inner) {
+ return class Pure extends Component {
+ shouldComponentUpdate (nextProps) {
+ return !shallowEqual(this.props, nextProps)
+ }
+
+ render () {
+ // we have to clone this or Preact mutates it:
+ // https://github.com/preactjs/preact/issues/836
+ // TODO can be removed if we upgrade to Preact X
+ const props = { ...this.props }
+ return
+ }
+ }
+}