2019-01-05 03:43:33 +00:00
|
|
|
|
import React, { Fragment } from 'react';
|
2018-11-14 02:55:11 +00:00
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
import Resumablejs from '@seafile/resumablejs';
|
|
|
|
|
import MD5 from 'MD5';
|
2019-07-19 03:57:22 +00:00
|
|
|
|
import { enableResumableFileUpload, resumableUploadFileBlockSize } from '../../utils/constants';
|
2018-11-14 02:55:11 +00:00
|
|
|
|
import { seafileAPI } from '../../utils/seafile-api';
|
2019-01-08 03:26:20 +00:00
|
|
|
|
import { Utils } from '../../utils/utils';
|
2019-03-09 03:40:05 +00:00
|
|
|
|
import { gettext } from '../../utils/constants';
|
2018-11-14 02:55:11 +00:00
|
|
|
|
import UploadProgressDialog from './upload-progress-dialog';
|
|
|
|
|
import UploadRemindDialog from '../dialog/upload-remind-dialog';
|
2019-07-16 02:01:09 +00:00
|
|
|
|
import toaster from '../toast';
|
2018-11-14 02:55:11 +00:00
|
|
|
|
import '../../css/file-uploader.css';
|
|
|
|
|
|
|
|
|
|
const propTypes = {
|
2018-11-28 04:41:49 +00:00
|
|
|
|
repoID: PropTypes.string.isRequired,
|
|
|
|
|
direntList: PropTypes.array.isRequired,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
filetypes: PropTypes.array,
|
|
|
|
|
chunkSize: PropTypes.number,
|
|
|
|
|
withCredentials: PropTypes.bool,
|
|
|
|
|
maxFiles: PropTypes.number,
|
|
|
|
|
maxFileSize: PropTypes.number,
|
|
|
|
|
testMethod: PropTypes.string,
|
|
|
|
|
testChunks: PropTypes.number,
|
|
|
|
|
simultaneousUploads: PropTypes.number,
|
|
|
|
|
fileParameterName: PropTypes.string,
|
|
|
|
|
maxFilesErrorCallback: PropTypes.func,
|
|
|
|
|
maxFileSizeErrorCallback: PropTypes.func,
|
|
|
|
|
minFileSizeErrorCallback: PropTypes.func,
|
|
|
|
|
fileTypeErrorCallback: PropTypes.func,
|
|
|
|
|
dragAndDrop: PropTypes.bool.isRequired,
|
2018-11-22 03:26:00 +00:00
|
|
|
|
path: PropTypes.string.isRequired,
|
2018-11-29 09:55:14 +00:00
|
|
|
|
onFileUploadSuccess: PropTypes.func.isRequired,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class FileUploader extends React.Component {
|
|
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.state = {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
retryFileList: [],
|
2018-11-14 02:55:11 +00:00
|
|
|
|
uploadFileList: [],
|
|
|
|
|
totalProgress: 0,
|
|
|
|
|
isUploadProgressDialogShow: false,
|
|
|
|
|
isUploadRemindDialogShow: false,
|
|
|
|
|
currentResumableFile: null,
|
2019-08-22 08:59:44 +00:00
|
|
|
|
uploadBitrate: 0,
|
2019-03-09 03:40:05 +00:00
|
|
|
|
allFilesUploaded: false,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
};
|
2019-01-05 03:43:33 +00:00
|
|
|
|
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.uploadInput = React.createRef();
|
|
|
|
|
|
2019-01-05 09:52:26 +00:00
|
|
|
|
this.notifiedFolders = [];
|
2019-01-08 03:26:20 +00:00
|
|
|
|
|
|
|
|
|
this.timestamp = null;
|
|
|
|
|
this.loaded = 0;
|
|
|
|
|
this.bitrateInterval = 500; // Interval in milliseconds to calculate the bitrate
|
2019-08-02 12:53:39 +00:00
|
|
|
|
window.onbeforeunload = this.onbeforeunload;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
this.resumable = new Resumablejs({
|
|
|
|
|
target: '',
|
|
|
|
|
query: this.setQuery || {},
|
|
|
|
|
fileType: this.props.filetypes,
|
|
|
|
|
maxFiles: this.props.maxFiles,
|
|
|
|
|
maxFileSize: this.props.maxFileSize,
|
|
|
|
|
testMethod: this.props.testMethod || 'post',
|
|
|
|
|
testChunks: this.props.testChunks || false,
|
|
|
|
|
headers: this.setHeaders || {},
|
|
|
|
|
withCredentials: this.props.withCredentials || false,
|
2019-07-19 03:57:22 +00:00
|
|
|
|
chunkSize: parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1 * 1024 * 1024,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
simultaneousUploads: this.props.simultaneousUploads || 1,
|
|
|
|
|
fileParameterName: this.props.fileParameterName,
|
|
|
|
|
generateUniqueIdentifier: this.generateUniqueIdentifier,
|
|
|
|
|
forceChunkSize: true,
|
2019-03-09 03:40:05 +00:00
|
|
|
|
maxChunkRetries: 3,
|
2019-08-22 08:59:44 +00:00
|
|
|
|
minFileSize: 0,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
|
|
|
|
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.resumable.assignBrowse(this.uploadInput.current, true);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
|
|
|
|
//Enable or Disable DragAnd Drop
|
|
|
|
|
if (this.props.dragAndDrop === true) {
|
|
|
|
|
this.resumable.enableDropOnDocument();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.bindCallbackHandler();
|
|
|
|
|
this.bindEventHandler();
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-10 03:41:53 +00:00
|
|
|
|
componentWillUnmount = () => {
|
2019-08-02 12:53:39 +00:00
|
|
|
|
window.onbeforeunload = null;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
if (this.props.dragAndDrop === true) {
|
|
|
|
|
this.resumable.disableDropOnDocument();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 12:53:39 +00:00
|
|
|
|
onbeforeunload = () => {
|
|
|
|
|
if (window.uploader &&
|
|
|
|
|
window.uploader.isUploadProgressDialogShow &&
|
|
|
|
|
window.uploader.totalProgress !== 100) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
bindCallbackHandler = () => {
|
|
|
|
|
let {maxFilesErrorCallback, minFileSizeErrorCallback, maxFileSizeErrorCallback, fileTypeErrorCallback } = this.props;
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
if (maxFilesErrorCallback) {
|
|
|
|
|
this.resumable.opts.maxFilesErrorCallback = this.props.maxFilesErrorCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (minFileSizeErrorCallback) {
|
|
|
|
|
this.resumable.opts.minFileSizeErrorCallback = this.props.minFileSizeErrorCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (maxFileSizeErrorCallback) {
|
|
|
|
|
this.resumable.opts.maxFileSizeErrorCallback = this.props.maxFileSizeErrorCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fileTypeErrorCallback) {
|
|
|
|
|
this.resumable.opts.fileTypeErrorCallback = this.props.fileTypeErrorCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindEventHandler = () => {
|
2019-01-08 03:26:20 +00:00
|
|
|
|
this.resumable.on('chunkingComplete', this.onChunkingComplete.bind(this));
|
|
|
|
|
this.resumable.on('fileAdded', this.onFileAdded.bind(this));
|
|
|
|
|
this.resumable.on('filesAddedComplete', this.filesAddedComplete.bind(this));
|
|
|
|
|
this.resumable.on('fileProgress', this.onFileProgress.bind(this));
|
|
|
|
|
this.resumable.on('fileSuccess', this.onFileUploadSuccess.bind(this));
|
|
|
|
|
this.resumable.on('progress', this.onProgress.bind(this));
|
|
|
|
|
this.resumable.on('complete', this.onComplete.bind(this));
|
|
|
|
|
this.resumable.on('pause', this.onPause.bind(this));
|
|
|
|
|
this.resumable.on('fileRetry', this.onFileRetry.bind(this));
|
|
|
|
|
this.resumable.on('fileError', this.onFileError.bind(this));
|
|
|
|
|
this.resumable.on('error', this.onError.bind(this));
|
|
|
|
|
this.resumable.on('beforeCancel', this.onBeforeCancel.bind(this));
|
|
|
|
|
this.resumable.on('cancel', this.onCancel.bind(this));
|
|
|
|
|
this.resumable.on('dragstart', this.onDragStart.bind(this));
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:49:16 +00:00
|
|
|
|
onChunkingComplete = (resumableFile) => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
|
|
|
|
let allFilesUploaded = this.state.allFilesUploaded;
|
|
|
|
|
if (allFilesUploaded === true) {
|
|
|
|
|
this.setState({allFilesUploaded: false});
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:49:16 +00:00
|
|
|
|
//get parent_dir relative_path
|
2018-11-22 03:26:00 +00:00
|
|
|
|
let path = this.props.path === '/' ? '/' : this.props.path + '/';
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let fileName = resumableFile.fileName;
|
|
|
|
|
let relativePath = resumableFile.relativePath;
|
|
|
|
|
let isFile = fileName === relativePath;
|
|
|
|
|
|
2019-07-04 10:49:16 +00:00
|
|
|
|
//update formdata
|
2018-11-14 02:55:11 +00:00
|
|
|
|
resumableFile.formData = {};
|
2019-07-04 10:49:16 +00:00
|
|
|
|
if (isFile) { // upload file
|
2018-11-14 02:55:11 +00:00
|
|
|
|
resumableFile.formData = {
|
2018-11-22 03:26:00 +00:00
|
|
|
|
parent_dir: path,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
};
|
2019-07-04 10:49:16 +00:00
|
|
|
|
} else { // upload folder
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let relative_path = relativePath.slice(0, relativePath.lastIndexOf('/') + 1);
|
|
|
|
|
resumableFile.formData = {
|
2018-11-22 03:26:00 +00:00
|
|
|
|
parent_dir: path,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
relative_path: relative_path
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-07-04 10:49:16 +00:00
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
2019-07-04 10:49:16 +00:00
|
|
|
|
onFileAdded = (resumableFile, files) => {
|
|
|
|
|
let isFile = resumableFile.fileName === resumableFile.relativePath;
|
|
|
|
|
// uploading is file and only upload one file
|
2018-11-14 02:55:11 +00:00
|
|
|
|
if (isFile && files.length === 1) {
|
|
|
|
|
let hasRepetition = false;
|
|
|
|
|
let direntList = this.props.direntList;
|
|
|
|
|
for (let i = 0; i < direntList.length; i++) {
|
|
|
|
|
if (direntList[i].type === 'file' && direntList[i].name === resumableFile.fileName) {
|
|
|
|
|
hasRepetition = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hasRepetition) {
|
|
|
|
|
this.setState({
|
|
|
|
|
isUploadRemindDialogShow: true,
|
|
|
|
|
currentResumableFile: resumableFile,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.setUploadFileList(this.resumable.files);
|
2019-07-04 10:49:16 +00:00
|
|
|
|
this.resumableUpload(resumableFile);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
2019-07-04 10:49:16 +00:00
|
|
|
|
} else {
|
2018-11-14 02:55:11 +00:00
|
|
|
|
this.setUploadFileList(this.resumable.files);
|
2019-07-04 10:49:16 +00:00
|
|
|
|
if (isFile) {
|
|
|
|
|
this.resumableUpload(resumableFile);
|
|
|
|
|
} else {
|
2019-07-04 09:05:48 +00:00
|
|
|
|
this.resumable.upload();
|
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:49:16 +00:00
|
|
|
|
resumableUpload = (resumableFile) => {
|
|
|
|
|
let { repoID, path } = this.props;
|
|
|
|
|
seafileAPI.getFileUploadedBytes(repoID, path, resumableFile.fileName).then(res => {
|
|
|
|
|
let uploadedBytes = res.data.uploadedBytes;
|
2019-08-16 09:12:47 +00:00
|
|
|
|
let blockSize = parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1024 * 1024;
|
|
|
|
|
let offset = Math.floor(uploadedBytes / blockSize);
|
2019-07-04 10:49:16 +00:00
|
|
|
|
resumableFile.markChunksCompleted(offset);
|
|
|
|
|
this.resumable.upload();
|
2019-07-16 02:01:09 +00:00
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2019-07-04 10:49:16 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
filesAddedComplete = (resumable, files) => {
|
2018-11-22 03:26:00 +00:00
|
|
|
|
// single file uploading can check repetition, because custom dialog conn't prevent program execution;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
setUploadFileList = () => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
let uploadFileList = this.resumable.files;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
this.setState({
|
2019-01-05 03:43:33 +00:00
|
|
|
|
uploadFileList: uploadFileList,
|
2019-08-15 06:52:08 +00:00
|
|
|
|
isUploadProgressDialogShow: true,
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
2019-08-02 12:53:39 +00:00
|
|
|
|
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 06:52:08 +00:00
|
|
|
|
onFileProgress = (resumableFile) => {
|
|
|
|
|
let uploadBitrate = this.getBitrate();
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
if (uploadBitrate) {
|
|
|
|
|
let lastSize = (item.size - (item.size * item.progress())) * 8;
|
2019-08-16 09:12:47 +00:00
|
|
|
|
let time = Math.floor(lastSize / uploadBitrate);
|
2019-08-15 06:52:08 +00:00
|
|
|
|
item.remainingTime = time;
|
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2019-08-15 06:52:08 +00:00
|
|
|
|
this.setState({
|
|
|
|
|
uploadBitrate: uploadBitrate,
|
|
|
|
|
uploadFileList: uploadFileList
|
|
|
|
|
});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
2019-01-08 03:26:20 +00:00
|
|
|
|
|
|
|
|
|
getBitrate = () => {
|
|
|
|
|
let loaded = 0;
|
|
|
|
|
let uploadBitrate = 0;
|
|
|
|
|
let now = new Date().getTime();
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
2019-01-08 03:26:20 +00:00
|
|
|
|
this.resumable.files.forEach(file => {
|
|
|
|
|
loaded += file.progress() * file.size;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (this.timestamp) {
|
|
|
|
|
let timeDiff = (now - this.timestamp);
|
|
|
|
|
if (timeDiff < this.bitrateInterval) {
|
2019-01-08 03:53:53 +00:00
|
|
|
|
return this.state.uploadBitrate;
|
2019-01-08 03:26:20 +00:00
|
|
|
|
}
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
|
|
|
|
// 1. Cancel will produce loaded greater than this.loaded
|
|
|
|
|
// 2. reset can make this.loaded to be 0
|
|
|
|
|
if (loaded < this.loaded || this.loaded === 0) {
|
|
|
|
|
this.loaded = loaded; //
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-08 03:26:20 +00:00
|
|
|
|
uploadBitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.timestamp = now;
|
|
|
|
|
this.loaded = loaded;
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
2019-01-08 03:26:20 +00:00
|
|
|
|
return uploadBitrate;
|
|
|
|
|
}
|
2019-01-05 03:43:33 +00:00
|
|
|
|
|
|
|
|
|
onProgress = () => {
|
|
|
|
|
let progress = Math.round(this.resumable.progress() * 100);
|
2019-08-15 06:52:08 +00:00
|
|
|
|
this.setState({totalProgress: progress});
|
2019-08-02 12:53:39 +00:00
|
|
|
|
Utils.registerGlobalVariable('uploader', 'totalProgress', progress);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
onFileUploadSuccess = (resumableFile, message) => {
|
|
|
|
|
let formData = resumableFile.formData;
|
|
|
|
|
let currentTime = new Date().getTime()/1000;
|
|
|
|
|
message = formData.replace ? message : JSON.parse(message)[0];
|
|
|
|
|
if (formData.relative_path) { // upload folder
|
|
|
|
|
let relative_path = formData.relative_path;
|
|
|
|
|
let dir_name = relative_path.slice(0, relative_path.indexOf('/'));
|
|
|
|
|
let dirent = {
|
|
|
|
|
id: message.id,
|
|
|
|
|
name: dir_name,
|
|
|
|
|
type: 'dir',
|
|
|
|
|
mtime: currentTime,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// update folders cache
|
2019-01-05 09:52:26 +00:00
|
|
|
|
let isExist = this.notifiedFolders.some(item => {return item.name === dirent.name;});
|
2019-01-05 03:43:33 +00:00
|
|
|
|
if (!isExist) {
|
2019-01-05 09:52:26 +00:00
|
|
|
|
this.notifiedFolders.push(dirent);
|
2019-01-05 03:43:33 +00:00
|
|
|
|
this.props.onFileUploadSuccess(dirent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update uploadFileList
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
item.newFileName = relative_path + message.name;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
item.isSaved = true;
|
2019-01-05 03:43:33 +00:00
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
|
|
|
|
this.setState({uploadFileList: uploadFileList});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (formData.replace) { // upload file -- replace exist file
|
|
|
|
|
let fileName = resumableFile.fileName;
|
|
|
|
|
let dirent = {
|
|
|
|
|
id: message,
|
|
|
|
|
name: fileName,
|
|
|
|
|
type: 'file',
|
|
|
|
|
mtime: currentTime
|
|
|
|
|
};
|
|
|
|
|
this.props.onFileUploadSuccess(dirent); // this contance: just one file
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
2019-01-10 03:41:53 +00:00
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
item.newFileName = fileName;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
item.isSaved = true;
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
|
|
|
|
this.setState({uploadFileList: uploadFileList});
|
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// upload file -- add files
|
|
|
|
|
let dirent = {
|
|
|
|
|
id: message.id,
|
|
|
|
|
type: 'file',
|
|
|
|
|
name: message.name,
|
|
|
|
|
size: message.size,
|
|
|
|
|
mtime: currentTime,
|
|
|
|
|
};
|
|
|
|
|
this.props.onFileUploadSuccess(dirent); // this contance: no repetition file
|
|
|
|
|
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
item.newFileName = message.name;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
item.isSaved = true;
|
2019-01-05 03:43:33 +00:00
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
|
|
|
|
this.setState({uploadFileList: uploadFileList});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-09 03:40:05 +00:00
|
|
|
|
onFileError = (resumableFile, message) => {
|
|
|
|
|
let error = '';
|
|
|
|
|
if (!message) {
|
|
|
|
|
error = gettext('Network error');
|
|
|
|
|
} else {
|
|
|
|
|
error = message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
this.state.retryFileList.push(item);
|
|
|
|
|
item.error = error;
|
2019-03-09 03:40:05 +00:00
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
|
|
|
|
this.loaded = 0; // reset loaded data;
|
|
|
|
|
this.setState({
|
|
|
|
|
retryFileList: this.state.retryFileList,
|
|
|
|
|
uploadFileList: uploadFileList
|
|
|
|
|
});
|
2019-01-05 03:43:33 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onComplete = () => {
|
2019-01-05 09:52:26 +00:00
|
|
|
|
this.notifiedFolders = [];
|
2019-03-09 03:40:05 +00:00
|
|
|
|
this.setState({allFilesUploaded: true});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
onPause = () => {
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
2019-03-09 03:40:05 +00:00
|
|
|
|
onError = (message) => {
|
2019-08-16 09:12:47 +00:00
|
|
|
|
// After the error, the user can switch windows
|
|
|
|
|
Utils.registerGlobalVariable('uploader', 'totalProgress', 100);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onFileRetry = () => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
// todo, cancel upload file, uploded again;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onBeforeCancel = () => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
// todo, giving a pop message ?
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onCancel = () => {
|
|
|
|
|
|
|
|
|
|
}
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
setHeaders = (resumableFile, resumable) => {
|
|
|
|
|
let offset = resumable.offset;
|
|
|
|
|
let chunkSize = resumable.getOpt('chunkSize');
|
2019-08-22 08:59:44 +00:00
|
|
|
|
let fileSize = resumableFile.size === 0 ? 1 : resumableFile.size;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let startByte = offset !== 0 ? offset * chunkSize : 0;
|
|
|
|
|
let endByte = Math.min(fileSize, (offset + 1) * chunkSize) - 1;
|
|
|
|
|
|
|
|
|
|
if (fileSize - resumable.endByte < chunkSize && !resumable.getOpt('forceChunkSize')) {
|
|
|
|
|
endByte = fileSize;
|
|
|
|
|
}
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let headers = {
|
|
|
|
|
'Accept': 'application/json; text/javascript, */*; q=0.01',
|
|
|
|
|
'Content-Disposition': 'attachment; filename="' + encodeURI(resumableFile.fileName) + '"',
|
|
|
|
|
'Content-Range': 'bytes ' + startByte + '-' + endByte + '/' + fileSize,
|
|
|
|
|
};
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
return headers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setQuery = (resumableFile) => {
|
|
|
|
|
let formData = resumableFile.formData;
|
|
|
|
|
return formData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generateUniqueIdentifier = (file) => {
|
|
|
|
|
let relativePath = file.webkitRelativePath||file.relativePath||file.fileName||file.name;
|
|
|
|
|
return MD5(relativePath + new Date()) + relativePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onClick = (e) => {
|
|
|
|
|
e.nativeEvent.stopImmediatePropagation();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onFileUpload = () => {
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.uploadInput.current.removeAttribute('webkitdirectory');
|
2018-11-28 04:41:49 +00:00
|
|
|
|
let repoID = this.props.repoID;
|
2018-11-22 03:26:00 +00:00
|
|
|
|
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
2019-07-08 05:16:01 +00:00
|
|
|
|
this.resumable.opts.target = res.data + '?ret-json=1';
|
2019-06-18 03:59:47 +00:00
|
|
|
|
if (Utils.isIEBrower()) {
|
|
|
|
|
this.uploadInput.current.click();
|
|
|
|
|
}
|
2019-07-16 02:01:09 +00:00
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
2019-06-18 03:59:47 +00:00
|
|
|
|
if (!Utils.isIEBrower()) {
|
|
|
|
|
this.uploadInput.current.click();
|
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onFolderUpload = () => {
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory');
|
2018-11-28 04:41:49 +00:00
|
|
|
|
let repoID = this.props.repoID;
|
2018-11-22 03:26:00 +00:00
|
|
|
|
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
2019-07-08 05:16:01 +00:00
|
|
|
|
this.resumable.opts.target = res.data + '?ret-json=1';
|
2019-06-18 03:59:47 +00:00
|
|
|
|
if (Utils.isIEBrower()) {
|
|
|
|
|
this.uploadInput.current.click();
|
|
|
|
|
}
|
2019-07-16 02:01:09 +00:00
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
2019-06-18 03:59:47 +00:00
|
|
|
|
if (!Utils.isIEBrower()) {
|
|
|
|
|
this.uploadInput.current.click();
|
|
|
|
|
}
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
onDragStart = () => {
|
2018-11-28 04:41:49 +00:00
|
|
|
|
let repoID = this.props.repoID;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory');
|
2018-11-22 03:26:00 +00:00
|
|
|
|
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
2019-07-08 05:16:01 +00:00
|
|
|
|
this.resumable.opts.target = res.data + '?ret-json=1';
|
2019-07-16 02:01:09 +00:00
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onCloseUploadDialog = () => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
this.loaded = 0;
|
2019-01-10 03:41:53 +00:00
|
|
|
|
this.resumable.files = [];
|
2018-11-14 02:55:11 +00:00
|
|
|
|
this.setState({isUploadProgressDialogShow: false, uploadFileList: []});
|
2019-08-02 12:53:39 +00:00
|
|
|
|
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', false);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-08 03:26:20 +00:00
|
|
|
|
onUploadCancel = (uploadingItem) => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
let uploadFileList = this.state.uploadFileList.filter(item => {
|
2019-01-08 03:26:20 +00:00
|
|
|
|
if (item.uniqueIdentifier === uploadingItem.uniqueIdentifier) {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
item.cancel(); // execute cancel function will delete the file at the same time
|
|
|
|
|
return false;
|
2019-01-08 03:26:20 +00:00
|
|
|
|
}
|
2019-08-15 06:52:08 +00:00
|
|
|
|
return true;
|
2018-11-14 02:55:11 +00:00
|
|
|
|
});
|
2019-08-15 06:52:08 +00:00
|
|
|
|
|
|
|
|
|
if (!this.resumable.isUploading()) {
|
|
|
|
|
this.setState({
|
|
|
|
|
totalProgress: '100',
|
|
|
|
|
allFilesUploaded: true,
|
|
|
|
|
});
|
|
|
|
|
this.loaded = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({uploadFileList: uploadFileList});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-08 03:26:20 +00:00
|
|
|
|
onCancelAllUploading = () => {
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.filter(item => {
|
2019-08-15 06:52:08 +00:00
|
|
|
|
if (Math.round(item.progress() !== 1)) {
|
|
|
|
|
item.cancel();
|
|
|
|
|
return false;
|
2019-01-08 03:26:20 +00:00
|
|
|
|
}
|
2019-08-15 06:52:08 +00:00
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.loaded = 0;
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
allFilesUploaded: true,
|
|
|
|
|
totalProgress: '100',
|
|
|
|
|
uploadFileList: uploadFileList
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onUploadRetry = (resumableFile) => {
|
|
|
|
|
|
|
|
|
|
seafileAPI.getUploadLink(this.props.repoID, this.props.path).then(res => {
|
|
|
|
|
this.resumable.opts.target = res.data;
|
|
|
|
|
|
|
|
|
|
let retryFileList = this.state.retryFileList.filter(item => {
|
|
|
|
|
return item.uniqueIdentifier !== resumableFile.uniqueIdentifier;
|
|
|
|
|
});
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.map(item => {
|
|
|
|
|
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
|
|
|
|
|
item.error = null;
|
2019-08-16 09:12:47 +00:00
|
|
|
|
this.retryUploadFile(item);
|
2019-08-15 06:52:08 +00:00
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
retryFileList: retryFileList,
|
|
|
|
|
uploadFileList: uploadFileList
|
|
|
|
|
});
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2019-01-08 03:26:20 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 06:52:08 +00:00
|
|
|
|
onUploadRetryAll = () => {
|
2018-11-14 02:55:11 +00:00
|
|
|
|
|
2019-08-15 06:52:08 +00:00
|
|
|
|
seafileAPI.getUploadLink(this.props.repoID, this.props.path).then(res => {
|
|
|
|
|
this.resumable.opts.target = res.data;
|
|
|
|
|
this.state.retryFileList.forEach(item => {
|
|
|
|
|
item.error = false;
|
2019-08-16 09:12:47 +00:00
|
|
|
|
this.retryUploadFile(item);
|
2019-08-15 06:52:08 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let uploadFileList = this.state.uploadFileList.slice(0);
|
|
|
|
|
this.setState({
|
|
|
|
|
retryFileList: [],
|
|
|
|
|
uploadFileList: uploadFileList
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
|
|
|
|
});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 09:12:47 +00:00
|
|
|
|
retryUploadFile = (resumableFile) => {
|
|
|
|
|
let { repoID, path } = this.props;
|
|
|
|
|
let fileName = resumableFile.fileName;
|
|
|
|
|
let isFile = resumableFile.fileName === resumableFile.relativePath;
|
|
|
|
|
if (!isFile) {
|
|
|
|
|
let relative_path = resumableFile.formData.relative_path;
|
|
|
|
|
let prefix = path === '/' ? (path + relative_path) : (path + '/' + relative_path);
|
|
|
|
|
fileName = prefix + fileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resumableFile.bootstrap();
|
|
|
|
|
var firedRetry = false;
|
|
|
|
|
resumableFile.resumableObj.on('chunkingComplete', () => {
|
|
|
|
|
if(!firedRetry) {
|
|
|
|
|
seafileAPI.getFileUploadedBytes(repoID, path, fileName).then(res => {
|
|
|
|
|
let uploadedBytes = res.data.uploadedBytes;
|
|
|
|
|
let blockSize = parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1024 * 1024;
|
|
|
|
|
let offset = Math.floor(uploadedBytes / blockSize);
|
|
|
|
|
resumableFile.markChunksCompleted(offset);
|
|
|
|
|
|
|
|
|
|
resumableFile.resumableObj.upload();
|
|
|
|
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
firedRetry = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
replaceRepetitionFile = () => {
|
2019-01-05 03:43:33 +00:00
|
|
|
|
let { repoID, path } = this.props;
|
|
|
|
|
seafileAPI.getUpdateLink(repoID, path).then(res => {
|
|
|
|
|
this.resumable.opts.target = res.data;
|
2018-11-22 03:26:00 +00:00
|
|
|
|
|
2019-01-05 03:43:33 +00:00
|
|
|
|
let resumableFile = this.resumable.files[this.resumable.files.length - 1];
|
|
|
|
|
resumableFile.formData['replace'] = 1;
|
|
|
|
|
resumableFile.formData['target_file'] = resumableFile.formData.parent_dir + resumableFile.fileName;
|
|
|
|
|
this.setState({isUploadRemindDialogShow: false});
|
|
|
|
|
this.setUploadFileList(this.resumable.files);
|
|
|
|
|
this.resumable.upload();
|
2019-07-16 02:01:09 +00:00
|
|
|
|
}).catch(error => {
|
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
|
toaster.danger(errMessage);
|
2019-01-05 03:43:33 +00:00
|
|
|
|
});
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
2019-01-05 03:43:33 +00:00
|
|
|
|
|
2018-11-14 02:55:11 +00:00
|
|
|
|
uploadFile = () => {
|
2019-01-05 03:43:33 +00:00
|
|
|
|
let resumableFile = this.resumable.files[this.resumable.files.length - 1];
|
|
|
|
|
this.setState({
|
|
|
|
|
isUploadRemindDialogShow: false,
|
|
|
|
|
isUploadProgressDialogShow: true,
|
2019-08-15 06:52:08 +00:00
|
|
|
|
uploadFileList: [...this.state.uploadFileList, resumableFile]
|
2019-01-05 03:43:33 +00:00
|
|
|
|
}, () => {
|
|
|
|
|
this.resumable.upload();
|
|
|
|
|
});
|
2019-08-02 12:53:39 +00:00
|
|
|
|
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true);
|
2018-11-14 02:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancelFileUpload = () => {
|
|
|
|
|
this.resumable.files.pop(); //delete latest file;
|
|
|
|
|
this.setState({isUploadRemindDialogShow: false});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
return (
|
2019-01-05 03:43:33 +00:00
|
|
|
|
<Fragment>
|
|
|
|
|
<div className="file-uploader-container">
|
|
|
|
|
<div className="file-uploader">
|
2019-01-10 03:41:53 +00:00
|
|
|
|
<input className="upload-input" type="file" ref={this.uploadInput} onClick={this.onClick}/>
|
2019-01-05 03:43:33 +00:00
|
|
|
|
</div>
|
2018-11-14 02:55:11 +00:00
|
|
|
|
</div>
|
2019-01-31 09:37:02 +00:00
|
|
|
|
{this.state.isUploadRemindDialogShow &&
|
|
|
|
|
<UploadRemindDialog
|
|
|
|
|
currentResumableFile={this.state.currentResumableFile}
|
|
|
|
|
replaceRepetitionFile={this.replaceRepetitionFile}
|
|
|
|
|
uploadFile={this.uploadFile}
|
|
|
|
|
cancelFileUpload={this.cancelFileUpload}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
{this.state.isUploadProgressDialogShow &&
|
|
|
|
|
<UploadProgressDialog
|
2019-08-15 06:52:08 +00:00
|
|
|
|
retryFileList={this.state.retryFileList}
|
2019-01-31 09:37:02 +00:00
|
|
|
|
uploadFileList={this.state.uploadFileList}
|
|
|
|
|
totalProgress={this.state.totalProgress}
|
2019-03-09 03:40:05 +00:00
|
|
|
|
uploadBitrate={this.state.uploadBitrate}
|
|
|
|
|
allFilesUploaded={this.state.allFilesUploaded}
|
2019-01-31 09:37:02 +00:00
|
|
|
|
onCloseUploadDialog={this.onCloseUploadDialog}
|
|
|
|
|
onCancelAllUploading={this.onCancelAllUploading}
|
|
|
|
|
onUploadCancel={this.onUploadCancel}
|
2019-08-15 06:52:08 +00:00
|
|
|
|
onUploadRetry={this.onUploadRetry}
|
|
|
|
|
onUploadRetryAll={this.onUploadRetryAll}
|
2019-01-31 09:37:02 +00:00
|
|
|
|
/>
|
|
|
|
|
}
|
2019-01-05 03:43:33 +00:00
|
|
|
|
</Fragment>
|
2018-11-14 02:55:11 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileUploader.propTypes = propTypes;
|
|
|
|
|
|
|
|
|
|
export default FileUploader;
|