Browse Source

🚨 convert all remaining hyperx strings to JSX

things might be broken! tested to the best of my abilities, fixed multiple issues on the way
Artur Paikin 7 years ago
parent
commit
276e730a85
36 changed files with 763 additions and 841 deletions
  1. 25 21
      src/plugins/Dashboard/ActionBrowseTagline.js
  2. 70 155
      src/plugins/Dashboard/Dashboard.js
  3. 70 57
      src/plugins/Dashboard/FileCard.js
  4. 106 110
      src/plugins/Dashboard/FileItem.js
  5. 9 10
      src/plugins/Dashboard/FileItemProgress.js
  6. 39 40
      src/plugins/Dashboard/FileList.js
  7. 55 48
      src/plugins/Dashboard/Tabs.js
  8. 52 52
      src/plugins/Dashboard/icons.js
  9. 46 23
      src/plugins/Dashboard/index.js
  10. 1 1
      src/plugins/DragDrop/index.js
  11. 10 10
      src/plugins/Dropbox/icons.js
  12. 5 8
      src/plugins/Dropbox/index.js
  13. 14 14
      src/plugins/Dummy.js
  14. 3 10
      src/plugins/GoogleDrive/index.js
  15. 1 1
      src/plugins/Informer.js
  16. 6 12
      src/plugins/Instagram/index.js
  17. 3 3
      src/plugins/ProgressBar.js
  18. 11 13
      src/plugins/Provider/view/AuthView.js
  19. 1 7
      src/plugins/Provider/view/Breadcrumb.js
  20. 4 6
      src/plugins/Provider/view/Breadcrumbs.js
  21. 20 29
      src/plugins/Provider/view/Browser.js
  22. 44 0
      src/plugins/Provider/view/Filter.js
  23. 3 7
      src/plugins/Provider/view/Loader.js
  24. 9 11
      src/plugins/Provider/view/Table.js
  25. 21 26
      src/plugins/Provider/view/TableRow.js
  26. 9 8
      src/plugins/Provider/view/index.js
  27. 80 93
      src/plugins/StatusBar/StatusBar.js
  28. 4 6
      src/plugins/Webcam/CameraIcon.js
  29. 16 22
      src/plugins/Webcam/CameraScreen.js
  30. 3 5
      src/plugins/Webcam/PermissionsScreen.js
  31. 9 11
      src/plugins/Webcam/RecordButton.js
  32. 2 4
      src/plugins/Webcam/RecordStartIcon.js
  33. 2 4
      src/plugins/Webcam/RecordStopIcon.js
  34. 5 7
      src/plugins/Webcam/SnapshotButton.js
  35. 4 6
      src/plugins/Webcam/WebcamIcon.js
  36. 1 1
      src/plugins/Webcam/index.js

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

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

+ 70 - 155
src/plugins/Dashboard/Dashboard.js

@@ -1,169 +1,84 @@
 const FileList = require('./FileList')
 const Tabs = require('./Tabs')
 const FileCard = require('./FileCard')
-const { isTouchDevice, toArray } = require('../../core/Utils')
+const classNames = require('classnames')
+const { isTouchDevice } = require('../../core/Utils')
 const { closeIcon } = require('./icons')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 // 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">
-
-        ${h(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>
+  )
+}

+ 70 - 57
src/plugins/Dashboard/FileCard.js

@@ -1,85 +1,98 @@
 const getFileTypeIcon = require('./getFileTypeIcon')
 const { checkIcon } = require('./icons')
+const { h, Component } = require('preact')
 
-const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
+module.exports = class FileCard extends Component {
+  constructor (props) {
+    super(props)
 
-module.exports = function fileCard (props) {
-  const file = props.fileCardFor ? props.files[props.fileCardFor] : false
-  const meta = {}
+    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" 
-                       type="text"
-                       data-name="name"
-                       value="${file.meta.name || ''}"
-                       placeholder="name"
-                       onkeyup=${tempStoreMetaOrSubmit}
-                       onkeydown=${tempStoreMetaOrSubmit}
-                       onkeypress=${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>
+  }
 }

+ 106 - 110
src/plugins/Dashboard/FileItem.js

@@ -8,10 +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')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 module.exports = function fileItem (props) {
   const file = props.file
@@ -40,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="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 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>
+    <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="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>`
-        : 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>
 }

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

@@ -1,6 +1,4 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 // http://codepen.io/Harkko/pen/rVxvNM
 // https://css-tricks.com/svg-line-animation-works/
@@ -12,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>
+  )
 }

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

