diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 534b40a205..e393f171e3 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -210,6 +210,11 @@ module.exports = { require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/pages/search", ], + uploadLink: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/pages/upload-link", + ] }, output: { diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index adcce2eccf..b62f51c4cc 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -90,6 +90,7 @@ module.exports = { orgAdmin: [require.resolve('./polyfills'), paths.appSrc + "/pages/org-admin"], sysAdmin: [require.resolve('./polyfills'), paths.appSrc + "/pages/sys-admin"], search: [require.resolve('./polyfills'), paths.appSrc + "/pages/search"], + uploadLink: [require.resolve('./polyfills'), paths.appSrc + "/pages/upload-link"], }, output: { diff --git a/frontend/src/css/upload-link.css b/frontend/src/css/upload-link.css new file mode 100644 index 0000000000..095644a1a6 --- /dev/null +++ b/frontend/src/css/upload-link.css @@ -0,0 +1,44 @@ +body { + overflow: hidden; +} +#wrapper { + height: 100%; +} +.top-header { + background: #f4f4f7; + border-bottom: 1px solid #e8e8e8; + padding: .5rem 1rem; + flex-shrink: 0; +} + +#upload-link-panel { + width: 928px; + max-width: calc(100% - 20px); + border: 1px solid #ddd; + margin: 2em auto; +} +.shared-by .avatar { + width: 20px; + height: 20px; +} +#upload-link-panel .warning-icon { + color: #f25041; + font-size: 48px; +} +#upload-link-panel .tip-list-item { + list-style: decimal inside none; +} +#upload-link-drop-zone { + background: rgba(240, 159, 63, 0.1); + border: 2px dashed #f09f3f; + border-radius: 4px; + padding: 28px 0; +} +#upload-link-drop-zone .upload-icon { + color: rgba(240, 159, 63, 0.8); + font-size: 60px; + line-height: 1; +} +.mh-2 { + min-height: 2rem; +} diff --git a/frontend/src/pages/upload-link/file-uploader.js b/frontend/src/pages/upload-link/file-uploader.js new file mode 100644 index 0000000000..4f9ebb3bcb --- /dev/null +++ b/frontend/src/pages/upload-link/file-uploader.js @@ -0,0 +1,635 @@ +// This file is copied from frontend/src/components/file-uploader/file-uploader.js, +// and modified according to the requirements of this page. +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import Resumablejs from '@seafile/resumablejs'; +import MD5 from 'MD5'; +import { resumableUploadFileBlockSize, maxUploadFileSize, maxNumberOfFilesForFileupload } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; +import { gettext } from '../../utils/constants'; +import UploadProgressDialog from './upload-progress-dialog'; +import toaster from '../../components/toast'; + +import '../../css/file-uploader.css'; + +const propTypes = { + dragAndDrop: PropTypes.bool.isRequired, + token: PropTypes.string.isRequired, + repoID: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + + filetypes: PropTypes.array, + chunkSize: PropTypes.number, + withCredentials: PropTypes.bool, + testMethod: PropTypes.string, + testChunks: PropTypes.number, + simultaneousUploads: PropTypes.number, + fileParameterName: PropTypes.string, + minFileSizeErrorCallback: PropTypes.func, + fileTypeErrorCallback: PropTypes.func, + onFileUploadSuccess: PropTypes.func.isRequired, +}; + +class FileUploader extends React.Component { + + constructor(props) { + super(props); + this.state = { + retryFileList: [], + uploadFileList: [], + forbidUploadFileList: [], + totalProgress: 0, + isUploadProgressDialogShow: false, + currentResumableFile: null, + uploadBitrate: 0, + allFilesUploaded: false, + }; + + this.uploadInput = React.createRef(); + + this.notifiedFolders = []; + + this.timestamp = null; + this.loaded = 0; + this.bitrateInterval = 500; // Interval in milliseconds to calculate the bitrate + this.isUploadLinkLoaded = false; + + window.onbeforeunload = this.onbeforeunload; + } + + componentDidMount() { + this.resumable = new Resumablejs({ + target: '', + query: this.setQuery || {}, + fileType: this.props.filetypes, + maxFiles: maxNumberOfFilesForFileupload || undefined, + maxFileSize: maxUploadFileSize * 1000 * 1000 || undefined, + testMethod: this.props.testMethod || 'post', + testChunks: this.props.testChunks || false, + headers: this.setHeaders || {}, + withCredentials: this.props.withCredentials || false, + chunkSize: parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1 * 1024 * 1024, + simultaneousUploads: this.props.simultaneousUploads || 1, + fileParameterName: this.props.fileParameterName, + generateUniqueIdentifier: this.generateUniqueIdentifier, + forceChunkSize: true, + maxChunkRetries: 3, + minFileSize: 0, + }); + + this.resumable.assignBrowse(this.uploadInput.current, true); + if (this.props.dragAndDrop) { + this.resumable.assignDrop(document.getElementById('upload-link-drop-zone')); + } + + this.bindCallbackHandler(); + this.bindEventHandler(); + } + + componentWillUnmount = () => { + window.onbeforeunload = null; + if (this.props.dragAndDrop === true) { + this.resumable.disableDropOnDocument(); + } + } + + onbeforeunload = () => { + if (window.uploader && + window.uploader.isUploadProgressDialogShow && + window.uploader.totalProgress !== 100) { + return ''; + } + } + + bindCallbackHandler = () => { + let { minFileSizeErrorCallback, fileTypeErrorCallback } = this.props; + + if (this.maxFilesErrorCallback) { + this.resumable.opts.maxFilesErrorCallback = this.maxFilesErrorCallback; + } + + if (minFileSizeErrorCallback) { + this.resumable.opts.minFileSizeErrorCallback = this.props.minFileSizeErrorCallback; + } + + if (this.maxFileSizeErrorCallback) { + this.resumable.opts.maxFileSizeErrorCallback = this.maxFileSizeErrorCallback; + } + + if (fileTypeErrorCallback) { + this.resumable.opts.fileTypeErrorCallback = this.props.fileTypeErrorCallback; + } + + } + + bindEventHandler = () => { + this.resumable.on('chunkingComplete', this.onChunkingComplete.bind(this)); + this.resumable.on('fileAdded', this.onFileAdded.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('fileError', this.onFileError.bind(this)); + this.resumable.on('error', this.onError.bind(this)); + this.resumable.on('dragstart', this.onDragStart.bind(this)); + } + + maxFilesErrorCallback = (files, errorCount) => { + let maxFiles = maxNumberOfFilesForFileupload; + let message = gettext('Please upload no more than {maxFiles} files at a time.'); + message = message.replace('{maxFiles}', maxFiles); + toaster.danger(message); + } + + maxFileSizeErrorCallback = (file) => { + let { forbidUploadFileList } = this.state; + forbidUploadFileList.push(file); + this.setState({forbidUploadFileList: forbidUploadFileList}); + } + + onChunkingComplete = (resumableFile) => { + + let allFilesUploaded = this.state.allFilesUploaded; + if (allFilesUploaded === true) { + this.setState({allFilesUploaded: false}); + } + + let path = this.props.path; + let fileName = resumableFile.fileName; + let relativePath = resumableFile.relativePath; + let isFile = fileName === relativePath; + + resumableFile.formData = {}; + if (isFile) { // upload file + resumableFile.formData = { + parent_dir: path, + }; + } else { // upload folder + let relative_path = relativePath.slice(0, relativePath.lastIndexOf('/') + 1); + resumableFile.formData = { + parent_dir: path, + relative_path: relative_path + }; + } + } + + onFileAdded = (resumableFile, files) => { + let isFile = resumableFile.fileName === resumableFile.relativePath; + 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); + seafileAPI.sharedUploadLinkGetFileUploadUrl(this.props.token).then(res => { + this.resumable.opts.target = res.data.upload_link + '?ret-json=1'; + this.resumableUpload(resumableFile); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + } else { + this.setUploadFileList(this.resumable.files); + if (!this.isUploadLinkLoaded) { + this.isUploadLinkLoaded = true; + seafileAPI.sharedUploadLinkGetFileUploadUrl(this.props.token).then(res => { + this.resumable.opts.target = res.data.upload_link + '?ret-json=1'; + this.resumable.upload(); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + } + } + + resumableUpload = (resumableFile) => { + let { repoID, path } = this.props; + seafileAPI.getFileUploadedBytes(repoID, path, resumableFile.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); + this.resumable.upload(); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + filesAddedComplete = (resumable, files) => { + let { forbidUploadFileList } = this.state; + if (forbidUploadFileList.length > 0 && files.length === 0) { + this.setState({ + isUploadProgressDialogShow: true, + totalProgress: 100 + }); + } + } + + setUploadFileList = () => { + let uploadFileList = this.resumable.files; + this.setState({ + uploadFileList: uploadFileList, + isUploadProgressDialogShow: true, + }); + Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true); + } + + onFileProgress = (resumableFile) => { + let uploadBitrate = this.getBitrate(); + let uploadFileList = this.state.uploadFileList.map(item => { + if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) { + if (uploadBitrate) { + let lastSize = (item.size - (item.size * item.progress())) * 8; + let time = Math.floor(lastSize / uploadBitrate); + item.remainingTime = time; + } + } + return item; + }); + + 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; + }); + + if (this.timestamp) { + let timeDiff = (now - this.timestamp); + 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; + + return uploadBitrate; + } + + onProgress = () => { + let progress = Math.round(this.resumable.progress() * 100); + this.setState({totalProgress: progress}); + Utils.registerGlobalVariable('uploader', 'totalProgress', progress); + } + + 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 + let isExist = this.notifiedFolders.some(item => {return item.name === dirent.name;}); + if (!isExist) { + this.notifiedFolders.push(dirent); + this.props.onFileUploadSuccess(dirent); + } + + // update uploadFileList + let uploadFileList = this.state.uploadFileList.map(item => { + if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) { + item.newFileName = relative_path + message.name; + item.isSaved = true; + } + 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 + + let uploadFileList = this.state.uploadFileList.map(item => { + if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) { + item.newFileName = fileName; + item.isSaved = true; + } + return item; + }); + this.setState({uploadFileList: uploadFileList}); + + return; + } + + // upload file -- add files + let dirent = { + id: message.id, + type: 'file', + name: message.name, + size: message.size, + mtime: currentTime, + }; + this.props.onFileUploadSuccess(dirent); + + let uploadFileList = this.state.uploadFileList.map(item => { + if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) { + item.newFileName = message.name; + item.isSaved = true; + } + return item; + }); + this.setState({uploadFileList: uploadFileList}); + } + + onFileError = (resumableFile, message) => { + let error = ''; + if (!message) { + error = gettext('Network error'); + } else { + // eg: '{"error": "Internal error" \n }' + let errorMessage = message.replace(/\n/g, ''); + errorMessage = JSON.parse(errorMessage); + error = errorMessage.error; + if (error === 'File locked by others.') { + error = gettext('File is locked by others.'); + } + if (error === 'Internal error.') { + error = gettext('Internal Server Error'); + } + } + + let uploadFileList = this.state.uploadFileList.map(item => { + if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) { + this.state.retryFileList.push(item); + item.error = error; + } + return item; + }); + + this.loaded = 0; // reset loaded data; + this.setState({ + retryFileList: this.state.retryFileList, + uploadFileList: uploadFileList + }); + + } + + onComplete = () => { + this.notifiedFolders = []; + // reset upload link loaded + this.isUploadLinkLoaded = false; + this.setState({allFilesUploaded: true}); + } + + onError = (message) => { + // reset upload link loaded + this.isUploadLinkLoaded = false; + // After the error, the user can switch windows + Utils.registerGlobalVariable('uploader', 'totalProgress', 100); + } + + setHeaders = (resumableFile, resumable) => { + let offset = resumable.offset; + let chunkSize = resumable.getOpt('chunkSize'); + let fileSize = resumableFile.size === 0 ? 1 : resumableFile.size; + 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; + } + + let headers = { + 'Accept': 'application/json; text/javascript, */*; q=0.01', + 'Content-Disposition': 'attachment; filename="' + encodeURI(resumableFile.fileName) + '"', + 'Content-Range': 'bytes ' + startByte + '-' + endByte + '/' + fileSize, + }; + + 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 = () => { + this.uploadInput.current.removeAttribute('webkitdirectory'); + this.uploadInput.current.click(); + } + + onFolderUpload = () => { + this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory'); + this.uploadInput.current.click(); + } + + onDragStart = () => { + this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory'); + } + + onCloseUploadDialog = () => { + this.loaded = 0; + this.resumable.files = []; + // reset upload link loaded + this.isUploadLinkLoaded = false; + this.setState({isUploadProgressDialogShow: false, uploadFileList: [], forbidUploadFileList: []}); + Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', false); + } + + onUploadCancel = (uploadingItem) => { + + let uploadFileList = this.state.uploadFileList.filter(item => { + if (item.uniqueIdentifier === uploadingItem.uniqueIdentifier) { + item.cancel(); // execute cancel function will delete the file at the same time + return false; + } + return true; + }); + + 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 => { + if (Math.round(item.progress() !== 1)) { + item.cancel(); + return false; + } + return true; + }); + + this.loaded = 0; + + this.setState({ + allFilesUploaded: true, + totalProgress: '100', + uploadFileList: uploadFileList + }); + // reset upload link loaded + this.isUploadLinkLoaded = false; + } + + onUploadRetry = (resumableFile) => { + seafileAPI.sharedUploadLinkGetFileUploadUrl(this.props.token).then(res => { + this.resumable.opts.target = res.data.upload_link + '?ret-json=1'; + 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; + this.retryUploadFile(item); + } + return item; + }); + + this.setState({ + retryFileList: retryFileList, + uploadFileList: uploadFileList + }); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + 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; + }); + + } + + replaceRepetitionFile = () => { + let { repoID, path } = this.props; + seafileAPI.getUpdateLink(repoID, path).then(res => { + this.resumable.opts.target = res.data; + + 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.setUploadFileList(this.resumable.files); + this.resumable.upload(); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + cancelFileUpload = () => { + this.resumable.files.pop(); //delete latest fileļ¼› + } + + render() { + return ( + +
+
+ +
+
+ +
+ ); + } +} + +FileUploader.propTypes = propTypes; + +export default FileUploader; diff --git a/frontend/src/pages/upload-link/forbid-upload-list-item.js b/frontend/src/pages/upload-link/forbid-upload-list-item.js new file mode 100644 index 0000000000..77de671bb6 --- /dev/null +++ b/frontend/src/pages/upload-link/forbid-upload-list-item.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { gettext, maxUploadFileSize } from '../../utils/constants'; + +const propTypes = { + file: PropTypes.object, +}; + +class ForbidUploadListItem extends React.Component { + + render() { + let { file } = this.props; + let msg = gettext('Please upload files less than {placeholder}M').replace('{placeholder}', maxUploadFileSize); + return ( + + +
{file.name}
+ + + {msg} + + ); + } +} + +ForbidUploadListItem.propTypes = propTypes; + +export default ForbidUploadListItem; diff --git a/frontend/src/pages/upload-link/index.js b/frontend/src/pages/upload-link/index.js new file mode 100644 index 0000000000..a187490581 --- /dev/null +++ b/frontend/src/pages/upload-link/index.js @@ -0,0 +1,72 @@ +import React, { Fragment } from 'react'; +import ReactDOM from 'react-dom'; +import { Utils } from '../../utils/utils'; +import { gettext } from '../../utils/constants'; +import Logo from '../../components/logo'; +import Account from '../../components/common/account'; +import FileUploader from './file-uploader'; + +import '../../css/upload-link.css'; + +const loggedUser = window.app.pageOptions.username; +const { + dirName, + sharedBy, + noQuota, + maxUploadFileSize, + token, + repoID, + path +} = window.uploadLink; + + +class SharedUploadLink extends React.Component { + + render() { + return ( +
+
+ + {loggedUser && } +
+
+ +
+
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/pages/upload-link/upload-list-item.js b/frontend/src/pages/upload-link/upload-list-item.js new file mode 100644 index 0000000000..df3a54ba97 --- /dev/null +++ b/frontend/src/pages/upload-link/upload-list-item.js @@ -0,0 +1,144 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { gettext } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; + +const propTypes = { + 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.remainingTime === 0 && !resumableFile.isSaved) { + uploadState = UPLOAD_ISSAVING; + } + + if (resumableFile.isSaved) { + uploadState = UPLOAD_UPLOADED; + } + } + + this.setState({uploadState: uploadState}); + } + + onUploadCancel = (e) => { + e.preventDefault(); + this.props.onUploadCancel(this.props.resumableFile); + } + + onUploadRetry = (e) => { + e.preventDefault(); + this.props.onUploadRetry(this.props.resumableFile); + } + + formatFileSize = (size) => { + if (typeof size !== 'number') { + return ''; + } + if (size >= 1000 * 1000 * 1000) { + return (size / (1000 * 1000 * 1000)).toFixed(1) + ' G'; + } + if (size >= 1000 * 1000) { + return (size / (1000 * 1000)).toFixed(1) + ' M'; + } + if (size >= 1000) { + return (size / 1000).toFixed(1) + ' K'; + } + return size.toFixed(1) + ' B'; + } + + render() { + let { resumableFile } = this.props; + let progress = Math.round(resumableFile.progress() * 100); + let error = resumableFile.error; + + return ( + + +
{resumableFile.newFileName}
+ + + {this.formatFileSize(resumableFile.size)} + + + {(this.state.uploadState === UPLOAD_UPLOADING || this.state.uploadState === UPLOAD_ISSAVING) && + + {resumableFile.size >= (100 * 1000 * 1000) && + + {resumableFile.isUploading() && ( +
+
+
+
+ {(resumableFile.remainingTime === -1) &&
{gettext('Preparing to upload...')}
} + {(resumableFile.remainingTime > 0) &&
{gettext('Remaining')}{' '}{Utils.formatTime(resumableFile.remainingTime)}
} + {(resumableFile.remainingTime === 0) &&
{gettext('Indexing...')}
} +
+ )} + {!resumableFile.isUploading() && ( +
+
+
+
+
+ )} +
+ } + {(resumableFile.size < (100 * 1000 * 1000)) && +
+
+
+
+
+ } +
+ } + {this.state.uploadState === UPLOAD_ERROR && ( +
+ )} + + + + {this.state.uploadState === UPLOAD_UPLOADING && ( + {gettext('Cancel')} + )} + {this.state.uploadState === UPLOAD_ERROR && ( + {gettext('Retry')} + )} + {this.state.uploadState === UPLOAD_ISSAVING && ( + {gettext('Saving...')} + )} + {this.state.uploadState === UPLOAD_UPLOADED && ( + {gettext('Uploaded')} + )} + + + + ); + } +} + +UploadListItem.propTypes = propTypes; + +export default UploadListItem; diff --git a/frontend/src/pages/upload-link/upload-progress-dialog.js b/frontend/src/pages/upload-link/upload-progress-dialog.js new file mode 100644 index 0000000000..b95f9e4c04 --- /dev/null +++ b/frontend/src/pages/upload-link/upload-progress-dialog.js @@ -0,0 +1,87 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { Button, ButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import { gettext } from '../../utils/constants'; +import UploadListItem from './upload-list-item'; +import ForbidUploadListItem from './forbid-upload-list-item'; + +const propTypes = { + uploadFileList: PropTypes.array.isRequired, + forbidUploadFileList: PropTypes.array.isRequired, + onCancelAllUploading: PropTypes.func.isRequired, + onUploadCancel: PropTypes.func.isRequired, + onUploadRetry: PropTypes.func.isRequired, + onFileUpload: PropTypes.func.isRequired, + onFolderUpload: PropTypes.func.isRequired, + allFilesUploaded: PropTypes.bool.isRequired +}; + +class UploadProgressDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + dropdownOpen: false + }; + } + + toggleDropdown = () => { + this.setState({ + dropdownOpen: !this.state.dropdownOpen + }); + } + + render() { + let { allFilesUploaded } = this.props; + return ( + +
+ + {gettext('Upload')} + + {gettext('Upload Files')} + {gettext('Upload Folder')} + + + +
+
+ + + + + + + + + + + {this.props.forbidUploadFileList.map((file, index) => { + return (); + })} + {this.props.uploadFileList.map((resumableFile, index) => { + return ( + + ); + }) + } + +
{gettext('name')}{gettext('size')}{gettext('progress')}{gettext('state')}
+
+
+ ); + } +} + +UploadProgressDialog.propTypes = propTypes; + +export default UploadProgressDialog; diff --git a/media/css/seahub.css b/media/css/seahub.css index 3426cdc1ed..6a74597d34 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -2020,13 +2020,6 @@ a.sf-popover-item { vertical-align:middle; line-height:16px; } -#upload-link-panel { - width:600px; - padding:10px 20px 20px; -} -#upload-link-panel .avatar { - vertical-align: middle; -} /* user profile */ #user-profile { width: 290px; diff --git a/media/css/sf_font3/iconfont.css b/media/css/sf_font3/iconfont.css index 7df3e820bb..adda71cb7f 100644 --- a/media/css/sf_font3/iconfont.css +++ b/media/css/sf_font3/iconfont.css @@ -1,10 +1,10 @@ @font-face {font-family: "sf3-font"; - src: url('iconfont.eot?t=1585125765434'); /* IE9 */ - src: url('iconfont.eot?t=1585125765434#iefix') format('embedded-opentype'), /* IE6-IE8 */ - url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), - url('iconfont.woff?t=1585125765434') format('woff'), - url('iconfont.ttf?t=1585125765434') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ - url('iconfont.svg?t=1585125765434#sf3-font') format('svg'); /* iOS 4.1- */ + src: url('iconfont.eot?t=1593420657805'); /* IE9 */ + src: url('iconfont.eot?t=1593420657805#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), + url('iconfont.woff?t=1593420657805') format('woff'), + url('iconfont.ttf?t=1593420657805') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1593420657805#sf3-font') format('svg'); /* iOS 4.1- */ } .sf3-font { @@ -15,6 +15,14 @@ -moz-osx-font-smoothing: grayscale; } +.sf3-font-tips:before { + content: "\e6d4"; +} + +.sf3-font-upload:before { + content: "\e6d3"; +} + .sf3-font-logout:before { content: "\e6bb"; } diff --git a/media/css/sf_font3/iconfont.eot b/media/css/sf_font3/iconfont.eot index 6719b32a8b..c42e4fbc4c 100644 Binary files a/media/css/sf_font3/iconfont.eot and b/media/css/sf_font3/iconfont.eot differ diff --git a/media/css/sf_font3/iconfont.js b/media/css/sf_font3/iconfont.js index a7221d1a3b..8d7f9a2709 100644 --- a/media/css/sf_font3/iconfont.js +++ b/media/css/sf_font3/iconfont.js @@ -1 +1 @@ -!function(n){var c,i='',t=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(t&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),c()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(l=c,o=n.document,h=!1,(i=function(){try{o.documentElement.doScroll("left")}catch(c){return void setTimeout(i,50)}e()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,e())});function e(){h||(h=!0,l())}var l,o,h,i}(function(){var c,t,e,l,o,h;(c=document.createElement("div")).innerHTML=i,i=null,(t=c.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(l=document.body).firstChild?(o=e,(h=l.firstChild).parentNode.insertBefore(o,h)):l.appendChild(e))})}(window); \ No newline at end of file +!function(c){var t,e,l,o,h,i,s,v='',n=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(n&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function a(){i||(i=!0,o())}e=function(){var c,t,e,l,o,h=document.createElement("div");h.innerHTML=v,v=null,(c=h.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",t=c,(e=document.body).firstChild?(l=t,(o=e.firstChild).parentNode.insertBefore(l,o)):e.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(e,0):(l=function(){document.removeEventListener("DOMContentLoaded",l,!1),e()},document.addEventListener("DOMContentLoaded",l,!1)):document.attachEvent&&(o=e,h=c.document,i=!1,(s=function(){try{h.documentElement.doScroll("left")}catch(c){return void setTimeout(s,50)}a()})(),h.onreadystatechange=function(){"complete"==h.readyState&&(h.onreadystatechange=null,a())})}(window); \ No newline at end of file diff --git a/media/css/sf_font3/iconfont.svg b/media/css/sf_font3/iconfont.svg index af261659ac..6edbbaeef6 100644 --- a/media/css/sf_font3/iconfont.svg +++ b/media/css/sf_font3/iconfont.svg @@ -20,6 +20,12 @@ Created by iconfont /> + + + + + + diff --git a/media/css/sf_font3/iconfont.ttf b/media/css/sf_font3/iconfont.ttf index 900164661f..4684b08ff1 100644 Binary files a/media/css/sf_font3/iconfont.ttf and b/media/css/sf_font3/iconfont.ttf differ diff --git a/media/css/sf_font3/iconfont.woff b/media/css/sf_font3/iconfont.woff index fccf6ecd8e..71040ec767 100644 Binary files a/media/css/sf_font3/iconfont.woff and b/media/css/sf_font3/iconfont.woff differ diff --git a/media/css/sf_font3/iconfont.woff2 b/media/css/sf_font3/iconfont.woff2 index e051a746c0..98fe94365c 100644 Binary files a/media/css/sf_font3/iconfont.woff2 and b/media/css/sf_font3/iconfont.woff2 differ diff --git a/seahub/templates/view_shared_upload_link_react.html b/seahub/templates/view_shared_upload_link_react.html new file mode 100644 index 0000000000..1e68e73c86 --- /dev/null +++ b/seahub/templates/view_shared_upload_link_react.html @@ -0,0 +1,27 @@ +{% extends "base_for_react.html" %} +{% load seahub_tags i18n avatar_tags %} +{% load render_bundle from webpack_loader %} + +{% block extra_style %} +{% render_bundle 'uploadLink' 'css' %} +{% endblock %} + +{% block extra_script %} + +{% render_bundle 'uploadLink' 'js' %} +{% endblock %} diff --git a/seahub/views/repo.py b/seahub/views/repo.py index 403c69bc8f..cb36a187a7 100644 --- a/seahub/views/repo.py +++ b/seahub/views/repo.py @@ -391,7 +391,8 @@ def view_shared_upload_link(request, uploadlink): no_quota = True if seaserv.check_quota(repo_id) < 0 else False - return render(request, 'view_shared_upload_link.html', { + return render(request, 'view_shared_upload_link_react.html', { + #return render(request, 'view_shared_upload_link.html', { 'repo': repo, 'path': path, 'username': username,