Kaynağa Gözat

Merge pull request #1097 from transloadit/status-bar/add-spinner

Status Bar: add spinner, pause/resume as small round buttons, different color for encoding
Artur Paikin 6 yıl önce
ebeveyn
işleme
c5c3c7fed6

+ 1 - 2
packages/@uppy/core/src/_variables.scss

@@ -6,7 +6,6 @@ $font-family-base: system-ui, -apple-system, BLinkMacSystemFont, Segoe UI, Robot
 
 $color-black: #000 !default;
 $color-gray: #939393 !default;
-$color-pink: #e02177 !default;
 $color-red: #D32F2F !default;
 $color-green: #1BB240 !default;
 $color-orange: #F6A623 !default;
@@ -21,7 +20,7 @@ $color-uppy-pink: #EB2177;
 // Sizes
 
 $size-focus-outline: 2px;
-$size-focus-offset: 3px;
+$size-focus-offset: 2px;
 
 // Z-index
 

+ 74 - 7
packages/@uppy/dashboard/src/components/PanelTopBar.js

@@ -1,8 +1,70 @@
 const { h } = require('preact')
 
-function DashboardContentTitle (props) {
-  if (props.newFiles.length) {
-    return props.i18n('xFilesSelected', { smart_count: props.newFiles.length })
+const uploadStates = {
+  'STATE_ERROR': 'error',
+  'STATE_WAITING': 'waiting',
+  'STATE_PREPROCESSING': 'preprocessing',
+  'STATE_UPLOADING': 'uploading',
+  'STATE_POSTPROCESSING': 'postprocessing',
+  'STATE_COMPLETE': 'complete',
+  'STATE_PAUSED': 'paused'
+}
+
+function getUploadingState (isAllErrored, isAllComplete, isAllPaused, files = {}) {
+  if (isAllErrored) {
+    return uploadStates.STATE_ERROR
+  }
+
+  if (isAllComplete) {
+    return uploadStates.STATE_COMPLETE
+  }
+
+  if (isAllPaused) {
+    return uploadStates.STATE_PAUSED
+  }
+
+  let state = uploadStates.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 uploadStates.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 !== uploadStates.STATE_UPLOADING) {
+      state = uploadStates.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 !== uploadStates.STATE_UPLOADING && state !== uploadStates.STATE_PREPROCESSING) {
+      state = uploadStates.STATE_POSTPROCESSING
+    }
+  }
+  return state
+}
+
+function UploadStatus (props) {
+  const uploadingState = getUploadingState(
+    props.isAllErrored,
+    props.isAllComplete,
+    props.isAllPaused,
+    props.files
+  )
+
+  switch (uploadingState) {
+    case 'uploading':
+      return props.i18n('uploadingXFiles', { smart_count: props.inProgressNotPausedFiles.length })
+    case 'preprocessing':
+    case 'postprocessing':
+      return props.i18n('processingXFiles', { smart_count: props.processingFiles.length })
+    case 'paused':
+      return props.i18n('uploadPaused')
+    case 'waiting':
+      return props.i18n('xFilesSelected', { smart_count: props.newFiles.length })
+    case 'complete':
+      return props.i18n('uploadComplete')
   }
 }
 
