Browse Source

getDroppedFiles.js - moved into @uppy/utils, refactored

Evgenia Karunus 6 years ago
parent
commit
bcd9472da5

+ 16 - 10
packages/@uppy/dashboard/src/index.js

@@ -6,10 +6,10 @@ const Informer = require('@uppy/informer')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
 const toArray = require('@uppy/utils/lib/toArray')
+const getDroppedFiles = require('@uppy/utils/src/getDroppedFiles')
 const cuid = require('cuid')
 const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
 const { defaultPickerIcon } = require('./components/icons')
-const getDroppedFiles = require('./utils/getDroppedFiles')
 
 // Some code for managing focus was adopted from https://github.com/ghosh/micromodal
 // MIT licence, https://github.com/ghosh/micromodal/blob/master/LICENSE.md
@@ -476,7 +476,12 @@ module.exports = class Dashboard extends Plugin {
         source: this.id,
         name: file.name,
         type: file.type,
-        data: data || file
+        data: data || file,
+        meta: {
+          // path of the file relative to the ancestor directory the user selected.
+          // e.g. 'docs/Old Prague/airbnb.pdf'
+          relativePath: file.relativePath || null
+        }
       })
     } catch (err) {
       // Nothing, restriction errors handled in Core
@@ -563,14 +568,15 @@ module.exports = class Dashboard extends Plugin {
     })
 
     // 4. Add all dropped files
