diff --git a/frontend/package-lock.json b/frontend/package-lock.json index baf5a05794..2a9720253f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4787,7 +4787,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4811,13 +4812,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4834,19 +4837,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4977,7 +4983,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4991,6 +4998,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5007,6 +5015,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5015,13 +5024,15 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5042,6 +5053,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5130,7 +5142,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5144,6 +5157,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5239,7 +5253,8 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5281,6 +5296,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5302,6 +5318,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5350,13 +5367,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true + "dev": true, + "optional": true } } }, @@ -11377,9 +11396,9 @@ } }, "seafile-js": { - "version": "0.2.140", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.140.tgz", - "integrity": "sha512-7WMvQMShWBorBGxygDFCIUt9XPBxHnbobnObSw3eOHflSWfdXrCu0G4ZEj32cDbeece/mEEaD5p/YNn7O7zgKw==", + "version": "0.2.143", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.143.tgz", + "integrity": "sha512-HydirrVXSyoOjzQHAYo+OPk8okkI2qHbwWa6fpoZY3S7xjSXGcG9kSCCFTbZSe4y9aTSIT6WTLlDXKeDpbyvUg==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2", diff --git a/frontend/package.json b/frontend/package.json index 5bb4f9947e..862487e000 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,7 +36,7 @@ "react-responsive": "^6.1.2", "react-select": "^2.4.1", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.140", + "seafile-js": "^0.2.143", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", diff --git a/frontend/src/components/dialog/copy-move-dirent-progress-dialog.js b/frontend/src/components/dialog/copy-move-dirent-progress-dialog.js new file mode 100644 index 0000000000..cbd31584aa --- /dev/null +++ b/frontend/src/components/dialog/copy-move-dirent-progress-dialog.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import { gettext } from '../../utils/constants'; + +const propTypes = { + type: PropTypes.oneOf(['move', 'copy']).isRequired, + asyncOperationProgress: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + toggleDialog: PropTypes.func.isRequired, +}; + +class CopyMoveDirentProgressDialog extends React.Component { + + render() { + + let { type , asyncOperationProgress } = this.props; + let title = type === 'move' ? gettext('Move Progress') : gettext('Copy Progress'); + let progressStyle = { + width: asyncOperationProgress + '%', + lineHeight: '40px', + textAlign: 'left', + }; + return ( + + {title} + +
+
+ {asyncOperationProgress + '%'} +
+
+
+
+ ); + } +} + +CopyMoveDirentProgressDialog.propTypes = propTypes; + +export default CopyMoveDirentProgressDialog; diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 161287c234..faf9d3162b 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import cookie from 'react-cookies'; import moment from 'moment'; @@ -19,6 +19,7 @@ import LibContentToolbar from './lib-content-toolbar'; import LibContentContainer from './lib-content-container'; import FileUploader from '../../components/file-uploader/file-uploader'; import SessionExpiredTip from '../../components/session-expired-tip'; +import CopyMoveDirentProgressDialog from '../../components/dialog/copy-move-dirent-progress-dialog'; const propTypes = { pathPrefix: PropTypes.array.isRequired, @@ -75,6 +76,9 @@ class LibContentView extends React.Component { updateDetail: false, itemsShowLength: 100, isSessionExpired: false, + isCopyMoveProgressDialogShow: false, + asyncCopyMoveTaskId: '', + asyncOperationProgress: 0, }; this.oldonpopstate = window.onpopstate; @@ -574,43 +578,100 @@ class LibContentView extends React.Component { }); } + async getAsyncCopyMoveProgress() { + let { asyncOperationType, asyncCopyMoveTaskId } = this.state; + try { + let res = await seafileAPI.queryAsyncOperationProgress(asyncCopyMoveTaskId); + let data = res.data; + if (data.failed) { + let message = gettext('Files moved to another repository failed.') + if (asyncOperationType === 'copy') { + message = gettext('Files copyed to another repository failed.') + } + toaster.danger(message); + this.setState({ + asyncOperationProgress: 0, + isCopyMoveProgressDialogShow: false, + }); + return; + } + + if (data.successful) { + this.setState({isCopyMoveProgressDialogShow: false}); + let message = gettext('Files moved to another repository successfully.') + if (asyncOperationType === 'copy') { + message = gettext('Files copyed to another repository successfully.') + } + toaster.success(message); + return; + } + + let asyncOperationProgress = parseInt((data.done/data.total * 100).toFixed(2)); + this.setState({asyncOperationProgress: asyncOperationProgress}); + this.getAsyncCopyMoveProgress(); + } catch (error) { + this.setState({ + asyncOperationProgress: 0, + isCopyMoveProgressDialogShow: false, + }); + } + } + + cancelCopyMoveDirent = () => { + let taskId = this.state.asyncCopyMoveTaskId; + seafileAPI.cancelCopyMoveOperation(taskId); + } + + onMoveProgressDialogToggle = () => { + let { asyncOperationProgress } = this.state; + if (asyncOperationProgress && asyncOperationProgress !== 100) { + this.cancelCopyMoveDirent(); + } + + this.setState({ + asyncOperationProgress: 0, + isCopyMoveProgressDialogShow: false, + }) + } + // toolbar operations onMoveItems = (destRepo, destDirentPath) => { let repoID = this.props.repoID; let direntPaths = this.getSelectedDirentPaths(); let dirNames = this.getSelectedDirentNames(); + if (repoID !== destRepo.repo_id) { + this.setState({ + asyncOperationProgress: 0, + asyncOperationType: 'move', + isCopyMoveProgressDialogShow: true + }); + } + seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => { if (repoID !== destRepo.repo_id) { - let taskId = res.data.task_id; - seafileAPI.queryAsyncOperationProgress(taskId).then(res => { - if (res.data.failed) { - let errMessage = Utils.getMoveFailedMessage(dirNames); - toaster.danger(errMessage); - return; - } - direntPaths.forEach((direntPath, index) => { - if (this.state.currentMode === 'column') { - this.deleteTreeNode(direntPath); - } - this.moveDirent(direntPath); - }); - let message = Utils.getMoveSuccessMessage(dirNames); - toaster.success(message); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } else { - direntPaths.forEach((direntPath, index) => { - if (this.state.currentMode === 'column') { - this.deleteTreeNode(direntPath); - } - this.moveDirent(direntPath); + this.setState({ + asyncCopyMoveTaskId: res.data.task_id, + }, () => { + this.getAsyncCopyMoveProgress(); }); + } + + direntPaths.forEach((direntPath, index) => { if (this.state.currentMode === 'column') { - this.updateMoveCopyTreeNode(destDirentPath); + this.deleteTreeNode(direntPath); } + this.moveDirent(direntPath); + }); + + // 1. move to current repo + // 2. tow columns mode need update left tree + if (repoID === destRepo.repo_id && this.state.currentMode === 'column') { + this.updateMoveCopyTreeNode(destDirentPath); + } + + // show tip message if move to current repo + if (repoID === destRepo.repo_id) { let message = Utils.getMoveSuccessMessage(dirNames); toaster.success(message); } @@ -625,38 +686,32 @@ class LibContentView extends React.Component { onCopyItems = (destRepo, destDirentPath) => { let repoID = this.props.repoID; - let direntPaths = this.getSelectedDirentPaths(); let dirNames = this.getSelectedDirentNames(); + if (repoID !== destRepo.repo_id) { + this.setState({ + asyncOperationProgress: 0, + asyncOperationType: 'copy', + isCopyMoveProgressDialogShow: true + }); + } + seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => { if (repoID !== destRepo.repo_id) { - let taskId = res.data.task_id; - seafileAPI.queryAsyncOperationProgress(taskId).then(res => { - if (res.data.failed) { - let errMessage = Utils.getCopyFailedMessage(dirNames); - toaster.danger(errMessage); - return; - } - if (destDirentPath === this.state.path) { - this.loadDirentList(this.state.path); - } - let message = Utils.getCopySuccessfulMessage(dirNames); - toaster.success(message); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); + this.setState({ + asyncCopyMoveTaskId: res.data.task_id, + }, () => { + this.getAsyncCopyMoveProgress(); }); - } else { - if (this.state.currentMode === 'column') { - this.updateMoveCopyTreeNode(destDirentPath); - } - if (destDirentPath === this.state.path) { - this.loadDirentList(this.state.path); - } - let message = Utils.getCopySuccessfulMessage(dirNames); - toaster.success(message); } - + + if (repoID === destRepo.repo_id && this.state.currentMode === 'column') { + this.updateMoveCopyTreeNode(destDirentPath); + } + + if (destDirentPath === this.state.path) { + this.loadDirentList(this.state.path); + } }).catch((error) => { let errMessage = Utils.getErrorMsg(error); if (errMessage === gettext('Error')) { @@ -951,34 +1006,38 @@ class LibContentView extends React.Component { nodeParentPath = this.state.path; } let direntPath = Utils.joinPath(nodeParentPath, dirName); + + if (repoID !== destRepo.repo_id) { + this.setState({ + asyncOperationProgress: 0, + asyncOperationType: 'move', + isCopyMoveProgressDialogShow: true, + }); + } + seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, nodeParentPath, dirName).then(res => { if (repoID !== destRepo.repo_id) { - let taskId = res.data.task_id; - seafileAPI.queryAsyncOperationProgress(taskId).then(res => { - if (res.data.failed) { - let errMessage = gettext('Failed to move {name}.'); - errMessage = errMessage.replace('{name}', dirName); - toaster.danger(errMessage); - return; - } - if (this.state.currentMode === 'column') { - this.deleteTreeNode(direntPath); - } - this.moveDirent(direntPath); - let message = gettext('Successfully moved {name}.'); - message = message.replace('{name}', dirName); - toaster.success(message); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); + this.setState({ + asyncCopyMoveTaskId: res.data.task_id, + }, () => { + this.getAsyncCopyMoveProgress(); }); - } else { - if (this.state.currentMode === 'column') { - this.deleteTreeNode(direntPath); - this.updateMoveCopyTreeNode(moveToDirentPath); - } - this.moveDirent(direntPath, moveToDirentPath); - + } + + if (this.state.currentMode === 'column') { + this.deleteTreeNode(direntPath); + } + + // 1. move to current repo + // 2. tow columns mode need update left tree + if (repoID === destRepo.repo_id && this.state.currentMode === 'column') { + this.updateMoveCopyTreeNode(moveToDirentPath); + } + + this.moveDirent(direntPath, moveToDirentPath); + + // show tip message if move to current repo + if (repoID === destRepo.repo_id) { let message = gettext('Successfully moved {name}.'); message = message.replace('{name}', dirName); toaster.success(message); @@ -1000,36 +1059,34 @@ class LibContentView extends React.Component { if (!nodeParentPath) { nodeParentPath = this.state.path; } - let direntPath = Utils.joinPath(nodeParentPath, dirName); + + if (repoID !== destRepo.repo_id) { + this.setState({ + asyncOperationProgress: 0, + asyncOperationType: 'copy', + isCopyMoveProgressDialogShow: true + }); + } seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, nodeParentPath, dirName).then(res => { if (repoID !== destRepo.repo_id) { - let taskId = res.data.task_id; - seafileAPI.queryAsyncOperationProgress(taskId).then(res => { - if (res.data.failed) { - let errMessage = gettext('Failed to copy %(name)s'); - errMessage = errMessage.replace('%(name)s', dirName); - toaster.danger(errMessage); - return; - } - if (copyToDirentPath === nodeParentPath) { - this.loadDirentList(this.state.path); - } - let message = gettext('Successfully copied %(name)s.'); - message = message.replace('%(name)s', dirName); - toaster.success(message); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); + this.setState({ + asyncCopyMoveTaskId: res.data.task_id, + }, () => { + this.getAsyncCopyMoveProgress(); }); - } else { - if (this.state.currentMode === 'column') { - this.updateMoveCopyTreeNode(copyToDirentPath); - } - if (copyToDirentPath === nodeParentPath) { - this.loadDirentList(this.state.path); - } + } + + if (repoID === destRepo.repo_id && this.state.currentMode === 'column') { + this.updateMoveCopyTreeNode(copyToDirentPath); + } + + if (copyToDirentPath === nodeParentPath) { + this.loadDirentList(this.state.path); + } + + if (repoID === destRepo.repo_id) { let message = gettext('Successfully copied %(name)s.'); message = message.replace('%(name)s', dirName); toaster.success(message); @@ -1672,137 +1729,147 @@ class LibContentView extends React.Component { }); return ( -
-
- -
-
- - {this.state.pathExist && !this.state.isViewFile && ( - this.uploader = uploader} - dragAndDrop={true} - path={this.state.path} + + +
+
+ - )} +
+
+ + {this.state.pathExist && !this.state.isViewFile && ( + this.uploader = uploader} + dragAndDrop={true} + path={this.state.path} + repoID={this.props.repoID} + direntList={this.state.direntList} + onFileUploadSuccess={this.onFileUploadSuccess} + /> + )} +
-
+ {this.state.isCopyMoveProgressDialogShow && ( + + )} + ); } }