Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Required meta fields UI #3285

Merged
merged 7 commits into from Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 31 additions & 12 deletions packages/@uppy/core/src/Uppy.js
Expand Up @@ -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)
Expand Down Expand Up @@ -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 })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we still logging required fields in informer even with the new UI?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, showInformer: 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')}`)
}
}

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions packages/@uppy/core/src/__snapshots__/Uppy.test.js.snap
Expand Up @@ -24,6 +24,7 @@ Object {
"name": "foo.jpg",
"type": "image/jpeg",
},
"missingRequiredMetaFields": Array [],
"name": "foo.jpg",
"preview": undefined,
"progress": Object {
Expand All @@ -47,6 +48,7 @@ Object {
"name": "bar.jpg",
"type": "image/jpeg",
},
"missingRequiredMetaFields": Array [],
"name": "bar.jpg",
"preview": undefined,
"progress": Object {
Expand Down
33 changes: 20 additions & 13 deletions 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) => {
Expand Down Expand Up @@ -54,22 +55,22 @@ const renderAuthor = (props) => {
}

const renderFileSize = (props) => props.file.size && (
<div className="uppy-Dashboard-Item-statusSize">
{prettierBytes(props.file.size)}
</div>
<div className="uppy-Dashboard-Item-statusSize">
{prettierBytes(props.file.size)}
</div>
)

const ReSelectButton = (props) => props.file.isGhost && (
<span>
{' \u2022 '}
<button
className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-reSelect"
type="button"
onClick={props.toggleAddFilesPanel}
>
{props.i18n('reSelect')}
</button>
</span>
<span>
{' \u2022 '}
<button
className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-reSelect"
type="button"
onClick={props.toggleAddFilesPanel}
>
{props.i18n('reSelect')}
</button>
</span>
)

const ErrorButton = ({ file, onClick }) => {
Expand Down Expand Up @@ -107,6 +108,12 @@ module.exports = function FileInfo (props) {
onClick={() => alert(props.file.error)} // TODO: move to a custom alert implementation
/>
</div>
<MetaErrorMessage
file={props.file}
i18n={props.i18n}
toggleFileCard={props.toggleFileCard}
metaFields={props.metaFields}
/>
</div>
)
}
Expand Up @@ -46,6 +46,7 @@
display: inline-block;
text-transform: uppercase;
vertical-align: bottom;
margin-bottom: 5px;
}

.uppy-Dashboard-Item-reSelect {
Expand All @@ -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;
}
}

@@ -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) {
Expand All @@ -19,11 +20,17 @@ module.exports = function FilePreviewAndLink (props) {
target="_blank"
aria-label={props.file.meta.name}
>
<span hidden>props.file.meta.name</span>
<span hidden>{props.file.meta.name}</span>
</a>
)
}
<FilePreview file={props.file} />
<MetaErrorMessage
file={props.file}
i18n={props.i18n}
toggleFileCard={props.toggleFileCard}
metaFields={props.metaFields}
/>
</div>
)
}
@@ -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 (
<div className="uppy-Dashboard-Item-errorMessage">
{i18n('missingRequiredMetaFields', {
smart_count: missingRequiredMetaFields.length,
fields: metaFieldsString,
})}
{' '}
<button
type="button"
class="uppy-u-reset uppy-Dashboard-Item-errorMessageBtn"
onClick={() => toggleFileCard(true, file.id)}
>
{i18n('editFile')}
</button>
</div>
)
}
5 changes: 5 additions & 0 deletions packages/@uppy/dashboard/src/components/FileItem/index.js
Expand Up @@ -76,6 +76,9 @@ module.exports = class FileItem extends Component {
<FilePreviewAndLink
file={file}
showLinkToFileUploadResult={this.props.showLinkToFileUploadResult}
i18n={this.props.i18n}
toggleFileCard={this.props.toggleFileCard}
metaFields={this.props.metaFields}
/>
<FileProgress
uppy={this.props.uppy}
Expand All @@ -101,6 +104,8 @@ module.exports = class FileItem extends Component {
containerWidth={this.props.containerWidth}
i18n={this.props.i18n}
toggleAddFilesPanel={this.props.toggleAddFilesPanel}
toggleFileCard={this.props.toggleFileCard}
metaFields={this.props.metaFields}
/>
<Buttons
file={file}
Expand Down
4 changes: 4 additions & 0 deletions packages/@uppy/dashboard/src/index.js
Expand Up @@ -105,6 +105,10 @@ module.exports = class Dashboard extends UIPlugin {
sessionRestored: 'Session restored',
reSelect: 'Re-select',
poweredBy: 'Powered by %{uppy}',
missingRequiredMetaFields: {
0: 'Missing required meta field: %{fields}.',
1: 'Missing required meta fields: %{fields}.',
},
},
}

Expand Down
4 changes: 4 additions & 0 deletions packages/@uppy/locales/src/en_US.js
Expand Up @@ -81,6 +81,10 @@ en_US.strings = {
micDisabled: 'Microphone access denied by user',
missingRequiredMetaField: 'Missing required meta fields',
missingRequiredMetaFieldOnFile: 'Missing required meta fields in %{fileName}',
missingRequiredMetaFields: {
'0': 'Missing required meta field: %{fields}.',
'1': 'Missing required meta fields: %{fields}.',
},
myDevice: 'My Device',
noCameraDescription: 'In order to take pictures or record video, please connect a camera device',
noCameraTitle: 'Camera Not Available',
Expand Down