@@ -1,47 +1,46 @@
 const FileItem = require('./FileItem')
 const ActionBrowseTagline = require('./ActionBrowseTagline')
 const { dashboardBgIcon } = require('./icons')
-
+const classNames = require('classnames')
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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">
-            ${h(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>
 }

+ 55 - 48
src/plugins/Dashboard/Tabs.js

@@ -1,64 +1,71 @@
 const ActionBrowseTagline = require('./ActionBrowseTagline')
 const { localIcon } = require('./icons')
 const { h, Component } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 class Tabs extends Component {
+  constructor (props) {
+    super(props)
+    this.handleClick = this.handleClick.bind(this)
+  }
+
+  handleClick (ev) {
+    this.input.click()
+  }
+
   render () {
     const isHidden = Object.keys(this.props.files).length === 0
+    const hasAcquirers = this.props.acquirers.length !== 0
 
-    if (this.props.acquirers.length === 0) {
-      return html`
-        <div class="UppyDashboardTabs" aria-hidden="${isHidden}">
-          <h3 class="UppyDashboardTabs-title">
-            ${h(ActionBrowseTagline, {
-              acquirers: this.props.acquirers,
-              handleInputChange: this.props.handleInputChange,
-              i18n: this.props.i18n
-            })}
-          </h3>
+    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) => this.input.click()}>
-              ${localIcon()}
-              <h5 class="UppyDashboardTab-name">${this.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 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>
+        {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>
-            <input class="UppyDashboard-input"
-                   hidden="true"
-                   aria-hidden="true" 
-                   tabindex="-1" 
-                   type="file" 
-                   name="files[]" 
-                   multiple="true"
-                   onchange=${this.props.handleInputChange} 
-                   ref=${(input) => { this.input = input }} />
           </li>
-          ${this.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=${() => this.props.showPanel(target.id)}>
-                ${target.icon()}
-                <h5 class="UppyDashboardTab-name">${target.name}</h5>
-              </button>
-            </li>`
-          })}
-        </ul>
-    </div>`
+        })}
+      </ul>
+    </div>
   }
 }
 

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

@@ -1,117 +1,117 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 // 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 = {

+ 46 - 23
src/plugins/Dashboard/index.js

@@ -4,7 +4,7 @@ const dragDrop = require('drag-drop')
 const Dashboard = 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')
 
@@ -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,7 +205,7 @@ 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()
@@ -223,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)
   }
@@ -243,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)
@@ -286,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({
@@ -313,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
@@ -414,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,
@@ -421,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,

+ 1 - 1
src/plugins/DragDrop/index.js

@@ -108,7 +108,7 @@ module.exports = class DragDrop extends Plugin {
   }
 
   render (state) {
-    const DragDropClass = `Uppy UppyTheme--default uppy-DragDrop-container ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}`
+    const DragDropClass = `uppy uppy-DragDrop-container ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}`
     const DragDropStyle = {
       width: this.opts.width,
       height: this.opts.height

+ 10 - 10
src/plugins/Dropbox/icons.js

@@ -1,14 +1,14 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
+  }
 }

+ 5 - 8
src/plugins/Dropbox/index.js

@@ -2,10 +2,7 @@ const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
 const icons = require('./icons')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 module.exports = class Dropbox extends Plugin {
   constructor (uppy, opts) {
@@ -13,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.

+ 14 - 14
src/plugins/Dummy.js

@@ -1,7 +1,5 @@
 const Plugin = require('../core/Plugin')
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 /**
  * Dummy
@@ -20,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)
   }
@@ -41,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) {
@@ -63,7 +61,9 @@ module.exports = class Dummy extends Plugin {
     }
 
     setTimeout(() => {
-      this.uppy.setState({dummy: {text: '!!!'}})
+      this.uppy.setState({
+        dummy: {text: '!!!'}
+      })
     }, 2000)
   }
 }

+ 3 - 10
src/plugins/GoogleDrive/index.js

@@ -1,10 +1,7 @@
 const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 module.exports = class GoogleDrive extends Plugin {
   constructor (uppy, opts) {
@@ -12,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',
@@ -29,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
@@ -79,7 +72,7 @@ module.exports = class GoogleDrive extends Plugin {
   }
 
   getItemIcon (item) {
-    return html`<img src=${item.iconLink}/>`
+    return <img src={item.iconLink} />
   }
 
   getItemSubList (item) {

+ 1 - 1
src/plugins/Informer.js

@@ -52,7 +52,7 @@ module.exports = class Informer extends Plugin {
     }
 
     return (
-      <div class="Uppy UppyInformer"
+      <div class="uppy uppy-Informer"
         style={style}
         aria-hidden={isHidden}>
         <p role="alert">

+ 6 - 12
src/plugins/Instagram/index.js

@@ -1,10 +1,7 @@
 const Plugin = require('../../core/Plugin')
 const Provider = require('../Provider')
 const View = require('../Provider/view')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 module.exports = class Instagram extends Plugin {
   constructor (uppy, opts) {
@@ -12,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',
@@ -31,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
@@ -83,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) {

+ 3 - 3
src/plugins/ProgressBar.js

@@ -28,9 +28,9 @@ module.exports = class ProgressBar extends Plugin {
   render (state) {
     const progress = state.totalProgress || 0
 
-    return <div class="UppyProgressBar" style={{ position: this.opts.fixed ? 'fixed' : 'initial' }}>
-      <div class="UppyProgressBar-inner" style={{ width: progress + '%' }} />
-      <div class="UppyProgressBar-percentage">{progress}</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>
   }
 

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

@@ -1,8 +1,5 @@
 const LoaderView = require('./Loader')
-
 const { h, Component } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 class AuthView extends Component {
   componentDidMount () {
@@ -10,23 +7,24 @@ class AuthView extends Component {
   }
 
   render () {
-    const demoLink = this.props.demo ? html`<button class="UppyProvider-authBtnDemo" onclick=${this.props.handleDemoAuth}>Proceed with Demo Account</button>` : null
-    const AuthBlock = () => html`
-      <div class="UppyProvider-auth">
-        <h1 class="UppyProvider-authTitle">Please authenticate with <span class="UppyProvider-authTitleName">${this.props.pluginName}</span><br> to select files</h1>
-        <button type="button" class="UppyProvider-authBtn" onclick=${this.props.handleAuth}>Connect to ${this.props.pluginName}</button>
-        ${demoLink}
+    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 html`
+    return (
       <div style="height: 100%;">
-        ${this.props.checkAuthInProgress
+        {this.props.checkAuthInProgress
           ? LoaderView()
           : AuthBlock()
         }
       </div>
-    `
+    )
   }
 }
 