-    getDroppedFiles(event.dataTransfer, (files) => {
-      if (files.length > 0) {
-        this.uppy.log('[Dashboard] Files were dropped')
-        files.forEach((file) =>
-          this.addFile(file)
-        )
-      }
-    })
+    getDroppedFiles(event.dataTransfer)
+      .then((files) => {
+        if (files.length > 0) {
+          this.uppy.log('[Dashboard] Files were dropped')
+          files.forEach((file) =>
+            this.addFile(file)
+          )
+        }
+      })
   }
 
   initEvents () {

+ 0 - 4
packages/@uppy/dashboard/src/utils/getDroppedFiles/README.md

@@ -1,4 +0,0 @@
-Heavily influenced by:
-  - https://github.com/leonadler/drag-and-drop-across-browsers
-  - https://github.com/silverwind/uppie/blob/master/uppie.js
-  - https://stackoverflow.com/a/50030399/3192470

+ 0 - 14
packages/@uppy/dashboard/src/utils/getDroppedFiles/index.js

@@ -1,14 +0,0 @@
-const webkitGetAsEntryApi = require('./utils/webkitGetAsEntryApi')
-const getFilesAndDirectoriesApi = require('./utils/getFilesAndDirectoriesApi')
-const fallbackApi = require('./utils/fallbackApi')
-
-module.exports = function getDroppedFiles (dataTransfer, callback) {
-  if (dataTransfer.items[0] && 'webkitGetAsEntry' in dataTransfer.items[0]) {
-    webkitGetAsEntryApi(dataTransfer, callback)
-  } else if ('getFilesAndDirectories' in dataTransfer) {
-    // Doesn't actually work in firefox, maybe in previous versions. webkitGetAsEntryApi() works.
-    getFilesAndDirectoriesApi(dataTransfer, callback)
-  } else {
-    fallbackApi(dataTransfer, callback)
-  }
-}

+ 0 - 7
packages/@uppy/dashboard/src/utils/getDroppedFiles/utils/fallbackApi.js

@@ -1,7 +0,0 @@
-const toArray = require('@uppy/utils/lib/toArray')
-
-// .files fallback, should be implemented in any browser
-module.exports = function fallbackApi (dataTransfer, cb) {
-  const files = toArray(dataTransfer.files)
-  cb(files)
-}

+ 0 - 27
packages/@uppy/dashboard/src/utils/getDroppedFiles/utils/getFilesAndDirectoriesApi.js

@@ -1,27 +0,0 @@
-// API implemented in Firefox 42+ and Edge
-module.exports = function getFilesAndDirectoriesApi (input, cb) {
-  const files = []
-
-  const iterate = function (entries, path, resolve) {
-    const promises = []
-    entries.forEach(function (entry) {
-      promises.push(new Promise(function (resolve) {
-        if ('getFilesAndDirectories' in entry) {
-          entry.getFilesAndDirectories().then(function (entries) {
-            iterate(entries, entry.path + '/', resolve)
-          })
-        } else {
-          files.push(entry)
-          resolve()
-        }
-      }))
-    })
-    Promise.all(promises).then(resolve)
-  }
-  input.getFilesAndDirectories()
-    .then((entries) => {
-      new Promise(function (resolve) {
-        iterate(entries, '/', resolve)
-      }).then(() => cb(files))
-    })
-}

+ 0 - 58
packages/@uppy/dashboard/src/utils/getDroppedFiles/utils/webkitGetAsEntryApi.js

@@ -1,58 +0,0 @@
-const toArray = require('@uppy/utils/lib/toArray')
-
-function readEntries (entry, reader, oldEntries, cb) {
-  const dirReader = reader || entry.createReader()
-  dirReader.readEntries(function (entries) {
-    const newEntries = oldEntries ? oldEntries.concat(entries) : entries
-    if (entries.length) {
-      setTimeout(() => {
-        readEntries(entry, dirReader, newEntries, cb)
-      }, 0)
-    } else {
-      cb(newEntries)
-    }
-  })
-}
-
-function readDirectory (files, entry, resolve) {
-  readEntries(entry, 0, 0, function (entries) {
-    const promises = []
-    entries.forEach(function (entry) {
-      promises.push(new Promise(function (resolve) {
-        if (entry.isFile) {
-          entry.file(function (file) {
-            files.push(file)
-            resolve()
-          }, resolve)
-        } else if (entry.isDirectory) {
-          readDirectory(files, entry, resolve)
-        }
-      }))
-    })
-    Promise.all(promises).then(resolve)
-  })
-}
-
-// Old drag and drop API implemented in Chrome 11+
-// Explanation: https://stackoverflow.com/a/50030399/3192470
-module.exports = function webkitGetAsEntryApi (dataTransfer, cb) {
-  const files = []
-
-  const rootPromises = []
-  toArray(dataTransfer.items).forEach(function (entry) {
-    entry = entry.webkitGetAsEntry()
-    if (entry) {
-      rootPromises.push(new Promise(function (resolve) {
-        if (entry.isFile) {
-          entry.file(function (file) {
-            files.push(file)
-            resolve()
-          }, resolve)
-        } else if (entry.isDirectory) {
-          readDirectory(files, entry, resolve)
-        }
-      }))
-    }
-  })
-  Promise.all(rootPromises).then(() => cb(files))
-}

+ 10 - 0
packages/@uppy/utils/src/getDroppedFiles/README.md

@@ -0,0 +1,10 @@
+Influenced by:
+  - https://github.com/leonadler/drag-and-drop-across-browsers
+  - https://github.com/silverwind/uppie/blob/master/uppie.js
+  - https://stackoverflow.com/a/50030399/3192470
+
+### Why do we not use `getFilesAndDirectories()` api?
+
+It's a proposed spec that seems to be barely implemented anywhere.
+Supposed to work in Firefox and Edge, but it doesn't work in Firefox, and both Firefox and Edge support `.webkitGetAsEntry()` api anyway.
+This page e.g. shows how this spec is supposed to function: https://wicg.github.io/directory-upload/, but it only works because of the polyfill.js that uses `.webkitGetAsEntry()` internally.

+ 17 - 0
packages/@uppy/utils/src/getDroppedFiles/index.js

@@ -0,0 +1,17 @@
+const webkitGetAsEntryApi = require('./utils/webkitGetAsEntryApi')
+const fallbackApi = require('./utils/fallbackApi')
+
+// Returns a promise that resolves to the array of dropped files (if a folder is dropped, and browser supports folder parsing - promise resolves to the flat array of all files in all directories).
+// Each file has .relativePath prop appended to it (e.g. "/docs/Prague/ticket_from_prague_to_ufa.pdf") if browser supports it. Otherwise it's undefined.
+//
+// @param {DataTransfer} dataTransfer
+// @returns {Promise} - Array<File>
+module.exports = function getDroppedFiles (dataTransfer) {
+  // Get all files from all subdirs. Works (at least) in Chrome, Mozilla, and Safari
+  if (dataTransfer.items[0] && 'webkitGetAsEntry' in dataTransfer.items[0]) {
+    return webkitGetAsEntryApi(dataTransfer)
+  // Otherwise just return all first-order files
+  } else {
+    return fallbackApi(dataTransfer)
+  }
+}

+ 7 - 0
packages/@uppy/utils/src/getDroppedFiles/utils/fallbackApi.js

@@ -0,0 +1,7 @@
+const toArray = require('../../../lib/toArray')
+
+// .files fallback, should be implemented in any browser
+module.exports = function fallbackApi (dataTransfer) {
+  const files = toArray(dataTransfer.files)
+  return Promise.resolve(files)
+}

+ 85 - 0
packages/@uppy/utils/src/getDroppedFiles/utils/webkitGetAsEntryApi.js

@@ -0,0 +1,85 @@
+const toArray = require('../../../lib/toArray')
+
+// Recursive function, calls the original callback() when the directory is entirely parsed.
+// @param {function} callback - called with ([ all files and directories in that directoryReader ])
+function readEntries (directoryReader, oldEntries, callback) {
+  directoryReader.readEntries(
+    (entries) => {
+      const newEntries = [...oldEntries, ...entries]
+      // According to the FileSystem API spec, readEntries() must be called until it calls the callback with an empty array.
+      if (entries.length) {
+        setTimeout(() => {
+          readEntries(directoryReader, newEntries, callback)
+        }, 0)
+      // Done iterating this particular directory
+      } else {
+        callback(newEntries)
+      }
+    },
+    // Make sure we resolve on error anyway
+    () =>
+      callback(oldEntries)
+  )
+}
+
+// @param {function} resolve - function that will be called when :files array is appended with a file
+// @param {Array<File>} files - array of files to enhance
+// @param {FileSystemFileEntry} fileEntry
+function addEntryToFiles (resolve, files, fileEntry) {
+  // Creates a new File object which can be used to read the file.
+  fileEntry.file(
+    (file) => {
+      // Preserve the relative path from the FileSystemFileEntry#fullPath, because File#webkitRelativePath is always '', at least onDrop.
+      // => "/docs/Prague/ticket_from_prague_to_ufa.pdf"
+      file.relativePath = fileEntry.fullPath
+      files.push(file)
+      resolve()
+    },
+    // Make sure we resolve on error anyway
+    () =>
+      resolve()
+  )
+}
+
+// @param {function} resolve - function that will be called when :directoryEntry is done being recursively parsed
+// @param {Array<File>} files - array of files to enhance
+// @param {FileSystemDirectoryEntry} directoryEntry
+function recursivelyAddFilesFromDirectory (resolve, files, directoryEntry) {
+  const directoryReader = directoryEntry.createReader()
+  readEntries(directoryReader, [], (entries) => {
+    const promises =
+      entries.map((entry) =>
+        createPromiseToAddFileOrParseDirectory(files, entry)
+      )
+    Promise.all(promises)
+      .then(() =>
+        resolve()
+      )
+  })
+}
+
+// @param {Array<File>} files - array of files to enhance
+// @param {(FileSystemFileEntry|FileSystemDirectoryEntry)} entry
+function createPromiseToAddFileOrParseDirectory (files, entry) {
+  return new Promise((resolve) => {
+    if (entry.isFile) {
+      addEntryToFiles(resolve, files, entry)
+    } else if (entry.isDirectory) {
+      recursivelyAddFilesFromDirectory(resolve, files, entry)
+    }
+  })
+}
+
+module.exports = function webkitGetAsEntryApi (dataTransfer) {
+  const files = []
+
+  const rootPromises =
+    toArray(dataTransfer.items)
+      .map((item) => {
+        const entry = item.webkitGetAsEntry()
+        return createPromiseToAddFileOrParseDirectory(files, entry)
+      })
+
+  return Promise.all(rootPromises)
+    .then(() => files)
+}

+ 4 - 0
packages/@uppy/utils/types/index.d.ts

@@ -125,3 +125,7 @@ declare module '@uppy/utils/lib/settle' {
 declare module '@uppy/utils/lib/toArray' {
   export default function toArray(list: any): any[];
 }
+
+declare module '@uppy/utils/lib/getDroppedFiles' {
+  export default function getDroppedFiles(dataTransfer: DataTransfer): Promise<File[]>;
+}