Browse Source

Merge pull request #451 from transloadit/refactor-preact

Switch to Preact
Artur Paikin 7 years ago
parent
commit
b9d22670c6
56 changed files with 1867 additions and 1587 deletions
  1. 1 1
      .babelrc
  2. 5 2
      .eslintrc
  3. 0 2
      CHANGELOG.md
  4. 622 304
      package-lock.json
  5. 5 6
      package.json
  6. 5 8
      src/core/Plugin.js
  7. 35 25
      src/plugins/Dashboard/ActionBrowseTagline.js
  8. 71 154
      src/plugins/Dashboard/Dashboard.js
  9. 71 49
      src/plugins/Dashboard/FileCard.js
  10. 108 109
      src/plugins/Dashboard/FileItem.js
  11. 10 9
      src/plugins/Dashboard/FileItemProgress.js
  12. 40 38
      src/plugins/Dashboard/FileList.js
  13. 59 49
      src/plugins/Dashboard/Tabs.js
  14. 0 21
      src/plugins/Dashboard/UploadBtn.js
  15. 53 51
      src/plugins/Dashboard/icons.js
  16. 55 29
      src/plugins/Dashboard/index.js
  17. 40 37
      src/plugins/DragDrop/index.js
  18. 11 9
      src/plugins/Dropbox/icons.js
  19. 6 7
      src/plugins/Dropbox/index.js
  20. 15 14
      src/plugins/Dummy.js
  21. 34 24
      src/plugins/FileInput.js
  22. 4 8
      src/plugins/GoogleDrive/index.js
  23. 21 15
      src/plugins/Informer.js
  24. 7 10
      src/plugins/Instagram/index.js
  25. 5 5
      src/plugins/ProgressBar.js
  26. 28 18
      src/plugins/Provider/view/AuthView.js
  27. 2 6
      src/plugins/Provider/view/Breadcrumb.js
  28. 5 5
      src/plugins/Provider/view/Breadcrumbs.js
  29. 21 27
      src/plugins/Provider/view/Browser.js
  30. 44 0
      src/plugins/Provider/view/Filter.js
  31. 4 6
      src/plugins/Provider/view/Loader.js
  32. 13 11
      src/plugins/Provider/view/Table.js
  33. 35 17
      src/plugins/Provider/view/TableRow.js
  34. 11 9
      src/plugins/Provider/view/index.js
  35. 82 93
      src/plugins/StatusBar/StatusBar.js
  36. 3 3
      src/plugins/StatusBar/index.js
  37. 5 5
      src/plugins/Webcam/CameraIcon.js
  38. 30 32
      src/plugins/Webcam/CameraScreen.js
  39. 4 4
      src/plugins/Webcam/PermissionsScreen.js
  40. 11 11
      src/plugins/Webcam/RecordButton.js
  41. 3 3
      src/plugins/Webcam/RecordStartIcon.js
  42. 3 3
      src/plugins/Webcam/RecordStopIcon.js
  43. 7 7
      src/plugins/Webcam/SnapshotButton.js
  44. 5 5
      src/plugins/Webcam/WebcamIcon.js
  45. 9 6
      src/plugins/Webcam/index.js
  46. 10 12
      src/scss/_common.scss
  47. 137 153
      src/scss/_dashboard.scss
  48. 1 8
      src/scss/_dragdrop.scss
  49. 5 5
      src/scss/_fileinput.scss
  50. 7 12
      src/scss/_informer.scss
  51. 3 3
      src/scss/_progressbar.scss
  52. 64 74
      src/scss/_provider.scss
  53. 0 31
      src/scss/_spinner.scss
  54. 24 24
      src/scss/_statusbar.scss
  55. 6 6
      src/scss/_webcam.scss
  56. 2 2
      test/endtoend/specs/uppy.test.js

+ 1 - 1
.babelrc

@@ -8,6 +8,6 @@
     "add-module-exports",
     "transform-object-assign",
     "es6-promise",
-    "yo-yoify"
+    ["transform-react-jsx", { "pragma":"h" }]
   ]
 }

+ 5 - 2
.eslintrc

@@ -1,5 +1,5 @@
 {
-  "extends": "standard",
+  "extends": ["standard", "standard-preact"],
   "env": {
     "browser": true,
     "node": true,
@@ -9,5 +9,8 @@
     "window": true,
     "hexo": true
   },
-  "plugins": ["jest"]
+  "plugins": ["jest"],
+  "rules": {
+    "jsx-quotes": ["error", "prefer-double"]
+  }
 }

+ 0 - 2
CHANGELOG.md

