diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 13953cf7c1..ae3bf8672c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2896,9 +2896,9 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "deep-is": { @@ -4186,9 +4186,9 @@ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" }, "follow-redirects": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz", - "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==", + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", + "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", "requires": { "debug": "=3.1.0" }, @@ -10168,9 +10168,9 @@ } }, "seafile-js": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.21.tgz", - "integrity": "sha512-zUY1X8YqoLHg2G+fmFLbNOK5w96EW9glmEc/pkNFlXi2SzHd5SasPt5J2scpnD+jyLmYWLfwqJbjqp+xLEO+Yg==", + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.25.tgz", + "integrity": "sha512-XVJ6qvFeSv6tfihBvNscBS+rmnb+TkhyXvEYKBa13PipZbJdxbzVeWRhCXiR82fQwWLzYdAAv2rclSeURyCe5Q==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2" @@ -11773,12 +11773,12 @@ } }, "webpack-bundle-tracker": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.3.0.tgz", - "integrity": "sha512-I0Gwkug8QX8xZS14SvmfWin1AmZDoZp/0AGvlgKqNxyw20DgkFkq1jTQ/Ml73YgjFTmQ5bATyQM7TjtYMP1nFA==", + "version": "0.4.2-beta", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.4.2-beta.tgz", + "integrity": "sha512-CCyJbCQnRtjR1sk97u/H5DtJibrIcJ79MnntMyjOpc9HCmfIQYgt7ze7i/Z+DStBZ4NC4HxqGDsB///2Na1DTA==", "dev": true, "requires": { - "deep-extend": "^0.4.1", + "deep-extend": "^0.6.0", "mkdirp": "^0.5.1", "strip-ansi": "^2.0.1" }, diff --git a/frontend/package.json b/frontend/package.json index 8a61158b00..a669e49fe4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,7 @@ "react-dom": "^16.5.2", "react-moment": "^0.7.9", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.21", + "seafile-js": "^0.2.25", "seafile-ui": "^0.1.10", "sw-precache-webpack-plugin": "0.11.4", "url-loader": "0.6.2", @@ -106,7 +106,7 @@ "react-dev-utils": "^5.0.0", "react-i18next": "^7.6.1", "webpack": "3.8.1", - "webpack-bundle-tracker": "^0.3.0", + "webpack-bundle-tracker": "^0.4.2-beta", "webpack-dev-server": "2.9.4", "webpack-manifest-plugin": "1.3.2" } diff --git a/frontend/src/components/dialog/zip-download-dialog.js b/frontend/src/components/dialog/zip-download-dialog.js index 21cdd6ccce..a64d1d1878 100644 --- a/frontend/src/components/dialog/zip-download-dialog.js +++ b/frontend/src/components/dialog/zip-download-dialog.js @@ -1,6 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +const propTypes = { + onCancelDownload: PropTypes.func.isRequired, + progress: PropTypes.string.isRequired, +}; + class ZipDownloadDialog extends React.Component { toggle = () => { @@ -19,4 +25,6 @@ class ZipDownloadDialog extends React.Component { } } +ZipDownloadDialog.propTypes = propTypes; + export default ZipDownloadDialog; diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js new file mode 100644 index 0000000000..7bc7b75f5b --- /dev/null +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -0,0 +1,125 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { serviceUrl, gettext } from '../../utils/constants'; +import OperationGroup from '../dirent-operation/operation-group'; + +const propTypes = { + isItemFreezed: PropTypes.bool.isRequired, + dirent: PropTypes.object.isRequired, + onItemClick: PropTypes.func.isRequired, + onItemMenuShow: PropTypes.func.isRequired, + onItemMenuHide: PropTypes.func.isRequired, + onItemDelete: PropTypes.func.isRequired, + onItemStarred: PropTypes.func.isRequired, + onItemDownload: PropTypes.func.isRequired, +}; + +class DirentListItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + isOperationShow: false, + highlight: false + }; + } + + //UI Interactive + onMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: true, + isOperationShow: true, + }); + } + } + + onMouseOver = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: true, + isOperationShow: true, + }); + } + } + + onMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: false, + isOperationShow: false, + }); + } + } + + onItemMenuShow = () => { + this.props.onItemMenuShow(); + } + + onItemMenuHide = () => { + this.setState({ + isOperationShow: false, + highlight: '' + }); + this.props.onItemMenuHide(); + } + + //buiness handler + onItemSelected = () => { + //todos; + } + + onItemStarred = () => { + this.props.onItemStarred(this.props.dirent); + } + + onItemClick = () => { + this.props.onItemClick(this.props.dirent); + } + + + onItemDownload = () => { + this.props.onItemDownload(this.props.dirent); + } + + onItemDelete = () => { + this.props.onItemDelete(this.props.dirent); + } + + render() { + let { dirent } = this.props; + return ( + + + + + + {dirent.starred !== undefined && !dirent.starred && } + {dirent.starred !== undefined && dirent.starred && } + + + {gettext('file + + {dirent.name} + + { + this.state.isOperationShow && + + } + + {dirent.size && dirent.size} + + + ); + } +} + +DirentListItem.propTypes = propTypes; + +export default DirentListItem; diff --git a/frontend/src/components/dirent-list-view/dirent-list-view.js b/frontend/src/components/dirent-list-view/dirent-list-view.js new file mode 100644 index 0000000000..6520db99ca --- /dev/null +++ b/frontend/src/components/dirent-list-view/dirent-list-view.js @@ -0,0 +1,158 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { gettext, repoID } from '../../utils/constants'; +import URLDecorator from '../../utils/url-decorator'; +import editorUtilities from '../../utils/editor-utilties'; +import { seafileAPI } from '../../utils/seafile-api'; +import DirentListItem from './dirent-list-item'; +import ZipDownloadDialog from '../dialog/zip-download-dialog'; + +const propTypes = { + filePath: PropTypes.string.isRequired, + direntList: PropTypes.array.isRequired, + onItemDelete: PropTypes.func.isRequired, + onItemClick: PropTypes.func.isRequired, + updateViewList: PropTypes.func.isRequired, +}; + +class DirentListView extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemFreezed: false, + isProgressDialogShow: false, + progress: '0%', + }; + } + + onItemMenuShow = () => { + this.setState({isItemFreezed: true}); + } + + onItemMenuHide = () => { + this.setState({isItemFreezed: false}); + } + + onItemClick = (dirent) => { + let direntPath = this.getDirentPath(dirent); + this.props.onItemClick(direntPath); + } + + onItemDelete = (dirent) => { + let direntPath = this.getDirentPath(dirent); + this.props.onItemDelete(direntPath); + } + + onItemStarred = (dirent) => { + let filePath = this.getDirentPath(dirent); + if (dirent.starred) { + seafileAPI.unStarFile(repoID, filePath).then(() => { + this.props.updateViewList(this.props.filePath); + }); + } else { + seafileAPI.starFile(repoID, filePath).then(() => { + this.props.updateViewList(this.props.filePath); + }); + } + } + + onItemDownload = (dirent) => { + if (dirent.type === 'dir') { + this.setState({isProgressDialogShow: true, progress: '0%'}); + editorUtilities.zipDownload(this.props.filePath, dirent.name).then(res => { + this.zip_token = res.data['zip_token']; + this.addDownloadAnimation(); + this.interval = setInterval(this.addDownloadAnimation, 1000); + }); + } else { + let path = this.getDirentPath(dirent); + let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: path}); + location.href = url; + } + } + + addDownloadAnimation = () => { + let _this = this; + let token = this.zip_token; + editorUtilities.queryZipProgress(token).then(res => { + let data = res.data; + let progress = data.total === 0 ? '100%' : (data.zipped / data.total * 100).toFixed(0) + '%'; + this.setState({progress: progress}); + + if (data['total'] === data['zipped']) { + this.setState({ + progress: '100%' + }); + clearInterval(this.interval); + location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token}); + setTimeout(function() { + _this.setState({isProgressDialogShow: false}); + }, 500); + } + + }); + } + + onCancelDownload = () => { + let zip_token = this.zip_token; + editorUtilities.cancelZipTask(zip_token).then(res => { + this.setState({ + isProgressDialogShow: false, + }); + }); + } + + getDirentPath = (dirent) => { + let path = this.props.filePath; + return path === '/' ? path + dirent.name : path + '/' + dirent.name; + } + + render() { + const { direntList } = this.props; + return ( +
+ + + + + + + + + + + + + + { + direntList.length !== 0 && direntList.map((dirent, index) => { + return ( + + ); + }) + } + +
{gettext('Name')}{gettext('Size')}{gettext('Last Update')}
+ { + this.state.isProgressDialogShow && + + } +
+ ); + } +} + +DirentListView.propTypes = propTypes; + +export default DirentListView; diff --git a/frontend/src/components/dirent-operation/operation-group.js b/frontend/src/components/dirent-operation/operation-group.js index 8335fbe689..eee0d41477 100644 --- a/frontend/src/components/dirent-operation/operation-group.js +++ b/frontend/src/components/dirent-operation/operation-group.js @@ -1,7 +1,16 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; import OperationMenu from './operation-menu'; +const propTypes = { + dirent: PropTypes.object.isRequired, + onItemMenuShow: PropTypes.func.isRequired, + onItemMenuHide: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onDownload: PropTypes.func.isRequired, +}; + class OperationGroup extends React.Component { constructor(props) { @@ -9,7 +18,7 @@ class OperationGroup extends React.Component { this.state = { isItemMenuShow: false, menuPosition: {top: 0, left: 0 }, - } + }; } componentDidMount() { @@ -34,24 +43,28 @@ class OperationGroup extends React.Component { this.props.onDelete(); } - onItemMenuShow = (e) => { + onItemMenuToggle = (e) => { + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + if (!this.state.isItemMenuShow) { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - - let left = e.clientX - 8*16; - let top = e.clientY + 15; - let position = Object.assign({},this.state.menuPosition, {left: left, top: top}); - this.setState({ - menuPosition: position, - isItemMenuShow: true, - }); - this.props.onItemMenuShow(); + this.onItemMenuShow(e); } else { this.onItemMenuHide(); } } + onItemMenuShow = (e) => { + let left = e.clientX - 8*16; + let top = e.clientY + 15; + let position = Object.assign({},this.state.menuPosition, {left: left, top: top}); + this.setState({ + menuPosition: position, + isItemMenuShow: true, + }); + this.props.onItemMenuShow(); + } + onItemMenuHide = () => { this.setState({ isItemMenuShow: false, @@ -81,13 +94,13 @@ class OperationGroup extends React.Component {
  • - +
  • { this.state.isItemMenuShow && { + let repo = new Repo(res.data); + seafileAPI.getAccountInfo().then(res => { + let user_email = res.data.email; + let is_repo_owner = repo.owner_email === user_email; + this.setState({ + repo: repo, + is_repo_owner: is_repo_owner + }); + }) + }); + } + getItemType() { - return this.props.currentItem.type; + let type = this.props.dirent.is_dir ? 'dir' : 'file'; + return type; } renderDirentDirMenu() { let position = this.props.menuPosition; let style = {position: 'fixed', left: position.left, top: position.top, display: 'block'}; - if (this.props.currentItem.permission === 'rw') { + if (this.props.dirent.permission === 'rw') { return (