Handle max file size before we process with server
This commit is contained in:
parent
c38ed64308
commit
1ce5b5b34f
3 changed files with 139 additions and 1 deletions
103
app/soapbox/actions/__tests__/compose.test.js
Normal file
103
app/soapbox/actions/__tests__/compose.test.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { InstanceRecord } from 'soapbox/normalizers';
|
||||||
|
import rootReducer from 'soapbox/reducers';
|
||||||
|
import { mockStore } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
|
import { uploadCompose } from '../compose';
|
||||||
|
|
||||||
|
describe('uploadCompose()', () => {
|
||||||
|
describe('with images', () => {
|
||||||
|
let files, store;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const instance = InstanceRecord({
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_media_attachments: 4,
|
||||||
|
},
|
||||||
|
media_attachments: {
|
||||||
|
image_size_limit: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = rootReducer(undefined, {})
|
||||||
|
.set('me', '1234')
|
||||||
|
.set('instance', instance);
|
||||||
|
|
||||||
|
store = mockStore(state);
|
||||||
|
files = [{
|
||||||
|
uri: 'image.png',
|
||||||
|
name: 'Image',
|
||||||
|
size: 15,
|
||||||
|
type: 'image/png',
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates an alert if exceeds max size', async() => {
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true },
|
||||||
|
{
|
||||||
|
type: 'ALERT_SHOW',
|
||||||
|
message: 'Image exceeds the current file size limit (10 Bytes)',
|
||||||
|
actionLabel: undefined,
|
||||||
|
actionLink: undefined,
|
||||||
|
severity: 'error',
|
||||||
|
},
|
||||||
|
{ type: 'COMPOSE_UPLOAD_FAIL', error: true, skipLoading: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
await store.dispatch(uploadCompose(files));
|
||||||
|
const actions = store.getActions();
|
||||||
|
|
||||||
|
expect(actions).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with videos', () => {
|
||||||
|
let files, store;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const instance = InstanceRecord({
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_media_attachments: 4,
|
||||||
|
},
|
||||||
|
media_attachments: {
|
||||||
|
video_size_limit: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = rootReducer(undefined, {})
|
||||||
|
.set('me', '1234')
|
||||||
|
.set('instance', instance);
|
||||||
|
|
||||||
|
store = mockStore(state);
|
||||||
|
files = [{
|
||||||
|
uri: 'video.mp4',
|
||||||
|
name: 'Video',
|
||||||
|
size: 15,
|
||||||
|
type: 'video/mp4',
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates an alert if exceeds max size', async() => {
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true },
|
||||||
|
{
|
||||||
|
type: 'ALERT_SHOW',
|
||||||
|
message: 'Video exceeds the current file size limit (10 Bytes)',
|
||||||
|
actionLabel: undefined,
|
||||||
|
actionLink: undefined,
|
||||||
|
severity: 'error',
|
||||||
|
},
|
||||||
|
{ type: 'COMPOSE_UPLOAD_FAIL', error: true, skipLoading: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
await store.dispatch(uploadCompose(files));
|
||||||
|
const actions = store.getActions();
|
||||||
|
|
||||||
|
expect(actions).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,6 +6,7 @@ import { defineMessages } from 'react-intl';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
import { formatBytes } from 'soapbox/utils/media';
|
||||||
|
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
||||||
|
@ -299,6 +300,8 @@ export function uploadCompose(files) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
const attachmentLimit = getState().getIn(['instance', 'configuration', 'statuses', 'max_media_attachments']);
|
const attachmentLimit = getState().getIn(['instance', 'configuration', 'statuses', 'max_media_attachments']);
|
||||||
|
const maxImageSize = getState().getIn(['instance', 'configuration', 'media_attachments', 'image_size_limit']);
|
||||||
|
const maxVideoSize = getState().getIn(['instance', 'configuration', 'media_attachments', 'video_size_limit']);
|
||||||
|
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
const progress = new Array(files.length).fill(0);
|
const progress = new Array(files.length).fill(0);
|
||||||
|
@ -314,6 +317,20 @@ export function uploadCompose(files) {
|
||||||
Array.from(files).forEach((f, i) => {
|
Array.from(files).forEach((f, i) => {
|
||||||
if (media.size + i > attachmentLimit - 1) return;
|
if (media.size + i > attachmentLimit - 1) return;
|
||||||
|
|
||||||
|
const isImage = f.type.match(/image.*/);
|
||||||
|
const isVideo = f.type.match(/video.*/);
|
||||||
|
if (isImage && maxImageSize && (f.size > maxImageSize)) {
|
||||||
|
const message = `Image exceeds the current file size limit (${formatBytes(maxImageSize)})`;
|
||||||
|
dispatch(snackbar.error(message));
|
||||||
|
dispatch(uploadComposeFail(true));
|
||||||
|
return;
|
||||||
|
} else if (isVideo && maxVideoSize && (f.size > maxVideoSize)) {
|
||||||
|
const message = `Video exceeds the current file size limit (${formatBytes(maxVideoSize)})`;
|
||||||
|
dispatch(snackbar.error(message));
|
||||||
|
dispatch(uploadComposeFail(true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Don't define function in loop
|
// FIXME: Don't define function in loop
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
resizeImage(f).then(file => {
|
resizeImage(f).then(file => {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
export const truncateFilename = (url, maxLength) => {
|
const truncateFilename = (url: string, maxLength: number) => {
|
||||||
const filename = url.split('/').pop();
|
const filename = url.split('/').pop();
|
||||||
|
|
||||||
|
if (!filename) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
if (filename.length <= maxLength) return filename;
|
if (filename.length <= maxLength) return filename;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -8,3 +12,17 @@ export const truncateFilename = (url, maxLength) => {
|
||||||
filename.substr(filename.length - maxLength/2),
|
filename.substr(filename.length - maxLength/2),
|
||||||
].join('…');
|
].join('…');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatBytes = (bytes: number, decimals: number = 2) => {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
export { formatBytes, truncateFilename };
|
||||||
|
|
Loading…
Reference in a new issue