diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35b2111be6..a1966b762d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,7 @@ jobs: sudo apt-get install -y libfuse-dev cmake re2c flex sqlite3 sudo apt-get install -y libssl-dev libsasl2-dev libldap2-dev libonig-dev sudo apt-get install -y libxml2 libxml2-dev libjwt-dev + sudo apt-get install -y libhiredis-dev - name: clone and build run: | diff --git a/frontend/src/app.js b/frontend/src/app.js index 3ae3ee9f2b..dcd28cb392 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -157,19 +157,6 @@ class App extends Component { } }; - onGroupChanged = (groupID) => { - setTimeout(function () { - let url; - if (groupID) { - url = siteRoot + 'group/' + groupID + '/'; - } - else { - url = siteRoot + 'libraries/'; - } - window.location = url.toString(); - }, 1); - }; - tabItemClick = (tabName, groupID) => { let pathPrefix = []; if (groupID || this.dirViewPanels.indexOf(tabName) > -1) { @@ -343,7 +330,7 @@ class App extends Component { - + diff --git a/frontend/src/components/dialog/dismiss-group-dialog.js b/frontend/src/components/dialog/dismiss-group-dialog.js index cb8825172f..fe26b696fa 100644 --- a/frontend/src/components/dialog/dismiss-group-dialog.js +++ b/frontend/src/components/dialog/dismiss-group-dialog.js @@ -9,14 +9,11 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; class DismissGroupDialog extends React.Component { - constructor(props) { - super(props); - } - dismissGroup = () => { - let that = this; - seafileAPI.deleteGroup(this.props.groupID).then((res) => { - that.props.onGroupChanged(); + const { groupID } = this.props; + seafileAPI.deleteGroup(groupID).then((res) => { + this.props.onGroupDeleted(); + toaster.success(gettext('Group deleted')); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -25,13 +22,13 @@ class DismissGroupDialog extends React.Component { render() { return ( - - {gettext('Delete Group')} + + {gettext('Delete Group')} {gettext('Really want to delete this group?')} - + @@ -40,11 +37,9 @@ class DismissGroupDialog extends React.Component { } const DismissGroupDialogPropTypes = { - showDismissGroupDialog: PropTypes.bool.isRequired, - toggleDismissGroupDialog: PropTypes.func.isRequired, - loadGroup: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, + groupID: PropTypes.number.isRequired, + toggleDialog: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired }; DismissGroupDialog.propTypes = DismissGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/group-members-dialog.js b/frontend/src/components/dialog/group-members-dialog.js index 0d845e8d6d..535177c915 100644 --- a/frontend/src/components/dialog/group-members-dialog.js +++ b/frontend/src/components/dialog/group-members-dialog.js @@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; import Loading from '../loading'; const propTypes = { - groupID: PropTypes.string.isRequired, + groupID: PropTypes.number.isRequired, toggleDialog: PropTypes.func.isRequired }; diff --git a/frontend/src/components/dialog/import-members-dialog.js b/frontend/src/components/dialog/import-members-dialog.js index 7547d3f02e..abeb08cacb 100644 --- a/frontend/src/components/dialog/import-members-dialog.js +++ b/frontend/src/components/dialog/import-members-dialog.js @@ -5,7 +5,7 @@ import { gettext, siteRoot, groupImportMembersExtraMsg } from '../../utils/const import SeahubModalHeader from '@/components/common/seahub-modal-header'; const propTypes = { - toggleImportMembersDialog: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired, importMembersInBatch: PropTypes.func.isRequired, }; @@ -19,7 +19,7 @@ class ImportMembersDialog extends React.Component { } toggle = () => { - this.props.toggleImportMembersDialog(); + this.props.toggleDialog(); }; openFileInput = () => { @@ -49,9 +49,8 @@ class ImportMembersDialog extends React.Component { return ( {gettext('Import members from a .xlsx file')} - -

{groupImportMembersExtraMsg}

+ {groupImportMembersExtraMsg &&

{groupImportMembersExtraMsg}

}

{gettext('Download an example file')}

diff --git a/frontend/src/components/dialog/leave-group-dialog.js b/frontend/src/components/dialog/leave-group-dialog.js index 1f30156cec..a80d4e939a 100644 --- a/frontend/src/components/dialog/leave-group-dialog.js +++ b/frontend/src/components/dialog/leave-group-dialog.js @@ -9,13 +9,10 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; class LeaveGroupDialog extends React.Component { - constructor(props) { - super(props); - } - leaveGroup = () => { - seafileAPI.quitGroup(this.props.groupID, username).then((res) => { - this.props.onGroupChanged(); + const { groupID } = this.props; + seafileAPI.quitGroup(groupID, username).then((res) => { + this.props.onLeavingGroup(); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -24,13 +21,13 @@ class LeaveGroupDialog extends React.Component { render() { return ( - - {gettext('Leave Group')} + + {gettext('Leave Group')}

{gettext('Really want to leave this group?')}

- +
@@ -39,9 +36,9 @@ class LeaveGroupDialog extends React.Component { } const LeaveGroupDialogPropTypes = { - toggleLeaveGroupDialog: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, + groupID: PropTypes.number.isRequired, + onLeavingGroup: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired }; LeaveGroupDialog.propTypes = LeaveGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/manage-members-dialog.js b/frontend/src/components/dialog/manage-members-dialog.js index cea7a01631..8b3b4c7dbc 100644 --- a/frontend/src/components/dialog/manage-members-dialog.js +++ b/frontend/src/components/dialog/manage-members-dialog.js @@ -8,7 +8,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; import '../../css/manage-members-dialog.css'; const propTypes = { - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, toggleManageMembersDialog: PropTypes.func, toggleDepartmentDetailDialog: PropTypes.func, diff --git a/frontend/src/components/dialog/rename-group-dialog.js b/frontend/src/components/dialog/rename-group-dialog.js index 7c3af16a38..1c564f89d1 100644 --- a/frontend/src/components/dialog/rename-group-dialog.js +++ b/frontend/src/components/dialog/rename-group-dialog.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; -import { Modal, ModalBody, ModalFooter, Input, Button } from 'reactstrap'; +import { Modal, ModalBody, ModalFooter, Input, Label, Button } from 'reactstrap'; import SeahubModalHeader from '@/components/common/seahub-modal-header'; import toaster from '../toast'; @@ -12,7 +12,7 @@ class RenameGroupDialog extends React.Component { constructor(props) { super(props); this.state = { - newGroupName: this.props.currentGroupName, + newGroupName: this.props.groupName, isSubmitBtnActive: false, }; } @@ -30,48 +30,42 @@ class RenameGroupDialog extends React.Component { }); }; - renameGroup = () => { - let name = this.state.newGroupName.trim(); - if (name) { - let that = this; - seafileAPI.renameGroup(this.props.groupID, name).then((res) => { - that.props.loadGroup(this.props.groupID); - that.props.onGroupChanged(res.data.id); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } - this.setState({ - newGroupName: '', + handleSubmit = () => { + const { groupID } = this.props; + const { newGroupName } = this.state; + seafileAPI.renameGroup(groupID, newGroupName.trim()).then((res) => { + const { name } = res.data; + this.props.onGroupNameChanged(name); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); }); - this.props.toggleRenameGroupDialog(); + this.props.toggleDialog(); }; handleKeyDown = (event) => { if (event.keyCode === 13) { - this.renameGroup(); + this.handleSubmit(); } }; render() { return ( - - {gettext('Rename Group')} + + {gettext('Rename Group')} - + - - + + ); @@ -79,12 +73,10 @@ class RenameGroupDialog extends React.Component { } const RenameGroupDialogPropTypes = { - showRenameGroupDialog: PropTypes.bool.isRequired, - toggleRenameGroupDialog: PropTypes.func.isRequired, - loadGroup: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, - currentGroupName: PropTypes.string.isRequired, + toggleDialog: PropTypes.func.isRequired, + groupID: PropTypes.number, + onGroupNameChanged: PropTypes.func.isRequired, + groupName: PropTypes.string.isRequired, }; RenameGroupDialog.propTypes = RenameGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/transfer-group-dialog.js b/frontend/src/components/dialog/transfer-group-dialog.js index c36540cd3f..7a6bae824b 100644 --- a/frontend/src/components/dialog/transfer-group-dialog.js +++ b/frontend/src/components/dialog/transfer-group-dialog.js @@ -11,9 +11,9 @@ import toaster from '../toast'; import '../../css/transfer-group-dialog.css'; const propTypes = { - groupID: PropTypes.string, - toggleTransferGroupDialog: PropTypes.func.isRequired, - onGroupChanged: PropTypes.func.isRequired + groupID: PropTypes.number.isRequired, + onGroupTransfered: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired }; class TransferGroupDialog extends React.Component { @@ -21,18 +21,14 @@ class TransferGroupDialog extends React.Component { constructor(props) { super(props); this.state = { - selectedOption: null, - errMessage: '', + selectedOption: null }; - this.options = []; } handleSelectChange = (option) => { this.setState({ - selectedOption: option, - errMessage: '', + selectedOption: option }); - this.options = []; }; transferGroup = () => { @@ -41,19 +37,21 @@ class TransferGroupDialog extends React.Component { if (selectedOption && selectedOption[0]) { email = selectedOption[0].email; } - if (email) { - seafileAPI.transferGroup(this.props.groupID, email).then((res) => { - this.props.toggleTransferGroupDialog(); - toaster.success(gettext('Group has been transfered')); - }).catch((error) => { - let errMessage = Utils.getErrorMsg(error); - this.setState({ errMessage: errMessage }); - }); + if (!email) { + return false; } + seafileAPI.transferGroup(this.props.groupID, email).then((res) => { + toaster.success(gettext('Group has been transfered')); + this.props.onGroupTransfered(res.data); + this.props.toggleDialog(); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); }; toggle = () => { - this.props.toggleTransferGroupDialog(); + this.props.toggleDialog(); }; render() { @@ -68,7 +66,6 @@ class TransferGroupDialog extends React.Component { placeholder={gettext('Please enter 1 or more character')} onSelectChange={this.handleSelectChange} /> -
{this.state.errMessage}
diff --git a/frontend/src/components/group-members.js b/frontend/src/components/group-members.js index 3f15bb6e1f..2bda3f81ac 100644 --- a/frontend/src/components/group-members.js +++ b/frontend/src/components/group-members.js @@ -10,7 +10,7 @@ import OpIcon from './op-icon'; const propTypes = { groupMembers: PropTypes.array.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired, toggleItemFreezed: PropTypes.func.isRequired, @@ -61,7 +61,7 @@ const MemberPropTypes = { changeMember: PropTypes.func.isRequired, deleteMember: PropTypes.func.isRequired, toggleItemFreezed: PropTypes.func.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired }; diff --git a/frontend/src/components/list-and-add-group-members.js b/frontend/src/components/list-and-add-group-members.js index af0f2e1bcf..d12900890b 100644 --- a/frontend/src/components/list-and-add-group-members.js +++ b/frontend/src/components/list-and-add-group-members.js @@ -12,7 +12,7 @@ import GroupMembers from './group-members'; const propTypes = { toggleManageMembersDialog: PropTypes.func, toggleDepartmentDetailDialog: PropTypes.func, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired }; diff --git a/frontend/src/pages/groups/group-item.js b/frontend/src/pages/groups/group-item.js index 09e2079968..f35e3571a1 100644 --- a/frontend/src/pages/groups/group-item.js +++ b/frontend/src/pages/groups/group-item.js @@ -1,14 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { gettext, siteRoot, username } from '../../utils/constants'; +import { gettext, siteRoot } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view'; -import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; -import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; -import Repo from '../../models/repo'; import { LIST_MODE } from '../../components/dir-view-mode/constants'; +import GroupOperationMenu from './group-op-menu'; const propTypes = { inAllLibs: PropTypes.bool, @@ -17,21 +15,18 @@ const propTypes = { onMonitorRepo: PropTypes.func, renameRelatedGroupsRepos: PropTypes.func, deleteRelatedGroupsRepos: PropTypes.func, - insertRepoIntoGroup: PropTypes.func, + addRepoToGroup: PropTypes.func, unshareRepoToGroup: PropTypes.func, onTransferRepo: PropTypes.func.isRequired, + onGroupNameChanged: PropTypes.func.isRequired, + onGroupTransfered: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired, + onLeavingGroup: PropTypes.func.isRequired }; class GroupItem extends React.Component { - constructor(props) { - super(props); - this.state = { - isCreateRepoDialogOpen: false - }; - } - onItemUnshare = (repo) => { const { group } = this.props; const { id: group_id } = group; @@ -61,53 +56,45 @@ class GroupItem extends React.Component { this.props.onMonitorRepo(repo, monitored); }; - toggleCreateRepoDialog = () => { - this.setState({ - isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen - }); - }; - - onCreateRepo = (repo) => { + addNewRepo = (newRepo) => { const { group } = this.props; const { id: group_id } = group; - seafileAPI.createGroupOwnedLibrary(group_id, repo).then(res => { - let object = { - repo_id: res.data.id, - repo_name: res.data.name, - owner_name: res.data.group_name, - owner_email: res.data.owner, - permission: res.data.permission, - mtime: res.data.mtime, - size: res.data.size, - encrypted: res.data.encrypted, - }; - const newRepo = new Repo(object); - this.props.insertRepoIntoGroup({ repo: newRepo, group_id }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - this.toggleCreateRepoDialog(); + this.props.addRepoToGroup({ repo: newRepo, group_id }); + }; + + onGroupNameChanged = (newName) => { + const { group } = this.props; + this.props.onGroupNameChanged(newName, group.id); + }; + + onGroupDeleted = () => { + const { group } = this.props; + this.props.onGroupDeleted(group.id); + }; + + onLeavingGroup = () => { + const { group } = this.props; + this.props.onLeavingGroup(group.id); }; render() { const { inAllLibs = false, group, currentViewMode = LIST_MODE } = this.props; - const { parent_group_id, admins } = group; const emptyTip =

{gettext('No libraries')}

; - const isDeptAdmin = parent_group_id != 0 && admins.indexOf(username) > -1; return (

{group.name} - {isDeptAdmin && - - } +

{group.repos.length === 0 ? @@ -127,13 +114,6 @@ class GroupItem extends React.Component { currentViewMode={currentViewMode} /> } - {this.state.isCreateRepoDialogOpen && - - }
); } diff --git a/frontend/src/pages/groups/group-op-menu.js b/frontend/src/pages/groups/group-op-menu.js new file mode 100644 index 0000000000..b387d39b91 --- /dev/null +++ b/frontend/src/pages/groups/group-op-menu.js @@ -0,0 +1,273 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { gettext, username, canAddRepo } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; +import toaster from '../../components/toast'; +import { Repo } from '../../models'; +import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; +import GroupMembersDialog from '../../components/dialog/group-members-dialog'; +import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog'; +import RenameGroupDialog from '../../components/dialog/rename-group-dialog'; +import TransferGroupDialog from '../../components/dialog/transfer-group-dialog'; +import ImportMembersDialog from '../../components/dialog/import-members-dialog'; +import ManageMembersDialog from '../../components/dialog/manage-members-dialog'; +import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog'; +import LeaveGroupDialog from '../../components/dialog/leave-group-dialog'; +import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; + +import '../../css/group-view.css'; + +const propTypes = { + group: PropTypes.object.isRequired, + addNewRepo: PropTypes.func.isRequired, + onGroupNameChanged: PropTypes.func.isRequired, + onGroupTransfered: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired, + onLeavingGroup: PropTypes.func.isRequired +}; + +class GroupOperationMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + isCreateRepoDialogOpen: false, + isShowDepartmentDetailDialog: false, + isRenameGroupDialogOpen: false, + isDeleteGroupDialogOpen: false, + isTransferGroupDialogOpen: false, + isImportMembersDialogOpen: false, + isManageMembersDialogOpen: false, + isLeaveGroupDialogOpen: false, + isMembersDialogOpen: false + }; + } + + onCreateRepoToggle = () => { + this.setState({ isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen }); + }; + + onCreateRepo = (repo, groupType) => { + const { group } = this.props; + const groupId = group.id; + if (groupType && groupType === 'department') { + seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { + let object = { + repo_id: res.data.id, + repo_name: res.data.name, + owner_name: res.data.group_name, + owner_email: res.data.owner, + permission: res.data.permission, + mtime: res.data.mtime, + size: res.data.size, + encrypted: res.data.encrypted, + }; + const repo = new Repo(object); + this.props.addNewRepo(repo); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + + } else { + seafileAPI.createGroupRepo(groupId, repo).then(res => { + const repo = new Repo(res.data); + this.props.addNewRepo(repo); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + this.onCreateRepoToggle(); + }; + + toggleDeleteGroupDialog = () => { + this.setState({ + isDeleteGroupDialogOpen: !this.state.isDeleteGroupDialogOpen, + }); + }; + + toggleRenameGroupDialog = () => { + this.setState({ + isRenameGroupDialogOpen: !this.state.isRenameGroupDialogOpen, + }); + }; + + toggleTransferGroupDialog = () => { + this.setState({ + isTransferGroupDialogOpen: !this.state.isTransferGroupDialogOpen, + }); + }; + + toggleImportMembersDialog = () => { + this.setState({ + isImportMembersDialogOpen: !this.state.isImportMembersDialogOpen + }); + }; + + importMembersInBatch = (file) => { + toaster.notify(gettext('It may take some time, please wait.'), { 'id': 'importing-members' }); + const { group } = this.props; + seafileAPI.importGroupMembersViaFile(group.id, file).then((res) => { + res.data.success.forEach(item => { + toaster.success(gettext('Successfully imported {user_placeholder}').replace('{user_placeholder}', `${item.contact_email}`), { 'id': 'importing-members' }); + }); + res.data.failed.forEach(item => { + toaster.danger(`${item.email}: ${item.error_msg}`, { 'id': 'importing-members' }); + }); + }).catch((error) => { + let errMsg = Utils.getErrorMsg(error); + toaster.danger(errMsg); + }); + }; + + toggleManageMembersDialog = () => { + this.setState({ + isManageMembersDialogOpen: !this.state.isManageMembersDialogOpen, + }); + }; + + toggleLeaveGroupDialog = () => { + this.setState({ + isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen, + }); + }; + + toggleMembersDialog = () => { + this.setState({ + isMembersDialogOpen: !this.state.isMembersDialogOpen + }); + }; + + toggleDepartmentDetailDialog = () => { + this.setState({ + isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog + }); + }; + + getOpList = () => { + const { group } = this.props; + const isDepartment = group.parent_group_id !== 0; + const isStaff = group.admins.indexOf(username) > -1; + const isOwner = group.owner === username; + const opList = []; + if ((!isDepartment && canAddRepo) || + (isDepartment && isStaff)) { + this.newLibraryEnabled = true; + opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider'); + } + opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog }); + if (isStaff || isOwner) { + opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog }); + opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog }); + opList.push('Divider'); + opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog }); + if (isOwner) { + opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog }); + } + if (isOwner) { + opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDeleteGroupDialog }); + } + } + + if (!isOwner && !isDepartment) { + opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog }); + } + + return opList; + }; + + + render() { + const { + isCreateRepoDialogOpen, + isMembersDialogOpen + } = this.state; + const { group } = this.props; + const { id: groupID, parent_group_id, owner } = group; + const isDepartment = parent_group_id !== 0; + const isOwner = owner === username; + + const opList = this.getOpList(); + return ( + + {group && ( + + )} + {isCreateRepoDialogOpen && + + } + {isMembersDialogOpen && + + } + {this.state.isRenameGroupDialogOpen && + + } + {this.state.isDeleteGroupDialogOpen && + + } + {this.state.isTransferGroupDialogOpen && + + } + {this.state.isImportMembersDialogOpen && + + } + {this.state.isManageMembersDialogOpen && + + } + {this.state.isShowDepartmentDetailDialog && + + } + {this.state.isLeaveGroupDialogOpen && + + } + + ); + } +} + +GroupOperationMenu.propTypes = propTypes; + +export default GroupOperationMenu; diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js index fc523fc855..2e023ec14d 100644 --- a/frontend/src/pages/groups/group-view.js +++ b/frontend/src/pages/groups/group-view.js @@ -2,35 +2,25 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import cookie from 'react-cookies'; import classnames from 'classnames'; -import { gettext, username, canAddRepo } from '../../utils/constants'; +import { navigate } from '@gatsbyjs/reach-router'; +import { gettext, siteRoot, username } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import Loading from '../../components/loading'; import EmptyTip from '../../components/empty-tip'; -import ModalPortal from '../../components/modal-portal'; import toaster from '../../components/toast'; import { Group, Repo } from '../../models'; -import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; -import GroupMembersDialog from '../../components/dialog/group-members-dialog'; -import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog'; -import RenameGroupDialog from '../../components/dialog/rename-group-dialog'; -import TransferGroupDialog from '../../components/dialog/transfer-group-dialog'; -import ImportMembersDialog from '../../components/dialog/import-members-dialog'; -import ManageMembersDialog from '../../components/dialog/manage-members-dialog'; -import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog'; -import LeaveGroupDialog from '../../components/dialog/leave-group-dialog'; import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view'; import SortOptionsDialog from '../../components/dialog/sort-options'; -import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; import ViewModes from '../../components/view-modes'; import ReposSortMenu from '../../components/sort-menu'; import { LIST_MODE } from '../../components/dir-view-mode/constants'; +import GroupOperationMenu from './group-op-menu'; import '../../css/group-view.css'; const propTypes = { - onGroupChanged: PropTypes.func.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.string }; class GroupView extends React.Component { @@ -43,9 +33,6 @@ class GroupView extends React.Component { errMessage: '', emptyTip: null, currentGroup: null, - currentRepo: null, - isStaff: false, - isOwner: false, currentViewMode: localStorage.getItem('sf_repo_list_view_mode') || LIST_MODE, sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size' sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc' @@ -54,19 +41,7 @@ class GroupView extends React.Component { currentPage: 1, perPage: 300, hasNextPage: false, - libraryType: 'group', - isCreateRepoDialogShow: false, isDepartmentGroup: false, - isShowDepartmentDetailDialog: false, - showGroupDropdown: false, - showGroupMembersPopover: false, - showRenameGroupDialog: false, - showDismissGroupDialog: false, - showTransferGroupDialog: false, - showImportMembersDialog: false, - showManageMembersDialog: false, - isLeaveGroupDialogOpen: false, - isMembersDialogOpen: false }; } @@ -86,9 +61,7 @@ class GroupView extends React.Component { this.setState({ emptyTip: this.getEmptyTip(currentGroup), currentGroup, - isStaff: currentGroup.admins.indexOf(username) > -1, // for item operations isDepartmentGroup: currentGroup.parent_group_id !== 0, - isOwner: currentGroup.owner === username, currentPage: 1, repoList: [] // empty it for the current group }, () => { @@ -160,45 +133,6 @@ class GroupView extends React.Component { return null; }; - onCreateRepoToggle = () => { - this.setState({ isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow }); - }; - - onCreateRepo = (repo, groupOwnerType) => { - let groupId = this.props.groupID; - if (groupOwnerType && groupOwnerType === 'department') { - seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { // need modify endpoint api - let object = { - repo_id: res.data.id, - repo_name: res.data.name, - owner_name: res.data.group_name, - owner_email: res.data.owner, - permission: res.data.permission, - mtime: res.data.mtime, - size: res.data.size, - encrypted: res.data.encrypted, - }; - let repo = new Repo(object); - let repoList = this.addRepoItem(repo); - this.setState({ repoList: repoList }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - - } else { - seafileAPI.createGroupRepo(groupId, repo).then(res => { - let repo = new Repo(res.data); - let repoList = this.addRepoItem(repo); - this.setState({ repoList: repoList }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } - this.onCreateRepoToggle(); - }; - onItemDelete = (repo) => { let repoList = this.state.repoList.filter(item => { return item.repo_id !== repo.repo_id; @@ -215,12 +149,6 @@ class GroupView extends React.Component { this.loadGroup(this.props.groupID); }; - addRepoItem = (repo) => { - let newRepoList = this.state.repoList.map(item => {return item;}); - newRepoList.unshift(repo); - return newRepoList; - }; - onItemUnshare = (repo) => { let group = this.state.currentGroup; seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => { @@ -260,59 +188,6 @@ class GroupView extends React.Component { this.setState({ repoList: repoList }); }; - toggleDismissGroupDialog = () => { - this.setState({ - showDismissGroupDialog: !this.state.showDismissGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleRenameGroupDialog = () => { - this.setState({ - showRenameGroupDialog: !this.state.showRenameGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleTransferGroupDialog = () => { - this.setState({ - showTransferGroupDialog: !this.state.showTransferGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleImportMembersDialog = () => { - this.setState({ - showImportMembersDialog: !this.state.showImportMembersDialog - }); - }; - - importMembersInBatch = (file) => { - toaster.notify(gettext('It may take some time, please wait.')); - seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => { - res.data.failed.forEach(item => { - toaster.danger(`${item.email}: ${item.error_msg}`); - }); - }).catch((error) => { - let errMsg = Utils.getErrorMsg(error); - toaster.danger(errMsg); - }); - }; - - toggleManageMembersDialog = () => { - this.setState({ - showManageMembersDialog: !this.state.showManageMembersDialog, - showGroupDropdown: false, - }); - }; - - toggleLeaveGroupDialog = () => { - this.setState({ - isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen, - showGroupDropdown: false, - }); - }; - sortItems = (sortBy, sortOrder) => { cookie.save('seafile-repo-dir-sort-by', sortBy); cookie.save('seafile-repo-dir-sort-order', sortOrder); @@ -323,18 +198,6 @@ class GroupView extends React.Component { }); }; - translateRole = (role) => { - if (role === 'Admin') { - return gettext('Admin'); - } - else if (role === 'Member') { - return gettext('Member'); - } - else if (role === 'Owner') { - return gettext('Owner'); - } - }; - toggleSortOptionsDialog = () => { this.setState({ isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen @@ -357,43 +220,6 @@ class GroupView extends React.Component { } }; - toggleMembersDialog = () => { - this.setState({ - isMembersDialogOpen: !this.state.isMembersDialogOpen - }); - }; - - getOpList = () => { - const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state; - const opList = []; - if ((!isDepartmentGroup && canAddRepo) || - (isDepartmentGroup && isStaff)) { - this.newLibraryEnalbed = true; - opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider'); - } - opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog }); - if (currentGroup) { - if (isStaff || isOwner) { - opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog }); - opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog }); - opList.push('Divider'); - opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog }); - if (isOwner) { - opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog }); - } - if (isOwner) { - opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDismissGroupDialog }); - } - } - - if (!isOwner && !isDepartmentGroup) { - opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog }); - } - } - - return opList; - }; - switchViewMode = (newMode) => { this.setState({ currentViewMode: newMode @@ -409,16 +235,38 @@ class GroupView extends React.Component { }); }; - toggleDepartmentDetailDialog = () => { + addNewRepo = (newRepo) => { + let { repoList } = this.state; + repoList.unshift(newRepo); + this.setState({ repoList: repoList }); + }; + + onGroupNameChanged = (newName) => { + const { currentGroup } = this.state; + currentGroup.name = newName; this.setState({ - isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog + currentGroup: currentGroup }); }; + onGroupTransfered = (group) => { + this.setState({ + currentGroup: group + }); + }; + + onGroupDeleted = () => { + navigate(siteRoot); + }; + + onLeavingGroup = () => { + navigate(siteRoot); + }; + render() { const { isLoading, repoList, errMessage, emptyTip, - currentGroup, isDepartmentGroup, isMembersDialogOpen, + currentGroup, isDepartmentGroup, currentViewMode, sortBy, sortOrder } = this.state; @@ -427,7 +275,6 @@ class GroupView extends React.Component { useRate = currentGroup.group_quota_usage / currentGroup.group_quota * 100 + '%'; } - const opList = this.getOpList(); return (
@@ -440,9 +287,13 @@ class GroupView extends React.Component { } {currentGroup.name} -
@@ -510,85 +361,6 @@ class GroupView extends React.Component {
- {this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && ( - - - - )} - {this.state.isCreateRepoDialogShow && this.state.isDepartmentGroup && - - } - {isMembersDialogOpen && - - } - {this.state.showRenameGroupDialog && - - } - {this.state.showDismissGroupDialog && - - } - {this.state.showTransferGroupDialog && - - } - { this.state.showImportMembersDialog && - - } - {this.state.showManageMembersDialog && - - } - {this.state.isShowDepartmentDetailDialog && - - } - {this.state.isLeaveGroupDialogOpen && - - }
); } diff --git a/frontend/src/pages/libraries/index.js b/frontend/src/pages/libraries/index.js index 5d39a8b752..99b297631a 100644 --- a/frontend/src/pages/libraries/index.js +++ b/frontend/src/pages/libraries/index.js @@ -52,7 +52,7 @@ class Libraries extends Component { const eventBus = EventBus.getInstance(); this.unsubscribeAddNewGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_NEW_GROUP, this.addNewGroup); - this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.insertRepoIntoGroup); + this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.addRepoToGroup); this.unsubscribeUnsharedRepoToGroup = eventBus.subscribe(EVENT_BUS_TYPE.UN_SHARE_REPO_TO_GROUP, this.unshareRepoToGroup); } @@ -282,7 +282,7 @@ class Libraries extends Component { }; */ - insertRepoIntoGroup = ({ repo, group_id }) => { + addRepoToGroup = ({ repo, group_id }) => { if (!repo) { return; } @@ -299,11 +299,37 @@ class Libraries extends Component { } targetGroup.repos.unshift(repo); - targetGroup.repos = Utils.sortRepos(targetGroup.repos, this.state.sortBy, this.state.sortOrder); this.groupsReposManager.add(repo.repo_id, group_id); this.setState({ groupList: newGroupList }); }; + onGroupNameChanged = (newName, groupID) => { + const { groupList } = this.state; + let newGroupList = [...groupList]; + let targetGroup = newGroupList.find((group) => group.id === groupID); + targetGroup.name = newName; + this.setState({ groupList: newGroupList }); + }; + + onGroupTransfered = (group) => { + const { groupList } = this.state; + let newGroupList = [...groupList]; + let targetGroup = newGroupList.find((item) => item.id === group.id); + targetGroup.owner = group.owner; + targetGroup.admins = group.admins; + this.setState({ groupList: newGroupList }); + }; + + onGroupDeleted = (groupID) => { + const { groupList } = this.state; + this.setState({ groupList: groupList.filter(item => item.id != groupID) }); + }; + + onLeavingGroup = (groupID) => { + const { groupList } = this.state; + this.setState({ groupList: groupList.filter(item => item.id != groupID) }); + }; + unshareRepoToGroup = ({ repo_id, group_id }) => { const { groupList } = this.state; let newGroupList = [...groupList]; @@ -498,7 +524,11 @@ class Libraries extends Component { onMonitorRepo={this.onMonitorRepo} renameRelatedGroupsRepos={this.renameRelatedGroupsRepos} deleteRelatedGroupsRepos={this.deleteRelatedGroupsRepos} - insertRepoIntoGroup={this.insertRepoIntoGroup} + addRepoToGroup={this.addRepoToGroup} + onGroupNameChanged={this.onGroupNameChanged} + onGroupTransfered={this.onGroupTransfered} + onGroupDeleted={this.onGroupDeleted} + onLeavingGroup={this.onLeavingGroup} unshareRepoToGroup={this.unshareRepoToGroup} onTransferRepo={this.onGroupTransferRepo} currentViewMode={currentViewMode}