From 44b241b5ca5605e5f7e8e7aab425e6e48deeb86d Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Thu, 21 Jul 2022 09:32:34 +0100 Subject: [PATCH] Upload files dropped onto the file tree (#8064) GitOrigin-RevId: 97043661e344b72ff742326c9dc2809e46d0bb9c --- package-lock.json | 18 +++++++ .../modes/file-tree-upload-doc.js | 54 ++++++++++++++++--- .../file-tree-draggable-preview-layer.js | 2 +- .../contexts/file-tree-actionable.js | 5 ++ .../file-tree/contexts/file-tree-draggable.js | 27 ++++++++-- services/web/package.json | 1 + 6 files changed, 93 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 80c85f4258..f357177335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34542,6 +34542,7 @@ "@uppy/core": "^1.15.0", "@uppy/dashboard": "^1.11.0", "@uppy/react": "^1.11.0", + "@uppy/utils": "^4.0.7", "@uppy/xhr-upload": "^1.6.8", "abort-controller": "^3.0.0", "accepts": "^1.3.7", @@ -34819,6 +34820,14 @@ "lodash": "^4.17.15" } }, + "services/web/node_modules/@uppy/utils": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-4.0.7.tgz", + "integrity": "sha512-nKViMT8XchKy+NWpb3DtVKuzZBmW7au26LrMq89EsvTwIOT6UR9+7bmz/+zr3+lc7UC7vMgNChIC6G+/Ya9wWQ==", + "dependencies": { + "lodash.throttle": "^4.1.1" + } + }, "services/web/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -42084,6 +42093,7 @@ "@uppy/core": "^1.15.0", "@uppy/dashboard": "^1.11.0", "@uppy/react": "^1.11.0", + "@uppy/utils": "^4.0.7", "@uppy/xhr-upload": "^1.6.8", "abort-controller": "^3.0.0", "accepts": "^1.3.7", @@ -42330,6 +42340,14 @@ "lodash": "^4.17.15" } }, + "@uppy/utils": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-4.0.7.tgz", + "integrity": "sha512-nKViMT8XchKy+NWpb3DtVKuzZBmW7au26LrMq89EsvTwIOT6UR9+7bmz/+zr3+lc7UC7vMgNChIC6G+/Ya9wWQ==", + "requires": { + "lodash.throttle": "^4.1.1" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js index db356bce35..4a5359db19 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js @@ -1,6 +1,6 @@ import { Trans } from 'react-i18next' import { Alert, Button } from 'react-bootstrap' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import PropTypes from 'prop-types' import Uppy from '@uppy/core' import XHRUpload from '@uppy/xhr-upload' @@ -14,7 +14,8 @@ import { refreshProjectMetadata } from '../../../util/api' import ErrorMessage from '../error-message' export default function FileTreeUploadDoc() { - const { parentFolderId, cancel, isDuplicate } = useFileTreeActionable() + const { parentFolderId, cancel, isDuplicate, droppedFiles, setDroppedFiles } = + useFileTreeActionable() const { _id: projectId } = useProjectContext(projectContextPropTypes) const [error, setError] = useState() @@ -28,16 +29,22 @@ export default function FileTreeUploadDoc() { // calculate conflicts const buildConflicts = files => Object.values(files).filter(file => - isDuplicate(parentFolderId, file.meta.name) + isDuplicate(file.meta.targetFolderId ?? parentFolderId, file.meta.name) ) + const buildEndpoint = (projectId, targetFolderId) => { + let endpoint = `/project/${projectId}/upload` + + if (targetFolderId) { + endpoint += `?folder_id=${targetFolderId}` + } + + return endpoint + } + // initialise the Uppy object const uppy = useUppy(() => { - let endpoint = `/project/${projectId}/upload` - - if (parentFolderId) { - endpoint += `?folder_id=${parentFolderId}` - } + const endpoint = buildEndpoint(projectId, parentFolderId) return ( new Uppy({ @@ -113,6 +120,37 @@ export default function FileTreeUploadDoc() { ) }) + useEffect(() => { + if (uppy && droppedFiles) { + uppy.setOptions({ + autoProceed: false, + }) + for (const file of droppedFiles.files) { + const fileId = uppy.addFile({ + name: file.name, + type: file.type, + data: file, + source: 'Local', + isRemote: false, + meta: { + targetFolderId: droppedFiles.targetFolderId, + }, + }) + const uppyFile = uppy.getFile(fileId) + uppy.setFileState(fileId, { + xhrUpload: { + ...uppyFile.xhrUpload, + endpoint: buildEndpoint(projectId, droppedFiles.targetFolderId), + }, + }) + } + } + + return () => { + setDroppedFiles(null) + } + }, [uppy, droppedFiles, setDroppedFiles, projectId]) + // handle forced overwriting of conflicting files const handleOverwrite = useCallback(() => { setOverwrite(true) diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.js b/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.js index 2e8e1f6860..1eae7619f8 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.js +++ b/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.js @@ -20,7 +20,7 @@ function FileTreeDraggablePreviewLayer({ isOver }) { ? ref.current.getBoundingClientRect() : null - if (!isDragging) { + if (!isDragging || !item.title) { return null } diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js index 061e40de89..2376148131 100644 --- a/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js +++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js @@ -5,6 +5,7 @@ import { useReducer, useContext, useEffect, + useState, } from 'react' import PropTypes from 'prop-types' @@ -132,6 +133,8 @@ export function FileTreeActionableProvider({ children }) { const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeData() const { selectedEntityIds } = useFileTreeSelectable() + const [droppedFiles, setDroppedFiles] = useState(null) + const startRenaming = useCallback(() => { dispatch({ type: ACTION_TYPES.START_RENAME }) }, []) @@ -363,6 +366,8 @@ export function FileTreeActionableProvider({ children }) { finishCreatingDoc, finishCreatingLinkedFile, cancel, + droppedFiles, + setDroppedFiles, } return ( diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.js index bdd762dff8..b9d81e57a2 100644 --- a/services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.js +++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.js @@ -1,9 +1,14 @@ import { useRef, useEffect, useState } from 'react' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' +import getDroppedFiles from '@uppy/utils/lib/getDroppedFiles' import { DndProvider, createDndContext, useDrag, useDrop } from 'react-dnd' -import { HTML5Backend, getEmptyImage } from 'react-dnd-html5-backend' +import { + HTML5Backend, + getEmptyImage, + NativeTypes, +} from 'react-dnd-html5-backend' import { findAllInTreeOrThrow, @@ -119,20 +124,32 @@ const editorContextPropTypes = { } export function useDroppable(droppedEntityId) { - const { finishMoving } = useFileTreeActionable() + const { finishMoving, setDroppedFiles, startUploadingDocOrFile } = + useFileTreeActionable() const [{ isOver }, dropRef] = useDrop({ - accept: DRAGGABLE_TYPE, + accept: [DRAGGABLE_TYPE, NativeTypes.FILE], canDrop: (item, monitor) => { const isOver = monitor.isOver({ shallow: true }) if (!isOver) return false - if (item.forbiddenFolderIds.has(droppedEntityId)) return false + if ( + item.type === DRAGGABLE_TYPE && + item.forbiddenFolderIds.has(droppedEntityId) + ) + return false return true }, drop: (item, monitor) => { const didDropInChild = monitor.didDrop() if (didDropInChild) return - finishMoving(droppedEntityId, item.draggedEntityIds) + if (item.type === DRAGGABLE_TYPE) { + finishMoving(droppedEntityId, item.draggedEntityIds) + } else { + getDroppedFiles(item).then(files => { + setDroppedFiles({ files, targetFolderId: droppedEntityId }) + startUploadingDocOrFile() + }) + } }, collect: monitor => ({ isOver: monitor.canDrop(), diff --git a/services/web/package.json b/services/web/package.json index 0168e533bc..e36dcff17b 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -76,6 +76,7 @@ "@uppy/core": "^1.15.0", "@uppy/dashboard": "^1.11.0", "@uppy/react": "^1.11.0", + "@uppy/utils": "^4.0.7", "@uppy/xhr-upload": "^1.6.8", "abort-controller": "^3.0.0", "accepts": "^1.3.7",