Преглед на файлове

Converting Modal and everything else to components & shared state

Artur Paikin преди 9 години
родител
ревизия
7de611f936
променени са 7 файла, в които са добавени 394 реда и са изтрити 156 реда
  1. 38 1
      src/core/Core.js
  2. 36 37
      src/plugins/DragDrop.js
  3. 103 113
      src/plugins/Modal.js
  4. 211 0
      src/plugins/OldModal.js
  5. 2 2
      src/plugins/Plugin.js
  6. 3 2
      src/plugins/ProgressDrawer.js
  7. 1 1
      website/src/examples/modal/app.es6

+ 38 - 1
src/core/Core.js

@@ -42,7 +42,11 @@ export default class Core {
 
     this.defaultState = {
       selectedFiles: {},
-      uploadedFiles: {}
+      uploadedFiles: {},
+      modal: {
+        isVisible: false,
+        targets: {}
+      }
     }
 
     this.state = Object.assign({}, this.state, this.defaultState)
@@ -96,6 +100,39 @@ export default class Core {
       reader.readAsDataURL(file.data)
     }
 
+    // add new acquirer target to Modal
+    this.emitter.on('modal-add-target', (target) => {
+      const modal = Object.assign({}, this.state.modal)
+      modal.targets[target.id] = target
+      this.updateState({modal: modal})
+    })
+
+    this.emitter.on('modal-panel-show', (id) => {
+      const modal = Object.assign({}, this.state.modal)
+
+      // hide all panelSelectorPrefix
+      Object.keys(modal.targets).forEach((target) => {
+        modal.targets[target].isVisible = false
+      })
+
+      // then show this one
+      modal.targets[id].isVisible = true
+
+      this.updateState({modal: modal})
+    })
+
+    this.emitter.on('modal-open', () => {
+      const modal = Object.assign({}, this.state.modal)
+      modal.isVisible = true
+      this.updateState({modal: modal})
+    })
+
+    this.emitter.on('modal-close', () => {
+      const modal = Object.assign({}, this.state.modal)
+      modal.isVisible = false
+      this.updateState({modal: modal})
+    })
+
     // `reset` resets state to `defaultState`
     this.emitter.on('reset', () => {
       this.resetState()

+ 36 - 37
src/plugins/DragDrop.js

@@ -1,5 +1,6 @@
 import Utils from '../core/Utils'
 import Plugin from './Plugin'
+import yo from 'yo-yo'
 
 /**
  * Drag & Drop plugin
@@ -10,8 +11,8 @@ export default class DragDrop extends Plugin {
     super(core, opts)
     this.type = 'acquirer'
     this.name = 'Drag & Drop'
-    this.icon = `
-      <svg class="UppyModalTab-icon" width="28" height="28" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+    this.icon = yo`
+      <svg class="UppyModalTab-icon" width="28" height="28" viewBox="0 0 16 16">
         <path d="M15.982 2.97c0-.02 0-.02-.018-.037 0-.017-.017-.035-.035-.053 0 0 0-.018-.02-.018-.017-.018-.034-.053-.052-.07L13.19.123c-.017-.017-.034-.035-.07-.053h-.018c-.018-.017-.035-.017-.053-.034h-.02c-.017 0-.034-.018-.052-.018h-6.31a.415.415 0 0 0-.446.426V11.11c0 .25.196.446.445.446h8.89A.44.44 0 0 0 16 11.11V3.023c-.018-.018-.018-.035-.018-.053zm-2.65-1.46l1.157 1.157h-1.157V1.51zm1.78 9.157h-8V.89h5.332v2.22c0 .25.196.446.445.446h2.22v7.11z"/>
         <path d="M9.778 12.89H4V2.666a.44.44 0 0 0-.444-.445.44.44 0 0 0-.445.445v10.666c0 .25.197.445.446.445h6.222a.44.44 0 0 0 .444-.445.44.44 0 0 0-.444-.444z"/>
         <path d="M.444 16h6.223a.44.44 0 0 0 .444-.444.44.44 0 0 0-.443-.445H.89V4.89a.44.44 0 0 0-.446-.446A.44.44 0 0 0 0 4.89v10.666c0 .248.196.444.444.444z"/>
@@ -38,24 +39,31 @@ export default class DragDrop extends Plugin {
     this.handleInputChange = this.handleInputChange.bind(this)
   }
 
-  render () {
+  update (state) {
+    var newEl = this.render(state)
+    yo.update(this.el, newEl)
+  }
+
+  render (state) {
     // Another way not to render next/upload button — if Modal is used as a target
     const target = this.opts.target.name
-    return `
-      <form class="UppyDragDrop-inner">
-        <input class="UppyDragDrop-input"
-               id="UppyDragDrop-input"
-               type="file"
-               name="files[]"
-               multiple />
-        <label class="UppyDragDrop-label" for="UppyDragDrop-input">
-          <strong>${this.core.i18n('chooseFile')}</strong>
-          <span class="UppyDragDrop-dragText">${this.core.i18n('orDragDrop')}</span>.
-        </label>
-        ${!this.core.opts.autoProceed && target !== 'Modal'
-          ? `<button class="UppyDragDrop-uploadBtn UppyNextBtn" type="submit">${this.core.i18n('upload')}</button>`
-          : ''}
-      </form>
+    return yo`
+      <div class="UppyDragDrop ${this.isDragDropSupported ? 'is-dragdrop-supported' : ''}">
+        <form class="UppyDragDrop-inner">
+          <input class="UppyDragDrop-input"
+                 id="UppyDragDrop-input"
+                 type="file"
+                 name="files[]"
+                 multiple="true">
+          <label class="UppyDragDrop-label" for="UppyDragDrop-input">
+            <strong>${this.core.i18n('chooseFile')}</strong>
+            <span class="UppyDragDrop-dragText">${this.core.i18n('orDragDrop')}</span>.
+          </label>
+          ${!this.core.opts.autoProceed && target !== 'Modal'
+            ? `<button class="UppyDragDrop-uploadBtn UppyNextBtn" type="submit">${this.core.i18n('upload')}</button>`
+            : ''}
+        </form>
+      </div>
     `
   }
 
@@ -145,14 +153,6 @@ export default class DragDrop extends Plugin {
       plugin: this,
       acquiredFiles: newFiles
     })
-
-    // this.core.emitter.emit('next')
-    //
-    // newFiles.forEach((newFile) => {
-    //   this.files.push(newFile)
-    // })
-
-    // return this.result(files)
   }
 
   handleInputChange () {
@@ -165,11 +165,9 @@ export default class DragDrop extends Plugin {
       acquiredFiles: newFiles
     })
 
-    newFiles.forEach((newFile) => {
-      this.files.push(newFile)
-    })
-
-    // return this.result(files)
+    // newFiles.forEach((newFile) => {
+    //   this.files.push(newFile)
+    // })
   }
 
   result () {
@@ -202,19 +200,20 @@ export default class DragDrop extends Plugin {
   }
 
   install () {
+    this.el = this.render(this.core.state)
     const caller = this
-    this.target = this.getTarget(this.opts.target, caller)
-    this.container = document.querySelector(this.target)
-    this.container.innerHTML = this.render()
+
+    this.target = this.getTarget(this.opts.target, caller, this.el)
+    document.querySelector(this.target).appendChild(this.el)
 
     // Set selectors
     this.dropzone = document.querySelector(`${this.target} .UppyDragDrop-inner`)
     this.input = document.querySelector(`${this.target} .UppyDragDrop-input`)
 
     this.container.classList.add('UppyDragDrop')
-    if (this.isDragDropSupported) {
-      this.container.classList.add('is-dragdrop-supported')
-    }
+    // if (this.isDragDropSupported) {
+    //   this.container.classList.add('is-dragdrop-supported')
+    // }
   }
 
   run (results) {

+ 103 - 113
src/plugins/Modal.js

@@ -1,5 +1,6 @@
 import Plugin from './Plugin'
-import Utils from '../core/Utils'
+// import Utils from '../core/Utils'
+import yo from 'yo-yo'
 
 /**
  * Modal
@@ -20,20 +21,19 @@ export default class Modal extends Plugin {
       panelSelectorPrefix: 'UppyModalContent-panel'
     }
 
-    this.tabPanels = []
-
-    this.isModalVisible = false
-
     // merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
-    this.initTabs = this.initTabs.bind(this)
     this.hideModal = this.hideModal.bind(this)
     this.showModal = this.showModal.bind(this)
-    this.install = this.install.bind(this)
   }
 
-  prepareTarget (callerPlugin) {
+  update (state) {
+    var newEl = this.render(state)
+    yo.update(this.el, newEl)
+  }
+
+  prepareTarget (callerPlugin, el) {
     const callerPluginId = callerPlugin.constructor.name
     const callerPluginName = callerPlugin.name || callerPluginId
     const callerPluginIcon = callerPlugin.icon || this.opts.defaultTabIcon
@@ -44,35 +44,15 @@ export default class Modal extends Plugin {
       case 'presenter':
         return '.UppyModal-presenter'
       case 'acquirer':
-
-        // add tab panel, where plugin will render
-        const modalContent = document.querySelector('.UppyModalContent')
-        const nodeForPlugin = document.createElement('div')
-
-        modalContent.appendChild(nodeForPlugin)
-        nodeForPlugin.outerHTML = `
-          <div class="UppyModalContent-panel ${this.opts.panelSelectorPrefix}--${callerPluginId}"
-               role="tabpanel"
-               aria-hidden="true">
-          </div>`
-
-        // add tab switch button
-        const modalTabs = document.querySelector('.UppyModalTabs')
-        const modalTab = document.createElement('div')
-
-        modalTabs.appendChild(modalTab)
-        modalTab.outerHTML = `
-          <li class="UppyModalTab">
-            <button class="UppyModalTab-btn"
-                    role="tab"
-                    aria-controls="${callerPluginId}"
-                    data-open="${this.opts.panelSelectorPrefix}--${callerPluginId}">
-              ${callerPluginIcon}
-              <span class="UppyModalTab-name">${callerPluginName}</span>
-            </button>
-          </li>`
-
+        this.core.emitter.emit('modal-add-target', {
+          id: callerPluginId,
+          name: callerPluginName,
+          icon: callerPluginIcon,
+          el: el,
+          isVisible: false
+        })
         return `.${this.opts.panelSelectorPrefix}--${callerPluginId}`
+
       default:
         let msg = 'Error: Modal can only be used by plugins of types: acquirer, progressindicator'
         this.core.log(msg)
@@ -80,70 +60,87 @@ export default class Modal extends Plugin {
     }
   }
 
-  allDone () {
-    this.hideAllTabPanels()
-  }
-
-  render () {
+  render (state) {
     // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
 
-    return `
-      <div class="UppyModal"
-           aria-hidden="true"
-           aria-labelledby="modalTitle"
-           aria-describedby="modalDescription"
-           role="dialog">
-        <div class="UppyModal-overlay js-UppyModal-close" tabindex="-1"></div>
-        <div class="UppyModal-inner">
-          <button class="UppyModal-close js-UppyModal-close" title="Close Uppy modal">×</button>
-
-          <ul class="UppyModalTabs" role="tablist"></ul>
-
-          <div class="UppyModalContent">
-            <div class="UppyModal-presenter"></div>
-            <div class="UppyModal-progress">
-              <div class="UppyModal-progressBarContainer"></div>
-            </div>
+    const modalTargets = state.modal.targets
+
+    return yo`<div class="UppyModal"
+                   ${state.modal.isVisible ? '' : 'aria-hidden'}
+                   aria-labelledby="modalTitle"
+                   aria-describedby="modalDescription"
+                   role="dialog">
+      <div class="UppyModal-overlay js-UppyModal-close" tabindex="-1"></div>
+      <div class="UppyModal-inner">
+        <button class="UppyModal-close js-UppyModal-close" title="Close Uppy modal">×</button>
+        <ul class="UppyModalTabs" role="tablist">
+          ${Object.keys(modalTargets).map((target) => {
+            return yo`<li class="UppyModalTab">
+              <button class="UppyModalTab-btn"
+                      role="tab"
+                      aria-controls="${modalTargets[target].id}"
+                      data-open="${this.opts.panelSelectorPrefix}--${modalTargets[target].id}"
+                      onclick=${() => {
+                        this.showTabPanel(modalTargets[target].id)
+                      }}}>
+                ${modalTargets[target].icon}
+                <span class="UppyModalTab-name">${modalTargets[target].name}</span>
+              </button>
+            </li>`
+          })}
+        </ul>
+
+        <div class="UppyModalContent">
+          <div class="UppyModal-presenter"></div>
+          <div class="UppyModal-progress">
+            <div class="UppyModal-progressBarContainer"></div>
           </div>
-
+          ${Object.keys(modalTargets).map((target) => {
+            return yo`<div class="UppyModalContent-panel ${this.opts.panelSelectorPrefix}--${modalTargets[target].id}"
+                 role="tabpanel"
+                 ${state.modal.isVisible ? '' : 'aria-hidden'}>
+                 ${modalTargets[target].el}
+            </div>`
+          })}
         </div>
-      </div>
-    `
-  }
 
-  hideModal () {
-    this.isModalVisible = false
-    this.modalEl.setAttribute('aria-hidden', 'true')
-    this.core.emitter.emit('reset')
+      </div>
+    </div>`
   }
 
-  showModal () {
-    this.isModalVisible = true
-    this.modalEl.removeAttribute('aria-hidden')
-    this.initTabs()
+  showTabPanel (id) {
+    this.core.emitter.emit('modal-panel-show', id)
   }
 
-  hideAllTabPanels () {
-    this.tabPanels.forEach((tabPanel) => tabPanel.setAttribute('aria-hidden', true))
-    this.tabs.forEach((tab) => tab.removeAttribute('aria-selected'))
+  hideModal () {
+    this.core.emitter.emit('modal-close')
   }
 
-  showTabPanel (pluginSelector, pluginName) {
-    this.hideAllTabPanels()
-
-    const tab = document.querySelector(`[aria-controls="${pluginName}"]`)
-    const tabPanel = document.querySelector(`.${pluginSelector}`)
-
-    tabPanel.removeAttribute('aria-hidden')
-    tab.setAttribute('aria-selected', 'true')
-
-    this.core.log(pluginName)
-    this.core.getPlugin(pluginName).focus()
+  showModal () {
+    this.core.emitter.emit('modal-open')
   }
+  //
+  // hideAllTabPanels () {
+  //   this.tabPanels.forEach((tabPanel) => tabPanel.setAttribute('aria-hidden', true))
+  //   this.tabs.forEach((tab) => tab.removeAttribute('aria-selected'))
+  // }
+  //
+  // showTabPanel (pluginSelector, pluginName) {
+  //   this.hideAllTabPanels()
+  //
+  //   const tab = document.querySelector(`[aria-controls="${pluginName}"]`)
+  //   const tabPanel = document.querySelector(`.${pluginSelector}`)
+  //
+  //   tabPanel.removeAttribute('aria-hidden')
+  //   tab.setAttribute('aria-selected', 'true')
+  //
+  //   this.core.log(pluginName)
+  //   this.core.getPlugin(pluginName).focus()
+  // }
 
   events () {
     // Listen for allDone event to close all tabs
-    this.core.emitter.on('allDone', () => this.allDone())
+    // this.core.emitter.on('allDone', () => this.allDone())
 
     // this.core.emitter.on('file-add', (data) => {
     //   this.nextButton.classList.add('is-active')
@@ -170,42 +167,35 @@ export default class Modal extends Plugin {
     })
   }
 
-  initTabs () {
-    // Get all tab buttons and loop through them, to determine which
-    // tabPanel they trigger, set events
-    this.tabs = Utils.qsa('.UppyModalTab-btn')
-    this.tabs.forEach((tab) => {
-      const pluginSelector = tab.getAttribute('data-open')
-      const pluginName = tab.getAttribute('aria-controls')
-      const tabPanel = document.querySelector(`.${pluginSelector}`)
-      this.tabPanels.push(tabPanel)
-
-      tab.addEventListener('click', (event) => {
-        event.preventDefault()
-        this.showTabPanel(pluginSelector, pluginName)
-      })
-    })
-
-    // Select first tab right away
-    this.tabs[0].click()
-  }
+  // initTabs () {
+  //   // Get all tab buttons and loop through them, to determine which
+  //   // tabPanel they trigger, set events
+  //   this.tabs = Utils.qsa('.UppyModalTab-btn')
+  //   this.tabs.forEach((tab) => {
+  //     const pluginSelector = tab.getAttribute('data-open')
+  //     const pluginName = tab.getAttribute('aria-controls')
+  //     const tabPanel = document.querySelector(`.${pluginSelector}`)
+  //     this.tabPanels.push(tabPanel)
+  //
+  //     tab.addEventListener('click', (event) => {
+  //       event.preventDefault()
+  //       this.showTabPanel(pluginSelector, pluginName)
+  //     })
+  //   })
+  //
+  //   // Select first tab right away
+  //   this.tabs[0].click()
+  // }
 
   install () {
-    const node = document.createElement('div')
-    document.body.appendChild(node)
-    node.outerHTML = this.render()
-    this.modalEl = document.querySelector('.UppyModal')
+    this.el = this.render(this.core.state)
+    document.body.appendChild(this.el)
 
     // Add events for opening and closing the modal
     // const hideModalTrigger = Utils.qsa('.js-UppyModal-close')
     this.showModalTrigger = document.querySelector(this.opts.trigger)
     this.showModalTrigger.addEventListener('click', this.showModal)
 
-    // When `next` (upload) button is clicked, emit `next` event,
-    // so that plugins can proceed to the next stage
-    // this.nextButton = document.querySelector('.UppyModal-next')
-    // this.nextButton.addEventListener('click', () => this.core.emitter.emit('next'))
-
     this.events()
   }
 }

+ 211 - 0
src/plugins/OldModal.js

@@ -0,0 +1,211 @@
+import Plugin from './Plugin'
+import Utils from '../core/Utils'
+
+/**
+ * Modal
+ *
+ */
+export default class Modal extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'orchestrator'
+
+    // set default options
+    const defaultOptions = {
+      defaultTabIcon: `
+        <svg class="UppyModalTab-icon" width="28" height="28" viewBox="0 0 101 58" xmlns="http://www.w3.org/2000/svg">
+          <path d="M17.582.3L.915 41.713l32.94 13.295L17.582.3zm83.333 41.414L67.975 55.01 84.25.3l16.665 41.414zm-48.998 5.403L63.443 35.59H38.386l11.527 11.526v5.905l-3.063 3.32 1.474 1.36 2.59-2.806 2.59 2.807 1.475-1.357-3.064-3.32v-5.906zm16.06-26.702c-3.973 0-7.194-3.22-7.194-7.193 0-3.973 3.222-7.193 7.193-7.193 3.974 0 7.193 3.22 7.193 7.19 0 3.974-3.22 7.194-7.195 7.194zM70.48 8.682c-.737 0-1.336.6-1.336 1.337 0 .736.6 1.335 1.337 1.335.738 0 1.338-.598 1.338-1.336 0-.74-.6-1.338-1.338-1.338zM33.855 20.415c-3.973 0-7.193-3.22-7.193-7.193 0-3.973 3.22-7.193 7.195-7.193 3.973 0 7.192 3.22 7.192 7.19 0 3.974-3.22 7.194-7.192 7.194zM36.36 8.682c-.737 0-1.336.6-1.336 1.337 0 .736.6 1.335 1.337 1.335.738 0 1.338-.598 1.338-1.336 0-.74-.598-1.338-1.337-1.338z"/>
+        </svg>
+      `,
+      panelSelectorPrefix: 'UppyModalContent-panel'
+    }
+
+    this.tabPanels = []
+
+    this.isModalVisible = false
+
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
+
+    this.initTabs = this.initTabs.bind(this)
+    this.hideModal = this.hideModal.bind(this)
+    this.showModal = this.showModal.bind(this)
+    this.install = this.install.bind(this)
+  }
+
+  prepareTarget (callerPlugin) {
+    const callerPluginId = callerPlugin.constructor.name
+    const callerPluginName = callerPlugin.name || callerPluginId
+    const callerPluginIcon = callerPlugin.icon || this.opts.defaultTabIcon
+
+    switch (callerPlugin.type) {
+      case 'progressindicator':
+        return '.UppyModal-progressBarContainer'
+      case 'presenter':
+        return '.UppyModal-presenter'
+      case 'acquirer':
+
+        // add tab panel, where plugin will render
+        const modalContent = document.querySelector('.UppyModalContent')
+        const nodeForPlugin = document.createElement('div')
+
+        modalContent.appendChild(nodeForPlugin)
+        nodeForPlugin.outerHTML = `
+          <div class="UppyModalContent-panel ${this.opts.panelSelectorPrefix}--${callerPluginId}"
+               role="tabpanel"
+               aria-hidden="true">
+          </div>`
+
+        // add tab switch button
+        const modalTabs = document.querySelector('.UppyModalTabs')
+        const modalTab = document.createElement('div')
+
+        modalTabs.appendChild(modalTab)
+        modalTab.outerHTML = `
+          <li class="UppyModalTab">
+            <button class="UppyModalTab-btn"
+                    role="tab"
+                    aria-controls="${callerPluginId}"
+                    data-open="${this.opts.panelSelectorPrefix}--${callerPluginId}">
+              ${callerPluginIcon}
+              <span class="UppyModalTab-name">${callerPluginName}</span>
+            </button>
+          </li>`
+
+        return `.${this.opts.panelSelectorPrefix}--${callerPluginId}`
+      default:
+        let msg = 'Error: Modal can only be used by plugins of types: acquirer, progressindicator'
+        this.core.log(msg)
+        break
+    }
+  }
+
+  allDone () {
+    this.hideAllTabPanels()
+  }
+
+  render () {
+    // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
+
+    return `
+      <div class="UppyModal"
+           aria-hidden="true"
+           aria-labelledby="modalTitle"
+           aria-describedby="modalDescription"
+           role="dialog">
+        <div class="UppyModal-overlay js-UppyModal-close" tabindex="-1"></div>
+        <div class="UppyModal-inner">
+          <button class="UppyModal-close js-UppyModal-close" title="Close Uppy modal">×</button>
+
+          <ul class="UppyModalTabs" role="tablist"></ul>
+
+          <div class="UppyModalContent">
+            <div class="UppyModal-presenter"></div>
+            <div class="UppyModal-progress">
+              <div class="UppyModal-progressBarContainer"></div>
+            </div>
+          </div>
+
+        </div>
+      </div>
+    `
+  }
+
+  hideModal () {
+    this.isModalVisible = false
+    this.modalEl.setAttribute('aria-hidden', 'true')
+    this.core.emitter.emit('reset')
+  }
+
+  showModal () {
+    this.isModalVisible = true
+    this.modalEl.removeAttribute('aria-hidden')
+    this.initTabs()
+  }
+
+  hideAllTabPanels () {
+    this.tabPanels.forEach((tabPanel) => tabPanel.setAttribute('aria-hidden', true))
+    this.tabs.forEach((tab) => tab.removeAttribute('aria-selected'))
+  }
+
+  showTabPanel (pluginSelector, pluginName) {
+    this.hideAllTabPanels()
+
+    const tab = document.querySelector(`[aria-controls="${pluginName}"]`)
+    const tabPanel = document.querySelector(`.${pluginSelector}`)
+
+    tabPanel.removeAttribute('aria-hidden')
+    tab.setAttribute('aria-selected', 'true')
+
+    this.core.log(pluginName)
+    this.core.getPlugin(pluginName).focus()
+  }
+
+  events () {
+    // Listen for allDone event to close all tabs
+    this.core.emitter.on('allDone', () => this.allDone())
+
+    // this.core.emitter.on('file-add', (data) => {
+    //   this.nextButton.classList.add('is-active')
+    //
+    //   const files = Object.keys(this.core.state.selectedFiles)
+    //   const selectedFileCount = files.length
+    //   this.nextButton.innerHTML = this.core.i18n('uploadFiles', {'smart_count': selectedFileCount})
+    // })
+
+    // this.core.emitter.on('reset', () => this.nextButton.classList.remove('is-active'))
+
+    // Close the Modal on esc key press
+    document.body.addEventListener('keyup', (event) => {
+      if (event.keyCode === 27) {
+        this.hideModal()
+      }
+    })
+
+    // Close on click outside modal or close buttons
+    document.addEventListener('click', (e) => {
+      if (e.target.classList.contains('js-UppyModal-close')) {
+        this.hideModal()
+      }
+    })
+  }
+
+  initTabs () {
+    // Get all tab buttons and loop through them, to determine which
+    // tabPanel they trigger, set events
+    this.tabs = Utils.qsa('.UppyModalTab-btn')
+    this.tabs.forEach((tab) => {
+      const pluginSelector = tab.getAttribute('data-open')
+      const pluginName = tab.getAttribute('aria-controls')
+      const tabPanel = document.querySelector(`.${pluginSelector}`)
+      this.tabPanels.push(tabPanel)
+
+      tab.addEventListener('click', (event) => {
+        event.preventDefault()
+        this.showTabPanel(pluginSelector, pluginName)
+      })
+    })
+
+    // Select first tab right away
+    this.tabs[0].click()
+  }
+
+  install () {
+    const node = document.createElement('div')
+    document.body.appendChild(node)
+    node.outerHTML = this.render()
+    this.modalEl = document.querySelector('.UppyModal')
+
+    // Add events for opening and closing the modal
+    // const hideModalTrigger = Utils.qsa('.js-UppyModal-close')
+    this.showModalTrigger = document.querySelector(this.opts.trigger)
+    this.showModalTrigger.addEventListener('click', this.showModal)
+
+    // When `next` (upload) button is clicked, emit `next` event,
+    // so that plugins can proceed to the next stage
+    // this.nextButton = document.querySelector('.UppyModal-next')
+    // this.nextButton.addEventListener('click', () => this.core.emitter.emit('next'))
+
+    this.events()
+  }
+}

+ 2 - 2
src/plugins/Plugin.js

@@ -24,7 +24,7 @@ export default class Plugin {
    * @param {String|Object} target
    *
    */
-  getTarget (target, callerPlugin) {
+  getTarget (target, callerPlugin, el) {
     if (typeof target === 'string') {
       // this.core.log('string is a target')
       return target
@@ -33,7 +33,7 @@ export default class Plugin {
 
       let targetPlugin = this.core.getPlugin(target.name)
 
-      return targetPlugin.prepareTarget(callerPlugin)
+      return targetPlugin.prepareTarget(callerPlugin, el)
     }
   }
 

+ 3 - 2
src/plugins/ProgressDrawer.js

@@ -39,8 +39,9 @@ export default class ProgressDrawer extends Plugin {
           <polygon points="2.836,14.708 5.665,11.878 13.415,19.628 26.334,6.712 29.164,9.54 13.415,25.288 "></polygon>
         </svg>`
 
-      return yo`<li class="UppyProgressDrawer-item ${isUploaded ? 'is-uploaded' : ''}">
-        <img class="UppyProgressDrawer-itemIcon" src="${file.preview}">
+      return yo`<li class="UppyProgressDrawer-item ${isUploaded ? 'is-uploaded' : ''}"
+                    title="${file.name}">
+        <img class="UppyProgressDrawer-itemIcon" alt="${file.name}" src="${file.preview}">
         <div class="UppyProgressDrawer-itemInner">
           <span class="UppyProgressDrawer-itemProgress"
                 style="width: ${file.progress}%"></span>

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

@@ -7,7 +7,7 @@ uppy
   .use(Modal, {trigger: '#uppyModalOpener'})
   // .use(ProgressBar, {target: Modal})
   .use(DragDrop, {target: Modal})
-  .use(GoogleDrive, {target: Modal})
+  // .use(GoogleDrive, {target: Modal})
   // .use(Dummy, {target: Modal})
   // .use(Present, {target: Modal})
   .use(Tus10, {endpoint: 'http://master.tus.io:8080/files/'})