Artur Paikin 9 vuotta sitten
vanhempi
commit
377e3951be

+ 10 - 10
CHANGELOG.md

@@ -9,32 +9,32 @@ Please add your entries in the form of:
 
 
 Work not started yet
 Work not started yet
 
 
+- [ ] modal: Add basic Modal plugin that can be used as a target
 - [ ] google: Add basic Google Drive plugin example
 - [ ] google: Add basic Google Drive plugin example
 - [ ] instagram: Add basic Instagram plugin example (#21)
 - [ ] instagram: Add basic Instagram plugin example (#21)
 - [ ] presets: Add basic preset that mimics Transloadit's jQuery plugin (#28)
 - [ ] presets: Add basic preset that mimics Transloadit's jQuery plugin (#28)
+- [ ] tus: Add support tus 1.0 uploading capabilities (#3)
 - [ ] core: Apply plugins when DOM elements aren't static (#25)
 - [ ] core: Apply plugins when DOM elements aren't static (#25)
+- [ ] core: Create a progressbar/spinner/etc plugin (#18)
 - [ ] multipart: Write an acceptance test for the Multipart example via Saucelabs (#2, #23, @hedgerh)
 - [ ] multipart: Write an acceptance test for the Multipart example via Saucelabs (#2, #23, @hedgerh)
 - [ ] test: setup an HTML page with all sorts of crazy styles, resets & bootstrap to see what brakes Uppy
 - [ ] test: setup an HTML page with all sorts of crazy styles, resets & bootstrap to see what brakes Uppy
-- [ ] tus: Improve tus uploading capabilities (#3 @kvz)
 
 
 ## 0.0.2 (Unreleased, work in progress)
 ## 0.0.2 (Unreleased, work in progress)
 
 
+- [ ] meta: Create an Uppy logo (@vvolfy)
 - [ ] core: Decide on good names for `cdn` vs `npm` builds and rename all-the-things
 - [ ] core: Decide on good names for `cdn` vs `npm` builds and rename all-the-things
 - [ ] server: Add a deploy target for uppy-server so we can use it in demos (#39, @kvz)
 - [ ] server: Add a deploy target for uppy-server so we can use it in demos (#39, @kvz)
-- [ ] server: Create a (barely) working uppy-server (#39, @hedgerh)
-- [ ] core: Create a progressbar/spinner/etc plugin (#18)
-- [ ] modal: Add basic Modal plugin that can be used as a target (@hedgerh)
-- [x] tus: Add basic support tus 1.0 uploading capabilities (#3 @kvz)
-- [x] dragdrop: Use templates, autoUpload setting, show progress (@arturi)
-- [x] meta: Implement playground to test things in, templates in this case (@arturi)
-- [x] core: Implement es6 templating (@arturi, @hedgerh)
-- [ ] core: Improve on `_i18n` support, add tests (#47, @arturi)
+- [x] server: Create a (barely) working uppy-server (#39, @hedgerh)
+- [x] core: implement a non-blocking `install` method (for Progressbar, for example)  (@arturi, @kvz)
+- [x] dragdrop: Use templates, autoProceed setting, show progress (#50, #18, @arturi)
+- [x] meta: Implement playground to test things in, templates in this case
+- [x] core: Implement ejs or es6 templating (@arturi, @hedgerh)
+- [x] core: Improve on `_i18n` support, add tests (#47, @arturi)
 - [x] buildsystem: Use parallelshell and tweak browserify to work with templates (@arturi)
 - [x] buildsystem: Use parallelshell and tweak browserify to work with templates (@arturi)
 - [x] docs: Fix build-documentation.js crashes, add more docs to Utils and Translator (@arturi, @kvz)
 - [x] docs: Fix build-documentation.js crashes, add more docs to Utils and Translator (@arturi, @kvz)
 - [x] core: Integrate eslint in our build procedure and make Travis fail on errors found in our examples, Core and Plugins, such as `> 100` char lines (@kvz)
 - [x] core: Integrate eslint in our build procedure and make Travis fail on errors found in our examples, Core and Plugins, such as `> 100` char lines (@kvz)
 - [x] core: Add basic i18n support via `core.translate()` and locale loading (#47, @arturi)
 - [x] core: Add basic i18n support via `core.translate()` and locale loading (#47, @arturi)
 - [x] website: Fix Uppy deploys (postcss-svg problem) (@arturi, @kvz)
 - [x] website: Fix Uppy deploys (postcss-svg problem) (@arturi, @kvz)
-- [ ] meta: Create an Uppy logo (@vvolfy)
 
 
 ## 0.0.1 (December 20, 2015)
 ## 0.0.1 (December 20, 2015)
 
 

+ 2 - 0
package.json

@@ -70,6 +70,8 @@
     "zuul": "3.7.2"
     "zuul": "3.7.2"
   },
   },
   "dependencies": {
   "dependencies": {
+    "event-emitter": "^0.3.4",
+    "superagent": "1.5.0",
     "tus-js-client": "1.1.3"
     "tus-js-client": "1.1.3"
   }
   }
 }
 }

+ 4 - 3
src/components/dragdrop.js

@@ -1,14 +1,13 @@
 export default (context) => {
 export default (context) => {
-  return `<form class="UppyDragDrop-form"
+  return `<form class="UppyDragDrop-inner"
       method="post"
       method="post"
       action="${context.endpoint}"
       action="${context.endpoint}"
       enctype="multipart/form-data">
       enctype="multipart/form-data">
-    <img class="UppyDragDrop-puppy" src="/images/uppy.svg" />
+    <img class="UppyDragDrop-puppy" src="/images/uppy.svg">
     <input class="UppyDragDrop-input"
     <input class="UppyDragDrop-input"
            id="UppyDragDrop-input"
            id="UppyDragDrop-input"
            type="file"
            type="file"
            name="files[]"
            name="files[]"
-           data-multiple-caption="{count} files selected"
            multiple />
            multiple />
     <label class="UppyDragDrop-label" for="UppyDragDrop-input">
     <label class="UppyDragDrop-label" for="UppyDragDrop-input">
       <strong>${context.chooseFile}</strong>
       <strong>${context.chooseFile}</strong>
@@ -17,6 +16,8 @@ export default (context) => {
   ${!context.showUploadBtn
   ${!context.showUploadBtn
     ? `<button class="UppyDragDrop-uploadBtn" type="submit">${context.upload}</button>`
     ? `<button class="UppyDragDrop-uploadBtn" type="submit">${context.upload}</button>`
     : ''}
     : ''}
+  <div class="UppyProgress"></div>
   <div class="UppyDragDrop-status"></div>
   <div class="UppyDragDrop-status"></div>
+  <div class="UppyDragDrop-progress"></div>
 </form>`
 </form>`
 }
 }

+ 45 - 17
src/core/Core.js

@@ -1,5 +1,6 @@
 import Utils from '../core/Utils'
 import Utils from '../core/Utils'
 import Translator from '../core/Translator'
 import Translator from '../core/Translator'
+import ee from 'event-emitter'
 
 
 /**
 /**
  * Main Uppy core
  * Main Uppy core
@@ -11,14 +12,16 @@ export default class Core {
     // set default options
     // set default options
     const defaultOptions = {
     const defaultOptions = {
       // load English as the default locale
       // load English as the default locale
-      locale: require('../locale/en_US.js')
+      locale: require('../locale/en_US.js'),
+      autoProceed: false,
+      debug: false
     }
     }
 
 
     // Merge default options with the ones set by user
     // Merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
     this.opts = Object.assign({}, defaultOptions, opts)
 
 
     // Dictates in what order different plugin types are ran:
     // Dictates in what order different plugin types are ran:
-    this.types = [ 'presetter', 'selecter', 'uploader' ]
+    this.types = [ 'presetter', 'progress', 'selecter', 'uploader' ]
 
 
     this.type = 'core'
     this.type = 'core'
 
 
@@ -27,15 +30,18 @@ export default class Core {
 
 
     this.translator = new Translator({locale: this.opts.locale})
     this.translator = new Translator({locale: this.opts.locale})
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18n = this.translator.translate.bind(this.translator)
-    console.log(this.i18n('filesChosen', {smart_count: 3}))
+    // console.log(this.i18n('filesChosen', {smart_count: 3}))
+
+    // Set up an event EventEmitter
+    this.emitter = ee()
   }
   }
 
 
 /**
 /**
  * Registers a plugin with Core
  * Registers a plugin with Core
  *
  *
  * @param {Class} Plugin object
  * @param {Class} Plugin object
- * @param {object} options object that will be passed to Plugin later
- * @return {object} self for chaining
+ * @param {Object} options object that will be passed to Plugin later
+ * @return {Object} self for chaining
  */
  */
   use (Plugin, opts) {
   use (Plugin, opts) {
     // Instantiate
     // Instantiate
@@ -59,6 +65,17 @@ export default class Core {
     return this
     return this
   }
   }
 
 
+/**
+ * Logs stuff to console, only if `debug` is set to true. Silent in production.
+ *
+ * @return {String|Object} to log
+ */
+  log (msg) {
+    if (this.opts.debug) {
+      console.log(`DEBUG LOG: ${msg}`)
+    }
+  }
+
 /**
 /**
  * Runs all plugins of the same type in parallel
  * Runs all plugins of the same type in parallel
  *
  *
@@ -66,13 +83,13 @@ export default class Core {
  * @param {array} files
  * @param {array} files
  * @return {Promise} of all methods
  * @return {Promise} of all methods
  */
  */
-  runType (type, files) {
+  runType (type, method, files) {
     const methods = this.plugins[type].map(
     const methods = this.plugins[type].map(
-      plugin => plugin.run(files)
+      plugin => plugin[method](files)
     )
     )
 
 
     return Promise.all(methods)
     return Promise.all(methods)
-      .catch((error) => console.error(error))
+      .catch(error => console.error(error))
   }
   }
 
 
 /**
 /**
@@ -81,18 +98,29 @@ export default class Core {
  */
  */
   run () {
   run () {
     console.log({
     console.log({
-      class: 'Core',
+      class: this.constructor.name,
       method: 'run'
       method: 'run'
     })
     })
 
 
-    // First we select only plugins of current type,
-    // then create an array of runType methods of this plugins
-    let typeMethods = this.types.filter(type => {
-      return this.plugins[type]
-    }).map(type => this.runType.bind(this, type))
+    // Forse `autoProceed` option to false if there are multiple selector Plugins active
+    if (this.plugins.selecter && this.plugins.selecter.length > 1) {
+      this.opts.autoProceed = false
+    }
 
 
-    Utils.promiseWaterfall(typeMethods)
-      .then((result) => console.log(result))
-      .catch((error) => console.error(error))
+    // Each Plugin can have `run` and/or `install` methods.
+    // `install` adds event listeners and does some non-blocking work, useful for `progress`,
+    // `run` waits for the previous step to finish (user selects files) before proceeding
+    return ['install', 'run'].forEach(method => {
+      // First we select only plugins of current type,
+      // then create an array of runType methods of this plugins
+      const typeMethods = this.types.filter(type => {
+        return this.plugins[type]
+      }).map(type => this.runType.bind(this, type, method))
+
+      // Run waterfall of typeMethods
+      return Utils.promiseWaterfall(typeMethods)
+        .then(result => result)
+        .catch(error => console.error(error))
+    })
   }
   }
 }
 }

+ 3 - 3
src/core/Utils.js

@@ -15,9 +15,9 @@
  */
  */
 function promiseWaterfall (methods) {
 function promiseWaterfall (methods) {
   const [resolvedPromise, ...tasks] = methods
   const [resolvedPromise, ...tasks] = methods
-  const finalTaskPromise = tasks.reduce(function (prevTaskPromise, task) {
+  const finalTaskPromise = tasks.reduce((prevTaskPromise, task) => {
     return prevTaskPromise.then(task)
     return prevTaskPromise.then(task)
-  }, resolvedPromise([]))  // initial value
+  }, resolvedPromise([])) // initial value
 
 
   return finalTaskPromise
   return finalTaskPromise
 }
 }
@@ -51,7 +51,7 @@ function toggleClass (el, className) {
  * Adds a class to a DOM element
  * Adds a class to a DOM element
  *
  *
  * @memberof Utils
  * @memberof Utils
- * @param {String} el selector
+ * @param {Object} el selector
  * @param {String} className to add
  * @param {String} className to add
  * @return {String}
  * @return {String}
  */
  */

+ 1 - 1
src/locale/en_US.js

@@ -18,7 +18,7 @@ en_US.pluralize = function (n) {
   return 1
   return 1
 }
 }
 
 
-if (typeof window.Uppy !== 'undefined') {
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
   window.Uppy.locale.en_US = en_US
   window.Uppy.locale.en_US = en_US
 }
 }
 
 

+ 5 - 3
src/locale/ru.js

@@ -2,13 +2,15 @@ const ru = {}
 
 
 ru.strings = {
 ru.strings = {
   chooseFile: 'Выберите файл',
   chooseFile: 'Выберите файл',
-  youHaveChosen: 'или перенесите его сюда',
+  orDragDrop: 'или перенесите его сюда',
+  youHaveChosen: 'Вы выбрали: %{file_name}',
   orDragDrop: 'Вы выбрали: %{file_name}',
   orDragDrop: 'Вы выбрали: %{file_name}',
   filesChosen: {
   filesChosen: {
     0: 'Выбран %{smart_count} файл',
     0: 'Выбран %{smart_count} файл',
     1: 'Выбрано %{smart_count} файла',
     1: 'Выбрано %{smart_count} файла',
     2: 'Выбрано %{smart_count} файлов'
     2: 'Выбрано %{smart_count} файлов'
-  }
+  },
+  upload: 'Загрузить'
 }
 }
 
 
 ru.pluralize = function (n) {
 ru.pluralize = function (n) {
@@ -23,7 +25,7 @@ ru.pluralize = function (n) {
   return 2
   return 2
 }
 }
 
 
-if (typeof window.Uppy !== 'undefined') {
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
   window.Uppy.locale.ru = ru
   window.Uppy.locale.ru = ru
 }
 }
 
 

+ 67 - 59
src/plugins/DragDrop.js

@@ -1,6 +1,6 @@
 import Utils from '../core/Utils'
 import Utils from '../core/Utils'
 import Plugin from './Plugin'
 import Plugin from './Plugin'
-import componentDragDrop from '../components/dragdrop.js'
+// import componentDragDrop from '../components/dragdrop.js'
 
 
 /**
 /**
  * Drag & Drop plugin
  * Drag & Drop plugin
@@ -13,42 +13,63 @@ export default class DragDrop extends Plugin {
 
 
     // set default options
     // set default options
     const defaultOptions = {
     const defaultOptions = {
-      autoSubmit: true
+      target: '.UppyDragDrop'
     }
     }
 
 
     // merge default options with the ones set by user
     // merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
     this.opts = Object.assign({}, defaultOptions, opts)
 
 
+    // check if dragDrop is supported in the browser
     this.isDragDropSupported = this.checkDragDropSupport()
     this.isDragDropSupported = this.checkDragDropSupport()
-    this.initHtml()
 
 
-    // crazy stuff so that ‘this’ will behave in class
-    this.listenForEvents = this.listenForEvents.bind(this)
+    // Initialize dragdrop component, mount it to container DOM node
+    this.container = document.querySelector(this.opts.target)
+    this.container.innerHTML = this.render()
+
+    // Set selectors
+    this.dropzone = document.querySelector(`${this.opts.target} .UppyDragDrop-inner`)
+    this.input = document.querySelector(`${this.opts.target} .UppyDragDrop-input`)
+    this.status = document.querySelector(`${this.opts.target} .UppyDragDrop-status`)
+    this.progress = document.querySelector(`${this.opts.target} .UppyDragDrop-progress`)
+
+    Utils.addClass(this.container, 'UppyDragDrop')
+    if (this.isDragDropSupported) {
+      Utils.addClass(this.container, 'is-dragdrop-supported')
+    }
+
+    // Bind `this` to class methods
+    this.initEvents = this.initEvents.bind(this)
     this.handleDrop = this.handleDrop.bind(this)
     this.handleDrop = this.handleDrop.bind(this)
     this.checkDragDropSupport = this.checkDragDropSupport.bind(this)
     this.checkDragDropSupport = this.checkDragDropSupport.bind(this)
     this.handleInputChange = this.handleInputChange.bind(this)
     this.handleInputChange = this.handleInputChange.bind(this)
   }
   }
 
 
-  initHtml () {
-    this.dragDropContainer = document.querySelector('.UppyDragDrop')
-
-    this.dragDropContainer.innerHTML = componentDragDrop({
-      endpoint: this.opts.endpoint,
-      chooseFile: this.core.i18n('chooseFile'),
-      orDragDrop: this.core.i18n('orDragDrop'),
-      showUploadBtn: this.opts.autoSubmit,
-      upload: this.core.i18n('upload')
-    })
-
-    // get the element where the Drag & Drop event will occur
-    this.dropzone = document.querySelector(this.opts.target)
-    this.dropzoneInput = document.querySelector('.UppyDragDrop-input')
-
-    this.status = document.querySelector('.UppyDragDrop-status')
+  render () {
+    return `<form class="UppyDragDrop-inner"
+        method="post"
+        action="${this.opts.endpoint}"
+        enctype="multipart/form-data">
+      <img class="UppyDragDrop-puppy" src="/images/uppy.svg">
+      <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
+      ? `<button class="UppyDragDrop-uploadBtn" type="submit">${this.core.i18n('upload')}</button>`
+      : ''}
+    <div class="UppyDragDrop-status"></div>
+    <div class="UppyDragDrop-progress"></div>
+  </form>`
   }
   }
 
 
 /**
 /**
- * Checks if the browser supports Drag & Drop
+ * Checks if the browser supports Drag & Drop,
+ * not supported on mobile devices, for example.
  * @return {Boolean} true if supported, false otherwise
  * @return {Boolean} true if supported, false otherwise
  */
  */
   checkDragDropSupport () {
   checkDragDropSupport () {
@@ -69,12 +90,8 @@ export default class DragDrop extends Plugin {
     return true
     return true
   }
   }
 
 
-  listenForEvents () {
-    console.log(`waiting for some files to be dropped on ${this.opts.target}`)
-
-    if (this.isDragDropSupported) {
-      Utils.addClass(this.dropzone, 'is-dragdrop-supported')
-    }
+  initEvents () {
+    this.core.log(`waiting for some files to be dropped on ${this.opts.target}`)
 
 
     // prevent default actions for all drag & drop events
     // prevent default actions for all drag & drop events
     const strEvents = 'drag dragstart dragend dragover dragenter dragleave drop'
     const strEvents = 'drag dragstart dragend dragover dragenter dragleave drop'
@@ -85,11 +102,11 @@ export default class DragDrop extends Plugin {
 
 
     // Toggle is-dragover state when files are dragged over or dropped
     // Toggle is-dragover state when files are dragged over or dropped
     Utils.addListenerMulti(this.dropzone, 'dragover dragenter', (e) => {
     Utils.addListenerMulti(this.dropzone, 'dragover dragenter', (e) => {
-      Utils.addClass(this.dropzone, 'is-dragover')
+      Utils.addClass(this.container, 'is-dragover')
     })
     })
 
 
     Utils.addListenerMulti(this.dropzone, 'dragleave dragend drop', (e) => {
     Utils.addListenerMulti(this.dropzone, 'dragleave dragend drop', (e) => {
-      Utils.removeClass(this.dropzone, 'is-dragover')
+      Utils.removeClass(this.container, 'is-dragover')
     })
     })
 
 
     const onDrop = new Promise((resolve, reject) => {
     const onDrop = new Promise((resolve, reject) => {
@@ -99,66 +116,57 @@ export default class DragDrop extends Plugin {
     })
     })
 
 
     const onInput = new Promise((resolve, reject) => {
     const onInput = new Promise((resolve, reject) => {
-      this.dropzoneInput.addEventListener('change', (e) => {
+      this.input.addEventListener('change', (e) => {
         resolve(this.handleInputChange.bind(null, e))
         resolve(this.handleInputChange.bind(null, e))
       })
       })
     })
     })
 
 
-    return Promise.race([onDrop, onInput]).then(handler => handler())
-  }
+    this.container.addEventListener('progress', (e) => {
+      const percentage = e.detail
+      this.setProgress(percentage)
+    })
 
 
-  displayStatus (status) {
-    this.status.innerHTML = status
+    return Promise.race([onDrop, onInput]).then(handler => handler())
   }
   }
 
 
   handleDrop (e) {
   handleDrop (e) {
     console.log('all right, someone dropped something...')
     console.log('all right, someone dropped something...')
-    const files = e.dataTransfer.files
-    // const arrayOfFiles = Array.from(files)
-
-    const formData = new FormData(this.dropzone)
 
 
-    Array.from(files).forEach((file, i) => {
-      console.log(`file-${i}`)
-      formData.append(`file-${i}`, file)
-    })
-
-    return this.result(files, formData)
+    const files = e.dataTransfer.files
+    return this.result(files)
   }
   }
 
 
   handleInputChange () {
   handleInputChange () {
     console.log('all right, something selected through input...')
     console.log('all right, something selected through input...')
-    const files = this.dropzoneInput.files
-    const formData = new FormData(this.dropzone)
 
 
-    return this.result(files, formData)
-
-    // return Promise.resolve({from: 'DragDrop', files, formData})
+    const files = this.input.files
+    return this.result(files)
   }
   }
 
 
-  result (files, formData) {
+  result (files) {
     return new Promise((resolve, reject) => {
     return new Promise((resolve, reject) => {
-      // if autoSubmit is false, wait for upload button to be pushed,
+      const result = {from: 'DragDrop', files}
+      // const result = files
+      // if autoProceed is false, wait for upload button to be pushed,
       // otherwise just pass files to uploaders right away
       // otherwise just pass files to uploaders right away
-      if (!this.opts.autoSubmit) {
+      if (this.core.opts.autoProceed) {
+        return resolve(result)
+      } else {
         this.dropzone.addEventListener('submit', (e) => {
         this.dropzone.addEventListener('submit', (e) => {
           e.preventDefault()
           e.preventDefault()
-          console.log('yo!')
-          return resolve({from: 'DragDrop', files, formData})
+          return resolve(result)
         })
         })
-      } else {
-        return resolve({from: 'DragDrop', files, formData})
       }
       }
     })
     })
   }
   }
 
 
   run (results) {
   run (results) {
     console.log({
     console.log({
-      class: 'DragDrop',
+      class: this.constructor.name,
       method: 'run',
       method: 'run',
       results: results
       results: results
     })
     })
 
 
-    return this.listenForEvents()
+    return this.initEvents()
   }
   }
 }
 }

+ 8 - 0
src/plugins/Formtag.js

@@ -15,6 +15,14 @@ export default class Formtag extends Plugin {
 
 
     this.setProgress(0)
     this.setProgress(0)
 
 
+    // form FormData
+    // const formData = new FormData(this.dropzone)
+    //
+    // Array.from(files).forEach((file, i) => {
+    //   console.log(`file-${i}`)
+    //   formData.append(`file-${i}`, file)
+    // })
+
     const button = document.querySelector(this.opts.doneButtonSelector)
     const button = document.querySelector(this.opts.doneButtonSelector)
     var self = this
     var self = this
 
 

+ 26 - 19
src/plugins/Plugin.js

@@ -16,21 +16,21 @@ export default class Plugin {
     this.name = this.constructor.name
     this.name = this.constructor.name
   }
   }
 
 
-  setProgress (percentage, current, total) {
-    var finalPercentage = percentage
-
-    // if (current !== undefined && total !== undefined) {
-    //   var percentageOfTotal = (percentage / total);
-    //   // finalPercentage = percentageOfTotal;
-    //   if (current > 1) {
-    //     finalPercentage = percentage + (100 / (total * current));
-    //   } else {
-    //     finalPercentage = percentage;
-    //   }
-    // }
-
-    this.core.setProgress(this, finalPercentage)
-  }
+  // setProgress (percentage, current, total) {
+  //   var finalPercentage = percentage
+  //
+  //   // if (current !== undefined && total !== undefined) {
+  //   //   var percentageOfTotal = (percentage / total);
+  //   //   // finalPercentage = percentageOfTotal;
+  //   //   if (current > 1) {
+  //   //     finalPercentage = percentage + (100 / (total * current));
+  //   //   } else {
+  //   //     finalPercentage = percentage;
+  //   //   }
+  //   // }
+  //
+  //   this.core.setProgress(this, finalPercentage)
+  // }
 
 
   extractFiles (results) {
   extractFiles (results) {
     console.log({
     console.log({
@@ -46,15 +46,18 @@ export default class Plugin {
 
 
     const files = []
     const files = []
     results.forEach(result => {
     results.forEach(result => {
-      Array.from(result.files).forEach(file => files.push(file))
+      try {
+        Array.from(result.files).forEach(file => files.push(file))
+      } catch (e) {
+        console.log(e)
+      }
     })
     })
 
 
     // const files = [];
     // const files = [];
     // for (let i in results) {
     // for (let i in results) {
-    //   // console.log('yo12131');
-    //   // console.log(results[i].files);
     //   for (let j in results[i].files) {
     //   for (let j in results[i].files) {
-    //     console.log(results[i].files.item(j));
+    //     files.push(results[i].files.item(j));
+    //   for (let j in results[i].files) {
     //     // files.push(results[i].files.item(j));
     //     // files.push(results[i].files.item(j));
     //   }
     //   }
     // }
     // }
@@ -66,4 +69,8 @@ export default class Plugin {
   run (results) {
   run (results) {
     return results
     return results
   }
   }
+
+  install () {
+    return
+  }
 }
 }

+ 42 - 0
src/plugins/Progressbar.js

@@ -0,0 +1,42 @@
+import Plugin from './Plugin'
+
+/**
+ * Progress bar
+ *
+ */
+export default class Progress extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'progress'
+
+    // set default options
+    const defaultOptions = {}
+
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
+
+    this.progressBarElement = document.querySelector('.UppyDragDrop-progressInner')
+  }
+
+  progressBar (percentage) {
+    const progressContainer = document.querySelector(this.opts.target)
+    progressContainer.innerHTML = '<div class="UppyProgressBar"></div>'
+    const progressBarElement = document.querySelector(`${this.opts.target} .UppyProgressBar`)
+    progressBarElement.setAttribute('style', `width: ${percentage}%`)
+  }
+
+  initEvents () {
+    this.core.emitter.on('progress', data => {
+      const percentage = data.percentage
+      const plugin = data.plugin
+      this.core.log(
+        `this is what the progress is: ${percentage}, and its set by ${plugin.constructor.name}`
+      )
+      this.progressBar(percentage)
+    })
+  }
+
+  install () {
+    return this.initEvents()
+  }
+}

+ 47 - 33
src/plugins/Tus10.js

@@ -9,36 +9,12 @@ export default class Tus10 extends Plugin {
   constructor (core, opts) {
   constructor (core, opts) {
     super(core, opts)
     super(core, opts)
     this.type = 'uploader'
     this.type = 'uploader'
-  }
-
-/**
- * Add files to an array of `upload()` calles, passing the current and total file count numbers
- *
- * @param {array | object} results
- * @returns {Promise} of parallel uploads `Promise.all(uploaders)`
- */
-  run (results) {
-    console.log({
-      class: 'Tus10',
-      method: 'run',
-      results: results
-    })
-
-    const files = this.extractFiles(results)
 
 
-    // console.log(files);
+    // set default options
+    const defaultOptions = {}
 
 
-    this.setProgress(0)
-    // var uploaded  = [];
-    const uploaders = []
-    for (let i in files) {
-      const file = files[i]
-      const current = parseInt(i, 10) + 1
-      const total = files.length
-      uploaders.push(this.upload(file, current, total))
-    }
-
-    return Promise.all(uploaders)
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
   }
   }
 
 
 /**
 /**
@@ -51,19 +27,25 @@ export default class Tus10 extends Plugin {
  */
  */
   upload (file, current, total) {
   upload (file, current, total) {
     console.log(`uploading ${current} of ${total}`)
     console.log(`uploading ${current} of ${total}`)
+
     // Create a new tus upload
     // Create a new tus upload
-    const self = this
     const upload = new tus.Upload(file, {
     const upload = new tus.Upload(file, {
       endpoint: this.opts.endpoint,
       endpoint: this.opts.endpoint,
-      onError: function (error) {
+      onError: error => {
         return Promise.reject('Failed because: ' + error)
         return Promise.reject('Failed because: ' + error)
       },
       },
-      onProgress: function (bytesUploaded, bytesTotal) {
+      onProgress: (bytesUploaded, bytesTotal) => {
         let percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
         let percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
         percentage = Math.round(percentage)
         percentage = Math.round(percentage)
-        self.setProgress(percentage, current, total)
+        // self.setProgress(percentage, current, total)
+
+        // Dispatch progress event
+        this.core.emitter.emit('progress', {
+          plugin: this,
+          percentage: percentage
+        })
       },
       },
-      onSuccess: function () {
+      onSuccess: () => {
         console.log(`Download ${upload.file.name} from ${upload.url}`)
         console.log(`Download ${upload.file.name} from ${upload.url}`)
         return Promise.resolve(upload)
         return Promise.resolve(upload)
       }
       }
@@ -71,4 +53,36 @@ export default class Tus10 extends Plugin {
     // Start the upload
     // Start the upload
     upload.start()
     upload.start()
   }
   }
+
+/**
+ * Add files to an array of `upload()` calles, passing the current and total file count numbers
+ *
+ * @param {Array | Object} results
+ * @returns {Promise} of parallel uploads `Promise.all(uploaders)`
+ */
+  run (results) {
+    console.log({
+      class: this.constructor.name,
+      method: 'run',
+      results: results
+    })
+
+    const files = this.extractFiles(results)
+
+    this.core.log('tus got this: ')
+    this.core.log(results)
+
+    // this.setProgress(0)
+
+    // var uploaded  = [];
+    const uploaders = []
+    for (let i in files) {
+      const file = files[i]
+      const current = parseInt(i, 10) + 1
+      const total = files.length
+      uploaders.push(this.upload(file, current, total))
+    }
+
+    return Promise.all(uploaders)
+  }
 }
 }

+ 4 - 0
src/plugins/index.js

@@ -6,6 +6,9 @@ import DragDrop from './DragDrop'
 import Dropbox from './Dropbox'
 import Dropbox from './Dropbox'
 import Formtag from './Formtag'
 import Formtag from './Formtag'
 
 
+// Visualizers
+import Progressbar from './Progressbar'
+
 // Uploaders
 // Uploaders
 import Tus10 from './Tus10'
 import Tus10 from './Tus10'
 import Multipart from './Multipart'
 import Multipart from './Multipart'
@@ -15,6 +18,7 @@ import TransloaditBasic from './TransloaditBasic'
 
 
 export default {
 export default {
   Plugin,
   Plugin,
+  Progressbar,
   DragDrop,
   DragDrop,
   Dropbox,
   Dropbox,
   Formtag,
   Formtag,

+ 43 - 16
src/scss/_dragdrop.scss

@@ -1,15 +1,33 @@
 /**
 /**
 * Drag & Drop CSS to style the plugin
 * Drag & Drop CSS to style the plugin
 */
 */
-.UppyDragDrop-form {
-  width: 300px;
-  text-align: center;
-  padding: 100px 10px;
+.UppyDragDrop {
+  min-height: 300px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 }
 
 
-.UppyDragDrop-form.is-dragdrop-supported {
-  border: 2px dashed;
-  border-color: #ccc;
+  .UppyDragDrop.is-dragdrop-supported {
+    border: 2px dashed;
+    border-color: #ccc;
+  }
+
+  .UppyDragDrop.is-dragdrop-supported .UppyDragDrop-dragText {
+    display: inline;
+  }
+
+  .UppyDragDrop.is-dragover {
+    border-color: #d2ecea;
+    background-color: #dbf5f3;
+  }
+
+.UppyDragDrop-inner {
+  // width: 300px;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  // padding: 100px 10px;
 }
 }
 
 
 /* http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ */
 /* http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ */
@@ -23,6 +41,7 @@
 }
 }
 
 
 .UppyDragDrop-label {
 .UppyDragDrop-label {
+  display: block;
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
@@ -30,19 +49,15 @@
   display: none;
   display: none;
 }
 }
 
 
-.is-dragdrop-supported .UppyDragDrop-dragText {
-  display: inline;
-}
-
-.UppyDragDrop-form.is-dragover {
-  border-color: #d2ecea;
-  background-color: #dbf5f3;
-}
-
 .UppyDragDrop-uploadBtn {
 .UppyDragDrop-uploadBtn {
   display: block;
   display: block;
   margin: auto;
   margin: auto;
   margin-top: 20px;
   margin-top: 20px;
+  min-width: 110px;
+  min-height: 30px;
+  font-size: 12px;
+  text-transform: uppercase;
+  letter-spacing: 1px;
   border: 0;
   border: 0;
   border: 1px solid #ccc;
   border: 1px solid #ccc;
   background: none;
   background: none;
@@ -52,3 +67,15 @@
     background: #ccc;
     background: #ccc;
   }
   }
 }
 }
+
+.UppyDragDrop-progress {
+  width: 80%;
+  height: 3px;
+  margin: 15px auto;
+}
+
+.UppyProgressBar {
+  background-color: $color-pink;
+  height: 100%;
+  width: 0;
+}

+ 1 - 0
src/scss/_variables.scss

@@ -1 +1,2 @@
 $color-gray: #ccc;
 $color-gray: #ccc;
+$color-pink: #e02177;

+ 23 - 9
test/core.spec.js

@@ -1,22 +1,36 @@
 var test = require('tape')
 var test = require('tape')
-var Core = require('../src/core/index.js')
+var Uppy = require('../src/core/index.js')
+
+const TestPlugin = require('./mocks/test-plugin.js')
+const uppy = new Uppy({debug: true})
+uppy
+  .use(TestPlugin, {target: '.UppyDragDrop-One'})
+  .run()
 
 
 test('core object', function (t) {
 test('core object', function (t) {
-  const core = new Core()
-  t.equal(typeof core, 'object', 'new Core() should return an object')
+  const uppy = new Uppy()
+  t.equal(typeof uppy, 'object', 'new Core() should return an object')
   t.end()
   t.end()
 })
 })
 
 
 test('core type', function (t) {
 test('core type', function (t) {
-  const core = new Core()
-  t.equal(core.type, 'core', 'core.type should equal core')
+  const uppy = new Uppy()
+  t.equal(uppy.type, 'core', 'core.type should equal core')
   t.end()
   t.end()
 })
 })
 
 
-test('translation', function (t) {
-  const russianDict = require('../src/locale/ru_RU.json')
-  const core = new Core({locale: russianDict})
+test('run one plugin success', function (t) {
+  const TestPlugin = require('./mocks/test-plugin.js')
+  const uppy = new Uppy({debug: true})
+  uppy
+    .use(TestPlugin)
+    .run()
+
+  t.equal(uppy.then(result => result), [1, 2, 3])
+
+  // setTimeout(function () {
+  //   t.equal(uppy, [1, 2, 3])
+  // }, 4000)
 
 
-  t.equal(core.translate('Choose a file'), 'Выберите файл', 'should return translated string')
   t.end()
   t.end()
 })
 })

+ 3 - 1
test/index.js

@@ -1,4 +1,6 @@
 require('babel/register')({
 require('babel/register')({
   stage: 0
   stage: 0
 })
 })
-require('./translate.spec.js')
+
+require('./core.spec.js')
+require('./translator.spec.js')

+ 35 - 0
test/mocks/test-plugin.js

@@ -0,0 +1,35 @@
+var Plugin = require('../../src/plugins/Plugin.js')
+
+export default class TestSelector extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'selecter'
+
+    // set default options
+    const defaultOptions = {}
+
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
+  }
+
+  getFiles () {
+    return new Promise((resolve, reject) => {
+      const files = [1, 2, 3]
+      resolve(files)
+      // setTimeout(function () {
+      //   const files = [1, 2, 3]
+      //   resolve(files)
+      // }, 3000)
+    })
+  }
+
+  run (results) {
+    console.log({
+      class: this.constructor.name,
+      method: 'run',
+      results: results
+    })
+
+    return this.getFiles()
+  }
+}

+ 3 - 3
test/translator.spec.js

@@ -6,7 +6,7 @@ test('russian translation', function (t) {
   const core = new Core({locale: russian})
   const core = new Core({locale: russian})
 
 
   t.equal(
   t.equal(
-    core.translator.t('chooseFile'),
+    core.translator.translate('chooseFile'),
     'Выберите файл',
     'Выберите файл',
     'should return translated string'
     'should return translated string'
   )
   )
@@ -19,7 +19,7 @@ test('interpolation', function (t) {
   const core = new Core({locale: english})
   const core = new Core({locale: english})
 
 
   t.equal(
   t.equal(
-    core.translator.t('youHaveChosen', {'fileName': 'img.jpg'}),
+    core.translator.translate('youHaveChosen', {'fileName': 'img.jpg'}),
     'You have chosen: img.jpg',
     'You have chosen: img.jpg',
     'should return interpolated string'
     'should return interpolated string'
   )
   )
@@ -32,7 +32,7 @@ test('pluralization', function (t) {
   const core = new Core({locale: russian})
   const core = new Core({locale: russian})
 
 
   t.equal(
   t.equal(
-    core.translator.t('filesChosen', {'smart_count': '18'}),
+    core.translator.translate('filesChosen', {'smart_count': '18'}),
     'Выбрано 18 файлов',
     'Выбрано 18 файлов',
     'should return interpolated & pluralized string'
     'should return interpolated & pluralized string'
   )
   )

+ 13 - 9
website/src/examples/dragdrop/app.es6

@@ -1,14 +1,18 @@
 import Uppy from 'uppy/core'
 import Uppy from 'uppy/core'
-import { DragDrop, Tus10 } from 'uppy/plugins'
+import { DragDrop, Progressbar, Tus10 } from 'uppy/plugins'
 
 
-const uppy = new Uppy({wait: false})
-uppy
-  .use(DragDrop, {
-    target: '.UppyDragDrop-form',
-    endpoint: 'http://master.tus.io:8080/files/',
-    autoSubmit: false
-  })
+const uppyOne = new Uppy({autoProceed: true, debug: true})
+uppyOne
+  .use(DragDrop, {target: '.UppyDragDrop-One'})
   .use(Tus10, {endpoint: 'http://master.tus.io:8080/files/'})
   .use(Tus10, {endpoint: 'http://master.tus.io:8080/files/'})
+  .use(Progressbar, {target: '.UppyDragDrop-One .UppyDragDrop-progress'})
   .run()
   .run()
 
 
-console.log('Uppy ' + uppy.type + ' loaded')
+const uppyTwo = new Uppy({debug: true})
+uppyTwo
+  .use(DragDrop, {target: '#UppyDragDrop-Two'})
+  .use(Tus10, {endpoint: 'http://master.tus.io:8080/files/'})
+  .use(Progressbar, {target: '#UppyDragDrop-Two .UppyDragDrop-progress'})
+  .run()
+
+// console.log(`Uppy ${uppyOne.type} loaded`)

+ 15 - 2
website/src/examples/dragdrop/app.html

@@ -1,5 +1,18 @@
 <!-- Basic Uppy styles -->
 <!-- Basic Uppy styles -->
 <link rel="stylesheet" href="/uppy/uppy.css">
 <link rel="stylesheet" href="/uppy/uppy.css">
 
 
-<!-- Target DOM node -->
-<div class="UppyDragDrop"></div>
+<div class="grid">
+  <div class="column-1-2">
+    <h5>autoProceed is on</h5>
+
+    <!-- Target DOM node #1 -->
+    <div class="UppyDragDrop-One"></div>
+  </div>
+
+  <div class="column-1-2">
+    <h5>autoProceed is off</h5>
+
+    <!-- Target DOM node #2 -->
+    <div id="UppyDragDrop-Two"></div>
+  </div>
+</div>

+ 4 - 14
website/src/examples/i18n/app.html

@@ -1,26 +1,16 @@
 <!-- Basic Uppy styles -->
 <!-- Basic Uppy styles -->
-<link rel="stylesheet" href="/css/uppy.css">
+<link rel="stylesheet" href="/uppy/uppy.css">
 
 
-<form id="upload-target" class="UppyDragDrop" method="post" action="/" enctype="multipart/form-data">
-  <img class="UppyDragDrop-puppy" src="/images/uppy.svg">
-  <div>
-    <input id="UppyDragDrop-input" class="UppyDragDrop-input" type="file" name="files[]" data-multiple-caption="{count} files selected" multiple />
-    <label class="UppyDragDrop-label" for="UppyDragDrop-input">
-      <strong>Choose a file</strong>
-      <span class="UppyDragDrop-dragText"> or drag it here</span>
-    </label>
-  </div>
-  <div class="UppyDragDrop-status"></div>
-</form>
+<div class="UppyDragDrop"></div>
 
 
 <!-- Load Uppy pre-built bundled version and Russian language pack -->
 <!-- Load Uppy pre-built bundled version and Russian language pack -->
 <script src="/uppy/uppy.js"></script>
 <script src="/uppy/uppy.js"></script>
 <script src="/uppy/locale/ru.js"></script>
 <script src="/uppy/locale/ru.js"></script>
 <script>
 <script>
   var uppy = new Uppy.Core({locale: Uppy.locale.ru});
   var uppy = new Uppy.Core({locale: Uppy.locale.ru});
-  uppy.use(Uppy.plugins.DragDrop, {selector: '#upload-target'});
+  uppy.use(Uppy.plugins.DragDrop, {target: '.UppyDragDrop-form'});
   uppy.use(Uppy.plugins.Tus10, {endpoint: 'http://master.tus.io:8080/files/'});
   uppy.use(Uppy.plugins.Tus10, {endpoint: 'http://master.tus.io:8080/files/'});
   uppy.run();
   uppy.run();
 
 
-  console.log('--> Uppy CDN version with Tus10, DragDrop & Russian language pack has loaded');
+  console.log('--> Uppy pre-built version with Tus10, DragDrop & Russian language pack has loaded');
 </script>
 </script>

+ 50 - 0
website/themes/uppy/source/css/_grid.scss

@@ -0,0 +1,50 @@
+.grid {
+  @include clearfix;
+}
+
+@media #{$screen-medium} {
+
+  // Gaps
+  .grid [class^="column-"],
+  .grid [class*=" column-"] {
+    padding: 4px;
+  }
+
+  .grid.no-gap [class^="column-"],
+  .grid.no-gap [class*=" column-"] {
+    padding: 0;
+  }
+
+  // Remove gaps for the first and last column in row
+  [class^="column-"]:first-child,
+  [class*=" column-"]:first-child {
+    padding-left: 0;
+  }
+
+  [class^="column-"]:last-child,
+  [class*=" column-"]:last-child {
+    padding-right: 0;
+  }
+
+  // Columns
+  [class^="column-"] {
+    float: left;
+  }
+
+  .column-1-2 {
+    width: 50%;
+  }
+
+  .column-1-3 {
+    width: 33.333333%;
+  }
+
+  .column-1-4 {
+    width: 25%;
+  }
+
+  .column-full {
+    width: 100%;
+  }
+
+}

+ 0 - 95
website/themes/uppy/source/css/_index.scss

@@ -144,7 +144,6 @@
 .IndexExample-block {
 .IndexExample-block {
   padding: 7px;
   padding: 7px;
   text-align: left;
   text-align: left;
-
   background-color: $color-codebg;
   background-color: $color-codebg;
   border-radius: $size-radius;
   border-radius: $size-radius;
   overflow-x: scroll;
   overflow-x: scroll;
@@ -189,18 +188,6 @@
     padding-left: 40px;
     padding-left: 40px;
     margin-bottom: 0.5em;
     margin-bottom: 0.5em;
   }
   }
-
-  // li:before {
-  //   content: '';
-  //   display: inline-block;
-  //   width: 24px;
-  //   height: 24px;
-  //   background: svg('check.svg', '[fill]: #{$color-white}') center center no-repeat;
-  //   background-size: 24px 24px;
-  //   margin-right: 10px;
-  //   position: relative;
-  //   top: 4px;
-  // }
 }
 }
 
 
 .IndexFooter {
 .IndexFooter {
@@ -220,85 +207,3 @@
 
 
   p { margin: .3em 0; }
   p { margin: .3em 0; }
 }
 }
-
-// @media screen and (max-width: 480px) {
-//
-//   #hero {
-//     .inner h1 {
-//       font-size: 2em;
-//       font-weight: 400;
-//     }
-//
-//     .buttons {
-//       max-width: 300px;
-//       margin: 1em auto;
-//     }
-//
-//     a.button {
-//       width: 260px;
-//       font-size: .8em;
-//       margin: .5em 0;
-//     }
-//
-//     .desc {
-//       max-width: 300px;
-//     }
-//
-//     .warning, .desc {
-//       font-size: .85em;
-//       margin: 1em auto;
-//       br { display: none; }
-//     }
-//
-//     // .down { display: none; }
-//   }
-//
-//   #social {
-//     max-width: 340px;
-//     margin: 1.2em auto;
-//   }
-//
-//   .cool { margin-top: 0; }
-//
-//   #features .feat {
-//     font-size: 14px;
-//     margin: .8em 0;
-//     width: 250px;
-//   }
-//
-//   #design_goals {
-//     ul {
-//       width: 280px;
-//       font-size: 1.1em;
-//     }
-//
-//     h2 {
-//       font-size: 1.4em;
-//     }
-//   }
-// }
-//
-// @media screen and (max-width: 860px) {
-//   #features, #hero {
-//     font-size: 16px;
-//   }
-//
-//   #example {
-//     .block {
-//       display: block;
-//       width: auto;
-//       height: auto;
-//     }
-//   }
-//
-//   #demo {
-//     padding: 5px 20px 20px;
-//   }
-//
-//   .sign {
-//     display: block;
-//     height: 1em;
-//     line-height: 1em;
-//     padding: .5em 0;
-//   }
-// }

+ 8 - 0
website/themes/uppy/source/css/_mixins.scss

@@ -1,3 +1,11 @@
+@mixin clearfix {
+  &:after {
+    content: '';
+    display: table;
+    clear: both;
+  }
+}
+
 @mixin zoom() {
 @mixin zoom() {
   transform: scale(1.05);
   transform: scale(1.05);
 }
 }

+ 1 - 0
website/themes/uppy/source/css/main.scss

@@ -1,5 +1,6 @@
 @import '_settings.scss';
 @import '_settings.scss';
 @import '_mixins.scss';
 @import '_mixins.scss';
+@import '_grid.scss';
 @import '_syntax.scss';
 @import '_syntax.scss';
 @import '_common.scss';
 @import '_common.scss';
 @import '_index.scss';
 @import '_index.scss';

+ 0 - 4
website/themes/uppy/source/css/test.css

@@ -1,4 +0,0 @@
-body {
-  background-image: svg('uppy.svg', '[fill]: black');
-  display: flex;
-}

+ 0 - 42
website/themes/uppy/source/css/uppy.css

@@ -1,42 +0,0 @@
-/**
-* Uppy CSS and all of its out-of-the-box plugins:
-*/
-/**
-* Drag & Drop CSS to style the plugin
-*/
-.UppyDragDrop {
-  width: 300px;
-  text-align: center;
-  padding: 100px 10px; }
-
-/* http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ */
-.UppyDragDrop-input {
-  width: 0.1px;
-  height: 0.1px;
-  opacity: 0;
-  overflow: hidden;
-  position: absolute;
-  z-index: -1; }
-
-.UppyDragDrop.is-dragdrop-supported {
-  border: 2px dashed;
-  border-color: #ccc; }
-
-.UppyDragDrop-label {
-  cursor: pointer; }
-
-.UppyDragDrop-dragText {
-  display: none; }
-
-.is-dragdrop-supported .UppyDragDrop-dragText {
-  display: inline; }
-
-.UppyDragDrop.is-dragover {
-  border-color: #d2ecea;
-  background-color: #dbf5f3; }
-
-.uppy {
-  font-family: "Comic Sans MS";
-  color: purple;
-  border: 1px dashed pink;
-  font-size: 32px; }