From 25cee90edab5989538e8ffea0cdfca712aeaf435 Mon Sep 17 00:00:00 2001 From: llj Date: Tue, 20 Oct 2020 16:24:25 +0800 Subject: [PATCH] [shared upload link] rewrote it with react (#4611) * [shared upload link] rewrote it with react * modification --- frontend/config/webpack.config.dev.js | 5 + frontend/config/webpack.config.prod.js | 1 + frontend/src/css/upload-link.css | 44 ++ .../src/pages/upload-link/file-uploader.js | 635 ++++++++++++++++++ .../upload-link/forbid-upload-list-item.js | 28 + frontend/src/pages/upload-link/index.js | 72 ++ .../src/pages/upload-link/upload-list-item.js | 144 ++++ .../upload-link/upload-progress-dialog.js | 87 +++ media/css/seahub.css | 7 - media/css/sf_font3/iconfont.css | 20 +- media/css/sf_font3/iconfont.eot | Bin 4488 -> 4836 bytes media/css/sf_font3/iconfont.js | 2 +- media/css/sf_font3/iconfont.svg | 6 + media/css/sf_font3/iconfont.ttf | Bin 4320 -> 4668 bytes media/css/sf_font3/iconfont.woff | Bin 2780 -> 2988 bytes media/css/sf_font3/iconfont.woff2 | Bin 2256 -> 2404 bytes .../view_shared_upload_link_react.html | 27 + seahub/views/repo.py | 3 +- 18 files changed, 1066 insertions(+), 15 deletions(-) create mode 100644 frontend/src/css/upload-link.css create mode 100644 frontend/src/pages/upload-link/file-uploader.js create mode 100644 frontend/src/pages/upload-link/forbid-upload-list-item.js create mode 100644 frontend/src/pages/upload-link/index.js create mode 100644 frontend/src/pages/upload-link/upload-list-item.js create mode 100644 frontend/src/pages/upload-link/upload-progress-dialog.js create mode 100644 seahub/templates/view_shared_upload_link_react.html 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 6719b32a8beca143fc1bceaba94538a07f0a7285..c42e4fbc4cea53a4315ad7cd0b6a33b19342fb6e 100644 GIT binary patch delta 931 zcmY+CPe>F|9LK-EnVng8b#``i-BnAA-EXEBizn2GBV0AJJ#pqqav7j|0Iqbz&BTec^CBR5nV#FRySJJ*o;?Q~(||kHb<^xD zzp?(A_iiweE*6}d@;Hy*F-N-M14B{b5onFOXsoxx^r+r-j(@=O!*O#cK|#Fb^)hST zW5#dJyr}qb3DAqUU_8;=KQPz$i<23aq4%jy3RUZ{QZm~!RpIM;^d|}(EC3%5W$yg_ zre0Aw>8WLUNo%Pt>0D}DDoDMS{FfK<(ZD?9{OA~e-JEU#4m{*&KfJ7{LJ}>aRhXhr z42pTND3-)pE(ZwOr_bi~)m$!R%B6M0v1R+(L0ApY;6V`zxrz$~a6*B~wd8cOY*2F1 zGR%+1N^Qdd5Ry$^Z{ab+Ng!HnI30v(W7!vdHe3Q?(1wda%-V2Sh&dZJ05NZqWG@hG z!h&5vEZOiGK&;xpGQ&U6;?L;;=kXMu=o$^tBYHzS(gkT*ZjkRgB1*)H%}vW2ay#2R zfTMs?gKz{*egss=LABKI`A<@Pq|rz=)Pz{5BwIK|JeRPN{jMlvtgMisDB0aFy3ePd z{l9BhNY7+Qx7IV6?5_09r)R%fd#FVBWjp=_uxHyepE*zqG}Ti0G&S*CMae)F1w7;o z1#7r=#4=S!H9?(Jf8cn%pM9yJz1Wx}jdXjaVzDVt_lP8o#im`F=4v(Eg=wu)OS=k; ztCmorjkYBwJw3xj!#$qKMBAvQxp}Kk-_&(cR6?3-+O1iq3CJI$9lk5+YGtjE@9Cbm Y!XTZza13-O`qjZitk>*Jl^XrO0W3M%`~Uy| delta 610 zcmYk2J!lkB6ot=yGdue?qw5bLE-7}2v9gE|LJ$irY%D@T#FR3dU0o&PZvJ3}Kq?`S zDokmi3!)*IY(S!sO<`qiVPzFjF2Tw|K`e}C_9XyelmG!G@D_ejp1GqI4h0WJ*N^QVo$c?+R5APfvED?e@p_w zI~qpK#=_#gYkmY2hIwHtuFBw0fMg`8bjHiKPhRrf+hy*9km>)2l9W&2iKwT(sDE76mG<8lBQ<4Rv0ZbVv8JdvpY-98q*;!.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}")}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 900164661f62e2ce54baff6b18b529103b7854ed..4684b08ff1b2d6d4665354008dfeae7e5024577c 100644 GIT binary patch delta 963 zcmY+CO-NKx6vxkbU-S9ioxyRm53OcoTqFjjGel;K2%<$4QeunprIx~*@#RMtM3a!Y zu_y;5XwkT-of=ajXwb@q47AXlRs+Iq)0a>LEoM4*UZB1Ae)rw;zvtZZe;V z=@kIH0|1x0lcs$v=Q;-<1&GuQ>-P2Yg{RK|lq`VP>gzRo_FUWiM7aeb-baFKOMXc6 zWumSxIXv2mGy`0xq177bHVrMXN%i+AKA1E|ZH&MhTCWyy%1rjYe7^VR1pwx!hDm#1 zXn3yWH*FRahTc9|j-F&7b|gN(D7?SQT7Q90Apv0HK>p^=y_8iHxr<^qNJ6DU)Uix0 z&ft3H9iGm7KzBx!!WS3HD**E#muC%h_fq--C~%)@!Vn-u;}UP@9o*!De1y;QMZUz> zOCf}K6!0D+3Wxbu|gs9*h4zxRP(?L89A9SFOz(*XYH}F{pashnKf&2hZ&(lDV zHQ;1IKqi4NIgnl8YYyTSbNa-Be=if@3_OC5cm+rC0lvl`(phOmZkF#VaWyV#OIf)f zcahC~a2P<1L46z&VW`qTj%by^P&#}Zo8m3O=2(=38Vr@=6G*WHtHtlCiow-Y4615z z`wI(&*s1@AE)`imk4&uR^TloHTZk3EiX5z9pFqBY61y$70 gg>Lkq7k%hQ9Svj{0A;w}9@0i^YryQuBm+Z#0cgY4-~a#s delta 560 zcmYk2KS*0q6vn@M^E8RcLqkiDASI#*g$^C0#8M~@T{>ATy0tzvnigLTY9Ix{$;rj? z#zIY?v_Y^6;y>~R*KQ6@f?z2e#K}QHT+;963idvJ_nvd^Io~<&hP`Lqt368_fLa7F zGU0p0-MwrT&@u9Qdgk-g`T62kz$pUpp+er9?93lTnR`Z_6&Q&AjjYfPNpHblDEBK` zP{-W*XJ#k7wvWkM*1x6S<$L9#%3_E2o%FMxJU{<)Epzn-a1YtgFV4;{d>BmnK*v)i zYu^wJ{zG&y;)s@E85K|Fq$FFiFZJM^PU}V^sHz8#71PrHeHmm} znn03LaSjkgD_V@qcBoToslrRMl7Mg~1Ct?~)6k3$(Hx{6F4Jexv{Z diff --git a/media/css/sf_font3/iconfont.woff b/media/css/sf_font3/iconfont.woff index fccf6ecd8e2654028ad0ceb4b90fb9f03b4b5f0c..71040ec7676f0d8a4fb9f2831ce84aef30570c88 100644 GIT binary patch delta 2508 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%0DaPhH{d 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 a?lf@(yRVG#4<ubC0t5hs-Vj0002D)KJm@ 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,