Pārlūkot izejas kodu

Merge pull request #682 from transloadit/improvements/ui-tweaks

[WIP] UI improvements
Artur Paikin 7 gadi atpakaļ
vecāks
revīzija
9b0f9829c3
45 mainītis faili ar 1273 papildinājumiem un 961 dzēšanām
  1. 8 40
      examples/bundled-example/main.js
  2. 12 14
      src/core/Core.js
  3. 2 1
      src/core/Plugin.js
  4. 4 5
      src/index.js
  5. 11 3
      src/plugins/Dashboard/Dashboard.js
  6. 28 32
      src/plugins/Dashboard/FileCard.js
  7. 28 34
      src/plugins/Dashboard/FileItem.js
  8. 8 7
      src/plugins/Dashboard/FileList.js
  9. 21 0
      src/plugins/Dashboard/FilePreview.js
  10. 2 25
      src/plugins/Dashboard/icons.js
  11. 27 19
      src/plugins/Dashboard/index.js
  12. 2 2
      src/plugins/Informer.js
  13. 17 2
      src/plugins/Instagram/index.js
  14. 97 100
      src/plugins/StatusBar/StatusBar.js
  15. 8 0
      src/plugins/StatusBar/StatusBarStates.js
  16. 54 14
      src/plugins/StatusBar/index.js
  17. 12 3
      src/plugins/Url/UrlUI.js
  18. 74 43
      src/plugins/Url/index.js
  19. 2 3
      src/plugins/Webcam/CameraIcon.js
  20. 1 0
      src/plugins/Webcam/PermissionsScreen.js
  21. 13 11
      src/plugins/Webcam/RecordButton.js
  22. 0 7
      src/plugins/Webcam/RecordStartIcon.js
  23. 0 7
      src/plugins/Webcam/RecordStopIcon.js
  24. 4 4
      src/plugins/Webcam/SnapshotButton.js
  25. 0 10
      src/plugins/Webcam/WebcamIcon.js
  26. 21 17
      src/plugins/Webcam/index.js
  27. 194 33
      src/scss/_common.scss
  28. 205 246
      src/scss/_dashboard.scss
  29. 8 8
      src/scss/_informer.scss
  30. 136 132
      src/scss/_provider.scss
  31. 69 46
      src/scss/_statusbar.scss
  32. 14 15
      src/scss/_url.scss
  33. 4 2
      src/scss/_variables.scss
  34. 56 15
      src/scss/_webcam.scss
  35. 1 1
      src/server/RequestClient.js
  36. 3 2
      src/views/ProviderView/AuthView.js
  37. 0 5
      src/views/ProviderView/Breadcrumb.js
  38. 6 1
      src/views/ProviderView/Breadcrumbs.js
  39. 15 16
      src/views/ProviderView/Browser.js
  40. 29 15
      src/views/ProviderView/Filter.js
  41. 3 6
      src/views/ProviderView/Item.js
  42. 7 3
      src/views/ProviderView/ItemList.js
  43. 12 3
      src/views/ProviderView/index.js
  44. 40 7
      website/src/docs/dashboard.md
  45. 15 2
      website/src/docs/statusbar.md

+ 8 - 40
examples/bundled-example/main.js

@@ -6,15 +6,6 @@ const Webcam = require('../../src/plugins/Webcam')
 const Tus = require('../../src/plugins/Tus')
 const Form = require('../../src/plugins/Form')
 
-// const Dropbox = require('../../src/plugins/Dropbox')
-// const XHRUpload = require('../../src/plugins/XHRUpload')
-// const FileInput = require('../../src/plugins/FileInput')
-// const MetaData = require('../../src/plugins/MetaData')
-// const Informer = require('../../src/plugins/Informer')
-// const StatusBar = require('../../src/plugins/StatusBar')
-// const DragDrop = require('../../src/plugins/DragDrop')
-// const GoldenRetriever = require('../../src/plugins/GoldenRetriever')
-
 const TUS_ENDPOINT = 'https://master.tus.io/files/'
 
 const uppy = Uppy({
@@ -24,41 +15,18 @@ const uppy = Uppy({
     username: 'John',
     license: 'Creative Commons'
   }
-  // restrictions: {
-  //   maxFileSize: 300000,
-  //   maxNumberOfFiles: 10,
-  //   minNumberOfFiles: 2,
-  //   allowedFileTypes: ['image/*', 'video/*']
-  // }
-  // onBeforeFileAdded: (currentFile, files) => {
-  //   if (currentFile.name === 'pitercss-IMG_0616.jpg') {
-  //     return Promise.resolve()
-  //   }
-  //   return Promise.reject('this is not the file I was looking for')
-  // },
-  // onBeforeUpload: (files) => {
-  //   if (Object.keys(files).length < 2) {
-  //     return Promise.reject('too few files')
-  //   }
-  //   return Promise.resolve()
-  // }
 })
   .use(Dashboard, {
     trigger: '#pick-files',
+    // inline: true,
+    // target: 'body',
     metaFields: [
       { id: 'license', name: 'License', placeholder: 'specify license' },
       { id: 'caption', name: 'Caption', placeholder: 'add caption' }
-    ]
-    // target: '.uppy-target',
-    // inline: true,
-    // maxWidth: 500,
-    // maxHeight: 350,
-    // replaceTargetContent: true,
-    // closeModalOnClickOutside: false,
-    // note: 'Images and video only, 300kb or less',
-    // locale: {
-    //   strings: { browse: 'browse' }
-    // }
+    ],
+    showProgressDetails: true,
+    proudlyDisplayPoweredByUppy: true,
+    note: '2 files, images and video only'
   })
   .use(GoogleDrive, { target: Dashboard, host: 'http://localhost:3020' })
   .use(Instagram, { target: Dashboard, host: 'http://localhost:3020' })
@@ -89,5 +57,5 @@ if ('serviceWorker' in navigator) {
     })
 }
 
-// var modalTrigger = document.querySelector('#uppyModalOpener')
-// if (modalTrigger) modalTrigger.click()
+var modalTrigger = document.querySelector('#pick-files')
+if (modalTrigger) modalTrigger.click()

+ 12 - 14
src/core/Core.js

@@ -28,7 +28,10 @@ class Uppy {
         },
         exceedsSize: 'This file exceeds maximum allowed size of',
         youCanOnlyUploadFileTypes: 'You can only upload:',
-        uppyServerError: 'Connection with Uppy Server failed'
+        uppyServerError: 'Connection with Uppy Server failed',
+        failedToUpload: 'Failed to upload',
+        noInternetConnection: 'No Internet connection',
+        connectedToInternet: 'Connected to the Intenet!'
       }
     }
 
