Ver código fonte

Required meta fields UI (#3285)

* add checkRequiredMetaFieldsOnFile for checking single file's meta fields after editing

* add MetaErrorMessage component, mobile and desktop styles

* use AggregateRestrictionError again

* metaFieldIdToName — use names of meta fields instead of id

* Update MetaErrorMessage.js

* desktop style tweaks

* update jest snapshot
Artur Paikin 3 anos atrás
pai
commit
6e4884f559

+ 31 - 12
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)

+ 2 - 0
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 {

+ 20 - 13
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 && (
-<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 }) => {
@@ -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>
   )
 }

+ 47 - 2
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;
+  }
+}
+

+ 8 - 1
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}
           >
-            <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>
   )
 }

+ 35 - 0
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 (
+    <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 - 0
packages/@uppy/dashboard/src/components/FileItem/index.js

@@ -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}
@@ -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}

+ 4 - 0
packages/@uppy/dashboard/src/index.js

@@ -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}.',
+        },
       },
     }
 

+ 4 - 0
packages/@uppy/locales/src/en_US.js

@@ -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',