Allow multiple files upload through web UI, including drag & drop (#9856)
* Allow drag and drop uploads of multiple files to compose * Calculate aggregate upload progress for single action * Allow multiple uploads to compose through traditional input, consolidate update file limit logic, provide file limit feedback
This commit is contained in:
parent
582f86ab32
commit
750c67660d
|
@ -22,7 +22,7 @@ export function clearAlert() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function showAlert(title, message) {
|
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) {
|
||||||
return {
|
return {
|
||||||
type: ALERT_SHOW,
|
type: ALERT_SHOW,
|
||||||
title,
|
title,
|
||||||
|
@ -44,6 +44,6 @@ export function showAlertForError(error) {
|
||||||
return showAlert(title, message);
|
return showAlert(title, message);
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return showAlert(messages.unexpectedTitle, messages.unexpectedMessage);
|
return showAlert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image';
|
||||||
import { importFetchedAccounts } from './importer';
|
import { importFetchedAccounts } from './importer';
|
||||||
import { updateTimeline } from './timelines';
|
import { updateTimeline } from './timelines';
|
||||||
import { showAlertForError } from './alerts';
|
import { showAlertForError } from './alerts';
|
||||||
|
import { showAlert } from './alerts';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
let cancelFetchComposeSuggestionsAccounts;
|
let cancelFetchComposeSuggestionsAccounts;
|
||||||
|
|
||||||
|
@ -49,6 +51,10 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'
|
||||||
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
||||||
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||||
|
});
|
||||||
|
|
||||||
export function changeCompose(text) {
|
export function changeCompose(text) {
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_CHANGE,
|
type: COMPOSE_CHANGE,
|
||||||
|
@ -184,21 +190,33 @@ export function submitComposeFail(error) {
|
||||||
|
|
||||||
export function uploadCompose(files) {
|
export function uploadCompose(files) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
if (getState().getIn(['compose', 'media_attachments']).size > 3) {
|
const uploadLimit = 4;
|
||||||
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
|
const total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||||
|
const progress = new Array(files.length).fill(0);
|
||||||
|
|
||||||
|
if (files.length + media.size > uploadLimit) {
|
||||||
|
dispatch(showAlert(undefined, messages.uploadErrorLimit));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(uploadComposeRequest());
|
dispatch(uploadComposeRequest());
|
||||||
|
|
||||||
resizeImage(files[0]).then(file => {
|
for (const [i, f] of Array.from(files).entries()) {
|
||||||
|
if (media.size + i > 3) break;
|
||||||
|
|
||||||
|
resizeImage(f).then(file => {
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
|
||||||
return api(getState).post('/api/v1/media', data, {
|
return api(getState).post('/api/v1/media', data, {
|
||||||
onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)),
|
onUploadProgress: function({ loaded }){
|
||||||
|
progress[i] = loaded;
|
||||||
|
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||||
|
},
|
||||||
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
||||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||||
};
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function changeUploadCompose(id, params) {
|
export function changeUploadCompose(id, params) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
key={resetFileKey}
|
key={resetFileKey}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
type='file'
|
type='file'
|
||||||
multiple={false}
|
multiple
|
||||||
accept={acceptContentTypes.toArray().join(',')}
|
accept={acceptContentTypes.toArray().join(',')}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -263,7 +263,7 @@ class UI extends React.PureComponent {
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
this.dragTargets = [];
|
this.dragTargets = [];
|
||||||
|
|
||||||
if (e.dataTransfer && e.dataTransfer.files.length === 1) {
|
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
|
||||||
this.props.dispatch(uploadCompose(e.dataTransfer.files));
|
this.props.dispatch(uploadCompose(e.dataTransfer.files));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,15 @@
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/actions/alerts.json"
|
"path": "app/javascript/mastodon/actions/alerts.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"descriptors": [
|
||||||
|
{
|
||||||
|
"defaultMessage": "File upload limit exceeded.",
|
||||||
|
"id": "upload_error.limit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "app/javascript/mastodon/actions/compose.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -342,6 +342,7 @@
|
||||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||||
"upload_area.title": "Drag & drop to upload",
|
"upload_area.title": "Drag & drop to upload",
|
||||||
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
|
"upload_error.limit": "File upload limit exceeded.",
|
||||||
"upload_form.description": "Describe for the visually impaired",
|
"upload_form.description": "Describe for the visually impaired",
|
||||||
"upload_form.focus": "Change preview",
|
"upload_form.focus": "Change preview",
|
||||||
"upload_form.undo": "Delete",
|
"upload_form.undo": "Delete",
|
||||||
|
|
Reference in New Issue