@@ -15,11 +77,16 @@ function PanelTopBar (props) {
 
   return (
     <div class="uppy-DashboardContent-bar">
-      <button class="uppy-DashboardContent-back"
-        type="button"
-        onclick={props.cancelAll}>{props.i18n('cancel')}</button>
+      <div>
+        {!props.isAllComplete
+        ? <button class="uppy-DashboardContent-back"
+          type="button"
+          onclick={props.cancelAll}>{props.i18n('cancel')}</button>
+          : null
+        }
+      </div>
       <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
-        <DashboardContentTitle {...props} />
+        <UploadStatus {...props} />
       </div>
       { allowNewUpload &&
         <button class="uppy-DashboardContent-addMore"

+ 77 - 18
packages/@uppy/dashboard/src/index.js

@@ -7,7 +7,7 @@ const Informer = require('@uppy/informer')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
 const toArray = require('@uppy/utils/lib/toArray')
-const prettyBytes = require('prettier-bytes')
+// const prettyBytes = require('prettier-bytes')
 const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
 const { defaultTabIcon } = require('./components/icons')
 
@@ -76,6 +76,7 @@ module.exports = class Dashboard extends Plugin {
         uploadAllNewFiles: 'Upload all new files',
         emptyFolderAdded: 'No files were added from empty folder',
         uploadComplete: 'Upload complete',
+        uploadPaused: 'Upload paused',
         resumeUpload: 'Resume upload',
         pauseUpload: 'Pause upload',
         retryUpload: 'Retry upload',
@@ -88,6 +89,14 @@ module.exports = class Dashboard extends Plugin {
           0: 'Upload %{smart_count} file',
           1: 'Upload %{smart_count} files'
         },
+        uploadingXFiles: {
+          0: 'Uploading %{smart_count} file',
+          1: 'Uploading %{smart_count} files'
+        },
+        processingXFiles: {
+          0: 'Processing %{smart_count} file',
+          1: 'Processing %{smart_count} files'
+        },
         uploadXNewFiles: {
           0: 'Upload +%{smart_count} file',
           1: 'Upload +%{smart_count} files'
@@ -521,28 +530,70 @@ module.exports = class Dashboard extends Plugin {
     const pluginState = this.getPluginState()
     const { files, capabilities, allowNewUpload } = state
 
+    // TODO: move this to Core, to share between Status Bar and Dashboard
+    // (and any other plugin that might need it, too)
     const newFiles = Object.keys(files).filter((file) => {
       return !files[file].progress.uploadStarted
     })
+
+    const uploadStartedFiles = Object.keys(files).filter((file) => {
+      return files[file].progress.uploadStarted
+    })
+
+    const pausedFiles = Object.keys(files).filter((file) => {
+      return files[file].isPaused
+    })
+
+    const completeFiles = Object.keys(files).filter((file) => {
+      return files[file].progress.uploadComplete
+    })
+
+    const erroredFiles = Object.keys(files).filter((file) => {
+      return files[file].error
+    })
+
     const inProgressFiles = Object.keys(files).filter((file) => {
       return !files[file].progress.uploadComplete &&
-             files[file].progress.uploadStarted &&
-             !files[file].isPaused
+             files[file].progress.uploadStarted
     })
 
-    let inProgressFilesArray = []
-    inProgressFiles.forEach((file) => {
-      inProgressFilesArray.push(files[file])
+    const inProgressNotPausedFiles = inProgressFiles.filter((file) => {
+      return !files[file].isPaused
     })
 
-    let totalSize = 0
-    let totalUploadedSize = 0
-    inProgressFilesArray.forEach((file) => {
-      totalSize = totalSize + (file.progress.bytesTotal || 0)
-      totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
+    const processingFiles = Object.keys(files).filter((file) => {
+      return files[file].progress.preprocess || files[file].progress.postprocess
     })
-    totalSize = prettyBytes(totalSize)
-    totalUploadedSize = prettyBytes(totalUploadedSize)
+
+    const isUploadStarted = uploadStartedFiles.length > 0
+
+    const isAllComplete = state.totalProgress === 100 &&
+      completeFiles.length === Object.keys(files).length &&
+      processingFiles.length === 0
+
+    const isAllErrored = isUploadStarted &&
+      erroredFiles.length === uploadStartedFiles.length
+
+    const isAllPaused = inProgressFiles.length !== 0 &&
+      pausedFiles.length === inProgressFiles.length
+    // const isAllPaused = inProgressNotPausedFiles.length === 0 &&
+    //   !isAllComplete &&
+    //   !isAllErrored &&
+    //   uploadStartedFiles.length > 0
+
+    // let inProgressNotPausedFilesArray = []
+    // inProgressNotPausedFiles.forEach((file) => {
+    //   inProgressNotPausedFilesArray.push(files[file])
+    // })
+
+    // let totalSize = 0
+    // let totalUploadedSize = 0
+    // inProgressNotPausedFilesArray.forEach((file) => {
+    //   totalSize = totalSize + (file.progress.bytesTotal || 0)
+    //   totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
+    // })
+    // totalSize = prettyBytes(totalSize)
+    // totalUploadedSize = prettyBytes(totalUploadedSize)
 
     const attachRenderFunctionToTarget = (target) => {
       const plugin = this.uppy.getPlugin(target.id)
@@ -588,8 +639,18 @@ module.exports = class Dashboard extends Plugin {
     return DashboardUI({
       state,
       modal: pluginState,
-      newFiles,
       files,
+      newFiles,
+      uploadStartedFiles,
+      completeFiles,
+      erroredFiles,
+      inProgressFiles,
+      inProgressNotPausedFiles,
+      processingFiles,
+      isUploadStarted,
+      isAllComplete,
+      isAllErrored,
+      isAllPaused,
       totalFileCount: Object.keys(files).length,
       totalProgress: state.totalProgress,
       allowNewUpload,
@@ -600,9 +661,6 @@ module.exports = class Dashboard extends Plugin {
       getPlugin: this.uppy.getPlugin,
       progressindicators: progressindicators,
       autoProceed: this.uppy.opts.autoProceed,
-      hideUploadButton: this.opts.hideUploadButton,
-      hideRetryButton: this.opts.hideRetryButton,
-      hidePauseResumeCancelButtons: this.opts.hidePauseResumeCancelButtons,
       id: this.id,
       closeModal: this.requestCloseModal,
       handleClickOutside: this.handleClickOutside,
@@ -693,7 +751,8 @@ module.exports = class Dashboard extends Plugin {
         target: this,
         hideUploadButton: this.opts.hideUploadButton,
         hideRetryButton: this.opts.hideRetryButton,
-        hidePauseResumeCancelButtons: this.opts.hidePauseResumeCancelButtons,
+        hidePauseResumeButton: this.opts.hidePauseResumeButton,
+        hideCancelButton: this.opts.hideCancelButton,
         showProgressDetails: this.opts.showProgressDetails,
         hideAfterFinish: this.opts.hideProgressAfterFinish,
         locale: this.opts.locale

+ 7 - 3
packages/@uppy/dashboard/src/style.scss

@@ -633,8 +633,8 @@
 }
 
 .uppy-DashboardItem-preview {
-  width: 60px;
-  height: 60px;
+  width: 50px;
+  height: 50px;
   border-bottom: 0;
   position: relative;
   display: flex;
@@ -836,7 +836,7 @@
 
 .uppy-DashboardItem-action {
   position: absolute;
-  top: 23px;
+  top: 18px;
   right: 10px;
   z-index: $zIndex-3;
 
@@ -1026,6 +1026,10 @@
   }
 }
 
+.uppy-DashboardItem.is-processing .uppy-DashboardItem-progress {
+  opacity: 0;
+}
+
 .uppy-DashboardItem.is-complete {
   .uppy-DashboardItem-progressIndicator {
     cursor: default;

+ 1 - 0
packages/@uppy/informer/src/style.scss

@@ -33,6 +33,7 @@
     opacity: 0;
     transform: translateY(350%);
     transition: all 300ms ease-in;
+    z-index: $zIndex-negative;
   }
 
 .uppy-Informer p {

+ 82 - 33
packages/@uppy/status-bar/src/StatusBar.js

@@ -50,6 +50,17 @@ function togglePauseResume (props) {
 module.exports = (props) => {
   props = props || {}
 
+  const { newFiles,
+    allowNewUpload,
+    isUploadInProgress,
+    isAllPaused,
+    resumableUploads,
+    error,
+    hideUploadButton,
+    hidePauseResumeButton,
+    hideCancelButton,
+    hideRetryButton } = props
+
   const uploadState = props.uploadState
 
   let progressValue = props.totalProgress
@@ -78,11 +89,18 @@ module.exports = (props) => {
     (uploadState === statusBarStates.STATE_WAITING && !props.newFiles > 0) ||
     (uploadState === statusBarStates.STATE_COMPLETE && props.hideAfterFinish)
 
-  const showUploadButton = props.newFiles && !props.hideUploadButton && props.allowNewUpload
-  const showRetryButton = props.error && !props.hideRetryButton
-  const showCancelButton = !props.hidePauseResumeCancelButtons &&
+  const showUploadBtn = !error && newFiles &&
+    !isUploadInProgress && !isAllPaused &&
+    allowNewUpload && !hideUploadButton
+  const showCancelBtn = !hideCancelButton &&
+    uploadState !== statusBarStates.STATE_WAITING &&
+    uploadState !== statusBarStates.STATE_COMPLETE
+  const showPauseResumeBtn = resumableUploads && !hidePauseResumeButton &&
     uploadState !== statusBarStates.STATE_WAITING &&
+    uploadState !== statusBarStates.STATE_PREPROCESSING &&
+    uploadState !== statusBarStates.STATE_POSTPROCESSING &&
     uploadState !== statusBarStates.STATE_COMPLETE
+  const showRetryBtn = error && !hideRetryButton
 
   const progressClassNames = `uppy-StatusBar-progress
                            ${progressMode ? 'is-' + progressMode : ''}`
@@ -90,8 +108,7 @@ module.exports = (props) => {
   const statusBarClassNames = classNames(
     { 'uppy-Root': props.isTargetDOMEl },
     'uppy-StatusBar',
-    `is-${uploadState}`,
-    { 'uppy-StatusBar--detailedProgress': props.showProgressDetails }
+    `is-${uploadState}`
   )
 
   return (
@@ -104,9 +121,10 @@ module.exports = (props) => {
         aria-valuenow={progressValue} />
       {progressBarContent}
       <div class="uppy-StatusBar-actions">
-        { showUploadButton ? <UploadBtn {...props} uploadState={uploadState} /> : null }
-        { showRetryButton ? <RetryBtn {...props} /> : null }
-        { showCancelButton ? <CancelBtn {...props} /> : null }
+        { showUploadBtn ? <UploadBtn {...props} uploadState={uploadState} /> : null }
+        { showRetryBtn ? <RetryBtn {...props} /> : null }
+        { showPauseResumeBtn ? <PauseResumeButton {...props} /> : null }
+        { showCancelBtn ? <CancelBtn {...props} /> : null }
       </div>
     </div>
   )
@@ -125,7 +143,7 @@ const UploadBtn = (props) => {
     class={uploadBtnClassNames}
     aria-label={props.i18n('uploadXFiles', { smart_count: props.newFiles })}
     onclick={props.startUpload}>
-    {props.newFiles && props.uploadStarted
+    {props.newFiles && props.isUploadStarted
       ? props.i18n('uploadXNewFiles', { smart_count: props.newFiles })
       : props.i18n('uploadXFiles', { smart_count: props.newFiles })
     }
@@ -141,39 +159,43 @@ const RetryBtn = (props) => {
 
 const CancelBtn = (props) => {
   return <button type="button"
-    class="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--cancel"
+    class="uppy-u-reset uppy-StatusBar-actionCircleBtn"
+    title={props.i18n('cancel')}
     aria-label={props.i18n('cancel')}
-    onclick={props.cancelAll}>{props.i18n('cancel')}</button>
+    onclick={props.cancelAll}>
+    <svg aria-hidden="true" class="UppyIcon" width="8" height="8" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
+      <path d="M5.21 4.104l1.658 1.658-1.106 1.106-1.658-1.659-1.659 1.659L1.34 5.762l1.658-1.658L1.34 2.445 2.445 1.34l1.659 1.658L5.762 1.34l1.106 1.105-1.659 1.659z" fill-rule="evenodd" />
+    </svg>
+  </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" />
+const PauseResumeButton = (props) => {
+  const { isAllPaused, i18n } = props
+  const title = isAllPaused ? i18n('resume') : i18n('pause')
+
+  return <button title={title} class="uppy-u-reset uppy-StatusBar-actionCircleBtn" type="button" onclick={() => togglePauseResume(props)}>
+    {isAllPaused
+      ? <svg aria-hidden="true" class="UppyIcon" width="8" height="8" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
+        <path d="M6.736 3.852l-4.472 2.84V1.075z" fill-rule="evenodd" />
+      </svg>
+      : <svg aria-hidden="true" class="UppyIcon" width="8" height="8" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
+        <path d="M1 1h2v6H1zM5 1h2v6H5z" fill-rule="evenodd" />
       </svg>
     }
   </button>
 }
 
+const LoadingSpinner = (props) => {
+  return <svg class="uppy-StatusBar-spinner" width="14" height="14" xmlns="http://www.w3.org/2000/svg">
+    <path d="M13.983 6.547c-.12-2.509-1.64-4.893-3.939-5.936-2.48-1.127-5.488-.656-7.556 1.094C.524 3.367-.398 6.048.162 8.562c.556 2.495 2.46 4.52 4.94 5.183 2.932.784 5.61-.602 7.256-3.015-1.493 1.993-3.745 3.309-6.298 2.868-2.514-.434-4.578-2.349-5.153-4.84a6.226 6.226 0 0 1 2.98-6.778C6.34.586 9.74 1.1 11.373 3.493c.407.596.693 1.282.842 1.988.127.598.073 1.197.161 1.794.078.525.543 1.257 1.15.864.525-.341.49-1.05.456-1.592-.007-.15.02.3 0 0" fill-rule="evenodd" />
+  </svg>
+}
+
 const ProgressBarProcessing = (props) => {
   const value = Math.round(props.value * 100)
 
   return <div class="uppy-StatusBar-content">
+    <LoadingSpinner {...props} />
     {props.mode === 'determinate' ? `${value}% \u00B7 ` : ''}
     {props.message}
   </div>
@@ -187,6 +209,26 @@ const ProgressDetails = (props) => {
   </div>
 }
 
+const UploadNewlyAddedFiles = (props) => {
+  const uploadBtnClassNames = classNames(
+    'uppy-u-reset',
+    'uppy-c-btn',
+    'uppy-StatusBar-actionBtn'
+  )
+
+  return <div class="uppy-StatusBar-statusSecondary">
+    <div class="uppy-StatusBar-statusSecondaryHint">
+      { props.i18n('xMoreFilesAdded', { smart_count: props.newFiles }) }
+    </div>
+    <button type="button"
+      class={uploadBtnClassNames}
+      aria-label={props.i18n('uploadXFiles', { smart_count: props.newFiles })}
+      onclick={props.startUpload}>
+      {props.i18n('upload')}
+    </button>
+  </div>
+}
+
 const ThrottledProgressDetails = throttle(ProgressDetails, 500, { leading: true, trailing: true })
 
 const ProgressBarUploading = (props) => {
@@ -195,13 +237,18 @@ const ProgressBarUploading = (props) => {
   }
 
   const title = props.isAllPaused ? props.i18n('paused') : props.i18n('uploading')
+  const showUploadNewlyAddedFiles = props.newFiles && props.isUploadStarted
 
   return (
     <div class="uppy-StatusBar-content" aria-label={title} title={title}>
-      { !props.hidePauseResumeCancelButtons && <PauseResumeButtons {...props} /> }
+      { !props.isAllPaused ? <LoadingSpinner {...props} /> : null }
       <div class="uppy-StatusBar-status">
         <div class="uppy-StatusBar-statusPrimary">{title}: {props.totalProgress}%</div>
-        { !props.isAllPaused && <ThrottledProgressDetails {...props} /> }
+        { !props.isAllPaused && !showUploadNewlyAddedFiles && props.showProgressDetails
+          ? <ThrottledProgressDetails {...props} />
+          : null
+        }
+        { showUploadNewlyAddedFiles ? <UploadNewlyAddedFiles {...props} /> : null }
       </div>
     </div>
   )
@@ -222,7 +269,9 @@ const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
   return (
     <div class="uppy-StatusBar-content" role="alert">
       <span class="uppy-StatusBar-contentPadding">{i18n('uploadFailed')}.</span>
-      { !hideRetryButton && <span class="uppy-StatusBar-contentPadding">{i18n('pleasePressRetry')}</span> }
+      {!hideRetryButton &&
+        <span class="uppy-StatusBar-contentPadding">{i18n('pleasePressRetry')}</span>
+      }
       <span class="uppy-StatusBar-details"
         aria-label={error}
         data-microtip-position="top"

+ 49 - 21
packages/@uppy/status-bar/src/index.js

@@ -21,6 +21,7 @@ module.exports = class StatusBar extends Plugin {
     const defaultLocale = {
       strings: {
         uploading: 'Uploading',
+        upload: 'Upload',
         complete: 'Complete',
         uploadFailed: 'Upload failed',
         pleasePressRetry: 'Please press Retry to upload again',
@@ -28,11 +29,13 @@ module.exports = class StatusBar extends Plugin {
         error: 'Error',
         retry: 'Retry',
         cancel: 'Cancel',
+        pause: 'Pause',
+        resume: 'Resume',
         pressToRetry: 'Press to retry',
-        retryUpload: 'Retry upload',
-        resumeUpload: 'Resume upload',
-        cancelUpload: 'Cancel upload',
-        pauseUpload: 'Pause upload',
+        // retryUpload: 'Retry upload',
+        // resumeUpload: 'Resume upload',
+        // cancelUpload: 'Cancel upload',
+        // pauseUpload: 'Pause upload',
         filesUploadedOfTotal: {
           0: '%{complete} of %{smart_count} file uploaded',
           1: '%{complete} of %{smart_count} files uploaded'
@@ -46,6 +49,10 @@ module.exports = class StatusBar extends Plugin {
         uploadXNewFiles: {
           0: 'Upload +%{smart_count} file',
           1: 'Upload +%{smart_count} files'
+        },
+        xMoreFilesAdded: {
+          0: '%{smart_count} more file added',
+          1: '%{smart_count} more files added'
         }
       }
     }
@@ -55,7 +62,8 @@ module.exports = class StatusBar extends Plugin {
       target: 'body',
       hideUploadButton: false,
       hideRetryButton: false,
-      hidePauseResumeCancelButtons: false,
+      hidePauseResumeButton: false,
+      hideCancelButton: false,
       showProgressDetails: false,
       locale: defaultLocale,
       hideAfterFinish: true
@@ -140,45 +148,60 @@ module.exports = class StatusBar extends Plugin {
       error
     } = state
 
-    const uploadStartedFiles = Object.keys(files).filter((file) => {
-      return files[file].progress.uploadStarted
-    })
+    // TODO: move this to Core, to share between Status Bar and Dashboard
+    // (and any other plugin that might need it, too)
     const newFiles = Object.keys(files).filter((file) => {
       return !files[file].progress.uploadStarted &&
         !files[file].progress.preprocess &&
         !files[file].progress.postprocess
     })
+
+    const uploadStartedFiles = Object.keys(files).filter((file) => {
+      return files[file].progress.uploadStarted
+    })
+
+    const pausedFiles = uploadStartedFiles.filter((file) => {
+      return files[file].isPaused
+    })
+
     const completeFiles = Object.keys(files).filter((file) => {
       return files[file].progress.uploadComplete
     })
+
     const erroredFiles = Object.keys(files).filter((file) => {
       return files[file].error
     })
+
     const inProgressFiles = Object.keys(files).filter((file) => {
       return !files[file].progress.uploadComplete &&
-             files[file].progress.uploadStarted &&
-             !files[file].isPaused
+             files[file].progress.uploadStarted
     })
+
+    const inProgressNotPausedFiles = inProgressFiles.filter((file) => {
+      return !files[file].isPaused
+    })
+
     const startedFiles = Object.keys(files).filter((file) => {
       return files[file].progress.uploadStarted ||
         files[file].progress.preprocess ||
         files[file].progress.postprocess
     })
+
     const processingFiles = Object.keys(files).filter((file) => {
       return files[file].progress.preprocess || files[file].progress.postprocess
     })
 
-    let inProgressFilesArray = inProgressFiles.map((file) => {
+    let inProgressNotPausedFilesArray = inProgressNotPausedFiles.map((file) => {
       return files[file]
     })
 
-    const totalSpeed = prettyBytes(this.getTotalSpeed(inProgressFilesArray))
-    const totalETA = prettyETA(this.getTotalETA(inProgressFilesArray))
+    const totalSpeed = prettyBytes(this.getTotalSpeed(inProgressNotPausedFilesArray))
+    const totalETA = prettyETA(this.getTotalETA(inProgressNotPausedFilesArray))
 
     // total size and uploaded size
     let totalSize = 0
     let totalUploadedSize = 0
-    inProgressFilesArray.forEach((file) => {
+    inProgressNotPausedFilesArray.forEach((file) => {
       totalSize = totalSize + (file.progress.bytesTotal || 0)
       totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
     })
@@ -194,25 +217,29 @@ module.exports = class StatusBar extends Plugin {
     const isAllErrored = isUploadStarted &&
       erroredFiles.length === uploadStartedFiles.length
 
-    const isAllPaused = inProgressFiles.length === 0 &&
-      !isAllComplete &&
-      !isAllErrored &&
-      uploadStartedFiles.length > 0
+    const isAllPaused = inProgressFiles.length !== 0 &&
+      pausedFiles.length === inProgressFiles.length
+    // const isAllPaused = inProgressFiles.length === 0 &&
+    //   !isAllComplete &&
+    //   !isAllErrored &&
+    //   uploadStartedFiles.length > 0
+
+    const isUploadInProgress = inProgressFiles.length > 0
 
     const resumableUploads = capabilities.resumableUploads || false
 
     return StatusBarUI({
       error,
-      uploadState: this.getUploadingState(isAllErrored, isAllComplete, files || {}),
+      uploadState: this.getUploadingState(isAllErrored, isAllComplete, state.files || {}),
       allowNewUpload,
       totalProgress,
       totalSize,
       totalUploadedSize,
-      uploadStarted: uploadStartedFiles.length,
       isAllComplete,
       isAllPaused,
       isAllErrored,
       isUploadStarted,
+      isUploadInProgress,
       complete: completeFiles.length,
       newFiles: newFiles.length,
       numUploads: startedFiles.length,
@@ -229,7 +256,8 @@ module.exports = class StatusBar extends Plugin {
       showProgressDetails: this.opts.showProgressDetails,
       hideUploadButton: this.opts.hideUploadButton,
       hideRetryButton: this.opts.hideRetryButton,
-      hidePauseResumeCancelButtons: this.opts.hidePauseResumeCancelButtons,
+      hidePauseResumeButton: this.opts.hidePauseResumeButton,
+      hideCancelButton: this.opts.hideCancelButton,
       hideAfterFinish: this.opts.hideAfterFinish,
       isTargetDOMEl: this.isTargetDOMEl
     })

+ 113 - 45
packages/@uppy/status-bar/src/style.scss

@@ -44,13 +44,6 @@
   background-color: $color-red;
 }
 
-.uppy-StatusBar.is-complete .uppy-StatusBar-content {
-  // width: 100%;
-  // text-align: center;
-  // padding-left: 0;
-  // justify-content: center;
-}
-
 .uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator {
   cursor: default;
   color: $color-green;
@@ -70,33 +63,37 @@
   transition: background-color, width .3s ease-out;
 
   &.is-indeterminate {
-    $stripe-color: darken($color-cornflower-blue, 10%);
+    background-color: $color-orange;
+    $stripe-color: darken($color-orange, 10%);
     background-size: 64px 64px;
     background-image: linear-gradient(45deg, $stripe-color 25%, transparent 25%, transparent 50%, $stripe-color 50%, $stripe-color 75%, transparent 75%, transparent);
-    animation: statusBarProgressStripes 1s linear infinite;
+    animation: uppy-StatusBar-ProgressStripes 1s linear infinite;
   }
 }
 
+  @keyframes uppy-StatusBar-ProgressStripes {
+    from { background-position: 0 0; }
+    to { background-position: 64px 0; }
+  }
+
 .uppy-StatusBar.is-waiting .uppy-StatusBar-progress {
   display: none;
 }
 
-@keyframes statusBarProgressStripes {
-  from { background-position: 64px 0; }
-  to { background-position: 0 0; }
-}
-
 .uppy-StatusBar-content {
   display: flex;
   align-items: center;
   position: relative;
   z-index: $zIndex-3;
-  padding-left: 15px;
+  padding-left: 10px;
   white-space: nowrap;
   text-overflow: ellipsis;
-  // color: $color-white;
   color: $color-black;
   height: 100%;
+
+  .uppy-size--md & {
+    padding-left: 15px;
+  }
 }
 
 .uppy-StatusBar-contentPadding {
@@ -104,39 +101,67 @@
 }
 
 .uppy-StatusBar-status {
-  line-height: 1.5;
+  line-height: 1.4;
   font-weight: normal;
   display: flex;
   flex-direction: column;
   justify-content: center;
+  max-width: 170px;
+  overflow-x: hidden;
+
+  .uppy-size--md & {
+    line-height: 1.5;
+    max-width: 400px;
+  }
 }
 
 .uppy-StatusBar-statusPrimary {
-  font-size: 13px;
+  font-size: 12px;
+  font-weight: 400;
+
+  .uppy-size--md & {
+    font-size: 13px;
+  }
 }
 
 .uppy-StatusBar-statusSecondary {
-  font-size: 11px;
-  display: none;
+  font-size: 10px;
+  // line-height: 1;
+  display: inline-block;
   color: rgba($color-asphalt-gray, 0.8);
-  max-width: 170px;
   text-overflow: ellipsis;
   white-space: nowrap;
-  overflow: hidden;
+  overflow-x: hidden;
+  // max-width: 170px;
 
   .uppy-size--md & {
-    max-width: 500px;
+    font-size: 11px;
+    // max-width: 500px;
   }
 }
 
-  .uppy-StatusBar--detailedProgress .uppy-StatusBar-statusSecondary {
+  .uppy-StatusBar-statusSecondaryHint {
     display: inline-block;
+    vertical-align: middle;
+    margin-right: 5px;
+
+    .uppy-size--md & {
+      margin-right: 8px;
+    }
   }
 
+  // .uppy-StatusBar--detailedProgress .uppy-StatusBar-statusSecondary {
+  //   display: inline-block;
+  // }
+
 .uppy-StatusBar-statusIndicator {
   color: $color-asphalt-gray;
-  margin-right: 15px;
+  margin-right: 10px;
   cursor: pointer;
+
+  .uppy-size--md & {
+    margin-right: 12px;
+  }
 }
 
   .uppy-StatusBar-statusIndicator svg {
@@ -165,16 +190,49 @@
   background-color: $color-almost-white;
 }
 
+.uppy-StatusBar-actionCircleBtn {
+  display: block;
+  width: 20px;
+  height: 20px;
+  line-height: 22px;
+  border-radius: 50%;
+  cursor: pointer;
+  color: rgba($color-black, 0.6);
+  background-color: rgba($color-gray, 0.3);
+  text-align: center;
+}
+
+  .uppy-StatusBar-actionCircleBtn:not(:last-child) {
+    margin-right: 8px;
+  }
+
+  // .uppy-size--md .uppy-StatusBar-actionCircleBtn {
+  //   padding: 1px 4px;
+  // }
+
+  .uppy-StatusBar-actionCircleBtn svg {
+    width: 12px;
+    height: 12px;
+    fill: currentColor;
+    // vertical-align: middle;
+  }
+
 .uppy-StatusBar-actionBtn {
-  font-size: 12px;
-  padding: 6px;
-  // border-radius: 4px;
+  display: inline-block;
+  vertical-align: middle;
+  font-size: 10px;
+  // margin-left: 6px;
+  // margin-right: 6px;
   color: $color-cornflower-blue;
-}
 
-  .uppy-size--md .uppy-StatusBar-actionBtn {
-    padding: 3px 5px;
+  .uppy-size--md & {
+    font-size: 11px;
   }
+}
+
+  // .uppy-size--md .uppy-StatusBar-actionBtn {
+  //   padding: 2px 4px;
+  // }
 
   .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
     font-size: 14px;
@@ -203,20 +261,6 @@
     color: $color-cornflower-blue;
   }
 
-  // .uppy-StatusBar-actionBtn--retry {
-  //   background-color: $color-white;
-  //   color: $color-red;
-  //   border: 1px solid transparent;
-  // }
-
-  // .uppy-StatusBar-actionBtn--cancel {
-  //   // background-color: lighten($color-asphalt-gray, 8%);
-  //   // border: 1px solid lighten($color-black, 10%);
-  //   background-color: transparent;
-  //   border: 1px solid $color-white;
-  //   color: $color-white;
-  // }
-
 .uppy-StatusBar-details {
   line-height: 12px;
   width: 13px;
@@ -242,3 +286,27 @@
   line-height: 1.3;
   word-wrap: break-word;
 }
+
+.uppy-StatusBar-spinner {
+  animation-name: uppy-StatusBar-spinnerAnimation;
+  animation-duration: 1s;
+  animation-iteration-count: infinite;
+  animation-timing-function: linear;
+  margin-right: 10px;
+  fill: $color-cornflower-blue;
+}
+
+  .uppy-StatusBar.is-preprocessing .uppy-StatusBar-spinner,
+  .uppy-StatusBar.is-postprocessing .uppy-StatusBar-spinner {
+    fill: $color-orange;
+  }
+
+
+  @keyframes uppy-StatusBar-spinnerAnimation {
+    0% {
+      transform: rotate(0deg);
+    }
+    100% {
+      transform: rotate(360deg);
+    }
+  }

+ 18 - 4
website/src/docs/dashboard.md

@@ -129,26 +129,40 @@ By default, when a file upload has completed, the file icon in the Dashboard tur
 
 ### `showProgressDetails: false`
 
-By default, progress in StatusBar is shown as a simple percentage. If you would like to also display remaining upload size and time, set this to `true`.
+Passed to the Status Bar plugin used in the Dashboard.
+
+By default, progress in Status Bar is shown as a simple percentage. If you would 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`
 
+Passed to the Status Bar plugin used in the Dashboard.
+
 Hide the upload button. Use this if you are providing a custom upload button somewhere, and using the `uppy.upload()` API.
 
 ### `hideRetryButton: false`
 
+Passed to the Status Bar plugin used in the Dashboard.
+
 Hide the retry button. Use this if you are providing a custom retry button somewhere, and using the `uppy.retryAll()` or `uppy.retryUpload(fileID)` API.
 
-### `hidePauseResumeCancelButtons: false`
+### `hidePauseResumeButton: false`
+
+Passed to the Status Bar plugin used in the Dashboard. 
+
+Hide pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for example). Use this if you are providing custom cancel or pause/resume buttons somewhere, and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API.
+
+### `hideCancelButton: false`
+
+Passed to the Status Bar plugin used in the Dashboard.
 
-Hide the cancel or pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for example). Use this if you are providing custom cancel or pause/resume buttons somewhere, and using the `uppy.pauseResume(fileID)`, `uppy.cancelAll()` or `uppy.removeFile(fileID)` API.
+Hide the cancel button. Use this if you are providing a custom retry button somewhere, and using the `uppy.cancelAll()` API.
 
 ### `hideProgressAfterFinish: false`
 
-Hide StatusBar after the upload has finished.
+Hide Status Bar after the upload has finished.
 
 ### `showSelectedFiles: true`
 

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

@@ -77,13 +77,21 @@ By default, progress in the Status Bar is shown as simple percentage. If you wou
 `showProgressDetails: false`: Uploading: 45%
 `showProgressDetails: true`: Uploading: 45%・43 MB of 101 MB・8s left
 
+### `hideUploadButton: false`
+
+Hide the upload button. Use this if you are providing a custom upload button somewhere, and using the `uppy.upload()` API.
+
 ### `hideRetryButton: false`
 
 Hide the retry button. Use this if you are providing a custom retry button somewhere, and using the `uppy.retryAll()` or `uppy.retryUpload(fileID)` API.
 
-### `hidePauseResumeCancelButtons: false`
+### `hidePauseResumeButton: false`
+
+Hide pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for example). Use this if you are providing custom cancel or pause/resume buttons somewhere, and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API.
+
+### `hideCancelButton: false`
 
-Hide the cancel or pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for example). Use this if you are providing custom cancel or pause/resume buttons somewhere, and using the `uppy.pauseResume(fileID)`, `uppy.cancelAll()` or `uppy.removeFile(fileID)` API.
+Hide the cancel button. Use this if you are providing a custom retry button somewhere, and using the `uppy.cancelAll()` API.
 
 ### `locale: {}`