+ 1 - 7
src/plugins/Provider/view/Breadcrumb.js

@@ -1,11 +1,5 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
 }

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

@@ -1,12 +1,10 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 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),
@@ -15,5 +13,5 @@ module.exports = (props) => {
         })
       }
     </ul>
-  `
+  )
 }

+ 20 - 29
src/plugins/Provider/view/Browser.js

@@ -1,9 +1,7 @@
 const Breadcrumbs = require('./Breadcrumbs')
+const Filter = require('./Filter')
 const Table = require('./Table')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 module.exports = (props) => {
   let filteredFolders = props.folders
@@ -14,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-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'
@@ -63,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>
+  }
+}

+ 3 - 7
src/plugins/Provider/view/Loader.js

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

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

@@ -1,25 +1,23 @@
-const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 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) {
@@ -37,7 +35,7 @@ module.exports = (props) => {
             columns: props.columns
           })
         })}
-        ${props.files.map(file => {
+        {props.files.map(file => {
           return Row({
             title: props.getItemName(file),
             type: 'file',
@@ -52,5 +50,5 @@ module.exports = (props) => {
         })}
       </tbody>
     </table>
-  `
+  )
 }

+ 21 - 26
src/plugins/Provider/view/TableRow.js

@@ -1,7 +1,5 @@
-const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 const cuid = require('cuid')
+const { h } = require('preact')
 
 module.exports = (props) => {
   const uniqueId = cuid()
@@ -14,38 +12,35 @@ module.exports = (props) => {
   }
 
   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()
+      return props.handleClick(ev)
     }
     props.handleCheckboxClick(ev)
   }
 
