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

improve upload ui (#2780)

This commit is contained in:
杨顺强
2019-01-08 11:26:20 +08:00
committed by Daniel Pan
parent db2d0e01e7
commit 93a2be4ada
5 changed files with 136 additions and 39 deletions

View File

@@ -4,6 +4,7 @@ import Resumablejs from '@seafile/resumablejs';
import MD5 from 'MD5';
import { enableResumableFileUpload } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import UploadProgressDialog from './upload-progress-dialog';
import UploadRemindDialog from '../dialog/upload-remind-dialog';
import '../../css/file-uploader.css';
@@ -39,9 +40,14 @@ class FileUploader extends React.Component {
isUploadProgressDialogShow: false,
isUploadRemindDialogShow: false,
currentResumableFile: null,
uploadBitrate: 0
};
this.notifiedFolders = [];
this.timestamp = null;
this.loaded = 0;
this.bitrateInterval = 500; // Interval in milliseconds to calculate the bitrate
}
componentDidMount() {
@@ -95,20 +101,20 @@ class FileUploader extends React.Component {
}
bindEventHandler = () => {
this.resumable.on('chunkingComplete', this.onChunkingComplete);
this.resumable.on('fileAdded', this.onFileAdded);
this.resumable.on('filesAddedComplete', this.filesAddedComplete);
this.resumable.on('fileProgress', this.onFileProgress);
this.resumable.on('fileSuccess', this.onFileUploadSuccess);
this.resumable.on('progress', this.onProgress);
this.resumable.on('complete', this.onComplete);
this.resumable.on('pause', this.onPause);
this.resumable.on('fileRetry', this.onFileRetry);
this.resumable.on('fileError', this.onFileError);
this.resumable.on('error', this.onError);
this.resumable.on('beforeCancel', this.onBeforeCancel);
this.resumable.on('cancel', this.onCancel);
this.resumable.on('dragstart', this.onDragStart);
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));
}
onChunkingComplete = (file) => {
@@ -164,11 +170,11 @@ class FileUploader extends React.Component {
});
} else {
this.setUploadFileList(this.resumable.files);
resumableFile.upload();
this.resumable.upload();
}
} else {
this.setUploadFileList(this.resumable.files);
resumableFile.upload();
this.resumable.upload();
}
}
@@ -205,10 +211,38 @@ class FileUploader extends React.Component {
this.setState({uploadFileList: uploadFileList});
}
getBitrate = () => {
let loaded = 0;
let uploadBitrate = 0;
let now = new Date().getTime();
this.resumable.files.forEach(file => {
loaded += file.progress() * file.size;
});
if (this.timestamp) {
let timeDiff = (now - this.timestamp);
if (timeDiff < this.bitrateInterval) {
return this.state.uploadBitrate;
}
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);
this.setState({totalProgress: progress});
let uploadBitrate = this.getBitrate();
this.setState({
totalProgress: progress,
uploadBitrate: uploadBitrate
});
}
onFileUploadSuccess = (resumableFile, message) => {
@@ -370,17 +404,18 @@ class FileUploader extends React.Component {
});
}
onMinimizeUploadDialog = () => {
this.setState({isUploadProgressDialogShow: false});
}
onCloseUploadDialog = () => {
this.setState({isUploadProgressDialogShow: false, uploadFileList: []});
}
onUploadCancel = (resumableFile) => {
onUploadCancel = (uploadingItem) => {
let uploadFileList = this.state.uploadFileList.filter(item => {
return item.uniqueIdentifier !== resumableFile.uniqueIdentifier;
if (item.uniqueIdentifier === uploadingItem.uniqueIdentifier) {
uploadingItem.resumableFile.cancel();
this.resumable.removeFile(uploadingItem.resumableFile.file);
} else {
return item;
}
});
let newUploaderFileList = uploadFileList.map(item => {
let progress = Math.round(item.resumableFile.progress() * 100);
@@ -390,6 +425,19 @@ class FileUploader extends React.Component {
this.setState({uploadFileList: newUploaderFileList});
}
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;
}
});
this.setState({uploadFileList: uploadFileList});
}
onUploaderRetry = () => {
}
@@ -447,9 +495,10 @@ class FileUploader extends React.Component {
<UploadProgressDialog
uploadFileList={this.state.uploadFileList}
totalProgress={this.state.totalProgress}
onMinimizeUploadDialog={this.onMinimizeUploadDialog}
onCloseUploadDialog={this.onCloseUploadDialog}
onCancelAllUploading={this.onCancelAllUploading}
onUploadCancel={this.onUploadCancel}
uploadBitrate={this.state.uploadBitrate}
/>
}
</Fragment>

View File

