From f4fd1809bd4548813954b23c28bf9e25c6a0c7d0 Mon Sep 17 00:00:00 2001 From: sam-shubham Date: Wed, 25 Dec 2024 02:46:33 +0530 Subject: [PATCH] =?UTF-8?q?Here=E2=80=99s=20the=20revised=20commit=20messa?= =?UTF-8?q?ge:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#3086] Enable Sidebar Drag and Drop with Multi-level Uploads Enhanced the Sketch Files sidebar to support drag-and-drop file uploads, improving accessibility and workflow. Details: • Introduced a wrapper component to handle drag-and-drop functionality. • Enabled multi-level uploads, allowing files to be uploaded into nested directories. • Updated initSidebarUpload to dynamically initialize uploads for the active folder or root directory. • Integrated drag-and-drop with for seamless uploads. Improvements: • Simplified file upload process by removing the need for [+] > [Upload File]. • Enhanced user feedback with dynamic drag-over messages. This update improves user workflow and sets the foundation for future features like cross-sketch file duplication. --- client/constants.js | 1 + client/modules/IDE/actions/ide.js | 7 + client/modules/IDE/actions/uploader.js | 10 +- client/modules/IDE/components/Sidebar.jsx | 5 +- .../SidebarFileDragDropUploadWrapper.jsx | 128 ++++++++++++++++++ client/modules/IDE/reducers/ide.js | 2 + 6 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 client/modules/IDE/components/SidebarFileDragDropUploadWrapper.jsx diff --git a/client/constants.js b/client/constants.js index fa6010aed1..5990b3597a 100644 --- a/client/constants.js +++ b/client/constants.js @@ -78,6 +78,7 @@ export const SHOW_FOLDER_CHILDREN = 'SHOW_FOLDER_CHILDREN'; export const HIDE_FOLDER_CHILDREN = 'HIDE_FOLDER_CHILDREN'; export const OPEN_UPLOAD_FILE_MODAL = 'OPEN_UPLOAD_FILE_MODAL'; export const CLOSE_UPLOAD_FILE_MODAL = 'CLOSE_UPLOAD_FILE_MODAL'; +export const INITIALIZE_SIDEBAR_UPLOAD = 'INITIALIZE_SIDEBAR_UPLOAD'; export const SHOW_SHARE_MODAL = 'SHOW_SHARE_MODAL'; export const CLOSE_SHARE_MODAL = 'CLOSE_SHARE_MODAL'; diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 80a43443bc..6e4e9d664b 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -91,6 +91,13 @@ export function closeUploadFileModal() { }; } +export function initSidebarUpload(parentId) { + return { + type: ActionTypes.INITIALIZE_SIDEBAR_UPLOAD, + parentId + }; +} + export function expandSidebar() { return { type: ActionTypes.EXPAND_SIDEBAR diff --git a/client/modules/IDE/actions/uploader.js b/client/modules/IDE/actions/uploader.js index e2831df75f..54a7886ea9 100644 --- a/client/modules/IDE/actions/uploader.js +++ b/client/modules/IDE/actions/uploader.js @@ -22,10 +22,12 @@ export async function dropzoneAcceptCallback(userId, file, done) { file.content = await file.text(); // Make it an error so that it won't be sent to S3, but style as a success. done('Uploading plaintext file locally.'); - file.previewElement.classList.remove('dz-error'); - file.previewElement.classList.add('dz-success'); - file.previewElement.classList.add('dz-processing'); - file.previewElement.querySelector('.dz-upload').style.width = '100%'; + if (file.previewElement) { + file.previewElement.classList.remove('dz-error'); + file.previewElement.classList.add('dz-success'); + file.previewElement.classList.add('dz-processing'); + file.previewElement.querySelector('.dz-upload').style.width = '100%'; + } } catch (error) { done(`Failed to download file ${file.name}: ${error}`); console.warn(file); diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx index 79ffed3b86..23236ddc89 100644 --- a/client/modules/IDE/components/Sidebar.jsx +++ b/client/modules/IDE/components/Sidebar.jsx @@ -16,6 +16,7 @@ import { getAuthenticated, selectCanEditSketch } from '../selectors/users'; import ConnectedFileNode from './FileNode'; import { PlusIcon } from '../../../common/icons'; import { FileDrawer } from './Editor/MobileEditor'; +import SidebarFileDragDropUploadWrapper from './SidebarFileDragDropUploadWrapper'; // TODO: use a generic Dropdown UI component @@ -130,7 +131,9 @@ export default function SideBar() { - + + + ); diff --git a/client/modules/IDE/components/SidebarFileDragDropUploadWrapper.jsx b/client/modules/IDE/components/SidebarFileDragDropUploadWrapper.jsx new file mode 100644 index 0000000000..eaf9067475 --- /dev/null +++ b/client/modules/IDE/components/SidebarFileDragDropUploadWrapper.jsx @@ -0,0 +1,128 @@ +import Dropzone from 'dropzone'; +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils'; +import { + dropzoneAcceptCallback, + dropzoneCompleteCallback, + dropzoneSendingCallback, + s3BucketHttps +} from '../actions/uploader'; +import { selectActiveFile, selectRootFile } from '../selectors/files'; +import { initSidebarUpload } from '../actions/ide'; + +Dropzone.autoDiscover = false; + +// Styled Components +const Wrapper = styled.div` + height: 100%; + display: flex; + position: relative; +`; + +const HiddenInputContainer = styled.div``; + +const DraggingOverMessage = styled.div` + position: absolute; + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0.5); + color: rgba(255, 255, 255, 0.85); + display: none; + justify-content: center; + align-items: center; + text-align: center; + padding: 1rem; + font-weight: semibold; + transition: background-color 0.3s ease; +`; + +function SidebarFileDragDropUploadWrapper({ children }) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const userId = useSelector((state) => state.user.id); + + const rootFile = useSelector(selectRootFile); + const activeFile = useSelector(selectActiveFile); + + useEffect(() => { + dispatch( + initSidebarUpload( + activeFile.fileType === 'folder' ? activeFile.id : rootFile.id + ) + ); + + let dragCounter = 0; + + const uploader = new Dropzone('div#sidebar-file-drag-drop-upload-wrapper', { + url: s3BucketHttps, + method: 'post', + autoProcessQueue: false, + clickable: false, + hiddenInputContainer: '#sidebar-hidden-input-container', + maxFiles: 6, + parallelUploads: 1, + maxFilesize: 5, // in MB + maxThumbnailFilesize: 8, // in MB + thumbnailWidth: 10, + previewsContainer: false, + thumbnailHeight: 10, + acceptedFiles: fileExtensionsAndMimeTypes, + dictDefaultMessage: t('FileUploader.DictDefaultMessage'), + accept: (file, done) => { + dropzoneAcceptCallback(userId, file, done); + }, + sending: dropzoneSendingCallback + }); + + uploader.on('complete', (file) => { + dispatch(dropzoneCompleteCallback(file)); + }); + + uploader.on('dragenter', () => { + dragCounter += 1; + document.getElementById('dragging-over-message').style.display = 'flex'; + }); + + uploader.on('dragleave', () => { + dragCounter -= 1; + + if (dragCounter <= 0) { + dragCounter = 0; + document.getElementById('dragging-over-message').style.display = 'none'; + } + }); + + uploader.on('drop', () => { + dragCounter = 0; + document.getElementById('dragging-over-message').style.display = 'none'; + }); + + return () => { + uploader.off('complete'); + uploader.off('dragenter'); + uploader.off('dragleave'); + uploader.off('drop'); + uploader.destroy(); + }; + }, [userId, dispatch, t, activeFile]); + + return ( + + + {children} + + {t('FileUploader.DictDefaultMessage')} + + + ); +} + +SidebarFileDragDropUploadWrapper.propTypes = { + children: PropTypes.node.isRequired +}; + +export default SidebarFileDragDropUploadWrapper; diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js index 8d45b8b50a..09c18faa18 100644 --- a/client/modules/IDE/reducers/ide.js +++ b/client/modules/IDE/reducers/ide.js @@ -122,6 +122,8 @@ const ide = (state = initialState, action) => { }); case ActionTypes.CLOSE_UPLOAD_FILE_MODAL: return Object.assign({}, state, { uploadFileModalVisible: false }); + case ActionTypes.INITIALIZE_SIDEBAR_UPLOAD: + return Object.assign({}, state, { parentId: action.parentId }); default: return state; }