Skip to content

Commit fa70bff

Browse files
committed
cleanup uploader code
1 parent d6db24b commit fa70bff

File tree

3 files changed

+91
-186
lines changed

3 files changed

+91
-186
lines changed

client/modules/IDE/actions/uploader.js

Lines changed: 46 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,68 @@
1+
import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils';
12
import apiClient from '../../../utils/apiClient';
23
import getConfig from '../../../utils/getConfig';
34
import { handleCreateFile } from './files';
4-
import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils';
55

6-
const s3BucketHttps =
6+
export const s3BucketHttps =
77
getConfig('S3_BUCKET_URL_BASE') ||
88
`https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig(
99
'S3_BUCKET'
1010
)}/`;
1111
const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB
1212

13-
function localIntercept(file, options = {}) {
14-
return new Promise((resolve, reject) => {
15-
if (!options.readType) {
16-
// const mime = file.type;
17-
// const textType = a(_textTypes).any(type => {
18-
// const re = new RegExp(type);
19-
// return re.test(mime);
20-
// });
21-
// options.readType = textType ? 'readAsText' : 'readAsDataURL';
22-
options.readType = 'readAsText'; // eslint-disable-line
23-
}
24-
const reader = new window.FileReader();
25-
reader.onload = () => {
26-
resolve(reader.result);
27-
};
28-
reader.onerror = () => {
29-
reject(reader.result);
30-
};
31-
32-
// run the reader
33-
reader[options.readType](file);
34-
});
35-
}
36-
37-
function toBinary(string) {
38-
const codeUnits = new Uint16Array(string.length);
39-
for (let i = 0; i < codeUnits.length; i += 1) {
40-
codeUnits[i] = string.charCodeAt(i);
41-
}
42-
return String.fromCharCode(...new Uint8Array(codeUnits.buffer));
13+
function isS3Upload(file) {
14+
return !TEXT_FILE_REGEX.test(file.name) || file.size >= MAX_LOCAL_FILE_SIZE;
4315
}
4416

45-
export function dropzoneAcceptCallback(userId, file, done) {
46-
return () => {
47-
// if a user would want to edit this file as text, local interceptor
48-
if (file.name.match(TEXT_FILE_REGEX) && file.size < MAX_LOCAL_FILE_SIZE) {
49-
localIntercept(file)
50-
.then((result) => {
51-
file.content = result; // eslint-disable-line
52-
done('Uploading plaintext file locally.');
53-
file.previewElement.classList.remove('dz-error');
54-
file.previewElement.classList.add('dz-success');
55-
file.previewElement.classList.add('dz-processing');
56-
file.previewElement.querySelector('.dz-upload').style.width = '100%';
57-
})
58-
.catch((result) => {
59-
done(`Failed to download file ${file.name}: ${result}`);
60-
console.warn(file);
61-
});
62-
} else {
63-
file.postData = []; // eslint-disable-line
64-
apiClient
65-
.post('/S3/sign', {
66-
name: file.name,
67-
type: file.type,
68-
size: file.size,
69-
userId
70-
// _csrf: document.getElementById('__createPostToken').value
71-
})
72-
.then((response) => {
73-
file.custom_status = 'ready'; // eslint-disable-line
74-
file.postData = response.data; // eslint-disable-line
75-
file.s3 = response.data.key; // eslint-disable-line
76-
file.previewTemplate.className += ' uploading'; // eslint-disable-line
77-
done();
78-
})
79-
.catch((error) => {
80-
const { response } = error;
81-
file.custom_status = 'rejected'; // eslint-disable-line
82-
if (
83-
response.data &&
84-
response.data.responseText &&
85-
response.data.responseText.message
86-
) {
87-
done(response.data.responseText.message);
88-
}
89-
done('Error: Reached upload limit.');
90-
});
17+
export async function dropzoneAcceptCallback(userId, file, done) {
18+
// if a user would want to edit this file as text, local interceptor
19+
if (!isS3Upload(file)) {
20+
try {
21+
// eslint-disable-next-line no-param-reassign
22+
file.content = await file.text();
23+
// Make it an error so that it won't be sent to S3, but style as a success.
24+
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%';
29+
} catch (error) {
30+
done(`Failed to download file ${file.name}: ${error}`);
31+
console.warn(file);
9132
}
92-
};
33+
} else {
34+
try {
35+
const response = await apiClient.post('/S3/sign', {
36+
name: file.name,
37+
type: file.type,
38+
size: file.size,
39+
userId
40+
// _csrf: document.getElementById('__createPostToken').value
41+
});
42+
// eslint-disable-next-line no-param-reassign
43+
file.postData = response.data;
44+
done();
45+
} catch (error) {
46+
done(
47+
error?.response?.data?.responseText?.message ||
48+
error?.message ||
49+
'Error: Reached upload limit.'
50+
);
51+
}
52+
}
9353
}
9454

9555
export function dropzoneSendingCallback(file, xhr, formData) {
96-
return () => {
97-
if (!file.name.match(TEXT_FILE_REGEX) || file.size >= MAX_LOCAL_FILE_SIZE) {
98-
Object.keys(file.postData).forEach((key) => {
99-
formData.append(key, file.postData[key]);
100-
});
101-
}
102-
};
56+
if (isS3Upload(file)) {
57+
Object.keys(file.postData).forEach((key) => {
58+
formData.append(key, file.postData[key]);
59+
});
60+
}
10361
}
10462

10563
export function dropzoneCompleteCallback(file) {
106-
return (dispatch) => { // eslint-disable-line
107-
if (
108-
(!file.name.match(TEXT_FILE_REGEX) || file.size >= MAX_LOCAL_FILE_SIZE) &&
109-
file.status !== 'error'
110-
) {
111-
let inputHidden = '<input type="hidden" name="attachments[]" value="';
112-
const json = {
113-
url: `${s3BucketHttps}${file.postData.key}`,
114-
originalFilename: file.name
115-
};
116-
117-
let jsonStr = JSON.stringify(json);
118-
119-
// convert the json string to binary data so that btoa can encode it
120-
jsonStr = toBinary(jsonStr);
121-
inputHidden += `${window.btoa(jsonStr)}" />`;
122-
document.getElementById('uploader').innerHTML += inputHidden;
123-
64+
return (dispatch) => {
65+
if (isS3Upload(file) && file.postData && file.status !== 'error') {
12466
const formParams = {
12567
name: file.name,
12668
url: `${s3BucketHttps}${file.postData.key}`
Lines changed: 45 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
1-
import PropTypes from 'prop-types';
2-
import React from 'react';
31
import Dropzone from 'dropzone';
4-
import { bindActionCreators } from 'redux';
5-
import { connect } from 'react-redux';
6-
import { withTranslation } from 'react-i18next';
7-
import * as UploaderActions from '../actions/uploader';
8-
import getConfig from '../../../utils/getConfig';
2+
import React, { useEffect } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { useDispatch, useSelector } from 'react-redux';
5+
import styled from 'styled-components';
96
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
10-
11-
const s3Bucket =
12-
getConfig('S3_BUCKET_URL_BASE') ||
13-
`https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig(
14-
'S3_BUCKET'
15-
)}/`;
16-
17-
class FileUploader extends React.Component {
18-
componentDidMount() {
19-
this.createDropzone();
20-
Dropzone.autoDiscover = false;
7+
import { remSize } from '../../../theme';
8+
import {
9+
dropzoneAcceptCallback,
10+
dropzoneCompleteCallback,
11+
dropzoneSendingCallback,
12+
s3BucketHttps
13+
} from '../actions/uploader';
14+
15+
Dropzone.autoDiscover = false;
16+
17+
// TODO: theming for dark vs. light theme
18+
// TODO: include color and background-color settings after migrating the themify variables.
19+
const StyledUploader = styled.div`
20+
min-height: ${remSize(200)};
21+
width: 100%;
22+
text-align: center;
23+
.dz-preview.dz-image-preview {
24+
background-color: transparent;
2125
}
26+
`;
2227

23-
createDropzone() {
24-
const userId = this.props.project.owner
25-
? this.props.project.owner.id
26-
: this.props.user.id;
27-
this.uploader = new Dropzone('div#uploader', {
28-
url: s3Bucket,
28+
function FileUploader() {
29+
const { t } = useTranslation();
30+
const dispatch = useDispatch();
31+
const userId = useSelector((state) => state.user.id);
32+
33+
useEffect(() => {
34+
const uploader = new Dropzone('div#uploader', {
35+
url: s3BucketHttps,
2936
method: 'post',
3037
autoProcessQueue: true,
3138
clickable: true,
@@ -36,55 +43,21 @@ class FileUploader extends React.Component {
3643
thumbnailWidth: 200,
3744
thumbnailHeight: 200,
3845
acceptedFiles: fileExtensionsAndMimeTypes,
39-
dictDefaultMessage: this.props.t('FileUploader.DictDefaultMessage'),
40-
accept: this.props.dropzoneAcceptCallback.bind(this, userId),
41-
sending: this.props.dropzoneSendingCallback
46+
dictDefaultMessage: t('FileUploader.DictDefaultMessage'),
47+
accept: (file, done) => {
48+
dropzoneAcceptCallback(userId, file, done);
49+
},
50+
sending: dropzoneSendingCallback
4251
});
43-
this.uploader.on('complete', this.props.dropzoneCompleteCallback);
44-
}
45-
46-
render() {
47-
return <div id="uploader" className="uploader dropzone"></div>;
48-
}
49-
}
50-
51-
FileUploader.propTypes = {
52-
dropzoneAcceptCallback: PropTypes.func.isRequired,
53-
dropzoneSendingCallback: PropTypes.func.isRequired,
54-
dropzoneCompleteCallback: PropTypes.func.isRequired,
55-
project: PropTypes.shape({
56-
owner: PropTypes.shape({
57-
id: PropTypes.string
58-
})
59-
}),
60-
user: PropTypes.shape({
61-
id: PropTypes.string
62-
}),
63-
t: PropTypes.func.isRequired
64-
};
65-
66-
FileUploader.defaultProps = {
67-
project: {
68-
id: undefined,
69-
owner: undefined
70-
},
71-
user: {
72-
id: undefined
73-
}
74-
};
75-
76-
function mapStateToProps(state) {
77-
return {
78-
files: state.files,
79-
project: state.project,
80-
user: state.user
81-
};
82-
}
52+
uploader.on('complete', (file) => {
53+
dispatch(dropzoneCompleteCallback(file));
54+
});
55+
return () => {
56+
uploader.destroy();
57+
};
58+
}, [userId, t, dispatch]);
8359

84-
function mapDispatchToProps(dispatch) {
85-
return bindActionCreators(UploaderActions, dispatch);
60+
return <StyledUploader id="uploader" className="dropzone" />;
8661
}
8762

88-
export default withTranslation()(
89-
connect(mapStateToProps, mapDispatchToProps)(FileUploader)
90-
);
63+
export default FileUploader;

client/styles/components/_uploader.scss

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,3 @@
44
color: getThemifyVariable('input-text-color');
55
}
66
}
7-
8-
.dropzone .dz-preview.dz-image-preview {
9-
background-color: transparent;
10-
}
11-
12-
.uploader {
13-
min-height: #{200 / $base-font-size}rem;
14-
width: 100%;
15-
text-align: center;
16-
}

0 commit comments

Comments
 (0)