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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +212,37 @@ 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>
|
||||
|
@@ -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>
|
||||
|
@@ -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 (
|
||||
|
@@ -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;
|
||||
@@ -47,3 +46,11 @@
|
||||
background-color: #fff;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.file-upload-item .progress {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.file-upload-item .progress .progress-bar {
|
||||
color: #e83;
|
||||
}
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user