diff --git a/examples/bundled/index.js b/examples/bundled/index.js
index 6df562565..6fac992ad 100644
--- a/examples/bundled/index.js
+++ b/examples/bundled/index.js
@@ -32,6 +32,7 @@ const uppy = new Uppy({
showProgressDetails: true,
proudlyDisplayPoweredByUppy: true,
note: '2 files, images and video only',
+ restrictions: { requiredMetaFields: ['caption'] },
})
.use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' })
.use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' })
diff --git a/examples/dev/Dashboard.js b/examples/dev/Dashboard.js
index 8aa98ebc7..071e19a9d 100644
--- a/examples/dev/Dashboard.js
+++ b/examples/dev/Dashboard.js
@@ -58,6 +58,7 @@ module.exports = () => {
username: 'John',
license: 'Creative Commons',
},
+ restrictions: { requiredMetaFields: ['caption'] },
})
.use(Dashboard, {
trigger: '#pick-files',
diff --git a/packages/@uppy/core/src/index.js b/packages/@uppy/core/src/index.js
index 6375ee2b7..60e3c3c9f 100644
--- a/packages/@uppy/core/src/index.js
+++ b/packages/@uppy/core/src/index.js
@@ -21,6 +21,21 @@ class RestrictionError extends Error {
this.isRestriction = true
}
}
+if (typeof window.AggregateError === 'undefined') {
+ // eslint-disable-next-line no-global-assign
+ AggregateError = class AggregateError extends Error {
+ constructor (message, errors) {
+ super(message)
+ this.errors = errors
+ }
+ }
+}
+class AggregateRestrictionError extends AggregateError {
+ constructor (...args) {
+ super(...args)
+ this.isRestriction = true
+ }
+}
/**
* Uppy Core module.
@@ -51,6 +66,8 @@ class Uppy {
0: 'You have to select at least %{smart_count} file',
1: 'You have to select at least %{smart_count} files',
},
+ missingRequiredMetaField: 'Missing required meta fields',
+ missingRequiredMetaFieldOnFile: 'Missing required meta fields in %{fileName}',
// The default `exceedsSize2` string only combines the `exceedsSize` string (%{backwardsCompat}) with the size.
// Locales can override `exceedsSize2` to specify a different word order. This is for backwards compat with
// Uppy 1.9.x and below which did a naive concatenation of `exceedsSize2 + size` instead of using a locale-specific
@@ -108,6 +125,7 @@ class Uppy {
maxNumberOfFiles: null,
minNumberOfFiles: null,
allowedFileTypes: null,
+ requiredMetaFields: [],
},
meta: {},
onBeforeFileAdded: (currentFile) => currentFile,
@@ -542,6 +560,33 @@ class Uppy {
}
}
+ /**
+ * Check if requiredMetaField restriction is met before uploading.
+ *
+ * @private
+ */
+ checkRequiredMetaFields (files) {
+ const { requiredMetaFields } = this.opts.restrictions
+ const { hasOwnProperty } = Object.prototype.hasOwnProperty
+
+ const errors = []
+ const fileIDs = Object.keys(files)
+ for (let i = 0; i < fileIDs.length; i++) {
+ const file = this.getFile(fileIDs[i])
+ for (let i = 0; i < requiredMetaFields.length; i++) {
+ if (!hasOwnProperty.call(file.meta, requiredMetaFields[i])) {
+ const err = new RestrictionError(`${this.i18n('missingRequiredMetaFieldOnFile', { fileName: file.name })}`)
+ errors.push(err)
+ this.showOrLogErrorAndThrow(err, { file, throwErr: false })
+ }
+ }
+ }
+
+ if (errors.length) {
+ throw new AggregateRestrictionError(`${this.i18n('missingRequiredMetaField')}`, errors)
+ }
+ }
+
/**
* Logs an error, sets Informer message, then throws the error.
* Emits a 'restriction-failed' event if it’s a restriction error
@@ -1688,7 +1733,10 @@ class Uppy {
}
return Promise.resolve()
- .then(() => this.checkMinNumberOfFiles(files))
+ .then(() => {
+ this.checkMinNumberOfFiles(files)
+ this.checkRequiredMetaFields(files)
+ })
.catch((err) => {
this.showOrLogErrorAndThrow(err)
})
diff --git a/packages/@uppy/dashboard/src/components/FileCard/index.js b/packages/@uppy/dashboard/src/components/FileCard/index.js
index 7fd14931c..aa0a75412 100644
--- a/packages/@uppy/dashboard/src/components/FileCard/index.js
+++ b/packages/@uppy/dashboard/src/components/FileCard/index.js
@@ -1,5 +1,6 @@
const { h, Component } = require('preact')
const classNames = require('classnames')
+const cuid = require('cuid')
const getFileTypeIcon = require('../../utils/getFileTypeIcon')
const ignoreEvent = require('../../utils/ignoreEvent.js')
const FilePreview = require('../FilePreview')
@@ -19,15 +20,8 @@ class FileCard extends Component {
this.state = {
formState: storedMetaData,
}
- }
- saveOnEnter = (ev) => {
- if (ev.keyCode === 13) {
- ev.stopPropagation()
- ev.preventDefault()
- const file = this.props.files[this.props.fileCardFor]
- this.props.saveFileCard(this.state.formState, file.id)
- }
+ this.form.id = cuid()
}
updateMeta = (newVal, name) => {
@@ -39,7 +33,10 @@ class FileCard extends Component {
})
}
- handleSave = () => {
+ form = document.createElement('form');
+
+ handleSave = (e) => {
+ e.preventDefault()
const fileID = this.props.fileCardFor
this.props.saveFileCard(this.state.formState, fileID)
}
@@ -48,6 +45,17 @@ class FileCard extends Component {
this.props.toggleFileCard(false)
}
+ // TODO(aduh95): move this to `UNSAFE_componentWillMount` when updating to Preact X+.
+ componentWillMount () {
+ this.form.addEventListener('submit', this.handleSave)
+ document.body.appendChild(this.form)
+ }
+
+ componentWillUnmount () {
+ this.form.removeEventListener('submit', this.handleSave)
+ document.body.removeChild(this.form)
+ }
+
renderMetaFields = () => {
const metaFields = this.getMetaFields() || []
const fieldCSSClasses = {
@@ -56,6 +64,7 @@ class FileCard extends Component {
return metaFields.map((field) => {
const id = `uppy-Dashboard-FileCard-input-${field.id}`
+ const required = this.props.requiredMetaFields.includes(field.id)
return (