diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 2a687a61d3..91d273428f 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -26,12 +26,13 @@ class RestrictionError extends Error { if (typeof AggregateError === 'undefined') { // eslint-disable-next-line no-global-assign globalThis.AggregateError = class AggregateError extends Error { - constructor (message, errors) { + constructor (errors, message) { super(message) this.errors = errors } } } + class AggregateRestrictionError extends AggregateError { constructor (...args) { super(...args) @@ -557,27 +558,39 @@ class Uppy { } /** - * Check if requiredMetaField restriction is met before uploading. + * Check if requiredMetaField restriction is met for a specific file. * */ - #checkRequiredMetaFields (files) { + #checkRequiredMetaFieldsOnFile (file) { const { requiredMetaFields } = this.opts.restrictions const { hasOwnProperty } = Object.prototype const errors = [] - for (const fileID of Object.keys(files)) { - const file = this.getFile(fileID) - for (let i = 0; i < requiredMetaFields.length; i++) { - if (!hasOwnProperty.call(file.meta, requiredMetaFields[i]) || file.meta[requiredMetaFields[i]] === '') { - const err = new RestrictionError(`${this.i18n('missingRequiredMetaFieldOnFile', { fileName: file.name })}`) - errors.push(err) - this.#showOrLogErrorAndThrow(err, { file, showInformer: false, throwErr: false }) - } + const missingFields = [] + for (let i = 0; i < requiredMetaFields.length; i++) { + if (!hasOwnProperty.call(file.meta, requiredMetaFields[i]) || file.meta[requiredMetaFields[i]] === '') { + const err = new RestrictionError(`${this.i18n('missingRequiredMetaFieldOnFile', { fileName: file.name })}`) + errors.push(err) + missingFields.push(requiredMetaFields[i]) + this.#showOrLogErrorAndThrow(err, { file, showInformer: false, throwErr: false }) } } + this.setFileState(file.id, { missingRequiredMetaFields: missingFields }) + return errors + } + + /** + * Check if requiredMetaField restriction is met before uploading. + * + */ + #checkRequiredMetaFields (files) { + const errors = Object.keys(files).flatMap((fileID) => { + const file = this.getFile(fileID) + return this.#checkRequiredMetaFieldsOnFile(file) + }) if (errors.length) { - throw new AggregateRestrictionError(`${this.i18n('missingRequiredMetaField')}`, errors) + throw new AggregateRestrictionError(errors, `${this.i18n('missingRequiredMetaField')}`) } } @@ -1277,6 +1290,12 @@ class Uppy { this.calculateTotalProgress() }) + this.on('dashboard:file-edit-complete', (file) => { + if (file) { + this.#checkRequiredMetaFieldsOnFile(file) + } + }) + // show informer if offline if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('online', this.#updateOnlineStatus) diff --git a/packages/@uppy/core/src/__snapshots__/Uppy.test.js.snap b/packages/@uppy/core/src/__snapshots__/Uppy.test.js.snap index 1762b1101e..c0d9282118 100644 --- a/packages/@uppy/core/src/__snapshots__/Uppy.test.js.snap +++ b/packages/@uppy/core/src/__snapshots__/Uppy.test.js.snap @@ -24,6 +24,7 @@ Object { "name": "foo.jpg", "type": "image/jpeg", }, + "missingRequiredMetaFields": Array [], "name": "foo.jpg", "preview": undefined, "progress": Object { @@ -47,6 +48,7 @@ Object { "name": "bar.jpg", "type": "image/jpeg", }, + "missingRequiredMetaFields": Array [], "name": "bar.jpg", "preview": undefined, "progress": Object { diff --git a/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js b/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js index 843e1b3ca9..c9c974b286 100644 --- a/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js +++ b/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js @@ -1,5 +1,6 @@ const { h, Fragment } = require('preact') const prettierBytes = require('@transloadit/prettier-bytes') +const MetaErrorMessage = require('../MetaErrorMessage') const truncateString = require('@uppy/utils/lib/truncateString') const renderFileName = (props) => { @@ -54,22 +55,22 @@ const renderAuthor = (props) => { } const renderFileSize = (props) => props.file.size && ( -
- {prettierBytes(props.file.size)} -
+
+ {prettierBytes(props.file.size)} +
) const ReSelectButton = (props) => props.file.isGhost && ( - - {' \u2022 '} - - + + {' \u2022 '} + + ) const ErrorButton = ({ file, onClick }) => { @@ -107,6 +108,12 @@ module.exports = function FileInfo (props) { onClick={() => alert(props.file.error)} // TODO: move to a custom alert implementation /> + ) } diff --git a/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss b/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss index b2a10f4a51..4f7dab37fa 100644 --- a/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss +++ b/packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss @@ -46,6 +46,7 @@ display: inline-block; text-transform: uppercase; vertical-align: bottom; + margin-bottom: 5px; } .uppy-Dashboard-Item-reSelect { @@ -54,5 +55,49 @@ font-size: inherit; font-family: inherit; } -// ...uppy-Dashboard-Item-status| -// ...uppy-Dashboard-Item-fileInfo| + +.uppy-Dashboard-Item-errorMessage { + font-size: 11px; + font-weight: 500; + line-height: 1.3; + color: darken($red, 15%); + background-color: lighten($red, 45%); + padding: 5px 6px; +} + +.uppy-Dashboard-Item-errorMessageBtn { + text-decoration: underline; + cursor: pointer; + font-weight: 500; +} + +// Error message desktop / large screen +.uppy-Dashboard-Item-preview .uppy-Dashboard-Item-errorMessage { + display: none; + + .uppy-size--md & { + display: block; + border-top: 1px solid lighten($red, 35%); + padding: 6px 8px; + line-height: 1.4; + position: absolute; + bottom: 0; + left: 0; + right: 0; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } +} + +// Error message mobile / small screen +.uppy-Dashboard-Item-fileInfo .uppy-Dashboard-Item-errorMessage { + display: inline-block; + position: static; + border: 1px solid lighten($red, 35%); + border-radius: 3px; + + .uppy-size--md & { + display: none; + } +} + diff --git a/packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js b/packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js index 2b99484f35..b0b95a4ef3 100644 --- a/packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js +++ b/packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js @@ -1,5 +1,6 @@ const { h } = require('preact') const FilePreview = require('../../FilePreview') +const MetaErrorMessage = require('../MetaErrorMessage') const getFileTypeIcon = require('../../../utils/getFileTypeIcon') module.exports = function FilePreviewAndLink (props) { @@ -19,11 +20,17 @@ module.exports = function FilePreviewAndLink (props) { target="_blank" aria-label={props.file.meta.name} > - + ) } + ) } diff --git a/packages/@uppy/dashboard/src/components/FileItem/MetaErrorMessage.js b/packages/@uppy/dashboard/src/components/FileItem/MetaErrorMessage.js new file mode 100644 index 0000000000..00d2bcc2e1 --- /dev/null +++ b/packages/@uppy/dashboard/src/components/FileItem/MetaErrorMessage.js @@ -0,0 +1,35 @@ +const { h } = require('preact') + +const metaFieldIdToName = (metaFieldId, metaFields) => { + const field = metaFields.filter(f => f.id === metaFieldId) + return field[0].name +} + +module.exports = function renderMissingMetaFieldsError (props) { + const { file, toggleFileCard, i18n, metaFields } = props + const { missingRequiredMetaFields } = file + if (!missingRequiredMetaFields?.length) { + return null + } + + const metaFieldsString = missingRequiredMetaFields.map(missingMetaField => ( + metaFieldIdToName(missingMetaField, metaFields) + )).join(', ') + + return ( +
+ {i18n('missingRequiredMetaFields', { + smart_count: missingRequiredMetaFields.length, + fields: metaFieldsString, + })} + {' '} + +
+ ) +} diff --git a/packages/@uppy/dashboard/src/components/FileItem/index.js b/packages/@uppy/dashboard/src/components/FileItem/index.js index 3eed378abe..3a9c57b54e 100644 --- a/packages/@uppy/dashboard/src/components/FileItem/index.js +++ b/packages/@uppy/dashboard/src/components/FileItem/index.js @@ -76,6 +76,9 @@ module.exports = class FileItem extends Component {