@@ -113,7 +113,6 @@ Theme: 🎄 Christmas edition
 - [x] core: renamed core to uppy in plugins and what not. So instead of this.core.state we would use this.uppy.state; (@arturi / #438) 
 - [x] core: renamed events to remove core: prefix, as been suggested already. So: `success`, `error`, `upload-started` and so on, and prefixed event names for plugins sometimes, like `dashboard:file-card` (@arturi / #438) 
 - [x] dashboard: added `metaFields` option, pass an array of settings for UI field objects `{ id: 'caption', name: 'Caption', placeholder: 'describe what the image is about' }` (@arturi, @goto-bus-stop / #438)
-
 - [ ] add `Form`: a plugin that is used in conjunction with any other acquirer, responsible for 1. acquiring the metadata from form; 2. intercepting submit event on the form, opening Uppy dialog instead; 3. injecting any result (like from Transloadit plugin) back into the form (jquery-sdk includes the whole Assembly Status JSON in a hidden field i think) (@arturi)
 - [ ] core: return `processing` results among with `upload` results in `success` event and `upload()` promise
 - [ ] core: css-in-js, while keeping non-random classnames (ideally prefixed) and useful preprocessor features. also see simple https://github.com/codemirror/CodeMirror/blob/master/lib/codemirror.css (@arturi, @goto-bus-stop)
@@ -124,7 +123,6 @@ Theme: 🎄 Christmas edition
 - [ ] docs: quick start guide: https://community.transloadit.com/t/quick-start-guide-would-be-really-helpful/14605 (@arturi)
 - [ ] docs: on writing plugins (@goto-bus-stop)
 - [ ] goldenretriever: add “ghost” files (@arturi)
-- [ ] tus: Review “tus: Remove old upload and events when starting a new upload.” b3cc48130e292f08c2a09f2f0adf6b6332bf7692 (@arturi)
 - [ ] webcam: URL.createObjectURL(MediaStream) is deprecated and will be removed soon: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
 - [ ] xhrupload: add bundle option to send multiple files in one request #442
 - [ ] uppy-server: benchmarks / stress test, large file, uppy-server / tus / S3 (10 GB)

File diff suppressed because it is too large
+ 622 - 304
package-lock.json


+ 5 - 6
package.json

@@ -54,7 +54,7 @@
     "babel-plugin-es6-promise": "1.0.0",
     "babel-plugin-transform-object-assign": "6.8.0",
     "babel-plugin-transform-proto-to-assign": "6.9.0",
-    "babel-plugin-yo-yoify": "0.6.0",
+    "babel-plugin-transform-react-jsx": "^6.24.1",
     "babel-polyfill": "6.9.1",
     "babel-preset-es2015": "6.24.0",
     "babel-register": "6.9.0",
@@ -66,6 +66,7 @@
     "disc": "1.3.2",
     "eslint": "^3.19.0",
     "eslint-config-standard": "^10.2.1",
+    "eslint-config-standard-preact": "^1.1.6",
     "eslint-plugin-import": "^2.7.0",
     "eslint-plugin-jest": "^21.5.0",
     "eslint-plugin-node": "^4.2.3",
@@ -101,6 +102,7 @@
     "webdriverio": "^4.9.5"
   },
   "dependencies": {
+    "classnames": "^2.2.5",
     "cuid": "^1.3.8",
     "drag-drop": "2.13.2",
     "es6-promise": "3.2.1",
@@ -108,16 +110,13 @@
     "lodash.throttle": "4.1.1",
     "mime-match": "^1.0.2",
     "namespace-emitter": "^2.0.0",
-    "nanoraf": "3.0.1",
-    "on-load": "3.2.0",
+    "preact": "^8.2.7",
     "prettier-bytes": "1.0.4",
     "prop-types": "^15.5.10",
     "socket.io-client": "2.0.2",
     "tus-js-client": "^1.4.5",
     "url-parse": "1.1.9",
-    "whatwg-fetch": "2.0.3",
-    "yo-yo": "1.4.0",
-    "yo-yoify": "3.7.2"
+    "whatwg-fetch": "2.0.3"
   },
   "scripts": {
     "build:bundle": "node ./bin/build-js.js",

+ 5 - 8
src/core/Plugin.js

@@ -1,5 +1,4 @@
-const yo = require('yo-yo')
-const nanoraf = require('nanoraf')
+const preact = require('preact')
 const { findDOMElement } = require('../core/Utils')
 const getFormData = require('get-form-data')
 
@@ -63,10 +62,9 @@ module.exports = class Plugin {
     const targetElement = findDOMElement(target)
 
     if (targetElement) {
-      // Set up nanoraf.
-      this.updateUI = nanoraf((state) => {
-        this.el = yo.update(this.el, this.render(state))
-      })
+      this.updateUI = (state) => {
+        this.el = preact.render(this.render(state), targetElement, this.el)
+      }
 
       this.uppy.log(`Installing ${callerPluginName} to a DOM element`)
 
@@ -81,8 +79,7 @@ module.exports = class Plugin {
         targetElement.innerHTML = ''
       }
 
-      this.el = plugin.render(this.uppy.state)
-      targetElement.appendChild(this.el)
+      this.el = preact.render(this.render(this.uppy.state), targetElement)
 
       return this.el
     }

+ 35 - 25
src/plugins/Dashboard/ActionBrowseTagline.js

@@ -1,28 +1,38 @@
-const html = require('yo-yo')
+const { h, Component } = require('preact')
 
-module.exports = (props) => {
-  const input = html`
-    <input class="UppyDashboard-input"
-           hidden="true"
-           aria-hidden="true" 
-           tabindex="-1" 
-           type="file" 
-           name="files[]" 
-           multiple="true"
-           onchange=${props.handleInputChange} />`
+class ActionBrowseTagline extends Component {
+  constructor (props) {
+    super(props)
+    this.handleClick = this.handleClick.bind(this)
+  }
 
-  return html`
-    <span>
-      ${props.acquirers.length === 0
-        ? props.i18n('dropPaste')
-        : props.i18n('dropPasteImport')
-      }
-      <button type="button"
-              class="UppyDashboard-browse"
-              onclick=${(ev) => {
-                input.click()
-              }}>${props.i18n('browse')}</button>
-      ${input}
-    </span>
-  `
+  handleClick (ev) {
+    this.input.click()
+  }
+
+  render () {
+    return (
+      <span>
+        {this.props.acquirers.length === 0
+          ? this.props.i18n('dropPaste')
+          : this.props.i18n('dropPasteImport')
+        } <button type="button" class="uppy-Dashboard-browse" onclick={this.handleClick}>
+          {this.props.i18n('browse')}
+        </button>
+        <input class="uppy-Dashboard-input"
+          hidden="true"
+          aria-hidden="true"
+          tabindex="-1"
+          type="file"
+          name="files[]"
+          multiple="true"
+          onchange={this.props.handleInputChange}
+          ref={(input) => {
+            this.input = input
+          }} />
+      </span>
+    )
+  }
 }
+
+module.exports = ActionBrowseTagline

+ 71 - 154
src/plugins/Dashboard/Dashboard.js

@@ -1,167 +1,84 @@
-const html = require('yo-yo')
 const FileList = require('./FileList')
 const Tabs = require('./Tabs')
 const FileCard = require('./FileCard')
-// const UploadBtn = require('./UploadBtn')
-const { isTouchDevice, toArray } = require('../../core/Utils')
+const classNames = require('classnames')
+const { isTouchDevice } = require('../../core/Utils')
 const { closeIcon } = require('./icons')
+const { h } = require('preact')
 
 // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
 // https://github.com/ghosh/micromodal
 
-module.exports = function Dashboard (props) {
-  function handleInputChange (ev) {
-    ev.preventDefault()
-    const files = toArray(ev.target.files)
-
-    files.forEach((file) => {
-      props.addFile({
-        source: props.id,
-        name: file.name,
-        type: file.type,
-        data: file
-      })
-    })
-  }
-
-  // @TODO Exprimental, work in progress
-  // no names, weird API, Chrome-only http://stackoverflow.com/a/22940020
-  function handlePaste (ev) {
-    ev.preventDefault()
-
-    const files = toArray(ev.clipboardData.items)
-    files.forEach((file) => {
-      if (file.kind !== 'file') return
-
-      const blob = file.getAsFile()
-      if (!blob) {
-        props.log('[Dashboard] File pasted, but the file blob is empty')
-        props.info('Error pasting file', 'error')
-        return
-      }
-      props.log('[Dashboard] File pasted')
-      props.addFile({
-        source: props.id,
-        name: file.name,
-        type: file.type,
-        data: blob
-      })
-    })
-  }
-
-  const renderInnerPanel = (props) => {
-    return html`<div style="width: 100%; height: 100%;">
-          <div class="UppyDashboardContent-bar">
-          <h2 class="UppyDashboardContent-title">
-            ${props.i18n('importFrom')} ${props.activePanel ? props.activePanel.name : null}
-          </h2>
-          <button class="UppyDashboardContent-back"
-                type="button"
-                onclick=${props.hideAllPanels}>${props.i18n('done')}</button>
-        </div>
-        ${props.getPlugin(props.activePanel.id).render(props.state)}
-    </div>`
-  }
-
-  return html`
-    <div class="Uppy UppyTheme--default UppyDashboard
-                          ${isTouchDevice() ? 'Uppy--isTouchDevice' : ''}
-                          ${props.semiTransparent ? 'UppyDashboard--semiTransparent' : ''}
-                          ${!props.inline ? 'UppyDashboard--modal' : ''}
-                          ${props.isWide ? 'UppyDashboard--wide' : ''}"
-          aria-hidden="${props.inline ? 'false' : props.modal.isHidden}"
-          aria-label="${!props.inline
-                       ? props.i18n('dashboardWindowTitle')
-                       : props.i18n('dashboardTitle')}"
-          onpaste=${handlePaste}>
-
-    <div class="UppyDashboard-overlay" tabindex="-1" onclick=${props.handleClickOutside}></div>
-
-    <div class="UppyDashboard-inner"
-         aria-modal="true"
-         role="dialog"
-         style="
-          ${props.inline && props.maxWidth ? `max-width: ${props.maxWidth}px;` : ''}
-          ${props.inline && props.maxHeight ? `max-height: ${props.maxHeight}px;` : ''}"
-         onload=${() => props.updateDashboardElWidth()}>
-      <button class="UppyDashboard-close"
-              type="button"
-              aria-label="${props.i18n('closeModal')}"
-              title="${props.i18n('closeModal')}"
-              onclick=${props.closeModal}>${closeIcon()}</button>
-
-      <div class="UppyDashboard-innerWrap">
-
-        ${Tabs({
-          files: props.files,
-          handleInputChange: handleInputChange,
-          acquirers: props.acquirers,
-          panelSelectorPrefix: props.panelSelectorPrefix,
-          showPanel: props.showPanel,
-          i18n: props.i18n
-        })}
-
-        ${FileCard({
-          files: props.files,
-          fileCardFor: props.fileCardFor,
-          done: props.fileCardDone,
-          metaFields: props.metaFields,
-          log: props.log,
-          i18n: props.i18n
-        })}
-
-        <div class="UppyDashboard-filesContainer">
-
-          ${FileList({
-            acquirers: props.acquirers,
-            files: props.files,
-            handleInputChange: handleInputChange,
-            showFileCard: props.showFileCard,
-            showProgressDetails: props.showProgressDetails,
-            totalProgress: props.totalProgress,
-            totalFileCount: props.totalFileCount,
-            info: props.info,
-            note: props.note,
-            i18n: props.i18n,
-            log: props.log,
-            removeFile: props.removeFile,
-            pauseAll: props.pauseAll,
-            resumeAll: props.resumeAll,
-            pauseUpload: props.pauseUpload,
-            startUpload: props.startUpload,
-            cancelUpload: props.cancelUpload,
-            retryUpload: props.retryUpload,
-            resumableUploads: props.resumableUploads,
-            isWide: props.isWide
-          })}
-
-        </div>
-
-        <div class="UppyDashboardContent-panel"
-             role="tabpanel"
-             aria-hidden="${props.activePanel ? 'false' : 'true'}">
-         ${props.activePanel ? renderInnerPanel(props) : ''}
-        </div>
-
-        <div class="UppyDashboard-progressindicators">
-          ${props.progressindicators.map((target) => {
-            return props.getPlugin(target.id).render(props.state)
-          })}
-        </div>
-
+const renderInnerPanel = (props) => {
+  return <div style={{ width: '100%', height: '100%' }}>
+    <div class="uppy-DashboardContent-bar">
+      <div class="uppy-DashboardContent-title">
+        {props.i18n('importFrom')} {props.activePanel ? props.activePanel.name : null}
       </div>
+      <button class="uppy-DashboardContent-back"
+        type="button"
+        onclick={props.hideAllPanels}>{props.i18n('done')}</button>
     </div>
+    {props.getPlugin(props.activePanel.id).render(props.state)}
   </div>
-  `
 }
 
-// <div class="UppyDashboard-actions">
-// ${!props.hideUploadButton && !props.autoProceed && props.newFiles.length > 0
-//  ? UploadBtn({
-//    i18n: props.i18n,
-//    startUpload: props.startUpload,
-//    newFileCount: props.newFiles.length
-//  })
-//  : null
-// }
-// </div>
+module.exports = function Dashboard (props) {
+  const dashboardClassName = classNames(
+    'uppy',
+    'uppy-Dashboard',
+    { 'Uppy--isTouchDevice': isTouchDevice() },
+    { 'uppy-Dashboard--semiTransparent': props.semiTransparent },
+    { 'uppy-Dashboard--modal': !props.inline },
+    { 'uppy-Dashboard--wide': props.isWide }
+  )
+
+  return (
+    <div class={dashboardClassName}
+      aria-hidden={props.inline ? 'false' : props.modal.isHidden}
+      aria-label={!props.inline ? props.i18n('dashboardWindowTitle') : props.i18n('dashboardTitle')}
+      onpaste={props.handlePaste}>
+
+      <div class="uppy-Dashboard-overlay" tabindex="-1" onclick={props.handleClickOutside} />
+
+      <div class="uppy-Dashboard-inner"
+        aria-modal={!props.inline && 'true'}
+        role={!props.inline && 'dialog'}
+        style={{
+          maxWidth: props.inline && props.maxWidth ? props.maxWidth : '',
+          maxHeight: props.inline && props.maxHeight ? props.maxHeight : ''
+        }}>
+        <button class="uppy-Dashboard-close"
+          type="button"
+          aria-label={props.i18n('closeModal')}
+          title={props.i18n('closeModal')}
+          onclick={props.closeModal}>
+          {closeIcon()}
+        </button>
+
+        <div class="uppy-Dashboard-innerWrap">
+          <Tabs {...props} />
+
+          <FileCard {...props} />
+
+          <div class="uppy-Dashboard-filesContainer">
+            <FileList {...props} />
+          </div>
+
+          <div class="uppy-DashboardContent-panel"
+            role="tabpanel"
+            id={props.activePanel && `uppy-DashboardContent-panel--${props.activePanel.id}`}
+            aria-hidden={props.activePanel ? 'false' : 'true'}>
+            {props.activePanel && renderInnerPanel(props)}
+          </div>
+
+          <div class="uppy-Dashboard-progressindicators">
+            {props.progressindicators.map((target) => {
+              return props.getPlugin(target.id).render(props.state)
+            })}
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 71 - 49
src/plugins/Dashboard/FileCard.js

@@ -1,76 +1,98 @@
-const html = require('yo-yo')
 const getFileTypeIcon = require('./getFileTypeIcon')
 const { checkIcon } = require('./icons')
+const { h, Component } = require('preact')
 
-module.exports = function fileCard (props) {
-  const file = props.fileCardFor ? props.files[props.fileCardFor] : false
-  const meta = {}
+module.exports = class FileCard extends Component {
+  constructor (props) {
+    super(props)
+
+    this.meta = {}
+
+    this.tempStoreMetaOrSubmit = this.tempStoreMetaOrSubmit.bind(this)
+    this.renderMetaFields = this.renderMetaFields.bind(this)
+    this.handleClick = this.handleClick.bind(this)
+  }
+
+  tempStoreMetaOrSubmit (ev) {
+    const file = this.props.files[this.props.fileCardFor]
 
-  const tempStoreMetaOrSubmit = (ev) => {
     if (ev.keyCode === 13) {
       ev.stopPropagation()
       ev.preventDefault()
-      props.done(meta, file.id)
+      this.props.fileCardDone(this.meta, file.id)
       return
     }
 
     const value = ev.target.value
     const name = ev.target.dataset.name
-    meta[name] = value
+    this.meta[name] = value
   }
 
-  function renderMetaFields (file) {
-    const metaFields = props.metaFields || []
+  renderMetaFields (file) {
+    const metaFields = this.props.metaFields || []
     return metaFields.map((field) => {
-      return html`<fieldset class="UppyDashboardFileCard-fieldset">
-        <label class="UppyDashboardFileCard-label">${field.name}</label>
-        <input class="UppyDashboardFileCard-input"
-               type="text"
-               data-name="${field.id}"
-               value="${file.meta[field.id] || ''}"
-               placeholder="${field.placeholder || ''}"
-               onkeyup=${tempStoreMetaOrSubmit}
-               onkeydown=${tempStoreMetaOrSubmit}
-               onkeypress=${tempStoreMetaOrSubmit} /></fieldset>`
+      return <fieldset class="uppy-DashboardFileCard-fieldset">
+        <label class="uppy-DashboardFileCard-label">{field.name}</label>
+        <input class="uppy-DashboardFileCard-input"
+          type="text"
+          data-name={field.id}
+          value={file.meta[field.id]}
+          placeholder={field.placeholder}
+          onkeyup={this.tempStoreMetaOrSubmit}
+          onkeydown={this.tempStoreMetaOrSubmit}
+          onkeypress={this.tempStoreMetaOrSubmit} /></fieldset>
     })
   }
 
-  return html`<div class="UppyDashboardFileCard" aria-hidden="${!props.fileCardFor}">
-    ${props.fileCardFor
-      ? html`
+  handleClick (ev) {
+    const file = this.props.files[this.props.fileCardFor]
+    this.props.fileCardDone(this.meta, file.id)
+  }
+
+  render () {
+    const file = this.props.files[this.props.fileCardFor]
+
+    return <div class="uppy-DashboardFileCard" aria-hidden={!this.props.fileCardFor}>
+      {this.props.fileCardFor &&
         <div style="width: 100%; height: 100%;">
-          <div class="UppyDashboardContent-bar">
-            <h2 class="UppyDashboardContent-title">Editing <span class="UppyDashboardContent-titleFile">${file.meta ? file.meta.name : file.name}</span></h2>
-            <button class="UppyDashboardContent-back" type="button" title="Finish editing file"
-                    onclick=${() => props.done(meta, file.id)}>Done</button>
+          <div class="uppy-DashboardContent-bar">
+            <h2 class="uppy-DashboardContent-title">Editing <span class="uppy-DashboardContent-titleFile">{file.meta ? file.meta.name : file.name}</span></h2>
+            <button class="uppy-DashboardContent-back" type="button" title="Finish editing file"
+              onclick={this.handleClick}>Done</button>
           </div>
-          <div class="UppyDashboardFileCard-inner">
-            <div class="UppyDashboardFileCard-preview" style="background-color: ${getFileTypeIcon(file.type).color}">
-              ${file.preview
-                ? html`<img alt="${file.name}" src="${file.preview}">`
-                : html`<div class="UppyDashboardItem-previewIconWrap">
-                  <span class="UppyDashboardItem-previewIcon" style="color: ${getFileTypeIcon(file.type).color}">${getFileTypeIcon(file.type).icon}</span>
-                  <svg class="UppyDashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF"/><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4"/></g></svg>
-                </div>`
+          <div class="uppy-DashboardFileCard-inner">
+            <div class="uppy-DashboardFileCard-preview" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
+              {file.preview
+                ? <img alt={file.name} src={file.preview} />
+                : <div class="uppy-DashboardItem-previewIconWrap">
+                  <span class="uppy-DashboardItem-previewIcon" style={{ color: getFileTypeIcon(file.type).color }}>{getFileTypeIcon(file.type).icon}</span>
+                  <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
+                </div>
               }
             </div>
-            <div class="UppyDashboardFileCard-info">
-              <fieldset class="UppyDashboardFileCard-fieldset">
-                <label class="UppyDashboardFileCard-label">Name</label>
-                <input class="UppyDashboardFileCard-input" data-name="name" type="text" value="${file.meta.name}"
-                       onkeyup=${tempStoreMetaOrSubmit} />
+            <div class="uppy-DashboardFileCard-info">
+              <fieldset class="uppy-DashboardFileCard-fieldset">
+                <label class="uppy-DashboardFileCard-label">Name</label>
+                <input class="uppy-DashboardFileCard-input"
+                  type="text"
+                  data-name="name"
+                  value={file.meta.name || ''}
+                  placeholder="name"
+                  onkeyup={this.tempStoreMetaOrSubmit}
+                  onkeydown={this.tempStoreMetaOrSubmit}
+                  onkeypress={this.tempStoreMetaOrSubmit} />
               </fieldset>
-              ${renderMetaFields(file)}
+              {this.renderMetaFields(file)}
             </div>
           </div>
-          <div class="UppyDashboard-actions">
-            <button class="UppyButton--circular UppyButton--blue UppyDashboardFileCard-done"
-                    type="button"
-                    title="Finish editing file"
-                    onclick=${() => props.done(meta, file.id)}>${checkIcon()}</button>
+          <div class="uppy-Dashboard-actions">
+            <button class="UppyButton--circular UppyButton--blue uppy-DashboardFileCard-done"
+              type="button"
+              title="Finish editing file"
+              onclick={this.handleClick}>{checkIcon()}</button>
           </div>
-        </div>`
-      : null
-    }
-  </div>`
+        </div>
+      }
+    </div>
+  }
 }

+ 108 - 109
src/plugins/Dashboard/FileItem.js

@@ -1,4 +1,3 @@
-const html = require('yo-yo')
 const { getETA,
          getSpeed,
          prettyETA,
@@ -9,6 +8,8 @@ const prettyBytes = require('prettier-bytes')
 const FileItemProgress = require('./FileItemProgress')
 const getFileTypeIcon = require('./getFileTypeIcon')
 const { iconEdit, iconCopy, iconRetry } = require('./icons')
+const classNames = require('classnames')
+const { h } = require('preact')
 
 module.exports = function fileItem (props) {
   const file = props.file
@@ -22,7 +23,7 @@ module.exports = function fileItem (props) {
   const error = file.error || false
 
   const fileName = getFileNameAndExtension(file.meta.name).name
-  const truncatedFileName = props.isWide ? truncateString(fileName, 16) : fileName
+  const truncatedFileName = props.isWide ? truncateString(fileName, 14) : fileName
 
   const onPauseResumeCancelRetry = (ev) => {
     if (isUploaded) return
@@ -37,123 +38,121 @@ module.exports = function fileItem (props) {
     }
   }
 
-  return html`<li class="UppyDashboardItem
-                        ${uploadInProgress ? 'is-inprogress' : ''}
-                        ${isProcessing ? 'is-processing' : ''}
-                        ${isUploaded ? 'is-complete' : ''}
-                        ${isPaused ? 'is-paused' : ''}
-                        ${error ? 'is-error' : ''}
-                        ${props.resumableUploads ? 'is-resumable' : ''}"
-                  id="uppy_${file.id}"
-                  title="${file.meta.name}">
-      <div class="UppyDashboardItem-preview">
-        <div class="UppyDashboardItem-previewInnerWrap" style="background-color: ${getFileTypeIcon(file.type).color}">
-          ${file.preview
-            ? html`<img alt="${file.name}" src="${file.preview}">`
-            : html`<div class="UppyDashboardItem-previewIconWrap">
-                <span class="UppyDashboardItem-previewIcon" style="color: ${getFileTypeIcon(file.type).color}">${getFileTypeIcon(file.type).icon}</span>
-                <svg class="UppyDashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF"/><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4"/></g></svg>
-              </div>`
-          }
-        </div>
-        <div class="UppyDashboardItem-progress">
-          ${isUploaded
-            ? html`<div class="UppyDashboardItem-progressIndicator">
-                ${FileItemProgress({
-                  progress: file.progress.percentage,
-                  fileID: file.id
-                })}
-              </div>`
-            : html`<button class="UppyDashboardItem-progressIndicator"
-                    type="button"
-                    title="${isUploaded
-                            ? 'upload complete'
-                            : props.resumableUploads
-                              ? file.isPaused
-                                ? 'resume upload'
-                                : 'pause upload'
-                              : 'cancel upload'
-                          }"
-                    onclick=${onPauseResumeCancelRetry}>
-              ${error
-                ? iconRetry()
-                : FileItemProgress({
-                  progress: file.progress.percentage,
-                  fileID: file.id
-                })
-              }
-            </button>`
-          }
-          ${props.showProgressDetails
-            ? html`<div class="UppyDashboardItem-progressInfo"
-                        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
-                }
-              </div>`
-            : null
-          }
-        </div>
+  const dashboardItemClass = classNames(
+    'uppy-DashboardItem',
+    { 'is-inprogress': uploadInProgress },
+    { 'is-processing': isProcessing },
+    { 'is-complete': isUploaded },
+    { 'is-paused': isPaused },
+    { 'is-error': error },
+    { 'is-resumable': props.resumableUploads }
+  )
+
+  return <li class={dashboardItemClass} id={`uppy_${file.id}`} title={file.meta.name}>
+    <div class="uppy-DashboardItem-preview">
+      <div class="uppy-DashboardItem-previewInnerWrap" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
+        {file.preview
+          ? <img alt={file.name} src={file.preview} />
+          : <div class="uppy-DashboardItem-previewIconWrap">
+            <span class="uppy-DashboardItem-previewIcon" style={{ color: getFileTypeIcon(file.type).color }}>{getFileTypeIcon(file.type).icon}</span>
+            <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
+          </div>
+        }
       </div>
-    <div class="UppyDashboardItem-info">
-      <h4 class="UppyDashboardItem-name" title="${fileName}">
-        ${file.uploadURL
-          ? html`<a href="${file.uploadURL}" target="_blank">
-              ${file.extension ? truncatedFileName + '.' + file.extension : truncatedFileName}
-            </a>`
+      <div class="uppy-DashboardItem-progress">
+        {isUploaded
+          ? <div class="uppy-DashboardItem-progressIndicator">
+            {FileItemProgress({
+              progress: file.progress.percentage,
+              fileID: file.id
+            })}
+          </div>
+          : <button class="uppy-DashboardItem-progressIndicator"
+            type="button"
+            title={isUploaded
+                    ? 'upload complete'
+                    : props.resumableUploads
+                      ? file.isPaused
+                        ? 'resume upload'
+                        : 'pause upload'
+                      : 'cancel upload'
+                  }
+            onclick={onPauseResumeCancelRetry}>
+            {error
+              ? iconRetry()
+              : FileItemProgress({
+                progress: file.progress.percentage,
+                fileID: file.id
+              })
+            }
+          </button>
+        }
+        {props.showProgressDetails &&
+          <div class="uppy-DashboardItem-progressInfo"
+            title={props.i18n('fileProgress')}
+            aria-label={props.i18n('fileProgress')}>
+            {(!file.isPaused && !isUploaded) &&
+              <span>{prettyETA(getETA(file.progress))} ・ ↑ {prettyBytes(getSpeed(file.progress))}/s</span>
+            }
+          </div>
+        }
+      </div>
+    </div>
+    <div class="uppy-DashboardItem-info">
+      <h4 class="uppy-DashboardItem-name" title="{fileName}">
+        {file.uploadURL
+          ? <a href="{file.uploadURL}" target="_blank">
+            {file.extension ? truncatedFileName + '.' + file.extension : truncatedFileName}
+          </a>
           : file.extension ? truncatedFileName + '.' + file.extension : truncatedFileName
         }
       </h4>
-      <div class="UppyDashboardItem-status">
-        ${file.data.size && html`<div class="UppyDashboardItem-statusSize">${prettyBytes(file.data.size)}</div>`}
-        ${file.source && html`<div class="UppyDashboardItem-sourceIcon">
-            ${acquirers.map(acquirer => {
-              if (acquirer.id === file.source) return html`<span title="${props.i18n('fileSource')}: ${acquirer.name}">${acquirer.icon()}</span>`
+      <div class="uppy-DashboardItem-status">
+        {file.data.size && <div class="uppy-DashboardItem-statusSize">{prettyBytes(file.data.size)}</div>}
+        {file.source && <div class="uppy-DashboardItem-sourceIcon">
+            {acquirers.map(acquirer => {
+              if (acquirer.id === file.source) return <span title="{props.i18n('fileSource')}: {acquirer.name}">{acquirer.icon()}</span>
             })}
-          </div>`
+          </div>
         }
       </div>
-      ${!uploadInProgressOrComplete
-        ? html`<button class="UppyDashboardItem-edit"
-                       type="button"
-                       aria-label="Edit file"
-                       title="Edit file"
-                       onclick=${(e) => props.showFileCard(file.id)}>
-                        ${iconEdit()}</button>`
-        : null
+      {!uploadInProgressOrComplete &&
+        <button class="uppy-DashboardItem-edit"
+          type="button"
+          aria-label="Edit file"
+          title="Edit file"
+          onclick={(e) => props.showFileCard(file.id)}>
+          {iconEdit()}
+        </button>
       }
-      ${file.uploadURL
-        ? html`<button class="UppyDashboardItem-copyLink"
-                       type="button"
-                       aria-label="Copy link"
-                       title="Copy link"
-                       onclick=${() => {
-                         copyToClipboard(file.uploadURL, props.i18n('copyLinkToClipboardFallback'))
-                          .then(() => {
-                            props.log('Link copied to clipboard.')
-                            props.info(props.i18n('copyLinkToClipboardSuccess'), 'info', 3000)
-                          })
-                          .catch(props.log)
-                       }}>${iconCopy()}</button>`
-        : null
+      {file.uploadURL &&
+        <button class="uppy-DashboardItem-copyLink"
+          type="button"
+          aria-label="Copy link"
+          title="Copy link"
+          onclick={() => {
+            copyToClipboard(file.uploadURL, props.i18n('copyLinkToClipboardFallback'))
+            .then(() => {
+              props.log('Link copied to clipboard.')
+              props.info(props.i18n('copyLinkToClipboardSuccess'), 'info', 3000)
+            })
+            .catch(props.log)
+          }}>{iconCopy()}</button>
       }
     </div>
-    <div class="UppyDashboardItem-action">
-      ${!isUploaded
-        ? html`<button class="UppyDashboardItem-remove"
-                       type="button"
-                       aria-label="Remove file"
-                       title="Remove file"
-                       onclick=${() => props.removeFile(file.id)}>
-                 <svg aria-hidden="true" class="UppyIcon" width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
-                    <path stroke="#FFF" stroke-width="0.8px" fill-rule="nonzero" vector-effect="non-scaling-stroke" d="M30 1C14 1 1 14 1 30s13 29 29 29 29-13 29-29S46 1 30 1z" />
-                    <path fill="#FFF" vector-effect="non-scaling-stroke" d="M42 39.667L39.667 42 30 32.333 20.333 42 18 39.667 27.667 30 18 20.333 20.333 18 30 27.667 39.667 18 42 20.333 32.333 30z"/>
-                 </svg>
-               </button>`
-        : null
+    <div class="uppy-DashboardItem-action">
+      {!isUploaded &&
+        <button class="uppy-DashboardItem-remove"
+          type="button"
+          aria-label="Remove file"
+          title="Remove file"
+          onclick={() => props.removeFile(file.id)}>
+          <svg aria-hidden="true" class="UppyIcon" width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
+            <path stroke="#FFF" stroke-width="1" fill-rule="nonzero" vector-effect="non-scaling-stroke" d="M30 1C14 1 1 14 1 30s13 29 29 29 29-13 29-29S46 1 30 1z" />
+            <path fill="#FFF" vector-effect="non-scaling-stroke" d="M42 39.667L39.667 42 30 32.333 20.333 42 18 39.667 27.667 30 18 20.333 20.333 18 30 27.667 39.667 18 42 20.333 32.333 30z" />
+          </svg>
+        </button>
       }
     </div>
-  </li>`
+  </li>
 }

+ 10 - 9
src/plugins/Dashboard/FileItemProgress.js

@@ -1,4 +1,4 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 
 // http://codepen.io/Harkko/pen/rVxvNM
 // https://css-tricks.com/svg-line-animation-works/
@@ -10,21 +10,22 @@ const circleLength = 2 * Math.PI * 15
 // stroke-dashoffset is a percentage of the progress from circleLength,
 // substracted from circleLength, because its an offset
 module.exports = (props) => {
-  return html`
+  return (
     <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" stroke-width="2" fill="none" class="bg" />
         <circle r="15" cx="18" cy="18" transform="rotate(-90, 18, 18)" stroke-width="2" fill="none" class="progress"
-                stroke-dasharray=${circleLength}
-                stroke-dashoffset=${circleLength - (circleLength / 100 * props.progress)}
+          stroke-dasharray={circleLength}
+          stroke-dashoffset={circleLength - (circleLength / 100 * props.progress)}
         />
       </g>
-      <polygon transform="translate(3, 3)" points="12 20 12 10 20 15" class="play"/>
+      <polygon transform="translate(3, 3)" points="12 20 12 10 20 15" class="play" />
       <g transform="translate(14.5, 13)" class="pause">
         <rect x="0" y="0" width="2" height="10" rx="0" />
         <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>`
+      <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" />
+    </svg>
+  )
 }

+ 40 - 38
src/plugins/Dashboard/FileList.js

@@ -1,44 +1,46 @@
-const html = require('yo-yo')
 const FileItem = require('./FileItem')
 const ActionBrowseTagline = require('./ActionBrowseTagline')
 const { dashboardBgIcon } = require('./icons')
+const classNames = require('classnames')
+const { h } = require('preact')
 
 module.exports = (props) => {
-  return html`<ul class="UppyDashboard-files
-                         ${props.totalFileCount === 0 ? 'UppyDashboard-files--noFiles' : ''}">
-      ${props.totalFileCount === 0
-       ? html`<div class="UppyDashboard-bgIcon">
-          ${dashboardBgIcon()}
-          <h3 class="UppyDashboard-dropFilesTitle">
-            ${ActionBrowseTagline({
-              acquirers: props.acquirers,
-              handleInputChange: props.handleInputChange,
-              i18n: props.i18n
-            })}
-          </h3>
-          ${props.note
-            ? html`<p class="UppyDashboard-note">${props.note}</p>`
-            : ''
-          }
-         </div>`
-       : null
-      }
-      ${Object.keys(props.files).map((fileID) => {
-        return FileItem({
-          acquirers: props.acquirers,
-          file: props.files[fileID],
-          showFileCard: props.showFileCard,
-          showProgressDetails: props.showProgressDetails,
-          info: props.info,
-          log: props.log,
-          i18n: props.i18n,
-          removeFile: props.removeFile,
-          pauseUpload: props.pauseUpload,
-          cancelUpload: props.cancelUpload,
-          retryUpload: props.retryUpload,
-          resumableUploads: props.resumableUploads,
-          isWide: props.isWide
-        })
-      })}
-    </ul>`
+  const noFiles = props.totalFileCount === 0
+  const dashboardFilesClass = classNames(
+    'uppy-Dashboard-files',
+    { 'uppy-Dashboard-files--noFiles': noFiles }
+  )
+
+  return <ul class={dashboardFilesClass}>
+    {noFiles &&
+      <div class="uppy-Dashboard-bgIcon">
+        {dashboardBgIcon()}
+        <h3 class="uppy-Dashboard-dropFilesTitle">
+          {h(ActionBrowseTagline, {
+            acquirers: props.acquirers,
+            handleInputChange: props.handleInputChange,
+            i18n: props.i18n
+          })}
+        </h3>
+        { props.note && <p class="uppy-Dashboard-note">{props.note}</p> }
+      </div>
+    }
+    {Object.keys(props.files).map((fileID) => {
+      return FileItem({
+        acquirers: props.acquirers,
+        file: props.files[fileID],
+        showFileCard: props.showFileCard,
+        showProgressDetails: props.showProgressDetails,
+        info: props.info,
+        log: props.log,
+        i18n: props.i18n,
+        removeFile: props.removeFile,
+        pauseUpload: props.pauseUpload,
+        cancelUpload: props.cancelUpload,
+        retryUpload: props.retryUpload,
+        resumableUploads: props.resumableUploads,
+        isWide: props.isWide
+      })
+    })}
+  </ul>
 }

+ 59 - 49
src/plugins/Dashboard/Tabs.js

@@ -1,62 +1,72 @@
-const html = require('yo-yo')
 const ActionBrowseTagline = require('./ActionBrowseTagline')
 const { localIcon } = require('./icons')
+const { h, Component } = require('preact')
 
-module.exports = (props) => {
-  const isHidden = Object.keys(props.files).length === 0
+class Tabs extends Component {
+  constructor (props) {
+    super(props)
+    this.handleClick = this.handleClick.bind(this)
+  }
 
-  if (props.acquirers.length === 0) {
-    return html`
-      <div class="UppyDashboardTabs" aria-hidden="${isHidden}">
-        <h3 class="UppyDashboardTabs-title">
-        ${ActionBrowseTagline({
-          acquirers: props.acquirers,
-          handleInputChange: props.handleInputChange,
-          i18n: props.i18n
-        })}
-        </h3>
-      </div>
-    `
+  handleClick (ev) {
+    this.input.click()
   }
 
-  const input = html`
-    <input class="UppyDashboard-input"
-          hidden="true"
-          aria-hidden="true" 
-          tabindex="-1" 
-          type="file" 
-          name="files[]" 
-          multiple="true"
-          onchange=${props.handleInputChange} />`
+  render () {
+    const isHidden = Object.keys(this.props.files).length === 0
+    const hasAcquirers = this.props.acquirers.length !== 0
+
+    if (!hasAcquirers) {
+      return (
+        <div class="uppy-DashboardTabs" aria-hidden={isHidden}>
+          <div class="uppy-DashboardTabs-title">
+            <ActionBrowseTagline
+              acquirers={this.props.acquirers}
+              handleInputChange={this.props.handleInputChange}
+              i18n={this.props.i18n} />
+          </div>
+        </div>
+      )
+    }
 
-  return html`<div class="UppyDashboardTabs">
-      <ul class="UppyDashboardTabs-list" role="tablist">
-        <li class="UppyDashboardTab" role="presentation">
-          <button type="button" class="UppyDashboardTab-btn"
-                  role="tab"
-                  tabindex="0"
-                  onclick=${(ev) => {
-                    input.click()
-                  }}>
-            ${localIcon()}
-            <h5 class="UppyDashboardTab-name">${props.i18n('myDevice')}</h5>
+    return <div class="uppy-DashboardTabs">
+      <ul class="uppy-DashboardTabs-list" role="tablist">
+        <li class="uppy-DashboardTab" role="presentation">
+          <button type="button"
+            class="uppy-DashboardTab-btn"
+            role="tab"
+            tabindex="0"
+            onclick={this.handleClick}>
+            {localIcon()}
+            <div class="uppy-DashboardTab-name">{this.props.i18n('myDevice')}</div>
           </button>
-          ${input}
+          <input class="uppy-Dashboard-input"
+            hidden="true"
+            aria-hidden="true"
+            tabindex="-1"
+            type="file"
+            name="files[]"
+            multiple="true"
+            onchange={this.props.handleInputChange}
+            ref={(input) => { this.input = input }} />
         </li>
-        ${props.acquirers.map((target) => {
-          return html`<li class="UppyDashboardTab" role="presentation">
-            <button class="UppyDashboardTab-btn"
-                    type="button"
-                    role="tab"
-                    tabindex="0"
-                    aria-controls="UppyDashboardContent-panel--${target.id}"
-                    aria-selected="${target.isHidden ? 'false' : 'true'}"
-                    onclick=${() => props.showPanel(target.id)}>
-              ${target.icon()}
-              <h5 class="UppyDashboardTab-name">${target.name}</h5>
+        {this.props.acquirers.map((target) => {
+          return <li class="uppy-DashboardTab" role="presentation">
+            <button class="uppy-DashboardTab-btn"
+              type="button"
+              role="tab"
+              tabindex="0"
+              aria-controls={`uppy-DashboardContent-panel--${target.id}`}
+              aria-selected={this.props.activePanel.id === target.id}
+              onclick={() => this.props.showPanel(target.id)}>
+              {target.icon()}
+              <h5 class="uppy-DashboardTab-name">{target.name}</h5>
             </button>
-          </li>`
+          </li>
         })}
       </ul>
-  </div>`
+    </div>
+  }
 }
+
+module.exports = Tabs

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

@@ -1,21 +0,0 @@
-const html = require('yo-yo')
-const { uploadIcon } = require('./icons')
-
-module.exports = (props) => {
-  props = props || {}
-
-  return html`<button class="UppyButton--circular
-                   UppyButton--blue
-                   UppyDashboard-upload"
-                 type="button"
-                 title="${props.i18n('uploadAllNewFiles')}"
-                 aria-label="${props.i18n('uploadAllNewFiles')}"
-                 onclick=${props.startUpload}>
-            ${uploadIcon()}
-            <sup class="UppyDashboard-uploadCount"
-                 title="${props.i18n('numberOfSelectedFiles')}"
-                 aria-label="${props.i18n('numberOfSelectedFiles')}">
-                  ${props.newFileCount}</sup>
-    </button>
-  `
-}

+ 53 - 51
src/plugins/Dashboard/icons.js

@@ -1,115 +1,117 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 
 // https://css-tricks.com/creating-svg-icon-system-react/
 
 function defaultTabIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="30" height="30" viewBox="0 0 30 30">
+  return <svg aria-hidden="true" class="UppyIcon" width="30" height="30" viewBox="0 0 30 30">
     <path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
-  </svg>`
+  </svg>
 }
 
 function iconCopy () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="51" height="51" viewBox="0 0 51 51">
-    <path d="M17.21 45.765a5.394 5.394 0 0 1-7.62 0l-4.12-4.122a5.393 5.393 0 0 1 0-7.618l6.774-6.775-2.404-2.404-6.775 6.776c-3.424 3.427-3.424 9 0 12.426l4.12 4.123a8.766 8.766 0 0 0 6.216 2.57c2.25 0 4.5-.858 6.214-2.57l13.55-13.552a8.72 8.72 0 0 0 2.575-6.213 8.73 8.73 0 0 0-2.575-6.213l-4.123-4.12-2.404 2.404 4.123 4.12a5.352 5.352 0 0 1 1.58 3.81c0 1.438-.562 2.79-1.58 3.808l-13.55 13.55z"/>
-    <path d="M44.256 2.858A8.728 8.728 0 0 0 38.043.283h-.002a8.73 8.73 0 0 0-6.212 2.574l-13.55 13.55a8.725 8.725 0 0 0-2.575 6.214 8.73 8.73 0 0 0 2.574 6.216l4.12 4.12 2.405-2.403-4.12-4.12a5.357 5.357 0 0 1-1.58-3.812c0-1.437.562-2.79 1.58-3.808l13.55-13.55a5.348 5.348 0 0 1 3.81-1.58c1.44 0 2.792.562 3.81 1.58l4.12 4.12c2.1 2.1 2.1 5.518 0 7.617L39.2 23.775l2.404 2.404 6.775-6.777c3.426-3.427 3.426-9 0-12.426l-4.12-4.12z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="51" height="51" viewBox="0 0 51 51">
+    <path d="M17.21 45.765a5.394 5.394 0 0 1-7.62 0l-4.12-4.122a5.393 5.393 0 0 1 0-7.618l6.774-6.775-2.404-2.404-6.775 6.776c-3.424 3.427-3.424 9 0 12.426l4.12 4.123a8.766 8.766 0 0 0 6.216 2.57c2.25 0 4.5-.858 6.214-2.57l13.55-13.552a8.72 8.72 0 0 0 2.575-6.213 8.73 8.73 0 0 0-2.575-6.213l-4.123-4.12-2.404 2.404 4.123 4.12a5.352 5.352 0 0 1 1.58 3.81c0 1.438-.562 2.79-1.58 3.808l-13.55 13.55z" />
+    <path d="M44.256 2.858A8.728 8.728 0 0 0 38.043.283h-.002a8.73 8.73 0 0 0-6.212 2.574l-13.55 13.55a8.725 8.725 0 0 0-2.575 6.214 8.73 8.73 0 0 0 2.574 6.216l4.12 4.12 2.405-2.403-4.12-4.12a5.357 5.357 0 0 1-1.58-3.812c0-1.437.562-2.79 1.58-3.808l13.55-13.55a5.348 5.348 0 0 1 3.81-1.58c1.44 0 2.792.562 3.81 1.58l4.12 4.12c2.1 2.1 2.1 5.518 0 7.617L39.2 23.775l2.404 2.404 6.775-6.777c3.426-3.427 3.426-9 0-12.426l-4.12-4.12z" />
+  </svg>
 }
 
 function iconResume () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 44 44">
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 44 44">
     <polygon class="play" transform="translate(6, 5.5)" points="13 21.6666667 13 11 21 16.3333333" />
-  </svg>`
+  </svg>
 }
 
 function iconPause () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="25px" height="25px" viewBox="0 0 44 44">
+  return <svg aria-hidden="true" class="UppyIcon" width="25px" height="25px" viewBox="0 0 44 44">
     <g transform="translate(18, 17)" class="pause">
       <rect x="0" y="0" width="2" height="10" rx="0" />
       <rect x="6" y="0" width="2" height="10" rx="0" />
     </g>
-  </svg>`
+  </svg>
 }
 
 function iconEdit () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 28 28">
+  return <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 28 28">
     <path d="M25.436 2.566a7.98 7.98 0 0 0-2.078-1.51C22.638.703 21.906.5 21.198.5a3 3 0 0 0-1.023.17 2.436 2.436 0 0 0-.893.562L2.292 18.217.5 27.5l9.28-1.796 16.99-16.99c.255-.254.444-.56.562-.888a3 3 0 0 0 .17-1.023c0-.708-.205-1.44-.555-2.16a8 8 0 0 0-1.51-2.077zM9.01 24.252l-4.313.834c0-.03.008-.06.012-.09.007-.944-.74-1.715-1.67-1.723-.04 0-.078.007-.118.01l.83-4.29L17.72 5.024l5.264 5.264L9.01 24.252zm16.84-16.96a.818.818 0 0 1-.194.31l-1.57 1.57-5.26-5.26 1.57-1.57a.82.82 0 0 1 .31-.194 1.45 1.45 0 0 1 .492-.074c.397 0 .917.126 1.468.397.55.27 1.13.678 1.656 1.21.53.53.94 1.11 1.208 1.655.272.55.397 1.07.393 1.468.004.193-.027.358-.074.488z" />
-  </svg>`
+  </svg>
 }
 
 function localIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="27" height="25" viewBox="0 0 27 25">
-    <path d="M5.586 9.288a.313.313 0 0 0 .282.176h4.84v3.922c0 1.514 1.25 2.24 2.792 2.24 1.54 0 2.79-.726 2.79-2.24V9.464h4.84c.122 0 .23-.068.284-.176a.304.304 0 0 0-.046-.324L13.735.106a.316.316 0 0 0-.472 0l-7.63 8.857a.302.302 0 0 0-.047.325z"/>
-    <path d="M24.3 5.093c-.218-.76-.54-1.187-1.208-1.187h-4.856l1.018 1.18h3.948l2.043 11.038h-7.193v2.728H9.114v-2.725h-7.36l2.66-11.04h3.33l1.018-1.18H3.907c-.668 0-1.06.46-1.21 1.186L0 16.456v7.062C0 24.338.676 25 1.51 25h23.98c.833 0 1.51-.663 1.51-1.482v-7.062L24.3 5.093z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="27" height="25" viewBox="0 0 27 25">
+    <path d="M5.586 9.288a.313.313 0 0 0 .282.176h4.84v3.922c0 1.514 1.25 2.24 2.792 2.24 1.54 0 2.79-.726 2.79-2.24V9.464h4.84c.122 0 .23-.068.284-.176a.304.304 0 0 0-.046-.324L13.735.106a.316.316 0 0 0-.472 0l-7.63 8.857a.302.302 0 0 0-.047.325z" />
+    <path d="M24.3 5.093c-.218-.76-.54-1.187-1.208-1.187h-4.856l1.018 1.18h3.948l2.043 11.038h-7.193v2.728H9.114v-2.725h-7.36l2.66-11.04h3.33l1.018-1.18H3.907c-.668 0-1.06.46-1.21 1.186L0 16.456v7.062C0 24.338.676 25 1.51 25h23.98c.833 0 1.51-.663 1.51-1.482v-7.062L24.3 5.093z" />
+  </svg>
 }
 
 function closeIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="14px" height="14px" viewBox="0 0 19 19">
-    <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="14px" height="14px" viewBox="0 0 19 19">
+    <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
+  </svg>
 }
 
 function iconRetry () {
-  return html`<svg class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
-    <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z"/>
-    <path d="M7.9 3H10v2H7.9z"/><path d="M8.536.5l3.535 3.536-1.414 1.414L7.12 1.914z"/><path d="M10.657 2.621l1.414 1.415L8.536 7.57 7.12 6.157z"/>
-  </svg>`
+  return <svg class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
+    <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z" />
+    <path d="M7.9 3H10v2H7.9z" />
+    <path d="M8.536.5l3.535 3.536-1.414 1.414L7.12 1.914z" />
+    <path d="M10.657 2.621l1.414 1.415L8.536 7.57 7.12 6.157z" />
+  </svg>
 }
 
 function pluginIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 32 30">
-      <path d="M6.6209894,11.1451162 C6.6823051,11.2751669 6.81374248,11.3572188 6.95463813,11.3572188 L12.6925482,11.3572188 L12.6925482,16.0630427 C12.6925482,17.880509 14.1726048,18.75 16.0000083,18.75 C17.8261072,18.75 19.3074684,17.8801847 19.3074684,16.0630427 L19.3074684,11.3572188 L25.0437478,11.3572188 C25.1875787,11.3572188 25.3164069,11.2751669 25.3790272,11.1451162 C25.4370814,11.0173358 25.4171865,10.8642587 25.3252129,10.7562615 L16.278212,0.127131837 C16.2093949,0.0463771751 16.1069846,0 15.9996822,0 C15.8910751,0 15.7886648,0.0463771751 15.718217,0.127131837 L6.6761083,10.7559371 C6.58250402,10.8642587 6.56293518,11.0173358 6.6209894,11.1451162 L6.6209894,11.1451162 Z"/>
-      <path d="M28.8008722,6.11142645 C28.5417891,5.19831555 28.1583331,4.6875 27.3684848,4.6875 L21.6124454,4.6875 L22.8190234,6.10307874 L27.4986725,6.10307874 L29.9195817,19.3486449 L21.3943891,19.3502502 L21.3943891,22.622552 L10.8023461,22.622552 L10.8023461,19.3524977 L2.07815702,19.3534609 L5.22979699,6.10307874 L9.17871529,6.10307874 L10.3840011,4.6875 L4.6308691,4.6875 C3.83940559,4.6875 3.37421888,5.2390909 3.19815864,6.11142645 L0,19.7470874 L0,28.2212959 C0,29.2043992 0.801477937,30 1.78870751,30 L30.2096773,30 C31.198199,30 32,29.2043992 32,28.2212959 L32,19.7470874 L28.8008722,6.11142645 L28.8008722,6.11142645 Z"/>
-    </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 32 30">
+    <path d="M6.6209894,11.1451162 C6.6823051,11.2751669 6.81374248,11.3572188 6.95463813,11.3572188 L12.6925482,11.3572188 L12.6925482,16.0630427 C12.6925482,17.880509 14.1726048,18.75 16.0000083,18.75 C17.8261072,18.75 19.3074684,17.8801847 19.3074684,16.0630427 L19.3074684,11.3572188 L25.0437478,11.3572188 C25.1875787,11.3572188 25.3164069,11.2751669 25.3790272,11.1451162 C25.4370814,11.0173358 25.4171865,10.8642587 25.3252129,10.7562615 L16.278212,0.127131837 C16.2093949,0.0463771751 16.1069846,0 15.9996822,0 C15.8910751,0 15.7886648,0.0463771751 15.718217,0.127131837 L6.6761083,10.7559371 C6.58250402,10.8642587 6.56293518,11.0173358 6.6209894,11.1451162 L6.6209894,11.1451162 Z" />
+    <path d="M28.8008722,6.11142645 C28.5417891,5.19831555 28.1583331,4.6875 27.3684848,4.6875 L21.6124454,4.6875 L22.8190234,6.10307874 L27.4986725,6.10307874 L29.9195817,19.3486449 L21.3943891,19.3502502 L21.3943891,22.622552 L10.8023461,22.622552 L10.8023461,19.3524977 L2.07815702,19.3534609 L5.22979699,6.10307874 L9.17871529,6.10307874 L10.3840011,4.6875 L4.6308691,4.6875 C3.83940559,4.6875 3.37421888,5.2390909 3.19815864,6.11142645 L0,19.7470874 L0,28.2212959 C0,29.2043992 0.801477937,30 1.78870751,30 L30.2096773,30 C31.198199,30 32,29.2043992 32,28.2212959 L32,19.7470874 L28.8008722,6.11142645 L28.8008722,6.11142645 Z" />
+  </svg>
 }
 
 function checkIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon UppyIcon-check" width="13px" height="9px" viewBox="0 0 13 9">
-    <polygon points="5 7.293 1.354 3.647 0.646 4.354 5 8.707 12.354 1.354 11.646 0.647"></polygon>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon UppyIcon-check" width="13px" height="9px" viewBox="0 0 13 9">
+    <polygon points="5 7.293 1.354 3.647 0.646 4.354 5 8.707 12.354 1.354 11.646 0.647" />
+  </svg>
 }
 
 function iconAudio () {
-  return html`<svg aria-hidden="true" class="UppyIcon" viewBox="0 0 55 55">
-    <path d="M52.66.25c-.216-.19-.5-.276-.79-.242l-31 4.01a1 1 0 0 0-.87.992V40.622C18.174 38.428 15.273 37 12 37c-5.514 0-10 4.037-10 9s4.486 9 10 9 10-4.037 10-9c0-.232-.02-.46-.04-.687.014-.065.04-.124.04-.192V16.12l29-3.753v18.257C49.174 28.428 46.273 27 43 27c-5.514 0-10 4.037-10 9s4.486 9 10 9c5.464 0 9.913-3.966 9.993-8.867 0-.013.007-.024.007-.037V1a.998.998 0 0 0-.34-.75zM12 53c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zm31-10c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zM22 14.1V5.89l29-3.753v8.21l-29 3.754z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 55 55">
+    <path d="M52.66.25c-.216-.19-.5-.276-.79-.242l-31 4.01a1 1 0 0 0-.87.992V40.622C18.174 38.428 15.273 37 12 37c-5.514 0-10 4.037-10 9s4.486 9 10 9 10-4.037 10-9c0-.232-.02-.46-.04-.687.014-.065.04-.124.04-.192V16.12l29-3.753v18.257C49.174 28.428 46.273 27 43 27c-5.514 0-10 4.037-10 9s4.486 9 10 9c5.464 0 9.913-3.966 9.993-8.867 0-.013.007-.024.007-.037V1a.998.998 0 0 0-.34-.75zM12 53c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zm31-10c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zM22 14.1V5.89l29-3.753v8.21l-29 3.754z" />
+  </svg>
 }
 
 function iconVideo () {
-  return html`<svg aria-hidden="true" class="UppyIcon" viewBox="0 0 58 58">
-    <path d="M36.537 28.156l-11-7a1.005 1.005 0 0 0-1.02-.033C24.2 21.3 24 21.635 24 22v14a1 1 0 0 0 1.537.844l11-7a1.002 1.002 0 0 0 0-1.688zM26 34.18V23.82L34.137 29 26 34.18z"/><path d="M57 6H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h56a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1zM10 28H2v-9h8v9zm-8 2h8v9H2v-9zm10 10V8h34v42H12V40zm44-12h-8v-9h8v9zm-8 2h8v9h-8v-9zm8-22v9h-8V8h8zM2 8h8v9H2V8zm0 42v-9h8v9H2zm54 0h-8v-9h8v9z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 58 58">
+    <path d="M36.537 28.156l-11-7a1.005 1.005 0 0 0-1.02-.033C24.2 21.3 24 21.635 24 22v14a1 1 0 0 0 1.537.844l11-7a1.002 1.002 0 0 0 0-1.688zM26 34.18V23.82L34.137 29 26 34.18z" /><path d="M57 6H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h56a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1zM10 28H2v-9h8v9zm-8 2h8v9H2v-9zm10 10V8h34v42H12V40zm44-12h-8v-9h8v9zm-8 2h8v9h-8v-9zm8-22v9h-8V8h8zM2 8h8v9H2V8zm0 42v-9h8v9H2zm54 0h-8v-9h8v9z" />
+  </svg>
 }
 
 function iconPDF () {
-  return html`<svg aria-hidden="true" class="UppyIcon" viewBox="0 0 342 335">
+  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 342 335">
     <path d="M329.337 227.84c-2.1 1.3-8.1 2.1-11.9 2.1-12.4 0-27.6-5.7-49.1-14.9 8.3-.6 15.8-.9 22.6-.9 12.4 0 16 0 28.2 3.1 12.1 3 12.2 9.3 10.2 10.6zm-215.1 1.9c4.8-8.4 9.7-17.3 14.7-26.8 12.2-23.1 20-41.3 25.7-56.2 11.5 20.9 25.8 38.6 42.5 52.8 2.1 1.8 4.3 3.5 6.7 5.3-34.1 6.8-63.6 15-89.6 24.9zm39.8-218.9c6.8 0 10.7 17.06 11 33.16.3 16-3.4 27.2-8.1 35.6-3.9-12.4-5.7-31.8-5.7-44.5 0 0-.3-24.26 2.8-24.26zm-133.4 307.2c3.9-10.5 19.1-31.3 41.6-49.8 1.4-1.1 4.9-4.4 8.1-7.4-23.5 37.6-39.3 52.5-49.7 57.2zm315.2-112.3c-6.8-6.7-22-10.2-45-10.5-15.6-.2-34.3 1.2-54.1 3.9-8.8-5.1-17.9-10.6-25.1-17.3-19.2-18-35.2-42.9-45.2-70.3.6-2.6 1.2-4.8 1.7-7.1 0 0 10.8-61.5 7.9-82.3-.4-2.9-.6-3.7-1.4-5.9l-.9-2.5c-2.9-6.76-8.7-13.96-17.8-13.57l-5.3-.17h-.1c-10.1 0-18.4 5.17-20.5 12.84-6.6 24.3.2 60.5 12.5 107.4l-3.2 7.7c-8.8 21.4-19.8 43-29.5 62l-1.3 2.5c-10.2 20-19.5 37-27.9 51.4l-8.7 4.6c-.6.4-15.5 8.2-19 10.3-29.6 17.7-49.28 37.8-52.54 53.8-1.04 5-.26 11.5 5.01 14.6l8.4 4.2c3.63 1.8 7.53 2.7 11.43 2.7 21.1 0 45.6-26.2 79.3-85.1 39-12.7 83.4-23.3 122.3-29.1 29.6 16.7 66 28.3 89 28.3 4.1 0 7.6-.4 10.5-1.2 4.4-1.1 8.1-3.6 10.4-7.1 4.4-6.7 5.4-15.9 4.1-25.4-.3-2.8-2.6-6.3-5-8.7z" />
-  </svg>`
+  </svg>
 }
 
 function iconFile () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="44" height="58" viewBox="0 0 44 58">
+  return <svg aria-hidden="true" class="UppyIcon" width="44" height="58" viewBox="0 0 44 58">
     <path d="M27.437.517a1 1 0 0 0-.094.03H4.25C2.037.548.217 2.368.217 4.58v48.405c0 2.212 1.82 4.03 4.03 4.03H39.03c2.21 0 4.03-1.818 4.03-4.03V15.61a1 1 0 0 0-.03-.28 1 1 0 0 0 0-.093 1 1 0 0 0-.03-.032 1 1 0 0 0 0-.03 1 1 0 0 0-.032-.063 1 1 0 0 0-.03-.063 1 1 0 0 0-.032 0 1 1 0 0 0-.03-.063 1 1 0 0 0-.032-.03 1 1 0 0 0-.03-.063 1 1 0 0 0-.063-.062l-14.593-14a1 1 0 0 0-.062-.062A1 1 0 0 0 28 .708a1 1 0 0 0-.374-.157 1 1 0 0 0-.156 0 1 1 0 0 0-.03-.03l-.003-.003zM4.25 2.547h22.218v9.97c0 2.21 1.82 4.03 4.03 4.03h10.564v36.438a2.02 2.02 0 0 1-2.032 2.032H4.25c-1.13 0-2.032-.9-2.032-2.032V4.58c0-1.13.902-2.032 2.03-2.032zm24.218 1.345l10.375 9.937.75.718H30.5c-1.13 0-2.032-.9-2.032-2.03V3.89z" />
-  </svg>`
+  </svg>
 }
 
 function iconText () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="62" height="62" viewBox="0 0 62 62" xmlns="http://www.w3.org/2000/svg">
-    <path d="M4.309 4.309h24.912v53.382h-6.525v3.559h16.608v-3.559h-6.525V4.309h24.912v10.676h3.559V.75H.75v14.235h3.559z" fill-rule="nonzero" fill="#000"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="62" height="62" viewBox="0 0 62 62" xmlns="http://www.w3.org/2000/svg">
+    <path d="M4.309 4.309h24.912v53.382h-6.525v3.559h16.608v-3.559h-6.525V4.309h24.912v10.676h3.559V.75H.75v14.235h3.559z" fill-rule="nonzero" fill="#000" />
+  </svg>
 }
 
 function uploadIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="37" height="33" viewBox="0 0 37 33">
-    <path d="M29.107 24.5c4.07 0 7.393-3.355 7.393-7.442 0-3.994-3.105-7.307-7.012-7.502l.468.415C29.02 4.52 24.34.5 18.886.5c-4.348 0-8.27 2.522-10.138 6.506l.446-.288C4.394 6.782.5 10.758.5 15.608c0 4.924 3.906 8.892 8.76 8.892h4.872c.635 0 1.095-.467 1.095-1.104 0-.636-.46-1.103-1.095-1.103H9.26c-3.644 0-6.63-3.035-6.63-6.744 0-3.71 2.926-6.685 6.57-6.685h.964l.14-.28.177-.362c1.477-3.4 4.744-5.576 8.347-5.576 4.58 0 8.45 3.452 9.01 8.072l.06.536.05.446h1.101c2.87 0 5.204 2.37 5.204 5.295s-2.333 5.296-5.204 5.296h-6.062c-.634 0-1.094.467-1.094 1.103 0 .637.46 1.104 1.094 1.104h6.12z"/>
-    <path d="M23.196 18.92l-4.828-5.258-.366-.4-.368.398-4.828 5.196a1.13 1.13 0 0 0 0 1.546c.428.46 1.11.46 1.537 0l3.45-3.71-.868-.34v15.03c0 .64.445 1.118 1.075 1.118.63 0 1.075-.48 1.075-1.12V16.35l-.867.34 3.45 3.712a1 1 0 0 0 .767.345 1 1 0 0 0 .77-.345c.416-.33.416-1.036 0-1.485v.003z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="37" height="33" viewBox="0 0 37 33">
+    <path d="M29.107 24.5c4.07 0 7.393-3.355 7.393-7.442 0-3.994-3.105-7.307-7.012-7.502l.468.415C29.02 4.52 24.34.5 18.886.5c-4.348 0-8.27 2.522-10.138 6.506l.446-.288C4.394 6.782.5 10.758.5 15.608c0 4.924 3.906 8.892 8.76 8.892h4.872c.635 0 1.095-.467 1.095-1.104 0-.636-.46-1.103-1.095-1.103H9.26c-3.644 0-6.63-3.035-6.63-6.744 0-3.71 2.926-6.685 6.57-6.685h.964l.14-.28.177-.362c1.477-3.4 4.744-5.576 8.347-5.576 4.58 0 8.45 3.452 9.01 8.072l.06.536.05.446h1.101c2.87 0 5.204 2.37 5.204 5.295s-2.333 5.296-5.204 5.296h-6.062c-.634 0-1.094.467-1.094 1.103 0 .637.46 1.104 1.094 1.104h6.12z" />
+    <path d="M23.196 18.92l-4.828-5.258-.366-.4-.368.398-4.828 5.196a1.13 1.13 0 0 0 0 1.546c.428.46 1.11.46 1.537 0l3.45-3.71-.868-.34v15.03c0 .64.445 1.118 1.075 1.118.63 0 1.075-.48 1.075-1.12V16.35l-.867.34 3.45 3.712a1 1 0 0 0 .767.345 1 1 0 0 0 .77-.345c.416-.33.416-1.036 0-1.485v.003z" />
+  </svg>
 }
 
 function dashboardBgIcon () {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="48" height="69" viewBox="0 0 48 69">
-    <path d="M.5 1.5h5zM10.5 1.5h5zM20.5 1.5h5zM30.504 1.5h5zM45.5 11.5v5zM45.5 21.5v5zM45.5 31.5v5zM45.5 41.502v5zM45.5 51.502v5zM45.5 61.5v5zM45.5 66.502h-4.998zM35.503 66.502h-5zM25.5 66.502h-5zM15.5 66.502h-5zM5.5 66.502h-5zM.5 66.502v-5zM.5 56.502v-5zM.5 46.503V41.5zM.5 36.5v-5zM.5 26.5v-5zM.5 16.5v-5zM.5 6.5V1.498zM44.807 11H36V2.195z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="48" height="69" viewBox="0 0 48 69">
+    <path d="M.5 1.5h5zM10.5 1.5h5zM20.5 1.5h5zM30.504 1.5h5zM45.5 11.5v5zM45.5 21.5v5zM45.5 31.5v5zM45.5 41.502v5zM45.5 51.502v5zM45.5 61.5v5zM45.5 66.502h-4.998zM35.503 66.502h-5zM25.5 66.502h-5zM15.5 66.502h-5zM5.5 66.502h-5zM.5 66.502v-5zM.5 56.502v-5zM.5 46.503V41.5zM.5 36.5v-5zM.5 26.5v-5zM.5 16.5v-5zM.5 6.5V1.498zM44.807 11H36V2.195z" />
+  </svg>
 }
 
 module.exports = {

+ 55 - 29
src/plugins/Dashboard/index.js

@@ -1,10 +1,10 @@
 const Plugin = require('../../core/Plugin')
 const Translator = require('../../core/Translator')
 const dragDrop = require('drag-drop')
-const Dashboard = require('./Dashboard')
+const DashboardUI = require('./Dashboard')
 const StatusBar = require('../StatusBar')
 const Informer = require('../Informer')
-const { findAllDOMElements } = require('../../core/Utils')
+const { findAllDOMElements, toArray } = require('../../core/Utils')
 const prettyBytes = require('prettier-bytes')
 const { defaultTabIcon } = require('./icons')
 
@@ -25,7 +25,7 @@ const FOCUSABLE_ELEMENTS = [
 /**
  * Dashboard UI with previews, metadata editing, tabs for various services and more
  */
-module.exports = class DashboardUI extends Plugin {
+module.exports = class Dashboard extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'Dashboard'
@@ -70,7 +70,7 @@ module.exports = class DashboardUI extends Plugin {
       width: 750,
       height: 550,
       semiTransparent: false,
-      defaultTabIcon: defaultTabIcon(),
+      defaultTabIcon: defaultTabIcon,
       showProgressDetails: false,
       hideUploadButton: false,
       note: null,
@@ -105,9 +105,8 @@ module.exports = class DashboardUI extends Plugin {
     this.handleClickOutside = this.handleClickOutside.bind(this)
     this.handleFileCard = this.handleFileCard.bind(this)
     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.handlePaste = this.handlePaste.bind(this)
+    this.handleInputChange = this.handleInputChange.bind(this)
     this.updateDashboardElWidth = this.updateDashboardElWidth.bind(this)
     this.render = this.render.bind(this)
     this.install = this.install.bind(this)
@@ -129,8 +128,7 @@ module.exports = class DashboardUI extends Plugin {
     const target = {
       id: callerPluginId,
       name: callerPluginName,
-      type: callerPluginType,
-      isHidden: true
+      type: callerPluginType
     }
 
     const state = this.getPluginState()
@@ -207,12 +205,15 @@ module.exports = class DashboardUI extends Plugin {
 
     // add class to body that sets position fixed, move everything back
     // to scroll position
-    document.body.classList.add('is-UppyDashboard-open')
+    document.body.classList.add('uppy-Dashboard-isOpen')
     document.body.style.top = `-${this.savedDocumentScrollPosition}px`
 
+    this.updateDashboardElWidth()
+    this.setFocusToFirstNode()
+
     // timeout is needed because yo-yo/morphdom/nanoraf; not needed without nanoraf
-    setTimeout(this.setFocusToFirstNode, 100)
-    setTimeout(this.updateDashboardElWidth, 100)
+    // setTimeout(this.setFocusToFirstNode, 100)
+    // setTimeout(this.updateDashboardElWidth, 100)
   }
 
   closeModal () {
@@ -220,7 +221,7 @@ module.exports = class DashboardUI extends Plugin {
       isHidden: true
     })
 
-    document.body.classList.remove('is-UppyDashboard-open')
+    document.body.classList.remove('uppy-Dashboard-isOpen')
 
     window.scrollTo(0, this.savedDocumentScrollPosition)
   }
@@ -240,6 +241,41 @@ module.exports = class DashboardUI extends Plugin {
     if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
   }
 
+  handlePaste (ev) {
+    const files = toArray(ev.clipboardData.items)
+    files.forEach((file) => {
+      if (file.kind !== 'file') return
+
+      const blob = file.getAsFile()
+      if (!blob) {
+        this.uppy.log('[Dashboard] File pasted, but the file blob is empty')
+        this.uppy.info('Error pasting file', 'error')
+        return
+      }
+      this.uppy.log('[Dashboard] File pasted')
+      this.uppy.addFile({
+        source: this.id,
+        name: file.name,
+        type: file.type,
+        data: blob
+      })
+    })
+  }
+
+  handleInputChange (ev) {
+    ev.preventDefault()
+    const files = toArray(ev.target.files)
+
+    files.forEach((file) => {
+      this.uppy.addFile({
+        source: this.id,
+        name: file.name,
+        type: file.type,
+        data: file
+      })
+    })
+  }
+
   initEvents () {
     // Modal open button
     const showModalTrigger = findAllDOMElements(this.opts.trigger)
@@ -283,7 +319,7 @@ module.exports = class DashboardUI extends Plugin {
   }
 
   updateDashboardElWidth () {
-    const dashboardEl = this.el.querySelector('.UppyDashboard-inner')
+    const dashboardEl = this.el.querySelector('.uppy-Dashboard-inner')
     this.uppy.log(`Dashboard width: ${dashboardEl.offsetWidth}`)
 
     this.setPluginState({
@@ -310,18 +346,6 @@ module.exports = class DashboardUI extends Plugin {
     })
   }
 
-  cancelAll () {
-    this.uppy.emit('cancel-all')
-  }
-
-  pauseAll () {
-    this.uppy.emit('pause-all')
-  }
-
-  resumeAll () {
-    this.uppy.emit('resume-all')
-  }
-
   render (state) {
     const pluginState = this.getPluginState()
     const files = state.files
@@ -395,7 +419,7 @@ module.exports = class DashboardUI extends Plugin {
       this.uppy.emit('dashboard:file-card')
     }
 
-    return Dashboard({
+    return DashboardUI({
       state: state,
       modal: pluginState,
       newFiles: newFiles,
@@ -411,6 +435,8 @@ module.exports = class DashboardUI extends Plugin {
       id: this.id,
       closeModal: this.requestCloseModal,
       handleClickOutside: this.handleClickOutside,
+      handleInputChange: this.handleInputChange,
+      handlePaste: this.handlePaste,
       showProgressDetails: this.opts.showProgressDetails,
       inline: this.opts.inline,
       semiTransparent: this.opts.semiTransparent,
@@ -418,8 +444,8 @@ module.exports = class DashboardUI extends Plugin {
       hideAllPanels: this.hideAllPanels,
       log: this.uppy.log,
       i18n: this.i18n,
-      pauseAll: this.pauseAll,
-      resumeAll: this.resumeAll,
+      // pauseAll: this.pauseAll,
+      // resumeAll: this.resumeAll,
       addFile: this.uppy.addFile,
       removeFile: this.uppy.removeFile,
       info: this.uppy.info,

+ 40 - 37
src/plugins/DragDrop/index.js

@@ -2,7 +2,7 @@ const Plugin = require('../../core/Plugin')
 const Translator = require('../../core/Translator')
 const { toArray } = require('../../core/Utils')
 const dragDrop = require('drag-drop')
-const html = require('yo-yo')
+const { h } = require('preact')
 
 /**
  * Drag & Drop plugin
@@ -14,13 +14,6 @@ module.exports = class DragDrop extends Plugin {
     this.type = 'acquirer'
     this.id = this.opts.id || 'DragDrop'
     this.title = 'Drag & Drop'
-    this.icon = html`
-      <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 16 16">
-        <path d="M15.982 2.97c0-.02 0-.02-.018-.037 0-.017-.017-.035-.035-.053 0 0 0-.018-.02-.018-.017-.018-.034-.053-.052-.07L13.19.123c-.017-.017-.034-.035-.07-.053h-.018c-.018-.017-.035-.017-.053-.034h-.02c-.017 0-.034-.018-.052-.018h-6.31a.415.415 0 0 0-.446.426V11.11c0 .25.196.446.445.446h8.89A.44.44 0 0 0 16 11.11V3.023c-.018-.018-.018-.035-.018-.053zm-2.65-1.46l1.157 1.157h-1.157V1.51zm1.78 9.157h-8V.89h5.332v2.22c0 .25.196.446.445.446h2.22v7.11z"/>
-        <path d="M9.778 12.89H4V2.666a.44.44 0 0 0-.444-.445.44.44 0 0 0-.445.445v10.666c0 .25.197.445.446.445h6.222a.44.44 0 0 0 .444-.445.44.44 0 0 0-.444-.444z"/>
-        <path d="M.444 16h6.223a.44.44 0 0 0 .444-.444.44.44 0 0 0-.443-.445H.89V4.89a.44.44 0 0 0-.446-.446A.44.44 0 0 0 0 4.89v10.666c0 .248.196.444.444.444z"/>
-      </svg>
-    `
 
     const defaultLocale = {
       strings: {
@@ -42,7 +35,7 @@ module.exports = class DragDrop extends Plugin {
     // Merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOpts, opts)
 
-        // Check for browser dragDrop support
+    // Check for browser dragDrop support
     this.isDragDropSupported = this.checkDragDropSupport()
 
     this.locale = Object.assign({}, defaultLocale, this.opts.locale)
@@ -54,16 +47,16 @@ module.exports = class DragDrop extends Plugin {
 
     // Bind `this` to class methods
     this.handleDrop = this.handleDrop.bind(this)
-    this.onBrowseClick = this.onBrowseClick.bind(this)
-    this.onInputChange = this.onInputChange.bind(this)
+    this.handleBrowseClick = this.handleBrowseClick.bind(this)
+    this.handleInputChange = this.handleInputChange.bind(this)
     this.checkDragDropSupport = this.checkDragDropSupport.bind(this)
     this.render = this.render.bind(this)
   }
 
-/**
- * Checks if the browser supports Drag & Drop (not supported on mobile devices, for example).
- * @return {Boolean} true if supported, false otherwise
- */
+  /**
+   * Checks if the browser supports Drag & Drop (not supported on mobile devices, for example).
+   * @return {Boolean}
+   */
   checkDragDropSupport () {
     const div = document.createElement('div')
 
@@ -95,7 +88,7 @@ module.exports = class DragDrop extends Plugin {
     })
   }
 
-  onInputChange (ev) {
+  handleInputChange (ev) {
     this.uppy.log('[DragDrop] Files selected through input')
 
     const files = toArray(ev.target.files)
@@ -110,33 +103,38 @@ module.exports = class DragDrop extends Plugin {
     })
   }
 
-  onBrowseClick (ev) {
-    const input = this.el.querySelector('.uppy-DragDrop-input')
-    input.click()
+  handleBrowseClick (ev) {
+    this.input.click()
   }
 
   render (state) {
-    return html`
-      <div class="Uppy UppyTheme--default uppy-DragDrop-container ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}"
-           style="width: ${this.opts.width}; height: ${this.opts.height};">
-        <form class="uppy-DragDrop-inner" onsubmit=${(ev) => ev.preventDefault()}>
-          <svg class="UppyIcon uppy-DragDrop-arrow" aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-            <path d="M11 10V0H5v10H2l6 6 6-6h-3zm0 0" fill-rule="evenodd"/>
+    const DragDropClass = `uppy uppy-DragDrop-container ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}`
+    const DragDropStyle = {
+      width: this.opts.width,
+      height: this.opts.height
+    }
+    return (
+      <div class={DragDropClass} style={DragDropStyle} onclick={this.handleBrowseClick}>
+        <div class="uppy-DragDrop-inner">
+          <svg aria-hidden="true" class="UppyIcon uppy-DragDrop-arrow" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+            <path d="M11 10V0H5v10H2l6 6 6-6h-3zm0 0" fill-rule="evenodd" />
           </svg>
-          <input class="uppy-DragDrop-input uppy-DragDrop-focus"
-                 type="file"
-                 name="files[]"
-                 multiple="true"
-                 value=""
-                 onchange=${this.onInputChange} />
-          <label class="uppy-DragDrop-label" onclick=${this.onBrowseClick}>
-            ${this.i18n('dropHereOr')}
-            <span class="uppy-DragDrop-dragText">${this.i18n('browse')}</span>
+          <input class="uppy-DragDrop-input"
+            type="file"
+            name="files[]"
+            multiple="true"
+            value=""
+            ref={(input) => {
+              this.input = input
+            }}
+            onchange={this.handleInputChange} />
+          <label class="uppy-DragDrop-label" onclick={this.handleBrowseClick}>
+            {this.i18n('dropHereOr')} <span class="uppy-DragDrop-dragText">{this.i18n('browse')}</span>
           </label>
-          <span class="uppy-DragDrop-note">${this.opts.note}</span>
-        </form>
+          <span class="uppy-DragDrop-note">{this.opts.note}</span>
+        </div>
       </div>
-    `
+    )
   }
 
   install () {
@@ -149,4 +147,9 @@ module.exports = class DragDrop extends Plugin {
       this.uppy.log(files)
     })
   }
+
+  uninstall () {
+    this.unmount()
+    this.removeDragDropListener()
+  }
 }

+ 11 - 9
src/plugins/Dropbox/icons.js

@@ -1,12 +1,14 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 
 module.exports = {
-  folder: () =>
-    html`<svg aria-hidden="true" class="UppyIcon" style="width:16px;margin-right:3px" viewBox="0 0 276.157 276.157">
-      <path d="M273.08 101.378c-3.3-4.65-8.86-7.32-15.254-7.32h-24.34V67.59c0-10.2-8.3-18.5-18.5-18.5h-85.322c-3.63 0-9.295-2.875-11.436-5.805l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.73c-9.292 0-18.638 6.608-21.737 15.372l-2.033 5.752c-.958 2.71-4.72 5.37-7.596 5.37H18.5C8.3 49.09 0 57.39 0 67.59v167.07c0 .886.16 1.73.443 2.52.152 3.306 1.18 6.424 3.053 9.064 3.3 4.652 8.86 7.32 15.255 7.32h188.487c11.395 0 23.27-8.425 27.035-19.18l40.677-116.188c2.11-6.035 1.43-12.164-1.87-16.816zM18.5 64.088h8.864c9.295 0 18.64-6.607 21.738-15.37l2.032-5.75c.96-2.712 4.722-5.373 7.597-5.373h29.565c3.63 0 9.295 2.876 11.437 5.806l6.386 8.735c4.982 6.815 15.104 11.954 23.546 11.954h85.322c1.898 0 3.5 1.602 3.5 3.5v26.47H69.34c-11.395 0-23.27 8.423-27.035 19.178L15 191.23V67.59c0-1.898 1.603-3.5 3.5-3.5zm242.29 49.15l-40.676 116.188c-1.674 4.78-7.812 9.135-12.877 9.135H18.75c-1.447 0-2.576-.372-3.02-.997-.442-.625-.422-1.814.057-3.18l40.677-116.19c1.674-4.78 7.812-9.134 12.877-9.134h188.487c1.448 0 2.577.372 3.02.997.443.625.423 1.814-.056 3.18z"/>
-  </svg>`,
-  file: () =>
-    html`<svg aria-hidden="true" class="UppyIcon" width="11" height="14.5" viewBox="0 0 44 58">
-    <path d="M27.437.517a1 1 0 0 0-.094.03H4.25C2.037.548.217 2.368.217 4.58v48.405c0 2.212 1.82 4.03 4.03 4.03H39.03c2.21 0 4.03-1.818 4.03-4.03V15.61a1 1 0 0 0-.03-.28 1 1 0 0 0 0-.093 1 1 0 0 0-.03-.032 1 1 0 0 0 0-.03 1 1 0 0 0-.032-.063 1 1 0 0 0-.03-.063 1 1 0 0 0-.032 0 1 1 0 0 0-.03-.063 1 1 0 0 0-.032-.03 1 1 0 0 0-.03-.063 1 1 0 0 0-.063-.062l-14.593-14a1 1 0 0 0-.062-.062A1 1 0 0 0 28 .708a1 1 0 0 0-.374-.157 1 1 0 0 0-.156 0 1 1 0 0 0-.03-.03l-.003-.003zM4.25 2.547h22.218v9.97c0 2.21 1.82 4.03 4.03 4.03h10.564v36.438a2.02 2.02 0 0 1-2.032 2.032H4.25c-1.13 0-2.032-.9-2.032-2.032V4.58c0-1.13.902-2.032 2.03-2.032zm24.218 1.345l10.375 9.937.75.718H30.5c-1.13 0-2.032-.9-2.032-2.03V3.89z" />
-  </svg>`
+  folder: () => {
+    return <svg aria-hidden="true" class="UppyIcon" style="width:16px;margin-right:3px" viewBox="0 0 276.157 276.157">
+      <path d="M273.08 101.378c-3.3-4.65-8.86-7.32-15.254-7.32h-24.34V67.59c0-10.2-8.3-18.5-18.5-18.5h-85.322c-3.63 0-9.295-2.875-11.436-5.805l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.73c-9.292 0-18.638 6.608-21.737 15.372l-2.033 5.752c-.958 2.71-4.72 5.37-7.596 5.37H18.5C8.3 49.09 0 57.39 0 67.59v167.07c0 .886.16 1.73.443 2.52.152 3.306 1.18 6.424 3.053 9.064 3.3 4.652 8.86 7.32 15.255 7.32h188.487c11.395 0 23.27-8.425 27.035-19.18l40.677-116.188c2.11-6.035 1.43-12.164-1.87-16.816zM18.5 64.088h8.864c9.295 0 18.64-6.607 21.738-15.37l2.032-5.75c.96-2.712 4.722-5.373 7.597-5.373h29.565c3.63 0 9.295 2.876 11.437 5.806l6.386 8.735c4.982 6.815 15.104 11.954 23.546 11.954h85.322c1.898 0 3.5 1.602 3.5 3.5v26.47H69.34c-11.395 0-23.27 8.423-27.035 19.178L15 191.23V67.59c0-1.898 1.603-3.5 3.5-3.5zm242.29 49.15l-40.676 116.188c-1.674 4.78-7.812 9.135-12.877 9.135H18.75c-1.447 0-2.576-.372-3.02-.997-.442-.625-.422-1.814.057-3.18l40.677-116.19c1.674-4.78 7.812-9.134 12.877-9.134h188.487c1.448 0 2.577.372 3.02.997.443.625.423 1.814-.056 3.18z" />
+    </svg>
+  },
+  file: () => {
+    return <svg aria-hidden="true" class="UppyIcon" width="11" height="14.5" viewBox="0 0 44 58">
+      <path d="M27.437.517a1 1 0 0 0-.094.03H4.25C2.037.548.217 2.368.217 4.58v48.405c0 2.212 1.82 4.03 4.03 4.03H39.03c2.21 0 4.03-1.818 4.03-4.03V15.61a1 1 0 0 0-.03-.28 1 1 0 0 0 0-.093 1 1 0 0 0-.03-.032 1 1 0 0 0 0-.03 1 1 0 0 0-.032-.063 1 1 0 0 0-.03-.063 1 1 0 0 0-.032 0 1 1 0 0 0-.03-.063 1 1 0 0 0-.032-.03 1 1 0 0 0-.03-.063 1 1 0 0 0-.063-.062l-14.593-14a1 1 0 0 0-.062-.062A1 1 0 0 0 28 .708a1 1 0 0 0-.374-.157 1 1 0 0 0-.156 0 1 1 0 0 0-.03-.03l-.003-.003zM4.25 2.547h22.218v9.97c0 2.21 1.82 4.03 4.03 4.03h10.564v36.438a2.02 2.02 0 0 1-2.032 2.032H4.25c-1.13 0-2.032-.9-2.032-2.032V4.58c0-1.13.902-2.032 2.03-2.032zm24.218 1.345l10.375 9.937.75.718H30.5c-1.13 0-2.032-.9-2.032-2.03V3.89z" />
+    </svg>
+  }
 }

+ 6 - 7
src/plugins/Dropbox/index.js

@@ -1,8 +1,8 @@
-const html = require('yo-yo')
 const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
 const icons = require('./icons')
+const { h } = require('preact')
 
 module.exports = class Dropbox extends Plugin {
   constructor (uppy, opts) {
@@ -10,13 +10,13 @@ module.exports = class Dropbox extends Plugin {
     this.type = 'acquirer'
     this.id = this.opts.id || 'Dropbox'
     this.title = 'Dropbox'
-    this.icon = () => html`
+    this.icon = () => (
       <svg class="UppyIcon" width="128" height="118" viewBox="0 0 128 118">
-        <path d="M38.145.777L1.108 24.96l25.608 20.507 37.344-23.06z"/>
-        <path d="M1.108 65.975l37.037 24.183L64.06 68.525l-37.343-23.06zM64.06 68.525l25.917 21.633 37.036-24.183-25.61-20.51z"/>
-        <path d="M127.014 24.96L89.977.776 64.06 22.407l37.345 23.06zM64.136 73.18l-25.99 21.567-11.122-7.262v8.142l37.112 22.256 37.114-22.256v-8.142l-11.12 7.262z"/>
+        <path d="M38.145.777L1.108 24.96l25.608 20.507 37.344-23.06z" />
+        <path d="M1.108 65.975l37.037 24.183L64.06 68.525l-37.343-23.06zM64.06 68.525l25.917 21.633 37.036-24.183-25.61-20.51z" />
+        <path d="M127.014 24.96L89.977.776 64.06 22.407l37.345 23.06zM64.136 73.18l-25.99 21.567-11.122-7.262v8.142l37.112 22.256 37.114-22.256v-8.142l-11.12 7.262z" />
       </svg>
-    `
+    )
 
     // writing out the key explicitly for readability the key used to store
     // the provider instance must be equal to this.id.
@@ -28,7 +28,6 @@ module.exports = class Dropbox extends Plugin {
     this.files = []
 
     this.onAuth = this.onAuth.bind(this)
-
     this.render = this.render.bind(this)
 
     // set default options

+ 15 - 14
src/plugins/Dummy.js

@@ -1,6 +1,5 @@
 const Plugin = require('../core/Plugin')
-const html = require('yo-yo')
-// const yo = require('yo-yo')
+const { h } = require('preact')
 
 /**
  * Dummy
@@ -19,7 +18,7 @@ module.exports = class Dummy extends Plugin {
     // merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
-    this.strange = html`<h1>this is strange 1</h1>`
+    this.strange = <h1>this is strange 1</h1>
     this.render = this.render.bind(this)
     this.install = this.install.bind(this)
   }
@@ -40,21 +39,21 @@ module.exports = class Dummy extends Plugin {
   }
 
   render (state) {
-    const bla = html`<h2>this is strange 2</h2>`
-    return html`
+    const bla = <h2>this is strange 2</h2>
+    return (
       <div class="wow-this-works">
-        <input class="UppyDummy-firstInput" type="text" value="hello" onload=${(el) => {
-          el.focus()
-        }} />
-        ${this.strange}
-        ${bla}
-        ${state.dummy.text}
+        <input class="UppyDummy-firstInput" type="text" value="hello" />
+        {this.strange}
+        {bla}
+        {state.dummy.text}
       </div>
-    `
+    )
   }
 
   install () {
-    this.uppy.setState({dummy: {text: '123'}})
+    this.uppy.setState({
+      dummy: { text: '123' }
+    })
 
     const target = this.opts.target
     if (target) {
@@ -62,7 +61,9 @@ module.exports = class Dummy extends Plugin {
     }
 
     setTimeout(() => {
-      this.uppy.setState({dummy: {text: '!!!'}})
+      this.uppy.setState({
+        dummy: {text: '!!!'}
+      })
     }, 2000)
   }
 }

+ 34 - 24
src/plugins/FileInput.js

@@ -1,7 +1,7 @@
 const Plugin = require('../core/Plugin')
 const { toArray } = require('../core/Utils')
 const Translator = require('../core/Translator')
-const html = require('yo-yo')
+const { h } = require('preact')
 
 module.exports = class FileInput extends Plugin {
   constructor (uppy, opts) {
@@ -12,16 +12,16 @@ module.exports = class FileInput extends Plugin {
 
     const defaultLocale = {
       strings: {
-        selectToUpload: 'Select to upload'
+        chooseFiles: 'Choose files'
       }
     }
 
     // Default options
     const defaultOptions = {
-      target: '.UppyForm',
+      target: null,
       getMetaFromForm: true,
-      replaceTargetContent: true,
-      multipleFiles: true,
+      replaceTargetContent: false,
+      allowMultipleFiles: true,
       pretty: true,
       locale: defaultLocale,
       inputName: 'files[]'
@@ -38,10 +38,12 @@ module.exports = class FileInput extends Plugin {
     this.i18n = this.translator.translate.bind(this.translator)
 
     this.render = this.render.bind(this)
+    this.handleInputChange = this.handleInputChange.bind(this)
+    this.handleClick = this.handleClick.bind(this)
   }
 
   handleInputChange (ev) {
-    this.uppy.log('All right, something selected through input...')
+    this.uppy.log('[FileInput] Something selected through input...')
 
     const files = toArray(ev.target.files)
 
@@ -55,26 +57,34 @@ module.exports = class FileInput extends Plugin {
     })
   }
 
-  render (state) {
-    const hiddenInputStyle = 'width: 0.1px; height: 0.1px; opacity: 0; overflow: hidden; position: absolute; z-index: -1;'
+  handleClick (ev) {
+    this.input.click()
+  }
 
-    const input = html`<input class="uppy-FileInput-input"
-           style="${this.opts.pretty ? hiddenInputStyle : ''}"
-           type="file"
-           name=${this.opts.inputName}
-           onchange=${this.handleInputChange.bind(this)}
-           multiple="${this.opts.multipleFiles ? 'true' : 'false'}"
-           value="">`
+  render (state) {
+    const hiddenInputStyle = {
+      width: '0.1px',
+      height: '0.1px',
+      opacity: 0,
+      overflow: 'hidden',
+      position: 'absolute',
+      zIndex: -1
+    }
 
-    return html`<form class="Uppy uppy-FileInput-form">
-      ${input}
-      ${this.opts.pretty
-        ? html`<button class="uppy-FileInput-btn" type="button" onclick=${() => input.click()}>
-          ${this.i18n('selectToUpload')}
-        </button>`
-       : null
-     }
-    </form>`
+    return <div class="uppy uppy-FileInput-container">
+      <input class="uppy-FileInput-input"
+        style={this.opts.pretty && hiddenInputStyle}
+        type="file"
+        name={this.opts.inputName}
+        onchange={this.handleInputChange}
+        multiple={this.opts.allowMultipleFiles}
+        ref={(input) => { this.input = input }} />
+      {this.opts.pretty &&
+        <button class="uppy-FileInput-btn" type="button" onclick={this.handleClick}>
+          {this.i18n('chooseFiles')}
+        </button>
+      }
+    </div>
   }
 
   install () {

+ 4 - 8
src/plugins/GoogleDrive/index.js

@@ -1,7 +1,7 @@
-const html = require('yo-yo')
 const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
+const { h } = require('preact')
 
 module.exports = class GoogleDrive extends Plugin {
   constructor (uppy, opts) {
@@ -9,14 +9,11 @@ module.exports = class GoogleDrive extends Plugin {
     this.type = 'acquirer'
     this.id = this.opts.id || 'GoogleDrive'
     this.title = 'Google Drive'
-    this.icon = () => html`
+    this.icon = () =>
       <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 16 16">
-        <path d="M2.955 14.93l2.667-4.62H16l-2.667 4.62H2.955zm2.378-4.62l-2.666 4.62L0 10.31l5.19-8.99 2.666 4.62-2.523 4.37zm10.523-.25h-5.333l-5.19-8.99h5.334l5.19 8.99z"/>
+        <path d="M2.955 14.93l2.667-4.62H16l-2.667 4.62H2.955zm2.378-4.62l-2.666 4.62L0 10.31l5.19-8.99 2.666 4.62-2.523 4.37zm10.523-.25h-5.333l-5.19-8.99h5.334l5.19 8.99z" />
       </svg>
-    `
 
-    // writing out the key explicitly for readability the key used to store
-    // the provider instance must be equal to this.id.
     this[this.id] = new Provider(uppy, {
       host: this.opts.host,
       provider: 'drive',
@@ -26,7 +23,6 @@ module.exports = class GoogleDrive extends Plugin {
     this.files = []
 
     this.onAuth = this.onAuth.bind(this)
-    // Visual
     this.render = this.render.bind(this)
 
     // set default options
@@ -76,7 +72,7 @@ module.exports = class GoogleDrive extends Plugin {
   }
 
   getItemIcon (item) {
-    return html`<img src=${item.iconLink}/>`
+    return <img src={item.iconLink} />
   }
 
   getItemSubList (item) {

+ 21 - 15
src/plugins/Informer.js

@@ -1,5 +1,6 @@
 const Plugin = require('../core/Plugin')
-const html = require('yo-yo')
+
+const { h } = require('preact')
 
 /**
  * Informer
@@ -14,7 +15,6 @@ module.exports = class Informer extends Plugin {
     this.type = 'progressindicator'
     this.id = this.opts.id || 'Informer'
     this.title = 'Informer'
-    // this.timeoutID = undefined
 
     // set default options
     const defaultOptions = {
@@ -45,20 +45,26 @@ module.exports = class Informer extends Plugin {
   }
 
   render (state) {
-    const {isHidden, type, message, details} = state.info
-    const style = `background-color: ${this.opts.typeColors[type].bg}; color: ${this.opts.typeColors[type].text};`
+    const { isHidden, type, message, details } = state.info
+    const style = {
+      backgroundColor: this.opts.typeColors[type].bg,
+      color: this.opts.typeColors[type].text
+    }
 
-    return html`<div class="Uppy UppyInformer" 
-                     style="${style}" 
-                     aria-hidden="${isHidden}" >
-      <p role="alert">
-        ${message} 
-        ${details ? html`<span style="color: ${this.opts.typeColors[type].bg}" 
-                               data-balloon="${details}" 
-                               data-balloon-pos="up" 
-                               data-balloon-length="large">?</span>` : null}
-      </p>
-    </div>`
+    return (
+      <div class="uppy uppy-Informer"
+        style={style}
+        aria-hidden={isHidden}>
+        <p role="alert">
+          {message}
+          {details && <span style={{ color: this.opts.typeColors[type].bg }}
+            data-balloon={details}
+            data-balloon-pos="up"
+            data-balloon-length="large">?</span>
+          }
+        </p>
+      </div>
+    )
   }
 
   install () {

+ 7 - 10
src/plugins/Instagram/index.js

@@ -1,7 +1,7 @@
-const html = require('yo-yo')
 const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
+const { h } = require('preact')
 
 module.exports = class Instagram extends Plugin {
   constructor (uppy, opts) {
@@ -9,16 +9,14 @@ module.exports = class Instagram extends Plugin {
     this.type = 'acquirer'
     this.id = this.opts.id || 'Instagram'
     this.title = 'Instagram'
-    this.icon = () => html`
+    this.icon = () => (
       <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 512 512">
-        <path d="M256,49.471c67.266,0,75.233.257,101.8,1.469,24.562,1.121,37.9,5.224,46.778,8.674a78.052,78.052,0,0,1,28.966,18.845,78.052,78.052,0,0,1,18.845,28.966c3.45,8.877,7.554,22.216,8.674,46.778,1.212,26.565,1.469,34.532,1.469,101.8s-0.257,75.233-1.469,101.8c-1.121,24.562-5.225,37.9-8.674,46.778a83.427,83.427,0,0,1-47.811,47.811c-8.877,3.45-22.216,7.554-46.778,8.674-26.56,1.212-34.527,1.469-101.8,1.469s-75.237-.257-101.8-1.469c-24.562-1.121-37.9-5.225-46.778-8.674a78.051,78.051,0,0,1-28.966-18.845,78.053,78.053,0,0,1-18.845-28.966c-3.45-8.877-7.554-22.216-8.674-46.778-1.212-26.564-1.469-34.532-1.469-101.8s0.257-75.233,1.469-101.8c1.121-24.562,5.224-37.9,8.674-46.778A78.052,78.052,0,0,1,78.458,78.458a78.053,78.053,0,0,1,28.966-18.845c8.877-3.45,22.216-7.554,46.778-8.674,26.565-1.212,34.532-1.469,101.8-1.469m0-45.391c-68.418,0-77,.29-103.866,1.516-26.815,1.224-45.127,5.482-61.151,11.71a123.488,123.488,0,0,0-44.62,29.057A123.488,123.488,0,0,0,17.3,90.982C11.077,107.007,6.819,125.319,5.6,152.134,4.369,179,4.079,187.582,4.079,256S4.369,333,5.6,359.866c1.224,26.815,5.482,45.127,11.71,61.151a123.489,123.489,0,0,0,29.057,44.62,123.486,123.486,0,0,0,44.62,29.057c16.025,6.228,34.337,10.486,61.151,11.71,26.87,1.226,35.449,1.516,103.866,1.516s77-.29,103.866-1.516c26.815-1.224,45.127-5.482,61.151-11.71a128.817,128.817,0,0,0,73.677-73.677c6.228-16.025,10.486-34.337,11.71-61.151,1.226-26.87,1.516-35.449,1.516-103.866s-0.29-77-1.516-103.866c-1.224-26.815-5.482-45.127-11.71-61.151a123.486,123.486,0,0,0-29.057-44.62A123.487,123.487,0,0,0,421.018,17.3C404.993,11.077,386.681,6.819,359.866,5.6,333,4.369,324.418,4.079,256,4.079h0Z"/>
-        <path d="M256,126.635A129.365,129.365,0,1,0,385.365,256,129.365,129.365,0,0,0,256,126.635Zm0,213.338A83.973,83.973,0,1,1,339.974,256,83.974,83.974,0,0,1,256,339.973Z"/>
-        <circle cx="390.476" cy="121.524" r="30.23"/>
+        <path d="M256,49.471c67.266,0,75.233.257,101.8,1.469,24.562,1.121,37.9,5.224,46.778,8.674a78.052,78.052,0,0,1,28.966,18.845,78.052,78.052,0,0,1,18.845,28.966c3.45,8.877,7.554,22.216,8.674,46.778,1.212,26.565,1.469,34.532,1.469,101.8s-0.257,75.233-1.469,101.8c-1.121,24.562-5.225,37.9-8.674,46.778a83.427,83.427,0,0,1-47.811,47.811c-8.877,3.45-22.216,7.554-46.778,8.674-26.56,1.212-34.527,1.469-101.8,1.469s-75.237-.257-101.8-1.469c-24.562-1.121-37.9-5.225-46.778-8.674a78.051,78.051,0,0,1-28.966-18.845,78.053,78.053,0,0,1-18.845-28.966c-3.45-8.877-7.554-22.216-8.674-46.778-1.212-26.564-1.469-34.532-1.469-101.8s0.257-75.233,1.469-101.8c1.121-24.562,5.224-37.9,8.674-46.778A78.052,78.052,0,0,1,78.458,78.458a78.053,78.053,0,0,1,28.966-18.845c8.877-3.45,22.216-7.554,46.778-8.674,26.565-1.212,34.532-1.469,101.8-1.469m0-45.391c-68.418,0-77,.29-103.866,1.516-26.815,1.224-45.127,5.482-61.151,11.71a123.488,123.488,0,0,0-44.62,29.057A123.488,123.488,0,0,0,17.3,90.982C11.077,107.007,6.819,125.319,5.6,152.134,4.369,179,4.079,187.582,4.079,256S4.369,333,5.6,359.866c1.224,26.815,5.482,45.127,11.71,61.151a123.489,123.489,0,0,0,29.057,44.62,123.486,123.486,0,0,0,44.62,29.057c16.025,6.228,34.337,10.486,61.151,11.71,26.87,1.226,35.449,1.516,103.866,1.516s77-.29,103.866-1.516c26.815-1.224,45.127-5.482,61.151-11.71a128.817,128.817,0,0,0,73.677-73.677c6.228-16.025,10.486-34.337,11.71-61.151,1.226-26.87,1.516-35.449,1.516-103.866s-0.29-77-1.516-103.866c-1.224-26.815-5.482-45.127-11.71-61.151a123.486,123.486,0,0,0-29.057-44.62A123.487,123.487,0,0,0,421.018,17.3C404.993,11.077,386.681,6.819,359.866,5.6,333,4.369,324.418,4.079,256,4.079h0Z" />
+        <path d="M256,126.635A129.365,129.365,0,1,0,385.365,256,129.365,129.365,0,0,0,256,126.635Zm0,213.338A83.973,83.973,0,1,1,339.974,256,83.974,83.974,0,0,1,256,339.973Z" />
+        <circle cx="390.476" cy="121.524" r="30.23" />
       </svg>
-    `
+    )
 
-    // writing out the key explicitly for readability the key used to store
-    // the provider instance must be equal to this.id.
     this[this.id] = new Provider(uppy, {
       host: this.opts.host,
       provider: 'instagram',
@@ -28,7 +26,6 @@ module.exports = class Instagram extends Plugin {
     this.files = []
 
     this.onAuth = this.onAuth.bind(this)
-    // Visual
     this.render = this.render.bind(this)
 
     // set default options
@@ -80,7 +77,7 @@ module.exports = class Instagram extends Plugin {
   }
 
   getItemIcon (item) {
-    return html`<img width="100" src=${item.images.thumbnail.url}/>`
+    return <img width="100" src={item.images.thumbnail.url} />
   }
 
   getItemSubList (item) {

+ 5 - 5
src/plugins/ProgressBar.js

@@ -1,5 +1,5 @@
 const Plugin = require('../core/Plugin')
-const html = require('yo-yo')
+const { h } = require('preact')
 
 /**
  * Progress bar
@@ -28,10 +28,10 @@ module.exports = class ProgressBar extends Plugin {
   render (state) {
     const progress = state.totalProgress || 0
 
-    return html`<div class="UppyProgressBar" style="${this.opts.fixed ? 'position: fixed' : 'null'}">
-      <div class="UppyProgressBar-inner" style="width: ${progress}%"></div>
-      <div class="UppyProgressBar-percentage">${progress}</div>
-    </div>`
+    return <div class="uppy uppy-ProgressBar" style={{ position: this.opts.fixed ? 'fixed' : 'initial' }}>
+      <div class="uppy-ProgressBar-inner" style={{ width: progress + '%' }} />
+      <div class="uppy-ProgressBar-percentage">{progress}</div>
+    </div>
   }
 
   install () {

+ 28 - 18
src/plugins/Provider/view/AuthView.js

@@ -1,21 +1,31 @@
-const html = require('yo-yo')
-const onload = require('on-load')
 const LoaderView = require('./Loader')
+const { h, Component } = require('preact')
 
-module.exports = (props) => {
-  const demoLink = props.demo ? html`<button class="UppyProvider-authBtnDemo" onclick=${props.handleDemoAuth}>Proceed with Demo Account</button>` : null
-  const AuthBlock = () => html`
-    <div class="UppyProvider-auth">
-      <h1 class="UppyProvider-authTitle">Please connect your ${props.pluginName}<br> account to select files</h1>
-      <button type="button" class="UppyProvider-authBtn" onclick=${props.handleAuth}>Connect to ${props.pluginName}</button>
-      ${demoLink}
-    </div>
-  `
-  return onload(html`
-    <div style="height: 100%;">
-      ${props.checkAuthInProgress
-        ? LoaderView()
-        : AuthBlock()
-      }
-    </div>`, props.checkAuth, null, `auth${props.pluginName}`)
+class AuthView extends Component {
+  componentDidMount () {
+    this.props.checkAuth()
+  }
+
+  render () {
+    const AuthBlock = () => {
+      return <div class="uppy-Provider-auth">
+        <h1 class="uppy-Provider-authTitle">Please authenticate with <span class="uppy-Provider-authTitleName">{this.props.pluginName}</span><br /> to select files</h1>
+        <button type="button" class="uppy-Provider-authBtn" onclick={this.props.handleAuth}>Connect to {this.props.pluginName}</button>
+        {this.props.demo &&
+          <button class="uppy-Provider-authBtnDemo" onclick={this.props.handleDemoAuth}>Proceed with Demo Account</button>
+        }
+      </div>
+    }
+
+    return (
+      <div style="height: 100%;">
+        {this.props.checkAuthInProgress
+          ? LoaderView()
+          : AuthBlock()
+        }
+      </div>
+    )
+  }
 }
+
+module.exports = AuthView

+ 2 - 6
src/plugins/Provider/view/Breadcrumb.js

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

+ 5 - 5
src/plugins/Provider/view/Breadcrumbs.js

@@ -1,10 +1,10 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 const Breadcrumb = require('./Breadcrumb')
 
 module.exports = (props) => {
-  return html`
-    <ul class="UppyProvider-breadcrumbs">
-      ${
+  return (
+    <ul class="uppy-Provider-breadcrumbs">
+      {
         props.directories.map((directory, i) => {
           return Breadcrumb({
             getFolder: () => props.getFolder(directory.id),
@@ -13,5 +13,5 @@ module.exports = (props) => {
         })
       }
     </ul>
-  `
+  )
 }

+ 21 - 27
src/plugins/Provider/view/Browser.js

@@ -1,6 +1,7 @@
-const html = require('yo-yo')
 const Breadcrumbs = require('./Breadcrumbs')
+const Filter = require('./Filter')
 const Table = require('./Table')
+const { h } = require('preact')
 
 module.exports = (props) => {
   let filteredFolders = props.folders
@@ -11,36 +12,29 @@ module.exports = (props) => {
     filteredFiles = props.filterItems(props.files)
   }
 
-  return html`
-    <div class="Browser Browser-viewType--${props.viewType}">
-      <header class="Browser-header">
-        <div class="Browser-search" aria-hidden="${!props.isSearchVisible}">
-          <input type="text" class="Browser-searchInput" placeholder="Search"
-                 onkeyup=${props.filterQuery} value="${props.filterInput}"/>
-          <button type="button" class="Browser-searchClose"
-                  onclick=${props.toggleSearch}>
-            <svg class="UppyIcon" viewBox="0 0 19 19">
-              <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z"/>
-            </svg>
-          </button>
+  return (
+    <div class={`uppy uppy-ProviderBrowser uppy-ProviderBrowser-viewType--${props.viewType}`}>
+      <header class="uppy-ProviderBrowser-header">
+        <div class="uppy-ProviderBrowser-search" aria-hidden={!props.isSearchVisible}>
+          { props.isSearchVisible && <Filter {...props} /> }
         </div>
-        <div class="Browser-headerBar">
-          <button type="button" class="Browser-searchToggle"
-                  onclick=${props.toggleSearch}>
+        <div class="uppy-ProviderBrowser-headerBar">
+          <button type="button" class="uppy-ProviderBrowser-searchToggle"
+            onclick={props.toggleSearch}>
             <svg class="UppyIcon" viewBox="0 0 100 100">
-              <path d="M87.533 80.03L62.942 55.439c3.324-4.587 5.312-10.207 5.312-16.295 0-.312-.043-.611-.092-.908.05-.301.093-.605.093-.922 0-15.36-12.497-27.857-27.857-27.857-.273 0-.536.043-.799.08-.265-.037-.526-.08-.799-.08-15.361 0-27.858 12.497-27.858 27.857 0 .312.042.611.092.909a5.466 5.466 0 0 0-.093.921c0 15.36 12.496 27.858 27.857 27.858.273 0 .535-.043.8-.081.263.038.524.081.798.081 5.208 0 10.071-1.464 14.245-3.963L79.582 87.98a5.603 5.603 0 0 0 3.976 1.647 5.621 5.621 0 0 0 3.975-9.597zM39.598 55.838c-.265-.038-.526-.081-.8-.081-9.16 0-16.612-7.452-16.612-16.612 0-.312-.042-.611-.092-.908.051-.301.093-.605.093-.922 0-9.16 7.453-16.612 16.613-16.612.272 0 .534-.042.799-.079.263.037.525.079.799.079 9.16 0 16.612 7.452 16.612 16.612 0 .312.043.611.092.909-.05.301-.094.604-.094.921 0 9.16-7.452 16.612-16.612 16.612-.274 0-.536.043-.798.081z"/>
+              <path d="M87.533 80.03L62.942 55.439c3.324-4.587 5.312-10.207 5.312-16.295 0-.312-.043-.611-.092-.908.05-.301.093-.605.093-.922 0-15.36-12.497-27.857-27.857-27.857-.273 0-.536.043-.799.08-.265-.037-.526-.08-.799-.08-15.361 0-27.858 12.497-27.858 27.857 0 .312.042.611.092.909a5.466 5.466 0 0 0-.093.921c0 15.36 12.496 27.858 27.857 27.858.273 0 .535-.043.8-.081.263.038.524.081.798.081 5.208 0 10.071-1.464 14.245-3.963L79.582 87.98a5.603 5.603 0 0 0 3.976 1.647 5.621 5.621 0 0 0 3.975-9.597zM39.598 55.838c-.265-.038-.526-.081-.8-.081-9.16 0-16.612-7.452-16.612-16.612 0-.312-.042-.611-.092-.908.051-.301.093-.605.093-.922 0-9.16 7.453-16.612 16.613-16.612.272 0 .534-.042.799-.079.263.037.525.079.799.079 9.16 0 16.612 7.452 16.612 16.612 0 .312.043.611.092.909-.05.301-.094.604-.094.921 0 9.16-7.452 16.612-16.612 16.612-.274 0-.536.043-.798.081z" />
             </svg>
           </button>
-          ${Breadcrumbs({
+          {Breadcrumbs({
             getFolder: props.getFolder,
             directories: props.directories,
             title: props.title
           })}
-          <button type="button" onclick=${props.logout} class="Browser-userLogout">Log out</button>
+          <button type="button" onclick={props.logout} class="uppy-ProviderBrowser-userLogout">Log out</button>
         </div>
       </header>
-      <div class="Browser-body">
-        ${Table({
+      <div class="uppy-ProviderBrowser-body">
+        {Table({
           columns: [{
             name: 'Name',
             key: 'title'
@@ -60,15 +54,15 @@ module.exports = (props) => {
           title: props.title
         })}
       </div>
-      <button class="UppyButton--circular UppyButton--blue Browser-doneBtn"
-              type="button"
-              aria-label="Done picking files"
-              title="Done picking files"
-              onclick=${props.done}>
+      <button class="UppyButton--circular UppyButton--blue uppy-ProviderBrowser-doneBtn"
+        type="button"
+        aria-label="Done picking files"
+        title="Done picking files"
+        onclick={props.done}>
         <svg aria-hidden="true" class="UppyIcon" width="13px" height="9px" viewBox="0 0 13 9">
           <polygon points="5 7.293 1.354 3.647 0.646 4.354 5 8.707 12.354 1.354 11.646 0.647" />
         </svg>
       </button>
     </div>
-  `
+  )
 }

+ 44 - 0
src/plugins/Provider/view/Filter.js

@@ -0,0 +1,44 @@
+const { h, Component } = require('preact')
+
+module.exports = class Filter extends Component {
+  constructor (props) {
+    super(props)
+
+    this.handleKeyPress = this.handleKeyPress.bind(this)
+  }
+
+  componentDidMount () {
+    this.input.focus()
+  }
+
+  handleKeyPress (ev) {
+    if (ev.keyCode === 13) {
+      ev.stopPropagation()
+      ev.preventDefault()
+      return
+    }
+    this.props.filterQuery(ev)
+  }
+
+  render () {
+    return <div style={{ display: 'flex', width: '100%' }}>
+      <input
+        class="uppy-ProviderBrowser-searchInput"
+        type="text"
+        placeholder="Search"
+        onkeyup={this.handleKeyPress}
+        onkeydown={this.handleKeyPress}
+        onkeypress={this.handleKeyPress}
+        value={this.props.filterInput}
+        ref={(input) => { this.input = input }} />
+      <button
+        class="uppy-ProviderBrowser-searchClose"
+        type="button"
+        onclick={this.props.toggleSearch}>
+        <svg class="UppyIcon" viewBox="0 0 19 19">
+          <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
+        </svg>
+      </button>
+    </div>
+  }
+}

+ 4 - 6
src/plugins/Provider/view/Loader.js

@@ -1,9 +1,7 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 
 module.exports = (props) => {
-  return html`
-    <div class="UppyProvider-loading">
-      <span>Loading...</span>
-    </div>
-  `
+  return <div class="uppy-Provider-loading">
+    <span>Loading...</span>
+  </div>
 }

+ 13 - 11
src/plugins/Provider/view/Table.js

@@ -1,23 +1,23 @@
-const html = require('yo-yo')
 const Row = require('./TableRow')
+const { h } = require('preact')
 
 module.exports = (props) => {
   // const headers = props.columns.map((column) => {
   //   return html`
-  //     <th class="BrowserTable-headerColumn BrowserTable-column" onclick=${props.sortByTitle}>
+  //     <th class="uppy-ProviderBrowserTable-headerColumn uppy-ProviderBrowserTable-column" onclick=${props.sortByTitle}>
   //       ${column.name}
   //     </th>
   //   `
   // })
 
-  // <thead class="BrowserTable-header">
+  // <thead class="uppy-ProviderBrowserTable-header">
   //   <tr>${headers}</tr>
   // </thead>
 
-  return html`
-    <table class="BrowserTable" onscroll=${props.handleScroll}>
-      <tbody role="listbox" aria-label="List of files from ${props.title}">
-        ${props.folders.map((folder) => {
+  return (
+    <table class="uppy-ProviderBrowserTable" onscroll={props.handleScroll}>
+      <tbody role="listbox" aria-label={`List of files from ${props.title}`}>
+        {props.folders.map(folder => {
           let isDisabled = false
           let isChecked = props.isChecked(folder)
           if (isChecked) {
@@ -25,7 +25,8 @@ module.exports = (props) => {
           }
           return Row({
             title: props.getItemName(folder),
-            active: props.activeRow(folder),
+            type: 'folder',
+            // active: props.activeRow(folder),
             getItemIcon: () => props.getItemIcon(folder),
             handleClick: () => props.handleFolderClick(folder),
             isDisabled: isDisabled,
@@ -34,10 +35,11 @@ module.exports = (props) => {
             columns: props.columns
           })
         })}
-        ${props.files.map((file) => {
+        {props.files.map(file => {
           return Row({
             title: props.getItemName(file),
-            active: props.activeRow(file),
+            type: 'file',
+            // active: props.activeRow(file),
             getItemIcon: () => props.getItemIcon(file),
             handleClick: () => props.handleFileClick(file),
             isDisabled: false,
@@ -48,5 +50,5 @@ module.exports = (props) => {
         })}
       </tbody>
     </table>
-  `
+  )
 }

+ 35 - 17
src/plugins/Provider/view/TableRow.js

@@ -1,28 +1,46 @@
-const html = require('yo-yo')
 const cuid = require('cuid')
+const { h } = require('preact')
 
 module.exports = (props) => {
-  // const classes = props.active ? 'BrowserTable-row is-active' : 'BrowserTable-row'
   const uniqueId = cuid()
 
-  return html`
-    <tr class="BrowserTable-row">
-      <td class="BrowserTable-column">
-        <div class="BrowserTable-checkbox">
+  const stop = (ev) => {
+    if (ev.keyCode === 13) {
+      ev.stopPropagation()
+      ev.preventDefault()
+    }
+  }
+
+  const handleItemClick = (ev) => {
+    ev.preventDefault()
+    // when file is clicked, select it, but when folder is clicked, open it
+    if (props.type === 'folder') {
+      return props.handleClick(ev)
+    }
+    props.handleCheckboxClick(ev)
+  }
+
+  return (
+    <tr class="uppy-ProviderBrowserTable-row">
+      <td class="uppy-ProviderBrowserTable-column">
+        <div class="uppy-ProviderBrowserTable-checkbox">
           <input type="checkbox"
-                 role="option" 
-                 tabindex="0"
-                 aria-label="Select file: ${props.title}"
-                 id=${uniqueId}
-                 checked=${props.isChecked}
-                 disabled=${props.isDisabled}
-                 onchange=${props.handleCheckboxClick} />
-          <label for=${uniqueId}></label>
+            role="option"
+            tabindex="0"
+            aria-label={`Select ${props.title}`}
+            id={uniqueId}
+            checked={props.isChecked}
+            disabled={props.isDisabled}
+            onchange={props.handleCheckboxClick}
+            onkeyup={stop}
+            onkeydown={stop}
+            onkeypress={stop} />
+          <label for={uniqueId} onclick={handleItemClick} />
         </div>
-        <button type="button" class="BrowserTable-item" aria-label="Select file: ${props.title}" tabindex="0" onclick=${props.handleClick}>
-          ${props.getItemIcon()} ${props.title}
+        <button type="button" class="uppy-ProviderBrowserTable-item" aria-label={`Select ${props.title}`} tabindex="0" onclick={handleItemClick}>
+          {props.getItemIcon()} {props.title}
         </button>
       </td>
     </tr>
-  `
+  )
 }

+ 11 - 9
src/plugins/Provider/view/index.js

@@ -2,6 +2,7 @@ const AuthView = require('./AuthView')
 const Browser = require('./Browser')
 const LoaderView = require('./Loader')
 const Utils = require('../../../core/Utils')
+const { h } = require('preact')
 
 /**
  * Class to easily generate generic views for plugins
@@ -201,19 +202,19 @@ module.exports = class View {
     }))
   }
 
-  toggleSearch () {
+  toggleSearch (inputEl) {
     const state = this.plugin.getPluginState()
-    const searchInputEl = document.querySelector('.Browser-searchInput')
+    // const searchInputEl = document.querySelector('.Browser-searchInput')
 
-    this.plugin.setPluginState(Object.assign({}, state, {
+    this.plugin.setPluginState({
       isSearchVisible: !state.isSearchVisible,
       filterInput: ''
-    }))
+    })
 
-    searchInputEl.value = ''
-    if (!state.isSearchVisible) {
-      searchInputEl.focus()
-    }
+    // searchInputEl.value = ''
+    // if (!state.isSearchVisible) {
+    //   searchInputEl.focus()
+    // }
   }
 
   filterItems (items) {
@@ -425,6 +426,7 @@ module.exports = class View {
    * for all of them, depending on current file state.
    */
   toggleCheckbox (e, file) {
+    console.log(e, e.shiftKey)
     e.stopPropagation()
     e.preventDefault()
     let { folders, files, filterInput } = this.plugin.getPluginState()
@@ -555,7 +557,7 @@ module.exports = class View {
     }
 
     if (!authenticated) {
-      return AuthView({
+      return h(AuthView, {
         pluginName: this.plugin.title,
         demo: this.plugin.opts.demo,
         checkAuth: this.checkAuth,

+ 82 - 93
src/plugins/StatusBar/StatusBar.js

@@ -1,11 +1,11 @@
-const html = require('yo-yo')
 const throttle = require('lodash.throttle')
+const { h } = require('preact')
 
 function progressDetails (props) {
-  return html`<span>${props.totalProgress || 0}%・${props.complete} / ${props.inProgress}${props.totalUploadedSize} / ${props.totalSize}・↑ ${props.totalSpeed}/s・${props.totalETA}</span>`
+  return <span>{props.totalProgress || 0}%・{props.complete} / {props.inProgress}・{props.totalUploadedSize} / {props.totalSize}・↑ {props.totalSpeed}/s・{props.totalETA}</span>
 }
 
-const throttledProgressDetails = throttle(progressDetails, 1000, {leading: true, trailing: true})
+const ThrottledProgressDetails = throttle(progressDetails, 1000, {leading: true, trailing: true})
 
 const STATE_ERROR = 'error'
 const STATE_WAITING = 'waiting'
@@ -76,6 +76,20 @@ function calculateProcessingProgress (files) {
   }
 }
 
+function togglePauseResume (props) {
+  if (props.isAllComplete) return
+
+  if (!props.resumableUploads) {
+    return props.cancelAll()
+  }
+
+  if (props.isAllPaused) {
+    return props.resumeAll()
+  }
+
+  return props.pauseAll()
+}
+
 module.exports = (props) => {
   props = props || {}
 
@@ -105,104 +119,93 @@ module.exports = (props) => {
   const isHidden = (uploadState === STATE_WAITING && props.hideUploadButton) ||
     (uploadState === STATE_WAITING && !props.newFiles > 0)
 
-  const statusBarEl = html`
-    <div class="UppyStatusBar is-${uploadState}"
-         aria-hidden="${isHidden}">
-      <div class="UppyStatusBar-progress ${progressMode ? `is-${progressMode}` : ''}"
-           style="width: ${width}%"
-           role="progressbar"
-           aria-valuemin="0"
-           aria-valuemax="100"
-           ${progressValue
-           ? { 'aria-valuenow': progressValue }
-           : {}}></div>
-      ${progressBarContent}
-      <div class="UppyStatusBar-actions">
-        ${props.newFiles && !props.hideUploadButton ? UploadBtn(props) : ''}
-        ${props.error ? RetryBtn(props) : ''}
+  const progressClasses = `uppy-StatusBar-progress 
+                           ${progressMode ? 'is-' + progressMode : ''}`
+
+  return (
+    <div class={`uppy uppy-StatusBar is-${uploadState}`} aria-hidden={isHidden}>
+      <div class={progressClasses}
+        style={{ width: width + '%' }}
+        role="progressbar"
+        aria-valuemin="0"
+        aria-valuemax="100"
+        aria-valuenow={progressValue} />
+      {progressBarContent}
+      <div class="uppy-StatusBar-actions">
+        { props.newFiles && !props.hideUploadButton ? <UploadBtn {...props} /> : null }
+        { props.error ? <RetryBtn {...props} /> : null }
       </div>
     </div>
-  `
-
-  // if (progressValue) {
-  //   statusBarEl.querySelector('.UppyStatusBar-progress').setAttribute('aria-valuenow', progressValue)
-  // }
-
-  return statusBarEl
+  )
 }
 
 const UploadBtn = (props) => {
-  return html`<button type="button"
-                      class="UppyStatusBar-actionBtn UppyStatusBar-actionBtn--upload"
-                      aria-label="${props.i18n('uploadXFiles', { smart_count: props.newFiles })}"
-                      onclick=${props.startUpload}>
-                ${props.inProgress
-                  ? props.i18n('uploadXNewFiles', { smart_count: props.newFiles })
-                  : props.i18n('uploadXFiles', { smart_count: props.newFiles })
-                }
-              </button>`
+  return <button type="button"
+    class="uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--upload"
+    aria-label={props.i18n('uploadXFiles', { smart_count: props.newFiles })}
+    onclick={props.startUpload}>
+    {props.inProgress
+      ? props.i18n('uploadXNewFiles', { smart_count: props.newFiles })
+      : props.i18n('uploadXFiles', { smart_count: props.newFiles })
+    }
+  </button>
 }
 
 const RetryBtn = (props) => {
-  return html`<button type="button"
-                      class="UppyStatusBar-actionBtn UppyStatusBar-actionBtn--retry"
-                      aria-label="${props.i18n('retryUpload')}"
-                      onclick=${props.retryAll}>
-                ${props.i18n('retry')}
-              </button>`
+  return <button type="button"
+    class="uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry"
+    aria-label={props.i18n('retryUpload')}
+    onclick={props.retryAll}>{props.i18n('retry')}</button>
 }
 
 const ProgressBarProcessing = (props) => {
   const value = Math.round(props.value * 100)
 
-  return html`
-    <div class="UppyStatusBar-content">
-        ${props.mode === 'determinate' ? `${value}%・` : ''}
-        ${props.message}
-    </div>
-  `
+  return <div class="uppy-StatusBar-content">
+    {props.mode === 'determinate' ? `${value}%・` : ''}
+    {props.message}
+  </div>
 }
 
 const ProgressBarUploading = (props) => {
-  return html`
-    <div class="UppyStatusBar-content">
-      ${props.isUploadStarted && !props.isAllComplete
+  return (
+    <div class="uppy-StatusBar-content">
+      {props.isUploadStarted && !props.isAllComplete
         ? !props.isAllPaused
-          ? html`<div title="Uploading">${pauseResumeButtons(props)} Uploading... ${throttledProgressDetails(props)}</div>`
-          : html`<div title="Paused">${pauseResumeButtons(props)} Paused・${props.totalProgress}%</div>`
+          ? <div title="Uploading">{ <PauseResumeButtons {...props} /> } Uploading... { <ThrottledProgressDetails {...props} /> }</div>
+          : <div title="Paused">{ <PauseResumeButtons {...props} /> } Paused・{props.totalProgress}%</div>
         : null
       }
     </div>
-  `
+  )
 }
 
 const ProgressBarComplete = ({ totalProgress, i18n }) => {
-  return html`
-    <div class="UppyStatusBar-content" role="status">
+  return (
+    <div class="uppy-StatusBar-content" role="status">
       <span title="Complete">
-        <svg aria-hidden="true" class="UppyStatusBar-statusIndicator UppyIcon" width="18" height="17" viewBox="0 0 23 17">
+        <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="18" height="17" viewBox="0 0 23 17">
           <path d="M8.944 17L0 7.865l2.555-2.61 6.39 6.525L20.41 0 23 2.645z" />
         </svg>
-        ${i18n('uploadComplete')}・${totalProgress}%
+        {i18n('uploadComplete')}・{totalProgress}%
       </span>
     </div>
-  `
+  )
 }
 
 const ProgressBarError = ({ error, retryAll, i18n }) => {
-  return html`
-    <div class="UppyStatusBar-content" role="alert">
-        <strong>${i18n('uploadFailed')}.</strong>
-        <span>${i18n('pleasePressRetry')}</span>
-        <span class="UppyStatusBar-details"
-              data-balloon="${error}"
-              data-balloon-pos="up"
-              data-balloon-length="large">?</span>
-      </div>
-  `
+  return (
+    <div class="uppy-StatusBar-content" role="alert">
+      <strong>{i18n('uploadFailed')}.</strong> <span>{i18n('pleasePressRetry')}</span>
+      <span class="uppy-StatusBar-details"
+        data-balloon={error}
+        data-balloon-pos="up"
+        data-balloon-length="large">?</span>
+    </div>
+  )
 }
 
-const pauseResumeButtons = (props) => {
+const PauseResumeButtons = (props) => {
   const { resumableUploads, isAllPaused, i18n } = props
   const title = resumableUploads
                 ? isAllPaused
@@ -210,32 +213,18 @@ const pauseResumeButtons = (props) => {
                   : i18n('pauseUpload')
                 : i18n('cancelUpload')
 
-  return html`<button title="${title}" class="UppyStatusBar-statusIndicator" type="button" onclick=${() => togglePauseResume(props)}>
-    ${resumableUploads
+  return <button title={title} class="uppy-StatusBar-statusIndicator" type="button" onclick={() => togglePauseResume(props)}>
+    {resumableUploads
       ? isAllPaused
-        ? html`<svg aria-hidden="true" class="UppyIcon" width="15" height="17" viewBox="0 0 11 13">
+        ? <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>`
-        : html`<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>`
-      : html`<svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 19 19">
-        <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z"/>
-      </svg>`
+        </svg>
+        : <svg aria-hidden="true" class="UppyIcon" width="16" height="17" viewBox="0 0 12 13">
+          <path d="M4.888.81v11.38c0 .446-.324.81-.722.81H2.722C2.324 13 2 12.636 2 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81zM9.888.81v11.38c0 .446-.324.81-.722.81H7.722C7.324 13 7 12.636 7 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81z" />
+        </svg>
+      : <svg aria-hidden="true" class="UppyIcon" width="16px" height="16px" viewBox="0 0 19 19">
+        <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
+      </svg>
     }
-  </button>`
-}
-
-const togglePauseResume = (props) => {
-  if (props.isAllComplete) return
-
-  if (!props.resumableUploads) {
-    return props.cancelAll()
-  }
-
-  if (props.isAllPaused) {
-    return props.resumeAll()
-  }
-
-  return props.pauseAll()
+  </button>
 }

+ 3 - 3
src/plugins/StatusBar/index.js

@@ -1,6 +1,6 @@
 const Plugin = require('../../core/Plugin')
 const Translator = require('../../core/Translator')
-const StatusBar = require('./StatusBar')
+const StatusBarUI = require('./StatusBar')
 const { getSpeed } = require('../../core/Utils')
 const { getBytesRemaining } = require('../../core/Utils')
 const { prettyETA } = require('../../core/Utils')
@@ -9,7 +9,7 @@ const prettyBytes = require('prettier-bytes')
 /**
  * A status bar.
  */
-module.exports = class StatusBarUI extends Plugin {
+module.exports = class StatusBar extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'StatusBar'
@@ -141,7 +141,7 @@ module.exports = class StatusBarUI extends Plugin {
 
     const resumableUploads = this.uppy.getState().capabilities.resumableUploads || false
 
-    return StatusBar({
+    return StatusBarUI({
       error: state.error,
       totalProgress: state.totalProgress,
       totalSize: totalSize,

+ 5 - 5
src/plugins/Webcam/CameraIcon.js

@@ -1,8 +1,8 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 
 module.exports = (props) => {
-  return html`<svg aria-hidden="true" class="UppyIcon" width="100" height="77" viewBox="0 0 100 77">
-    <path d="M50 32c-7.168 0-13 5.832-13 13s5.832 13 13 13 13-5.832 13-13-5.832-13-13-13z"/>
-    <path d="M87 13H72c0-7.18-5.82-13-13-13H41c-7.18 0-13 5.82-13 13H13C5.82 13 0 18.82 0 26v38c0 7.18 5.82 13 13 13h74c7.18 0 13-5.82 13-13V26c0-7.18-5.82-13-13-13zM50 68c-12.683 0-23-10.318-23-23s10.317-23 23-23 23 10.318 23 23-10.317 23-23 23z"/>
-  </svg>`
+  return <svg aria-hidden="true" class="UppyIcon" width="100" height="77" viewBox="0 0 100 77">
+    <path d="M50 32c-7.168 0-13 5.832-13 13s5.832 13 13 13 13-5.832 13-13-5.832-13-13-13z" />
+    <path d="M87 13H72c0-7.18-5.82-13-13-13H41c-7.18 0-13 5.82-13 13H13C5.82 13 0 18.82 0 26v38c0 7.18 5.82 13 13 13h74c7.18 0 13-5.82 13-13V26c0-7.18-5.82-13-13-13zM50 68c-12.683 0-23-10.318-23-23s10.317-23 23-23 23 10.318 23 23-10.317 23-23 23z" />
+  </svg>
 }

+ 30 - 32
src/plugins/Webcam/CameraScreen.js

@@ -1,4 +1,4 @@
-const html = require('yo-yo')
+const { h, Component } = require('preact')
 const SnapshotButton = require('./SnapshotButton')
 const RecordButton = require('./RecordButton')
 
@@ -6,40 +6,38 @@ function isModeAvailable (modes, mode) {
   return modes.indexOf(mode) !== -1
 }
 
-module.exports = (props) => {
-  const src = props.src || ''
-  let video
-
-  if (props.useTheFlash) {
-    video = props.getSWFHTML()
-  } else {
-    video = html`<video class="UppyWebcam-video" autoplay muted src="${src}"></video>`
+class CameraScreen extends Component {
+  componentDidMount () {
+    this.props.onFocus()
+    this.btnContainer.firstChild.focus()
   }
 
-  const shouldShowRecordButton = props.supportsRecording && (
-    isModeAvailable(props.modes, 'video-only') ||
-    isModeAvailable(props.modes, 'audio-only') ||
-    isModeAvailable(props.modes, 'video-audio')
-  )
+  componentWillUnmount () {
+    this.props.onStop()
+  }
 
-  const shouldShowSnapshotButton = isModeAvailable(props.modes, 'picture')
+  render () {
+    const shouldShowRecordButton = this.props.supportsRecording && (
+      isModeAvailable(this.props.modes, 'video-only') ||
+      isModeAvailable(this.props.modes, 'audio-only') ||
+      isModeAvailable(this.props.modes, 'video-audio')
+    )
+    const shouldShowSnapshotButton = isModeAvailable(this.props.modes, 'picture')
 
-  return html`
-    <div class="UppyWebcam-container" onload=${(el) => {
-      props.onFocus()
-      const recordButton = el.querySelector('.UppyWebcam-recordButton')
-      if (recordButton) recordButton.focus()
-    }} onunload=${(el) => {
-      props.onStop()
-    }}>
-      <div class='UppyWebcam-videoContainer'>
-        ${video}
-      </div>
-      <div class='UppyWebcam-buttonContainer'>
-        ${shouldShowRecordButton ? RecordButton(props) : null}
-        ${shouldShowSnapshotButton ? SnapshotButton(props) : null}
+    return (
+      <div class="uppy uppy-Webcam-container">
+        <div class="uppy-Webcam-videoContainer">
+          <video class="uppy-Webcam-video" autoplay muted src={this.props.src || ''} />
+        </div>
+        <div class="uppy-Webcam-buttonContainer" ref={(el) => { this.btnContainer = el }}>
+          {shouldShowSnapshotButton ? SnapshotButton(this.props) : null}
+          {' '}
+          {shouldShowRecordButton ? RecordButton(this.props) : null}
+        </div>
+        <canvas class="uppy-Webcam-canvas" style="display: none;" />
       </div>
-      <canvas class="UppyWebcam-canvas" style="display: none;"></canvas>
-    </div>
-  `
+    )
+  }
 }
+
+module.exports = CameraScreen

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

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

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

@@ -1,27 +1,27 @@
-const html = require('yo-yo')
 const RecordStartIcon = require('./RecordStartIcon')
 const RecordStopIcon = require('./RecordStopIcon')
+const { h } = require('preact')
 
 module.exports = function RecordButton ({ recording, onStartRecording, onStopRecording }) {
   if (recording) {
-    return html`
-      <button class="UppyButton--circular UppyButton--red UppyButton--sizeM UppyWebcam-recordButton"
+    return (
+      <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
         type="button"
         title="Stop Recording"
         aria-label="Stop Recording"
-        onclick=${onStopRecording}>
-        ${RecordStopIcon()}
+        onclick={onStopRecording}>
+        {RecordStopIcon()}
       </button>
-    `
+    )
   }
 
-  return html`
-    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM UppyWebcam-recordButton"
+  return (
+    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
       type="button"
       title="Begin Recording"
       aria-label="Begin Recording"
-      onclick=${onStartRecording}>
-      ${RecordStartIcon()}
+      onclick={onStartRecording}>
+      {RecordStartIcon()}
     </button>
-  `
+  )
 }

+ 3 - 3
src/plugins/Webcam/RecordStartIcon.js

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

+ 3 - 3
src/plugins/Webcam/RecordStopIcon.js

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

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

@@ -1,14 +1,14 @@
-const html = require('yo-yo')
+const { h } = require('preact')
 const CameraIcon = require('./CameraIcon')
 
-module.exports = function SnapshotButton ({ onSnapshot }) {
-  return html`
-    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM UppyWebcam-recordButton"
+module.exports = ({ onSnapshot }) => {
+  return (
+    <button class="UppyButton--circular UppyButton--red UppyButton--sizeM uppy-Webcam-recordButton"
       type="button"
       title="Take a snapshot"
       aria-label="Take a snapshot"
-      onclick=${onSnapshot}>
-      ${CameraIcon()}
+      onclick={onSnapshot}>
+      {CameraIcon()}
     </button>
-  `
+  )
 }

+ 5 - 5
src/plugins/Webcam/WebcamIcon.js

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

+ 9 - 6
src/plugins/Webcam/index.js

@@ -1,3 +1,4 @@
+const { h } = require('preact')
 const Plugin = require('../../core/Plugin')
 const Translator = require('../../core/Translator')
 const {
@@ -43,7 +44,6 @@ module.exports = class Webcam extends Plugin {
     this.title = 'Webcam'
     this.type = 'acquirer'
     this.icon = WebcamIcon
-    this.focus = this.focus.bind(this)
 
     const defaultLocale = {
       strings: {
@@ -86,6 +86,7 @@ module.exports = class Webcam extends Plugin {
     this.startRecording = this.startRecording.bind(this)
     this.stopRecording = this.stopRecording.bind(this)
     this.oneTwoThreeSmile = this.oneTwoThreeSmile.bind(this)
+    this.focus = this.focus.bind(this)
 
     this.webcamActive = false
 
@@ -167,11 +168,13 @@ module.exports = class Webcam extends Plugin {
         isRecording: false
       })
       return this.getVideo()
-    }).then((file) => {
-      return this.uppy.addFile(file)
-    }).then(() => {
+    })
+    .then(this.uppy.addFile)
+    .then(() => {
       this.recordingChunks = null
       this.recorder = null
+      const dashboard = this.uppy.getPlugin('Dashboard')
+      if (dashboard) dashboard.hideAllPanels()
     }, (error) => {
       this.recordingChunks = null
       this.recorder = null
@@ -192,7 +195,7 @@ module.exports = class Webcam extends Plugin {
   }
 
   getVideoElement () {
-    return this.el.querySelector('.UppyWebcam-video')
+    return this.el.querySelector('.uppy-Webcam-video')
   }
 
   oneTwoThreeSmile () {
@@ -301,7 +304,7 @@ module.exports = class Webcam extends Plugin {
       return PermissionsScreen(webcamState)
     }
 
-    return CameraScreen(Object.assign({}, webcamState, {
+    return h(CameraScreen, Object.assign({}, webcamState, {
       onSnapshot: this.takeSnapshot,
       onStartRecording: this.startRecording,
       onStopRecording: this.stopRecording,

+ 10 - 12
src/scss/_common.scss

@@ -2,11 +2,19 @@
 * General Uppy styles that apply to everything inside the .Uppy container
 */
 
-.Uppy {
+.uppy {
+  // all: initial;
   box-sizing: border-box;
+  font-family: -apple-system, BlinkMacSystemFont,
+    'avenir next', avenir,
+    helvetica, 'helvetica neue',
+    ubuntu, roboto, noto,
+    'segoe ui', arial, sans-serif;
+  line-height: 1;
+  // -webkit-font-smoothing: antialiased;
 }
 
-.Uppy *, .Uppy *:before, .Uppy *:after {
+.uppy *, .uppy *:before, .uppy *:after {
   box-sizing: inherit;
 }
 
@@ -28,16 +36,6 @@
   position: relative;
 }
 
-.UppyTheme--default {
-  font-family: -apple-system, BlinkMacSystemFont,
-               'avenir next', avenir,
-               helvetica, 'helvetica neue',
-               ubuntu, roboto, noto,
-               'segoe ui', arial, sans-serif;
-  line-height: 1;
-  -webkit-font-smoothing: antialiased;
-}
-
 // Buttons
 
 .UppyButton--circular {

File diff suppressed because it is too large
+ 137 - 153
src/scss/_dashboard.scss


+ 1 - 8
src/scss/_dragdrop.scss

@@ -6,6 +6,7 @@
     justify-content: center;
     border-radius: 7px;
     background-color: $color-white;
+    cursor: pointer;
   }
   
   .uppy-DragDrop-inner {
@@ -62,14 +63,6 @@
     color: lighten($color-gray, 10%);
   }
   
-  // .uppy-DragDrop-selectedCount {
-  //   text-align: center;
-  //   font-size: 0.75em;
-  //   text-transform: uppercase;
-  //   letter-spacing: 1px;
-  //   margin-top: 10px;
-  // }
-  
   .uppy-DragDrop-dragText {
     color: $color-cornflower-blue;
   }

+ 5 - 5
src/scss/_fileinput.scss

@@ -1,4 +1,4 @@
-.uppy-FileInput-form {
+.uppy-FileInput-container {
   margin-bottom: 15px;
 }
 
@@ -9,13 +9,13 @@
   font-size: 0.85em;
   // text-transform: uppercase;
   padding: 10px 15px;
-  color: $color-pink;
-  border: 1px solid $color-pink;
-  border-radius: 15px;
+  color: darken($color-cornflower-blue, 20%);
+  border: 1px solid darken($color-cornflower-blue, 20%);
+  border-radius: 8px;
   cursor: pointer;
 
   &:hover {
-    background-color: $color-pink;
+    background-color: darken($color-cornflower-blue, 20%);
     color: $color-white;
   }
 }

+ 7 - 12
src/scss/_informer.scss

@@ -1,9 +1,4 @@
-// @import '_variables.scss';
-// @import '_utils.scss';
-// @import '_animation.scss';
-// @import '_common.scss';
-
-.UppyInformer {
+.uppy-Informer {
   position: absolute;
   bottom: 0;
   left: 0;
@@ -21,32 +16,32 @@
   transition: all 300ms ease-in;
   z-index: $zIndex-4;
 
-  .UppyDashboard--wide & {
+  .uppy-Dashboard--wide & {
     height: 40px;
     line-height: 40px;
     font-size: 13px;
   }
 }
 
-  .UppyInformer[aria-hidden=true] {
+  .uppy-Informer[aria-hidden=true] {
     opacity: 0;
     transform: translateY(200%);
     transition: all 300ms ease-in;
   }
 
-.UppyInformer p {
+.uppy-Informer p {
   margin: 0;
   padding: 0;
   height: 30px;
   line-height: 30px;
 
-  .UppyDashboard--wide & {
+  .uppy-Dashboard--wide & {
     height: 40px;
     line-height: 40px;
   }
 }
 
-.UppyInformer span {
+.uppy-Informer span {
   line-height: 12px;
   width: 13px;
   height: 13px;
@@ -61,7 +56,7 @@
   margin-left: -1px;
 }
 
-.UppyInformer span:after {
+.uppy-Informer span:after {
   line-height: 1.3;
   word-wrap: break-word;
 }

+ 3 - 3
src/scss/_progressbar.scss

@@ -3,7 +3,7 @@
 // @import '_animation.scss';
 // @import '_common.scss';
 
-.UppyProgressBar {
+.uppy-ProgressBar {
   position: absolute;
   top: 0;
   left: 0;
@@ -12,7 +12,7 @@
   z-index: 10000;
 }
 
-.UppyProgressBar-inner {
+.uppy-ProgressBar-inner {
   background-color: $color-cornflower-blue;
   box-shadow: 0 0 10px rgba($color-cornflower-blue, 0.7);
   height: 100%;
@@ -20,7 +20,7 @@
   transition: width 0.4s ease;
 }
 
-.UppyProgressBar-percentage {
+.uppy-ProgressBar-percentage {
   display: none;
   text-align: center;
   position: absolute;

+ 64 - 74
src/scss/_provider.scss

@@ -1,9 +1,4 @@
-// @import '_variables.scss';
-// @import '_utils.scss';
-// @import '_animation.scss';
-// @import '_common.scss';
-
-.UppyProvider-auth, .UppyProvider-error, .UppyProvider-loading {
+.uppy-Provider-auth, .uppy-Provider-error, .uppy-Provider-loading {
   display: flex;
   align-items: center;
   justify-content: center;
@@ -11,7 +6,7 @@
   height: 100%;
 }
 
-.UppyProvider-authTitle {
+.uppy-Provider-authTitle {
   font-size: 22px;
   line-height: 1.35;
   font-weight: 300;
@@ -21,12 +16,13 @@
   text-align: center;
 }
 
-.UppyProvider-authBtn {
+.uppy-Provider-authBtn {
   @include reset-button;
   border-radius: 6px;
   background-color: $color-cornflower-blue;
   color: $color-white;
   font-size: 20px;
+  font-weight: 400;
   padding: 12px 46px;
   transition: background-color 0.3s;
   text-decoration: none;
@@ -39,7 +35,7 @@
   }
 }
 
-// .UppyProvider-breadcrumbs {
+// .uppy-Provider-breadcrumbs {
 //   color: $color-black;
 //   font-size: 12px;
 //   line-height: 40px;
@@ -49,41 +45,31 @@
 //   // padding: 15px 0;
 // }
 
-.UppyProvider-breadcrumbs button,
-.UppyGoogleDrive-sidebar button {
+.uppy-Provider-breadcrumbs button {
   @include reset-button;
   cursor: pointer;
   font-size: 14px;
 }
 
-.UppyProvider-breadcrumbs button:hover {
+.uppy-Provider-breadcrumbs button:hover {
   text-decoration: underline;
 }
 
-.UppyGoogleDrive-sidebar button {
-  font-size: 14px;
-  padding: 0;
-}
-
-.UppyProvider-breadcrumbs button:focus,
-.UppyGoogleDrive-sidebar button:focus {
+.uppy-Provider-breadcrumbs button:focus {
   outline: 1px dotted #aaa;
 }
 
-.UppyProvider-breadcrumbs li {
+.uppy-Provider-breadcrumbs li {
   display: inline-block;
   margin: 0;
   margin-right: 6px;
 }
 
-.UppyProvider-breadcrumbs li:not(:last-child):after {
-  content: '/';
+.uppy-Provider-breadcrumbs li:not(:last-child):after {
+  content: ' /';
 }
 
-
-/** NEW PLUGIN BROWSER STYLES */
-
-.Browser {
+.uppy-ProviderBrowser {
   // border: 1px solid #ddd;
   // border-radius: 5px;
   display: flex;
@@ -96,11 +82,11 @@
   // width: 718px;
 }
 
-.Browser-user {
+.uppy-ProviderBrowser-user {
   margin: 16px 0;
 }
 
-.Browser-header {
+.uppy-ProviderBrowser-header {
   z-index: 1000;
   border-bottom: 1px solid #e5e5e5;
   position: relative;
@@ -122,7 +108,7 @@
    color: rgba(119,119,119,0.75);
 }
 
-.Browser-headerBar {
+.uppy-ProviderBrowser-headerBar {
   height: 50px;
   line-height: 50px;
   display: flex;
@@ -132,7 +118,7 @@
   z-index: $zIndex-2;
 }
 
-.Browser-search {
+.uppy-ProviderBrowser-search {
   height: 50px;
   position: absolute;
   top: 0;
@@ -146,11 +132,11 @@
   align-items: center;
 }
 
-  .Browser-search[aria-hidden=false] {
+  .uppy-ProviderBrowser-search:not([aria-hidden=true]) {
     transform: translate(0, 0);
   }
 
-.Browser-search input {
+.uppy-ProviderBrowser-search input {
   flex: 1;
   background-color: transparent;
   outline: 0;
@@ -162,7 +148,7 @@
 }
 
 
-.Browser-searchToggle {
+.uppy-ProviderBrowser-searchToggle {
   @include reset-button();
   width: 15px;
   cursor: pointer;
@@ -174,7 +160,7 @@
   }
 }
 
-.Browser-searchClose {
+.uppy-ProviderBrowser-searchClose {
   @include reset-button();
   cursor: pointer;
   width: 12px;
@@ -188,7 +174,7 @@
   }
 }
 
-.Browser-userLogout {
+.uppy-ProviderBrowser-userLogout {
   @include reset-button();
   margin-right: 16px;
   cursor: pointer;
@@ -198,7 +184,7 @@
   }
 }
 
-.UppyProvider-breadcrumbs {
+.uppy-Provider-breadcrumbs {
   flex: 1;
   color: $color-black;
   font-size: 12px;
@@ -209,18 +195,18 @@
   margin-left: 16px;
 }
 
-.Browser-breadcrumbs span {
+.uppy-ProviderBrowser-breadcrumbs span {
   font-size: 16px;
   margin-left: 32px;
 }
 
-.Browser-breadcrumbs span.active {
+.uppy-ProviderBrowser-breadcrumbs span.active {
   color: #666;
   flex: 1;
   font-weight: 500;
 }
 
-// .Browser-subHeader {
+// .uppy-ProviderBrowser-subHeader {
 //   display: flex;
 //   justify-content: space-between;
 //   align-items: center;
@@ -231,12 +217,12 @@
 //   z-index: 1000;
 // }
 
-.Browser-body {
+.uppy-ProviderBrowser-body {
   flex: 1;
   position: relative;
 }
 
-.BrowserTable {
+.uppy-ProviderBrowserTable {
   display: block;
   width: 100%;
   height: 100%;
@@ -251,30 +237,30 @@
   right: 0;
 }
 
-.Browser-viewType--grid {
-  .BrowserTable tbody {
+.uppy-ProviderBrowser-viewType--grid {
+  .uppy-ProviderBrowserTable tbody {
     display: flex;
     flex-direction: row;
     flex-wrap: wrap;
     justify-content: space-around;
   }
 
-  // .BrowserTable tbody:after {
+  // .uppy-ProviderBrowserTable tbody:after {
   //   content: "";
   //   flex: auto;
   // }
 
-  .BrowserTable-row {
+  .uppy-ProviderBrowserTable-row {
     position: relative;
   }
 
-  .BrowserTable-column {
+  .uppy-ProviderBrowserTable-column {
     // width: 100px;
     // padding: 15px 12px;
     padding: 14px 10px;
   }
 
-  .BrowserTable-checkbox {
+  .uppy-ProviderBrowserTable-checkbox {
     position: absolute;
     top: 7px;
     right: 7px;
@@ -282,31 +268,35 @@
   }
 }
 
-.BrowserTable tbody {
+.uppy-ProviderBrowserTable tbody {
   display: block;
 }
 
 
-.BrowserTable-checkbox input {
+.uppy-ProviderBrowserTable-checkbox input {
   opacity: 0;
 }
 
 // https://medium.com/claritydesignsystem/pure-css-accessible-checkboxes-and-radios-buttons-54063e759bb3
-.BrowserTable-checkbox {
+.uppy-ProviderBrowserTable-checkbox {
   position: relative;
   display: inline-block;
   top: -3px;
   margin-right: 25px;
 }
 
-.BrowserTable-checkbox label::before,
-.BrowserTable-checkbox label::after {
+.uppy-ProviderBrowserTable-checkbox label {
+  display: block;
+}
+
+.uppy-ProviderBrowserTable-checkbox label::before,
+.uppy-ProviderBrowserTable-checkbox label::after {
   position: absolute;
   cursor: pointer;
 }
 
 // Outer circle
-.BrowserTable-checkbox label:before {
+.uppy-ProviderBrowserTable-checkbox label:before {
   content: "";
   display: inline-block;
   height: 20px;
@@ -318,7 +308,7 @@
 }
 
 // Inner checkbox
-.BrowserTable-checkbox label:after {
+.uppy-ProviderBrowserTable-checkbox label:after {
   content: "";
   display: inline-block;
   height: 5px;
@@ -331,81 +321,81 @@
 }
 
 // Hide the checkmark by default
-.BrowserTable-checkbox input + label::after {
+.uppy-ProviderBrowserTable-checkbox input + label::after {
   content: none;
 }
 
 // Unhide the checkmark on the checked state
-.BrowserTable-checkbox input:checked + label::after {
+.uppy-ProviderBrowserTable-checkbox input:checked + label::after {
   content: "";
 }
 
-.BrowserTable-checkbox input:checked + label::before {
+.uppy-ProviderBrowserTable-checkbox input:checked + label::before {
   background-color: $color-cornflower-blue;
 }
 
 // Adding focus styles on the outer-box of the fake checkbox*/
-.BrowserTable-checkbox input:focus + label::before {
+.uppy-ProviderBrowserTable-checkbox input:focus + label::before {
   outline: rgb(59, 153, 252) auto 5px;
 }
 
-.BrowserTable-row {
+.uppy-ProviderBrowserTable-row {
   // border-bottom: 1px solid #eee;
   display: block;
   cursor: pointer;
   // height: 50px;
 }
 
-.BrowserTable-header {
+.uppy-ProviderBrowserTable-header {
   display: block;
 }
 
-  .BrowserTable-header:hover {
+  .uppy-ProviderBrowserTable-header:hover {
     background-color: $color-gray;
   }
 
-.BrowserTable-column {
+.uppy-ProviderBrowserTable-column {
   color: $color-asphalt-gray;
   line-height: 1.2;
   font-weight: bold;
   padding: 16px;
 }
 
-.BrowserTable-column img {
+.uppy-ProviderBrowserTable-column img {
   vertical-align: text-top;
   margin-right: 3px;
 }
 
-.BrowserTable-column input {
+.uppy-ProviderBrowserTable-column input {
   margin-top: 0;
   cursor: pointer;
 }
 
-.BrowserTable-item {
+.uppy-ProviderBrowserTable-item {
   @include reset-button();
   cursor: pointer;
   font-weight: 600;
 }
 
-.BrowserTable-headerColumn {
+.uppy-ProviderBrowserTable-headerColumn {
   cursor: pointer;
   text-align: left;
 }
 
-.BrowserTable-row:hover {
-  background-color: #eee;
-}
+// .uppy-ProviderBrowserTable-row:hover {
+//   background-color: #eee;
+// }
 
-.BrowserTable-header {
+.uppy-ProviderBrowserTable-header {
   background-color: #fafafa;
 }
 
-// .BrowserTable-rowColumn.BrowserTable-column {
+// .uppy-ProviderBrowserTable-rowColumn.uppy-ProviderBrowserTable-column {
 //   display: block;
 //   // width: 708px;
 // }
 
-.Browser-doneBtn {
+.uppy-ProviderBrowser-doneBtn {
   position: absolute;
   bottom: 16px;
   right: 16px;
@@ -413,13 +403,13 @@
   width: 50px;
   height: 50px;
 
-  .UppyDashboard--wide & {
+  .uppy-Dashboard--wide & {
     width: 60px;
     height: 60px;
   }
 }
 
-.Browser-doneBtn .UppyIcon {
+.uppy-ProviderBrowser-doneBtn .UppyIcon {
   width: 30px;
   height: 30px;
 }

+ 0 - 31
src/scss/_spinner.scss

@@ -1,31 +0,0 @@
-// http://stephanwagner.me/only-css-loading-spinner
-
-@keyframes spinner {
-  to {transform: rotate(360deg);}
-}
-
-.UppySpinner {
-  min-width: 24px;
-  min-height: 24px;
-  margin: 25px auto;
-  position: relative;
-}
-
-.UppySpinner.is-spinning:before {
-  content: 'Loading…';
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  width: 16px;
-  height: 16px;
-  margin-top: -10px;
-  margin-left: -10px;
-}
-
-.UppySpinner.is-spinning:not(:required):before {
-  content: '';
-  border-radius: 50%;
-  border: 2px solid rgba(0, 0, 0, .3);
-  border-top-color: rgba(0, 0, 0, .6);
-  animation: spinner .6s linear infinite;
-}

+ 24 - 24
src/scss/_statusbar.scss

@@ -1,4 +1,4 @@
-.UppyStatusBar {
+.uppy-StatusBar {
   position: relative;
   height: 30px;
   line-height: 30px;
@@ -12,30 +12,30 @@
   transition: height .35s;
 }
 
-.UppyStatusBar[aria-hidden=true] {
+.uppy-StatusBar[aria-hidden=true] {
   height: 0;
 }
 
-.UppyStatusBar.is-complete .UppyStatusBar-progress {
+.uppy-StatusBar.is-complete .uppy-StatusBar-progress {
   background-color: $color-green;
 }
 
-.UppyStatusBar.is-error .UppyStatusBar-progress {
+.uppy-StatusBar.is-error .uppy-StatusBar-progress {
   background-color: $color-red;
 }
 
-.UppyStatusBar.is-complete .UppyStatusBar-content {
+.uppy-StatusBar.is-complete .uppy-StatusBar-content {
   text-align: center;
   padding-left: 0;
 }
 
-.UppyStatusBar[aria-hidden=false].is-waiting {
+.uppy-StatusBar:not([aria-hidden=true]).is-waiting {
   background-color: $color-white;
   height: 65px;
   line-height: 65px;
 }
 
-.UppyStatusBar-progress {
+.uppy-StatusBar-progress {
   background-color: $color-cornflower-blue;
   height: 100%;
   position: absolute;
@@ -50,7 +50,7 @@
   }
 }
 
-.UppyStatusBar.is-waiting .UppyStatusBar-progress {
+.uppy-StatusBar.is-waiting .uppy-StatusBar-progress {
   display: none;
 }
 
@@ -59,8 +59,8 @@
   to { background-position: 0 0; }
 }
 
-.UppyStatusBar-content {
-  font-weight: 600;
+.uppy-StatusBar-content {
+  font-weight: 400;
   position: relative;
   z-index: $zIndex-3;
   padding-left: 15px;
@@ -69,7 +69,7 @@
   color: $color-white;
 }
 
-// .UppyStatusBar-content .UppyIcon {
+// .uppy-StatusBar-content .UppyIcon {
 //   width: 15px;
 //   height: 15px;
 
@@ -80,18 +80,18 @@
 // }
 
 
-.UppyStatusBar-statusIndicator {
+.uppy-StatusBar-statusIndicator {
   color: $color-white;
   margin-right: 8px;
 }
 
-  button.UppyStatusBar-statusIndicator {
+  button.uppy-StatusBar-statusIndicator {
     @include reset-button;
     margin-right: 8px;
     cursor: pointer;
   }
 
-.UppyStatusBar-actions {
+.uppy-StatusBar-actions {
   position: absolute;
   top: 0;
   bottom: 0;
@@ -99,37 +99,37 @@
   z-index: $zIndex-4;
 }
 
-.UppyStatusBar.is-waiting .UppyStatusBar-actions {
+.uppy-StatusBar.is-waiting .uppy-StatusBar-actions {
   left: 20px;
   right: initial;
 }
 
-.UppyStatusBar-actionBtn {
+.uppy-StatusBar-actionBtn {
   @include reset-button;
   font-size: 13px;
   line-height: 1em;
-  font-weight: bold;
+  font-weight: 400;
   padding: 7px 10px;
   border-radius: 4px;
   cursor: pointer;
   transition: all 0.3s;
 }
 
-  .UppyStatusBar-actionBtn:not(:last-child) {
+  .uppy-StatusBar-actionBtn:not(:last-child) {
     margin-right: 5px;
   }
 
-  .UppyStatusBar.is-waiting .UppyStatusBar-actionBtn {
+  .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn {
     padding: 13px 30px;
     font-size: 15px;
   }
 
-  .UppyStatusBar-actionBtn--upload {
+  .uppy-StatusBar-actionBtn--upload {
     background-color: $color-white;
     color: $color-cornflower-blue;
   }
 
-    .UppyStatusBar.is-waiting .UppyStatusBar-actionBtn--upload {
+    .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
         background-color: $color-cornflower-blue;
         color: $color-white;
 
@@ -138,12 +138,12 @@
         }
       }
 
-  .UppyStatusBar-actionBtn--retry {
+  .uppy-StatusBar-actionBtn--retry {
     background-color: $color-white;
     color: $color-red;
   }
 
-.UppyStatusBar-details {
+.uppy-StatusBar-details {
   line-height: 12px;
   width: 13px;
   height: 13px;
@@ -160,7 +160,7 @@
   text-align: center;
 }
 
-.UppyStatusBar-details:after {
+.uppy-StatusBar-details:after {
   line-height: 1.3;
   word-wrap: break-word;
 }

+ 6 - 6
src/scss/_webcam.scss

@@ -3,7 +3,7 @@
 // @import '_animation.scss';
 // @import '_common.scss';
 
-.UppyWebcam-container {
+.uppy-Webcam-container {
   width: 100%;
   height: 100%;
   display: flex;
@@ -11,29 +11,29 @@
   align-items: center;
 }
 
-.UppyWebcam-videoContainer {
+.uppy-Webcam-videoContainer {
   height: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
 }
 
-.UppyWebcam-video {
+.uppy-Webcam-video {
   max-width: 100%;
   max-height: 100%;
 }
 
-// .UppyWebcam-canvas {
+// .uppy-Webcam-canvas {
 //   display: none;
 // }
 
-.UppyWebcam-buttonContainer {
+.uppy-Webcam-buttonContainer {
   position: absolute;
   bottom: 30px;
   right: 30px;
 }
 
-.UppyWebcam-recordButton .UppyIcon {
+.uppy-Webcam-recordButton .UppyIcon {
   width: 50%;
   height: 50%;
   position: relative;

+ 2 - 2
test/endtoend/specs/uppy.test.js

@@ -33,7 +33,7 @@ describe('File upload with DragDrop + Tus, DragDrop + XHRUpload, i18n translated
       browser.execute(uppySelectFakeFile, 'uppyDragDrop')
     }
     browser.pause(3000)
-    var html = browser.getHTML('#uppyDragDrop-progress .UppyProgressBar-percentage', false)
+    var html = browser.getHTML('#uppyDragDrop-progress .uppy-ProgressBar-percentage', false)
     expect(parseInt(html)).to.be.equal(100)
   })
 
@@ -44,7 +44,7 @@ describe('File upload with DragDrop + Tus, DragDrop + XHRUpload, i18n translated
       browser.execute(uppySelectFakeFile, 'uppyi18n')
     }
     browser.pause(3000)
-    var html = browser.getHTML('#uppyi18n-progress .UppyProgressBar-percentage', false)
+    var html = browser.getHTML('#uppyi18n-progress .uppy-ProgressBar-percentage', false)
     expect(parseInt(html)).to.be.equal(100)
   })
 

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