@@ -11,9 +11,7 @@ class UploadListItem extends React.Component {
onUploadCancel = (e) => {
e.preventDefault();
let item = this.props.item;
item.resumableFile.cancel();
this.props.onUploadCancel(item);
this.props.onUploadCancel(this.props.item);
}
formatFileSize = (size) => {
@@ -37,13 +35,16 @@ class UploadListItem extends React.Component {
let progress = Math.round(item.resumableFile.progress() * 100);
return (
<tr className="file-upload-item">
<td width="50%" className="upload-name ellipsis">{item.resumableFile.relativePath}</td>
<td width="30%" className="upload-progress upload-size">
{
progress === 100 ? this.formatFileSize(item.resumableFile.size) : progress + '%'
<td className="upload-name ellipsis">{item.resumableFile.relativePath}</td>
<td className="upload-progress">
<span className="file-size">{this.formatFileSize(item.resumableFile.size)}</span>
{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>
}
</td>
<td width="20%" className="upload-operation">
<td className="upload-operation">
{ progress !== 100 ?
<a href="#" onClick={this.onUploadCancel}>{gettext(('cancel'))}</a> :
<span>{gettext('uploaded')}</span>

View File

@@ -4,18 +4,30 @@ import { gettext } from '../../utils/constants';
import UploadListItem from './upload-list-item';
const propTypes = {
uploadBitrate: PropTypes.number.isRequired,
totalProgress: PropTypes.number.isRequired,
uploadFileList: PropTypes.array.isRequired,
onMinimizeUploadDialog: PropTypes.func.isRequired,
onCloseUploadDialog: PropTypes.func.isRequired,
onCancelAllUploading: PropTypes.func.isRequired,
onUploadCancel: PropTypes.func.isRequired,
};
class UploadProgressDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
isMinimized: false
};
}
onCancelAllUploading = () => {
this.props.onCancelAllUploading();
}
onMinimizeUpload = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.props.onMinimizeUploadDialog();
this.setState({isMinimized: !this.state.isMinimized});
}
onCloseUpload = (e) => {
@@ -25,7 +37,7 @@ class UploadProgressDialog extends React.Component {
render() {
let uploadedMessage = gettext('File Upload');
let uploadingMessage = gettext('File Uploading...') + this.props.totalProgress + '%';
let uploadingMessage = gettext('File Uploading...') + ' ' + this.props.totalProgress + '%' + ' (' + this.props.uploadBitrate + ')';
let uploadingOptions = (<span className="sf2-icon-minus" onClick={this.onMinimizeUpload}></span>);
@@ -39,7 +51,7 @@ class UploadProgressDialog extends React.Component {
let totalProgress = this.props.totalProgress;
return (
<div className="uploader-list-view">
<div className="uploader-list-view" style={{height: this.state.isMinimized ? '2.25rem' : '20rem'}}>
<div className="uploader-list-header">
<div className="title">
{totalProgress === 100 ? uploadedMessage : uploadingMessage}
@@ -49,8 +61,18 @@ class UploadProgressDialog extends React.Component {
</div>
</div>
<div className="uploader-list-content">
<table>
<table className="table-thead-hidden">
<thead>
<tr>
<th width="50%">{gettext('name')}</th>
<th width="40%">{gettext('progress')}</th>
<th width="10%">{gettext('state')}</th>
</tr>
</thead>
<tbody>
{(this.props.totalProgress !== 100) &&
<tr><td className="text-right" colSpan={3}><span className="cursor-pointer" onClick={this.onCancelAllUploading}>{gettext('Cancel All')}</span></td></tr>
}
{
this.props.uploadFileList.map((item, index) => {
return (

View File

@@ -15,8 +15,7 @@
right: 1px;
bottom: 1px;
width: 35rem;
min-height: 15rem;
max-height: 20rem;
height: 20rem;
border: 1px solid #ddd;
border-radius: 3px;
box-shadow: 0 0 6px #ddd;
@@ -46,4 +45,12 @@
padding: 0.625rem 1rem 1.25rem;
background-color: #fff;
overflow: auto;
}
.file-upload-item .progress {
width: 80%;
}
.file-upload-item .progress .progress-bar {
color: #e83;
}

View File

@@ -363,6 +363,24 @@ export const Utils = {
}
},
formatBitRate: function(bits) {
var Bs;
if (typeof bits !== 'number') {
return '';
}
Bs = bits / 8;
if (Bs >= 1000000000) {
return (Bs / 1000000000).toFixed(2) + ' GB/s';
}
if (Bs >= 1000000) {
return (Bs / 1000000).toFixed(2) + ' MB/s';
}
if (Bs >= 1000) {
return (Bs / 1000).toFixed(2) + ' kB/s';
}
return Bs.toFixed(2) + ' B/s';
},
isMarkdownFile: function(filePath) {
let index = filePath.lastIndexOf('.');
if (index === -1) {