-  return html`
-    <tr class="BrowserTable-row" onclick=${handleItemClick}>
-      <td class="BrowserTable-column">
-        <div class="BrowserTable-checkbox">
+  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 ${props.title}"
-                 id=${uniqueId}
-                 ${props.isChecked
-                  ? { 'checked': true }
-                 : {}}
-                 ${props.isDisabled
-                  ? { 'disabled': true }
-                 : {}}
-                 onchange=${props.handleCheckboxClick}
-                 onkeyup=${stop}
-                 onkeydown=${stop}
-                 onkeypress=${stop} />
-          <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 ${props.title}" tabindex="0" onclick=${handleItemClick}>
-          ${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>
-  `
+  )
 }

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

@@ -202,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) {
@@ -426,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()

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

@@ -1,11 +1,8 @@
 const throttle = require('lodash.throttle')
-
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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})
@@ -79,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 || {}
 
@@ -108,101 +119,91 @@ 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-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) => {
@@ -213,32 +214,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>
 }

+ 4 - 6
src/plugins/Webcam/CameraIcon.js

@@ -1,10 +1,8 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
 }

+ 16 - 22
src/plugins/Webcam/CameraScreen.js

@@ -1,6 +1,4 @@
 const { h, Component } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 const SnapshotButton = require('./SnapshotButton')
 const RecordButton = require('./RecordButton')
 
@@ -9,21 +7,9 @@ function isModeAvailable (modes, mode) {
 }
 
 class CameraScreen extends Component {
-  constructor (props) {
-    super(props)
-    this.src = this.props.src || ''
-    this.shouldShowRecordButton = this.props.supportsRecording && (
-      isModeAvailable(this.props.modes, 'video-only') ||
-      isModeAvailable(this.props.modes, 'audio-only') ||
-      isModeAvailable(this.props.modes, 'video-audio')
-    )
-    this.shouldShowSnapshotButton = isModeAvailable(this.props.modes, 'picture')
-  }
-
   componentDidMount () {
     this.props.onFocus()
-    // const recordButton = el.querySelector('.UppyWebcam-recordButton')
-    // if (recordButton) recordButton.focus()
+    this.btnContainer.firstChild.focus()
   }
 
   componentWillUnmount () {
@@ -31,18 +17,26 @@ class CameraScreen extends Component {
   }
 
   render () {
-    return html`
+    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 (
       <div class="UppyWebcam-container">
         <div class="UppyWebcam-videoContainer">
-          <video class="UppyWebcam-video" autoplay muted src="${this.src}"></video>
+          <video class="UppyWebcam-video" autoplay muted src={this.props.src || ''} />
         </div>
-        <div class="UppyWebcam-buttonContainer">
-          ${this.shouldShowSnapshotButton ? SnapshotButton(this.props) : null}
-          ${this.shouldShowRecordButton ? RecordButton(this.props) : null}
+        <div class="UppyWebcam-buttonContainer" ref={(el) => { this.btnContainer = el }}>
+          {shouldShowSnapshotButton ? SnapshotButton(this.props) : null}
+          {' '}
+          {shouldShowRecordButton ? RecordButton(this.props) : null}
         </div>
-        <canvas class="UppyWebcam-canvas" style="display: none;"></canvas>
+        <canvas class="UppyWebcam-canvas" style="display: none;" />
       </div>
-    `
+    )
   }
 }
 

+ 3 - 5
src/plugins/Webcam/PermissionsScreen.js

@@ -1,13 +1,11 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
-  `
+  )
 }

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

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

+ 2 - 4
src/plugins/Webcam/RecordStartIcon.js

@@ -1,9 +1,7 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
 }

+ 2 - 4
src/plugins/Webcam/RecordStopIcon.js

@@ -1,9 +1,7 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
 }

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

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

+ 4 - 6
src/plugins/Webcam/WebcamIcon.js

@@ -1,12 +1,10 @@
 const { h } = require('preact')
-const hyperx = require('hyperx')
-const html = hyperx(h)
 
 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>
-  `
+  )
 }

+ 1 - 1
src/plugins/Webcam/index.js

@@ -44,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: {
@@ -87,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