Browse Source

Merge branch 'master' into feature/form-meta

Artur Paikin 7 years ago
parent
commit
63b8e54e9a

+ 8 - 6
CHANGELOG.md

@@ -76,25 +76,27 @@ maybe later:
 - [ ] webcam: look into simplifying / improving webcam plugin (@arturi, @goto-bus-stop)
 - [ ] core: add error in file progress state? (@arturi)
 - [ ] uppy-server: add uppy-server metrics to Librato (@ifedapoolarewaju)
+- [ ] dashboard: error UI, question mark button, `core:error` (@arturi)
+- [ ] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit, stage 2, release (@goto-bus-stop)
 
 ## 0.17.0
 
 To be released: 2017-06-30
 
-- [ ] core: add `uppy.reset()` as discussed in #179 (@arturi)
-- [ ] core: research !important styles to be immune to any environment/page. Maybe use smth like https://www.npmjs.com/package/postcss-safe-important. Or increase specificity (with .Uppy) (@arturi)
+- [x] core: add `uppy.reset()` as discussed in #179 (@arturi)
+- [ ] core: research !important styles to be immune to any environment/page. Maybe use smth like `postcss-safe-important`. Or increase specificity (with .Uppy) (@arturi)
 - [ ] core: restrictions — by file type, size, number of files (@arturi)
 - [ ] core: see if we can figure out css-in-js, while keeping non-random classnames (ideally prefixed) and useful preprocessor features. also see simple https://github.com/codemirror/CodeMirror/blob/master/lib/codemirror.css (@arturi, @goto-bus-stop)
-- [ ] dashboard: error UI, question mark button, `core:error` (@arturi)
 - [ ] fileinput: allow retriving fields/options from form (@arturi #153)
 - [ ] provider: improve UI: add icons for file types (@arturi)
 - [ ] provider: improve UI: improve overall look, breadcrumbs (@arturi)
 - [ ] provider: improve UI: steps towards making it responsive (@arturi)
 - [ ] server: what if smth changed in GDrive while it was open in Uppy? refresh file list? (@ifedapoolarewaju)
-- [ ] statusbar: show status “Upload started...” when the remote upload has begun, but no progress events received yet (@arturi)
+- [x] statusbar: show status “Upload started...” when the remote upload has begun, but no progress events received yet (@arturi)
 - [ ] test: add tests for `npm install uppy` and running in different browsers, the real world use case (@arturi)
-- [ ] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit (@goto-bus-stop)
-- [ ] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
+- [x] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
+- [x] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit, stage 1, WIP (@goto-bus-stop)
+- [x] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
 - [x] uppy/uppy-server: some file types cannot be downloaded/uploaded on google drive (e.g google docs). How to handle that? (@ifedapoolarewaju)
 - [x] uppy: fix google drive uploads on mobile (double click issue) (@arturi)
 - [ ] website: new demo video / gif (@arturi)

+ 28 - 31
examples/bundled-example/main.js

@@ -1,8 +1,9 @@
 const Uppy = require('../../src/core/Core.js')
-// const Dashboard = require('../../src/plugins/Dashboard')
-// const GoogleDrive = require('../../src/plugins/GoogleDrive')
-// const Dropbox = require('../../src/plugins/Dropbox')
-// const Webcam = require('../../src/plugins/Webcam')
+const Dashboard = require('../../src/plugins/Dashboard')
+const GoogleDrive = require('../../src/plugins/GoogleDrive')
+const Dropbox = require('../../src/plugins/Dropbox')
+const Instagram = require('../../src/plugins/Instagram')
+const Webcam = require('../../src/plugins/Webcam')
 const Tus10 = require('../../src/plugins/Tus10')
 // const Multipart = require('../../src/plugins/Multipart')
 // const DragDrop = require('../../src/plugins/FileInput')
@@ -23,37 +24,33 @@ const uppy = Uppy({
   debug: true,
   autoProceed: true,
   meta: {
-    username: 'Artur'
+    username: 'John'
   }
 })
-  // .use(Dashboard, {
-  //   trigger: '#uppyModalOpener',
-  //   // maxWidth: 350,
-  //   // maxHeight: 400,
-  //   inline: false,
-  //   // disableStatusBar: true,
-  //   // disableInformer: true,
-  //   setMetaFromTargetForm: true,
-  //   target: '.MyForm',
-  //   locale: {
-  //     strings: { browse: 'wow' }
-  //   }
-  // })
-  // .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
-  // .use(Dropbox, {target: Dashboard, host: 'http://localhost:3020'})
-  .use(FileInput, {
+  .use(Dashboard, {
+    trigger: '#uppyModalOpener',
+    // maxWidth: 350,
+    // maxHeight: 400,
+    inline: false,
+    // disableStatusBar: true,
+    // disableInformer: true,
+    setMetaFromTargetForm: true,
     target: '.MyForm',
-    // replaceTargetContent: false,
     locale: {
-      strings: { selectToUpload: 'Выберите файл для загрузки' }
-    }})
-  // .use(DragDrop, {
-  //   target: '.MyForm',
-  //   locale: {
-  //     strings: {chooseFile: 'Выберите файл'}
-  //   },
-  //   setMetaFromTargetForm: true
-  // })
+      strings: { browse: 'wow' }
+    }
+  })
+  .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
+  .use(Dropbox, {target: Dashboard, host: 'http://localhost:3020'})
+  .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
+  .use(Dropbox, {target: Dashboard, host: 'http://localhost:3020'})
+  .use(Instagram, {target: Dashboard, host: 'http://localhost:3020'})
+  // .use(FileInput, {target: '.Uppy', locale: {
+  //   strings: {selectToUpload: 'Выберите файл для загрузки'}
+  // }})
+  // .use(DragDrop, {target: 'body', locale: {
+  //   strings: {chooseFile: 'Выберите файл'}
+  // }})
   // .use(ProgressBar, {target: 'body'})
   // .use(Webcam, {target: Dashboard})
   // .use(Multipart, {endpoint: '//api2.transloadit.com'})