@@ -447,20 +450,15 @@ class Uppy {
   }
 
   pauseResume (fileID) {
-    const updatedFiles = Object.assign({}, this.getState().files)
-
-    if (updatedFiles[fileID].uploadComplete) return
+    if (this.getFile(fileID).uploadComplete) return
 
-    const wasPaused = updatedFiles[fileID].isPaused || false
+    const wasPaused = this.getFile(fileID).isPaused || false
     const isPaused = !wasPaused
 
-    const updatedFile = Object.assign({}, updatedFiles[fileID], {
+    this.setFileState(fileID, {
       isPaused: isPaused
     })
 
-    updatedFiles[fileID] = updatedFile
-    this.setState({files: updatedFiles})
-
     this.emit('upload-pause', fileID, isPaused)
 
     return isPaused
@@ -627,7 +625,7 @@ class Uppy {
       this.setFileState(file.id, { error: error.message })
       this.setState({ error: error.message })
 
-      let message = `Failed to upload ${file.name}`
+      let message = `${this.i18n('failedToUpload')} ${file.name}`
       if (typeof error === 'object' && error.message) {
         message = { message: message, details: error.message }
       }
@@ -644,13 +642,13 @@ class Uppy {
         return
       }
       this.setFileState(file.id, {
-        progress: Object.assign({}, this.getFile(file.id), {
+        progress: {
           uploadStarted: Date.now(),
           uploadComplete: false,
           percentage: 0,
           bytesUploaded: 0,
           bytesTotal: file.size
-        })
+        }
       })
     })
 
@@ -750,13 +748,13 @@ class Uppy {
         : true
     if (!online) {
       this.emit('is-offline')
-      this.info('No internet connection', 'error', 0)
+      this.info(this.i18n('noInternetConnection'), 'error', 0)
       this.wasOffline = true
     } else {
       this.emit('is-online')
       if (this.wasOffline) {
         this.emit('back-online')
-        this.info('Connected!', 'success', 3000)
+        this.info(this.i18n('connectedToInternet'), 'success', 3000)
         this.wasOffline = false
       }
     }

+ 2 - 1
src/core/Plugin.js

@@ -58,6 +58,8 @@ module.exports = class Plugin {
     const targetElement = findDOMElement(target)
 
     if (targetElement) {
+      this.isTargetDOMEl = true
+
       this.updateUI = (state) => {
         this.el = preact.render(this.render(state), targetElement, this.el)
       }
@@ -113,7 +115,6 @@ module.exports = class Plugin {
     if (this.el && this.el.parentNode) {
       this.el.parentNode.removeChild(this.el)
     }
-    // this.target = null
   }
 
   install () {

+ 4 - 5
src/index.js

@@ -1,12 +1,10 @@
 const Core = require('./core')
 
 // Communication with Uppy Server
-const Server = require('./server')
+const server = require('./server')
 
 // Reusable views
-const Views = require('./views')
-
-const lib = { Views, Server }
+const views = require('./views')
 
 // Parent
 const Plugin = require('./core/Plugin')
@@ -40,7 +38,8 @@ const ReduxDevTools = require('./plugins/ReduxDevTools')
 
 module.exports = {
   Core,
-  lib,
+  views,
+  server,
   Plugin,
   StatusBar,
   ProgressBar,

+ 11 - 3
src/plugins/Dashboard/Dashboard.js

@@ -3,7 +3,6 @@ const Tabs = require('./Tabs')
 const FileCard = require('./FileCard')
 const classNames = require('classnames')
 const { isTouchDevice } = require('../../core/Utils')
-const { closeIcon } = require('./icons')
 const { h } = require('preact')
 
 // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
@@ -23,9 +22,15 @@ const renderInnerPanel = (props) => {
   </div>
 }
 
+const poweredByUppy = (props) => {
+  return <a href="https://uppy.io" target="_blank" class="uppy-Dashboard-poweredBy">Powered by <svg aria-hidden="true" class="uppy-Dashboard-poweredByIcon" width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="nonzero" d="M8.57 7.554v4.149H3.424V7.554H0L6 0l6 7.554H8.57z" />
+  </svg><span class="uppy-Dashboard-poweredByUppy">Uppy</span></a>
+}
+
 module.exports = function Dashboard (props) {
   const dashboardClassName = classNames(
-    'uppy',
+    { 'uppy-Root': props.isTargetDOMEl },
     'uppy-Dashboard',
     { 'Uppy--isTouchDevice': isTouchDevice() },
     { 'uppy-Dashboard--modal': !props.inline },
@@ -52,7 +57,7 @@ module.exports = function Dashboard (props) {
           aria-label={props.i18n('closeModal')}
           title={props.i18n('closeModal')}
           onclick={props.closeModal}>
-          {closeIcon()}
+          <span aria-hidden="true">×</span>
         </button>
 
         <div class="uppy-Dashboard-innerWrap">
@@ -77,6 +82,9 @@ module.exports = function Dashboard (props) {
             })}
           </div>
         </div>
+
+        { props.proudlyDisplayPoweredByUppy && poweredByUppy(props) }
+
       </div>
     </div>
   )

+ 28 - 32
src/plugins/Dashboard/FileCard.js

@@ -1,5 +1,5 @@
 const getFileTypeIcon = require('./getFileTypeIcon')
-const { checkIcon } = require('./icons')
+const FilePreview = require('./FilePreview')
 const { h, Component } = require('preact')
 
 module.exports = class FileCard extends Component {
@@ -10,7 +10,8 @@ module.exports = class FileCard extends Component {
 
     this.tempStoreMetaOrSubmit = this.tempStoreMetaOrSubmit.bind(this)
     this.renderMetaFields = this.renderMetaFields.bind(this)
-    this.handleClick = this.handleClick.bind(this)
+    this.handleSave = this.handleSave.bind(this)
+    this.handleCancel = this.handleCancel.bind(this)
   }
 
   tempStoreMetaOrSubmit (ev) {
@@ -19,7 +20,7 @@ module.exports = class FileCard extends Component {
     if (ev.keyCode === 13) {
       ev.stopPropagation()
       ev.preventDefault()
-      this.props.fileCardDone(this.meta, file.id)
+      this.props.saveFileCard(this.meta, file.id)
       return
     }
 
@@ -33,7 +34,7 @@ module.exports = class FileCard extends Component {
     return metaFields.map((field) => {
       return <fieldset class="uppy-DashboardFileCard-fieldset">
         <label class="uppy-DashboardFileCard-label">{field.name}</label>
-        <input class="uppy-DashboardFileCard-input"
+        <input class="uppy-c-textInput uppy-DashboardFileCard-input"
           type="text"
           data-name={field.id}
           value={file.meta[field.id]}
@@ -44,9 +45,14 @@ module.exports = class FileCard extends Component {
     })
   }
 
-  handleClick (ev) {
-    const file = this.props.files[this.props.fileCardFor]
-    this.props.fileCardDone(this.meta, file.id)
+  handleSave (ev) {
+    const fileID = this.props.fileCardFor
+    this.props.saveFileCard(this.meta, fileID)
+  }
+
+  handleCancel (ev) {
+    this.meta = {}
+    this.props.toggleFileCard()
   }
 
   render () {
@@ -58,38 +64,28 @@ module.exports = class FileCard extends Component {
           <div class="uppy-DashboardContent-bar">
             <h2 class="uppy-DashboardContent-title">{this.props.i18n('editing')} <span class="uppy-DashboardContent-titleFile">{file.meta ? file.meta.name : file.name}</span></h2>
             <button class="uppy-DashboardContent-back" type="button" title={this.props.i18n('finishEditingFile')}
-              onclick={this.handleClick}>{this.props.i18n('done')}</button>
+              onclick={this.handleSave}>{this.props.i18n('done')}</button>
           </div>
+
           <div class="uppy-DashboardFileCard-inner">
             <div class="uppy-DashboardFileCard-preview" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
-              {file.preview
-                ? <img alt={file.name} src={file.preview} />
-                : <div class="uppy-DashboardItem-previewIconWrap">
-                  <span class="uppy-DashboardItem-previewIcon" style={{ color: getFileTypeIcon(file.type).color }}>{getFileTypeIcon(file.type).icon}</span>
-                  <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
-                </div>
-              }
+              <FilePreview file={file} />
             </div>
+
             <div class="uppy-DashboardFileCard-info">
-              <fieldset class="uppy-DashboardFileCard-fieldset">
-                <label class="uppy-DashboardFileCard-label">{this.props.i18n('name')}</label>
-                <input class="uppy-DashboardFileCard-input"
-                  type="text"
-                  data-name="name"
-                  value={file.meta.name || ''}
-                  placeholder={this.props.i18n('name')}
-                  onkeyup={this.tempStoreMetaOrSubmit}
-                  onkeydown={this.tempStoreMetaOrSubmit}
-                  onkeypress={this.tempStoreMetaOrSubmit} />
-              </fieldset>
               {this.renderMetaFields(file)}
             </div>
-          </div>
-          <div class="uppy-Dashboard-actions">
-            <button class="UppyButton--circular UppyButton--blue uppy-DashboardFileCard-done"
-              type="button"
-              title={this.props.i18n('finishEditingFiles')}
-              onclick={this.handleClick}>{checkIcon()}</button>
+
+            <div class="uppy-Dashboard-actions">
+              <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Dashboard-actionsBtn"
+                type="button"
+                title={this.props.i18n('finishEditingFiles')}
+                onclick={this.handleSave}>Save changes</button>
+              <button class="uppy-u-reset uppy-c-btn uppy-c-btn-link uppy-Dashboard-actionsBtn"
+                type="button"
+                title={this.props.i18n('finishEditingFiles')}
+                onclick={this.handleCancel}>Cancel</button>
+            </div>
           </div>
         </div>
       }

+ 28 - 34
src/plugins/Dashboard/FileItem.js

@@ -1,12 +1,10 @@
-const { getETA,
-         getSpeed,
-         prettyETA,
-         getFileNameAndExtension,
+const { getFileNameAndExtension,
          truncateString,
          copyToClipboard } = require('../../core/Utils')
 const prettyBytes = require('prettier-bytes')
 const FileItemProgress = require('./FileItemProgress')
 const getFileTypeIcon = require('./getFileTypeIcon')
+const FilePreview = require('./FilePreview')
 const { iconEdit, iconCopy, iconRetry } = require('./icons')
 const classNames = require('classnames')
 const { h } = require('preact')
@@ -48,16 +46,24 @@ module.exports = function fileItem (props) {
     { 'is-resumable': props.resumableUploads }
   )
 
+  const progressIndicatorTitle = isUploaded
+    ? props.i18n('uploadComplete')
+    : props.resumableUploads
+      ? file.isPaused
+        ? props.i18n('resumeUpload')
+        : props.i18n('pauseUpload')
+      : error
+        ? props.i18n('retryUpload')
+        : props.i18n('cancelUpload')
+
   return <li class={dashboardItemClass} id={`uppy_${file.id}`} title={file.meta.name}>
     <div class="uppy-DashboardItem-preview">
       <div class="uppy-DashboardItem-previewInnerWrap" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
-        {file.preview
-          ? <img alt={file.name} src={file.preview} />
-          : <div class="uppy-DashboardItem-previewIconWrap">
-            <span class="uppy-DashboardItem-previewIcon" style={{ color: getFileTypeIcon(file.type).color }}>{getFileTypeIcon(file.type).icon}</span>
-            <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
-          </div>
+        {props.showLinkToFileUploadResult && file.uploadURL
+          ? <a class="uppy-DashboardItem-previewLink" href={file.uploadURL} target="_blank" />
+          : null
         }
+        <FilePreview file={file} />
       </div>
       <div class="uppy-DashboardItem-progress">
         {isUploaded
@@ -69,16 +75,8 @@ module.exports = function fileItem (props) {
           </div>
           : <button class="uppy-DashboardItem-progressIndicator"
             type="button"
-            title={isUploaded
-                    ? 'upload complete'
-                    : props.resumableUploads
-                      ? file.isPaused
-                        ? 'resume upload'
-                        : 'pause upload'
-                      : error
-                        ? 'retry upload'
-                        : 'cancel upload'
-                  }
+            aria-label={progressIndicatorTitle}
+            title={progressIndicatorTitle}
             onclick={onPauseResumeCancelRetry}>
             {error
               ? iconRetry()
@@ -89,15 +87,6 @@ module.exports = function fileItem (props) {
             }
           </button>
         }
-        {props.showProgressDetails &&
-          <div class="uppy-DashboardItem-progressInfo"
-            title={props.i18n('fileProgress')}
-            aria-label={props.i18n('fileProgress')}>
-            {(!file.isPaused && !isUploaded) &&
-              <span>{prettyETA(getETA(file.progress))} ・ ↑ {prettyBytes(getSpeed(file.progress))}/s</span>
-            }
-          </div>
-        }
       </div>
     </div>
     <div class="uppy-DashboardItem-info">
@@ -110,22 +99,27 @@ module.exports = function fileItem (props) {
         }
       </h4>
       <div class="uppy-DashboardItem-status">
-        {file.data.size && <div class="uppy-DashboardItem-statusSize">{prettyBytes(file.data.size)}</div>}
+        {file.data.size ? <div class="uppy-DashboardItem-statusSize">{prettyBytes(file.data.size)}</div> : null}
         {file.source && <div class="uppy-DashboardItem-sourceIcon">
             {acquirers.map(acquirer => {
-              if (acquirer.id === file.source) return <span title={`${props.i18n('fileSource')}: ${acquirer.name}`}>{acquirer.icon()}</span>
+              if (acquirer.id === file.source) {
+                return <span title={`${props.i18n('fileSource')}: ${acquirer.name}`}>
+                  {acquirer.icon()}
+                </span>
+              }
             })}
           </div>
         }
       </div>
-      {!uploadInProgressOrComplete &&
-        <button class="uppy-DashboardItem-edit"
+      {(!uploadInProgressOrComplete && props.metaFields && props.metaFields.length)
+        ? <button class="uppy-DashboardItem-edit"
           type="button"
           aria-label={props.i18n('editFile')}
           title={props.i18n('editFile')}
-          onclick={(e) => props.showFileCard(file.id)}>
+          onclick={(e) => props.toggleFileCard(file.id)}>
           {iconEdit()}
         </button>
+        : null
       }
       {file.uploadURL &&
         <button class="uppy-DashboardItem-copyLink"

+ 8 - 7
src/plugins/Dashboard/FileList.js

@@ -1,6 +1,6 @@
 const FileItem = require('./FileItem')
 const ActionBrowseTagline = require('./ActionBrowseTagline')
-const { dashboardBgIcon } = require('./icons')
+// const { dashboardBgIcon } = require('./icons')
 const classNames = require('classnames')
 const { h } = require('preact')
 
@@ -14,22 +14,21 @@ module.exports = (props) => {
   return <ul class={dashboardFilesClass}>
     {noFiles &&
       <div class="uppy-Dashboard-bgIcon">
-        {dashboardBgIcon()}
-        <h3 class="uppy-Dashboard-dropFilesTitle">
+        <div class="uppy-Dashboard-dropFilesTitle">
           {h(ActionBrowseTagline, {
             acquirers: props.acquirers,
             handleInputChange: props.handleInputChange,
             i18n: props.i18n
           })}
-        </h3>
-        { props.note && <p class="uppy-Dashboard-note">{props.note}</p> }
+        </div>
+        { props.note && <div class="uppy-Dashboard-note">{props.note}</div> }
       </div>
     }
     {Object.keys(props.files).map((fileID) => {
       return FileItem({
         acquirers: props.acquirers,
         file: props.files[fileID],
-        showFileCard: props.showFileCard,
+        toggleFileCard: props.toggleFileCard,
         showProgressDetails: props.showProgressDetails,
         info: props.info,
         log: props.log,
@@ -39,7 +38,9 @@ module.exports = (props) => {
         cancelUpload: props.cancelUpload,
         retryUpload: props.retryUpload,
         resumableUploads: props.resumableUploads,
-        isWide: props.isWide
+        isWide: props.isWide,
+        showLinkToFileUploadResult: props.showLinkToFileUploadResult,
+        metaFields: props.metaFields
       })
     })}
   </ul>

+ 21 - 0
src/plugins/Dashboard/FilePreview.js

@@ -0,0 +1,21 @@
+const getFileTypeIcon = require('./getFileTypeIcon')
+const { h } = require('preact')
+
+module.exports = function FilePreview (props) {
+  const file = props.file
+
+  if (file.preview) {
+    return <img class="uppy-DashboardItem-previewImg" alt={file.name} src={file.preview} />
+  }
+
+  const { color, icon } = getFileTypeIcon(file.type)
+
+  return (
+    <div class="uppy-DashboardItem-previewIconWrap">
+      <span class="uppy-DashboardItem-previewIcon" style={{ color: color }}>{icon}</span>
+      <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
+    </div>
+  )
+}
+
+// <span class="uppy-DashboardItem-previewType">{file.extension && file.extension.length < 5 ? file.extension : null}</span>

+ 2 - 25
src/plugins/Dashboard/icons.js

@@ -43,12 +43,6 @@ function localIcon () {
   </svg>
 }
 
-function closeIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="14px" height="14px" viewBox="0 0 19 19">
-    <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
-  </svg>
-}
-
 function iconRetry () {
   return <svg class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
     <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z" />
@@ -58,21 +52,14 @@ function iconRetry () {
   </svg>
 }
 
-function pluginIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 32 30">
-    <path d="M6.6209894,11.1451162 C6.6823051,11.2751669 6.81374248,11.3572188 6.95463813,11.3572188 L12.6925482,11.3572188 L12.6925482,16.0630427 C12.6925482,17.880509 14.1726048,18.75 16.0000083,18.75 C17.8261072,18.75 19.3074684,17.8801847 19.3074684,16.0630427 L19.3074684,11.3572188 L25.0437478,11.3572188 C25.1875787,11.3572188 25.3164069,11.2751669 25.3790272,11.1451162 C25.4370814,11.0173358 25.4171865,10.8642587 25.3252129,10.7562615 L16.278212,0.127131837 C16.2093949,0.0463771751 16.1069846,0 15.9996822,0 C15.8910751,0 15.7886648,0.0463771751 15.718217,0.127131837 L6.6761083,10.7559371 C6.58250402,10.8642587 6.56293518,11.0173358 6.6209894,11.1451162 L6.6209894,11.1451162 Z" />
-    <path d="M28.8008722,6.11142645 C28.5417891,5.19831555 28.1583331,4.6875 27.3684848,4.6875 L21.6124454,4.6875 L22.8190234,6.10307874 L27.4986725,6.10307874 L29.9195817,19.3486449 L21.3943891,19.3502502 L21.3943891,22.622552 L10.8023461,22.622552 L10.8023461,19.3524977 L2.07815702,19.3534609 L5.22979699,6.10307874 L9.17871529,6.10307874 L10.3840011,4.6875 L4.6308691,4.6875 C3.83940559,4.6875 3.37421888,5.2390909 3.19815864,6.11142645 L0,19.7470874 L0,28.2212959 C0,29.2043992 0.801477937,30 1.78870751,30 L30.2096773,30 C31.198199,30 32,29.2043992 32,28.2212959 L32,19.7470874 L28.8008722,6.11142645 L28.8008722,6.11142645 Z" />
-  </svg>
-}
-
 function checkIcon () {
-  return <svg aria-hidden="true" class="UppyIcon UppyIcon-check" width="13px" height="9px" viewBox="0 0 13 9">
+  return <svg aria-hidden="true" class="UppyIcon UppyIcon-check" width="13" height="9" viewBox="0 0 13 9">
     <polygon points="5 7.293 1.354 3.647 0.646 4.354 5 8.707 12.354 1.354 11.646 0.647" />
   </svg>
 }
 
 function iconAudio () {
-  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 55 55">
+  return <svg aria-hidden="true" class="UppyIcon" width="55" height="55" viewBox="0 0 55 55">
     <path d="M52.66.25c-.216-.19-.5-.276-.79-.242l-31 4.01a1 1 0 0 0-.87.992V40.622C18.174 38.428 15.273 37 12 37c-5.514 0-10 4.037-10 9s4.486 9 10 9 10-4.037 10-9c0-.232-.02-.46-.04-.687.014-.065.04-.124.04-.192V16.12l29-3.753v18.257C49.174 28.428 46.273 27 43 27c-5.514 0-10 4.037-10 9s4.486 9 10 9c5.464 0 9.913-3.966 9.993-8.867 0-.013.007-.024.007-.037V1a.998.998 0 0 0-.34-.75zM12 53c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zm31-10c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zM22 14.1V5.89l29-3.753v8.21l-29 3.754z" />
   </svg>
 }
@@ -101,13 +88,6 @@ function iconText () {
   </svg>
 }
 
-function uploadIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="37" height="33" viewBox="0 0 37 33">
-    <path d="M29.107 24.5c4.07 0 7.393-3.355 7.393-7.442 0-3.994-3.105-7.307-7.012-7.502l.468.415C29.02 4.52 24.34.5 18.886.5c-4.348 0-8.27 2.522-10.138 6.506l.446-.288C4.394 6.782.5 10.758.5 15.608c0 4.924 3.906 8.892 8.76 8.892h4.872c.635 0 1.095-.467 1.095-1.104 0-.636-.46-1.103-1.095-1.103H9.26c-3.644 0-6.63-3.035-6.63-6.744 0-3.71 2.926-6.685 6.57-6.685h.964l.14-.28.177-.362c1.477-3.4 4.744-5.576 8.347-5.576 4.58 0 8.45 3.452 9.01 8.072l.06.536.05.446h1.101c2.87 0 5.204 2.37 5.204 5.295s-2.333 5.296-5.204 5.296h-6.062c-.634 0-1.094.467-1.094 1.103 0 .637.46 1.104 1.094 1.104h6.12z" />
-    <path d="M23.196 18.92l-4.828-5.258-.366-.4-.368.398-4.828 5.196a1.13 1.13 0 0 0 0 1.546c.428.46 1.11.46 1.537 0l3.45-3.71-.868-.34v15.03c0 .64.445 1.118 1.075 1.118.63 0 1.075-.48 1.075-1.12V16.35l-.867.34 3.45 3.712a1 1 0 0 0 .767.345 1 1 0 0 0 .77-.345c.416-.33.416-1.036 0-1.485v.003z" />
-  </svg>
-}
-
 function dashboardBgIcon () {
   return <svg aria-hidden="true" class="UppyIcon" width="48" height="69" viewBox="0 0 48 69">
     <path d="M.5 1.5h5zM10.5 1.5h5zM20.5 1.5h5zM30.504 1.5h5zM45.5 11.5v5zM45.5 21.5v5zM45.5 31.5v5zM45.5 41.502v5zM45.5 51.502v5zM45.5 61.5v5zM45.5 66.502h-4.998zM35.503 66.502h-5zM25.5 66.502h-5zM15.5 66.502h-5zM5.5 66.502h-5zM.5 66.502v-5zM.5 56.502v-5zM.5 46.503V41.5zM.5 36.5v-5zM.5 26.5v-5zM.5 16.5v-5zM.5 6.5V1.498zM44.807 11H36V2.195z" />
@@ -122,14 +102,11 @@ module.exports = {
   iconRetry,
   iconEdit,
   localIcon,
-  closeIcon,
-  pluginIcon,
   checkIcon,
   iconAudio,
   iconVideo,
   iconPDF,
   iconFile,
   iconText,
-  uploadIcon,
   dashboardBgIcon
 }

+ 27 - 19
src/plugins/Dashboard/index.js

@@ -41,7 +41,7 @@ module.exports = class Dashboard extends Plugin {
         selectToUpload: 'Select files to upload',
         closeModal: 'Close Modal',
         upload: 'Upload',
-        importFrom: 'Import files from',
+        importFrom: 'Import from',
         dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
         dashboardTitle: 'Uppy Dashboard',
         copyLinkToClipboardSuccess: 'Link copied to clipboard.',
@@ -63,6 +63,10 @@ module.exports = class Dashboard extends Plugin {
         numberOfSelectedFiles: 'Number of selected files',
         uploadAllNewFiles: 'Upload all new files',
         emptyFolderAdded: 'No files were added from empty folder',
+        uploadComplete: 'Upload complete',
+        resumeUpload: 'Resume upload',
+        pauseUpload: 'Pause upload',
+        retryUpload: 'Retry upload',
         uploadXFiles: {
           0: 'Upload %{smart_count} file',
           1: 'Upload %{smart_count} files'
@@ -88,6 +92,7 @@ module.exports = class Dashboard extends Plugin {
       height: 550,
       thumbnailWidth: 280,
       defaultTabIcon: defaultTabIcon,
+      showLinkToFileUploadResult: true,
       showProgressDetails: false,
       hideUploadButton: false,
       hideProgressAfterFinish: false,
@@ -97,6 +102,7 @@ module.exports = class Dashboard extends Plugin {
       disableInformer: false,
       disableThumbnailGenerator: false,
       disablePageScrollWhenModalOpen: true,
+      proudlyDisplayPoweredByUppy: true,
       onRequestCloseModal: () => this.closeModal(),
       locale: defaultLocale
     }
@@ -125,7 +131,7 @@ module.exports = class Dashboard extends Plugin {
     this.initEvents = this.initEvents.bind(this)
     this.onKeydown = this.onKeydown.bind(this)
     this.handleClickOutside = this.handleClickOutside.bind(this)
-    this.handleFileCard = this.handleFileCard.bind(this)
+    this.toggleFileCard = this.toggleFileCard.bind(this)
     this.handleDrop = this.handleDrop.bind(this)
     this.handlePaste = this.handlePaste.bind(this)
     this.handleInputChange = this.handleInputChange.bind(this)
@@ -200,6 +206,11 @@ module.exports = class Dashboard extends Plugin {
     if (focusableNodes.length) focusableNodes[0].focus()
   }
 
+  setFocusToBrowse () {
+    const browseBtn = this.el.querySelector('.uppy-Dashboard-browse')
+    if (browseBtn) browseBtn.focus()
+  }
+
   maintainFocus (event) {
     var focusableNodes = this.getFocusableNodes()
     var focusedItemIndex = focusableNodes.indexOf(document.activeElement)
@@ -230,7 +241,8 @@ module.exports = class Dashboard extends Plugin {
     }
 
     this.updateDashboardElWidth()
-    this.setFocusToFirstNode()
+    // this.setFocusToFirstNode()
+    this.setFocusToBrowse()
   }
 
   closeModal () {
@@ -319,8 +331,6 @@ module.exports = class Dashboard extends Plugin {
       this.handleDrop(files)
     })
 
-    this.uppy.on('dashboard:file-card', this.handleFileCard)
-
     this.updateDashboardElWidth()
     window.addEventListener('resize', this.updateDashboardElWidth)
   }
@@ -336,7 +346,6 @@ module.exports = class Dashboard extends Plugin {
     }
 
     this.removeDragDropListener()
-    this.uppy.off('dashboard:file-card', this.handleFileCard)
     window.removeEventListener('resize', this.updateDashboardElWidth)
   }
 
@@ -349,7 +358,7 @@ module.exports = class Dashboard extends Plugin {
     })
   }
 
-  handleFileCard (fileId) {
+  toggleFileCard (fileId) {
     this.setPluginState({
       fileCardFor: fileId || false
     })
@@ -434,13 +443,9 @@ module.exports = class Dashboard extends Plugin {
       this.uppy.removeFile(fileID)
     }
 
-    const showFileCard = (fileID) => {
-      this.uppy.emit('dashboard:file-card', fileID)
-    }
-
-    const fileCardDone = (meta, fileID) => {
+    const saveFileCard = (meta, fileID) => {
       this.uppy.setFileMeta(fileID, meta)
-      this.uppy.emit('dashboard:file-card')
+      this.toggleFileCard()
     }
 
     return DashboardUI({
@@ -461,7 +466,6 @@ module.exports = class Dashboard extends Plugin {
       handleClickOutside: this.handleClickOutside,
       handleInputChange: this.handleInputChange,
       handlePaste: this.handlePaste,
-      showProgressDetails: this.opts.showProgressDetails,
       inline: this.opts.inline,
       showPanel: this.showPanel,
       hideAllPanels: this.hideAllPanels,
@@ -471,20 +475,23 @@ module.exports = class Dashboard extends Plugin {
       removeFile: this.uppy.removeFile,
       info: this.uppy.info,
       note: this.opts.note,
-      metaFields: this.getPluginState().metaFields,
+      metaFields: pluginState.metaFields,
       resumableUploads: this.uppy.state.capabilities.resumableUploads || false,
       startUpload: startUpload,
       pauseUpload: this.uppy.pauseResume,
       retryUpload: this.uppy.retryUpload,
       cancelUpload: cancelUpload,
       fileCardFor: pluginState.fileCardFor,
-      showFileCard: showFileCard,
-      fileCardDone: fileCardDone,
+      toggleFileCard: this.toggleFileCard,
+      saveFileCard: saveFileCard,
       updateDashboardElWidth: this.updateDashboardElWidth,
       maxWidth: this.opts.maxWidth,
       maxHeight: this.opts.maxHeight,
+      showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
+      proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
       currentWidth: pluginState.containerWidth,
-      isWide: pluginState.containerWidth > 400
+      isWide: pluginState.containerWidth > 400,
+      isTargetDOMEl: this.isTargetDOMEl
     })
   }
 
@@ -497,7 +504,7 @@ module.exports = class Dashboard extends Plugin {
   }
 
   install () {
-    // Set default state for Modal
+    // Set default state for Dashboard
     this.setPluginState({
       isHidden: true,
       showFileCard: false,
@@ -521,6 +528,7 @@ module.exports = class Dashboard extends Plugin {
       this.uppy.use(StatusBar, {
         target: this,
         hideUploadButton: this.opts.hideUploadButton,
+        showProgressDetails: this.opts.showProgressDetails,
         hideAfterFinish: this.opts.hideProgressAfterFinish,
         locale: this.opts.locale
       })

+ 2 - 2
src/plugins/Informer.js

@@ -28,11 +28,11 @@ module.exports = class Informer extends Plugin {
         },
         error: {
           text: '#fff',
-          bg: '#e74c3c'
+          bg: '#D32F2F'
         },
         success: {
           text: '#fff',
-          bg: '#7ac824'
+          bg: '#1BB240'
         }
       }
     }

+ 17 - 2
src/plugins/Instagram/index.js

@@ -10,7 +10,7 @@ module.exports = class Instagram extends Plugin {
     this.id = this.opts.id || 'Instagram'
     this.title = 'Instagram'
     this.icon = () => (
-      <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 512 512">
+      <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 512 512">
         <path d="M256,49.471c67.266,0,75.233.257,101.8,1.469,24.562,1.121,37.9,5.224,46.778,8.674a78.052,78.052,0,0,1,28.966,18.845,78.052,78.052,0,0,1,18.845,28.966c3.45,8.877,7.554,22.216,8.674,46.778,1.212,26.565,1.469,34.532,1.469,101.8s-0.257,75.233-1.469,101.8c-1.121,24.562-5.225,37.9-8.674,46.778a83.427,83.427,0,0,1-47.811,47.811c-8.877,3.45-22.216,7.554-46.778,8.674-26.56,1.212-34.527,1.469-101.8,1.469s-75.237-.257-101.8-1.469c-24.562-1.121-37.9-5.225-46.778-8.674a78.051,78.051,0,0,1-28.966-18.845,78.053,78.053,0,0,1-18.845-28.966c-3.45-8.877-7.554-22.216-8.674-46.778-1.212-26.564-1.469-34.532-1.469-101.8s0.257-75.233,1.469-101.8c1.121-24.562,5.224-37.9,8.674-46.778A78.052,78.052,0,0,1,78.458,78.458a78.053,78.053,0,0,1,28.966-18.845c8.877-3.45,22.216-7.554,46.778-8.674,26.565-1.212,34.532-1.469,101.8-1.469m0-45.391c-68.418,0-77,.29-103.866,1.516-26.815,1.224-45.127,5.482-61.151,11.71a123.488,123.488,0,0,0-44.62,29.057A123.488,123.488,0,0,0,17.3,90.982C11.077,107.007,6.819,125.319,5.6,152.134,4.369,179,4.079,187.582,4.079,256S4.369,333,5.6,359.866c1.224,26.815,5.482,45.127,11.71,61.151a123.489,123.489,0,0,0,29.057,44.62,123.486,123.486,0,0,0,44.62,29.057c16.025,6.228,34.337,10.486,61.151,11.71,26.87,1.226,35.449,1.516,103.866,1.516s77-.29,103.866-1.516c26.815-1.224,45.127-5.482,61.151-11.71a128.817,128.817,0,0,0,73.677-73.677c6.228-16.025,10.486-34.337,11.71-61.151,1.226-26.87,1.516-35.449,1.516-103.866s-0.29-77-1.516-103.866c-1.224-26.815-5.482-45.127-11.71-61.151a123.486,123.486,0,0,0-29.057-44.62A123.487,123.487,0,0,0,421.018,17.3C404.993,11.077,386.681,6.819,359.866,5.6,333,4.369,324.418,4.079,256,4.079h0Z" />
         <path d="M256,126.635A129.365,129.365,0,1,0,385.365,256,129.365,129.365,0,0,0,256,126.635Zm0,213.338A83.973,83.973,0,1,1,339.974,256,83.974,83.974,0,0,1,256,339.973Z" />
         <circle cx="390.476" cy="121.524" r="30.23" />
@@ -37,7 +37,10 @@ module.exports = class Instagram extends Plugin {
 
   install () {
     this.view = new ProviderView(this, {
-      viewType: 'grid'
+      viewType: 'grid',
+      showTitles: false,
+      showFilter: false,
+      showBreadcrumbs: false
     })
     // Set default state for Instagram
     this.setPluginState({
@@ -98,6 +101,18 @@ module.exports = class Instagram extends Plugin {
   }
 
   getItemName (item) {
+    if (item && item['created_time']) {
+      let date = new Date(item['created_time'] * 1000)
+      date = date.toLocaleDateString([], {
+        year: 'numeric',
+        month: 'short',
+        day: 'numeric',
+        hour: 'numeric',
+        minute: 'numeric'
+      })
+      // adding both date and carousel_id, so the name is unique
+      return `Instagram ${date} ${item.carousel_id || ''}`
+    }
     return ''
   }
 

+ 97 - 100
src/plugins/StatusBar/StatusBar.js

@@ -1,51 +1,8 @@
 const throttle = require('lodash.throttle')
+const classNames = require('classnames')
+const statusBarStates = require('./StatusBarStates')
 const { h } = require('preact')
 
-function progressDetails (props) {
-  return <span>{props.totalProgress || 0}%・{props.complete} / {props.inProgress}・{props.totalUploadedSize} / {props.totalSize}・↑ {props.totalSpeed}/s・{props.totalETA}</span>
-}
-
-const ThrottledProgressDetails = throttle(progressDetails, 500, {leading: true, trailing: true})
-
-const STATE_ERROR = 'error'
-const STATE_WAITING = 'waiting'
-const STATE_PREPROCESSING = 'preprocessing'
-const STATE_UPLOADING = 'uploading'
-const STATE_POSTPROCESSING = 'postprocessing'
-const STATE_COMPLETE = 'complete'
-
-function getUploadingState (props, files) {
-  if (props.isAllErrored) {
-    return STATE_ERROR
-  }
-
-  // If ALL files have been completed, show the completed state.
-  if (props.isAllComplete) {
-    return STATE_COMPLETE
-  }
-
-  let state = STATE_WAITING
-  const fileIDs = Object.keys(files)
-  for (let i = 0; i < fileIDs.length; i++) {
-    const progress = files[fileIDs[i]].progress
-    // If ANY files are being uploaded right now, show the uploading state.
-    if (progress.uploadStarted && !progress.uploadComplete) {
-      return STATE_UPLOADING
-    }
-    // If files are being preprocessed AND postprocessed at this time, we show the
-    // preprocess state. If any files are being uploaded we show uploading.
-    if (progress.preprocess && state !== STATE_UPLOADING) {
-      state = STATE_PREPROCESSING
-    }
-    // If NO files are being preprocessed or uploaded right now, but some files are
-    // being postprocessed, show the postprocess state.
-    if (progress.postprocess && state !== STATE_UPLOADING && state !== STATE_PREPROCESSING) {
-      state = STATE_POSTPROCESSING
-    }
-  }
-  return state
-}
-
 function calculateProcessingProgress (files) {
   // Collect pre or postprocessing progress states.
   const progresses = []
@@ -93,12 +50,13 @@ function togglePauseResume (props) {
 module.exports = (props) => {
   props = props || {}
 
-  const uploadState = getUploadingState(props, props.files || {})
+  const uploadState = props.uploadState
 
   let progressValue = props.totalProgress
   let progressMode
   let progressBarContent
-  if (uploadState === STATE_PREPROCESSING || uploadState === STATE_POSTPROCESSING) {
+
+  if (uploadState === statusBarStates.STATE_PREPROCESSING || uploadState === statusBarStates.STATE_POSTPROCESSING) {
     const progress = calculateProcessingProgress(props.files)
     progressMode = progress.mode
     if (progressMode === 'determinate') {
@@ -106,26 +64,33 @@ module.exports = (props) => {
     }
 
     progressBarContent = ProgressBarProcessing(progress)
-  } else if (uploadState === STATE_COMPLETE) {
+  } else if (uploadState === statusBarStates.STATE_COMPLETE) {
     progressBarContent = ProgressBarComplete(props)
-  } else if (uploadState === STATE_UPLOADING) {
+  } else if (uploadState === statusBarStates.STATE_UPLOADING) {
     progressBarContent = ProgressBarUploading(props)
-  } else if (uploadState === STATE_ERROR) {
+  } else if (uploadState === statusBarStates.STATE_ERROR) {
     progressValue = undefined
     progressBarContent = ProgressBarError(props)
   }
 
   const width = typeof progressValue === 'number' ? progressValue : 100
-  const isHidden = (uploadState === STATE_WAITING && props.hideUploadButton) ||
-    (uploadState === STATE_WAITING && !props.newFiles > 0) ||
-    (uploadState === STATE_COMPLETE && props.hideAfterFinish)
+  const isHidden = (uploadState === statusBarStates.STATE_WAITING && props.hideUploadButton) ||
+    (uploadState === statusBarStates.STATE_WAITING && !props.newFiles > 0) ||
+    (uploadState === statusBarStates.STATE_COMPLETE && props.hideAfterFinish)
 
-  const progressClasses = `uppy-StatusBar-progress
+  const progressClassNames = `uppy-StatusBar-progress
                            ${progressMode ? 'is-' + progressMode : ''}`
 
+  const statusBarClassNames = classNames(
+    'uppy',
+    'uppy-StatusBar',
+    `is-${uploadState}`,
+    { 'uppy-StatusBar--detailedProgress': props.showProgressDetails }
+  )
+
   return (
-    <div class={`uppy uppy-StatusBar is-${uploadState}`} aria-hidden={isHidden}>
-      <div class={progressClasses}
+    <div class={statusBarClassNames} aria-hidden={isHidden}>
+      <div class={progressClassNames}
         style={{ width: width + '%' }}
         role="progressbar"
         aria-valuemin="0"
@@ -133,19 +98,32 @@ module.exports = (props) => {
         aria-valuenow={progressValue} />
       {progressBarContent}
       <div class="uppy-StatusBar-actions">
-        { props.newFiles && !props.hideUploadButton ? <UploadBtn {...props} /> : null }
+        { props.newFiles && !props.hideUploadButton ? <UploadBtn {...props} uploadState={uploadState} /> : null }
         { props.error ? <RetryBtn {...props} /> : null }
+        { uploadState !== statusBarStates.STATE_WAITING && uploadState !== statusBarStates.STATE_COMPLETE
+          ? <CancelBtn {...props} />
+          : null
+        }
       </div>
     </div>
   )
 }
 
 const UploadBtn = (props) => {
+  const uploadBtnClassNames = classNames(
+    'uppy-u-reset',
+    'uppy-c-btn',
+    'uppy-StatusBar-actionBtn',
+    'uppy-StatusBar-actionBtn--upload',
+    { 'uppy-c-btn-primary': props.uploadState === statusBarStates.STATE_WAITING }
+    // { 'uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--upload': props.uploadState !== statusBarStates.STATE_WAITING }
+  )
+
   return <button type="button"
-    class="uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--upload"
+    class={uploadBtnClassNames}
     aria-label={props.i18n('uploadXFiles', { smart_count: props.newFiles })}
     onclick={props.startUpload}>
-    {props.inProgress
+    {props.newFiles && props.uploadStarted
       ? props.i18n('uploadXNewFiles', { smart_count: props.newFiles })
       : props.i18n('uploadXFiles', { smart_count: props.newFiles })
     }
@@ -154,11 +132,42 @@ const UploadBtn = (props) => {
 
 const RetryBtn = (props) => {
   return <button type="button"
-    class="uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry"
+    class="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry"
     aria-label={props.i18n('retryUpload')}
     onclick={props.retryAll}>{props.i18n('retry')}</button>
 }
 
+const CancelBtn = (props) => {
+  return <button type="button"
+    class="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--cancel"
+    aria-label={props.i18n('cancel')}
+    onclick={props.cancelAll}>Cancel{props.i18n('cancel')}</button>
+}
+
+const PauseResumeButtons = (props) => {
+  const { resumableUploads, isAllPaused, i18n } = props
+  const title = resumableUploads
+                ? isAllPaused
+                  ? i18n('resumeUpload')
+                  : i18n('pauseUpload')
+                : i18n('cancelUpload')
+
+  return <button title={title} class="uppy-u-reset uppy-StatusBar-statusIndicator" type="button" onclick={() => togglePauseResume(props)}>
+    {resumableUploads
+      ? isAllPaused
+        ? <svg aria-hidden="true" class="UppyIcon" width="15" height="17" viewBox="0 0 11 13">
+          <path d="M1.26 12.534a.67.67 0 0 1-.674.012.67.67 0 0 1-.336-.583v-11C.25.724.38.5.586.382a.658.658 0 0 1 .673.012l9.165 5.5a.66.66 0 0 1 .325.57.66.66 0 0 1-.325.573l-9.166 5.5z" />
+        </svg>
+        : <svg aria-hidden="true" class="UppyIcon" width="16" height="17" viewBox="0 0 12 13">
+          <path d="M4.888.81v11.38c0 .446-.324.81-.722.81H2.722C2.324 13 2 12.636 2 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81zM9.888.81v11.38c0 .446-.324.81-.722.81H7.722C7.324 13 7 12.636 7 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81z" />
+        </svg>
+      : <svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 19 19">
+        <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
+      </svg>
+    }
+  </button>
+}
+
 const ProgressBarProcessing = (props) => {
   const value = Math.round(props.value * 100)
 
@@ -168,29 +177,40 @@ const ProgressBarProcessing = (props) => {
   </div>
 }
 
+const progressDetails = (props) => {
+  return <span class="uppy-StatusBar-statusSecondary">
+    { props.inProgress > 1 && props.i18n('filesUploadedOfTotal', { complete: props.complete, smart_count: props.inProgress }) + '・' }
+    { props.i18n('dataUploadedOfTotal', { complete: props.totalUploadedSize, total: props.totalSize }) }・
+    { props.i18n('xTimeLeft', { time: props.totalETA }) }
+  </span>
+}
+
+const ThrottledProgressDetails = throttle(progressDetails, 500, { leading: true, trailing: true })
+
 const ProgressBarUploading = (props) => {
-  const { i18n } = props
+  if (!props.isUploadStarted || props.isAllComplete) {
+    return null
+  }
+
   return (
-    <div class="uppy-StatusBar-content">
-      {props.isUploadStarted && !props.isAllComplete
-        ? !props.isAllPaused
-          ? <div title="Uploading">{ <PauseResumeButtons {...props} /> } {i18n('uploading')} { <ThrottledProgressDetails {...props} /> }</div>
-          : <div title="Paused">{ <PauseResumeButtons {...props} /> } {i18n('paused')}・{props.totalProgress}%</div>
-        : null
-      }
+    <div class="uppy-StatusBar-content" title={props.isAllPaused ? props.i18n('paused') : props.i18n('uploading')}>
+      { <PauseResumeButtons {...props} /> }
+      <div class="uppy-StatusBar-status">
+        <span class="uppy-StatusBar-statusPrimary">{ props.isAllPaused ? props.i18n('paused') : props.i18n('uploading') }: {props.totalProgress}%</span>
+        <br />
+        { !props.isAllPaused && <ThrottledProgressDetails {...props} /> }
+      </div>
     </div>
   )
 }
 
 const ProgressBarComplete = ({ totalProgress, i18n }) => {
   return (
-    <div class="uppy-StatusBar-content" role="status">
-      <span title="Complete">
-        <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="18" height="17" viewBox="0 0 23 17">
-          <path d="M8.944 17L0 7.865l2.555-2.61 6.39 6.525L20.41 0 23 2.645z" />
-        </svg>
-        {i18n('uploadComplete')}・{totalProgress}%
-      </span>
+    <div class="uppy-StatusBar-content" role="status" title={i18n('complete')}>
+      <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="18" height="17" viewBox="0 0 23 17">
+        <path d="M8.944 17L0 7.865l2.555-2.61 6.39 6.525L20.41 0 23 2.645z" />
+      </svg>
+      {i18n('complete')}
     </div>
   )
 }
@@ -198,7 +218,8 @@ const ProgressBarComplete = ({ totalProgress, i18n }) => {
 const ProgressBarError = ({ error, retryAll, i18n }) => {
   return (
     <div class="uppy-StatusBar-content" role="alert">
-      <strong>{i18n('uploadFailed')}.</strong> <span>{i18n('pleasePressRetry')}</span>
+      <strong>{i18n('uploadFailed')}.</strong>
+      <span>{i18n('pleasePressRetry')}</span>
       <span class="uppy-StatusBar-details"
         aria-label={error}
         data-microtip-position="top"
@@ -207,27 +228,3 @@ const ProgressBarError = ({ error, retryAll, i18n }) => {
     </div>
   )
 }
-
-const PauseResumeButtons = (props) => {
-  const { resumableUploads, isAllPaused, i18n } = props
-  const title = resumableUploads
-                ? isAllPaused
-                  ? i18n('resumeUpload')
-                  : i18n('pauseUpload')
-                : i18n('cancelUpload')
-
-  return <button title={title} class="uppy-StatusBar-statusIndicator" type="button" onclick={() => togglePauseResume(props)}>
-    {resumableUploads
-      ? isAllPaused
-        ? <svg aria-hidden="true" class="UppyIcon" width="15" height="17" viewBox="0 0 11 13">
-          <path d="M1.26 12.534a.67.67 0 0 1-.674.012.67.67 0 0 1-.336-.583v-11C.25.724.38.5.586.382a.658.658 0 0 1 .673.012l9.165 5.5a.66.66 0 0 1 .325.57.66.66 0 0 1-.325.573l-9.166 5.5z" />
-        </svg>
-        : <svg aria-hidden="true" class="UppyIcon" width="16" height="17" viewBox="0 0 12 13">
-          <path d="M4.888.81v11.38c0 .446-.324.81-.722.81H2.722C2.324 13 2 12.636 2 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81zM9.888.81v11.38c0 .446-.324.81-.722.81H7.722C7.324 13 7 12.636 7 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81z" />
-        </svg>
-      : <svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 19 19">
-        <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
-      </svg>
-    }
-  </button>
-}

+ 8 - 0
src/plugins/StatusBar/StatusBarStates.js

@@ -0,0 +1,8 @@
+module.exports = {
+  'STATE_ERROR': 'error',
+  'STATE_WAITING': 'waiting',
+  'STATE_PREPROCESSING': 'preprocessing',
+  'STATE_UPLOADING': 'uploading',
+  'STATE_POSTPROCESSING': 'postprocessing',
+  'STATE_COMPLETE': 'complete'
+}

+ 54 - 14
src/plugins/StatusBar/index.js

@@ -1,13 +1,15 @@
 const Plugin = require('../../core/Plugin')
 const Translator = require('../../core/Translator')
 const StatusBarUI = require('./StatusBar')
+const statusBarStates = require('./StatusBarStates')
 const { getSpeed } = require('../../core/Utils')
 const { getBytesRemaining } = require('../../core/Utils')
 const { prettyETA } = require('../../core/Utils')
 const prettyBytes = require('prettier-bytes')
 
 /**
- * A status bar.
+ * StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
+ * progress percentage and time remaining.
  */
 module.exports = class StatusBar extends Plugin {
   constructor (uppy, opts) {
@@ -18,8 +20,8 @@ module.exports = class StatusBar extends Plugin {
 
     const defaultLocale = {
       strings: {
-        uploading: 'Uploading...',
-        uploadComplete: 'Upload complete',
+        uploading: 'Uploading',
+        complete: 'Complete',
         uploadFailed: 'Upload failed',
         pleasePressRetry: 'Please press Retry to upload again',
         paused: 'Paused',
@@ -30,6 +32,12 @@ module.exports = class StatusBar extends Plugin {
         resumeUpload: 'Resume upload',
         cancelUpload: 'Cancel upload',
         pauseUpload: 'Pause upload',
+        filesUploadedOfTotal: {
+          0: '%{complete} of %{smart_count} file uploaded',
+          1: '%{complete} of %{smart_count} files uploaded'
+        },
+        dataUploadedOfTotal: '%{complete} of %{total}',
+        xTimeLeft: '%{time} left',
         uploadXFiles: {
           0: 'Upload %{smart_count} file',
           1: 'Upload %{smart_count} files'
@@ -91,6 +99,37 @@ module.exports = class StatusBar extends Plugin {
     })
   }
 
+  getUploadingState (isAllErrored, isAllComplete, files) {
+    if (isAllErrored) {
+      return statusBarStates.STATE_ERROR
+    }
+
+    if (isAllComplete) {
+      return statusBarStates.STATE_COMPLETE
+    }
+
+    let state = statusBarStates.STATE_WAITING
+    const fileIDs = Object.keys(files)
+    for (let i = 0; i < fileIDs.length; i++) {
+      const progress = files[fileIDs[i]].progress
+      // If ANY files are being uploaded right now, show the uploading state.
+      if (progress.uploadStarted && !progress.uploadComplete) {
+        return statusBarStates.STATE_UPLOADING
+      }
+      // If files are being preprocessed AND postprocessed at this time, we show the
+      // preprocess state. If any files are being uploaded we show uploading.
+      if (progress.preprocess && state !== statusBarStates.STATE_UPLOADING) {
+        state = statusBarStates.STATE_PREPROCESSING
+      }
+      // If NO files are being preprocessed or uploaded right now, but some files are
+      // being postprocessed, show the postprocess state.
+      if (progress.postprocess && state !== statusBarStates.STATE_UPLOADING && state !== statusBarStates.STATE_PREPROCESSING) {
+        state = statusBarStates.STATE_POSTPROCESSING
+      }
+    }
+    return state
+  }
+
   render (state) {
     const files = state.files
 
@@ -117,9 +156,8 @@ module.exports = class StatusBar extends Plugin {
       return files[file].progress.preprocess || files[file].progress.postprocess
     })
 
-    let inProgressFilesArray = []
-    inProgressFiles.forEach((file) => {
-      inProgressFilesArray.push(files[file])
+    let inProgressFilesArray = inProgressFiles.map((file) => {
+      return files[file]
     })
 
     const totalSpeed = prettyBytes(this.getTotalSpeed(inProgressFilesArray))
@@ -149,31 +187,33 @@ module.exports = class StatusBar extends Plugin {
       !isAllErrored &&
       uploadStartedFiles.length > 0
 
-    const resumableUploads = this.uppy.getState().capabilities.resumableUploads || false
+    const resumableUploads = state.capabilities.resumableUploads || false
 
     return StatusBarUI({
       error: state.error,
+      uploadState: this.getUploadingState(isAllErrored, isAllComplete, state.files || {}),
       totalProgress: state.totalProgress,
       totalSize: totalSize,
       totalUploadedSize: totalUploadedSize,
-      uploadStartedFiles: uploadStartedFiles,
+      uploadStarted: uploadStartedFiles.length,
       isAllComplete: isAllComplete,
       isAllPaused: isAllPaused,
       isAllErrored: isAllErrored,
       isUploadStarted: isUploadStarted,
+      complete: completeFiles.length,
+      newFiles: newFiles.length,
+      inProgress: inProgressFiles.length,
+      totalSpeed: totalSpeed,
+      totalETA: totalETA,
+      files: state.files,
       i18n: this.i18n,
       pauseAll: this.uppy.pauseAll,
       resumeAll: this.uppy.resumeAll,
       retryAll: this.uppy.retryAll,
       cancelAll: this.uppy.cancelAll,
       startUpload: this.startUpload,
-      complete: completeFiles.length,
-      newFiles: newFiles.length,
-      inProgress: uploadStartedFiles.length,
-      totalSpeed: totalSpeed,
-      totalETA: totalETA,
-      files: state.files,
       resumableUploads: resumableUploads,
+      showProgressDetails: this.opts.showProgressDetails,
       hideUploadButton: this.opts.hideUploadButton,
       hideAfterFinish: this.opts.hideAfterFinish
     })

+ 12 - 3
src/plugins/Url/UrlUI.js

@@ -3,10 +3,12 @@ const { h, Component } = require('preact')
 class UrlUI extends Component {
   constructor (props) {
     super(props)
+    this.handleKeyPress = this.handleKeyPress.bind(this)
     this.handleClick = this.handleClick.bind(this)
   }
 
   componentDidMount () {
+    this.input.value = ''
     // My guess about why browser scrolls to top on focus:
     // Component is mounted right away, but the tab panel might be animating
     // still, so input element is positioned outside viewport. This fixes it.
@@ -15,6 +17,12 @@ class UrlUI extends Component {
     }, 150)
   }
 
+  handleKeyPress (ev) {
+    if (ev.keyCode === 13) {
+      this.props.addFile(this.input.value)
+    }
+  }
+
   handleClick () {
     this.props.addFile(this.input.value)
   }
@@ -22,14 +30,15 @@ class UrlUI extends Component {
   render () {
     return <div class="uppy-Url">
       <input
-        class="uppy-Url-input"
+        class="uppy-c-textInput uppy-Url-input"
         type="text"
         placeholder={this.props.i18n('enterUrlToImport')}
+        onkeyup={this.handleKeyPress}
         ref={(input) => { this.input = input }} />
       <button
-        class="uppy-Url-importButton"
+        class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Url-importButton"
         type="button"
-        aria-label={this.props.i18n('addUrl')}
+        aria-label={this.props.i18n('import')}
         onclick={this.handleClick}>
         {this.props.i18n('import')}
       </button>

+ 74 - 43
src/plugins/Url/index.js

@@ -13,7 +13,7 @@ module.exports = class Url extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'Url'
-    this.title = 'Url'
+    this.title = 'Link'
     this.type = 'acquirer'
     this.icon = () => <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="64" height="64" viewBox="0 0 64 64">
       <circle cx="32" cy="32" r="31" />
@@ -26,11 +26,10 @@ module.exports = class Url extends Plugin {
     // Set default options and locale
     const defaultLocale = {
       strings: {
-        addUrl: 'Add url',
         import: 'Import',
-        enterUrlToImport: 'Enter file url to import',
+        enterUrlToImport: 'Enter URL to import a file',
         failedToFetch: 'Uppy Server failed to fetch this URL, please make sure it’s correct',
-        enterCorrectUrl: 'Please enter correct URL to add file'
+        enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file'
       }
     }
 
@@ -59,58 +58,90 @@ module.exports = class Url extends Plugin {
     this.server = new RequestClient(uppy, {host: this.opts.host})
   }
 
-  getMeta (url) {
-    return this.server.post('url/meta', { url })
-  }
-
   getFileNameFromUrl (url) {
     return url.substring(url.lastIndexOf('/') + 1)
   }
 
+  checkIfCorrectURL (url) {
+    if (!url) return false
+
+    const protocol = url.match(/^([a-z0-9]+):\/\//)[1]
+    if (protocol !== 'http' && protocol !== 'https') {
+      return false
+    }
+
+    return true
+  }
+
+  addProtocolToURL (url) {
+    const protocolRegex = /^[a-z0-9]+:\/\//
+    const defaultProtocol = 'http://'
+    if (protocolRegex.test(url)) {
+      return url
+    }
+
+    return defaultProtocol + url
+  }
+
+  getMeta (url) {
+    return this.server.post('url/meta', { url })
+      .then((res) => {
+        if (res.error) {
+          this.uppy.log('[URL] Error:')
+          this.uppy.log(res.error)
+          throw new Error('Failed to fetch the file')
+        }
+        return res
+      })
+  }
+
   addFile (url) {
-    if (!url) {
-      this.uppy.log('[URL] Incorrect URL entered')
+    url = this.addProtocolToURL(url)
+    if (!this.checkIfCorrectURL(url)) {
+      this.uppy.log(`[URL] Incorrect URL entered: ${url}`)
       this.uppy.info(this.i18n('enterCorrectUrl'), 'error', 4000)
       return
     }
 
-    return this.getMeta(url).then((meta) => {
-      const tagFile = {
-        source: this.id,
-        name: this.getFileNameFromUrl(url),
-        type: meta.type,
-        data: {
-          size: meta.size
-        },
-        isRemote: true,
-        body: {
-          url: url
-        },
-        remote: {
-          host: this.opts.host,
-          url: `${this.hostname}/url/get`,
+    return this.getMeta(url)
+      .then((meta) => {
+        const tagFile = {
+          source: this.id,
+          name: this.getFileNameFromUrl(url),
+          type: meta.type,
+          data: {
+            size: meta.size
+          },
+          isRemote: true,
           body: {
-            fileId: url,
             url: url
+          },
+          remote: {
+            host: this.opts.host,
+            url: `${this.hostname}/url/get`,
+            body: {
+              fileId: url,
+              url: url
+            }
           }
         }
-      }
-
-      this.uppy.log('[Url] Adding remote file')
-      return this.uppy.addFile(tagFile)
-        .then(() => {
-          const dashboard = this.uppy.getPlugin('Dashboard')
-          if (dashboard) dashboard.hideAllPanels()
-        })
-    })
-    .catch((err) => {
-      const errorMsg = `${err.message}. Could be CORS issue?`
-      this.uppy.log(errorMsg, 'error')
-      this.uppy.info({
-        message: this.i18n('failedToFetch'),
-        details: errorMsg
-      }, 'error', 4000)
-    })
+        return tagFile
+      })
+      .then((tagFile) => {
+        this.uppy.log('[Url] Adding remote file')
+        return this.uppy.addFile(tagFile)
+      })
+      .then(() => {
+        const dashboard = this.uppy.getPlugin('Dashboard')
+        if (dashboard) dashboard.hideAllPanels()
+      })
+      .catch((err) => {
+        this.uppy.log(err)
+        this.uppy.info({
+          message: this.i18n('failedToFetch'),
+          details: err
+        }, 'error', 4000)
+      })
   }
 
   render (state) {

+ 2 - 3
src/plugins/Webcam/CameraIcon.js

@@ -1,8 +1,7 @@
 const { h } = require('preact')
 
 module.exports = (props) => {
-  return <svg aria-hidden="true" class="UppyIcon" width="100" height="77" viewBox="0 0 100 77">
-    <path d="M50 32c-7.168 0-13 5.832-13 13s5.832 13 13 13 13-5.832 13-13-5.832-13-13-13z" />
-    <path d="M87 13H72c0-7.18-5.82-13-13-13H41c-7.18 0-13 5.82-13 13H13C5.82 13 0 18.82 0 26v38c0 7.18 5.82 13 13 13h74c7.18 0 13-5.82 13-13V26c0-7.18-5.82-13-13-13zM50 68c-12.683 0-23-10.318-23-23s10.317-23 23-23 23 10.318 23 23-10.317 23-23 23z" />
+  return <svg aria-hidden="true" class="UppyIcon" width="66" height="55" viewBox="0 0 66 55" xmlns="http://www.w3.org/2000/svg">
+    <path d="M57.3 8.433c4.59 0 8.1 3.51 8.1 8.1v29.7c0 4.59-3.51 8.1-8.1 8.1H8.7c-4.59 0-8.1-3.51-8.1-8.1v-29.7c0-4.59 3.51-8.1 8.1-8.1h9.45l4.59-7.02c.54-.54 1.35-1.08 2.16-1.08h16.2c.81 0 1.62.54 2.16 1.08l4.59 7.02h9.45zM33 14.64c-8.62 0-15.393 6.773-15.393 15.393 0 8.62 6.773 15.393 15.393 15.393 8.62 0 15.393-6.773 15.393-15.393 0-8.62-6.773-15.393-15.393-15.393zM33 40c-5.648 0-9.966-4.319-9.966-9.967 0-5.647 4.318-9.966 9.966-9.966s9.966 4.319 9.966 9.966C42.966 35.681 38.648 40 33 40z" fill-rule="evenodd" />
   </svg>
 }

+ 1 - 0
src/plugins/Webcam/PermissionsScreen.js

@@ -3,6 +3,7 @@ const { h } = require('preact')
 module.exports = (props) => {
   return (
     <div class="uppy-Webcam-permissons">
+      <div class="uppy-Webcam-permissonsIcon">{props.icon()}</div>
       <h1 class="uppy-Webcam-Title">Please allow access to your camera</h1>
       <p>You have been prompted to allow camera access from this site.<br />
       In order to take pictures with your camera you must approve this request.</p>

+ 13 - 11
src/plugins/Webcam/RecordButton.js

@@ -1,27 +1,29 @@
-const RecordStartIcon = require('./RecordStartIcon')
-const RecordStopIcon = require('./RecordStopIcon')
 const { h } = require('preact')
 
-module.exports = function RecordButton ({ recording, onStartRecording, onStopRecording }) {
+module.exports = function RecordButton ({ recording, onStartRecording, onStopRecording, i18n }) {
   if (recording) {
     return (
-      <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
+      <button class="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--video"
         type="button"
-        title="Stop Recording"
-        aria-label="Stop Recording"
+        title={i18n('stopRecording')}
+        aria-label={i18n('stopRecording')}
         onclick={onStopRecording}>
-        {RecordStopIcon()}
+        <svg aria-hidden="true" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
+          <circle cx="50" cy="50" r="40" />
+        </svg>
       </button>
     )
   }
 
   return (
-    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
+    <button class="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--video"
       type="button"
-      title="Begin Recording"
-      aria-label="Begin Recording"
+      title={i18n('startRecording')}
+      aria-label={i18n('startRecording')}
       onclick={onStartRecording}>
-      {RecordStartIcon()}
+      <svg aria-hidden="true" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
+        <rect x="15" y="15" width="70" height="70" />
+      </svg>
     </button>
   )
 }

+ 0 - 7
src/plugins/Webcam/RecordStartIcon.js

@@ -1,7 +0,0 @@
-const { h } = require('preact')
-
-module.exports = (props) => {
-  return <svg aria-hidden="true" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
-    <circle cx="50" cy="50" r="40" />
-  </svg>
-}

+ 0 - 7
src/plugins/Webcam/RecordStopIcon.js

@@ -1,7 +0,0 @@
-const { h } = require('preact')
-
-module.exports = (props) => {
-  return <svg aria-hidden="true" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
-    <rect x="15" y="15" width="70" height="70" />
-  </svg>
-}

+ 4 - 4
src/plugins/Webcam/SnapshotButton.js

@@ -1,12 +1,12 @@
 const { h } = require('preact')
 const CameraIcon = require('./CameraIcon')
 
-module.exports = ({ onSnapshot }) => {
+module.exports = ({ onSnapshot, i18n }) => {
   return (
-    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
+    <button class="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--picture"
       type="button"
-      title="Take a snapshot"
-      aria-label="Take a snapshot"
+      title={i18n('takePicture')}
+      aria-label={i18n('takePicture')}
       onclick={onSnapshot}>
       {CameraIcon()}
     </button>

+ 0 - 10
src/plugins/Webcam/WebcamIcon.js

@@ -1,10 +0,0 @@
-const { h } = require('preact')
-
-module.exports = (props) => {
-  return (
-    <svg aria-hidden="true" class="UppyIcon" width="18" height="21" viewBox="0 0 18 21">
-      <path d="M14.8 16.9c1.9-1.7 3.2-4.1 3.2-6.9 0-5-4-9-9-9s-9 4-9 9c0 2.8 1.2 5.2 3.2 6.9C1.9 17.9.5 19.4 0 21h3c1-1.9 11-1.9 12 0h3c-.5-1.6-1.9-3.1-3.2-4.1zM9 4c3.3 0 6 2.7 6 6s-2.7 6-6 6-6-2.7-6-6 2.7-6 6-6z" />
-      <path d="M9 14c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zM8 8c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1c0-.5.4-1 1-1z" />
-    </svg>
-  )
-}

+ 21 - 17
src/plugins/Webcam/index.js

@@ -6,7 +6,7 @@ const {
   canvasToBlob
 } = require('../../core/Utils')
 const supportsMediaRecorder = require('./supportsMediaRecorder')
-const WebcamIcon = require('./WebcamIcon')
+const CameraIcon = require('./CameraIcon')
 const CameraScreen = require('./CameraScreen')
 const PermissionsScreen = require('./PermissionsScreen')
 
@@ -41,13 +41,16 @@ module.exports = class Webcam extends Plugin {
     this.supportsUserMedia = !!this.mediaDevices
     this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'
     this.id = this.opts.id || 'Webcam'
-    this.title = 'Webcam'
+    this.title = 'Camera'
     this.type = 'acquirer'
-    this.icon = WebcamIcon
+    this.icon = CameraIcon
 
     const defaultLocale = {
       strings: {
-        smile: 'Smile!'
+        smile: 'Smile!',
+        takePicture: 'Take a picture',
+        startRecording: 'Begin video recording',
+        stopRecording: 'Stop video recording'
       }
     }
 
@@ -316,21 +319,22 @@ module.exports = class Webcam extends Plugin {
     const webcamState = this.getPluginState()
 
     if (!webcamState.cameraReady) {
-      return PermissionsScreen(webcamState)
+      return <PermissionsScreen icon={CameraIcon} />
     }
 
-    return h(CameraScreen, Object.assign({}, webcamState, {
-      onSnapshot: this.takeSnapshot,
-      onStartRecording: this.startRecording,
-      onStopRecording: this.stopRecording,
-      onFocus: this.focus,
-      onStop: this.stop,
-      modes: this.opts.modes,
-      supportsRecording: supportsMediaRecorder(),
-      recording: webcamState.isRecording,
-      mirror: this.opts.mirror,
-      src: this.stream
-    }))
+    return <CameraScreen
+      {...webcamState}
+      onSnapshot={this.takeSnapshot}
+      onStartRecording={this.startRecording}
+      onStopRecording={this.stopRecording}
+      onFocus={this.focus}
+      onStop={this.stop}
+      i18n={this.i18n}
+      modes={this.opts.modes}
+      supportsRecording={supportsMediaRecorder()}
+      recording={webcamState.isRecording}
+      mirror={this.opts.mirror}
+      src={this.stream} />
   }
 
   install () {

+ 194 - 33
src/scss/_common.scss

@@ -2,8 +2,8 @@
 * General Uppy styles that apply to everything inside the .Uppy container
 */
 
-.uppy {
-  // all: initial;
+.uppy-Root {
+  all: initial;
   box-sizing: border-box;
   font-family: -apple-system, BlinkMacSystemFont,
     'avenir next', avenir,
@@ -14,10 +14,14 @@
   // -webkit-font-smoothing: antialiased;
 }
 
-.uppy *, .uppy *:before, .uppy *:after {
+.uppy-Root *, .uppy-Root *:before, .uppy-Root *:after {
   box-sizing: inherit;
 }
 
+// .uppy-Root *:focus:not(.focus-visible) {
+//   outline: 0;
+// }
+
 // https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
 
 .UppyIcon {
@@ -56,53 +60,210 @@
   }
 }
 
-.UppyButton--white {
-  color: $color-asphalt-gray;
-  background-color: $color-white;
+.UppyButton--red {
+  color: $color-white;
+  background-color: $color-red;
 
   &:hover,
   &:focus {
-    color: $color-white;
-    background-color: darken($color-cornflower-blue, 10%);
+    background-color: darken($color-red, 10%);
   }
 }
 
-.UppyButton--yellow {
-  color: $color-white;
-  background-color: $color-yellow;
+.UppyButton--sizeM {
+  width: 60px;
+  height: 60px;
+}
 
-  &:hover,
-  &:focus {
-    background-color: darken($color-yellow, 5%);
-  }
+.UppyButton--sizeS {
+  width: 45px;
+  height: 45px;
 }
 
-.UppyButton--green {
-  color: $color-white;
-  background-color: $color-green;
+// Utilities
 
-  &:hover,
-  &:focus {
-    background-color: darken($color-green, 10%);
+.uppy-u-reset {
+  // @include reset-button;
+  animation: none 0s ease 0s 1 normal none running;
+  backface-visibility: visible;
+  background: transparent none repeat 0 0 / auto auto padding-box border-box scroll;
+  border: medium none currentColor;
+  border-collapse: separate;
+  border-image: none;
+  border-radius: 0;
+  border-spacing: 0;
+  bottom: auto;
+  box-shadow: none;
+  // box-sizing: content-box;
+  caption-side: top;
+  clear: none;
+  clip: auto;
+  color: #000;
+  columns: auto;
+  column-count: auto;
+  column-fill: balance;
+  column-gap: normal;
+  column-rule: medium none currentColor;
+  column-span: 1;
+  column-width: auto;
+  content: normal;
+  counter-increment: none;
+  counter-reset: none;
+  cursor: auto;
+  // direction: ltr;
+  display: inline;
+  empty-cells: show;
+  float: none;
+  font-family: serif;
+  font-size: medium;
+  font-style: normal;
+  font-variant: normal;
+  font-weight: normal;
+  font-stretch: normal;
+  line-height: normal;
+  height: auto;
+  hyphens: none;
+  left: auto;
+  letter-spacing: normal;
+  list-style: disc outside none;
+  margin: 0;
+  max-height: none;
+  max-width: none;
+  min-height: 0;
+  min-width: 0;
+  opacity: 1;
+  orphans: 2;
+  outline: medium none invert;
+  overflow: visible;
+  overflow-x: visible;
+  overflow-y: visible;
+  padding: 0;
+  page-break-after: auto;
+  page-break-before: auto;
+  page-break-inside: auto;
+  perspective: none;
+  perspective-origin: 50% 50%;
+  position: static;
+  right: auto;
+  tab-size: 8;
+  table-layout: auto;
+  text-align: left;
+  text-align-last: auto;
+  text-decoration: none;
+  text-indent: 0;
+  text-shadow: none;
+  text-transform: none;
+  top: auto;
+  transform: none;
+  transform-origin: 50% 50% 0;
+  transform-style: flat;
+  transition: none 0s ease 0s;
+  unicode-bidi: normal;
+  vertical-align: baseline;
+  visibility: visible;
+  white-space: normal;
+  widows: 2;
+  width: auto;
+  word-spacing: normal;
+  z-index: auto;
+  // all: initial;
+}
+
+// Inputs
+
+.uppy-c-textInput {
+  border: 1px solid rgba($color-gray, 0.5);
+  border-radius: 4px;
+  font-size: 13px;
+  line-height: 1.5;
+  padding: 6px 8px;
+}
+
+  .uppy-Dashboard--wide .uppy-c-textInput {
+    font-size: 15px;
+    line-height: 1.8;
+    padding: 8px 12px;
+  }
+
+  .uppy-c-textInput:focus {
+    border-color: $color-cornflower-blue;
+    outline: none;
+    box-shadow: 0 0 1px 1px rgba($color-cornflower-blue, 0.3);
   }
+
+// Buttons
+
+.uppy-c-btn {
+  display: inline-block;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  font-family: inherit;
+  font-size: 16px;
+  line-height: 1;
+  font-weight: 300;
+  transition: all 0.3s;
+  user-select: none;
 }
 
-.UppyButton--red {
+  .uppy-c-btn:not(:disabled):not(.disabled) {
+    cursor: pointer;
+  }
+
+.uppy-c-btn-primary {
+  font-size: 13px;
+  padding: 10px 18px;
+  border-radius: 4px;
+  background-color: $color-cornflower-blue;
   color: $color-white;
-  background-color: $color-red;
+}
 
-  &:hover,
-  &:focus {
-    background-color: darken($color-red, 10%);
+  .uppy-Dashboard--wide .uppy-c-btn-primary {
+    font-size: 15px;
+    padding: 13px 28px;
+    // border-radius: 4px;
+  }
+
+  .uppy-c-btn-primary:hover {
+    background-color: darken($color-cornflower-blue, 10%);
+  }
+
+  .uppy-c-btn-primary:focus {
+    outline: none;
+    box-shadow: 0 0 3px 1px rgba($color-cornflower-blue, 0.7);
   }
+
+.uppy-c-btn-link {
+  font-size: 13px;
+  line-height: 1;
+  padding: 10px 18px;
+  border-radius: 4px;
+  background-color: transparent;
+  color: $color-black;
 }
 
-.UppyButton--sizeM {
-  width: 60px;
-  height: 60px;
+  .uppy-Dashboard--wide .uppy-c-btn-link {
+    font-size: 15px;
+    padding: 13px 28px;
+    // border-radius: 4px;
+  }
+
+  .uppy-c-btn-link:hover {
+    text-decoration: underline;
+  }
+
+  .uppy-c-btn-link:focus {
+    outline: none;
+    box-shadow: 0 0 3px 1px rgba($color-cornflower-blue, 0.7);
+  }
+
+.uppy-c-btn--small {
+  font-size: 0.9em;
+  padding: 7px 16px;
+  border-radius: 2px;
 }
 
-.UppyButton--sizeS {
-  width: 45px;
-  height: 45px;
-}
+  .uppy-Dashboard--wide .uppy-c-btn--small {
+    padding: 8px 10px;
+    border-radius: 2px;
+  }

+ 205 - 246
src/scss/_dashboard.scss

@@ -1,10 +1,13 @@
 .uppy-Dashboard--modal {
   z-index: $zIndex-2;
+  // transition: transform 0.2s ease-in-out;
+  // transform: none;
   // -webkit-overflow-scrolling: touch;
 }
 
 .uppy-Dashboard--modal[aria-hidden=true] {
   display: none;
+  // transform: translateY(-50%);
 }
 
 // Added to body to prevent the page from scrolling when Modal is open
@@ -19,18 +22,20 @@
   left: 0;
   right: 0;
   bottom: 0;
-  background-color: rgba($color-white, 0.8);
+  // background-color: rgba($color-white, 0.8);
+  background-color: rgba($color-black, 0.5);
   z-index: $zIndex-2;
 }
 
 .uppy-Dashboard-inner {
   position: relative;
   background-color: darken($color-white, 2%);
+  // background-color: $color-white;
   max-width: 100%;
   max-height: 100%;
   width: 100%;
   height: 100%;
-  overflow: hidden;
+  // overflow: hidden;
   outline: none;
   border: 1px solid rgba($color-gray, 0.2);
 
@@ -45,12 +50,52 @@
   }
 }
 
+.uppy-Dashboard-poweredBy {
+  position: absolute;
+  right: 4px;
+  bottom: -23px;
+  font-size: 11px;
+  font-weight: 300;
+  color: rgba($color-gray, 0.8);
+  text-align: right;
+  text-decoration: none;
+}
+
+  .uppy-Dashboard--modal .uppy-Dashboard-poweredBy {
+    color: rgba($color-white, 0.7);
+  }
+
+.uppy-Dashboard-poweredByUppy {
+  color: $color-gray;
+}
+
+  .uppy-Dashboard--modal .uppy-Dashboard-poweredByUppy {
+    color: $color-white;
+  }
+
+.uppy-Dashboard-poweredByIcon {
+  stroke: $color-gray;
+  fill: none;
+  margin-left: 1px;
+  margin-right: 2px;
+}
+
+  .uppy-Dashboard--modal .uppy-Dashboard-poweredByIcon {
+    stroke: none;
+    fill: $color-uppy-pink;
+  }
+
 .uppy-Dashboard-innerWrap {
   display: flex;
   flex-direction: column;
   height: 100%;
   overflow: hidden;
   min-height: 300px;
+  position: relative;
+
+  @media #{$screen-medium} {
+    border-radius: 5px;
+  }
 }
 
 .uppy-Dashboard--modal .uppy-Dashboard-inner {
@@ -63,7 +108,7 @@
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
-    box-shadow: 0px 0px 20px 7px rgba($color-gray, 0.15);
+    box-shadow: 0 5px 15px 4px rgba($color-black, 0.15);
   }
 }
 
@@ -71,27 +116,17 @@
   @include reset-button;
   display: none;
   position: absolute;
-  top: 7px;
-  right: 7px;
+  top: 2px;
+  right: 8px;
   cursor: pointer;
-  color: lighten($color-asphalt-gray, 20%);
-  transition: all 0.2s;
-
-  &:hover { color: $color-cornflower-blue; }
-
-  .UppyIcon {
-    width: 12px;
-    height: 12px;
-  }
+  color: rgba($color-asphalt-gray, 0.5);
+  transition: all 0.3s;
+  font-size: 23px;
 
   .uppy-Dashboard--wide & {
-    top: 10px;
+    font-size: 30px;
+    top: 2px;
     right: 8px;
-
-    .UppyIcon {
-      width: 14px;
-      height: 14px;
-    }
   }
 
   .uppy-Dashboard--modal & {
@@ -100,10 +135,15 @@
   }
 }
 
+.uppy-Dashboard-close:hover {
+  color: $color-cornflower-blue; 
+}
+
+
 .uppy-DashboardTabs {
   padding-top: 7px;
   padding-bottom: 7px;
-  border-bottom: 1px dashed lighten($color-gray, 15%);
+  border-bottom: 1px solid rgba($color-gray, 0.3);
 }
 
 .uppy-DashboardTabs[aria-hidden=true] {
@@ -128,9 +168,14 @@
 .uppy-Dashboard-browse {
   @include reset-button;
   cursor: pointer;
-  color: darken($color-cornflower-blue, 10%);
+  color: rgba($color-cornflower-blue, 0.9);
 }
 
+  .uppy-Dashboard-browse:focus {
+    outline: none;
+    border-bottom: 2px solid $color-cornflower-blue;
+  }
+
 .uppy-DashboardTabs-list {
   list-style-type: none;
   margin: 0;
@@ -161,13 +206,13 @@
   // outline: none;
   transition: all 0.3s;
   color: darken($color-gray, 25%);
+}
 
-  &:focus,
-  &:active,
-  &:hover {
+  // .uppy-DashboardTab-btn:focus,
+  // .uppy-DashboardTab-btn:active,
+  .uppy-DashboardTab-btn:hover {
     color: $color-cornflower-blue;
   }
-}
 
 .uppy-DashboardTab-name {
   font-size: 8px;
@@ -208,11 +253,14 @@
   position: absolute;
   top: 0;
   left: 0;
+  display: flex;
+  align-items: center;
   height: 40px;
   width: 100%;
   border-bottom: 1px solid rgba($color-gray, 0.3);
   z-index: $zIndex-4;
   background-color: darken($color-white, 4%);
+  padding: 0 15px;
 
   .uppy-Dashboard--wide & {
     height: 50px;
@@ -243,34 +291,30 @@
 }
 
 .uppy-DashboardContent-titleFile {
-  text-decoration: underline;
-  // overflow: hidden;
-  // display: inline-block;
-  // vertical-align: text-bottom;
+  // text-decoration: underline;
 }
 
 .uppy-DashboardContent-back {
   @include reset-button;
-  position: absolute;
-  top: 0;
-  left: 15px;
+  // position: absolute;
+  // top: 0;
+  // left: 15px;
   font-size: 14px;
-  line-height: 40px;
-  font-weight: 500;
+  // line-height: 40px;
+  font-weight: 400;
   cursor: pointer;
   color: $color-cornflower-blue;
 
   .uppy-Dashboard--wide & {
-    // top: 16px;
     font-size: 15px;
-    line-height: 50px;
+    // line-height: 50px;
   }
 }
 
-.uppy-DashboardContent-back .UppyIcon {
-  position: relative;
-  margin-right: 3px;
-}
+// .uppy-DashboardContent-back .UppyIcon {
+//   position: relative;
+//   margin-right: 3px;
+// }
 
 .uppy-DashboardContent-panel {
   position: absolute;
@@ -361,8 +405,7 @@
 
 .uppy-Dashboard-files {
   margin: 0;
-  padding: 10px;
-  padding-top: 15px;
+  padding: 0 0 10px 0;
   overflow-y: auto;
   position: absolute;
   top: 0;
@@ -371,35 +414,46 @@
   bottom: 0;
 }
 
+  .uppy-Dashboard--wide .uppy-Dashboard-files {
+    padding: 15px 10px 10px 10px;
+  }
+
 .uppy-Dashboard.drag .uppy-Dashboard-innerWrap  {
-  background-color: darken($color-white, 10%)
+  background-color: darken($color-white, 20%)
 }
 
 .uppy-Dashboard.drag .uppy-Dashboard-files--noFiles {
-  border-color: darken(#E4E4E4, 20%);
-}
+  border-color: darken($color-white, 20%);
+}
+
+// .uppy-Dashboard-bgIcon {
+  // width: 100%;
+  // max-width: 460px;
+  // position: absolute;
+  // top: 50%;
+  // left: 50%;
+  // transform: translate(-50%, -50%);
+  // opacity: 0.7;
+  // transition: all 0.3s;
+  // padding: 0 20px;
+// }
 
-.uppy-Dashboard-bgIcon {
-  width: 100%;
-  max-width: 360px;
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  opacity: 0.7;
-  transition: all 0.3s;
-  padding: 0 20px;
-}
+// .uppy-Dashboard-bgIcon .UppyIcon {
+//   width: 100%;
+//   height: 80px;
+//   fill: none;
+//   stroke: $color-asphalt-gray;
 
-.uppy-Dashboard-bgIcon .UppyIcon {
-  width: 100%;
-  height: 80px;
-  fill: none;
-  stroke: $color-asphalt-gray;
+//   .uppy-Dashboard--wide & {
+//     height: 110px;
+//   }
+// }
 
-  .uppy-Dashboard--wide & {
-    height: 110px;
-  }
+.uppy-Dashboard-bgIcon {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
 .uppy-Dashboard.drag .uppy-Dashboard-bgIcon {
@@ -407,28 +461,34 @@
 }
 
 .uppy-Dashboard-dropFilesTitle {
+  max-width: 460px;
   text-align: center;
-  font-size: 15px;
+  font-size: 18px;
   line-height: 1.45;
-  font-weight: normal;
-  color: $color-asphalt-gray;
-  margin: 0;
-  margin-top: 25px;
+  font-weight: 300;
+  color: rgba($color-asphalt-gray, 0.8);
+  padding: 0 15px;
+  // margin: 0;
+  // margin-top: 25px;
 
   .uppy-Dashboard--wide & {
-    font-size: 18px;
+    font-size: 24px;
   }
 }
 
 .uppy-Dashboard-note {
-  font-size: 12px;
+  font-size: 13px;
   line-height: 1.2;
   text-align: center;
-  margin-top: 20px;
-  color: $color-asphalt-gray;
+  // margin-top: 20px;
+  color: rgba($color-asphalt-gray, 0.7);
+  position: absolute;
+  bottom: 20px;
+  left: 0;
+  width: 100%;
 
   .uppy-Dashboard--wide & {
-    font-size: 13px;
+    font-size: 14px;
   }
 }
 
@@ -436,9 +496,12 @@
   list-style: none;
   margin: 10px 0;
   position: relative;
-  background-color: $color-white;
+  // background-color: $color-white;
   display: flex;
   align-items: center;
+  border-bottom: 1px solid lighten($color-gray, 35%);
+  padding-bottom: 10px;
+  padding-left: 10px;
 
   .uppy-Dashboard--wide & {
     flex-direction: column;
@@ -448,6 +511,9 @@
     margin: 5px 15px;
     border: 0;
     background-color: initial;
+    border-bottom: none;
+    padding-bottom: 0;
+    padding-left: 0;
   }
 }
 
@@ -467,11 +533,20 @@
   }
 }
 
+.uppy-DashboardItem-previewLink {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: $zIndex-3;
+}
+
 .uppy-DashboardItem-sourceIcon {
   display: inline-block;
   vertical-align: middle;
-  width: 7px;
-  height: 7px;
+  width: 10px;
+  height: 10px;
   color: rgba($color-gray, 0.6);
 
   .uppy-Dashboard--wide & {
@@ -489,8 +564,8 @@
   justify-content: center;
   align-items: center;
   flex-direction: column;
-  // box-shadow: 0 0 2px 0 rgba(175, 175, 175, 0.7);
-  // border-radius: 3px;
+  box-shadow: 0 0 2px 0 rgba($color-gray, 0.7);
+  border-radius: 3px;
 
   .uppy-Dashboard--wide & {
     // box-shadow: 0 0 2px 0 rgba(175, 175, 175, 0.7);
@@ -522,18 +597,20 @@
 
 
 .uppy-DashboardItem-previewIconWrap {
-  height: 85%;
+  height: 80px;
+  max-height: 90%;
   position: relative;
 }
 
 .uppy-DashboardItem-previewIconBg {
+  width: 100%;
   height: 100%;
-  filter: drop-shadow(rgba(0, 0, 0, 0.1) 0px 0px 1px);
+  filter: drop-shadow(rgba($color-black, 0.1) 0px 0px 1px);
 }
 
 .uppy-DashboardItem-previewIcon {
-  width: 25%;
-  max-height: 55%;
+  width: 18px;
+  height: 18px;
   z-index: $zIndex-1;
   position: absolute;
   top: 50%;
@@ -541,21 +618,35 @@
   transform: translate(-50%, -50%);
 
   .uppy-Dashboard--wide & {
-    width: 40%;
-    max-height: 80%;
+    width: 25px;
+    height: 25px;
   }
 }
 
+.uppy-DashboardItem-previewType {
+  position: absolute;
+  bottom: 14px;
+  left: 50%;
+  transform: translate(-50%, 0);
+  text-transform: uppercase;
+  font-size: 9px;
+  letter-spacing: 1px;
+  color: $color-asphalt-gray;
+  z-index: $zIndex-1;
+  user-select: none;
+}
+
 .uppy-DashboardItem-info {
-  padding: 10px 19px 0 25px;
+  // padding: 10px 19px 0 25px;
+  padding-left: 15px;
   position: relative;
-  max-width: 70%;
+  max-width: 65%;
 
   .uppy-Dashboard--wide & {
     width: 100%;
     max-width: 100%;
     flex: 1;
-    padding-left: 3px;
+    padding: 10px 19px 0 3px;
     // border-bottom-left-radius: 6px;
     // border-bottom-right-radius: 6px;
     // border: 1px solid rgba($color-gray, 0.2);
@@ -607,8 +698,8 @@
   text-align: left;
   cursor: pointer;
   position: absolute;
-  top: 12px;
-  right: 4px;
+  top: 0;
+  right: -20px;
 
   .uppy-Dashboard--wide & {
     top: 9px;
@@ -640,7 +731,7 @@
 
 .uppy-DashboardItem-action {
   position: absolute;
-  top: 8px;
+  top: 23px;
   right: 5px;
   z-index: $zIndex-3;
 
@@ -661,7 +752,6 @@
     width: 20px;
     height: 20px;
     color: lighten($color-asphalt-gray, 8%);
-    // font-size: 20px;
   }
 }
 
@@ -669,9 +759,6 @@
     display: none;
   }
 
-// .uppy-DashboardItem-remove .UppyIcon {
-//   max-width: 100%;
-// }
 
 .uppy-DashboardItem-progress {
   position: absolute;
@@ -760,7 +847,6 @@
 .uppy-DashboardItem .bg {
   stroke: rgba($color-white, 0.4);
   opacity: 0;
-  // transition: all 0.2s;
 }
 
 .uppy-DashboardItem .progress {
@@ -778,7 +864,6 @@
 }
 
 .uppy-DashboardItem .cancel {
-  // stroke: $color-white;
   fill: $color-white;
   opacity: 0;
   transition: all 0.2s;
@@ -848,87 +933,6 @@
   }
 }
 
-// .UppyTotalProgress {
-//   @include reset-button;
-//   width: 70px;
-//   height: 70px;
-//   cursor: pointer;
-// }
-
-// .UppyTotalProgress .UppyIcon {
-//   width: 100%;
-//   height: 100%;
-// }
-
-// .UppyTotalProgress .bg {
-//   stroke: rgba($color-cornflower-blue, 0.3);
-//   transition: all 0.2s;
-//   opacity: 1;
-//   box-shadow: 1px 2px 4px 0px rgba($color-black, 0.2);
-// }
-
-// .UppyTotalProgress .progress {
-//   stroke: darken($color-cornflower-blue, 5%);
-//   transition: stroke-dashoffset .5s ease-out;
-//   opacity: 1;
-// }
-
-// .UppyTotalProgress .play {
-//   stroke: darken($color-cornflower-blue, 5%);
-//   fill: darken($color-cornflower-blue, 5%);
-//   transition: all 0.2s;
-//   opacity: 0;
-// }
-
-// .UppyTotalProgress .pause {
-//   stroke: darken($color-cornflower-blue, 5%);
-//   fill: darken($color-cornflower-blue, 5%);
-//   transition: all 0.2s;
-//   opacity: 1;
-// }
-
-// .UppyTotalProgress .check {
-//   fill: $color-white;
-//   transition: all 0.2s;
-//   opacity: 0;
-// }
-
-// .UppyTotalProgress--is-paused {
-//   .pause {
-//     opacity: 0;
-//   }
-//   .play {
-//     opacity: 1;
-//   }
-// }
-
-// .UppyTotalProgress--is-complete {
-//   cursor: default;
-
-//   .pause, .play {
-//     opacity: 0;
-//   }
-
-//   .bg {
-//     stroke: $color-green;
-//     opacity: 1;
-//   }
-
-//   .progress {
-//     stroke: $color-green;
-//     fill: $color-green;
-//     opacity: 1;
-//   }
-//   .check {
-//     opacity: 1;
-//   }
-// }
-
-// .UppyTotalProgress-info {
-//   font-size: 10px;
-//   color: $color-asphalt-gray;
-// }
-
 .uppy-DashboardItem-progressNum {
   position: relative;
   z-index: $zIndex-2;
@@ -943,21 +947,20 @@
 }
 
 .uppy-Dashboard-actions {
-  text-align: center;
-  position: absolute;
-  bottom: 16px;
-  right: 16px;
-  z-index: $zIndex-3;
+  height: 55px;
+  border-top: 1px solid rgba($color-gray, 0.2);
+  display: flex;
+  align-items: center;
+  padding: 0 15px;
+}
 
-  .uppy-Dashboard--wide & {
-    bottom: 20px;
-    right: 20px;
+  .uppy-Dashboard--wide .uppy-Dashboard-actions {
+    height: 75px;
   }
-}
 
-// .uppy-Dashboard-actions button:nth-child(2) {
-//   margin-top: 10px;
-// }
+.uppy-Dashboard-actionsBtn {
+  margin-right: 10px;
+}
 
 .uppy-Dashboard-pauseResume .UppyIcon {
   width: 100%;
@@ -1025,6 +1028,8 @@
 }
 
 .uppy-DashboardFileCard-inner {
+  display: flex;
+  flex-direction: column;
   height: 100%;
   padding-top: 40px;
 
@@ -1034,10 +1039,10 @@
 }
 
 .uppy-DashboardFileCard-preview {
-  height: 45%;
   display: flex;
   align-items: center;
   justify-content: center;
+  flex-grow: 1;
   border-bottom: 1px solid rgba($color-gray, 0.3);
   background-color: lighten($color-gray, 40%);
   position: relative;
@@ -1048,11 +1053,13 @@
   max-width: 90%;
   max-height: 90%;
   object-fit: cover;
+  border-radius: 3px;
 }
 
 .uppy-DashboardFileCard-info {
   padding: 30px 20px 20px 20px;
-  height: 55%;
+  max-height: 40%;
+  flex-grow: 1;
   overflow-y: auto;
 }
 
@@ -1060,16 +1067,16 @@
   font-size: 0;
   border: 0;
   padding: 0;
-  max-width: 450px;
+  max-width: 640px;
   margin: auto;
-  margin-bottom: 15px;
+  margin-bottom: 12px;
 }
 
 .uppy-DashboardFileCard-label {
   display: inline-block;
   vertical-align: middle;
-  width: 25%;
-  font-size: 11px;
+  width: 22%;
+  font-size: 12px;
   color: $color-asphalt-gray;
 
   .uppy-Dashboard--wide & {
@@ -1080,53 +1087,5 @@
 .uppy-DashboardFileCard-input {
   display: inline-block;
   vertical-align: middle;
-  width: 75%;
-  border: 0;
-  border-bottom: 1px solid rgba($color-asphalt-gray, 0.3);
-  outline: none;
-  font-size: 16px;
-  line-height: 1.8;
-  padding-left: 5px;
-  margin: auto;
-
-  // .uppy-Dashboard--wide & {
-  //   font-size: 15px;
-  // }
-}
-
-.uppy-DashboardFileCard-input:focus {
-  border-color: $color-asphalt-gray;
-}
-
-.uppy-DashboardFileCard-done {
-  width: 50px;
-  height: 50px;
-
-  .uppy-Dashboard--wide & {
-    width: 60px;
-    height: 60px;
-  }
-}
-
-.uppy-DashboardFileCard-done .UppyIcon {
-  width: 25px;
-  height: 25px;
-
-  .uppy-Dashboard--wide & {
-    width: 30px;
-    height: 30px;
-  }
-}
-
-// StatusBar
-
-// .uppy-Dashboard--wide .uppy-StatusBar-content .UppyIcon {
-//   width: 17px;
-//   height: 17px;
-// }
-
-.uppy-Dashboard--wide .uppy-StatusBar {
-  height: 40px;
-  line-height: 40px;
-  font-size: 14px;
+  width: 78%;
 }

+ 8 - 8
src/scss/_informer.scss

@@ -7,8 +7,8 @@
   font-size: 12px;
   font-weight: 500;
   padding: 0 15px;
-  height: 30px;
-  line-height: 30px;
+  height: 35px;
+  line-height: 35px;
   background-color: $color-black;
   color: $color-white;
   opacity: 1;
@@ -17,8 +17,8 @@
   z-index: $zIndex-4;
 
   .uppy-Dashboard--wide & {
-    height: 40px;
-    line-height: 40px;
+    height: 45px;
+    line-height: 45px;
     font-size: 13px;
   }
 }
@@ -32,12 +32,12 @@
 .uppy-Informer p {
   margin: 0;
   padding: 0;
-  height: 30px;
-  line-height: 30px;
+  height: 35px;
+  line-height: 35px;
 
   .uppy-Dashboard--wide & {
-    height: 40px;
-    line-height: 40px;
+    height: 45px;
+    line-height: 45px;
   }
 }
 

+ 136 - 132
src/scss/_provider.scss

@@ -1,4 +1,6 @@
-.uppy-Provider-auth, .uppy-Provider-error, .uppy-Provider-loading {
+.uppy-Provider-auth, 
+.uppy-Provider-error, 
+.uppy-Provider-loading {
   display: flex;
   align-items: center;
   justify-content: center;
@@ -6,9 +8,16 @@
   height: 100%;
 }
 
+.uppy-Provider-authIcon .UppyIcon {
+  width: 100px;
+  height: 75px;
+  color: rgba($color-asphalt-gray, 0.3);
+  margin-bottom: 15px;
+}
+
 .uppy-Provider-authTitle {
-  font-size: 22px;
-  line-height: 1.35;
+  font-size: 20px;
+  line-height: 1.4;
   font-weight: 300;
   margin-bottom: 30px;
   padding: 0 15px;
@@ -16,25 +25,29 @@
   text-align: center;
 }
 
-.uppy-Provider-authBtn {
-  @include reset-button;
-  border-radius: 6px;
-  background-color: $color-cornflower-blue;
-  color: $color-white;
-  font-size: 20px;
-  font-weight: 400;
-  padding: 12px 46px;
-  transition: background-color 0.3s;
-  text-decoration: none;
-  margin-bottom: 20px;
-  cursor: pointer;
+.uppy-Provider-breadcrumbs {
+  flex: 1;
+  color: darken($color-gray, 25%);
+  font-size: 12px;
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+}
 
-  &:hover {
-    background-color: darken($color-cornflower-blue, 10%);
-    color: $color-white;
-  }
+.uppy-Provider-breadcrumbsIcon {
+  display: inline;
+  color: darken($color-gray, 25%);
+  vertical-align: middle;
+  // position: relative;
+  // top: 1px;
+  margin-right: 8px;
 }
 
+  .uppy-Provider-breadcrumbsIcon .UppyIcon {
+    width: 13px;
+    height: 13px;
+  }
+
 .uppy-Provider-breadcrumbs button {
   @include reset-button;
   cursor: pointer;
@@ -46,7 +59,7 @@
 }
 
 .uppy-Provider-breadcrumbs button:focus {
-  outline: 1px dotted #aaa;
+  outline: 1px dotted rgb(145, 145, 145);
 }
 
 .uppy-Provider-breadcrumbs li {
@@ -54,13 +67,9 @@
   margin: 0;
 }
 
-// .uppy-Provider-breadcrumbs li:not(:last-child):after {
-//   content: ' /';
-// }
-
 .uppy-Provider-breadcrumbs li ~ li:before {
   content: '/';
-  padding: 0 10px;
+  padding: 0 7px;
 }
 
 .uppy-ProviderBrowser {
@@ -81,110 +90,79 @@
   position: relative;
 }
 
-::-webkit-input-placeholder {
-   color: rgba(119,119,119,0.75);
-}
-
-::-moz-placeholder {
-   color: rgba(119,119,119,0.75);
-}
-
-:-ms-input-placeholder {
-   color: rgba(119,119,119,0.75);
-}
-
 .uppy-ProviderBrowser-headerBar {
-  height: 50px;
-  line-height: 50px;
+  height: 40px;
+  line-height: 40px;
   display: flex;
   align-items: center;
-  padding-left: 16px;
+  padding: 0 16px;
   background-color: lighten($color-gray, 40%);
   z-index: $zIndex-2;
+  color: darken($color-gray, 20%);
+}
+
+.uppy-ProviderBrowser-headerBar--simple {
+  text-align: center;
+  display: block;
 }
 
 .uppy-ProviderBrowser-search {
-  height: 50px;
-  position: absolute;
-  top: 0;
-  left: 0;
   width: 100%;
-  z-index: $zIndex-3;
-  background-color: lighten($color-gray, 40%);
-  transform: translate(0, -50px);
-  transition: all .2s;
-  display: flex;
-  align-items: center;
+  background-color: $color-white;
+  position: relative;
+  height: 30px;
+  margin-top: 15px;
+  margin-bottom: 5px;
 }
 
-  .uppy-ProviderBrowser-search:not([aria-hidden=true]) {
-    transform: translate(0, 0);
-  }
-
-.uppy-ProviderBrowser-search input {
-  flex: 1;
+.uppy-ProviderBrowser-searchInput {
+  width: 100%;
+  height: 30px;
   background-color: transparent;
   outline: 0;
-  font-size: 15px;
-  font-weight: 400;
-  line-height: 50px;
+  font-family: sans-serif;
+  font-size: 14px;
+  line-height: 30px;
   border: 0;
-  padding: 0 16px;
+  padding: 0 16px 0 43px;
+  z-index: $zIndex-2;
 }
 
+.uppy-ProviderBrowser-searchInput::-webkit-input-placeholder,
+.uppy-ProviderBrowser-searchInput::-moz-placeholder,
+.uppy-ProviderBrowser-searchInput::-ms-input-placeholder {
+  color: rgba($color-gray, 0.75);
+  letter-spacing: 1px;
+}
 
-.uppy-ProviderBrowser-searchToggle {
-  @include reset-button();
-  width: 15px;
-  cursor: pointer;
-  color: $color-gray;
-  transition: all .2s;
-
-  &:hover { color: $color-black;
-  }
+.uppy-ProviderBrowser-searchIcon {
+  width: 16px;
+  height: 16px;
+  position: absolute;
+  left: 16px;
+  top: 7px;
+  z-index: $zIndex-3;
+  color: rgba($color-gray, 0.6);
 }
 
 .uppy-ProviderBrowser-searchClose {
-  @include reset-button();
-  cursor: pointer;
   width: 12px;
-  color: $color-gray;
-  transition: all .2s;
-  position: relative;
+  height: 12px;
+  position: absolute;
   right: 16px;
-
-  &:hover { color: $color-black; }
+  top: 7px;
+  z-index: $zIndex-3;
+  color: rgba($color-gray, 0.6);
+  cursor: pointer;
 }
 
 .uppy-ProviderBrowser-userLogout {
   @include reset-button();
-  margin-right: 16px;
   cursor: pointer;
 
   &:hover { text-decoration: underline; }
 }
 
-.uppy-Provider-breadcrumbs {
-  flex: 1;
-  color: $color-black;
-  font-size: 12px;
-  list-style-type: none;
-  padding: 0;
-  margin: 0;
-  margin-left: 16px;
-}
-
-.uppy-ProviderBrowser-breadcrumbs span {
-  font-size: 16px;
-  margin-left: 32px;
-}
-
-.uppy-ProviderBrowser-breadcrumbs span.active {
-  color: $color-asphalt-gray;
-  flex: 1;
-  font-weight: 500;
-}
-
 .uppy-ProviderBrowser-body {
   flex: 1;
   position: relative;
@@ -222,12 +200,21 @@
 
 .uppy-ProviderBrowser-viewType--list {
 
+  background-color: $color-white;
+
   .uppy-ProviderBrowser-list {
-    padding-top: 6px;
+    // padding-top: 6px;
   }
 
   .uppy-ProviderBrowserItem {
-    padding: 10px 14px;
+    padding: 10px 15px;
+  }
+
+  .uppy-ProviderBrowserItem-inner {
+    max-width: 80%;
+    word-wrap: break-word;
+    text-align: left;
+    line-height: 1.4;
   }
 
   .uppy-ProviderBrowserItem-inner img {
@@ -236,7 +223,7 @@
   }
 
   .uppy-ProviderBrowserItem-checkbox label:before {
-    border-color: rgba($color-asphalt-gray, 0.3);
+    border-color: rgba($color-asphalt-gray, 0.4);
   }
 
   .uppy-ProviderBrowserItem-checkbox input:checked + label:before {
@@ -256,7 +243,7 @@
     flex-direction: row;
     flex-wrap: wrap;
     justify-content: space-between;
-    padding: 3px;
+    padding: 6px;
   }
 
   .uppy-ProviderBrowser-list:after {
@@ -266,19 +253,31 @@
 
   .uppy-ProviderBrowserItem {
     display: inline-block;
-    width: 33.3333%;
+    width: 50%;
     position: relative;
-    padding: 3px;
-    opacity: 0.9;
+    padding: 8px;
   }
 
-    .uppy-ProviderBrowserItem--selected {
-      opacity: 1;
+    .uppy-Dashboard--wide .uppy-ProviderBrowserItem {
+      width: 33.3333%;
+      padding: 12px;
+    }
+
+    // .uppy-ProviderBrowserItem--selected {
+    //   border-color: $color-cornflower-blue;
+    //   outline: none;
+    // }
+
+    .uppy-ProviderBrowserItem--selected .uppy-ProviderBrowserItem-inner {
+      box-shadow: 0 0 0 3px rgba(darken($color-cornflower-blue, 10%), 0.9);
     }
 
   .uppy-ProviderBrowserItem-inner {
     width: 100%;
     height: 100%;
+    border-radius: 4px;
+    overflow: hidden;
+    border: 2px solid transparent;
   }
 
   .uppy-ProviderBrowserItem img {
@@ -288,30 +287,34 @@
   }
 
   .uppy-ProviderBrowserItem-checkbox {
-    position: absolute;
-    top: 8px;
-    right: 10px;
-    margin-right: 0;
+    display: none;
   }
 
-  .uppy-ProviderBrowserItem-checkbox label:before {
-    background-color: $color-cornflower-blue;
-  }
+  // .uppy-ProviderBrowserItem-checkbox {
+  //   position: absolute;
+  //   top: 8px;
+  //   right: 10px;
+  //   margin-right: 0;
+  // }
 
-  // Hide checkbox when unchecked in grid view
-  .uppy-ProviderBrowserItem-checkbox input + label {
-    opacity: 0;
-  }
+  // .uppy-ProviderBrowserItem-checkbox label:before {
+  //   background-color: $color-cornflower-blue;
+  // }
 
-  // Unhide the checkbox on the checked state
-  .uppy-ProviderBrowserItem-checkbox input:checked + label {
-    opacity: 1;
-  }
+  // // Hide checkbox when unchecked in grid view
+  // .uppy-ProviderBrowserItem-checkbox input + label {
+  //   opacity: 0;
+  // }
+
+  // // Unhide the checkbox on the checked state
+  // .uppy-ProviderBrowserItem-checkbox input:checked + label {
+  //   opacity: 1;
+  // }
 
 }
 
 .uppy-Dashboard--wide .uppy-ProviderBrowser-viewType--grid .uppy-ProviderBrowserItem {
-  width: 20%;
+  width: 25%;
 }
 
 .uppy-ProviderBrowserItem-checkbox input {
@@ -323,7 +326,7 @@
   position: relative;
   display: inline-block;
   top: -3px;
-  margin-right: 25px;
+  margin-right: 20px;
 }
 
 .uppy-ProviderBrowserItem-checkbox label {
@@ -340,12 +343,13 @@
 .uppy-ProviderBrowserItem-checkbox label:before {
   content: "";
   display: inline-block;
-  height: 20px;
-  width: 20px;
-  top: 0;
+  height: 18px;
+  width: 18px;
+  top: 2px;
   border: 1px solid $color-cornflower-blue; 
   background-color: $color-white;
-  border-radius: 50%; 
+  border-radius: 2px;
+  // border-radius: 50%;
 }
 
 // Inner checkbox
@@ -354,8 +358,8 @@
   display: inline-block;
   height: 5px;
   width: 8px;
-  left: 6px;
-  top: 7px;
+  left: 5px;
+  top: 8px;
   border-left: 2px solid $color-white;
   border-bottom: 2px solid $color-white;
   transform: rotate(-45deg);
@@ -395,6 +399,6 @@
 }
 
 .uppy-ProviderBrowser-doneBtn .UppyIcon {
-  width: 30px;
-  height: 30px;
+  width: 45%;
+  height: 45%;
 }

+ 69 - 46
src/scss/_statusbar.scss

@@ -1,17 +1,23 @@
 .uppy-StatusBar {
+  display: flex;
   position: relative;
-  height: 30px;
-  line-height: 30px;
+  height: 35px;
+  line-height: 35px;
   font-size: 12px;
-  font-weight: 500;
+  font-weight: 400;
   color: $color-white;
-  background-color: rgba($color-asphalt-gray, 0.7);
-  box-shadow: 1px 1px 4px 0 rgba($color-asphalt-gray, 0.3);
-  // overflow-x: hidden;
+  background-color: lighten($color-black, 10%);
+  // box-shadow: 1px 1px 4px 0 rgba($color-asphalt-gray, 0.3);
+  // border-top: 1px solid rgba($color-gray, 0.2);
   z-index: $zIndex-2;
   transition: height .2s;
 }
 
+  .uppy-Dashboard--wide .uppy-StatusBar {
+    height: 45px;
+    font-size: 14px;
+  }
+
 .uppy-StatusBar[aria-hidden=true] {
   overflow-y: hidden;
   height: 0;
@@ -26,14 +32,16 @@
 }
 
 .uppy-StatusBar.is-complete .uppy-StatusBar-content {
+  width: 100%;
   text-align: center;
   padding-left: 0;
+  justify-content: center;
 }
 
 .uppy-StatusBar:not([aria-hidden=true]).is-waiting {
-  background-color: $color-white;
+  background-color: darken($color-white, 2%);
   height: 65px;
-  line-height: 65px;
+  border-top: 1px solid rgba($color-gray, 0.3);
 }
 
 .uppy-StatusBar-progress {
@@ -61,34 +69,45 @@
 }
 
 .uppy-StatusBar-content {
+  display: flex;
+  align-items: center;
   position: relative;
   z-index: $zIndex-3;
   padding-left: 15px;
   white-space: nowrap;
   text-overflow: ellipsis;
   color: $color-white;
+  height: 100%;
 }
 
-// .uppy-StatusBar-content .UppyIcon {
-//   width: 15px;
-//   height: 15px;
+.uppy-StatusBar-status {
+  line-height: 1.35;
+  font-weight: normal;
+  letter-spacing: 0.5px;
+}
 
-//   .UppyDashboard--wide & {
-//     width: 17px;
-//     height: 17px;
-//   }
-// }
+.uppy-StatusBar-statusPrimary {
+  font-size: 13px;
+}
+
+.uppy-StatusBar-statusSecondary {
+  font-size: 11px;
+  display: none;
+}
 
+  .uppy-StatusBar--detailedProgress .uppy-StatusBar-statusSecondary {
+    display: inline;
+  }
 
 .uppy-StatusBar-statusIndicator {
   color: $color-white;
-  margin-right: 8px;
+  margin-right: 15px;
+  cursor: pointer;
 }
 
-  button.uppy-StatusBar-statusIndicator {
-    @include reset-button;
-    margin-right: 8px;
-    cursor: pointer;
+  .uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator  {
+    width: 15px;
+    margin-right: 7px;
   }
 
 .uppy-StatusBar-actions {
@@ -102,49 +121,53 @@
 }
 
 .uppy-StatusBar.is-waiting .uppy-StatusBar-actions {
-  left: 20px;
-  right: initial;
+  width: 100%;
+  position: static;
+  padding: 0 15px;
 }
 
 .uppy-StatusBar-actionBtn {
-  @include reset-button;
-  font-size: 13px;
-  line-height: 1em;
-  font-weight: 400;
-  padding: 7px 10px;
+  font-size: 12px;
+  padding: 6px;
   border-radius: 4px;
-  cursor: pointer;
-  transition: all 0.2s;
 }
 
-  .uppy-StatusBar-actionBtn:not(:last-child) {
-    margin-right: 5px;
+  .uppy-Dashboard--wide .uppy-StatusBar-actionBtn {
+    padding: 7px 10px;
   }
 
-  .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn {
-    padding: 13px 30px;
-    font-size: 15px;
+  .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
+    font-size: 14px;
+    width: 100%;
+    padding: 15px 10px;
   }
 
-  .uppy-StatusBar-actionBtn--upload {
-    background-color: $color-white;
-    color: $color-cornflower-blue;
-  }
+    .uppy-Dashboard--wide .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
+      padding: 13px 28px;
+      width: auto;
+    }
 
-    .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
-        background-color: $color-cornflower-blue;
-        color: $color-white;
+  .uppy-StatusBar-actionBtn:not(:last-child) {
+    margin-right: 10px;
+  }
 
-        &:hover {
-          background-color: darken($color-cornflower-blue, 20%);
-        }
-      }
+  .uppy-StatusBar:not(.is-waiting) .uppy-StatusBar-actionBtn--upload {
+    background-color: transparent;
+    border: 1px solid $color-white;
+    color: $color-white;
+  }
 
   .uppy-StatusBar-actionBtn--retry {
     background-color: $color-white;
     color: $color-red;
   }
 
+  .uppy-StatusBar-actionBtn--cancel {
+    background-color: lighten($color-asphalt-gray, 8%);
+    border: 1px solid lighten($color-black, 10%);
+    color: $color-white;
+  }
+
 .uppy-StatusBar-details {
   line-height: 12px;
   width: 13px;

+ 14 - 15
src/scss/_url.scss

@@ -1,27 +1,26 @@
 .uppy-Url {
   width: 100%; 
   height: 100%; 
-  display: flex; 
+  display: flex;
+  flex-direction: column;
   justify-content: center; 
   align-items: center;
 }
 
 .uppy-Url-input {
-  width: 80%; 
-  height: 50px; 
-  padding-left: 10px;
-  font-size: 15px;
-  border: 1px solid rgba($color-gray, 0.5);
-  border-right: 0;
+  width: 90%;
+  max-width: 650px;
+  margin-bottom: 15px;
 }
 
+  .uppy-Dashboard--wide .uppy-Url-input  {
+    margin-bottom: 30px;
+  }
+
 .uppy-Url-importButton {
-  @include reset-button();
-  width: 100px;
-  height: 50px;
-  color: $color-white;
-  background-color: $color-cornflower-blue;
-  cursor: pointer;
-  font-size: 16px;
-  padding: 12px;
+  padding: 13px 25px;
 }
+
+  .uppy-Dashboard--wide .uppy-Url-importButton {
+    padding: 13px 35px;
+  }

+ 4 - 2
src/scss/_variables.scss

@@ -5,14 +5,16 @@ $color-black: #000 !default;
 $color-gray: #939393 !default;
 $color-pink: #e02177 !default;
 $color-red: #D32F2F !default;
-$color-green: #7AC824 !default;
+$color-green: #1BB240 !default;
 $color-orange: #F6A623 !default;
 $color-yellow: #FFD600 !default;
 $color-white: #fff !default;
 $color-almost-white: #FAFAFA !default;
-$color-cornflower-blue: #4A90E2 !default;
+$color-cornflower-blue: #2275D7 !default;
 $color-asphalt-gray: #525252 !default;
 
+$color-uppy-pink: #EB2177;
+
 // Z-index
 
 $zIndex-negative: -1000 !default;

+ 56 - 15
src/scss/_webcam.scss

@@ -4,18 +4,23 @@
   display: flex;
   justify-content: center;
   align-items: center;
+  flex-direction: column;
 }
 
 .uppy-Webcam-videoContainer {
   width: 100%;
-  height: 100%;
+  flex: 1;
+  flex-grow: 1;
+  overflow: hidden;
+  background-color: $color-black;
+  // height: 100%;
   // display: flex;
   // justify-content: center;
   // align-items: center;
 }
 
   .uppy-Dashboard--wide .uppy-Webcam-videoContainer {
-    height: initial;
+    // height: initial;
   }
 
 .uppy-Webcam-video {
@@ -30,16 +35,41 @@
   }
 
 .uppy-Webcam-buttonContainer {
-  position: absolute;
-  bottom: 30px;
-  right: 30px;
+  width: 100%;
+  height: 75px;
+  border-top: 1px solid rgba($color-gray, 0.2);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 20px;
+}
+
+.uppy-Webcam-button {
+  width: 45px;
+  height: 45px;
+  border-radius: 50%;
+  background-color: $color-red;
+  color: $color-white;
+  cursor: pointer;
+  transition: all 0.3s;
 }
 
-.uppy-Webcam-recordButton .UppyIcon {
-  width: 50%;
-  height: 50%;
-  position: relative;
-  top: -1px;
+  .uppy-Dashboard--wide .uppy-Webcam-button {
+    width: 60px;
+    height: 60px;
+  }
+
+  .uppy-Webcam-button:hover {
+    background-color: darken($color-red, 10%);
+  }
+
+  .uppy-Webcam-button .UppyIcon {
+    width: 30px;
+    height: 30px;
+  }
+
+.uppy-Webcam-button--picture {
+  margin-right: 12px;
 }
 
 .uppy-Webcam-permissons {
@@ -51,16 +81,27 @@
   height: 100%;
 }
 
-.uppy-Webcam-permissons p {
-  line-height: 1.4;
-}
-
 .uppy-Webcam-Title {
   font-size: 22px;
   line-height: 1.35;
   font-weight: 300;
-  margin-bottom: 30px;
+  margin: 0;
+  margin-bottom: 15px;
   padding: 0 15px;
   max-width: 500px;
   text-align: center;
 }
+
+.uppy-Webcam-permissons p {
+  text-align: center;
+  line-height: 1.45;
+  color: $color-gray;
+  margin: 0;
+}
+
+.uppy-Webcam-permissonsIcon .UppyIcon {
+  width: 100px;
+  height: 75px;
+  color: lighten($color-gray, 15%);
+  margin-bottom: 30px;
+}

+ 1 - 1
src/server/RequestClient.js

@@ -45,7 +45,7 @@ module.exports = class RequestClient {
   }
 
   post (path, data) {
-    fetch(`${this.hostname}/${path}`, {
+    return fetch(`${this.hostname}/${path}`, {
       method: 'post',
       credentials: 'include',
       headers: {

+ 3 - 2
src/views/ProviderView/AuthView.js

@@ -9,10 +9,11 @@ class AuthView extends Component {
   render () {
     const AuthBlock = () => {
       return <div class="uppy-Provider-auth">
+        <div class="uppy-Provider-authIcon">{this.props.pluginIcon()}</div>
         <h1 class="uppy-Provider-authTitle">Please authenticate with <span class="uppy-Provider-authTitleName">{this.props.pluginName}</span><br /> to select files</h1>
-        <button type="button" class="uppy-Provider-authBtn" onclick={this.props.handleAuth}>Connect to {this.props.pluginName}</button>
+        <button type="button" class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn" onclick={this.props.handleAuth}>Connect to {this.props.pluginName}</button>
         {this.props.demo &&
-          <button class="uppy-Provider-authBtnDemo" onclick={this.props.handleDemoAuth}>Proceed with Demo Account</button>
+          <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn" onclick={this.props.handleDemoAuth}>Proceed with Demo Account</button>
         }
       </div>
     }

+ 0 - 5
src/views/ProviderView/Breadcrumb.js

@@ -1,5 +0,0 @@
-const { h } = require('preact')
-
-module.exports = (props) => {
-  return <li><button type="button" onclick={props.getFolder}>{props.title}</button></li>
-}

+ 6 - 1
src/views/ProviderView/Breadcrumbs.js

@@ -1,5 +1,10 @@
 const { h } = require('preact')
-const Breadcrumb = require('./Breadcrumb')
+
+const Breadcrumb = (props) => {
+  return (
+    <li><button type="button" onclick={props.getFolder}>{props.title}</button></li>
+  )
+}
 
 module.exports = (props) => {
   return (

+ 15 - 16
src/views/ProviderView/Browser.js

@@ -1,6 +1,6 @@
 const Breadcrumbs = require('./Breadcrumbs')
 const Filter = require('./Filter')
-const Table = require('./Table')
+const Table = require('./ItemList')
 const { h } = require('preact')
 
 module.exports = (props) => {
@@ -13,26 +13,19 @@ module.exports = (props) => {
   }
 
   return (
-    <div class={`uppy uppy-ProviderBrowser uppy-ProviderBrowser-viewType--${props.viewType}`}>
-      <header class="uppy-ProviderBrowser-header">
-        <div class="uppy-ProviderBrowser-search" aria-hidden={!props.isSearchVisible}>
-          { props.isSearchVisible && <Filter {...props} /> }
-        </div>
-        <div class="uppy-ProviderBrowser-headerBar">
-          <button type="button" class="uppy-ProviderBrowser-searchToggle"
-            onclick={props.toggleSearch}>
-            <svg class="UppyIcon" viewBox="0 0 100 100">
-              <path d="M87.533 80.03L62.942 55.439c3.324-4.587 5.312-10.207 5.312-16.295 0-.312-.043-.611-.092-.908.05-.301.093-.605.093-.922 0-15.36-12.497-27.857-27.857-27.857-.273 0-.536.043-.799.08-.265-.037-.526-.08-.799-.08-15.361 0-27.858 12.497-27.858 27.857 0 .312.042.611.092.909a5.466 5.466 0 0 0-.093.921c0 15.36 12.496 27.858 27.857 27.858.273 0 .535-.043.8-.081.263.038.524.081.798.081 5.208 0 10.071-1.464 14.245-3.963L79.582 87.98a5.603 5.603 0 0 0 3.976 1.647 5.621 5.621 0 0 0 3.975-9.597zM39.598 55.838c-.265-.038-.526-.081-.8-.081-9.16 0-16.612-7.452-16.612-16.612 0-.312-.042-.611-.092-.908.051-.301.093-.605.093-.922 0-9.16 7.453-16.612 16.613-16.612.272 0 .534-.042.799-.079.263.037.525.079.799.079 9.16 0 16.612 7.452 16.612 16.612 0 .312.043.611.092.909-.05.301-.094.604-.094.921 0 9.16-7.452 16.612-16.612 16.612-.274 0-.536.043-.798.081z" />
-            </svg>
-          </button>
-          {Breadcrumbs({
+    <div class={`uppy-ProviderBrowser uppy-ProviderBrowser-viewType--${props.viewType}`}>
+      <div class="uppy-ProviderBrowser-header">
+        <div class={`uppy-ProviderBrowser-headerBar ${!props.showBreadcrumbs ? 'uppy-ProviderBrowser-headerBar--simple' : ''}`}>
+          <div class="uppy-Provider-breadcrumbsIcon">{props.pluginIcon && props.pluginIcon()}</div>
+          {props.showBreadcrumbs && Breadcrumbs({
             getFolder: props.getFolder,
             directories: props.directories,
             title: props.title
           })}
           <button type="button" onclick={props.logout} class="uppy-ProviderBrowser-userLogout">Log out</button>
         </div>
-      </header>
+      </div>
+      { props.showFilter && <Filter {...props} /> }
       {Table({
         columns: [{
           name: 'Name',
@@ -50,7 +43,9 @@ module.exports = (props) => {
         getItemName: props.getItemName,
         getItemIcon: props.getItemIcon,
         handleScroll: props.handleScroll,
-        title: props.title
+        title: props.title,
+        showTitles: props.showTitles,
+        getItemId: props.getItemId
       })}
       <button class="UppyButton--circular UppyButton--blue uppy-ProviderBrowser-doneBtn"
         type="button"
@@ -64,3 +59,7 @@ module.exports = (props) => {
     </div>
   )
 }
+
+// <div class="uppy-Dashboard-actions">
+//  <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Dashboard-actionsBtn" type="button">Select</button>
+// </div>

+ 29 - 15
src/views/ProviderView/Filter.js

@@ -3,13 +3,15 @@ const { h, Component } = require('preact')
 module.exports = class Filter extends Component {
   constructor (props) {
     super(props)
-
+    this.isEmpty = true
     this.handleKeyPress = this.handleKeyPress.bind(this)
+    this.handleClear = this.handleClear.bind(this)
   }
 
-  componentDidMount () {
-    this.input.focus()
-  }
+  // componentDidMount () {
+  //   this.isEmpty = true
+  //   // this.input.focus()
+  // }
 
   handleKeyPress (ev) {
     if (ev.keyCode === 13) {
@@ -17,28 +19,40 @@ module.exports = class Filter extends Component {
       ev.preventDefault()
       return
     }
+    this.isEmpty = !this.input.value.length > 0
     this.props.filterQuery(ev)
   }
 
+  handleClear (ev) {
+    this.input.value = ''
+    this.props.filterQuery()
+  }
+
   render () {
-    return <div style={{ display: 'flex', width: '100%' }}>
+    return <div class="uppy-u-reset uppy-ProviderBrowser-search">
+      <svg class="UppyIcon uppy-ProviderBrowser-searchIcon" viewBox="0 0 100 100">
+        <path d="M87.533 80.03L62.942 55.439c3.324-4.587 5.312-10.207 5.312-16.295 0-.312-.043-.611-.092-.908.05-.301.093-.605.093-.922 0-15.36-12.497-27.857-27.857-27.857-.273 0-.536.043-.799.08-.265-.037-.526-.08-.799-.08-15.361 0-27.858 12.497-27.858 27.857 0 .312.042.611.092.909a5.466 5.466 0 0 0-.093.921c0 15.36 12.496 27.858 27.857 27.858.273 0 .535-.043.8-.081.263.038.524.081.798.081 5.208 0 10.071-1.464 14.245-3.963L79.582 87.98a5.603 5.603 0 0 0 3.976 1.647 5.621 5.621 0 0 0 3.975-9.597zM39.598 55.838c-.265-.038-.526-.081-.8-.081-9.16 0-16.612-7.452-16.612-16.612 0-.312-.042-.611-.092-.908.051-.301.093-.605.093-.922 0-9.16 7.453-16.612 16.613-16.612.272 0 .534-.042.799-.079.263.037.525.079.799.079 9.16 0 16.612 7.452 16.612 16.612 0 .312.043.611.092.909-.05.301-.094.604-.094.921 0 9.16-7.452 16.612-16.612 16.612-.274 0-.536.043-.798.081z" />
+      </svg>
       <input
-        class="uppy-ProviderBrowser-searchInput"
+        class="uppy-u-reset uppy-ProviderBrowser-searchInput"
         type="text"
-        placeholder="Search"
+        placeholder="Filter"
+        aria-label="Filter"
         onkeyup={this.handleKeyPress}
         onkeydown={this.handleKeyPress}
         onkeypress={this.handleKeyPress}
         value={this.props.filterInput}
         ref={(input) => { this.input = input }} />
-      <button
-        class="uppy-ProviderBrowser-searchClose"
-        type="button"
-        onclick={this.props.toggleSearch}>
-        <svg class="UppyIcon" viewBox="0 0 19 19">
-          <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
-        </svg>
-      </button>
+      { !this.isEmpty &&
+        <button
+          class="uppy-u-reset uppy-ProviderBrowser-searchClose"
+          type="button"
+          onclick={this.handleClear}>
+          <svg class="UppyIcon" viewBox="0 0 19 19">
+            <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
+          </svg>
+        </button>
+      }
     </div>
   }
 }

+ 3 - 6
src/views/ProviderView/TableRow.js → src/views/ProviderView/Item.js

@@ -1,9 +1,6 @@
-const cuid = require('cuid')
 const { h } = require('preact')
 
 module.exports = (props) => {
-  const uniqueId = cuid()
-
   const stop = (ev) => {
     if (ev.keyCode === 13) {
       ev.stopPropagation()
@@ -27,21 +24,21 @@ module.exports = (props) => {
           role="option"
           tabindex="0"
           aria-label={`Select ${props.title}`}
-          id={uniqueId}
+          id={props.id}
           checked={props.isChecked}
           disabled={props.isDisabled}
           onchange={props.handleCheckboxClick}
           onkeyup={stop}
           onkeydown={stop}
           onkeypress={stop} />
-        <label for={uniqueId} />
+        <label for={props.id} />
       </div>
       <button type="button"
         class="uppy-ProviderBrowserItem-inner"
         aria-label={`Select ${props.title}`}
         tabindex="0"
         onclick={handleItemClick}>
-        {props.getItemIcon()} {props.title}
+        {props.getItemIcon()} {props.showTitles && props.title}
       </button>
     </li>
   )

+ 7 - 3
src/views/ProviderView/Table.js → src/views/ProviderView/ItemList.js

@@ -1,4 +1,4 @@
-const Row = require('./TableRow')
+const Row = require('./Item')
 const { h } = require('preact')
 
 module.exports = (props) => {
@@ -28,6 +28,7 @@ module.exports = (props) => {
           }
           return Row({
             title: props.getItemName(folder),
+            id: props.getItemId(folder),
             type: 'folder',
             // active: props.activeRow(folder),
             getItemIcon: () => props.getItemIcon(folder),
@@ -35,12 +36,14 @@ module.exports = (props) => {
             isDisabled: isDisabled,
             isChecked: isChecked,
             handleCheckboxClick: (e) => props.toggleCheckbox(e, folder),
-            columns: props.columns
+            columns: props.columns,
+            showTitles: props.showTitles
           })
         })}
         {props.files.map(file => {
           return Row({
             title: props.getItemName(file),
+            id: props.getItemId(file),
             type: 'file',
             // active: props.activeRow(file),
             getItemIcon: () => props.getItemIcon(file),
@@ -48,7 +51,8 @@ module.exports = (props) => {
             isDisabled: false,
             isChecked: props.isChecked(file),
             handleCheckboxClick: (e) => props.toggleCheckbox(e, file),
-            columns: props.columns
+            columns: props.columns,
+            showTitles: props.showTitles
           })
         })}
       </ul>

+ 12 - 3
src/views/ProviderView/index.js

@@ -44,7 +44,10 @@ module.exports = class ProviderView {
 
     // set default options
     const defaultOptions = {
-      viewType: 'list'
+      viewType: 'list',
+      showTitles: true,
+      showFilter: true,
+      showBreadcrumbs: true
     }
 
     // merge default options with the ones set by user
@@ -200,7 +203,7 @@ module.exports = class ProviderView {
   filterQuery (e) {
     const state = this.plugin.getPluginState()
     this.plugin.setPluginState(Object.assign({}, state, {
-      filterInput: e.target.value
+      filterInput: e ? e.target.value : ''
     }))
   }
 
@@ -554,6 +557,7 @@ module.exports = class ProviderView {
     if (!authenticated) {
       return h(AuthView, {
         pluginName: this.plugin.title,
+        pluginIcon: this.plugin.icon,
         demo: this.plugin.opts.demo,
         checkAuth: this.checkAuth,
         handleAuth: this.handleAuth,
@@ -576,12 +580,17 @@ module.exports = class ProviderView {
       isActiveRow: this.isActiveRow,
       isChecked: this.isChecked,
       toggleCheckbox: this.toggleCheckbox,
+      getItemId: this.plugin.getItemId,
       getItemName: this.plugin.getItemName,
       getItemIcon: this.plugin.getItemIcon,
       handleScroll: this.handleScroll,
       done: this.donePicking,
       title: this.plugin.title,
-      viewType: this.opts.viewType
+      viewType: this.opts.viewType,
+      showTitles: this.opts.showTitles,
+      showFilter: this.opts.showFilter,
+      showBreadcrumbs: this.opts.showBreadcrumbs,
+      pluginIcon: this.plugin.icon
     })
 
     return Browser(browserProps)

+ 40 - 7
website/src/docs/dashboard.md

@@ -20,31 +20,43 @@ Dashboard is a universal UI plugin for Uppy:
 ```js
 uppy.use(Dashboard, {
   target: 'body',
-  inline: false,
+  metaFields: [],
   trigger: '#uppy-select-files',
-  plugins: [],
-  maxWidth: 750,
-  maxHeight: 550,
+  inline: false,
+  width: 750,
+  height: 550,
+  thumbnailWidth: 280,
+  defaultTabIcon: defaultTabIcon,
+  showLinkToFileUploadResult: true,
   showProgressDetails: false,
   hideUploadButton: false,
   hideProgressAfterFinish: false,
   note: null,
-  metaFields: [],
   closeModalOnClickOutside: false,
   disableStatusBar: false,
   disableInformer: false,
+  disableThumbnailGenerator: false,
+  disablePageScrollWhenModalOpen: true,
+  proudlyDisplayPoweredByUppy: true,
+  onRequestCloseModal: () => this.closeModal(),
   locale: {
     strings: {
       selectToUpload: 'Select files to upload',
       closeModal: 'Close Modal',
       upload: 'Upload',
-      importFrom: 'Import files from',
+      importFrom: 'Import from',
       dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
       dashboardTitle: 'Uppy Dashboard',
       copyLinkToClipboardSuccess: 'Link copied to clipboard.',
       copyLinkToClipboardFallback: 'Copy the URL below',
+      copyLink: 'Copy link',
       fileSource: 'File source',
       done: 'Done',
+      name: 'Name',
+      removeFile: 'Remove file',
+      editFile: 'Edit file',
+      editing: 'Editing',
+      finishEditingFile: 'Finish editing file',
       localDisk: 'Local Disk',
       myDevice: 'My Device',
       dropPasteImport: 'Drop files here, paste, import from one of the locations above or',
@@ -54,6 +66,18 @@ uppy.use(Dashboard, {
       numberOfSelectedFiles: 'Number of selected files',
       uploadAllNewFiles: 'Upload all new files',
       emptyFolderAdded: 'No files were added from empty folder',
+      uploadComplete: 'Upload complete',
+      resumeUpload: 'Resume upload',
+      pauseUpload: 'Pause upload',
+      retryUpload: 'Retry upload',
+      uploadXFiles: {
+        0: 'Upload %{smart_count} file',
+        1: 'Upload %{smart_count} files'
+      },
+      uploadXNewFiles: {
+        0: 'Upload +%{smart_count} file',
+        1: 'Upload +%{smart_count} files'
+      },
       folderAdded: {
         0: 'Added %{smart_count} file from %{folder}',
         1: 'Added %{smart_count} files from %{folder}'
@@ -98,7 +122,10 @@ Maximum height of the Dashboard in pixels. Used when `inline: true`.
 
 ### `showProgressDetails: false`
 
-Show progress bars for the uploads.
+By default, progress in StatusBar is shown as simple percentage. If you’d like to also display remaining upload size and time, set this to `true`.
+
+`showProgressDetails: false`: Uploading: 45%
+`showProgressDetails: true`: Uploading: 45%・43 MB of 101 MB・8s left
 
 ### `hideUploadButton: false`
 
@@ -140,6 +167,12 @@ Set to true to automatically close the modal when the user clicks outside of it.
 
 By default when Dashboard modal is open, it will disable page scrolling, so when you scroll a list of files in Uppy the website in the background stays still. Set to false to override this behaviour and leave page scrolling intact.
 
+### `proudlyDisplayPoweredByUppy: true`
+
+Uppy is provided for the world for free by the [Transloadit team](https://transloadit.com). In return, we ask that you consider keeping a tiny Uppy logo at the bottom of the Dashboard, so that more people can discover and use Uppy. 
+
+This is entirely optional of course, just set this option to false if you do not wish to display Uppy logo.
+
 ### `disableStatusBar: false`
 
 Dashboard ships with the `StatusBar` plugin that shows upload progress and pause/resume/cancel buttons. If you want, you can disable the StatusBar to provide your custom solution.

+ 15 - 2
website/src/docs/statusbar.md

@@ -21,7 +21,7 @@ uppy.use(StatusBar, {
   locale: {
     strings: {
       uploading: 'Uploading',
-      uploadComplete: 'Upload complete',
+      complete: 'Complete',
       uploadFailed: 'Upload failed',
       pleasePressRetry: 'Please press Retry to upload again',
       paused: 'Paused',
@@ -32,6 +32,12 @@ uppy.use(StatusBar, {
       resumeUpload: 'Resume upload',
       cancelUpload: 'Cancel upload',
       pauseUpload: 'Pause upload',
+      filesUploadedOfTotal: {
+        0: '%{complete} of %{smart_count} file uploaded',
+        1: '%{complete} of %{smart_count} files uploaded'
+      },
+      dataUploadedOfTotal: '%{complete} of %{total}',
+      xTimeLeft: '%{time} left',
       uploadXFiles: {
         0: 'Upload %{smart_count} file',
         1: 'Upload %{smart_count} files'
@@ -51,7 +57,14 @@ DOM element, CSS selector, or plugin to mount the StatusBar into.
 
 ### `hideAfterFinish: true`
 
-Hide StatusBar after upload finish
+Hide StatusBar after upload is complete.
+
+### `showProgressDetails: false`
+
+By default, progress in StatusBar is shown as simple percentage. If you’d like to also display remaining upload size and time, set this to `true`.
+
+`showProgressDetails: false`: Uploading: 45%
+`showProgressDetails: true`: Uploading: 45%・43 MB of 101 MB・8s left
 
 [FileInput]: https://github.com/transloadit/uppy/blob/master/src/plugins/FileInput.js
 [DragDrop]: /docs/dragdrop