Skip to content

Commit f4fd180

Browse files
committed
Here’s the revised commit message:
[processing#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 <ConnectedFileNode> 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.
1 parent 9bd7c99 commit f4fd180

File tree

6 files changed

+148
-5
lines changed

6 files changed

+148
-5
lines changed

client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export const SHOW_FOLDER_CHILDREN = 'SHOW_FOLDER_CHILDREN';
7878
export const HIDE_FOLDER_CHILDREN = 'HIDE_FOLDER_CHILDREN';
7979
export const OPEN_UPLOAD_FILE_MODAL = 'OPEN_UPLOAD_FILE_MODAL';
8080
export const CLOSE_UPLOAD_FILE_MODAL = 'CLOSE_UPLOAD_FILE_MODAL';
81+
export const INITIALIZE_SIDEBAR_UPLOAD = 'INITIALIZE_SIDEBAR_UPLOAD';
8182

8283
export const SHOW_SHARE_MODAL = 'SHOW_SHARE_MODAL';
8384
export const CLOSE_SHARE_MODAL = 'CLOSE_SHARE_MODAL';

client/modules/IDE/actions/ide.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ export function closeUploadFileModal() {
9191
};
9292
}
9393

94+
export function initSidebarUpload(parentId) {
95+
return {
96+
type: ActionTypes.INITIALIZE_SIDEBAR_UPLOAD,
97+
parentId
98+
};
99+
}
100+
94101
export function expandSidebar() {
95102
return {
96103
type: ActionTypes.EXPAND_SIDEBAR

client/modules/IDE/actions/uploader.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ export async function dropzoneAcceptCallback(userId, file, done) {
2222
file.content = await file.text();
2323
// Make it an error so that it won't be sent to S3, but style as a success.
2424
done('Uploading plaintext file locally.');
25-
file.previewElement.classList.remove('dz-error');
26-
file.previewElement.classList.add('dz-success');
27-
file.previewElement.classList.add('dz-processing');
28-
file.previewElement.querySelector('.dz-upload').style.width = '100%';
25+
if (file.previewElement) {
26+
file.previewElement.classList.remove('dz-error');
27+
file.previewElement.classList.add('dz-success');
28+
file.previewElement.classList.add('dz-processing');
29+
file.previewElement.querySelector('.dz-upload').style.width = '100%';
30+
}
2931
} catch (error) {
3032
done(`Failed to download file ${file.name}: ${error}`);
3133
console.warn(file);

client/modules/IDE/components/Sidebar.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getAuthenticated, selectCanEditSketch } from '../selectors/users';
1616
import ConnectedFileNode from './FileNode';
1717
import { PlusIcon } from '../../../common/icons';
1818
import { FileDrawer } from './Editor/MobileEditor';
19+
import SidebarFileDragDropUploadWrapper from './SidebarFileDragDropUploadWrapper';
1920

2021
// TODO: use a generic Dropdown UI component
2122

@@ -130,7 +131,9 @@ export default function SideBar() {
130131
</ul>
131132
</div>
132133
</header>
133-
<ConnectedFileNode id={rootFile.id} canEdit={canEditProject} />
134+
<SidebarFileDragDropUploadWrapper>
135+
<ConnectedFileNode id={rootFile.id} canEdit={canEditProject} />
136+
</SidebarFileDragDropUploadWrapper>
134137
</section>
135138
</FileDrawer>
136139
);
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Dropzone from 'dropzone';
2+
import React, { useEffect } from 'react';
3+
import PropTypes from 'prop-types';
4+
import { useDispatch, useSelector } from 'react-redux';
5+
import { useTranslation } from 'react-i18next';
6+
import styled from 'styled-components';
7+
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
8+
import {
9+
dropzoneAcceptCallback,
10+
dropzoneCompleteCallback,
11+
dropzoneSendingCallback,
12+
s3BucketHttps
13+
} from '../actions/uploader';
14+
import { selectActiveFile, selectRootFile } from '../selectors/files';
15+
import { initSidebarUpload } from '../actions/ide';
16+
17+
Dropzone.autoDiscover = false;
18+
19+
// Styled Components
20+
const Wrapper = styled.div`
21+
height: 100%;
22+
display: flex;
23+
position: relative;
24+
`;
25+
26+
const HiddenInputContainer = styled.div``;
27+
28+
const DraggingOverMessage = styled.div`
29+
position: absolute;
30+
height: 100%;
31+
width: 100%;
32+
background-color: rgba(0, 0, 0, 0.5);
33+
color: rgba(255, 255, 255, 0.85);
34+
display: none;
35+
justify-content: center;
36+
align-items: center;
37+
text-align: center;
38+
padding: 1rem;
39+
font-weight: semibold;
40+
transition: background-color 0.3s ease;
41+
`;
42+
43+
function SidebarFileDragDropUploadWrapper({ children }) {
44+
const { t } = useTranslation();
45+
const dispatch = useDispatch();
46+
const userId = useSelector((state) => state.user.id);
47+
48+
const rootFile = useSelector(selectRootFile);
49+
const activeFile = useSelector(selectActiveFile);
50+
51+
useEffect(() => {
52+
dispatch(
53+
initSidebarUpload(
54+
activeFile.fileType === 'folder' ? activeFile.id : rootFile.id
55+
)
56+
);
57+
58+
let dragCounter = 0;
59+
60+
const uploader = new Dropzone('div#sidebar-file-drag-drop-upload-wrapper', {
61+
url: s3BucketHttps,
62+
method: 'post',
63+
autoProcessQueue: false,
64+
clickable: false,
65+
hiddenInputContainer: '#sidebar-hidden-input-container',
66+
maxFiles: 6,
67+
parallelUploads: 1,
68+
maxFilesize: 5, // in MB
69+
maxThumbnailFilesize: 8, // in MB
70+
thumbnailWidth: 10,
71+
previewsContainer: false,
72+
thumbnailHeight: 10,
73+
acceptedFiles: fileExtensionsAndMimeTypes,
74+
dictDefaultMessage: t('FileUploader.DictDefaultMessage'),
75+
accept: (file, done) => {
76+
dropzoneAcceptCallback(userId, file, done);
77+
},
78+
sending: dropzoneSendingCallback
79+
});
80+
81+
uploader.on('complete', (file) => {
82+
dispatch(dropzoneCompleteCallback(file));
83+
});
84+
85+
uploader.on('dragenter', () => {
86+
dragCounter += 1;
87+
document.getElementById('dragging-over-message').style.display = 'flex';
88+
});
89+
90+
uploader.on('dragleave', () => {
91+
dragCounter -= 1;
92+
93+
if (dragCounter <= 0) {
94+
dragCounter = 0;
95+
document.getElementById('dragging-over-message').style.display = 'none';
96+
}
97+
});
98+
99+
uploader.on('drop', () => {
100+
dragCounter = 0;
101+
document.getElementById('dragging-over-message').style.display = 'none';
102+
});
103+
104+
return () => {
105+
uploader.off('complete');
106+
uploader.off('dragenter');
107+
uploader.off('dragleave');
108+
uploader.off('drop');
109+
uploader.destroy();
110+
};
111+
}, [userId, dispatch, t, activeFile]);
112+
113+
return (
114+
<Wrapper id="sidebar-file-drag-drop-upload-wrapper">
115+
<HiddenInputContainer id="sidebar-hidden-input-container" />
116+
{children}
117+
<DraggingOverMessage id="dragging-over-message">
118+
{t('FileUploader.DictDefaultMessage')}
119+
</DraggingOverMessage>
120+
</Wrapper>
121+
);
122+
}
123+
124+
SidebarFileDragDropUploadWrapper.propTypes = {
125+
children: PropTypes.node.isRequired
126+
};
127+
128+
export default SidebarFileDragDropUploadWrapper;

client/modules/IDE/reducers/ide.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ const ide = (state = initialState, action) => {
122122
});
123123
case ActionTypes.CLOSE_UPLOAD_FILE_MODAL:
124124
return Object.assign({}, state, { uploadFileModalVisible: false });
125+
case ActionTypes.INITIALIZE_SIDEBAR_UPLOAD:
126+
return Object.assign({}, state, { parentId: action.parentId });
125127
default:
126128
return state;
127129
}

0 commit comments

Comments
 (0)