From 250b85e8c9c42309335d9394809a91a438147165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Wed, 13 Feb 2019 14:21:39 +0800 Subject: [PATCH] optimized my-libs (#2935) * optimized my-libs * Remove redundant directory hierarchies * repair reabse bug --- frontend/package-lock.json | 41 ++- frontend/package.json | 3 +- .../components/dialog/delete-repo-dialog.js | 22 +- .../src/components/dialog/transfer-dialog.js | 1 - frontend/src/pages/my-libs/content.js | 209 ----------- frontend/src/pages/my-libs/item.js | 341 ----------------- frontend/src/pages/my-libs/my-libs.js | 83 +++-- .../src/pages/my-libs/mylib-repo-list-item.js | 342 ++++++++++++++++++ .../src/pages/my-libs/mylib-repo-list-view.js | 133 +++++++ frontend/src/pages/my-libs/mylib-repo-menu.js | 164 +++++++++ frontend/src/pages/my-libs/table-body.js | 58 --- 11 files changed, 728 insertions(+), 669 deletions(-) delete mode 100644 frontend/src/pages/my-libs/content.js delete mode 100644 frontend/src/pages/my-libs/item.js create mode 100644 frontend/src/pages/my-libs/mylib-repo-list-item.js create mode 100644 frontend/src/pages/my-libs/mylib-repo-list-view.js create mode 100644 frontend/src/pages/my-libs/mylib-repo-menu.js delete mode 100644 frontend/src/pages/my-libs/table-body.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 07180b078a..d74dc19c02 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2945,6 +2945,11 @@ } } }, + "css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=" + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -5286,7 +5291,7 @@ }, "git-up": { "version": "1.2.1", - "resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", "integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=", "requires": { "is-ssh": "^1.0.0", @@ -5295,7 +5300,7 @@ }, "git-url-parse": { "version": "5.0.1", - "resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", "integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=", "requires": { "git-up": "^1.0.0" @@ -7487,6 +7492,14 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz", "integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==" }, + "matchmediaquery": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.0.tgz", + "integrity": "sha512-u0dlv+VENJ+3YepvwSPBieuvnA6DWfaYa/ctwysAR13y4XLJNyt7bEVKzNj/Nvjo+50d88Pj+xL9xaSo6JmX/w==", + "requires": { + "css-mediaquery": "^0.1.2" + } + }, "math-expression-evaluator": { "version": "1.2.17", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", @@ -7858,7 +7871,7 @@ }, "node-status-codes": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" }, "noop6": { @@ -8165,7 +8178,7 @@ }, "package.json": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", "integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=", "requires": { "git-package-json": "^1.4.0", @@ -8175,7 +8188,7 @@ "dependencies": { "got": { "version": "5.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "requires": { "create-error-class": "^3.0.1", @@ -8197,7 +8210,7 @@ }, "package-json": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", "requires": { "got": "^5.0.0", @@ -10034,6 +10047,16 @@ "react-deep-force-update": "^1.0.0" } }, + "react-responsive": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-6.1.1.tgz", + "integrity": "sha512-Po6pOEz70Agp+2lUmTxAnhfdkk0zp0IFgo/6bGcxv/S4Pa1sz0YG06WzkrIcASbyKSQ8x6AkcggeozXW3zj3kA==", + "requires": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.3.0", + "prop-types": "^15.6.1" + } + }, "react-s-alert": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/react-s-alert/-/react-s-alert-1.4.1.tgz", @@ -10846,9 +10869,9 @@ } }, "seafile-js": { - "version": "0.2.61", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.61.tgz", - "integrity": "sha512-fVQ8GlU95TgwnjOtRqvyiBtOah/lV/CnnT0yZzJnFYgCKKLpoGm+2aazUZaSjHQgP+r9u8ei14CMXnzQUiPTWg==", + "version": "0.2.62", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.62.tgz", + "integrity": "sha512-SA4LFmy65CmIsMr1zzgRxXPeGo+V+IOYs9pzwlgrTaNXHJBfQ/iS1rUhXmrj3EcBfLOdpdmUQlbutoz95jiwUA==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2", diff --git a/frontend/package.json b/frontend/package.json index ea7c49c0a4..1cc42143d5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,9 +31,10 @@ "react-dom": "^16.5.2", "react-image-lightbox": "^5.1.0", "react-moment": "^0.7.9", + "react-responsive": "^6.1.1", "react-select": "^2.1.1", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.61", + "seafile-js": "^0.2.62", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", diff --git a/frontend/src/components/dialog/delete-repo-dialog.js b/frontend/src/components/dialog/delete-repo-dialog.js index 24dc14b1ba..942b0b2bb6 100644 --- a/frontend/src/components/dialog/delete-repo-dialog.js +++ b/frontend/src/components/dialog/delete-repo-dialog.js @@ -5,7 +5,9 @@ import { gettext } from '../../utils/constants'; import { Utils } from '../../utils/utils'; const propTypes = { + repo: PropTypes.object.isRequired, toggle: PropTypes.func.isRequired, + onDeleteRepo: PropTypes.func.isRequired, }; class DeleteRepoDialog extends Component { @@ -14,35 +16,29 @@ class DeleteRepoDialog extends Component { this.props.toggle(); } - clickYes = () => { - this.toggle(); - - const data = this.props.data; - if (data) { - data.yesCallback.bind(data._this)(); - } + onDeleteRepo = () => { + this.props.onDeleteRepo(this.props.repo); } render() { - const data = this.props.data; - const repoName = data ? '' + Utils.HTMLescape(data.repoName) + '' : null; + const repo = this.props.repo; + const repoName = '' + Utils.HTMLescape(repo.repo_name) + ''; let message = gettext('Are you sure you want to delete %s ?'); message = message.replace('%s', repoName); - const popup = ( + + return ( {gettext('Delete Library')}

- +
); - - return popup; } } diff --git a/frontend/src/components/dialog/transfer-dialog.js b/frontend/src/components/dialog/transfer-dialog.js index 8c0b7c1ba0..705f81da1f 100644 --- a/frontend/src/components/dialog/transfer-dialog.js +++ b/frontend/src/components/dialog/transfer-dialog.js @@ -56,7 +56,6 @@ class TransferDialog extends React.Component { let message = gettext('Successfully transferred the library.'); toaster.success(message); this.props.submit(repoID); - this.props.toggleDialog(); }).catch(res => { let message = gettext('Failed. Please check the network.'); this.props.toggleDialog(); diff --git a/frontend/src/pages/my-libs/content.js b/frontend/src/pages/my-libs/content.js deleted file mode 100644 index 61974752d7..0000000000 --- a/frontend/src/pages/my-libs/content.js +++ /dev/null @@ -1,209 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { gettext, storages} from '../../utils/constants'; -import Loading from '../../components/loading'; -import TableBody from './table-body'; -import ModalPortal from '../../components/modal-portal'; -import LibHistorySetting from '../../components/dialog/lib-history-setting-dialog'; -import TransferDialog from '../../components/dialog/transfer-dialog'; -import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encrypted-repo-password-dialog'; -import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog'; - -const propTypes = { - loading: PropTypes.bool.isRequired, - errorMsg: PropTypes.string.isRequired, - sortBy: PropTypes.string.isRequired, - sortOrder: PropTypes.string.isRequired, - sortItems: PropTypes.func.isRequired, - items: PropTypes.array.isRequired, - onRenameRepo: PropTypes.func.isRequired, - onDeleteRepo: PropTypes.func.isRequired, - onTransferRepo: PropTypes.func.isRequired, - onRepoDetails: PropTypes.func.isRequired, - onItemClick: PropTypes.func.isRequired -}; - -class Content extends Component { - - constructor(props) { - super(props); - this.state = { - deleteItemPopupOpen: false, - showTransfer: false, - showResetEncryptedRepoPassword: false, - itemName: '', - showHistorySetting: false, - showDetails: false, - libID: '', - libSize: '', - libUpdateTime: '' - }; - } - - toggleDeleteItemPopup = () => { - this.setState({ - deleteItemPopupOpen: !this.state.deleteItemPopupOpen - }); - } - - showDeleteItemPopup = (data) => { - this.toggleDeleteItemPopup(); - this.setState({ - deleteItemPopupData: data - }); - } - - onTransfer = (itemName, itemID) => { - this.setState({ - showTransfer: !this.state.showTransfer, - itemName: itemName, - libID: itemID - }); - } - - onHistorySetting = (itemName, itemID) => { - this.setState({ - showHistorySetting: !this.state.showHistorySetting, - itemName: itemName, - libID: itemID - }); - } - - onResetEncryptedRepoPassword = (itemName, itemID) => { - this.setState({ - showResetEncryptedRepoPassword: !this.state.showResetEncryptedRepoPassword, - itemName: itemName, - libID: itemID - }); - } - - sortByName = (e) => { - e.preventDefault(); - const sortBy = 'name'; - const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; - this.props.sortItems(sortBy, sortOrder); - } - - sortByTime = (e) => { - e.preventDefault(); - const sortBy = 'time'; - const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; - this.props.sortItems(sortBy, sortOrder); - } - - render() { - const { loading, errorMsg, items, sortBy, sortOrder } = this.props; - - if (loading) { - return ; - } else if (errorMsg) { - return

{errorMsg}

; - } else { - const emptyTip = ( -
-

{gettext('You have not created any libraries')}

-

{gettext('You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately.')}

-
- ); - - // sort - const sortByName = sortBy == 'name'; - const sortByTime = sortBy == 'time'; - const sortIcon = sortOrder == 'asc' ? : ; - - // TODO: test 'storage backend' - const showStorageBackend = storages.length > 0; // only for desktop - const desktopThead = ( - - - {gettext('Library Type')} - {gettext('Name')} {sortByName && sortIcon} - {gettext('Actions')} - - {gettext('Size')} - {showStorageBackend ? {gettext('Storage backend')} : null} - {gettext('Last Update')} {sortByTime && sortIcon} - - - ); - - const mobileThead = ( - - - {gettext('Library Type')} - - {gettext('Sort:')} - {gettext('name')} {sortByName && sortIcon} - {gettext('last update')} {sortByTime && sortIcon} - - {gettext('Actions')} - - - ); - - const table = ( - - {window.innerWidth >= 768 ? desktopThead : mobileThead} - -
- ); - - const nonEmpty = ( - - {table} - {this.state.deleteItemPopupOpen && ( - - - - )} - {this.state.showTransfer && - - - - } - {this.state.showHistorySetting && - - - - } - {this.state.showResetEncryptedRepoPassword && - - - - } - - ); - - return items.length ? nonEmpty : emptyTip; - } - } -} - -Content.propTypes = propTypes; - -export default Content; diff --git a/frontend/src/pages/my-libs/item.js b/frontend/src/pages/my-libs/item.js deleted file mode 100644 index 6d9b30782f..0000000000 --- a/frontend/src/pages/my-libs/item.js +++ /dev/null @@ -1,341 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { Link } from '@reach/router'; -import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; -import { gettext, siteRoot, storages, folderPermEnabled, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoSnapshotLabel } from '../../utils/constants'; -import { Utils } from '../../utils/utils'; -import { seafileAPI } from '../../utils/seafile-api'; -import Rename from '../../components/rename'; -import ModalPotal from '../../components/modal-portal'; -import ShareDialog from '../../components/dialog/share-dialog'; -import ChangeRepoPasswordDialog from '../../components/dialog/change-repo-password-dialog'; - -const propTypes = { - data: PropTypes.object.isRequired, - isItemFreezed: PropTypes.bool.isRequired, - onItemFreezedToggle: PropTypes.func.isRequired, - onRenameRepo: PropTypes.func.isRequired, - onDeleteRepo: PropTypes.func.isRequired, - onTransfer: PropTypes.func.isRequired, - showDeleteItemPopup: PropTypes.func.isRequired, - onHistorySetting: PropTypes.func.isRequired, - onResetEncryptedRepoPassword: PropTypes.func.isRequired, - onRepoDetails: PropTypes.func.isRequired, - onItemClick: PropTypes.func.isRequired -}; - -class Item extends Component { - - constructor(props) { - super(props); - this.state = { - showOpIcon: false, - operationMenuOpen: false, - showChangeLibName: false, - isShowSharedDialog: false, - highlight: false, - isChangeRepoPasswordDialogOpen: false - }; - } - - handleMouseOver = () => { - if (!this.props.isItemFreezed) { - this.setState({ - showOpIcon: true, - highlight: true, - }); - } - } - - handleMouseOut = () => { - if (!this.props.isItemFreezed) { - this.setState({ - showOpIcon: false, - highlight: false - }); - } - } - - clickOperationMenuToggle = (e) => { - e.preventDefault(); - this.toggleOperationMenu(e); - } - - toggleOperationMenu = (e) => { - if (e.target.dataset.toggle !== 'item') { - if (this.props.isItemFreezed) { - this.setState({ - highlight: false, - showOpIcon: false, - }); - } - this.setState({ - operationMenuOpen: !this.state.operationMenuOpen - }); - this.props.onItemFreezedToggle(); - } - } - - share = (e) => { - e.preventDefault(); - this.setState({isShowSharedDialog: true}); - } - - toggleShareDialog = () => { - this.setState({isShowSharedDialog: false}); - } - - showDeleteItemPopup = (e) => { - e.preventDefault(); // for `` - const data = this.props.data; - this.props.showDeleteItemPopup({ - repoName: data.repo_name, - yesCallback: this.deleteItem, - _this: this - }); - } - - deleteItem = () => { - const repo = this.props.data; - seafileAPI.deleteRepo(repo.repo_id).then((res) => { - this.props.onDeleteRepo(repo); - // TODO: show feedback msg - }).catch((error) => { - - // TODO: show feedback msg - }); - } - - onRenameToggle = () => { - this.setState({ - showOpIcon: false, - showChangeLibName: true, - operationMenuOpen: false, - }); - } - - onRenameConfirm = (newName) => { - let repo = this.props.data; - let repoID = repo.repo_id; - seafileAPI.renameRepo(repoID, newName).then(() => { - this.props.onRenameRepo(repo, newName); - this.onRenameCancel(); - }); - } - - onRenameCancel = () => { - this.setState({ - showChangeLibName: !this.state.showChangeLibName, - }); - this.props.onItemFreezedToggle(); - } - - transfer = () => { - const itemName = this.props.data.repo_name; - const itemID = this.props.data.repo_id; - this.props.onTransfer(itemName, itemID); - } - - historySetting = () => { - const itemName = this.props.data.repo_name; - const itemID = this.props.data.repo_id; - this.props.onHistorySetting(itemName, itemID); - } - - changePassword = () => { - this.setState({isChangeRepoPasswordDialogOpen: true}); - } - - toggleChangePasswordDialog = () => { - this.setState({isChangeRepoPasswordDialogOpen: !this.state.isChangeRepoPasswordDialogOpen}); - } - - resetEncryptedRepoPassword = () => { - const repoName = this.props.data.repo_name; - const repoID = this.props.data.repo_id; - this.props.onResetEncryptedRepoPassword(repoName, repoID); - } - - folderPerm = () => { - } - - // on '' - onItemClick = (e) => { - // '' is clicked - if (e.target.tagName == 'TD') { - this.props.onItemClick(this.props.data); - } - } - - showDetails = () => { - let data = this.props.data; - this.props.onRepoDetails(data); - } - - label = () => { - } - - render() { - const data = this.props.data; - const showResetPasswordMenuItem = data.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured; - - data.icon_url = Utils.getLibIconUrl(data); - data.icon_title = Utils.getLibIconTitle(data); - data.url = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`; - - let iconVisibility = this.state.showOpIcon ? '' : ' invisible'; - let shareIconClassName = 'op-icon sf2-icon-share' + iconVisibility; - let deleteIconClassName = 'op-icon sf2-icon-delete' + iconVisibility; - let operationMenuToggleIconClassName = 'sf-dropdown-toggle sf2-icon-caret-down'; - if (window.innerWidth >= 768) { - operationMenuToggleIconClassName += iconVisibility; - } - - const commonToggle = ( - - ); - const commonOperationsInMenu = ( - - {gettext('Rename')} - {gettext('Transfer')} - {gettext('History Setting')} - {data.encrypted ? {gettext('Change Password')} : ''} - {showResetPasswordMenuItem ? {gettext('Reset Password')} : ''} - {folderPermEnabled ? {gettext('Folder Permission')} : ''} - {gettext('Details')} - - ); - - const desktopOperations = ( -
- - - - {commonToggle} - - {commonOperationsInMenu} - {enableRepoSnapshotLabel ? {gettext('Label current state')} : ''} - - -
- ); - - const mobileOperations = ( - - {commonToggle} -
-
-
- {gettext('Share')} - {gettext('Delete')} - {commonOperationsInMenu} -
-
-
- ); - - const desktopItem = ( - - - {data.icon_title} - - {this.state.showChangeLibName && ( - - )} - {!this.state.showChangeLibName && data.repo_name && ( - {data.repo_name} - )} - {!this.state.showChangeLibName && !data.repo_name && - (gettext('Broken (please contact your administrator to fix this library)')) - } - - {data.repo_name ? desktopOperations : ''} - {data.size} - {storages.length ? {data.storage_name} : null} - {moment(data.last_modified).fromNow()} - - {this.state.isShowSharedDialog && ( - - - - )} - {this.state.isChangeRepoPasswordDialogOpen && ( - - - - )} - - ); - - const mobileItem = ( - - - {data.icon_title} - - {data.repo_name ? - {data.repo_name} : - gettext('Broken (please contact your administrator to fix this library)')} -
- {data.size} - {moment(data.last_modified).fromNow()} - - {data.repo_name ? mobileOperations : ''} - - {this.state.isShowSharedDialog && ( - - - - )} - {this.state.isChangeRepoPasswordDialogOpen && ( - - - - )} -
- ); - - return window.innerWidth >= 768 ? desktopItem : mobileItem; - } -} - -Item.propTypes = propTypes; - -export default Item; diff --git a/frontend/src/pages/my-libs/my-libs.js b/frontend/src/pages/my-libs/my-libs.js index e55f21c61c..b5cd270e8e 100644 --- a/frontend/src/pages/my-libs/my-libs.js +++ b/frontend/src/pages/my-libs/my-libs.js @@ -3,22 +3,30 @@ import { seafileAPI } from '../../utils/seafile-api'; import { gettext, loginUrl} from '../../utils/constants'; import { Utils } from '../../utils/utils'; import Repo from '../../models/repo'; +import Loading from '../../components/loading'; import CommonToolbar from '../../components/toolbar/common-toolbar'; import RepoViewToolbar from '../../components/toolbar/repo-view-toobar'; import LibDetail from '../../components/dirent-detail/lib-details'; -import Content from './content'; +import MylibRepoListView from './mylib-repo-list-view'; class MyLibraries extends Component { constructor(props) { super(props); this.state = { - isShowDetails: false, - loading: true, errorMsg: '', - items: [], + isLoading: true, + repoList: [], + isShowDetails: false, sortBy: 'name', // 'name' or 'time' sortOrder: 'asc' // 'asc' or 'desc' }; + + this.emptyMessage = ( +
+

{gettext('You have not created any libraries')}

+

{gettext('You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately.')}

+
+ ); } componentDidMount() { @@ -28,26 +36,26 @@ class MyLibraries extends Component { return new Repo(item); }); this.setState({ - loading: false, - items: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder) + isLoading: false, + repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder) }); }).catch((error) => { if (error.response) { if (error.response.status == 403) { this.setState({ - loading: false, + isLoading: false, errorMsg: gettext('Permission denied') }); location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`; } else { this.setState({ - loading: false, + isLoading: false, errorMsg: gettext('Error') }); } } else { this.setState({ - loading: false, + isLoading: false, errorMsg: gettext('Please check the network.') }); } @@ -66,44 +74,44 @@ class MyLibraries extends Component { encrypted: res.data.encrypted, permission: permission, }; - this.state.items.unshift(repo); - this.setState({items: this.state.items}); + this.state.repoList.unshift(repo); + this.setState({repoList: this.state.repoList}); }); } - sortItems = (sortBy, sortOrder) => { + sortRepoList = (sortBy, sortOrder) => { this.setState({ sortBy: sortBy, sortOrder: sortOrder, - items: Utils.sortRepos(this.state.items, sortBy, sortOrder) + repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder) }); } onTransferRepo = (repoID) => { - let items = this.state.items.filter(item => { + let repoList = this.state.repoList.filter(item => { return item.repo_id !== repoID; }); - this.setState({items: items}); + this.setState({repoList: repoList}); } onRenameRepo = (repo, newName) => { - let items = this.state.items.map(item => { + let repoList = this.state.repoList.map(item => { if (item.repo_id === repo.repo_id) { item.repo_name = newName; } return item; }); - this.setState({items: items}); + this.setState({repoList: repoList}); } onDeleteRepo = (repo) => { - let items = this.state.items.filter(item => { + let repoList = this.state.repoList.filter(item => { return item.repo_id !== repo.repo_id; }); - this.setState({items: items}); + this.setState({repoList: repoList}); } - onItemClick = (repo) => { + onRepoClick = (repo) => { if (this.state.isShowDetails) { this.onRepoDetails(repo); } @@ -111,15 +119,13 @@ class MyLibraries extends Component { onRepoDetails = (repo) => { this.setState({ + currentRepo: repo, isShowDetails: true, - currentRepo: repo }); } closeDetails = () => { - this.setState({ - isShowDetails: !this.state.isShowDetails - }); + this.setState({isShowDetails: !this.state.isShowDetails}); } render() { @@ -135,19 +141,22 @@ class MyLibraries extends Component {

{gettext('My Libraries')}

- + {this.state.isLoading && } + {!this.state.isLoading && this.state.errorMsg &&

{this.state.errorMsg}

} + {!this.state.isLoading && this.state.repoList.length === 0 && this.emptyMessage} + {!this.state.isLoading && this.state.repoList.length > 0 && + + }
{this.state.isShowDetails && ( diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js new file mode 100644 index 0000000000..30c19a6114 --- /dev/null +++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js @@ -0,0 +1,342 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import MediaQuery from 'react-responsive'; +import moment from 'moment'; +import { Link } from '@reach/router'; +import { Utils } from '../../utils/utils'; +import { seafileAPI } from '../../utils/seafile-api'; +import { gettext, siteRoot, storages } from '../../utils/constants'; +import ModalPortal from '../../components/modal-portal'; +import ShareDialog from '../../components/dialog/share-dialog'; +import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog'; +import TransferDialog from '../../components/dialog/transfer-dialog'; +import LibHistorySettingDialog from '../../components/dialog/lib-history-setting-dialog'; +import ChangeRepoPasswordDialog from '../../components/dialog/change-repo-password-dialog'; +import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encrypted-repo-password-dialog'; +import Rename from '../../components/rename'; +import MylibRepoMenu from './mylib-repo-menu'; + +const propTypes = { + repo: PropTypes.object.isRequired, + isItemFreezed: PropTypes.bool.isRequired, + onFreezedItem: PropTypes.func.isRequired, + onUnfreezedItem: PropTypes.func.isRequired, + onRenameRepo: PropTypes.func.isRequired, + onDeleteRepo: PropTypes.func.isRequired, + onTransferRepo: PropTypes.func.isRequired, + onRepoDetails: PropTypes.func.isRequired, + onRepoClick: PropTypes.func.isRequired, +}; + +class MylibRepoListItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + isOpIconShow: false, + isRenaming: false, + isShareDialogShow: false, + isDeleteDialogShow: false, + isTransferDialogShow: false, + isHistorySettingDialogShow: false, + isChangePasswordDialogShow: false, + isResetPasswordDialogShow: false, + }; + } + + onMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShow: true, + highlight: true, + }); + } + } + + onMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShow: false, + highlight: false + }); + } + } + + onMenuItemClick = (item) => { + switch(item) { + case 'Share': + this.onShareToggle(); + break; + case 'Delete': + this.onDeleteToggle(); + break; + case 'Rename': + this.onRenameToggle(); + break; + case 'Transfer': + this.onTransferToggle(); + break; + case 'History Setting': + this.onHistorySettingToggle(); + break; + case 'Change Password': + this.onChangePasswordToggle(); + break; + case 'Reset Password': + this.onResetPasswordToggle(); + break; + case 'Folder Permission': + // todo + break; + case 'Details': + this.onRepoDetails(); + break; + case 'Label current state': + this.onLabel(); + break; + default: + break; + } + } + + onRepoClick = () => { + this.props.onRepoClick(this.props.repo); + } + + onShareToggle = () => { + this.setState({isShareDialogShow: !this.state.isShareDialogShow}); + } + + onDeleteToggle = () => { + this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); + } + + onRenameToggle = () => { + this.props.onFreezedItem(); + this.setState({isRenaming: !this.state.isRenaming}); + } + + onTransferToggle = () => { + this.setState({isTransferDialogShow: !this.state.isTransferDialogShow}); + } + + onHistorySettingToggle = () => { + this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow}); + } + + onChangePasswordToggle = () => { + this.setState({isChangePasswordDialogShow: !this.state.isChangePasswordDialogShow}); + } + + onResetPasswordToggle = () => { + this.setState({isResetPasswordDialogShow: !this.state.isResetPasswordDialogShow}); + } + + onRepoDetails = () => { + this.props.onRepoDetails(this.props.repo); + } + + onLabel = () => { + // todo + } + + onUnfreezedItem = () => { + this.setState({ + highlight: false, + isOpIconShow: false, + }); + this.props.onUnfreezedItem(); + } + + onRenameConfirm = (newName) => { + let repo = this.props.repo; + let repoID = repo.repo_id; + seafileAPI.renameRepo(repoID, newName).then(() => { + this.props.onRenameRepo(repo, newName); + this.onRenameCancel(); + }); + } + + onRenameCancel = () => { + this.props.onUnfreezedItem(); + this.setState({isRenaming: !this.state.isRenaming}); + } + + onTransferRepo = (repoID) => { + this.onTransferToggle(); + this.props.onTransferRepo(repoID); + } + + onDeleteRepo = (repo) => { + seafileAPI.deleteRepo(repo.repo_id).then((res) => { + this.props.onDeleteRepo(repo); + + // TODO: show feedback msg + }).catch((error) => { + + // TODO: show feedback msg + }); + } + + renderPCUI = () => { + let repo = this.props.repo; + let iconUrl = Utils.getLibIconUrl(repo); + let iconTitle = Utils.getLibIconTitle(repo); + let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`; + return ( + + {iconTitle} + + {this.state.isRenaming && ( + + )} + {!this.state.isRenaming && repo.repo_name && ( + {repo.repo_name} + )} + {!this.state.isRenaming && !repo.repo_name && + (gettext('Broken (please contact your administrator to fix this library)')) + } + + + {(repo.repo_name && this.state.isOpIconShow) && ( +
+ + + +
+ )} + + {repo.size} + {storages.length > 0 && {repo.storage_name}} + {moment(repo.last_modified).fromNow()} + + ); + } + + renderMobileUI = () => { + let repo = this.props.repo; + let iconUrl = Utils.getLibIconUrl(repo); + let iconTitle = Utils.getLibIconTitle(repo); + let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`; + + return ( + + {iconTitle} + + {this.state.isRenaming && ( + + )} + {!this.state.isRenaming && repo.repo_name && ( + {repo.repo_name} + )} + {!this.state.isRenaming && !repo.repo_name && + (gettext('Broken (please contact your administrator to fix this library)')) + }
+ {repo.size} + {moment(repo.last_modified).fromNow()} + + + {repo.repo_name && ( + + )} + + + ); + } + + render() { + let repo = this.props.repo; + return ( + + + {this.renderPCUI()} + + + {this.renderMobileUI()} + + {this.state.isShareDialogShow && ( + + + + )} + {this.state.isDeleteDialogShow && ( + + + + )} + {this.state.isTransferDialogShow && ( + + + + )} + {this.state.isHistorySettingDialogShow && ( + + + + )} + {this.state.isChangePasswordDialogShow && ( + + + + )} + {this.state.isResetPasswordDialogShow && ( + + + + )} + + ) + } +} + +MylibRepoListItem.propTypes = propTypes; + +export default MylibRepoListItem; diff --git a/frontend/src/pages/my-libs/mylib-repo-list-view.js b/frontend/src/pages/my-libs/mylib-repo-list-view.js new file mode 100644 index 0000000000..7e1e25a96b --- /dev/null +++ b/frontend/src/pages/my-libs/mylib-repo-list-view.js @@ -0,0 +1,133 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import MediaQuery from 'react-responsive'; +import { gettext, storages } from '../../utils/constants'; +import MylibRepoListItem from './mylib-repo-list-item'; + +const propTypes = { + sortBy: PropTypes.string.isRequired, + sortOrder: PropTypes.string.isRequired, + repoList: PropTypes.array.isRequired, + sortRepoList: PropTypes.func.isRequired, + onRenameRepo: PropTypes.func.isRequired, + onDeleteRepo: PropTypes.func.isRequired, + onTransferRepo: PropTypes.func.isRequired, + onRepoDetails: PropTypes.func.isRequired, + onRepoClick: PropTypes.func.isRequired, +}; + +class MylibRepoListView extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemFreezed: false, + }; + } + + onFreezedItem = () => { + this.setState({isItemFreezed: true}); + } + + onUnfreezedItem = () => { + this.setState({isItemFreezed: false}); + } + + sortByName = (e) => { + e.preventDefault(); + const sortBy = 'name'; + const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; + this.props.sortRepoList(sortBy, sortOrder); + } + + sortByTime = (e) => { + e.preventDefault(); + const sortBy = 'time'; + const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; + this.props.sortRepoList(sortBy, sortOrder); + } + + renderRepoListView = () => { + return ( + + {this.props.repoList.map(item => { + return ( + + ); + })} + + ); + } + + renderPCUI = () => { + const showStorageBackend = storages.length > 0; + const sortIcon = this.props.sortOrder === 'asc' ? : ; + return ( + + + + + + + + {showStorageBackend ? : null} + + + + + {this.renderRepoListView()} + +
{gettext('Library Type')}{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}{gettext('Actions')}{gettext('Size')}{gettext('Storage backend')}{gettext('Last Update')} {this.props.sortBy === 'time' && sortIcon}
+ ); + } + + renderMobileUI = () => { + const sortIcon = this.props.sortOrder === 'asc' ? : ; + return ( + + + + + + + + + + {this.renderRepoListView()} + +
{gettext('Library Type')} + {gettext('Sort:')} + {gettext('name')} {this.props.sortBy === 'name' && sortIcon} + {gettext('last update')} {this.props.sortBy === 'time' && sortIcon} + {gettext('Actions')}
+ ); + } + + render() { + return ( + + + {this.renderPCUI()} + + + {this.renderMobileUI()} + + + ); + } +} + +MylibRepoListView.propTypes = propTypes; + +export default MylibRepoListView; diff --git a/frontend/src/pages/my-libs/mylib-repo-menu.js b/frontend/src/pages/my-libs/mylib-repo-menu.js new file mode 100644 index 0000000000..8799f7adc1 --- /dev/null +++ b/frontend/src/pages/my-libs/mylib-repo-menu.js @@ -0,0 +1,164 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; +import { gettext, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants'; + +const propTypes = { + isPC: PropTypes.bool, + repo: PropTypes.object.isRequired, + onFreezedItem: PropTypes.func.isRequired, + onUnfreezedItem: PropTypes.func.isRequired, + onMenuItemClick: PropTypes.func.isRequired, +}; + +class MylibRepoMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemMenuShow: false, + }; + } + + onMenuItemClick = (e) => { + let operation = e.target.dataset.toggle; + this.props.onMenuItemClick(operation); + } + + onDropdownToggleClick = (e) => { + this.toggleOperationMenu(e); + } + + toggleOperationMenu = (e) => { + let dataset = e.target ? e.target.dataset : null; + if (dataset && dataset.toggle && dataset.toggle === 'Rename') { + this.setState({isItemMenuShow: !this.state.isItemMenuShow}); + return; + } + + this.setState( + {isItemMenuShow: !this.state.isItemMenuShow}, + () => { + if (this.state.isItemMenuShow) { + this.props.onFreezedItem(); + } else { + this.props.onUnfreezedItem(); + } + } + ); + } + + generatorOperations = () => { + let repo = this.props.repo; + let showResetPasswordMenuItem = repo.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured; + let operations = ['Rename', 'Transfer', 'History Setting']; + if (repo.encrypted) { + operations.push('Change Password'); + } + if (showResetPasswordMenuItem) { + operations.push('Reset Password') + } + if (folderPermEnabled) { + operations.push('Folder Permission'); + } + operations.push('Details'); + if (this.props.isPC && enableRepoSnapshotLabel) { + operations.push('Label current state'); + } + return operations; + } + + translateOperations = (item) => { + let translateResult = ''; + switch(item) { + case 'Share': + translateResult = gettext('Share'); + break; + case 'Delete': + translateResult = gettext('Delete'); + break; + case 'Rename': + translateResult = gettext('Rename'); + break; + case 'Transfer': + translateResult = gettext('Transfer'); + break; + case 'History Setting': + translateResult = gettext('History Setting'); + break; + case 'Change Password': + translateResult = gettext('Change Password'); + break; + case 'Reset Password': + translateResult = gettext('Reset Password'); + break; + case 'Folder Permission': + translateResult = gettext('Folder Permission'); + break; + case 'Details': + translateResult = gettext('Details'); + break; + case 'Label current state': + translateResult = gettext('Label current state'); + break; + default: + break; + } + + return translateResult; + } + + render() { + let operations = this.generatorOperations(); + + // pc menu + if (this.props.isPC) { + return ( + + + + {operations.map((item, index )=> { + return ({this.translateOperations(item)}); + })} + + + ) + } + + // mobile menu + operations.unshift('Share'); + operations.unshift('Delete'); + + return ( + + +
+
+
+ {operations.map((item, index) => { + return ({this.translateOperations(item)}); + })} +
+
+
+ ); + } +} + +MylibRepoMenu.propTypes = propTypes; + +export default MylibRepoMenu; diff --git a/frontend/src/pages/my-libs/table-body.js b/frontend/src/pages/my-libs/table-body.js deleted file mode 100644 index d11af91dd6..0000000000 --- a/frontend/src/pages/my-libs/table-body.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Item from './item'; - -const propTypes = { - items: PropTypes.array.isRequired, - onRenameRepo: PropTypes.func.isRequired, - onDeleteRepo: PropTypes.func.isRequired, - onTransfer: PropTypes.func.isRequired, - onResetEncryptedRepoPassword: PropTypes.func.isRequired, - showDeleteItemPopup: PropTypes.func.isRequired, - onHistorySetting: PropTypes.func.isRequired, - onRepoDetails: PropTypes.func.isRequired, - onItemClick: PropTypes.func.isRequired -}; - -class TableBody extends Component { - - constructor(props) { - super(props); - this.state = { - isItemFreezed: false, - }; - } - - onItemFreezedToggle = () => { - this.setState({isItemFreezed: !this.state.isItemFreezed}); - } - - render() { - let listItems = this.props.items.map((item, index) => { - return ( - - ); - }); - - return ( - {listItems} - ); - } -} - -TableBody.propTypes = propTypes; - -export default TableBody;