1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-01 07:01:12 +00:00

Improve upload module 2 (#3984)

* optimized upload file

* improve upload

* revert code

* optimized code

* optimized code

* optimized code

* optimized code

* update resumablejs version
This commit is contained in:
杨顺强
2019-08-15 14:52:08 +08:00
committed by Daniel Pan
parent 04e10f3a4c
commit 68d3a57e7d
7 changed files with 317 additions and 107 deletions

View File

@@ -37,6 +37,7 @@ class FileUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
retryFileList: [],
uploadFileList: [],
totalProgress: 0,
isUploadProgressDialogShow: false,
@@ -140,6 +141,12 @@ class FileUploader extends React.Component {
}
onChunkingComplete = (resumableFile) => {
let allFilesUploaded = this.state.allFilesUploaded;
if (allFilesUploaded === true) {
this.setState({allFilesUploaded: false});
}
//get parent_dir relative_path
let path = this.props.path === '/' ? '/' : this.props.path + '/';
let fileName = resumableFile.fileName;
@@ -210,42 +217,38 @@ class FileUploader extends React.Component {
}
setUploadFileList = () => {
let uploadFileList = this.resumable.files.map(resumableFile => {
return this.buildCustomFileObj(resumableFile);
});
let uploadFileList = this.resumable.files;
this.setState({
isUploadProgressDialogShow: true,
uploadFileList: uploadFileList,
isUploadProgressDialogShow: true,
});
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true);
}
buildCustomFileObj = (resumableFile) => {
return {
uniqueIdentifier: resumableFile.uniqueIdentifier,
resumableFile: resumableFile,
progress: resumableFile.progress(),
isSaved: resumableFile.progress() === 1 ? true : false, // The 'isSaved' property is not saved in resumableFile.
};
}
onFileProgress = (file) => {
let uniqueIdentifier = file.uniqueIdentifier;
onFileProgress = (resumableFile) => {
let uploadBitrate = this.getBitrate();
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.uniqueIdentifier === uniqueIdentifier) {
item.progress = Math.round(file.progress() * 100);
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
if (uploadBitrate) {
let lastSize = (item.size - (item.size * item.progress())) * 8;
let time = Math.ceil(lastSize / uploadBitrate);
item.remainingTime = time;
}
}
return item;
});
this.setState({uploadFileList: uploadFileList});
this.setState({
uploadBitrate: uploadBitrate,
uploadFileList: uploadFileList
});
}
getBitrate = () => {
let loaded = 0;
let uploadBitrate = 0;
let now = new Date().getTime();
this.resumable.files.forEach(file => {
loaded += file.progress() * file.size;
});
@@ -255,23 +258,25 @@ class FileUploader extends React.Component {
if (timeDiff < this.bitrateInterval) {
return this.state.uploadBitrate;
}
// 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; //
}
uploadBitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
}
this.timestamp = now;
this.loaded = loaded;
uploadBitrate = Utils.formatBitRate(uploadBitrate);
return uploadBitrate;
}
onProgress = () => {
let progress = Math.round(this.resumable.progress() * 100);
let uploadBitrate = this.getBitrate();
this.setState({
totalProgress: progress,
uploadBitrate: uploadBitrate
});
this.setState({totalProgress: progress});
Utils.registerGlobalVariable('uploader', 'totalProgress', progress);
}
@@ -298,10 +303,10 @@ class FileUploader extends React.Component {
// update uploadFileList
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.resumableFile.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.resumableFile.fileName = message.name;
item.resumableFile.relativePath = relative_path + message.name;
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.newFileName = relative_path + message.name;
item.isSaved = true;
item.remainingTime = 0;
}
return item;
});
@@ -321,8 +326,10 @@ class FileUploader extends React.Component {
this.props.onFileUploadSuccess(dirent); // this contance: just one file
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.resumableFile.uniqueIdentifier === resumableFile.uniqueIdentifier) {
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.newFileName = fileName;
item.isSaved = true;
item.remainingTime = 0;
}
return item;
});
@@ -342,10 +349,10 @@ class FileUploader extends React.Component {
this.props.onFileUploadSuccess(dirent); // this contance: no repetition file
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.resumableFile.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.resumableFile.fileName = message.name;
item.resumableFile.relativePath = message.name;
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.newFileName = message.name;
item.isSaved = true;
item.remainingTime = 0;
}
return item;
});
@@ -361,12 +368,18 @@ class FileUploader extends React.Component {
}
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.resumableFile.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.resumableFile.error = error;
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
this.state.retryFileList.push(item);
item.error = error;
}
return item;
});
this.setState({uploadFileList: uploadFileList});
this.loaded = 0; // reset loaded data;
this.setState({
retryFileList: this.state.retryFileList,
uploadFileList: uploadFileList
});
}
@@ -384,11 +397,11 @@ class FileUploader extends React.Component {
}
onFileRetry = () => {
//todos, cancel upload file, uploded again;
// todo, cancel upload file, uploded again;
}
onBeforeCancel = () => {
//todos, giving a pop message ?
// todo, giving a pop message ?
}
onCancel = () => {
@@ -476,43 +489,96 @@ class FileUploader extends React.Component {
}
onCloseUploadDialog = () => {
this.loaded = 0;
this.resumable.files = [];
this.setState({isUploadProgressDialogShow: false, uploadFileList: []});
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', false);
}
onUploadCancel = (uploadingItem) => {
let uploadFileList = this.state.uploadFileList.filter(item => {
if (item.uniqueIdentifier === uploadingItem.uniqueIdentifier) {
uploadingItem.resumableFile.cancel();
this.resumable.removeFile(uploadingItem.resumableFile.file);
} else {
return item;
item.cancel(); // execute cancel function will delete the file at the same time
return false;
}
return true;
});
let newUploaderFileList = uploadFileList.map(item => {
let progress = Math.round(item.resumableFile.progress() * 100);
item.progress = progress;
return item;
});
this.setState({uploadFileList: newUploaderFileList});
if (!this.resumable.isUploading()) {
this.setState({
totalProgress: '100',
allFilesUploaded: true,
});
this.loaded = 0;
}
this.setState({uploadFileList: uploadFileList});
}
onCancelAllUploading = () => {
let uploadFileList = this.state.uploadFileList.filter(item => {
let resumableFile = item.resumableFile;
if (Math.round(resumableFile.progress() !== 1)) {
resumableFile.cancel();
this.resumable.removeFile(resumableFile.file);
} else {
return item;
if (Math.round(item.progress() !== 1)) {
item.cancel();
return false;
}
return true;
});
this.loaded = 0;
this.setState({
allFilesUploaded: true,
totalProgress: '100',
uploadFileList: uploadFileList
});
this.setState({uploadFileList: uploadFileList});
}
onUploaderRetry = () => {
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;
item.retry();
}
return item;
});
this.setState({
retryFileList: retryFileList,
uploadFileList: uploadFileList
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onUploadRetryAll = () => {
seafileAPI.getUploadLink(this.props.repoID, this.props.path).then(res => {
this.resumable.opts.target = res.data;
this.state.retryFileList.forEach(item => {
item.retry();
item.error = false;
});
let uploadFileList = this.state.uploadFileList.slice(0);
this.setState({
retryFileList: [],
uploadFileList: uploadFileList
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
replaceRepetitionFile = () => {
@@ -534,11 +600,10 @@ class FileUploader extends React.Component {
uploadFile = () => {
let resumableFile = this.resumable.files[this.resumable.files.length - 1];
let fileObject = this.buildCustomFileObj(resumableFile);
this.setState({
isUploadRemindDialogShow: false,
isUploadProgressDialogShow: true,
uploadFileList: [...this.state.uploadFileList, fileObject]
uploadFileList: [...this.state.uploadFileList, resumableFile]
}, () => {
this.resumable.upload();
});
@@ -568,6 +633,7 @@ class FileUploader extends React.Component {
}
{this.state.isUploadProgressDialogShow &&
<UploadProgressDialog
retryFileList={this.state.retryFileList}
uploadFileList={this.state.uploadFileList}
totalProgress={this.state.totalProgress}
uploadBitrate={this.state.uploadBitrate}
@@ -575,6 +641,8 @@ class FileUploader extends React.Component {
onCloseUploadDialog={this.onCloseUploadDialog}
onCancelAllUploading={this.onCancelAllUploading}
onUploadCancel={this.onUploadCancel}
onUploadRetry={this.onUploadRetry}
onUploadRetryAll={this.onUploadRetryAll}
/>
}
</Fragment>

View File

@@ -1,17 +1,55 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
const propTypes = {
item: PropTypes.object.isRequired,
resumableFile: PropTypes.object.isRequired,
onUploadCancel: PropTypes.func.isRequired,
onUploadRetry: PropTypes.func.isRequired,
};
const UPLOAD_UPLOADING = 'uploading';
const UPLOAD_ERROR = 'error';
const UPLOAD_ISSAVING = 'isSaving';
const UPLOAD_UPLOADED = 'uploaded';
class UploadListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
uploadState: UPLOAD_UPLOADING
}
}
componentWillReceiveProps(nextProps) {
let { resumableFile } = nextProps;
let uploadState = UPLOAD_UPLOADING;
if (resumableFile.error) {
uploadState = UPLOAD_ERROR;
} else {
if (resumableFile.progress() === 1 && !resumableFile.isSaved) {
uploadState = UPLOAD_ISSAVING;
}
if (resumableFile.isSaved) {
uploadState = UPLOAD_UPLOADED;
}
}
this.setState({uploadState: uploadState});
}
onUploadCancel = (e) => {
e.preventDefault();
this.props.onUploadCancel(this.props.item);
this.props.onUploadCancel(this.props.resumableFile);
}
onUploadRetry = (e) => {
e.preventDefault();
this.props.onUploadRetry(this.props.resumableFile)
}
formatFileSize = (size) => {
@@ -31,38 +69,69 @@ class UploadListItem extends React.Component {
}
render() {
let { item } = this.props;
let progress = Math.round(item.resumableFile.progress() * 100);
let error = item.resumableFile.error;
let { resumableFile } = this.props;
let progress = Math.round(resumableFile.progress() * 100);
let error = resumableFile.error;
return (
<tr className="file-upload-item">
<td className="upload-name">
<div className="ellipsis">{item.resumableFile.relativePath}</div>
<div className="message err-message ml-0" dangerouslySetInnerHTML={{__html: error}}></div>
<div className="ellipsis">{resumableFile.newFileName}</div>
</td>
<td>
<span className="file-size">{this.formatFileSize(resumableFile.size)}</span>
</td>
<td className="upload-progress">
<span className="file-size">{this.formatFileSize(item.resumableFile.size)}</span>
{!item.resumableFile.error && progress !== 100 &&
<div className="progress">
<div className="progress-bar" role="progressbar" style={{width: `${progress}%`}} aria-valuenow={progress} aria-valuemin="0" aria-valuemax="100"></div>
</div>
{this.state.uploadState === UPLOAD_UPLOADING &&
<Fragment>
{resumableFile.size >= (100 * 1000 * 1000) &&
<Fragment>
{resumableFile.isUploading() && (
<div className="progress-container">
<div className="progress">
<div className="progress-bar" role="progressbar" style={{width: `${progress}%`}} aria-valuenow={progress} aria-valuemin="0" aria-valuemax="100"></div>
</div>
{resumableFile.remainingTime === 0 && <div className="progress-text">{gettext('Preparing to upload...')}</div>}
{resumableFile.remainingTime !== 0 && <div className="progress-text">{gettext('Remaining')}{' '}{Utils.formatTime(resumableFile.remainingTime)}</div>}
</div>
)}
{!resumableFile.isUploading() && (
<div className="progress-container d-flex align-items-center">
<div className="progress">
<div className="progress-bar" role="progressbar" style={{width: `${progress}%`}} aria-valuenow={progress} aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
)}
</Fragment>
}
{(resumableFile.size < (100 * 1000 * 1000)) &&
<div className="progress-container d-flex align-items-center">
<div className="progress">
<div className="progress-bar" role="progressbar" style={{width: `${progress}%`}} aria-valuenow={progress} aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
}
</Fragment>
}
{this.state.uploadState === UPLOAD_ERROR && (
<div className="message err-message ml-0" dangerouslySetInnerHTML={{__html: error}}></div>
)}
</td>
<td className="upload-operation">
{!item.resumableFile.error && (
<Fragment>
{(!item.isSaved && progress !== 100) && (
<a href="#" onClick={this.onUploadCancel}>{gettext('cancel')}</a>
)}
{(!item.isSaved && progress === 100) && (
<span className="saving">{gettext('saving...')}</span>
)}
{item.isSaved && (
<span className="uploaded">{gettext('uploaded')}</span>
)}
</Fragment>
)}
<Fragment>
{this.state.uploadState === UPLOAD_UPLOADING && (
<a href="#" onClick={this.onUploadCancel}>{gettext('Cancel')}</a>
)}
{this.state.uploadState === UPLOAD_ERROR && (
<a href="#" onClick={this.onUploadRetry}>{gettext('Retry')}</a>
)}
{this.state.uploadState === UPLOAD_ISSAVING && (
<span className="saving">{gettext('Saving...')}</span>
)}
{this.state.uploadState === UPLOAD_UPLOADED && (
<span className="uploaded">{gettext('Uploaded')}</span>
)}
</Fragment>
</td>
</tr>
);

View File

@@ -2,14 +2,18 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import UploadListItem from './upload-list-item';
import { Utils } from '../../utils/utils';
const propTypes = {
uploadBitrate: PropTypes.string.isRequired,
totalProgress: PropTypes.number.isRequired,
retryFileList: PropTypes.array.isRequired,
uploadFileList: PropTypes.array.isRequired,
onCloseUploadDialog: PropTypes.func.isRequired,
onCancelAllUploading: PropTypes.func.isRequired,
onUploadCancel: PropTypes.func.isRequired,
onUploadRetry: PropTypes.func.isRequired,
onUploadRetryAll: PropTypes.func.isRequired,
allFilesUploaded: PropTypes.bool.isRequired,
};
@@ -37,8 +41,10 @@ class UploadProgressDialog extends React.Component {
}
render() {
let uploadBitrate = Utils.formatBitRate(this.props.uploadBitrate)
let uploadedMessage = gettext('File Upload');
let uploadingMessage = gettext('File Uploading...') + ' ' + this.props.totalProgress + '%' + ' (' + this.props.uploadBitrate + ')';
let uploadingMessage = gettext('File Uploading...') + ' ' + this.props.totalProgress + '%' + ' (' + uploadBitrate + ')';
let uploadingOptions = (<span className="sf2-icon-minus" onClick={this.onMinimizeUpload}></span>);
@@ -49,7 +55,7 @@ class UploadProgressDialog extends React.Component {
</Fragment>
);
let { totalProgress, allFilesUploaded } = this.props;
let { totalProgress, allFilesUploaded, retryFileList } = this.props;
return (
<div className="uploader-list-view" style={{height: this.state.isMinimized ? '2.25rem' : '20rem'}}>
@@ -65,19 +71,38 @@ class UploadProgressDialog extends React.Component {
<table className="table-thead-hidden">
<thead>
<tr>
<th width="45%">{gettext('name')}</th>
<th width="40%">{gettext('progress')}</th>
<th width="35%">{gettext('name')}</th>
<th width="15%">{gettext('size')}</th>
<th width="35%">{gettext('progress')}</th>
<th width="15%">{gettext('state')}</th>
</tr>
</thead>
<tbody>
{(!allFilesUploaded) &&
<tr><td className="text-right" colSpan={3}><span className="cursor-pointer" onClick={this.onCancelAllUploading}>{gettext('Cancel All')}</span></td></tr>
}
<tr>
<td className="text-right" colSpan={3}>
{retryFileList.length > 0 ?
<span className="cursor-pointer" onClick={this.props.onUploadRetryAll}>{gettext('Retry All')}</span>
:
<span className="cursor-pointer disabled-link">{gettext('Retry All')}</span>
}
</td>
<td className="text-right" colSpan={1}>
{!allFilesUploaded ?
<span className="cursor-pointer" onClick={this.onCancelAllUploading}>{gettext('Cancel All')}</span>
:
<span className="cursor-pointer disabled-link" >{gettext('Cancel All')}</span>
}
</td>
</tr>
{
this.props.uploadFileList.map((item, index) => {
this.props.uploadFileList.map((resumableFile, index) => {
return (
<UploadListItem key={index} item={item} onUploadCancel={this.props.onUploadCancel}/>
<UploadListItem
key={index}
resumableFile={resumableFile}
onUploadCancel={this.props.onUploadCancel}
onUploadRetry={this.props.onUploadRetry}
/>
);
})
}