Browse Source

Merge pull request #132 from transloadit/api-improvements

Api improvements
Artur Paikin 8 years ago
parent
commit
c6cf55d68c
40 changed files with 642 additions and 372 deletions
  1. 14 0
      CHANGELOG.md
  2. 1 0
      example/index.html
  3. 22 15
      example/main.js
  4. 1 6
      package.json
  5. 45 40
      src/core/Core.js
  6. 6 6
      src/core/Translator.js
  7. 3 1
      src/locales/en_US.js
  8. 1 1
      src/locales/ru_RU.js
  9. 20 0
      src/plugins/Dashboard/ActionBrowseTagline.js
  10. 59 54
      src/plugins/Dashboard/Dashboard.js
  11. 6 2
      src/plugins/Dashboard/FileCard.js
  12. 15 5
      src/plugins/Dashboard/FileItem.js
  13. 2 1
      src/plugins/Dashboard/FileItemProgress.js
  14. 16 4
      src/plugins/Dashboard/FileList.js
  15. 63 29
      src/plugins/Dashboard/StatusBar.js
  16. 19 1
      src/plugins/Dashboard/Tabs.js
  17. 1 1
      src/plugins/Dashboard/icons.js
  18. 25 14
      src/plugins/Dashboard/index.js
  19. 16 40
      src/plugins/Dummy.js
  20. 13 0
      src/plugins/Multipart.js
  21. 7 0
      src/scss/_common.scss
  22. 148 87
      src/scss/_dashboard.scss
  23. 0 9
      src/scss/_fileinput.scss
  24. 1 1
      src/scss/_informer.scss
  25. 4 0
      src/scss/_variables.scss
  26. 6 7
      test/unit/core.spec.js
  27. 3 3
      test/unit/translator.spec.js
  28. 70 0
      website/src/_posts/2016-11-15-0.11.md
  29. 15 6
      website/src/api-usage-example.ejs
  30. 9 8
      website/src/examples/dashboard/app.es6
  31. 1 1
      website/src/examples/dragdrop/app.es6
  32. 2 5
      website/src/examples/i18n/app.es6
  33. 2 2
      website/src/examples/i18n/app.html
  34. 4 0
      website/src/frontpage-code-sample.ejs
  35. 0 9
      website/src/guide/overview.md
  36. BIN
      website/src/images/uppy-demo.mp4
  37. 9 5
      website/themes/uppy/layout/index.ejs
  38. 2 1
      website/themes/uppy/layout/partials/frontpage-code-sample.html
  39. 0 1
      website/themes/uppy/layout/partials/social.ejs
  40. 11 7
      website/themes/uppy/source/css/_index.scss

+ 14 - 0
CHANGELOG.md

