The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/ui/client/lib/fileUpload.js

283 lines
8.0 KiB

import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import s from 'underscore.string';
import { Handlebars } from 'meteor/ui';
import { Random } from 'meteor/random';
import { settings } from '../../../settings/client';
import { t, fileUploadIsValidContentType, APIClient } from '../../../utils';
import { modal, prependReplies } from '../../../ui-utils';
const readAsDataURL = (file, callback) => {
const reader = new FileReader();
reader.onload = (e) => callback(e.target.result, file);
return reader.readAsDataURL(file);
};
export const uploadFileWithMessage = async (rid, tmid, { description, fileName, msg, file }) => {
const data = new FormData();
description && data.append('description', description);
msg && data.append('msg', msg);
tmid && data.append('tmid', tmid);
data.append('file', file.file, fileName);
const uploads = Session.get('uploading') || [];
const upload = {
id: Random.id(),
name: fileName,
percentage: 0,
};
uploads.push(upload);
Session.set('uploading', uploads);
const { xhr, promise } = APIClient.upload(`v1/rooms.upload/${ rid }`, {}, data, {
progress(progress) {
const uploads = Session.get('uploading') || [];
if (progress === 100) {
return;
}
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.percentage = Math.round(progress) || 0;
});
Session.set('uploading', uploads);
},
error(error) {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.error = error.message;
u.percentage = 0;
});
Session.set('uploading', uploads);
},
});
Tracker.autorun((computation) => {
const isCanceling = Session.get(`uploading-cancel-${ upload.id }`);
if (!isCanceling) {
return;
}
computation.stop();
Session.delete(`uploading-cancel-${ upload.id }`);
xhr.abort();
const uploads = Session.get('uploading') || {};
Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
});
try {
await promise;
const uploads = Session.get('uploading') || [];
return Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
} catch (error) {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message;
u.percentage = 0;
});
Session.set('uploading', uploads);
}
};
const showUploadPreview = (file, callback) => {
// If greater then 10MB don't try and show a preview
if (file.file.size > (10 * 1000000)) {
return callback(file, null);
}
if (file.file.type == null) {
return callback(file, null);
}
if ((file.file.type.indexOf('audio') > -1) || (file.file.type.indexOf('video') > -1) || (file.file.type.indexOf('image') > -1)) {
file.type = file.file.type.split('/')[0];
return readAsDataURL(file.file, (content) => callback(file, content));
}
return callback(file, null);
};
const getAudioUploadPreview = (file, preview) => `\
<div class='upload-preview'>
<audio style="width: 100%;" controls="controls">
<source src="${ preview }" type="${ file.file.type }">
Your browser does not support the audio element.
</audio>
</div>
<div class='upload-preview-title'>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-name' style='display: inherit;' value='${ Handlebars._escape(file.name) }' placeholder='${ t('Upload_file_name') }'>
</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-description' style='display: inherit;' value='' placeholder='${ t('Upload_file_description') }'>
</div>
</div>`;
const getVideoUploadPreview = (file, preview) => `\
<div class='upload-preview'>
<video style="width: 100%;" controls="controls">
<source src="${ preview }" type="video/webm">
Your browser does not support the video element.
</video>
</div>
<div class='upload-preview-title'>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-name' style='display: inherit;' value='${ Handlebars._escape(file.name) }' placeholder='${ t('Upload_file_name') }'>
</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-description' style='display: inherit;' value='' placeholder='${ t('Upload_file_description') }'>
</div>
</div>`;
const getImageUploadPreview = (file, preview) => `\
<div class='upload-preview'>
<div class='upload-preview-file' style='background-image: url(${ preview })'></div>
</div>
<div class='upload-preview-title'>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-name' style='display: inherit;' value='${ Handlebars._escape(file.name) }' placeholder='${ t('Upload_file_name') }'>
</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-description' style='display: inherit;' value='' placeholder='${ t('Upload_file_description') }'>
</div>
</div>`;
const formatBytes = (bytes, decimals) => {
if (bytes === 0) {
return '0 Bytes';
}
const k = 1000;
const dm = (decimals + 1) || 3;
const sizes = [
'Bytes',
'KB',
'MB',
'GB',
'TB',
'PB',
];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${ parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) } ${ sizes[i] }`;
};
const getGenericUploadPreview = (file) => `\
<div class='upload-preview'>
<div>${ Handlebars._escape(file.name) } - ${ formatBytes(file.file.size) }</div>
</div>
<div class='upload-preview-title'>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-name' style='display: inherit;' value='${ Handlebars._escape(file.name) }' placeholder='${ t('Upload_file_name') }'>
</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" id='file-description' style='display: inherit;' value='' placeholder='${ t('Upload_file_description') }'>
</div>
</div>`;
const getUploadPreview = async (file, preview) => {
if (file.type === 'audio') {
return getAudioUploadPreview(file, preview);
}
if (file.type === 'video') {
return getVideoUploadPreview(file, preview);
}
const isImageFormatSupported = () => new Promise((resolve) => {
const element = document.createElement('img');
element.onload = () => resolve(true);
element.onerror = () => resolve(false);
element.src = preview;
});
if (file.type === 'image' && await isImageFormatSupported()) {
return getImageUploadPreview(file, preview);
}
return getGenericUploadPreview(file, preview);
};
export const fileUpload = async (files, input, { rid, tmid }) => {
const threadsEnabled = settings.get('Threads_enabled');
files = [].concat(files);
const replies = $(input).data('reply') || [];
const mention = $(input).data('mention-user') || false;
let msg = '';
if (!mention || !threadsEnabled) {
msg = await prependReplies('', replies, mention);
}
if (mention && threadsEnabled && replies.length) {
tmid = replies[0]._id;
}
const uploadNextFile = () => {
const file = files.pop();
if (!file) {
modal.close();
return;
}
if (file.file.type && !fileUploadIsValidContentType(file.file.type)) {
modal.open({
title: t('FileUpload_MediaType_NotAccepted'),
text: file.file.type || `*.${ s.strRightBack(file.file.name, '.') }`,
type: 'error',
timer: 3000,
});
return;
}
if (file.file.size === 0) {
modal.open({
title: t('FileUpload_File_Empty'),
type: 'error',
timer: 1000,
});
return;
}
showUploadPreview(file, async (file, preview) => modal.open({
title: t('Upload_file_question'),
text: await getUploadPreview(file, preview),
showCancelButton: true,
closeOnConfirm: false,
closeOnCancel: false,
confirmButtonText: t('Send'),
cancelButtonText: t('Cancel'),
html: true,
onRendered: () => $('#file-name').focus(),
}, async (isConfirm) => {
if (!isConfirm) {
return;
}
const fileName = document.getElementById('file-name').value || file.name || file.file.name;
uploadFileWithMessage(rid, tmid, {
description: document.getElementById('file-description').value || undefined,
fileName,
msg: msg || undefined,
file,
});
uploadNextFile();
}));
};
uploadNextFile();
};