+ 13 - 4
src/core/Core.js

@@ -102,6 +102,14 @@ class Uppy {
     return this.state
   }
 
+  reset () {
+    this.emit('core:pause-all')
+    this.emit('core:cancel-all')
+    this.setState({
+      totalProgress: 0
+    })
+  }
+
   addPreProcessor (fn) {
     this.preProcessors.push(fn)
   }
@@ -298,10 +306,9 @@ class Uppy {
     })
 
     this.on('core:cancel-all', () => {
-      const files = this.getState().files
-      Object.keys(files).forEach((file) => {
-        this.removeFile(files[file].id)
-      })
+      let updatedFiles = this.getState().files
+      updatedFiles = {}
+      this.setState({files: updatedFiles})
     })
 
     this.on('core:upload-started', (fileID, upload) => {
@@ -514,6 +521,8 @@ class Uppy {
    * Uninstall all plugins and close down this Uppy instance.
    */
   close () {
+    this.reset()
+
     this.iteratePlugins((plugin) => {
       plugin.uninstall()
     })

+ 2 - 2
src/generic-provider-views/Browser.js

@@ -40,11 +40,11 @@ module.exports = (props) => {
             activeRow: props.isActiveRow,
             sortByTitle: props.sortByTitle,
             sortByDate: props.sortByDate,
-            // handleRowClick: props.handleRowClick,
             handleFileClick: props.addFile,
             handleFolderClick: props.getNextFolder,
             getItemName: props.getItemName,
-            getItemIcon: props.getItemIcon
+            getItemIcon: props.getItemIcon,
+            handleScroll: props.handleScroll
           })}
         </main>
       </div>

+ 1 - 3
src/generic-provider-views/Table.js

@@ -17,13 +17,12 @@ module.exports = (props) => {
           ${headers}
         </tr>
       </thead>
-      <tbody>
+      <tbody onscroll=${props.handleScroll}>
         ${props.folders.map((folder) => {
           return Row({
             title: props.getItemName(folder),
             active: props.activeRow(folder),
             getItemIcon: () => props.getItemIcon(folder),
-            // handleClick: () => props.handleRowClick(folder),
             handleClick: () => props.handleFolderClick(folder),
             columns: props.columns
           })
@@ -33,7 +32,6 @@ module.exports = (props) => {
             title: props.getItemName(file),
             active: props.activeRow(file),
             getItemIcon: () => props.getItemIcon(file),
-            // handleClick: () => props.handleRowClick(file),
             handleClick: () => props.handleFileClick(file),
             columns: props.columns
           })

+ 39 - 38
src/generic-provider-views/index.js

@@ -34,6 +34,8 @@ const Utils = require('../core/Utils')
  *    @return {String} unique request path of the item when making calls to uppy server
  * getItemModifiedDate
  *    @return {object} or {String} date of when last the item was modified
+ * getItemThumbnailUrl
+ *    @return {String}
  */
 module.exports = class View {
   /**
@@ -49,7 +51,6 @@ module.exports = class View {
     this.filterQuery = this.filterQuery.bind(this)
     this.getFolder = this.getFolder.bind(this)
     this.getNextFolder = this.getNextFolder.bind(this)
-    // this.handleRowClick = this.handleRowClick.bind(this)
     this.logout = this.logout.bind(this)
     this.handleAuth = this.handleAuth.bind(this)
     this.handleDemoAuth = this.handleDemoAuth.bind(this)
@@ -57,6 +58,7 @@ module.exports = class View {
     this.sortByDate = this.sortByDate.bind(this)
     this.isActiveRow = this.isActiveRow.bind(this)
     this.handleError = this.handleError.bind(this)
+    this.handleScroll = this.handleScroll.bind(this)
 
     // Visual
     this.render = this.render.bind(this)
@@ -72,6 +74,18 @@ module.exports = class View {
     this.plugin.core.setState({[stateId]: Object.assign({}, state[stateId], newState)})
   }
 
+  _updateFilesAndFolders (res, files, folders) {
+    this.plugin.getItemSubList(res).forEach((item) => {
+      if (this.plugin.isFolder(item)) {
+        folders.push(item)
+      } else {
+        files.push(item)
+      }
+    })
+
+    this.updateState({ folders, files })
+  }
+
   /**
    * Based on folder ID, fetch a new folder and update it to state
    * @param  {String} id Folder id
@@ -94,18 +108,8 @@ module.exports = class View {
           updatedDirectories = state.directories.concat([{id, title: name || this.plugin.getItemName(res)}])
         }
 
-        this.plugin.getItemSubList(res).forEach((item) => {
-          if (this.plugin.isFolder(item)) {
-            folders.push(item)
-          } else {
-            files.push(item)
-          }
-        })
-
-        let data = {folders, files, directories: updatedDirectories}
-        this.updateState(data)
-
-        return data
+        this._updateFilesAndFolders(res, files, folders)
+        this.updateState({ directories: updatedDirectories })
       },
       this.handleError)
   }
@@ -124,7 +128,7 @@ module.exports = class View {
     const tagFile = {
       source: this.plugin.id,
       data: this.plugin.getItemData(file),
-      name: this.plugin.getItemName(file),
+      name: this.plugin.getItemName(file) || this.plugin.getItemId(file),
       type: this.plugin.getMimeType(file),
       isRemote: true,
       body: {
@@ -141,7 +145,7 @@ module.exports = class View {
 
     Utils.getFileType(tagFile).then(fileType => {
       if (Utils.isPreviewSupported(fileType[1])) {
-        tagFile.preview = `${this.plugin.opts.host}/${this.Provider.id}/thumbnail/${this.plugin.getItemRequestPath(file)}`
+        tagFile.preview = this.plugin.getItemThumbnailUrl(file)
       }
       this.plugin.core.log('Adding remote file')
       this.plugin.core.emitter.emit('core:file-add', tagFile)
@@ -167,19 +171,6 @@ module.exports = class View {
       }).catch(this.handleError)
   }
 
-  /**
-   * Used to set active file/folder.
-   * @param  {Object} file   Active file/folder
-   */
-  // handleRowClick (file) {
-  //   const state = this.plugin.core.getState()[this.plugin.stateId]
-  //   const newState = Object.assign({}, state, {
-  //     activeRow: this.plugin.getItemId(file)
-  //   })
-
-  //   this.updateState(newState)
-  // }
-
   filterQuery (e) {
     const state = this.plugin.core.getState()[this.plugin.stateId]
     this.updateState(Object.assign({}, state, {
@@ -297,17 +288,27 @@ module.exports = class View {
     this.updateState({ error })
   }
 
+  handleScroll (e) {
+    const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
+    const path = this.plugin.getNextPagePath ? this.plugin.getNextPagePath() : null
+
+    if (scrollPos < 50 && path && !this._isHandlingScroll) {
+      this.Provider.list(path)
+        .then((res) => {
+          const { files, folders } = this.plugin.core.getState()[this.plugin.stateId]
+          this._updateFilesAndFolders(res, files, folders)
+        }).catch(this.handleError)
+        .then(() => { this._isHandlingScroll = false }) // always called
+
+      this._isHandlingScroll = true
+    }
+  }
+
   // displays loader view while asynchronous request is being made.
   _loaderWrapper (promise, then, catch_) {
     promise
-      .then((result) => {
-        this.updateState({ loading: false })
-        then(result)
-      })
-      .catch((err) => {
-        this.updateState({ loading: false })
-        catch_(err)
-      })
+      .then(then).catch(catch_)
+      .then(() => this.updateState({ loading: false })) // always called.
     this.updateState({ loading: true })
   }
 
@@ -338,14 +339,14 @@ module.exports = class View {
       addFile: this.addFile,
       filterItems: this.filterItems,
       filterQuery: this.filterQuery,
-      handleRowClick: this.handleRowClick,
       sortByTitle: this.sortByTitle,
       sortByDate: this.sortByDate,
       logout: this.logout,
       demo: this.plugin.opts.demo,
       isActiveRow: this.isActiveRow,
       getItemName: this.plugin.getItemName,
-      getItemIcon: this.plugin.getItemIcon
+      getItemIcon: this.plugin.getItemIcon,
+      handleScroll: this.handleScroll
     })
 
     return Browser(browserProps)

+ 2 - 0
src/index.js

@@ -12,6 +12,7 @@ const DragDrop = require('./plugins/DragDrop/index.js')
 const FileInput = require('./plugins/FileInput.js')
 const GoogleDrive = require('./plugins/GoogleDrive/index.js')
 const Dropbox = require('./plugins/Dropbox/index.js')
+const Instagram = require('./plugins/Instagram/index.js')
 const Webcam = require('./plugins/Webcam/index.js')
 
 // Progressindicators
@@ -35,6 +36,7 @@ module.exports = {
   DragDrop,
   GoogleDrive,
   Dropbox,
+  Instagram,
   FileInput,
   Tus10,
   Multipart,

+ 4 - 0
src/plugins/Dropbox/index.js

@@ -122,6 +122,10 @@ module.exports = class Dropbox extends Plugin {
     return item.modified
   }
 
+  getItemThumbnailUrl (item) {
+    return `${this.opts.host}/${this.Dropbox.id}/thumbnail/${this.getItemRequestPath(item)}`
+  }
+
   render (state) {
     return this.view.render(state)
   }

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

@@ -113,6 +113,10 @@ module.exports = class Google extends Plugin {
     return item.modifiedByMeDate
   }
 
+  getItemThumbnailUrl (item) {
+    return `${this.opts.host}/${this.GoogleDrive.id}/thumbnail/${this.getItemRequestPath(item)}`
+  }
+
   render (state) {
     return this.view.render(state)
   }

+ 130 - 0
src/plugins/Instagram/index.js

@@ -0,0 +1,130 @@
+const html = require('yo-yo')
+const Plugin = require('../Plugin')
+
+const Provider = require('../../uppy-base/src/plugins/Provider')
+
+const View = require('../../generic-provider-views/index')
+
+module.exports = class Instagram extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'acquirer'
+    this.id = 'Instagram'
+    this.title = 'Instagram'
+    this.stateId = 'instagram'
+    this.icon = () => html`
+      <svg 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"/>
+      </svg>
+    `
+
+    // writing out the key explicitly for readability the key used to store
+    // the provider instance must be equal to this.id.
+    this.Instagram = new Provider({
+      host: this.opts.host,
+      provider: 'instagram',
+      authProvider: 'instagram'
+    })
+
+    this.files = []
+
+    this.onAuth = this.onAuth.bind(this)
+    // Visual
+    this.render = this.render.bind(this)
+
+    // set default options
+    const defaultOptions = {}
+
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
+  }
+
+  install () {
+    this.view = new View(this)
+    // Set default state for Google Drive
+    this.core.setState({
+      // writing out the key explicitly for readability the key used to store
+      // the plugin state must be equal to this.stateId.
+      instagram: {
+        authenticated: false,
+        files: [],
+        folders: [],
+        directories: [],
+        activeRow: -1,
+        filterInput: ''
+      }
+    })
+
+    const target = this.opts.target
+    const plugin = this
+    this.target = this.mount(target, plugin)
+
+    // catch error here.
+    this[this.id].auth().then(this.onAuth).catch(this.view.handleError)
+    return
+  }
+
+  uninstall () {
+    this.unmount()
+  }
+
+  onAuth (authenticated) {
+    this.view.updateState({authenticated})
+    if (authenticated) {
+      this.view.getFolder('recent')
+    }
+  }
+
+  isFolder (item) {
+    return false
+  }
+
+  getItemData (item) {
+    return item
+  }
+
+  getItemIcon (item) {
+    return html`<img width="100px" src=${item.images.thumbnail.url}/>`
+  }
+
+  getItemSubList (item) {
+    return item.data
+  }
+
+  getItemName (item) {
+    return ''
+  }
+
+  getMimeType (item) {
+    return item.type === 'video' ? 'video/mp4' : 'image/jpeg'
+  }
+
+  getItemId (item) {
+    return item.id
+  }
+
+  getItemRequestPath (item) {
+    return this.getItemId(item)
+  }
+
+  getItemModifiedDate (item) {
+    return item.created_time
+  }
+
+  getItemThumbnailUrl (item) {
+    return item.images.thumbnail.url
+  }
+
+  getNextPagePath () {
+    const { files } = this.core.getState()[this.stateId]
+    return `recent?max_id=${this.getItemId(files[files.length - 1])}`
+  }
+
+  render (state) {
+    return this.view.render(state)
+  }
+}