@@ -51,7 +51,11 @@ To be released: December 23, 2016.
 
 - [ ] presets: Add basic preset or plugin that mimics Transloadit’s jQuery plugin (#28)
 - [ ] dashboard: “list” view in addition to current “grid” view (@arturi)
+<<<<<<< HEAD
+- [ ] core: i18n for plugins as strings in options (@arturi)
+=======
 - [ ] core: i18n for plugins as strings in options (@arturi) 
+>>>>>>> master
 - [ ] server: investigate a pluggable uppy-server (express / koa for now) (@ifedapoolarewaju)
 
 ## 0.12.0
@@ -63,7 +67,11 @@ Theme: Responsive. Cancel. Feedback. ES6 Server
 - [ ] core: figure out import/require for core and plugins (@arturi)
 - [ ] dashboard: consider `<progress>` element for progressbar, like here https://overcast.fm/+BtuxMygVg/ (@arturi)
 - [ ] dashboard: more icons for file types? (@arturi)
+<<<<<<< HEAD
+- [x] meta: create a demo GIF, showcasing Uppy Dashboard for the main page, like https://zeit.co/blog/next (@arturi)
+=======
 - [ ] meta: create a demo GIF, showcasing Uppy Dashboard for the main page, like https://zeit.co/blog/next (@arturi)
+>>>>>>> master
 - [ ] server: add pre-commit and lint-staged (@arturi)
 - [ ] server: re-do build setup: building at `deploy` and `prepublish` when typing `npm run release:patch` 0.0.1 -> 0.0.2 (@ifedapoolarewaju)
 - [ ] server: re-do build setup: es6 `src` -> es5 `lib` (use plugin packs from Uppy)
@@ -76,8 +84,14 @@ Theme: Responsive. Cancel. Feedback. ES6 Server
 - [ ] webcam: stop using the webcam (green light off) after the picture is taken / tab is hidden (do we need mount/unmount maybe?)
 - [x] core: allow usage without `new`, start renaming `Core()` to `Uppy()` in examples (@arturi)
 - [x] core: api — consider Yosh’s feedback and proposals https://gist.github.com/yoshuawuyts/b5e5b3e7aacbee85a3e61b8a626709ab, come up with follow up questions (@arturi)
+<<<<<<< HEAD
+- [x] dashboard: local mode — no acquire plugins / external services, just DnD — ActionBrowseTagline (@arturi)
+- [x] dashboard: only show pause/resume when tus is used (@arturi)
+- [x] dashboard: cancel uploads button for multipart (@arturi)
+=======
 - [x] dashboard: local mode — no acquire plugins / external services, just DnD (@arturi)
 - [x] dashboard: only show pause/resume when tus is used, something else for multipart (@arturi)
+>>>>>>> master
 - [x] dashboard: responsive design — stage 1 (@arturi)
 - [x] meta: write 0.11 release blog post (@arturi)
 

+ 1 - 0
example/index.html

@@ -2,6 +2,7 @@
 <html>
   <head>
     <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>uppy</title>
   </head>
   <body>

+ 22 - 15
example/main.js

@@ -1,24 +1,31 @@
-import { Core,
-         Dummy,
-         Dashboard,
-         GoogleDrive,
-         Webcam,
-         Tus10,
-         MetaData,
-         Informer } from '../src/index.js'
+import Uppy from '../src/core/index.js'
+// import Dummy from '../src/plugins/Dummy.js'
+import Dashboard from '../src/plugins/Dashboard'
+// import GoogleDrive from '../src/plugins/GoogleDrive'
+// import Webcam from '../src/plugins/Webcam'
+import Tus10 from '../src/plugins/Tus10'
+import MetaData from '../src/plugins/MetaData'
+import Informer from '../src/plugins/Informer'
+// import Multipart from '../src/plugins/Multipart'
+
+const PROTOCOL = location.protocol === 'https:' ? 'https' : 'http'
+const TUS_ENDPOINT = PROTOCOL + '://master.tus.io/files/'
 
 // import ru_RU from '../src/locales/ru_RU.js'
 // import MagicLog from '../src/plugins/MagicLog'
 // import PersistentState from '../src/plugins/PersistentState'
 
-const uppy = new Core({debug: true, autoProceed: false})
-  // .use(FileInput, {text: 'Выбрать файл', pretty: true})
-  // .use(PersistentState)
+// const dummy = Dummy({bla: 'boop'})
+
+const uppy = Uppy({debug: true, autoProceed: false})
   .use(Dashboard, {trigger: '#uppyModalOpener', inline: false})
-  .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
-  .use(Dummy, {target: Dashboard})
-  .use(Webcam, {target: Dashboard})
-  .use(Tus10, {endpoint: 'https://tusd.tus.io/files/', resume: true})
+  // .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
+  // .use(Dummy, {target: Dashboard})
+  // .use(dummy)
+  // .use(Webcam, {target: Dashboard})
+  // .use(Multipart, {endpoint: '//api2.transloadit.com'})
+  .use(Tus10, {endpoint: TUS_ENDPOINT, resume: true})
+  // .use(Multipart)
   .use(Informer, {target: Dashboard})
   .use(MetaData, {
     fields: [

+ 1 - 6
package.json

@@ -65,21 +65,16 @@
     "sass": "0.5.0",
     "selenium-webdriver": "2.53.3",
     "tap-spec": "4.1.1",
-    "tape": "4.4.0",
+    "tape": "4.6.2",
     "uppy-server": "0.0.7",
     "watchify": "3.7.0"
   },
   "dependencies": {
-    "classnames": "^2.2.5",
-    "deep-freeze-strict": "1.1.1",
     "drag-drop": "2.11.0",
     "es6-promise": "3.2.1",
-    "mime-types": "2.1.11",
     "namespace-emitter": "1.0.0",
     "pretty-bytes": "3.0.1",
-    "redux": "^3.6.0",
     "tus-js-client": "1.4.1",
-    "uppy-base": "git+https://github.com/hedgerh/uppy-base.git",
     "whatwg-fetch": "1.0.0",
     "yo-yo": "1.3.1"
   },

+ 45 - 40
src/core/Core.js

@@ -1,22 +1,21 @@
 import Utils from '../core/Utils'
 import Translator from '../core/Translator'
 import ee from 'namespace-emitter'
-// import deepFreeze from 'deep-freeze-strict'
 import UppySocket from './UppySocket'
 import en_US from '../locales/en_US'
-// import throttle from 'throttle-debounce/throttle'
+// import deepFreeze from 'deep-freeze-strict'
 
 /**
  * Main Uppy core
  *
  * @param {object} opts general options, like locales, to show modal or not to show
  */
-export default class Core {
+class Uppy {
   constructor (opts) {
     // set default options
     const defaultOptions = {
-      // load English as the default locales
-      locales: en_US,
+      // load English as the default locale
+      locale: en_US,
       autoProceed: true,
       debug: false
     }
@@ -24,16 +23,14 @@ export default class Core {
     // Merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
-    // Dictates in what order different plugin types are ran:
-    this.types = [ 'presetter', 'orchestrator', 'progressindicator',
-                    'acquirer', 'modifier', 'uploader', 'presenter', 'debugger']
-
-    this.type = 'core'
+    // // Dictates in what order different plugin types are ran:
+    // this.types = [ 'presetter', 'orchestrator', 'progressindicator',
+    //                 'acquirer', 'modifier', 'uploader', 'presenter', 'debugger']
 
     // Container for different types of plugins
     this.plugins = {}
 
-    this.translator = new Translator({locales: this.opts.locales})
+    this.translator = new Translator({locale: this.opts.locale})
     this.i18n = this.translator.translate.bind(this.translator)
     this.getState = this.getState.bind(this)
     this.updateMeta = this.updateMeta.bind(this)
@@ -51,8 +48,8 @@ export default class Core {
       totalProgress: 0
     }
 
+    // for debugging and testing
     if (this.opts.debug) {
-      // for debugging and testing
       global.UppyState = this.state
       global.uppyLog = ''
       global.UppyAddFile = this.addFile.bind(this)
@@ -61,7 +58,7 @@ export default class Core {
   }
 
   /**
-   * Iterate on all plugins and run `update` on them. Called each time when state changes
+   * Iterate on all plugins and run `update` on them. Called each time state changes
    *
    */
   updateAll (state) {
@@ -83,9 +80,6 @@ export default class Core {
 
     this.state = newState
     this.updateAll(this.state)
-
-    // this.log('Updating state with: ')
-    // this.log(newState)
   }
 
   /**
@@ -93,6 +87,7 @@ export default class Core {
    *
    */
   getState () {
+    // use deepFreeze for debugging
     // return deepFreeze(this.state)
     return this.state
   }
@@ -160,6 +155,7 @@ export default class Core {
     const updatedFiles = Object.assign({}, this.getState().files)
     delete updatedFiles[fileID]
     this.setState({files: updatedFiles})
+    this.log(`Removed file: ${fileID}`)
   }
 
   addThumbnail (fileID) {
@@ -250,6 +246,13 @@ export default class Core {
       this.removeFile(fileID)
     })
 
+    this.on('core:cancel-all', () => {
+      const files = this.getState().files
+      Object.keys(files).forEach((file) => {
+        this.removeFile(files[file].id)
+      })
+    })
+
     this.on('core:upload-started', (fileID, upload) => {
       const updatedFiles = Object.assign({}, this.getState().files)
       const updatedFile = Object.assign({}, updatedFiles[fileID],
@@ -332,17 +335,18 @@ export default class Core {
  */
   use (Plugin, opts) {
     // Prepare props to pass to plugins
-    const props = {
-      getState: this.getState.bind(this),
-      setState: this.setState.bind(this),
-      updateMeta: this.updateMeta.bind(this),
-      addFile: this.addFile.bind(this),
-      i18n: this.i18n.bind(this),
-      bus: this.ee,
-      log: this.log.bind(this)
-    }
+    // const props = {
+    //   getState: this.getState.bind(this),
+    //   setState: this.setState.bind(this),
+    //   updateMeta: this.updateMeta.bind(this),
+    //   addFile: this.addFile.bind(this),
+    //   i18n: this.i18n.bind(this),
+    //   bus: this.ee,
+    //   log: this.log.bind(this)
+    // }
+
     // Instantiate
-    const plugin = new Plugin(this, opts, props)
+    const plugin = new Plugin(this, opts)
     const pluginName = plugin.id
     this.plugins[plugin.type] = this.plugins[plugin.type] || []
 
@@ -366,6 +370,7 @@ export default class Core {
     }
 
     this.plugins[plugin.type].push(plugin)
+    plugin.install()
 
     return this
   }
@@ -428,27 +433,21 @@ export default class Core {
     return this.socket
   }
 
-  installAll () {
-    Object.keys(this.plugins).forEach((pluginType) => {
-      this.plugins[pluginType].forEach((plugin) => {
-        plugin.install()
-      })
-    })
-  }
+  // installAll () {
+  //   Object.keys(this.plugins).forEach((pluginType) => {
+  //     this.plugins[pluginType].forEach((plugin) => {
+  //       plugin.install(this)
+  //     })
+  //   })
+  // }
 
 /**
  * Initializes actions, installs all plugins (by iterating on them and calling `install`), sets options
  *
- * (In the past was used to run a waterfall of runType plugin packs, like so:
- * All preseters(data) --> All acquirers(data) --> All uploaders(data) --> done)
  */
   run () {
     this.log('Core is run, initializing actions, installing plugins...')
 
-    // setInterval(() => {
-    //   this.updateAll(this.state)
-    // }, 1000)
-
     this.actions()
 
     // Forse set `autoProceed` option to false if there are multiple selector Plugins active
@@ -457,8 +456,14 @@ export default class Core {
     // }
 
     // Install all plugins
-    this.installAll()
+    // this.installAll()
 
     return
   }
 }
+
+export default function (opts) {
+  if (!(this instanceof Uppy)) {
+    return new Uppy(opts)
+  }
+}

+ 6 - 6
src/core/Translator.js

@@ -16,11 +16,11 @@ import en_US from '../locales/en_US'
 export default class Translator {
   constructor (opts) {
     const defaultOptions = {
-      locales: en_US
+      locale: en_US
     }
     this.opts = Object.assign({}, defaultOptions, opts)
-    this.locales = this.opts.locales
-    this.locales.strings = Object.assign({}, en_US.strings, this.opts.locales.strings)
+    this.locale = this.opts.locale
+    this.locale.strings = Object.assign({}, en_US.strings, this.opts.locale.strings)
   }
 
 /**
@@ -66,10 +66,10 @@ export default class Translator {
  */
   translate (key, options) {
     if (options && options.smart_count) {
-      var plural = this.locales.pluralize(options.smart_count)
-      return this.interpolate(this.opts.locales.strings[key][plural], options)
+      var plural = this.locale.pluralize(options.smart_count)
+      return this.interpolate(this.opts.locale.strings[key][plural], options)
     }
 
-    return this.interpolate(this.opts.locales.strings[key], options)
+    return this.interpolate(this.opts.locale.strings[key], options)
   }
 }

+ 3 - 1
src/locales/en_US.js

@@ -30,7 +30,9 @@ en_US.strings = {
   copyLinkToClipboardFallback: 'Copy the URL below',
   done: 'Done',
   localDisk: 'Local Disk',
-  dropPasteImport: 'Drop files here, paste or import from one of the locations above',
+  dropPasteImport: 'Drop files here, paste, import from one of the locations above or',
+  dropPaste: 'Drop files here, paste or',
+  browse: 'browse',
   fileProgress: 'File progress: upload speed and ETA',
   numberOfSelectedFiles: 'Number of selected files',
   uploadAllNewFiles: 'Upload all new files'

+ 1 - 1
src/locales/ru_RU.js

@@ -11,7 +11,7 @@ ru_RU.strings = {
   },
   upload: 'Загрузить',
   localDisk: 'Диск',
-  dropPasteImport: 'Перенесите файлы сюда, вставьте из буфера обмена или импортируйте:'
+  dropPasteImport: 'Перенесите файлы сюда, вставьте из буфера обмена или импортируйте из сервисов выше'
 }
 
 ru_RU.pluralize = function (n) {

+ 20 - 0
src/plugins/Dashboard/ActionBrowseTagline.js

@@ -0,0 +1,20 @@
+import html from '../../core/html'
+
+export default (props) => {
+  return html`
+    <span>
+      ${props.acquirers.length === 0
+        ? props.i18n('dropPaste')
+        : props.i18n('dropPasteImport')
+      }
+      <button type="button"
+              class="UppyDashboard-browse"
+              onclick=${(ev) => {
+                const input = document.querySelector(`${props.container} .UppyDashboard-input`)
+                input.click()
+              }}>${props.i18n('browse')}</button>
+      <input class="UppyDashboard-input" type="file" name="files[]" multiple="true"
+             onchange=${props.handleInputChange} />
+    </span>
+  `
+}

+ 59 - 54
src/plugins/Dashboard/Dashboard.js

@@ -3,7 +3,7 @@ import FileList from './FileList'
 import Tabs from './Tabs'
 import FileCard from './FileCard'
 import UploadBtn from './UploadBtn'
-import ProgressCircle from './ProgressCircle'
+// import ProgressCircle from './ProgressCircle'
 import StatusBar from './StatusBar'
 import { isTouchDevice, toArray } from '../../core/Utils'
 import { closeIcon } from './icons'
@@ -57,18 +57,20 @@ export default function Dashboard (props) {
           role="dialog"
           onpaste=${handlePaste}>
 
+    <button class="UppyDashboard-close"
+            aria-label="${props.i18n('closeModal')}"
+            title="${props.i18n('closeModal')}"
+            onclick=${props.hideModal}>${closeIcon()}</button>
+
     <div class="UppyDashboard-overlay"
          onclick=${props.hideModal}>
-      <button class="UppyDashboard-close"
-              aria-label="${props.i18n('closeModal')}"
-              title="${props.i18n('closeModal')}"
-              onclick=${props.hideModal}>${closeIcon()}</button>
     </div>
 
     <div class="UppyDashboard-inner" tabindex="0">
       <div class="UppyDashboard-innerWrap">
 
         ${Tabs({
+          files: props.files,
           handleInputChange: handleInputChange,
           acquirers: props.acquirers,
           container: props.container,
@@ -86,42 +88,40 @@ export default function Dashboard (props) {
           i18n: props.i18n
         })}
 
-        ${FileList({
-          files: props.files,
-          showFileCard: props.showFileCard,
-          showProgressDetails: props.showProgressDetails,
-          totalProgress: props.totalProgress,
-          totalFileCount: props.totalFileCount,
-          info: props.info,
-          i18n: props.i18n,
-          log: props.log,
-          removeFile: props.removeFile,
-          pauseAll: props.pauseAll,
-          resumeAll: props.resumeAll,
-          pauseUpload: props.pauseUpload,
-          startUpload: props.startUpload
-        })}
+        <div class="UppyDashboard-filesContainer">
+
+          ${FileList({
+            acquirers: props.acquirers,
+            files: props.files,
+            handleInputChange: handleInputChange,
+            container: props.container,
+            showFileCard: props.showFileCard,
+            showProgressDetails: props.showProgressDetails,
+            totalProgress: props.totalProgress,
+            totalFileCount: props.totalFileCount,
+            info: props.info,
+            i18n: props.i18n,
+            log: props.log,
+            removeFile: props.removeFile,
+            pauseAll: props.pauseAll,
+            resumeAll: props.resumeAll,
+            pauseUpload: props.pauseUpload,
+            startUpload: props.startUpload,
+            cancelUpload: props.cancelUpload,
+            resumableUploads: props.resumableUploads
+          })}
+
+          <div class="UppyDashboard-actions">
+            ${!props.autoProceed && props.newFiles.length > 0
+              ? UploadBtn({
+                i18n: props.i18n,
+                startUpload: props.startUpload,
+                newFileCount: props.newFiles.length
+              })
+              : null
+            }
+          </div>
 
-        <div class="UppyDashboard-actions">
-          ${!props.autoProceed && props.newFiles.length > 0
-            ? UploadBtn({
-              i18n: props.i18n,
-              startUpload: props.startUpload,
-              newFileCount: props.newFiles.length
-            })
-            : null
-          }
-
-          ${false && props.uploadStartedFiles.length > 0
-            ? ProgressCircle({
-              totalProgress: props.totalProgress,
-              isAllPaused: props.isAllPaused,
-              isAllComplete: props.isAllComplete,
-              pauseAll: props.pauseAll,
-              resumeAll: props.resumeAll
-            })
-            : null
-          }
         </div>
 
         <div class="UppyDashboardContent-panel"
@@ -138,20 +138,25 @@ export default function Dashboard (props) {
         </div>
 
         <div class="UppyDashboard-progressindicators">
-          ${props.uploadStartedFiles.length > 0 && !props.isAllComplete
-            ? StatusBar({
-              totalProgress: props.totalProgress,
-              isAllComplete: props.isAllComplete,
-              isAllPaused: props.isAllPaused,
-              pauseAll: props.pauseAll,
-              resumeAll: props.resumeAll,
-              complete: props.completeFiles.length,
-              inProgress: props.uploadStartedFiles.length,
-              totalSpeed: props.totalSpeed,
-              totalETA: props.totalETA
-            })
-            : null
-          }
+          ${StatusBar({
+            totalProgress: props.totalProgress,
+            totalFileCount: props.totalFileCount,
+            uploadStartedFiles: props.uploadStartedFiles,
+            isAllComplete: props.isAllComplete,
+            isAllPaused: props.isAllPaused,
+            isUploadStarted: props.isUploadStarted,
+            pauseAll: props.pauseAll,
+            resumeAll: props.resumeAll,
+            cancelAll: props.cancelAll,
+            complete: props.completeFiles.length,
+            inProgress: props.inProgress,
+            totalSpeed: props.totalSpeed,
+            totalETA: props.totalETA,
+            startUpload: props.startUpload,
+            newFileCount: props.newFiles.length,
+            i18n: props.i18n,
+            resumableUploads: props.resumableUploads
+          })}
 
           ${props.progressindicators.map((target) => {
             return target.render(props.state)

+ 6 - 2
src/plugins/Dashboard/FileCard.js

@@ -61,7 +61,11 @@ export default function fileCard (props) {
         </div>`
       : null
     }
-    <button class="UppyButton--circular UppyButton--blue UppyButton--sizeM UppyDashboardFileCard-done" type="button"
-            title="Finish editing file" onclick=${() => props.done(meta, file.id)}>${checkIcon()}</button>
+    <div class="UppyDashboard-actions">
+      <button class="UppyButton--circular UppyButton--blue UppyButton--sizeM UppyDashboardFileCard-done"
+              type="button"
+              title="Finish editing file"
+              onclick=${() => props.done(meta, file.id)}>${checkIcon()}</button>
+    </div>
     </div>`
 }

+ 15 - 5
src/plugins/Dashboard/FileItem.js

@@ -34,7 +34,8 @@ export default function fileItem (props) {
   return html`<li class="UppyDashboardItem
                         ${uploadInProgress ? 'is-inprogress' : ''}
                         ${isUploaded ? 'is-complete' : ''}
-                        ${isPaused ? 'is-paused' : ''}"
+                        ${isPaused ? 'is-paused' : ''}
+                        ${props.resumableUploads ? 'is-resumable' : ''}"
                   id="uppy_${file.id}"
                   title="${file.meta.name}">
       <div class="UppyDashboardItem-preview">
@@ -46,10 +47,19 @@ export default function fileItem (props) {
           <button class="UppyDashboardItem-progressBtn"
                   title="${isUploaded
                           ? 'upload complete'
-                          : file.isPaused ? 'resume upload' : 'pause upload'}"
+                          : props.resumableUploads
+                            ? file.isPaused
+                              ? 'resume upload'
+                              : 'pause upload'
+                            : 'cancel upload'
+                        }"
                   onclick=${(ev) => {
                     if (isUploaded) return
-                    props.pauseUpload(file.id)
+                    if (props.resumableUploads) {
+                      props.pauseUpload(file.id)
+                    } else {
+                      props.cancelUpload(file.id)
+                    }
                   }}>
             ${FileItemProgress({
               progress: file.progress.percentage,
@@ -58,8 +68,8 @@ export default function fileItem (props) {
           </button>
           ${props.showProgressDetails
             ? html`<div class="UppyDashboardItem-progressInfo"
-                        title="${props.i18n('localDisk')}"
-                        aria-label="${props.i18n('localDisk')}">
+                        title="${props.i18n('fileProgress')}"
+                        aria-label="${props.i18n('fileProgress')}">
                 ${!file.isPaused && !isUploaded
                   ? html`<span>${prettyETA(getETA(file.progress))} ・ ↑ ${prettyBytes(getSpeed(file.progress))}/s</span>`
                   : null

+ 2 - 1
src/plugins/Dashboard/FileItemProgress.js

@@ -8,7 +8,7 @@ export default function (props) {
     <svg width="70" height="70" viewBox="0 0 36 36" class="UppyIcon UppyIcon-progressCircle">
       <g class="progress-group">
         <circle r="15" cx="18" cy="18" stroke-width="2" fill="none" class="bg"/>
-        <circle r="15" cx="18" cy="18" transform="rotate(-90, 18, 18)" stroke-width="2" fill="none" stroke-dasharray="100" stroke-dashoffset="${100 - props.progress || 100}" class="progress"/>
+        <circle r="15" cx="18" cy="18" transform="rotate(-90, 18, 18)" stroke-width="2" fill="none" stroke-dasharray="100" stroke-dashoffset="${100 - props.progress}" class="progress"/>
       </g>
       <polygon transform="translate(3, 3)" points="12 20 12 10 20 15" class="play"/>
       <g transform="translate(14.5, 13)" class="pause">
@@ -16,5 +16,6 @@ export default function (props) {
         <rect x="5" y="0" width="2" height="10" rx="0" />
       </g>
       <polygon transform="translate(2, 3)" points="14 22.5 7 15.2457065 8.99985857 13.1732815 14 18.3547104 22.9729883 9 25 11.1005634" class="check"/>
+      <polygon class="cancel" transform="translate(2, 2)" points="19.8856516 11.0625 16 14.9481516 12.1019737 11.0625 11.0625 12.1143484 14.9481516 16 11.0625 19.8980263 12.1019737 20.9375 16 17.0518484 19.8856516 20.9375 20.9375 19.8980263 17.0518484 16 20.9375 12"></polygon>
   </svg>`
 }

+ 16 - 4
src/plugins/Dashboard/FileList.js

@@ -1,5 +1,6 @@
 import html from '../../core/html'
 import FileItem from './FileItem'
+import ActionBrowseTagline from './ActionBrowseTagline'
 import { dashboardBgIcon } from './icons'
 
 export default (props) => {
@@ -7,9 +8,18 @@ export default (props) => {
                          ${props.totalFileCount === 0 ? 'UppyDashboard-files--noFiles' : ''}">
       ${props.totalFileCount === 0
        ? html`<div class="UppyDashboard-bgIcon">
-        ${dashboardBgIcon()}
-        <h4 class="UppyDashboard-dropFilesTitle">${props.i18n('dropPasteImport')}</h4>
-       </div>`
+          ${dashboardBgIcon()}
+          <h3 class="UppyDashboard-dropFilesTitle">
+            ${ActionBrowseTagline({
+              acquirers: props.acquirers,
+              container: props.container,
+              handleInputChange: props.handleInputChange,
+              i18n: props.i18n
+            })}
+          </h3>
+          <input class="UppyDashboard-input" type="file" name="files[]" multiple="true"
+                 onchange=${props.handleInputChange} />
+         </div>`
        : null
       }
       ${Object.keys(props.files).map((fileID) => {
@@ -21,7 +31,9 @@ export default (props) => {
           log: props.log,
           i18n: props.i18n,
           removeFile: props.removeFile,
-          pauseUpload: props.pauseUpload
+          pauseUpload: props.pauseUpload,
+          cancelUpload: props.cancelUpload,
+          resumableUploads: props.resumableUploads
         })
       })}
     </ul>`

+ 63 - 29
src/plugins/Dashboard/StatusBar.js

@@ -1,44 +1,78 @@
 import html from '../../core/html'
-// import { makeCachingFunction } from '../../core/Utils'
-
-// let cachedElement = makeCachingFunction()
-
-const togglePauseResume = (props) => {
-  if (props.isAllComplete) return
-
-  if (props.isAllPaused) {
-    return props.resumeAll()
-  }
-
-  return props.pauseAll()
-}
 
 export default (props) => {
   props = props || {}
 
-  let statusBar = html`
-    <div class="UppyDashboard-statusBar" onclick=${() => togglePauseResume(props)}>
-      ${props.isAllPaused
-        ? html`<svg class="UppyIcon UppyDashboard-statusBarAction" 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>`
-        : html`<svg class="UppyIcon UppyDashboard-statusBarAction" 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>`
-      }
+  const isHidden = props.totalFileCount === 0 || !props.isUploadStarted
+
+  return html`
+    <div class="UppyDashboard-statusBar
+                ${props.isAllComplete ? 'is-complete' : ''}"
+                aria-hidden="${isHidden}">
+
       <div class="UppyDashboard-statusBarProgress" style="width: ${props.totalProgress}%"></div>
-      <div class="UppyDashboard-statusBarText">
-        ${!props.isAllComplete
+      <div class="UppyDashboard-statusBarContent">
+        ${props.isUploadStarted && !props.isAllComplete
           ? !props.isAllPaused
-            ? `Uploading... ${props.complete} / ${props.inProgress}${props.totalProgress || 0}%・${props.totalETA}・↑ ${props.totalSpeed}/s`
-            : `Paused・${props.totalProgress}%`
+            ? html`<span>${pauseResumeButtons(props)} Uploading... ${props.complete} / ${props.inProgress}${props.totalProgress || 0}%・${props.totalETA}・↑ ${props.totalSpeed}/s</span>`
+            : html`<span>${pauseResumeButtons(props)} Paused・${props.totalProgress}%</span>`
+          : null
+          }
+        ${props.isAllComplete
+          ? html`<span><svg class="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>Upload complete・${props.totalProgress}%</span>`
           : null
         }
       </div>
     </div>
   `
+}
+
+// ${!props.autoProceed && props.newFileCount > 0
+//   ? startUpload(props)
+//   : null
+// }
+
+// const startUpload = (props) => {
+//   return html`<button type="button" onclick=${props.startUpload}>
+//     Upload
+//     <sup class="UppyDashboard-uploadCountf"
+//          title="${props.i18n('numberOfSelectedFiles')}"
+//          aria-label="${props.i18n('numberOfSelectedFiles')}">
+//       ${props.newFileCount}
+//     </sup>
+//   </button>`
+// }
+
+const pauseResumeButtons = (props) => {
+  console.log(props.resumableUploads)
+  return html`<button class="UppyDashboard-statusBarAction" type="button" onclick=${() => togglePauseResume(props)}>
+    ${props.resumableUploads
+      ? props.isAllPaused
+        ? html`<svg 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>`
+        : html`<svg 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>`
+      : html`<svg 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>`
+}
 
-  return statusBar
+const togglePauseResume = (props) => {
+  if (props.isAllComplete) return
 
-  // return cachedElement(statusBar, 1000)
+  if (!props.resumableUploads) {
+    return props.cancelAll()
+  }
+
+  if (props.isAllPaused) {
+    return props.resumeAll()
+  }
+
+  return props.pauseAll()
 }

+ 19 - 1
src/plugins/Dashboard/Tabs.js

@@ -1,12 +1,30 @@
 import html from '../../core/html'
+import ActionBrowseTagline from './ActionBrowseTagline'
 import { localIcon } from './icons'
 
 export default (props) => {
+  const isHidden = Object.keys(props.files).length === 0
+
+  if (props.acquirers.length === 0) {
+    return html`
+      <div class="UppyDashboardTabs" aria-hidden="${isHidden}">
+        <h3 class="UppyDashboardTabs-title">
+        ${ActionBrowseTagline({
+          acquirers: props.acquirers,
+          container: props.container,
+          handleInputChange: props.handleInputChange,
+          i18n: props.i18n
+        })}
+        </h3>
+      </div>
+    `
+  }
+
   return html`<div class="UppyDashboardTabs">
     <nav>
       <ul class="UppyDashboardTabs-list" role="tablist">
         <li class="UppyDashboardTab">
-          <button class="UppyDashboardTab-btn UppyDashboard-focus"
+          <button type="button" class="UppyDashboardTab-btn UppyDashboard-focus"
                   role="tab"
                   tabindex="0"
                   onclick=${(ev) => {

+ 1 - 1
src/plugins/Dashboard/icons.js

@@ -45,7 +45,7 @@ export function localIcon () {
 
 export function closeIcon () {
   return html`<svg class="UppyIcon" width="14px" height="14px" viewBox="0 0 19 19">
-    <polygon points="17.3182539 17.2324466 9.93955339 9.85374611 9.586 9.50019272 9.23244661 9.85374611 1.85374611 17.2324466 2.56085289 17.2324466 1.93955339 16.6111471 1.93955865 17.3182486 9.31803946 9.93954813 9.67158232 9.58599474 9.31803419 9.23244661 1.93955339 1.85396581 1.93961588 2.56101008 2.56091538 1.93949089 1.85375137 1.93955865 9.23245187 9.31803946 9.586 9.67157706 9.93954813 9.31803946 17.3182486 1.93955865 16.6111471 1.93955339 17.2324466 2.56085289 17.2324466 1.85374611 9.85374611 9.23244661 9.50019272 9.586 9.85374611 9.93955339 17.2324466 17.3182539 17.9395534 16.6111471 10.5608529 9.23244661 10.5608529 9.93955339 17.9395534 2.56085289 18.2931068 2.2072995 17.9395534 1.85374611 17.3182539 1.23244661 16.9647058 0.878898482 16.6111524 1.23244135 9.23245187 8.61092215 9.93954813 8.61092215 2.56084763 1.23244135 2.20723173 0.87883598 1.85368362 1.23250911 1.23238412 1.85402831 0.878955712 2.20758169 1.23244661 2.56107259 8.61092741 9.93955339 8.61092215 9.23245187 1.23244135 16.6111524 0.878898482 16.9647058 1.23244661 17.3182539 1.85374611 17.9395534 2.2072995 18.2931068 2.56085289 17.9395534 9.93955339 10.5608529 9.23244661 10.5608529 16.6111471 17.9395534"></polygon>
+    <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>`
 }
 

+ 25 - 14
src/plugins/Dashboard/index.js

@@ -39,6 +39,7 @@ export default class DashboardUI extends Plugin {
     this.handleDrop = this.handleDrop.bind(this)
     this.pauseAll = this.pauseAll.bind(this)
     this.resumeAll = this.resumeAll.bind(this)
+    this.cancelAll = this.cancelAll.bind(this)
     this.render = this.render.bind(this)
     this.install = this.install.bind(this)
   }
@@ -168,14 +169,14 @@ export default class DashboardUI extends Plugin {
       })
     })
 
-    bus.on('core:success', (uploadedCount) => {
-      bus.emit(
-        'informer',
-        `${this.core.i18n('files', {'smart_count': uploadedCount})} successfully uploaded, Sir!`,
-        'info',
-        6000
-      )
-    })
+    // bus.on('core:success', (uploadedCount) => {
+    //   bus.emit(
+    //     'informer',
+    //     `${this.core.i18n('files', {'smart_count': uploadedCount})} successfully uploaded, Sir!`,
+    //     'info',
+    //     6000
+    //   )
+    // })
   }
 
   handleDrop (files) {
@@ -191,6 +192,10 @@ export default class DashboardUI extends Plugin {
     })
   }
 
+  cancelAll () {
+    this.core.bus.emit('core:cancel-all')
+  }
+
   pauseAll () {
     this.core.bus.emit('core:pause-all')
   }
@@ -244,16 +249,13 @@ export default class DashboardUI extends Plugin {
     const totalETA = prettyETA(this.getTotalETA(inProgressFilesArray))
 
     const isAllComplete = state.totalProgress === 100
-    const isAllPaused = inProgressFiles.length === 0 && !isAllComplete
+    const isAllPaused = inProgressFiles.length === 0 && !isAllComplete && uploadStartedFiles.length > 0
+    const isUploadStarted = uploadStartedFiles.length > 0
 
     const acquirers = state.modal.targets.filter((target) => {
       return target.type === 'acquirer'
     })
 
-    // let activeAcquirer = acquirers.filter((acquirer) => {
-    //   return !acquirer.isHidden
-    // })[0]
-
     const progressindicators = state.modal.targets.filter((target) => {
       return target.type === 'progressindicator'
     })
@@ -274,6 +276,11 @@ export default class DashboardUI extends Plugin {
       this.core.emitter.emit('core:upload-pause', fileID)
     }
 
+    const cancelUpload = (fileID) => {
+      this.core.emitter.emit('core:upload-cancel', fileID)
+      this.core.emitter.emit('core:file-remove', fileID)
+    }
+
     const showFileCard = (fileID) => {
       this.core.emitter.emit('dashboard:file-card', fileID)
     }
@@ -293,7 +300,8 @@ export default class DashboardUI extends Plugin {
       newFiles: newFiles,
       files: files,
       totalFileCount: Object.keys(files).length,
-      uploadStartedFiles: uploadStartedFiles,
+      isUploadStarted: isUploadStarted,
+      inProgress: uploadStartedFiles.length,
       completeFiles: completeFiles,
       inProgressFiles: inProgressFiles,
       totalSpeed: totalSpeed,
@@ -320,12 +328,15 @@ export default class DashboardUI extends Plugin {
       i18n: this.core.i18n,
       pauseAll: this.pauseAll,
       resumeAll: this.resumeAll,
+      cancelAll: this.cancelAll,
       addFile: addFile,
       removeFile: removeFile,
       info: info,
       metaFields: state.metaFields,
+      resumableUploads: this.core.getState().capabilities.resumableUploads || false,
       startUpload: startUpload,
       pauseUpload: pauseUpload,
+      cancelUpload: cancelUpload,
       fileCardFor: state.modal.fileCardFor,
       showFileCard: showFileCard,
       fileCardDone: fileCardDone

+ 16 - 40
src/plugins/Dummy.js

@@ -6,13 +6,13 @@ import html from '../core/html'
  * Dummy
  *
  */
-export default class Dummy extends Plugin {
-  constructor (core, opts, props) {
+class Dummy extends Plugin {
+  constructor (core, opts) {
     super(core, opts)
     this.type = 'acquirer'
     this.id = 'Dummy'
     this.title = 'Mr. Plugin'
-    this.props = props
+    // this.props = props
 
     // set default options
     const defaultOptions = {}
@@ -40,7 +40,7 @@ export default class Dummy extends Plugin {
     this.props.addFile(file)
   }
 
-  render () {
+  render (state) {
     const bla = html`<h2>this is strange 2</h2>`
     return html`
       <div class="wow-this-works">
@@ -49,50 +49,26 @@ export default class Dummy extends Plugin {
         }} />
         ${this.strange}
         ${bla}
+        ${state.dummy.text}
       </div>
     `
   }
 
-  // focus () {
-  //   return
-  //   console.log(`${this.target} .UppyDummy-firstInput`)
-  //
-  //   const firstInput = document.querySelector(`${this.target} .UppyDummy-firstInput`)
-  //
-  //   // only works for the first time if wrapped in setTimeout for some reason
-  //   // firstInput.focus()
-  //   setTimeout(function () {
-  //     firstInput.focus()
-  //   }, 10)
-  //
-  //   setTimeout(() => {
-  //     this.core.emit('informer', 'Hello! I’m a test Informer message', 'info', 4500)
-  //     this.addFakeFileJustToTest()
-  //   }, 1000)
-  // }
-
   install () {
+    this.core.setState({dummy: {text: '123'}})
+
     const target = this.opts.target
     const plugin = this
     this.target = this.mount(target, plugin)
 
-    // function workerFunc () {
-    //   self.addEventListener('message', (e) => {
-    //     const file = e.data.file
-    //     const reader = new FileReaderSync()
-    //     const dataURL = reader.readAsDataURL(file)
-    //     postMessage({file: dataURL})
-    //   })
-    // }
-    //
-    // const worker = createInlineWorker(workerFunc)
-    // const testFileBlob = new Blob(
-    //   ['data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDEyMCI+CiAgPGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNTAiLz4KPC9zdmc+Cg=='],
-    //   {type: 'image/svg+xml'}
-    // )
-    // worker.postMessage({file: testFileBlob})
-    // worker.addEventListener('message', (e) => {
-    //   console.log(e)
-    // })
+    setTimeout(() => {
+      this.core.setState({dummy: {text: '!!!'}})
+    }, 2000)
+  }
+}
+
+export default function (core, opts) {
+  if (!(this instanceof Dummy)) {
+    return new Dummy(core, opts)
   }
 }

+ 13 - 0
src/plugins/Multipart.js

@@ -82,6 +82,19 @@ export default class Multipart extends Plugin {
 
       xhr.open('POST', this.opts.endpoint, true)
       xhr.send(formPost)
+
+      this.core.emitter.on('core:upload-cancel', (fileID) => {
+        if (fileID === file.id) {
+          xhr.abort()
+        }
+      })
+
+      this.core.emitter.on('core:cancel-all', () => {
+        // const files = this.core.getState().files
+        // if (!files[file.id]) return
+        xhr.abort()
+      })
+
       this.core.emitter.emit('core:upload-started', file.id)
     })
   }

+ 7 - 0
src/scss/_common.scss

@@ -14,6 +14,13 @@
 }
 
 .UppyTheme--default {
+  font-family: -apple-system, BlinkMacSystemFont,
+               'avenir next', avenir,
+               helvetica, 'helvetica neue',
+               ubuntu,
+               roboto, noto,
+               'segoe ui', arial,
+               sans-serif;
   font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
   line-height: 1;
   -webkit-font-smoothing: antialiased;

+ 148 - 87
src/scss/_dashboard.scss

@@ -22,7 +22,7 @@
   left: 0;
   right: 0;
   bottom: 0;
-  background-color: rgba($color-black, 0.5);
+  background-color: rgba($color-white, 0.8);
   z-index: $zIndex-2;
 }
 
@@ -31,14 +31,17 @@
   background-color: darken($color-white, 2%);
   max-width: 100%;
   max-height: 100%;
-  width: 750px;
-  height: 550px;
-  // min-height: 400px;
+  width: 100%;
+  height: 100%;
   overflow: hidden;
   z-index: $zIndex-3;
   outline: none;
-  // border: 2px solid rgba($color-gray, 0.25);
-  border-radius: 5px;
+
+  @media #{$screen-medium} {
+    width: 750px;
+    height: 550px;
+    border-radius: 5px;
+  }
 }
 
 .UppyDashboard-innerWrap {
@@ -60,60 +63,77 @@
 
 .UppyDashboard--modal .UppyDashboard-inner {
   position: fixed;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  box-shadow: 0px 0px 10px 4px rgba($color-black, 0.1);
+  top: 0;
+  left: 0;
   border: none;
+
+  @media #{$screen-medium} {
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    box-shadow: 0px 0px 20px 7px rgba($color-gray, 0.15);
+  }
 }
 
 .UppyDashboard-close {
   @include reset-button;
   position: absolute;
-  top: 16px;
-  right: 16px;
+  top: 12px;
+  right: 12px;
   cursor: pointer;
-  // color: lighten($color-asphalt-gray, 20%);
-  color: $color-white;
-  z-index: $zIndex-3;
+  color: lighten($color-asphalt-gray, 20%);
+  z-index: $zIndex-4;
   transition: all 0.2s;
 
+  @media #{$screen-medium} {
+    color: $color-white;
+    top: 16px;
+    right: 16px;
+  }
+
   &:hover {
     color: $color-black;
-    // color: rgba($color-white, 0.8);
   }
-}
 
-  .UppyDashboard-close .UppyIcon {
-    width: 18px;
-    height: 18px;
+  .UppyIcon {
+    width: 14px;
+    height: 14px;
+    @media #{$screen-medium} {
+      width: 18px;
+      height: 18px;
+    }
   }
+}
 
 .UppyDashboardTabs {
-  // height: 22%;
-  // min-height: 100px;
-  padding-top: 15px;
+  min-height: 40px;
+  padding-top: 10px;
   padding-bottom: 10px;
-  // margin: 0 10px;
-  // border-bottom: 1px solid lighten($color-gray, 15%);
-  // border-bottom: 1px solid #E2E2E2;
+  border-bottom: 1px dashed lighten($color-gray, 15%);
+}
+
+.UppyDashboardTabs[aria-hidden=true] {
+  display: none;
 }
 
 .UppyDashboardTabs-title {
-  width: 100%;
-  font-size: 15px;
-  line-height: 1.6;
-  // text-transform: uppercase;
-  // letter-spacing: 2px;
+  font-size: 17px;
+  line-height: 40px;
   font-weight: 400;
   text-align: center;
   margin: 0;
   padding: 0;
   text-align: center;
-  margin-bottom: 8px;
+  // margin-bottom: 8px;
   color: $color-asphalt-gray;
 }
 
+.UppyDashboard-browse {
+  @include reset-button;
+  cursor: pointer;
+  color: darken($color-cornflower-blue, 10%);
+}
+
 .UppyDashboardTabs-list {
   list-style-type: none;
   margin: 0;
@@ -279,22 +299,23 @@
   }
 }
 
-
-.UppyDashboard-files {
-  // height: 78%;
-  // flex: 1;
-  // overflow: hidden;
+.UppyDashboard-filesContainer {
   @include clearfix;
   position: relative;
-  overflow-y: auto;
+  overflow-y: hidden;
   margin: 0;
-  // display: flex;
-  // flex-flow: row wrap;
-  padding: 10px 10px;
   flex: 1;
-  // border: 2px dashed rgba($color-gray, 0.2);
-  // border-radius: 10px;
-  border-top: 1px dashed lighten($color-gray, 15%);
+}
+
+.UppyDashboard-files {
+  margin: 0;
+  padding: 10px;
+  overflow-y: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
 }
 
 .UppyDashboard-files--noFiles {
@@ -313,6 +334,7 @@
 }
 
 .UppyDashboard-bgIcon {
+  width: 330px;
   max-width: 330px;
   position: absolute;
   top: 50%;
@@ -325,7 +347,6 @@
 .UppyDashboard-bgIcon .UppyIcon {
   width: 100%;
   height: 110px;
-  margin-bottom: 25px;
   fill: none;
   stroke: $color-asphalt-gray;
 }
@@ -341,6 +362,7 @@
   font-weight: normal;
   color: $color-asphalt-gray;
   margin: 0;
+  margin-top: 25px;
 }
 
 .UppyDashboardItem {
@@ -466,15 +488,16 @@
   width: 20px;
 }
 
+  .UppyDashboardItem.is-inprogress:not(.is-resumable) {
+    .UppyDashboardItem-remove {
+      display: none;
+    }
+  }
+
 .UppyDashboardItem-remove .UppyIcon {
   max-width: 100%;
 }
 
-  // .Uppy--isTouchDevice .UppyDashboardItem-remove,
-  .UppyDashboardItem:hover .UppyDashboardItem-remove {
-    opacity: 1;
-  }
-
 .UppyDashboardItem-progress {
   position: absolute;
   top: 50%;
@@ -518,30 +541,44 @@
   height: 100%;
 }
 
-.UppyIcon-progressCircle .bg {
+.UppyDashboardItem .bg {
   stroke: rgba($color-white, 0.4);
   opacity: 0;
-  transition: all 0.2s;
+  // transition: all 0.2s;
 }
 
-.UppyIcon-progressCircle .progress {
+.UppyDashboardItem .progress {
   stroke: $color-white;
   transition: stroke-dashoffset .5s ease-out;
   opacity: 0;
 }
 
-.UppyIcon-progressCircle .play {
+.UppyDashboardItem .play {
   stroke: $color-white;
   fill: $color-white;
   opacity: 0;
   transition: all 0.2s;
+  display: none;
+}
+
+.UppyDashboardItem .cancel {
+  // stroke: $color-white;
+  fill: $color-white;
+  opacity: 0;
+  transition: all 0.2s;
 }
 
-.UppyIcon-progressCircle .pause {
+.UppyDashboardItem .pause {
   stroke: $color-white;
   fill: $color-white;
   opacity: 0;
   transition: all 0.2s;
+  display: none;
+}
+
+.UppyDashboardItem.is-resumable {
+  .pause, .play { display: block; }
+  .cancel { display: none; }
 }
 
 .UppyIcon-progressCircle .check {
@@ -551,7 +588,7 @@
 }
 
 .UppyDashboardItem.is-inprogress {
-  .bg, .progress, .pause {
+  .bg, .progress, .pause, .cancel {
     opacity: 1;
   }
 
@@ -583,6 +620,7 @@
     fill: $color-green;
     opacity: 1;
   }
+
   .check {
     opacity: 1;
   }
@@ -696,13 +734,9 @@
   right: 20px;
 }
 
-.UppyDashboard-actions button:nth-child(2) {
-  margin-top: 10px;
-}
-
-.UppyDashboard-actionsItem {
-  // margin-bottom: 15px;
-}
+// .UppyDashboard-actions button:nth-child(2) {
+//   margin-top: 10px;
+// }
 
 .UppyDashboard-pauseResume .UppyIcon {
   width: 100%;
@@ -773,6 +807,7 @@
   box-shadow: 0px 3px 20px rgba($color-black, 0.15);
   max-width: 90%;
   max-height: 90%;
+  object-fit: cover;
 }
 
 .UppyDashboardFileCard-info {
@@ -815,12 +850,12 @@
   border-color: $color-asphalt-gray;
 }
 
-.UppyDashboardFileCard-done {
-  position: absolute;
-  bottom: 25px;
-  right: 25px;
-  transition: all 0.3s;
-}
+// .UppyDashboardFileCard-done {
+//   position: absolute;
+//   bottom: 25px;
+//   right: 25px;
+//   transition: all 0.3s;
+// }
 
 .UppyDashboardFileCard-done .UppyIcon {
   width: 30px;
@@ -828,21 +863,41 @@
 }
 
 .UppyDashboard-statusBar {
-  position: absolute;
-  left: 50%;
-  bottom: 20px;
-  margin-left: -200px;
-  width: 400px;
-  height: 35px;
-  line-height: 35px;
-  border-radius: 4px;
-  font-size: 13px;
+  // position: absolute;
+  // bottom: 0;
+  // left: 0;
+  // right: 0;
+  position: relative;
+  height: 40px;
+  line-height: 40px;
+  font-size: 14px;
   font-weight: 500;
   color: $color-white;
   background-color: rgba($color-black, 0.6);
   box-shadow: 1px 1px 4px 0 rgba($color-asphalt-gray, 0.3);
   overflow: hidden;
-  cursor: pointer;
+  z-index: $zIndex-2;
+  transition: height .35s;
+
+  @media #{$screen-medium} {
+    // left: 50%;
+    // bottom: 20px;
+    // margin-left: -200px;
+    // width: 400px;
+    // border-radius: 4px;
+  }
+}
+
+.UppyDashboard-statusBar[aria-hidden=true] {
+  height: 0;
+}
+
+.UppyDashboard-statusBar.is-complete .UppyDashboard-statusBarProgress {
+  background-color: $color-green;
+}
+
+.UppyDashboard-statusBar.is-complete .UppyDashboard-statusBarContent {
+  text-align: center;
 }
 
 .UppyDashboard-statusBarProgress {
@@ -850,23 +905,29 @@
   height: 100%;
   position: absolute;
   z-index: $zIndex-2;
-  // transition: width .3s ease-out;
+  transition: all .3s ease-out;
 }
 
-.UppyDashboard-statusBarText {
+.UppyDashboard-statusBarContent {
   font-weight: 600;
-  // max-width: 300px;
   position: relative;
   z-index: $zIndex-3;
-  padding-left: 40px;
+  padding-left: 15px;
   white-space: nowrap;
   text-overflow: ellipsis;
   overflow: hidden;
 }
 
+.UppyDashboard-statusBarContent .UppyIcon {
+  margin-right: 10px;
+}
+
 .UppyDashboard-statusBarAction {
-  position: absolute;
-  left: 14px;
-  top: 9px;
-  z-index: $zIndex-3;
+  @include reset-button;
+  // position: absolute;
+  // left: 14px;
+  // top: 12px;
+  // z-index: $zIndex-4;
+  color: $color-white;
+  cursor: pointer;
 }

+ 0 - 9
src/scss/_fileinput.scss

@@ -2,15 +2,6 @@
 @import '_utils.scss';
 @import '_common.scss';
 
-// .uppy-FileInput-input {
-//   width: 0.1px;
-//   height: 0.1px;
-//   opacity: 0;
-//   overflow: hidden;
-//   position: absolute;
-//   z-index: -1;
-// }
-
 .uppy-FileInput-btn {
   @include reset-button;
 

+ 1 - 1
src/scss/_informer.scss

@@ -16,7 +16,7 @@
 .UppyInformer {
   position: absolute;
   left: 15px;
-  bottom: 15px;
+  bottom: 50px;
   // width: 100%;
   // bottom: 0;
   // left: 0;

+ 4 - 0
src/scss/_variables.scss

@@ -21,3 +21,7 @@ $zIndex-2: 1001;
 $zIndex-3: 1002;
 $zIndex-3: 1003;
 $zIndex-4: 1004;
+$zIndex-5: 1005;
+
+// Media Queries
+$screen-medium: 'only screen and (min-width: 768px)';

+ 6 - 7
test/unit/core.spec.js

@@ -1,18 +1,17 @@
 import test from 'tape'
-import Core from '../../src/core/Core.js'
+import Uppy from '../../src/core/Core.js'
 import Acquirer1Plugin from './mocks/plugin-acquirer1.js'
 // import Acquirer2Plugin from './mocks/plugin-acquirer2.js'
 
 test('core', function (t) {
-  const uppy = new Core()
-
-  t.equal(typeof uppy, 'object', '`new Core()` should return an `object`')
-  t.ok(uppy instanceof Core, '`uppy` should be an instance of `Core` core')
+  t.equal(typeof new Uppy(), 'object', '`new Uppy()` should return an `object`')
+  t.equal(typeof Uppy(), 'object', '`Uppy()` without `new` should also return an `object`')
+  // t.ok(uppy instanceof Uppy, '`uppy` should be an instance of `Core` core')
   t.end()
 })
 
 test('use plugins', function (t) {
-  const uppy = new Core()
+  const uppy = new Uppy()
   uppy
     .use(Acquirer1Plugin)
 
@@ -21,7 +20,7 @@ test('use plugins', function (t) {
 })
 
 test('noDuplicates', function (t) {
-  const uppyTwoAcquirers = new Core()
+  const uppyTwoAcquirers = new Uppy()
 
   uppyTwoAcquirers.use(Acquirer1Plugin)
   const fn = uppyTwoAcquirers.use.bind(uppyTwoAcquirers, Acquirer1Plugin)

+ 3 - 3
test/unit/translator.spec.js

@@ -4,7 +4,7 @@ import russian from '../../src/locales/ru_RU.js'
 import english from '../../src/locales/en_US.js'
 
 test('translation', function (t) {
-  const core = new Core({locales: russian})
+  const core = new Core({locale: russian})
 
   t.equal(
     core.translator.translate('chooseFile'),
@@ -16,7 +16,7 @@ test('translation', function (t) {
 })
 
 test('interpolation', function (t) {
-  const core = new Core({locales: english})
+  const core = new Core({locale: english})
 
   t.equal(
     core.translator.translate('youHaveChosen', {'fileName': 'img.jpg'}),
@@ -28,7 +28,7 @@ test('interpolation', function (t) {
 })
 
 test('pluralization', function (t) {
-  const core = new Core({locales: russian})
+  const core = new Core({locale: russian})
 
   t.equal(
     core.translator.translate('filesChosen', {'smart_count': '18'}),

+ 70 - 0
website/src/_posts/2016-11-15-0.11.md

@@ -0,0 +1,70 @@
+---
+title: October 2016, Uppy 0.11 released
+date: 2016-11-15
+author: arturi
+published: false
+---
+
+Hey, what’s up!
+
+In October we worked on Uppy 0.11, that included: grand architecture write up and discussions, bringing in a friendly person to look at what we’ve been up to, Redux experiments, updated Dashboard UI and website example,
+
+<!-- more -->
+
+## More research and experiments
+
+Here’s what we’ve been up to:
+
+- Written up an [ARCHITECTURE.md](https://github.com/transloadit/uppy/blob/master/ARCHITECTURE.md) document describing our architecture and APIs.
+- Invited author of [Choo](https://github.com/yoshuawuyts/choo), Yoshua Wuyts to take a look at the state of things in Uppy, and he came up with a proposal for some changes, that we’ll be considering in the next releases.
+- Experimented with using Redux.
+
+## Dashboard: example features, StatusBar and updated UI
+
+**Dashboard example** [on our website](https://uppy.io/examples/dashboard/) now features several options:
+
+- Switch between “inline” and “modal dialog / popup” mode.
+- Toggle `autoProceed` option that starts uploads automatically, without waiting for the click on “upload” button.
+- Enable/disable acquire plugins, like Google Drive and Webcam.
+
+**StatusBar** is a bar (yes) that appears on the bottom of the Dashboard and unifies progress with pause/resume buttons.
+
+**Dashboard UI** has undergone minor improvements, like: new “drag files here” icon and tagline. The text before “acquire” icons on the top — Local Disk, Google Drive, Webcam — has been removed (it used to say “Import files from:”). New “remove file” icons. You know, small things, big difference.
+
+<img src="/images/blog/uppy-dashboard-oct-2016-1.jpg" alt="Dashboard UI, no files. Text: Drop files here, paste or import from one of the locations above">
+
+<img src="/images/blog/uppy-dashboard-oct-2016-2.jpg" alt="Dashboard UI, file upload in progress. StatusBar with pause/resume button and progress">
+
+## HTTPS
+
+We’ve upgraded both [uppy.io](http://uppy.io/) and [tus.io](http://tus.io/) to support https with Let’s Encrypt, so now Webcam example works, secure uploads work, and all is well.
+
+## And more
+
+- We’ve renamed FormTag plugin to FileInput and made it pretty be default, with an option to just show the default browser “choose file” if you wish.
+- [Fixed a bug](https://github.com/transloadit/uppy/issues/126) with `autoProceed: true` duplicating uploads.
+- Refactored Dashboard to only keep active acquire panel in DOM.
+- Added PersistentState plugin that saves state to localStorage — useful for development.
+- [Grand refactor of Uppy Server](https://github.com/transloadit/uppy/pull/131) with dynamic controllers.
+- Webcam stream no longer flashed when state is updated.
+
+## Release Notes
+
+Here is the full list of changes for version 0.11:
+
+- core: log method should have an option to throw error in addition to just logging (@arturi)
+- experimental: PersistentState plugin that saves state to localStorage — useful for development (@arturi)
+- dashboard: implement new StatusBar with progress and pause/resume buttons https://github.com/transloadit/uppy/issues/96#issuecomment-249401532 (@arturi)
+- dashboard: attempt to throttle StatusBar, so it doesn’t re-render too often (@arturi)
+- dashboard: refactor — only load one acquire panel at a time (activeAcquirer or empty), change focus behavior, utilize onload/onunload
+- experimental: create a Dashboard UI for Redux refactor (@hedgerh)
+- dashboard: make trigger optional — not needed when rendering inline (@arturi)
+- fileinput: pretty input element #93 (@arturi)
+- meta: document current Uppy architecture and question about the future (@arturi, @hedgerh)
+- test: see about adding tests for autoProceed: true (@arturi)
+- website: and ability to toggle options in Dashboard example: inline/modal, autoProceed, which plugins are enabled #89 (@arturi)
+- website: finish https upgrade for uppy.io, uppy-server and tus, set up pingdom notifications (@arturi, @kvz, @hedgerh)
+- website: update guide, API docs and main page example to match current actual API (@arturi)
+- uppy-server: Make uppy server have dynamic controllers (@hedgerh)
+
+The Uppy Team

+ 15 - 6
website/src/api-usage-example.ejs

@@ -1,10 +1,19 @@
-import { Core, DragDrop, ProgressBar, Tus10 } from 'uppy'
+import Uppy from 'uppy/lib/core'
+import Dashboard from 'uppy/lib/plugins/Dashboard'
+import Tus10 from 'uppy/lib/plugins/Tus10'
+import Informer from 'uppy/lib/plugins/Informer'
+import MetaData from 'uppy/lib/plugins/MetaData'
  
-const uppy = new Core({autoProceed: false})
-uppy
-  .use(DragDrop, {target: '#drop-target'})
-  .use(ProgressBar, {target: 'body'})
-  .use(Tus10, {endpoint: '//tusd.tus.io/files'})
+const uppy = Uppy({autoProceed: false})
+  .use(Dashboard, {trigger: '#select-files'})
+  .use(Informer, {target: Dashboard})
+  .use(Tus10, {endpoint: '://master.tus.io/files/'})
+  .use(MetaData, {
+    fields: [
+      { id: 'resizeTo', name: 'Resize to', value: 1200, placeholder: 'specify future image size' },
+      { id: 'description', name: 'Description', value: 'none', placeholder: 'describe what the file is for' }
+    ]
+  })
   .run()
  
 uppy.on('core:success', (fileCount) => {

+ 9 - 8
website/src/examples/dashboard/app.es6

@@ -1,10 +1,11 @@
-import { Core,
-         Dashboard,
-         GoogleDrive,
-         Webcam,
-         Tus10,
-         MetaData,
-         Informer } from '../../../../src/index.js'
+import Uppy from '../../../../src/core'
+import Dashboard from '../../../../src/plugins/Dashboard'
+import GoogleDrive from '../../../../src/plugins/GoogleDrive'
+import Webcam from '../../../../src/plugins/Webcam'
+import Tus10 from '../../../../src/plugins/Tus10'
+import MetaData from '../../../../src/plugins/MetaData'
+import Informer from '../../../../src/plugins/Informer'
+
 import { UPPY_SERVER } from '../env'
 
 const PROTOCOL = location.protocol === 'https:' ? 'https' : 'http'
@@ -18,7 +19,7 @@ function uppyInit () {
     dashboardElParent.removeChild(dashboardEl)
   }
 
-  const uppy = new Core({debug: true, autoProceed: opts.autoProceed})
+  const uppy = Uppy({debug: true, autoProceed: opts.autoProceed})
   uppy.use(Dashboard, {
     trigger: '.UppyModalOpenerBtn',
     inline: opts.DashboardInline,

+ 1 - 1
website/src/examples/dragdrop/app.es6

@@ -6,7 +6,7 @@ import Tus10 from '../../../../src/plugins/Tus10.js'
 const uppyOne = new Uppy({debug: true})
 uppyOne
   .use(DragDrop, {target: '.UppyDragDrop-One'})
-  .use(Tus10, {endpoint: '//tusd.tus.io/files/'})
+  .use(Tus10, {endpoint: '//master.tus.io/files/'})
   .use(ProgressBar, {target: '.UppyDragDrop-One-Progress'})
   .run()
 

+ 2 - 5
website/src/examples/i18n/app.es6

@@ -1,14 +1,11 @@
 import Uppy from '../../../../src/core/Core.js'
 import Tus10 from '../../../../src/plugins/GoogleDrive'
 import russian from '../../../../src/locales/ru_RU'
-// import Uppy from 'uppy/core'
-// import { Tus10 } from 'uppy/plugins'
-// import { ru_RU } from 'uppy/locales'
 
-const uppy = new Uppy({debug: true, wait: false, locales: russian})
+const uppy = new Uppy({debug: true, autoProceed: false, locale: russian})
 
 uppy
-  .use(Tus10, {endpoint: '//tusd.tus.io/files/'})
+  .use(Tus10, {endpoint: '//master.tus.io/files/'})
   .run()
 
 console.log('--> Uppy Bundled version with Tus10 & Russian language pack has loaded')

+ 2 - 2
website/src/examples/i18n/app.html

@@ -7,9 +7,9 @@
 <script src="/uppy/uppy.min.js"></script>
 <script src="/uppy/locales/ru_RU.js"></script>
 <script>
-  var uppy = new Uppy.Core({locales: Uppy.locales.ru_RU, debug: true});
+  var uppy = new Uppy.Core({locale: Uppy.locales.ru_RU, debug: true});
   uppy.use(Uppy.DragDrop, {target: '.UppyDragDrop'});
-  uppy.use(Uppy.Tus10, {endpoint: 'http://master.tus.io:3020/files/'});
+  uppy.use(Uppy.Tus10, {endpoint: '//master.tus.io/files/'});
   uppy.run();
 
   console.log('--> Uppy pre-built version with Tus10, DragDrop & Russian language pack has loaded');

+ 4 - 0
website/src/frontpage-code-sample.ejs

@@ -5,4 +5,8 @@ layout: false
 You can `npm run web:update:frontpage:code:sample` to render this code snippet and
 save it as a layout partial
 -->
+{% codeblock lang:bash %}
+$ npm install uppy --save
+{% endcodeblock %}
+
 {% include_code lang:js ../api-usage-example.ejs %}

+ 0 - 9
website/src/guide/overview.md

@@ -1,9 +0,0 @@
----
-title: "Overview"
-type: guide
-order: 1
----
-
-Uppy is (going to be) an uploader written in ES6 JavaScript with a plugin-based architecture, making it very extensible. Out of the box it supports tapping into Dropbox, Instagram, Local files. It has support for resumable file uploads via tus.io, and adding encoding backends.
-
-Uppy is brought to you by the people behind Transloadit and as such has first class support for adding their uploading and encoding backend, but this is opt-in.

BIN
website/src/images/uppy-demo.mp4


+ 9 - 5
website/themes/uppy/layout/index.ejs

@@ -32,13 +32,17 @@
 </section>
 
 <section id="example" class="IndexExample">
-  <h2 class="IndexSection-title">Quick Example</h2>
+  <h2 class="IndexSection-title">Demo</h2>
 
-  <div class="IndexExample-block"><%- partial('partials/frontpage-code-sample') %></div>
-
-  <div class="sign">&raquo;</div>
+  <div class="IndexExample-block">
+    <video class="IndexExample-video"
+           src="/images/uppy-demo.mp4"
+           autoplay loop muted></video>
+  </div>
 
-  <div class="IndexExample-block"><img src="/images/uppy-dragdrop-screenshot.png"></div>
+  <div class="IndexExample-block">
+    <%- partial('partials/frontpage-code-sample') %>
+  </div>
 
   <a class="GetStartedBtn" href="/guide/index.html">Get Started</a>
 </section>

File diff suppressed because it is too large
+ 2 - 1
website/themes/uppy/layout/partials/frontpage-code-sample.html


+ 0 - 1
website/themes/uppy/layout/partials/social.ejs

@@ -2,7 +2,6 @@
   <li><a href="https://twitter.com/uppy_io" class="twitter-follow-button" data-show-count="false" data-dnt="true">Follow @uppy_io</a></li>
   <li><iframe src="https://ghbtns.com/github-btn.html?user=transloadit&repo=uppy&type=watch&count=true"
 allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe></li>
- </li>
   <li class="BuildBadge">
    <span class="wrapper">
      <a href="https://travis-ci.org/transloadit/uppy"><img src="https://travis-ci.org/transloadit/uppy.svg" alt="Build Status"></a>

+ 11 - 7
website/themes/uppy/source/css/_index.scss

@@ -165,7 +165,7 @@
   transition: all .3s ease;
   background-color: $color-primary;
   color: $color-white;
-  margin-top: 3em;
+  margin-top: 1.2em;
   margin-bottom: 2em;
 
   &:hover { @include zoom; }
@@ -210,14 +210,14 @@
 
 .IndexSection-title {
   font-size: 25px;
-  margin-bottom: 1.5em;
+  margin-bottom: 0.7em;
 }
 
 .IndexExample-block {
-  padding: 7px;
+  // padding: 7px;
   text-align: left;
-  background-color: $color-codebg;
-  border-radius: $size-radius;
+  // background-color: $color-codebg;
+  // border-radius: $size-radius;
   overflow: hidden;
   max-width: 500px;
   margin: auto;
@@ -225,13 +225,17 @@
 
   @media #{$screen-medium} {
     max-width: 100%;
-    width: 43%;
+    width: 47%;
     display: inline-block;
     vertical-align: middle;
-    height: 250px;
+    // height: 250px;
   }
 }
 
+.IndexExample-video {
+  max-width: 100%;
+}
+
 .IndexExample-block img {
   margin: auto;
 }

Some files were not shown because too many files changed in this diff