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 (
+
+ );
+}
+
+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;
}