diff --git a/frontend/src/components/file-uploader/file-uploader.js b/frontend/src/components/file-uploader/file-uploader.js index a8dfda9b4f..29d422c844 100644 --- a/frontend/src/components/file-uploader/file-uploader.js +++ b/frontend/src/components/file-uploader/file-uploader.js @@ -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 { } diff --git a/frontend/src/components/file-uploader/upload-list-item.js b/frontend/src/components/file-uploader/upload-list-item.js index 23052db0fe..3a15066c0c 100644 --- a/frontend/src/components/file-uploader/upload-list-item.js +++ b/frontend/src/components/file-uploader/upload-list-item.js @@ -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 ( - {item.resumableFile.relativePath} - - { - progress === 100 ? this.formatFileSize(item.resumableFile.size) : progress + '%' + {item.resumableFile.relativePath} + + {this.formatFileSize(item.resumableFile.size)} + {progress !== 100 && +
+
+
} - + { progress !== 100 ? {gettext(('cancel'))} : {gettext('uploaded')} diff --git a/frontend/src/components/file-uploader/upload-progress-dialog.js b/frontend/src/components/file-uploader/upload-progress-dialog.js index 3b9842e0ae..c7937879ee 100644 --- a/frontend/src/components/file-uploader/upload-progress-dialog.js +++ b/frontend/src/components/file-uploader/upload-progress-dialog.js @@ -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 = (); @@ -39,7 +51,7 @@ class UploadProgressDialog extends React.Component { let totalProgress = this.props.totalProgress; return ( -
+
{totalProgress === 100 ? uploadedMessage : uploadingMessage} @@ -49,8 +61,18 @@ class UploadProgressDialog extends React.Component {
- +
+ + + + + + + + {(this.props.totalProgress !== 100) && + + } { this.props.uploadFileList.map((item, index) => { return ( diff --git a/frontend/src/css/file-uploader.css b/frontend/src/css/file-uploader.css index 1afb021283..77a2dcdf3f 100644 --- a/frontend/src/css/file-uploader.css +++ b/frontend/src/css/file-uploader.css @@ -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; } \ No newline at end of file diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index deef363531..08be34b896 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -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) {
{gettext('name')}{gettext('progress')}{gettext('state')}
{gettext('Cancel All')}