From 29fa239cd00c3f4646a8123ff33d265d9dc439ac Mon Sep 17 00:00:00 2001 From: llj Date: Fri, 24 Nov 2023 18:21:46 +0800 Subject: [PATCH] Single selector (#5791) * [single selector] added a new 'single selector' to replace the 'role editor' * [sys admin - group - member] replaced the old 'role selector' with the new 'single selector' * [sys admin - orgs] replaced the old 'role selector' with the new 'single selector' * [sys admin, org admin] replaced different selectors with the new 'single selector' for users & orgs * [share link perm selector] replaced it with the new 'single selector' --- frontend/src/components/group-members.js | 31 ++-- .../components/select-editor/role-editor.js | 44 ------ .../share-link-permission-editor.js | 36 ----- .../sysadmin-group-role-editor.js | 43 ----- .../sysadmin-user-status-editor.js | 41 ----- .../select-editor/user-status-editor.js | 43 ----- .../share-link-panel/link-details.js | 26 +-- .../components/share-link-panel/link-item.js | 11 +- frontend/src/components/single-selector.js | 89 +++++++++++ frontend/src/css/single-selector.css | 31 ++++ .../src/pages/org-admin/org-admin-list.js | 11 +- .../pages/org-admin/org-department-item.js | 44 +++--- frontend/src/pages/org-admin/org-user-item.js | 45 ++++-- .../src/pages/org-admin/org-users-admins.js | 17 ++ .../src/pages/org-admin/org-users-list.js | 7 +- frontend/src/pages/share-admin/share-links.js | 81 +++++++--- .../sys-admin/departments/member-item.js | 43 ++--- .../pages/sys-admin/groups/group-members.js | 56 +++++-- .../src/pages/sys-admin/orgs/org-users.js | 46 ++++-- .../src/pages/sys-admin/orgs/orgs-content.js | 71 +++++++-- .../pages/sys-admin/users/users-content.js | 148 +++++++++++++----- 21 files changed, 569 insertions(+), 395 deletions(-) delete mode 100644 frontend/src/components/select-editor/role-editor.js delete mode 100644 frontend/src/components/select-editor/share-link-permission-editor.js delete mode 100644 frontend/src/components/select-editor/sysadmin-group-role-editor.js delete mode 100644 frontend/src/components/select-editor/sysadmin-user-status-editor.js delete mode 100644 frontend/src/components/select-editor/user-status-editor.js create mode 100644 frontend/src/components/single-selector.js create mode 100644 frontend/src/css/single-selector.css diff --git a/frontend/src/components/group-members.js b/frontend/src/components/group-members.js index 6ea4f3e234..5092ce0555 100644 --- a/frontend/src/components/group-members.js +++ b/frontend/src/components/group-members.js @@ -4,7 +4,7 @@ import { Table } from 'reactstrap'; import { Utils } from '../utils/utils'; import { gettext } from '../utils/constants'; import { seafileAPI } from '../utils/seafile-api'; -import RoleEditor from './select-editor/role-editor'; +import RoleSelector from './single-selector'; import toaster from './toast'; import OpIcon from './op-icon'; @@ -70,14 +70,17 @@ class Member extends React.PureComponent { constructor(props) { super(props); - this.roles = ['Admin', 'Member']; + this.roleOptions = [ + { value: 'Admin', text: gettext('Admin'), isSelected: false }, + { value: 'Member', text: gettext('Member'), isSelected: false } + ]; this.state = ({ highlight: false, }); } - onChangeUserRole = (role) => { - let isAdmin = role === 'Admin' ? 'True' : 'False'; + onChangeUserRole = (roleOption) => { + let isAdmin = roleOption.value === 'Admin' ? 'True' : 'False'; seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => { this.props.changeMember(res.data); }).catch(error => { @@ -124,8 +127,17 @@ class Member extends React.PureComponent { }; render() { + const { highlight } = this.state; const { item, isOwner } = this.props; const deleteAuthority = (item.role !== 'Owner' && isOwner === true) || (item.role === 'Member' && isOwner === false); + + const { role: curRole } = item; + this.roleOptions = this.roleOptions.map(item => { + item.isSelected = item.value == curRole; + return item; + }); + const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0]; + return( @@ -135,12 +147,11 @@ class Member extends React.PureComponent { {this.translateRole(item.role)} } {(isOwner === true && item.role !== 'Owner') && - } diff --git a/frontend/src/components/select-editor/role-editor.js b/frontend/src/components/select-editor/role-editor.js deleted file mode 100644 index b896d15614..0000000000 --- a/frontend/src/components/select-editor/role-editor.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; -import SelectEditor from './select-editor'; - -const propTypes = { - isTextMode: PropTypes.bool.isRequired, - isEditIconShow: PropTypes.bool.isRequired, - roles: PropTypes.array.isRequired, - currentRole: PropTypes.string.isRequired, - onRoleChanged: PropTypes.func.isRequired, - toggleItemFreezed: PropTypes.func, -}; - -class RoleEditor extends React.Component { - - translateRole = (role) => { - if (role === 'Admin') { - return gettext('Admin'); - } - - if (role === 'Member') { - return gettext('Member'); - } - }; - - render() { - return ( - - ); - } -} - -RoleEditor.propTypes = propTypes; - -export default RoleEditor; diff --git a/frontend/src/components/select-editor/share-link-permission-editor.js b/frontend/src/components/select-editor/share-link-permission-editor.js deleted file mode 100644 index 5b09d232fe..0000000000 --- a/frontend/src/components/select-editor/share-link-permission-editor.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Utils } from '../../utils/utils'; -import SelectEditor from './select-editor'; - -const propTypes = { - isTextMode: PropTypes.bool.isRequired, - isEditIconShow: PropTypes.bool.isRequired, - permissionOptions: PropTypes.array.isRequired, - currentPermission: PropTypes.string.isRequired, - onPermissionChanged: PropTypes.func.isRequired -}; - -class ShareLinkPermissionEditor extends React.Component { - - translatePermission = (permission) => { - return Utils.getShareLinkPermissionObject(permission).text; - }; - - render() { - return ( - - ); - } -} - -ShareLinkPermissionEditor.propTypes = propTypes; - -export default ShareLinkPermissionEditor; diff --git a/frontend/src/components/select-editor/sysadmin-group-role-editor.js b/frontend/src/components/select-editor/sysadmin-group-role-editor.js deleted file mode 100644 index dd2a39654d..0000000000 --- a/frontend/src/components/select-editor/sysadmin-group-role-editor.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; -import SelectEditor from './select-editor'; - -const propTypes = { - isTextMode: PropTypes.bool.isRequired, - isEditIconShow: PropTypes.bool.isRequired, - roleOptions: PropTypes.array.isRequired, - currentRole: PropTypes.string.isRequired, - onRoleChanged: PropTypes.func.isRequired -}; - -class SysAdminGroupRoleEditor extends React.Component { - - translateRoles = (role) => { - switch (role) { - case 'Member': - return gettext('Member'); - case 'Admin': - return gettext('Admin'); - default: - return role; - } - }; - - render() { - return ( - - ); - } -} - -SysAdminGroupRoleEditor.propTypes = propTypes; - -export default SysAdminGroupRoleEditor; diff --git a/frontend/src/components/select-editor/sysadmin-user-status-editor.js b/frontend/src/components/select-editor/sysadmin-user-status-editor.js deleted file mode 100644 index f2d247517d..0000000000 --- a/frontend/src/components/select-editor/sysadmin-user-status-editor.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; -import SelectEditor from './select-editor'; - -const propTypes = { - isTextMode: PropTypes.bool.isRequired, - isEditIconShow: PropTypes.bool.isRequired, - statusOptions: PropTypes.array.isRequired, - currentStatus: PropTypes.string.isRequired, - onStatusChanged: PropTypes.func.isRequired -}; - -class SysAdminUserStatusEditor extends React.Component { - - translateStatus = (status) => { - switch (status) { - case 'active': - return gettext('Active'); - case 'inactive': - return gettext('Inactive'); - } - }; - - render() { - return ( - - ); - } -} - -SysAdminUserStatusEditor.propTypes = propTypes; - -export default SysAdminUserStatusEditor; diff --git a/frontend/src/components/select-editor/user-status-editor.js b/frontend/src/components/select-editor/user-status-editor.js deleted file mode 100644 index d61526349b..0000000000 --- a/frontend/src/components/select-editor/user-status-editor.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; -import SelectEditor from './select-editor'; - -const propTypes = { - isTextMode: PropTypes.bool.isRequired, - isEditIconShow: PropTypes.bool.isRequired, - statusArray: PropTypes.array.isRequired, - currentStatus: PropTypes.string.isRequired, - onStatusChanged: PropTypes.func.isRequired -}; - -class UserStatusEditor extends React.Component { - - translateStatus = (userStatus) => { - if (userStatus === 'active') { - return gettext('Active'); - } - - if (userStatus === 'inactive') { - return gettext('Inactive'); - } - }; - - render() { - return ( - - ); - } - -} - -UserStatusEditor.propTypes = propTypes; - -export default UserStatusEditor; diff --git a/frontend/src/components/share-link-panel/link-details.js b/frontend/src/components/share-link-panel/link-details.js index 592c09d4b9..81af76521c 100644 --- a/frontend/src/components/share-link-panel/link-details.js +++ b/frontend/src/components/share-link-panel/link-details.js @@ -4,7 +4,7 @@ import moment from 'moment'; import copy from 'copy-to-clipboard'; import { Button } from 'reactstrap'; import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, canSendShareLinkEmail } from '../../utils/constants'; -import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; +import Selector from '../../components/single-selector'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; @@ -119,9 +119,9 @@ class LinkDetails extends React.Component { this.setState({isOpIconShown: false}); }; - changePerm = (permission) => { + changePerm = (permOption) => { const { sharedLinkInfo } = this.props; - const { permissionDetails } = Utils.getShareLinkPermissionObject(permission); + const { permissionDetails } = Utils.getShareLinkPermissionObject(permOption.value); seafileAPI.updateShareLink(sharedLinkInfo.token, JSON.stringify(permissionDetails)).then((res) => { this.props.updateLink(new ShareLink(res.data)); }).catch((error) => { @@ -152,6 +152,15 @@ class LinkDetails extends React.Component { const { sharedLinkInfo, permissionOptions } = this.props; const { isOpIconShown } = this.state; const currentPermission = Utils.getShareLinkPermissionStr(sharedLinkInfo.permissions); + this.permOptions = permissionOptions.map(item => { + return { + value: item, + text: Utils.getShareLinkPermissionObject(item).text, + isSelected: item == currentPermission + }; + }); + const currentSelectedPermOption = this.permOptions.filter(item => item.isSelected)[0]; + return (
@@ -231,12 +240,11 @@ class LinkDetails extends React.Component { <>
{gettext('Permission:')}
-
diff --git a/frontend/src/components/share-link-panel/link-item.js b/frontend/src/components/share-link-panel/link-item.js index fee2ece362..87293296af 100644 --- a/frontend/src/components/share-link-panel/link-item.js +++ b/frontend/src/components/share-link-panel/link-item.js @@ -4,7 +4,6 @@ import moment from 'moment'; import copy from 'copy-to-clipboard'; import toaster from '../toast'; import { isPro, gettext } from '../../utils/constants'; -import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; import { Utils } from '../../utils/utils'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; @@ -105,15 +104,7 @@ class LinkItem extends React.Component { {this.cutLink(link)} - {(isPro && permissions) && ( - {}} - /> - )} + {(isPro && permissions) && Utils.getShareLinkPermissionObject(currentPermission).text} {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'} diff --git a/frontend/src/components/single-selector.js b/frontend/src/components/single-selector.js new file mode 100644 index 0000000000..f171611aa7 --- /dev/null +++ b/frontend/src/components/single-selector.js @@ -0,0 +1,89 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import '../css/single-selector.css'; + +const propTypes = { + isDropdownToggleShown: PropTypes.bool.isRequired, + currentSelectedOption: PropTypes.object.isRequired, + options: PropTypes.array.isRequired, + selectOption: PropTypes.func.isRequired, + toggleItemFreezed: PropTypes.func +}; + +class Selector extends Component { + + constructor(props) { + super(props); + this.state = { + isPopoverOpen: false + }; + } + + componentDidMount() { + document.addEventListener('click', this.handleOutsideClick); + } + + componentWillUnmount() { + document.removeEventListener('click', this.handleOutsideClick); + } + + handleOutsideClick = (e) => { + const { isPopoverOpen } = this.state; + if (isPopoverOpen && !this.selector.contains(e.target)) { + this.togglePopover(); + } + }; + + togglePopover = () => { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen + }, () => { + if (this.props.toggleItemFreezed) { + this.props.toggleItemFreezed(this.state.isPopoverOpen); + } + }); + }; + + onToggleClick = (e) => { + e.stopPropagation(); + this.togglePopover(); + }; + + selectItem = (e, targetItem) => { + e.stopPropagation(); + this.props.selectOption(targetItem); + this.togglePopover(); + }; + + render() { + const { isPopoverOpen } = this.state; + const { currentSelectedOption, options, isDropdownToggleShown } = this.props; + return ( +
+ + {currentSelectedOption.text} + {isDropdownToggleShown && } + + {isPopoverOpen && ( +
this.selector = ref}> +
    + {options.map((item, index) => { + return ( +
  • {this.selectItem(e, item);}}> + {item.text} + +
  • + ); + })} +
+
+ )} +
+ ); + } +} + +Selector.propTypes = propTypes; + +export default Selector; diff --git a/frontend/src/css/single-selector.css b/frontend/src/css/single-selector.css new file mode 100644 index 0000000000..518523bda8 --- /dev/null +++ b/frontend/src/css/single-selector.css @@ -0,0 +1,31 @@ +.sf-single-selector .cur-option { + cursor: pointer; +} + +.sf-single-selector .cur-option .toggle-icon { + color: #999; +} + +.sf-single-selector .options-container { + min-width: 165px; + background: #fff; + border: 1px solid #e8e8e8; + z-index: 2; +} + +.sf-single-selector .option-list { + min-height: 4rem; + max-height: 200px; +} + +.sf-single-selector .option-item { + cursor: pointer; +} + +.sf-single-selector .option-item:hover { + background: #f5f5f5; +} + +.sf-single-selector .option-item-text { + font-size: 14px; +} diff --git a/frontend/src/pages/org-admin/org-admin-list.js b/frontend/src/pages/org-admin/org-admin-list.js index f5bace870e..ca2f630ec6 100644 --- a/frontend/src/pages/org-admin/org-admin-list.js +++ b/frontend/src/pages/org-admin/org-admin-list.js @@ -10,6 +10,7 @@ const propTypes = { toggleDelete: PropTypes.func.isRequired, toggleRevokeAdmin: PropTypes.func.isRequired, orgAdminUsers: PropTypes.array.isRequired, + changeStatus: PropTypes.func.isRequired, initOrgAdmin: PropTypes.func.isRequired }; @@ -34,6 +35,10 @@ class OrgAdminList extends React.Component { this.setState({isItemFreezed: false}); }; + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + render() { let orgAdminUsers = this.props.orgAdminUsers; @@ -50,17 +55,19 @@ class OrgAdminList extends React.Component { - {orgAdminUsers.map(item => { + {orgAdminUsers.map((item, index) => { return ( ); })} diff --git a/frontend/src/pages/org-admin/org-department-item.js b/frontend/src/pages/org-admin/org-department-item.js index bec0d69250..70782f6f01 100644 --- a/frontend/src/pages/org-admin/org-department-item.js +++ b/frontend/src/pages/org-admin/org-department-item.js @@ -7,7 +7,7 @@ import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; import MainPanelTopbar from './main-panel-topbar'; import ModalPortal from '../../components/modal-portal'; -import RoleEditor from '../../components/select-editor/role-editor'; +import RoleSelector from '../../components/single-selector'; import AddDepartDialog from '../../components/dialog/org-add-department-dialog'; import AddMemberDialog from '../../components/dialog/org-add-member-dialog'; import DeleteMemberDialog from '../../components/dialog/org-delete-member-dialog'; @@ -396,10 +396,12 @@ class MemberItem extends React.Component { constructor(props) { super(props); this.state = { - highlight: false, - showRoleMenu: false, + highlight: false }; - this.roles = ['Admin', 'Member']; + this.roleOptions = [ + { value: 'Admin', text: gettext('Admin'), isSelected: false }, + { value: 'Member', text: gettext('Member'), isSelected: false } + ]; } onMouseEnter = () => { @@ -412,12 +414,8 @@ class MemberItem extends React.Component { this.setState({ highlight: false }); }; - toggleMemberRoleMenu = () => { - this.setState({ showRoleMenu: !this.state.showRoleMenu }); - }; - - onChangeUserRole = (role) => { - let isAdmin = role === 'Admin' ? true : false; + onChangeUserRole = (roleOption) => { + let isAdmin = roleOption.value === 'Admin' ? true : false; seafileAPI.orgAdminSetGroupMemberRole(orgID, this.props.groupID, this.props.member.email, isAdmin).then((res) => { this.props.onMemberChanged(); }).catch(error => { @@ -434,25 +432,29 @@ class MemberItem extends React.Component { const highlight = this.state.highlight; let memberLink = serviceURL + '/org/useradmin/info/' + member.email + '/'; if (member.role === 'Owner') return null; + + this.roleOptions = this.roleOptions.map(item => { + item.isSelected = item.value == member.role; + return item; + }); + const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0]; + return ( member-header {member.name} - - {!this.props.isItemFreezed ? - - - : - } + + + ); } diff --git a/frontend/src/pages/org-admin/org-user-item.js b/frontend/src/pages/org-admin/org-user-item.js index 3ceb17da13..fda8989755 100644 --- a/frontend/src/pages/org-admin/org-user-item.js +++ b/frontend/src/pages/org-admin/org-user-item.js @@ -5,7 +5,7 @@ import { gettext, siteRoot, orgID, username } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; -import UserStatusEditor from '../../components/select-editor/user-status-editor'; +import Selector from '../../components/single-selector'; const propTypes = { user: PropTypes.object, @@ -15,6 +15,7 @@ const propTypes = { toggleDelete: PropTypes.func.isRequired, onFreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired, + toggleItemFreezed: PropTypes.func.isRequired, changeStatus: PropTypes.func.isRequired, }; @@ -25,11 +26,8 @@ class UserItem extends React.Component { this.state = { highlight: false, showMenu: false, - currentStatus: this.props.user.is_active ? 'active' : 'inactive', isItemMenuShow: false }; - - this.statusArray = ['active', 'inactive']; } onMouseEnter = () => { @@ -78,8 +76,8 @@ class UserItem extends React.Component { this.props.toggleRevokeAdmin(email); }; - changeStatus = (value) => { - const isActive = value == 'active'; + changeStatus = (statusOption) => { + const isActive = statusOption.value == 'active'; if (isActive) { toaster.notify(gettext('It may take some time, please wait.')); } @@ -119,23 +117,44 @@ class UserItem extends React.Component { } }; + translateStatus = (status) => { + switch (status) { + case 'active': + return gettext('Active'); + case 'inactive': + return gettext('Inactive'); + } + }; + render() { + const { highlight } = this.state; let { user, currentTab } = this.props; let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/'; let isOperationMenuShow = (user.email !== username) && this.state.showMenu; - let isEditIconShow = isOperationMenuShow; + + // for 'user status' + const curStatus = user.is_active ? 'active' : 'inactive'; + this.statusOptions = ['active', 'inactive'].map(item => { + return { + value: item, + text: this.translateStatus(item), + isSelected: item == curStatus + }; + }); + const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0]; + return ( {user.name} - {`${Utils.formatSize({bytes: user.quota_usage})} / ${this.getQuotaTotal(user.quota_total)}`} diff --git a/frontend/src/pages/org-admin/org-users-admins.js b/frontend/src/pages/org-admin/org-users-admins.js index afd2d7475f..f0c0deac4c 100644 --- a/frontend/src/pages/org-admin/org-users-admins.js +++ b/frontend/src/pages/org-admin/org-users-admins.js @@ -75,6 +75,22 @@ class OrgUsers extends Component { this.toggleAddOrgAdmin(); }; + changeStatus = (email, isActive) => { + seafileAPI.orgAdminChangeOrgUserStatus(orgID, email, isActive).then(res => { + let users = this.state.orgAdminUsers.map(item => { + if (item.email == email) { + item['is_active']= res.data['is_active']; + } + return item; + }); + this.setState({orgAdminUsers: users}); + toaster.success(gettext('Edit succeeded.')); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + }; + render() { const topBtn = 'btn btn-secondary operation-item'; let topbarChildren; @@ -103,6 +119,7 @@ class OrgUsers extends Component { toggleRevokeAdmin={this.toggleRevokeAdmin} orgAdminUsers={this.state.orgAdminUsers} initOrgAdmin={this.initOrgAdmin} + changeStatus={this.changeStatus} />
diff --git a/frontend/src/pages/org-admin/org-users-list.js b/frontend/src/pages/org-admin/org-users-list.js index 11dadb952c..f42bf49156 100644 --- a/frontend/src/pages/org-admin/org-users-list.js +++ b/frontend/src/pages/org-admin/org-users-list.js @@ -10,7 +10,7 @@ const propTypes = { orgUsers: PropTypes.array.isRequired, page: PropTypes.number.isRequired, pageNext: PropTypes.bool.isRequired, - sortByQuotaUsage: PropTypes.string.isRequired, + sortByQuotaUsage: PropTypes.func.isRequired, sortOrder: PropTypes.string.isRequired, sortBy: PropTypes.string.isRequired, }; @@ -32,6 +32,10 @@ class OrgUsersList extends React.Component { this.setState({isItemFreezed: false}); }; + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + onChangePageNum = (e, num) => { e.preventDefault(); let page = this.props.page; @@ -86,6 +90,7 @@ class OrgUsersList extends React.Component { changeStatus={this.props.changeStatus} onFreezedItem={this.onFreezedItem} onUnfreezedItem={this.onUnfreezedItem} + toggleItemFreezed={this.toggleItemFreezed} /> );})} diff --git a/frontend/src/pages/share-admin/share-links.js b/frontend/src/pages/share-admin/share-links.js index 70090d4a01..488fb367f3 100644 --- a/frontend/src/pages/share-admin/share-links.js +++ b/frontend/src/pages/share-admin/share-links.js @@ -7,7 +7,6 @@ import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants'; import ShareLink from '../../models/share-link'; -import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; import Loading from '../../components/loading'; import toaster from '../../components/toast'; import EmptyTip from '../../components/empty-tip'; @@ -16,6 +15,7 @@ import ShareAdminLink from '../../components/dialog/share-admin-link'; import SortOptionsDialog from '../../components/dialog/sort-options'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import TopToolbar from '../../components/toolbar/top-toolbar'; +import Selector from '../../components/single-selector'; const contentPropTypes = { loading: PropTypes.bool.isRequired, @@ -29,6 +29,17 @@ const contentPropTypes = { class Content extends Component { + constructor(props) { + super(props); + this.state = { + isItemFreezed: false + }; + } + + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + sortByName = (e) => { e.preventDefault(); const sortBy = 'name'; @@ -67,7 +78,7 @@ class Content extends Component { // only for some columns const columnWidths = isPro ? ['14%', '7%', '14%'] : ['21%', '14%', '20%']; const table = ( - +
{isDesktop ? ( @@ -89,7 +100,15 @@ class Content extends Component { {items.map((item, index) => { - return (); + return ( + ); })}
@@ -105,7 +124,9 @@ Content.propTypes = contentPropTypes; const itemPropTypes = { item: PropTypes.object.isRequired, isDesktop: PropTypes.bool.isRequired, - onRemoveLink: PropTypes.func.isRequired + onRemoveLink: PropTypes.func.isRequired, + isItemFreezed: PropTypes.bool.isRequired, + toggleItemFreezed: PropTypes.func.isRequired }; class Item extends Component { @@ -114,6 +135,7 @@ class Item extends Component { super(props); this.state = { + highlight: false, isOpIconShown: false, isOpMenuOpen: false, // for mobile isPermSelectDialogOpen: false, // for mobile @@ -159,12 +181,22 @@ class Item extends Component { }); }; - handleMouseOver = () => { - this.setState({isOpIconShown: true}); + handleMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: true, + highlight: true + }); + } }; - handleMouseOut = () => { - this.setState({isOpIconShown: false}); + handleMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: false, + highlight: false + }); + } }; viewLink = (e) => { @@ -187,6 +219,11 @@ class Item extends Component { return ({expire_date}); }; + // for 'selector' in desktop + changePermission = (permOption) => { + this.changePerm(permOption.value); + }; + changePerm = (permission) => { const item = this.props.item; const permissionDetails = Utils.getShareLinkPermissionObject(permission).permissionDetails; @@ -205,6 +242,14 @@ class Item extends Component { render() { const item = this.props.item; const { currentPermission, permissionOptions , isOpIconShown, isPermSelectDialogOpen, isLinkDialogOpen } = this.state; + this.permOptions = permissionOptions.map(item => { + return { + value: item, + text: Utils.getShareLinkPermissionObject(item).text, + isSelected: item == currentPermission + }; + }); + const currentSelectedPermOption = this.permOptions.filter(item => item.isSelected)[0] || {}; let iconUrl, objUrl; if (item.is_dir) { @@ -218,7 +263,7 @@ class Item extends Component { const deletedTip = item.obj_id === '' ? {gettext('(deleted)')} : null; const desktopItem = ( - + {item.is_dir ? @@ -229,15 +274,15 @@ class Item extends Component { {item.repo_name} {isPro && - - - + + + } {item.view_cnt} {this.renderExpiration()} diff --git a/frontend/src/pages/sys-admin/departments/member-item.js b/frontend/src/pages/sys-admin/departments/member-item.js index 41798c37b2..bf9841b85a 100644 --- a/frontend/src/pages/sys-admin/departments/member-item.js +++ b/frontend/src/pages/sys-admin/departments/member-item.js @@ -2,8 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { seafileAPI } from '../../../utils/seafile-api'; import { Utils } from '../../../utils/utils'; +import { gettext } from '../../../utils/constants'; import toaster from '../../../components/toast'; -import RoleEditor from '../../../components/select-editor/role-editor'; +import RoleSelector from '../../../components/single-selector'; import UserLink from '../user-link'; const MemberItemPropTypes = { @@ -20,10 +21,12 @@ class MemberItem extends React.Component { constructor(props) { super(props); this.state = { - highlight: false, - showRoleMenu: false, + highlight: false }; - this.roles = ['Admin', 'Member']; + this.roleOptions = [ + { value: 'Admin', text: gettext('Admin'), isSelected: false }, + { value: 'Member', text: gettext('Member'), isSelected: false } + ]; } onMouseEnter = () => { @@ -36,12 +39,8 @@ class MemberItem extends React.Component { this.setState({ highlight: false }); }; - toggleMemberRoleMenu = () => { - this.setState({ showRoleMenu: !this.state.showRoleMenu }); - }; - - onChangeUserRole = (role) => { - let isAdmin = role === 'Admin' ? true : false; + onChangeUserRole = (roleOption) => { + let isAdmin = roleOption.value === 'Admin' ? true : false; seafileAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => { this.props.onMemberChanged(); }).catch(error => { @@ -57,25 +56,27 @@ class MemberItem extends React.Component { const member = this.props.member; const highlight = this.state.highlight; if (member.role === 'Owner') return null; + this.roleOptions = this.roleOptions.map(item => { + item.isSelected = item.value == member.role; + return item; + }); + const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0]; return ( member-header - - {!this.props.isItemFreezed ? - - - : - } + + + ); } diff --git a/frontend/src/pages/sys-admin/groups/group-members.js b/frontend/src/pages/sys-admin/groups/group-members.js index 28cbd839e0..5ab15f9e0b 100644 --- a/frontend/src/pages/sys-admin/groups/group-members.js +++ b/frontend/src/pages/sys-admin/groups/group-members.js @@ -10,7 +10,7 @@ import Loading from '../../../components/loading'; import Paginator from '../../../components/paginator'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import SysAdminGroupAddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog'; -import SysAdminGroupRoleEditor from '../../../components/select-editor/sysadmin-group-role-editor'; +import RoleSelector from '../../../components/single-selector'; import MainPanelTopbar from '../main-panel-topbar'; import UserLink from '../user-link'; import GroupNav from './group-nav'; @@ -19,8 +19,15 @@ class Content extends Component { constructor(props) { super(props); + this.state = { + isItemFreezed: false + }; } + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + getPreviousPageList = () => { this.props.getListByPage(this.props.pageInfo.current_page - 1); }; @@ -43,7 +50,7 @@ class Content extends Component { ); const table = ( - +
@@ -57,6 +64,8 @@ class Content extends Component { return (); @@ -96,18 +105,24 @@ class Item extends Component { constructor(props) { super(props); + this.roleOptions = [ + { value: 'Admin', text: gettext('Admin'), isSelected: false }, + { value: 'Member', text: gettext('Member'), isSelected: false } + ]; this.state = { - isOpIconShown: false, + highlighted: false, isDeleteDialogOpen: false }; } handleMouseEnter = () => { - this.setState({isOpIconShown: true}); + if (this.props.isItemFreezed) return; + this.setState({highlighted: true}); }; handleMouseLeave = () => { - this.setState({isOpIconShown: false}); + if (this.props.isItemFreezed) return; + this.setState({highlighted: false}); }; toggleDeleteDialog = (e) => { @@ -123,37 +138,44 @@ class Item extends Component { this.toggleDeleteDialog(); }; - updateMemberRole = (role) => { - this.props.updateMemberRole(this.props.item.email, role); + updateMemberRole = (roleOption) => { + this.props.updateMemberRole(this.props.item.email, roleOption.value); }; render() { - let { isOpIconShown, isDeleteDialogOpen } = this.state; + let { highlighted, isDeleteDialogOpen } = this.state; let { item } = this.props; let itemName = '' + Utils.HTMLescape(item.name) + ''; let dialogMsg = gettext('Are you sure you want to remove {placeholder} ?').replace('{placeholder}', itemName); + const { role: curRole } = item; + this.roleOptions = this.roleOptions.map(item => { + item.isSelected = item.value == curRole; + return item; + }); + const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0]; + return ( - + @@ -175,6 +197,8 @@ Item.propTypes = { item: PropTypes.object.isRequired, removeMember: PropTypes.func.isRequired, updateMemberRole: PropTypes.func.isRequired, + isItemFreezed: PropTypes.bool.isRequired, + toggleItemFreezed: PropTypes.func.isRequired }; class GroupMembers extends Component { diff --git a/frontend/src/pages/sys-admin/orgs/org-users.js b/frontend/src/pages/sys-admin/orgs/org-users.js index ce7c761cb5..d63efd8b50 100644 --- a/frontend/src/pages/sys-admin/orgs/org-users.js +++ b/frontend/src/pages/sys-admin/orgs/org-users.js @@ -8,7 +8,7 @@ import { gettext, username } from '../../../utils/constants'; import toaster from '../../../components/toast'; import EmptyTip from '../../../components/empty-tip'; import Loading from '../../../components/loading'; -import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor'; +import Selector from '../../../components/single-selector'; import SysAdminUserMembershipEditor from '../../../components/select-editor/sysadmin-user-membership-editor'; import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; @@ -26,6 +26,10 @@ class Content extends Component { }; } + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + onFreezedItem = () => { this.setState({isItemFreezed: true}); }; @@ -67,6 +71,7 @@ class Content extends Component { isItemFreezed={this.state.isItemFreezed} onFreezedItem={this.onFreezedItem} onUnfreezedItem={this.onUnfreezedItem} + toggleItemFreezed={this.toggleItemFreezed} updateStatus={this.props.updateStatus} updateMembership={this.props.updateMembership} deleteUser={this.props.deleteUser} @@ -154,8 +159,8 @@ class Item extends Component { this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen}); }; - updateStatus= (statusValue) => { - this.props.updateStatus(this.props.item.email, statusValue); + updateStatus= (statusOption) => { + this.props.updateStatus(this.props.item.email, statusOption.value); }; updateMembership= (membershipValue) => { @@ -190,25 +195,45 @@ class Item extends Component { return translateResult; }; + translateStatus = (status) => { + switch (status) { + case 'active': + return gettext('Active'); + case 'inactive': + return gettext('Inactive'); + } + }; + render() { const { item } = this.props; - const { isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state; + const { highlight, isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state; const itemName = '' + Utils.HTMLescape(item.name) + ''; let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); + // for 'user status' + const curStatus = item.active ? 'active' : 'inactive'; + this.statusOptions = ['active', 'inactive'].map(item => { + return { + value: item, + text: this.translateStatus(item), + isSelected: item == curStatus + }; + }); + const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0]; + return (
{/* icon */}
{item.role == 'Owner' ? gettext('Owner') : - } {item.role != 'Owner' && - + }
- @@ -264,6 +289,7 @@ Item.propTypes = { isItemFreezed: PropTypes.bool.isRequired, onFreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired, + toggleItemFreezed: PropTypes.func.isRequired, updateStatus: PropTypes.func.isRequired, updateMembership: PropTypes.func.isRequired, deleteUser: PropTypes.func.isRequired, diff --git a/frontend/src/pages/sys-admin/orgs/orgs-content.js b/frontend/src/pages/sys-admin/orgs/orgs-content.js index 586d9a584a..0ef3cd757a 100644 --- a/frontend/src/pages/sys-admin/orgs/orgs-content.js +++ b/frontend/src/pages/sys-admin/orgs/orgs-content.js @@ -8,7 +8,7 @@ import EmptyTip from '../../../components/empty-tip'; import Loading from '../../../components/loading'; import Paginator from '../../../components/paginator'; import { seafileAPI } from '../../../utils/seafile-api'; -import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor'; +import RoleSelector from '../../../components/single-selector'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import UserLink from '../user-link'; import toaster from '../../../components/toast'; @@ -17,6 +17,17 @@ const { availableRoles } = window.sysadmin.pageOptions; class Content extends Component { + constructor(props) { + super(props); + this.state = { + isItemFreezed: false + }; + } + + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + getPreviousPage = () => { this.props.getListByPage(this.props.currentPage - 1); }; @@ -39,7 +50,7 @@ class Content extends Component { ); const table = ( - +
@@ -57,6 +68,8 @@ class Content extends Component { item={item} updateRole={this.props.updateRole} deleteOrg={this.props.deleteOrg} + isItemFreezed={this.state.isItemFreezed} + toggleItemFreezed={this.toggleItemFreezed} />); })} @@ -81,7 +94,6 @@ class Content extends Component { Content.propTypes = { loading: PropTypes.bool.isRequired, errorMsg: PropTypes.string.isRequired, - item: PropTypes.object.isRequired, getListByPage: PropTypes.func.isRequired, currentPage: PropTypes.number, items: PropTypes.array.isRequired, @@ -97,18 +109,20 @@ class Item extends Component { constructor(props) { super(props); this.state = { - isOpIconShown: false, + highlighted: false, isDeleteDialogOpen: false, deleteDialogMsg: '', }; } handleMouseEnter = () => { - this.setState({isOpIconShown: true}); + if (this.props.isItemFreezed) return; + this.setState({highlighted: true}); }; handleMouseLeave = () => { - this.setState({isOpIconShown: false}); + if (this.props.isItemFreezed) return; + this.setState({highlighted: false}); }; toggleDeleteDialog = (e) => { @@ -135,8 +149,19 @@ class Item extends Component { }); }; - updateRole = (role) => { - this.props.updateRole(this.props.item.org_id, role); + translateRole = (role) => { + switch (role) { + case 'default': + return gettext('Default'); + case 'guest': + return gettext('Guest'); + default: + return role; + } + }; + + updateRole = (roleOption) => { + this.props.updateRole(this.props.item.org_id, roleOption.value); }; deleteOrg = () => { @@ -146,28 +171,38 @@ class Item extends Component { render() { const { item } = this.props; - const { isOpIconShown, isDeleteDialogOpen, deleteDialogMsg } = this.state; + const { highlighted, isDeleteDialogOpen, deleteDialogMsg } = this.state; + + const { role: curRole } = item; + this.roleOptions = availableRoles.map(item => { + return { + value: item, + text: this.translateRole(item), + isSelected: item == curRole + }; + }); + const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0]; return ( - + {isDeleteDialogOpen && @@ -188,6 +223,8 @@ Item.propTypes = { item: PropTypes.object.isRequired, updateRole: PropTypes.func.isRequired, deleteOrg: PropTypes.func.isRequired, + isItemFreezed: PropTypes.bool.isRequired, + toggleItemFreezed: PropTypes.func.isRequired }; export default Content; diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js index 239c456ea0..2e66253338 100644 --- a/frontend/src/pages/sys-admin/users/users-content.js +++ b/frontend/src/pages/sys-admin/users/users-content.js @@ -9,9 +9,7 @@ import toaster from '../../../components/toast'; import EmptyTip from '../../../components/empty-tip'; import Loading from '../../../components/loading'; import Paginator from '../../../components/paginator'; -import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor'; -import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor'; -import SelectEditor from '../../../components/select-editor/select-editor'; +import Selector from '../../../components/single-selector'; import OpMenu from '../../../components/dialog/op-menu'; import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; @@ -28,6 +26,10 @@ class Content extends Component { }; } + toggleItemFreezed = (isFreezed) => { + this.setState({ isItemFreezed: isFreezed }); + }; + onFreezedItem = () => { this.setState({isItemFreezed: true}); }; @@ -133,6 +135,7 @@ class Content extends Component { isItemFreezed={this.state.isItemFreezed} onFreezedItem={this.onFreezedItem} onUnfreezedItem={this.onUnfreezedItem} + toggleItemFreezed={this.toggleItemFreezed} updateUser={this.props.updateUser} deleteUser={this.props.deleteUser} updateAdminRole={this.props.updateAdminRole} @@ -247,20 +250,31 @@ class Item extends Component { this.props.onUserSelected(this.props.item); }; - updateStatus= (value) => { - const isActive = value == 'active'; + updateStatus= (roleOption) => { + const isActive = roleOption.value == 'active'; if (isActive) { toaster.notify(gettext('It may take some time, please wait.')); } this.props.updateUser(this.props.item.email, 'is_active', isActive); }; - updateRole = (value) => { - this.props.updateUser(this.props.item.email, 'role', value); + updateRole = (roleOption) => { + this.props.updateUser(this.props.item.email, 'role', roleOption.value); }; - updateAdminRole = (value) => { - this.props.updateAdminRole(this.props.item.email, value); + updateAdminRole = (roleOption) => { + this.props.updateAdminRole(this.props.item.email, roleOption.value); + }; + + translateRole = (role) => { + switch (role) { + case 'default': + return gettext('Default'); + case 'guest': + return gettext('Guest'); + default: + return role; + } }; translateAdminRole = (role) => { @@ -278,12 +292,17 @@ class Item extends Component { } }; - updateInstitution = (value) => { - this.props.updateUser(this.props.item.email, 'institution', value); + translateStatus = (status) => { + switch (status) { + case 'active': + return gettext('Active'); + case 'inactive': + return gettext('Inactive'); + } }; - translateInstitution = (inst) => { - return inst; + updateInstitution = (instOption) => { + this.props.updateUser(this.props.item.email, 'institution', instOption.value); }; updateQuota = (value) => { @@ -362,6 +381,7 @@ class Item extends Component { render() { const { item, isAdmin } = this.props; const { + highlight, isOpIconShown, isSetQuotaDialogOpen, isDeleteUserDialogOpen, @@ -374,6 +394,54 @@ class Item extends Component { const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName); + // for 'user status' + const curStatus = item.is_active ? 'active' : 'inactive'; + this.statusOptions = ['active', 'inactive'].map(item => { + return { + value: item, + text: this.translateStatus(item), + isSelected: item == curStatus + }; + }); + const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0]; + + let currentSelectedAdminRoleOption; + let currentSelectedRoleOption; + if (isAdmin) { + const { admin_role: curAdminRole } = item; + this.adminRoleOptions = availableAdminRoles.map(item => { + return { + value: item, + text: this.translateAdminRole(item), + isSelected: item == curAdminRole + }; + }); + currentSelectedAdminRoleOption = this.adminRoleOptions.filter(item => item.isSelected)[0]; + } else { + const { role: curRole } = item; + this.roleOptions = availableRoles.map(item => { + return { + value: item, + text: this.translateRole(item), + isSelected: item == curRole + }; + }); + currentSelectedRoleOption = this.roleOptions.filter(item => item.isSelected)[0]; + } + + let currentSelectedInstOption; + if (multiInstitution && !isAdmin) { + const { institution: curInstitution } = item; + this.instOptions = institutions.map(item => { + return { + value: item, + text: item, + isSelected: item == curInstitution + }; + }); + currentSelectedInstOption = this.instOptions.filter(item => item.isSelected)[0]; + } + return ( @@ -395,31 +463,31 @@ class Item extends Component { } {isPro && @@ -434,13 +502,12 @@ class Item extends Component { {(multiInstitution && !isAdmin) && } @@ -508,6 +575,7 @@ Item.propTypes = { isLDAPImported: PropTypes.bool, onFreezedItem: PropTypes.func, onUnfreezedItem: PropTypes.func, + toggleItemFreezed: PropTypes.func.isRequired, updateUser: PropTypes.func, deleteUser: PropTypes.func, updateAdminRole: PropTypes.func,
{gettext('Name')}
{item.org_name} - {`${Utils.bytesToSize(item.quota_usage)} / ${item.quota > 0 ? Utils.bytesToSize(item.quota) : '--'}`} {moment(item.ctime).format('YYYY-MM-DD HH:mm:ss')} - +
- {isAdmin ? - : - + : + } - 0} - options={institutions} - currentOption={item.institution} - onOptionChanged={this.updateInstitution} - translateOption={this.translateInstitution} + 0} + currentSelectedOption={currentSelectedInstOption} + options={this.instOptions} + selectOption={this.updateInstitution} + toggleItemFreezed={this.props.toggleItemFreezed} />