diff --git a/package-lock.json b/package-lock.json index 7d252f500..80de8e0c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7784,6 +7784,11 @@ } } }, + "datatransfer-files-promise": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/datatransfer-files-promise/-/datatransfer-files-promise-1.2.0.tgz", + "integrity": "sha512-M853WpHN6CG/6llqz3clGl9Z3dZG/3he1NR2p0H0xNPM0ZMb+3DWQGMIK+3WTIm3odPKalWeXjRCmpcwjfDzrA==" + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", diff --git a/package.json b/package.json index 6d5e70fd1..3ac8559d2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "cids": "^0.6.0", "countly-sdk-web": "^19.2.1", "d3": "^5.7.0", + "datatransfer-files-promise": "^1.2.0", "details-polyfill": "^1.1.0", "file-extension": "^4.0.5", "filesize": "^3.6.1", diff --git a/src/App.js b/src/App.js index 189d8a94b..f0b03fe9f 100644 --- a/src/App.js +++ b/src/App.js @@ -33,7 +33,7 @@ export class App extends Component { this.props.doInitIpfs() } - addFiles = (files) => { + addFiles = async (files) => { const { doFilesWrite, doUpdateHash, routeInfo } = this.props // Add the dropped files to the root @@ -84,10 +84,8 @@ const dropTarget = { if (monitor.didDrop()) { return } - - const item = monitor.getItem() - - App.addFiles(item) + const { filesPromise } = monitor.getItem() + App.addFiles(filesPromise) } } diff --git a/src/bundles/files.js b/src/bundles/files.js index f44316504..8f63c5038 100644 --- a/src/bundles/files.js +++ b/src/bundles/files.js @@ -54,6 +54,7 @@ const make = (basename, action) => (...args) => async (args2) => { await store.doUpdateHash(`/files${path}`) } } catch (error) { + console.log(error) dispatch({ type: `FILES_${basename}_FAILED`, payload: { id, error } }) } finally { if (basename !== actions.FETCH) { @@ -179,28 +180,27 @@ export default (opts = {}) => { ] } } else if (status === 'UPDATED') { - const action = state.pending.find(a => a.id === id) + const pendingAction = state.pending.find(a => a.id === id) return { ...state, pending: [ ...state.pending.filter(a => a.id !== id), { - ...action, + ...pendingAction, data: data } ] } } else if (status === 'FAILED') { - const action = state.pending.find(a => a.id === id) - + const pendingAction = state.pending.find(a => a.id === id) return { ...state, pending: state.pending.filter(a => a.id !== id), failed: [ ...state.failed, { - ...action, + ...pendingAction, end: Date.now(), error: data.error } @@ -245,8 +245,9 @@ export default (opts = {}) => { } }, - doFilesWrite: make(actions.WRITE, async (ipfs, root, rawFiles, id, { dispatch }) => { - const { streams, totalSize } = await filesToStreams(rawFiles) + doFilesWrite: make(actions.WRITE, async (ipfs, root, filesOrPromise, id, { dispatch }) => { + let files = await filesOrPromise + const { streams, totalSize } = await filesToStreams(files) // Normalise all paths to be relative. Dropped files come as absolute, // those added by the file input come as relative paths, so normalise them. @@ -262,11 +263,17 @@ export default (opts = {}) => { updateProgress(0) - const res = await ipfs.add(streams, { - pin: false, - wrapWithDirectory: false, - progress: updateProgress - }) + let res = null + try { + res = await ipfs.add(streams, { + pin: false, + wrapWithDirectory: false, + progress: updateProgress + }) + } catch (error) { + console.error(error) + throw error + } const numberOfFiles = streams.length const numberOfDirs = countDirs(streams) diff --git a/src/files/file/File.js b/src/files/file/File.js index a446c21ce..03564d5c3 100644 --- a/src/files/file/File.js +++ b/src/files/file/File.js @@ -152,7 +152,7 @@ const dropTarget = { const item = monitor.getItem() if (item.hasOwnProperty('files')) { - props.onAddFiles(item, props.path) + props.onAddFiles(item.filesPromise, props.path) } else { const src = item.path const dst = join(props.path, basename(item.path)) diff --git a/src/files/files-list/FilesList.js b/src/files/files-list/FilesList.js index dd11e9797..46bb7aa7f 100644 --- a/src/files/files-list/FilesList.js +++ b/src/files/files-list/FilesList.js @@ -495,10 +495,8 @@ const dropTarget = { if (monitor.didDrop()) { return } - - const item = monitor.getItem() - - onAddFiles(item) + const { filesPromise } = monitor.getItem() + onAddFiles(filesPromise) } } diff --git a/src/lib/dnd-backend.js b/src/lib/dnd-backend.js index 8984f99ac..38f404f0d 100644 --- a/src/lib/dnd-backend.js +++ b/src/lib/dnd-backend.js @@ -1,93 +1,40 @@ +import { getFilesFromDataTransferItems } from 'datatransfer-files-promise' import HTML5Backend from 'react-dnd-html5-backend' -import fileReader from 'pull-file-reader' -import { join } from 'path' - -const getFileFromEntry = (entry) => new Promise((resolve, reject) => { - if (entry.file) { - entry.file(resolve, reject) - } else { - resolve(null) - } -}).catch(() => null) - -const getAsEntry = (item) => item.getAsEntry - ? item.getAsEntry() - : item.webkitGetAsEntry - ? item.webkitGetAsEntry() - : null - -const readEntries = (reader) => new Promise((resolve, reject) => { - reader.readEntries(resolve, reject) -}) - -async function scanFiles (item, root = '') { - if (!item.isDirectory) { - const file = await getFileFromEntry(item) - const path = item.fullPath - return [{ - path, - content: fileReader(file), - size: file.size - }] - } - - let files = [] - const reader = item.createReader() - const entries = await readEntries(reader) - - for (const entry of entries) { - files = files.concat(await scanFiles(entry, join(root, item.name))) - } - - return files -} - -async function scan (files) { - let entries = [] - let streams = [] - let totalSize = 0 - - for (const file of files) { - if (file.getAsEntry || file.webkitGetAsEntry) { - entries.push({ - entry: getAsEntry(file), - path: file.name - }) - } else { - totalSize += file.size - - streams.push({ - path: file.webkitRelativePath || file.name, - content: fileReader(file), - size: file.size - }) - } - } - - for (const entry of entries) { - const res = await scanFiles(entry.entry, entry.name) - - for (const stream of res) { - totalSize += stream.size - } - - streams = streams.concat(res) - } - - return { streams, totalSize, isDir: false } -} +// If you drop a dir "foo" which contains "cat.jpg" & "dog.png" we receive a +// single item in the `event.dataTransfer.items` for the directory. +// +// We use 'datatransfer-files-promise' to map the dir tree to a flat list of +// FileSystemEntry objects, each with a filepath propety, that captures the +// files relative path within the dir that was dropped. +// +// so the "foo" becomes: +// [ +// { filepath: 'foo/cat.jpg' name: 'cat.jpg', size: Number }, +// { filepath: 'foo/dog.png' name: 'dog.png', size: Number } +// ] +// +// Which is a useful shape for passing to ipfs.add, with the caveat that each +// FileSystemEntry object must be passed to pull-file-reader or similar to +// convert to a stream style that ipfs.add accepts. +// +// ReactDnD doesn't give the calling code access to the `event.dataTransfer.items` +// so we have to work around it here by plugin a custom drop handler that does +// the work to map from a dir entry to a flat list of files and then stash it on +// a custom `filesPromise` prop on the return object, which we check for in our +// dropTarget drop handler functions. +// +// See: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry +// See: https://github.com/grabantot/datatransfer-files-promise/blob/72b6cc763f9b400c59197bcc787268965310c260/index.js export default (manager) => { const backend = HTML5Backend(manager) const handler = backend.handleTopDropCapture - backend.handleTopDropCapture = (event) => { handler.call(backend, event) - if (backend.currentNativeSource) { - backend.currentNativeSource.item.content = scan(event.dataTransfer.items) + const filesPromise = getFilesFromDataTransferItems(event.dataTransfer.items) + backend.currentNativeSource.item.filesPromise = filesPromise } } - return backend } diff --git a/src/lib/files.js b/src/lib/files.js index d9b9de47c..9564889b4 100644 --- a/src/lib/files.js +++ b/src/lib/files.js @@ -8,11 +8,6 @@ const ignore = [ ] export async function filesToStreams (files) { - if (files.hasOwnProperty('content')) { - // this is a promise - return files.content - } - const streams = [] let totalSize = 0 let isDir = false @@ -29,14 +24,13 @@ export async function filesToStreams (files) { } streams.push({ - path: file.webkitRelativePath || file.name, + path: file.filepath || file.webkitRelativePath || file.name, content: stream, size: file.size }) totalSize += file.size } - return { streams, totalSize, isDir } }