1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-12 21:30:39 +00:00

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'
This commit is contained in:
llj
2023-11-24 18:21:46 +08:00
committed by GitHub
parent 663a78061b
commit 29fa239cd0
21 changed files with 569 additions and 395 deletions

View File

@@ -4,7 +4,7 @@ import { Table } from 'reactstrap';
import { Utils } from '../utils/utils'; import { Utils } from '../utils/utils';
import { gettext } from '../utils/constants'; import { gettext } from '../utils/constants';
import { seafileAPI } from '../utils/seafile-api'; import { seafileAPI } from '../utils/seafile-api';
import RoleEditor from './select-editor/role-editor'; import RoleSelector from './single-selector';
import toaster from './toast'; import toaster from './toast';
import OpIcon from './op-icon'; import OpIcon from './op-icon';
@@ -70,14 +70,17 @@ class Member extends React.PureComponent {
constructor(props) { constructor(props) {
super(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 = ({ this.state = ({
highlight: false, highlight: false,
}); });
} }
onChangeUserRole = (role) => { onChangeUserRole = (roleOption) => {
let isAdmin = role === 'Admin' ? 'True' : 'False'; let isAdmin = roleOption.value === 'Admin' ? 'True' : 'False';
seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => { seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => {
this.props.changeMember(res.data); this.props.changeMember(res.data);
}).catch(error => { }).catch(error => {
@@ -124,8 +127,17 @@ class Member extends React.PureComponent {
}; };
render() { render() {
const { highlight } = this.state;
const { item, isOwner } = this.props; const { item, isOwner } = this.props;
const deleteAuthority = (item.role !== 'Owner' && isOwner === true) || (item.role === 'Member' && isOwner === false); 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( return(
<tr onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseLeave} className={this.state.highlight ? 'tr-highlight' : ''} tabIndex="0" onFocus={this.handleMouseOver}> <tr onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseLeave} className={this.state.highlight ? 'tr-highlight' : ''} tabIndex="0" onFocus={this.handleMouseOver}>
<th scope="row"><img className="avatar" src={item.avatar_url} alt=""/></th> <th scope="row"><img className="avatar" src={item.avatar_url} alt=""/></th>
@@ -135,12 +147,11 @@ class Member extends React.PureComponent {
<span className="group-admin">{this.translateRole(item.role)}</span> <span className="group-admin">{this.translateRole(item.role)}</span>
} }
{(isOwner === true && item.role !== 'Owner') && {(isOwner === true && item.role !== 'Owner') &&
<RoleEditor <RoleSelector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={this.state.highlight} currentSelectedOption={currentSelectedOption}
currentRole={item.role} options={this.roleOptions}
roles={this.roles} selectOption={this.onChangeUserRole}
onRoleChanged={this.onChangeUserRole}
toggleItemFreezed={this.props.toggleItemFreezed} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
} }

View File

@@ -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 (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.roles}
currentOption={this.props.currentRole}
onOptionChanged={this.props.onRoleChanged}
translateOption={this.translateRole}
toggleItemFreezed={this.props.toggleItemFreezed}
/>
);
}
}
RoleEditor.propTypes = propTypes;
export default RoleEditor;

View File

@@ -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 (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.permissionOptions}
currentOption={this.props.currentPermission}
onOptionChanged={this.props.onPermissionChanged}
translateOption={this.translatePermission}
/>
);
}
}
ShareLinkPermissionEditor.propTypes = propTypes;
export default ShareLinkPermissionEditor;

View File

@@ -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 (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.roleOptions}
currentOption={this.props.currentRole}
onOptionChanged={this.props.onRoleChanged}
translateOption={this.translateRoles}
/>
);
}
}
SysAdminGroupRoleEditor.propTypes = propTypes;
export default SysAdminGroupRoleEditor;

View File

@@ -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 (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.statusOptions}
currentOption={this.props.currentStatus}
onOptionChanged={this.props.onStatusChanged}
translateOption={this.translateStatus}
/>
);
}
}
SysAdminUserStatusEditor.propTypes = propTypes;
export default SysAdminUserStatusEditor;

View File

@@ -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 (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.statusArray}
currentOption={this.props.currentStatus}
onOptionChanged={this.props.onStatusChanged}
translateOption={this.translateStatus}
/>
);
}
}
UserStatusEditor.propTypes = propTypes;
export default UserStatusEditor;

View File

@@ -4,7 +4,7 @@ import moment from 'moment';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, canSendShareLinkEmail } from '../../utils/constants'; 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 CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
@@ -119,9 +119,9 @@ class LinkDetails extends React.Component {
this.setState({isOpIconShown: false}); this.setState({isOpIconShown: false});
}; };
changePerm = (permission) => { changePerm = (permOption) => {
const { sharedLinkInfo } = this.props; const { sharedLinkInfo } = this.props;
const { permissionDetails } = Utils.getShareLinkPermissionObject(permission); const { permissionDetails } = Utils.getShareLinkPermissionObject(permOption.value);
seafileAPI.updateShareLink(sharedLinkInfo.token, JSON.stringify(permissionDetails)).then((res) => { seafileAPI.updateShareLink(sharedLinkInfo.token, JSON.stringify(permissionDetails)).then((res) => {
this.props.updateLink(new ShareLink(res.data)); this.props.updateLink(new ShareLink(res.data));
}).catch((error) => { }).catch((error) => {
@@ -152,6 +152,15 @@ class LinkDetails extends React.Component {
const { sharedLinkInfo, permissionOptions } = this.props; const { sharedLinkInfo, permissionOptions } = this.props;
const { isOpIconShown } = this.state; const { isOpIconShown } = this.state;
const currentPermission = Utils.getShareLinkPermissionStr(sharedLinkInfo.permissions); 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 ( return (
<div> <div>
<button className="fa fa-arrow-left back-icon border-0 bg-transparent text-secondary p-0" onClick={this.goBack} title={gettext('Back')} aria-label={gettext('Back')}></button> <button className="fa fa-arrow-left back-icon border-0 bg-transparent text-secondary p-0" onClick={this.goBack} title={gettext('Back')} aria-label={gettext('Back')}></button>
@@ -231,12 +240,11 @@ class LinkDetails extends React.Component {
<> <>
<dt className="text-secondary font-weight-normal">{gettext('Permission:')}</dt> <dt className="text-secondary font-weight-normal">{gettext('Permission:')}</dt>
<dd style={{width:'250px'}} onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}> <dd style={{width:'250px'}} onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<ShareLinkPermissionEditor <Selector
isTextMode={true} isDropdownToggleShown={isOpIconShown && !sharedLinkInfo.is_expired}
isEditIconShow={isOpIconShown && !sharedLinkInfo.is_expired} currentSelectedOption={currentSelectedPermOption}
currentPermission={currentPermission} options={this.permOptions}
permissionOptions={permissionOptions} selectOption={this.changePerm}
onPermissionChanged={this.changePerm}
/> />
</dd> </dd>
</> </>

View File

@@ -4,7 +4,6 @@ import moment from 'moment';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import toaster from '../toast'; import toaster from '../toast';
import { isPro, gettext } from '../../utils/constants'; import { isPro, gettext } from '../../utils/constants';
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
@@ -105,15 +104,7 @@ class LinkItem extends React.Component {
{this.cutLink(link)} {this.cutLink(link)}
</td> </td>
<td> <td>
{(isPro && permissions) && ( {(isPro && permissions) && Utils.getShareLinkPermissionObject(currentPermission).text}
<ShareLinkPermissionEditor
isTextMode={true}
isEditIconShow={false}
currentPermission={currentPermission}
permissionOptions={permissionOptions}
onPermissionChanged={() => {}}
/>
)}
</td> </td>
<td> <td>
{expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'} {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}

View File

@@ -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 (
<div className="sf-single-selector position-relative">
<span className="cur-option" onClick={this.onToggleClick}>
{currentSelectedOption.text}
{isDropdownToggleShown && <i className="fas fa-caret-down ml-2 toggle-icon"></i>}
</span>
{isPopoverOpen && (
<div className="options-container position-absolute rounded shadow mt-1" ref={ref => this.selector = ref}>
<ul className="option-list list-unstyled p-3 o-auto">
{options.map((item, index) => {
return (
<li key={index} className="option-item h-6 p-1 rounded d-flex justify-content-between align-items-center" onClick={(e) => {this.selectItem(e, item);}}>
<span className="option-item-text flex-shrink-0 mr-3">{item.text}</span>
<i className={`sf2-icon-tick text-gray font-weight-bold ${item.isSelected ? '' : 'invisible'}`}></i>
</li>
);
})}
</ul>
</div>
)}
</div>
);
}
}
Selector.propTypes = propTypes;
export default Selector;

View File

@@ -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;
}

View File

@@ -10,6 +10,7 @@ const propTypes = {
toggleDelete: PropTypes.func.isRequired, toggleDelete: PropTypes.func.isRequired,
toggleRevokeAdmin: PropTypes.func.isRequired, toggleRevokeAdmin: PropTypes.func.isRequired,
orgAdminUsers: PropTypes.array.isRequired, orgAdminUsers: PropTypes.array.isRequired,
changeStatus: PropTypes.func.isRequired,
initOrgAdmin: PropTypes.func.isRequired initOrgAdmin: PropTypes.func.isRequired
}; };
@@ -34,6 +35,10 @@ class OrgAdminList extends React.Component {
this.setState({isItemFreezed: false}); this.setState({isItemFreezed: false});
}; };
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
render() { render() {
let orgAdminUsers = this.props.orgAdminUsers; let orgAdminUsers = this.props.orgAdminUsers;
@@ -50,17 +55,19 @@ class OrgAdminList extends React.Component {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{orgAdminUsers.map(item => { {orgAdminUsers.map((item, index) => {
return ( return (
<UserItem <UserItem
key={item.index} key={index}
user={item} user={item}
currentTab="admins" currentTab="admins"
isItemFreezed={this.state.isItemFreezed} isItemFreezed={this.state.isItemFreezed}
toggleDelete={this.props.toggleDelete} toggleDelete={this.props.toggleDelete}
toggleRevokeAdmin={this.props.toggleRevokeAdmin} toggleRevokeAdmin={this.props.toggleRevokeAdmin}
changeStatus={this.props.changeStatus}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
toggleItemFreezed={this.toggleItemFreezed}
/> />
); );
})} })}

View File

@@ -7,7 +7,7 @@ import { Utils } from '../../utils/utils';
import toaster from '../../components/toast'; import toaster from '../../components/toast';
import MainPanelTopbar from './main-panel-topbar'; import MainPanelTopbar from './main-panel-topbar';
import ModalPortal from '../../components/modal-portal'; 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 AddDepartDialog from '../../components/dialog/org-add-department-dialog';
import AddMemberDialog from '../../components/dialog/org-add-member-dialog'; import AddMemberDialog from '../../components/dialog/org-add-member-dialog';
import DeleteMemberDialog from '../../components/dialog/org-delete-member-dialog'; import DeleteMemberDialog from '../../components/dialog/org-delete-member-dialog';
@@ -396,10 +396,12 @@ class MemberItem extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
highlight: false, highlight: false
showRoleMenu: false,
}; };
this.roles = ['Admin', 'Member']; this.roleOptions = [
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
{ value: 'Member', text: gettext('Member'), isSelected: false }
];
} }
onMouseEnter = () => { onMouseEnter = () => {
@@ -412,12 +414,8 @@ class MemberItem extends React.Component {
this.setState({ highlight: false }); this.setState({ highlight: false });
}; };
toggleMemberRoleMenu = () => { onChangeUserRole = (roleOption) => {
this.setState({ showRoleMenu: !this.state.showRoleMenu }); let isAdmin = roleOption.value === 'Admin' ? true : false;
};
onChangeUserRole = (role) => {
let isAdmin = role === 'Admin' ? true : false;
seafileAPI.orgAdminSetGroupMemberRole(orgID, this.props.groupID, this.props.member.email, isAdmin).then((res) => { seafileAPI.orgAdminSetGroupMemberRole(orgID, this.props.groupID, this.props.member.email, isAdmin).then((res) => {
this.props.onMemberChanged(); this.props.onMemberChanged();
}).catch(error => { }).catch(error => {
@@ -434,25 +432,29 @@ class MemberItem extends React.Component {
const highlight = this.state.highlight; const highlight = this.state.highlight;
let memberLink = serviceURL + '/org/useradmin/info/' + member.email + '/'; let memberLink = serviceURL + '/org/useradmin/info/' + member.email + '/';
if (member.role === 'Owner') return null; 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 ( return (
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td> <td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td>
<td><a href={memberLink}>{member.name}</a></td> <td><a href={memberLink}>{member.name}</a></td>
<td> <td>
<RoleEditor <RoleSelector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={highlight} currentSelectedOption={currentSelectedOption}
currentRole={member.role} options={this.roleOptions}
roles={this.roles} selectOption={this.onChangeUserRole}
onRoleChanged={this.onChangeUserRole}
toggleItemFreezed={this.props.toggleItemFreezed} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
{!this.props.isItemFreezed ?
<td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}> <td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}>
<span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span> <span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
</td> : <td></td> </td>
}
</tr> </tr>
); );
} }

View File

@@ -5,7 +5,7 @@ import { gettext, siteRoot, orgID, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import toaster from '../../components/toast'; import toaster from '../../components/toast';
import UserStatusEditor from '../../components/select-editor/user-status-editor'; import Selector from '../../components/single-selector';
const propTypes = { const propTypes = {
user: PropTypes.object, user: PropTypes.object,
@@ -15,6 +15,7 @@ const propTypes = {
toggleDelete: PropTypes.func.isRequired, toggleDelete: PropTypes.func.isRequired,
onFreezedItem: PropTypes.func.isRequired, onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired,
toggleItemFreezed: PropTypes.func.isRequired,
changeStatus: PropTypes.func.isRequired, changeStatus: PropTypes.func.isRequired,
}; };
@@ -25,11 +26,8 @@ class UserItem extends React.Component {
this.state = { this.state = {
highlight: false, highlight: false,
showMenu: false, showMenu: false,
currentStatus: this.props.user.is_active ? 'active' : 'inactive',
isItemMenuShow: false isItemMenuShow: false
}; };
this.statusArray = ['active', 'inactive'];
} }
onMouseEnter = () => { onMouseEnter = () => {
@@ -78,8 +76,8 @@ class UserItem extends React.Component {
this.props.toggleRevokeAdmin(email); this.props.toggleRevokeAdmin(email);
}; };
changeStatus = (value) => { changeStatus = (statusOption) => {
const isActive = value == 'active'; const isActive = statusOption.value == 'active';
if (isActive) { if (isActive) {
toaster.notify(gettext('It may take some time, please wait.')); 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() { render() {
const { highlight } = this.state;
let { user, currentTab } = this.props; let { user, currentTab } = this.props;
let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/'; let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/';
let isOperationMenuShow = (user.email !== username) && this.state.showMenu; 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 ( return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td> <td>
<a href={href}>{user.name}</a> <a href={href}>{user.name}</a>
</td> </td>
<td> <td>
<UserStatusEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={isEditIconShow} currentSelectedOption={currentSelectedStatusOption}
currentStatus={user.is_active ? 'active' : 'inactive'} options={this.statusOptions}
statusArray={this.statusArray} selectOption={this.changeStatus}
onStatusChanged={this.changeStatus} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
<td>{`${Utils.formatSize({bytes: user.quota_usage})} / ${this.getQuotaTotal(user.quota_total)}`}</td> <td>{`${Utils.formatSize({bytes: user.quota_usage})} / ${this.getQuotaTotal(user.quota_total)}`}</td>

View File

@@ -75,6 +75,22 @@ class OrgUsers extends Component {
this.toggleAddOrgAdmin(); 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() { render() {
const topBtn = 'btn btn-secondary operation-item'; const topBtn = 'btn btn-secondary operation-item';
let topbarChildren; let topbarChildren;
@@ -103,6 +119,7 @@ class OrgUsers extends Component {
toggleRevokeAdmin={this.toggleRevokeAdmin} toggleRevokeAdmin={this.toggleRevokeAdmin}
orgAdminUsers={this.state.orgAdminUsers} orgAdminUsers={this.state.orgAdminUsers}
initOrgAdmin={this.initOrgAdmin} initOrgAdmin={this.initOrgAdmin}
changeStatus={this.changeStatus}
/> />
</div> </div>
</div> </div>

View File

@@ -10,7 +10,7 @@ const propTypes = {
orgUsers: PropTypes.array.isRequired, orgUsers: PropTypes.array.isRequired,
page: PropTypes.number.isRequired, page: PropTypes.number.isRequired,
pageNext: PropTypes.bool.isRequired, pageNext: PropTypes.bool.isRequired,
sortByQuotaUsage: PropTypes.string.isRequired, sortByQuotaUsage: PropTypes.func.isRequired,
sortOrder: PropTypes.string.isRequired, sortOrder: PropTypes.string.isRequired,
sortBy: PropTypes.string.isRequired, sortBy: PropTypes.string.isRequired,
}; };
@@ -32,6 +32,10 @@ class OrgUsersList extends React.Component {
this.setState({isItemFreezed: false}); this.setState({isItemFreezed: false});
}; };
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
onChangePageNum = (e, num) => { onChangePageNum = (e, num) => {
e.preventDefault(); e.preventDefault();
let page = this.props.page; let page = this.props.page;
@@ -86,6 +90,7 @@ class OrgUsersList extends React.Component {
changeStatus={this.props.changeStatus} changeStatus={this.props.changeStatus}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
toggleItemFreezed={this.toggleItemFreezed}
/> />
);})} );})}
</tbody> </tbody>

View File

@@ -7,7 +7,6 @@ import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants'; import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants';
import ShareLink from '../../models/share-link'; import ShareLink from '../../models/share-link';
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
import Loading from '../../components/loading'; import Loading from '../../components/loading';
import toaster from '../../components/toast'; import toaster from '../../components/toast';
import EmptyTip from '../../components/empty-tip'; 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 SortOptionsDialog from '../../components/dialog/sort-options';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
import TopToolbar from '../../components/toolbar/top-toolbar'; import TopToolbar from '../../components/toolbar/top-toolbar';
import Selector from '../../components/single-selector';
const contentPropTypes = { const contentPropTypes = {
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
@@ -29,6 +29,17 @@ const contentPropTypes = {
class Content extends Component { class Content extends Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false
};
}
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
sortByName = (e) => { sortByName = (e) => {
e.preventDefault(); e.preventDefault();
const sortBy = 'name'; const sortBy = 'name';
@@ -67,7 +78,7 @@ class Content extends Component {
// only for some columns // only for some columns
const columnWidths = isPro ? ['14%', '7%', '14%'] : ['21%', '14%', '20%']; const columnWidths = isPro ? ['14%', '7%', '14%'] : ['21%', '14%', '20%'];
const table = ( const table = (
<table className={`table-hover ${isDesktop ? '': 'table-thead-hidden'}`}> <table className={`${isDesktop ? '': 'table-thead-hidden'}`}>
<thead> <thead>
{isDesktop ? ( {isDesktop ? (
<tr> <tr>
@@ -89,7 +100,15 @@ class Content extends Component {
</thead> </thead>
<tbody> <tbody>
{items.map((item, index) => { {items.map((item, index) => {
return (<Item key={index} isDesktop={isDesktop} item={item} onRemoveLink={this.props.onRemoveLink} />); return (<Item
key={index}
isDesktop={isDesktop}
item={item}
onRemoveLink={this.props.onRemoveLink}
isItemFreezed={this.state.isItemFreezed}
toggleItemFreezed={this.toggleItemFreezed}
/>
);
})} })}
</tbody> </tbody>
</table> </table>
@@ -105,7 +124,9 @@ Content.propTypes = contentPropTypes;
const itemPropTypes = { const itemPropTypes = {
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
isDesktop: PropTypes.bool.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 { class Item extends Component {
@@ -114,6 +135,7 @@ class Item extends Component {
super(props); super(props);
this.state = { this.state = {
highlight: false,
isOpIconShown: false, isOpIconShown: false,
isOpMenuOpen: false, // for mobile isOpMenuOpen: false, // for mobile
isPermSelectDialogOpen: false, // for mobile isPermSelectDialogOpen: false, // for mobile
@@ -159,12 +181,22 @@ class Item extends Component {
}); });
}; };
handleMouseOver = () => { handleMouseEnter = () => {
this.setState({isOpIconShown: true}); if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: true,
highlight: true
});
}
}; };
handleMouseOut = () => { handleMouseLeave = () => {
this.setState({isOpIconShown: false}); if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: false,
highlight: false
});
}
}; };
viewLink = (e) => { viewLink = (e) => {
@@ -187,6 +219,11 @@ class Item extends Component {
return (<span className={item.is_expired ? 'error' : ''} title={expire_time}>{expire_date}</span>); return (<span className={item.is_expired ? 'error' : ''} title={expire_time}>{expire_date}</span>);
}; };
// for 'selector' in desktop
changePermission = (permOption) => {
this.changePerm(permOption.value);
};
changePerm = (permission) => { changePerm = (permission) => {
const item = this.props.item; const item = this.props.item;
const permissionDetails = Utils.getShareLinkPermissionObject(permission).permissionDetails; const permissionDetails = Utils.getShareLinkPermissionObject(permission).permissionDetails;
@@ -205,6 +242,14 @@ class Item extends Component {
render() { render() {
const item = this.props.item; const item = this.props.item;
const { currentPermission, permissionOptions , isOpIconShown, isPermSelectDialogOpen, isLinkDialogOpen } = this.state; 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; let iconUrl, objUrl;
if (item.is_dir) { if (item.is_dir) {
@@ -218,7 +263,7 @@ class Item extends Component {
const deletedTip = item.obj_id === '' ? <span style={{color:'red'}}>{gettext('(deleted)')}</span> : null; const deletedTip = item.obj_id === '' ? <span style={{color:'red'}}>{gettext('(deleted)')}</span> : null;
const desktopItem = ( const desktopItem = (
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut} onFocus={this.handleMouseOver}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onFocus={this.handleMouseEnter}>
<td><img src={iconUrl} width="24" alt="" /></td> <td><img src={iconUrl} width="24" alt="" /></td>
<td> <td>
{item.is_dir ? {item.is_dir ?
@@ -230,12 +275,12 @@ class Item extends Component {
<td><Link to={`${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}/`}>{item.repo_name}</Link></td> <td><Link to={`${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}/`}>{item.repo_name}</Link></td>
{isPro && {isPro &&
<td> <td>
<ShareLinkPermissionEditor <Selector
isTextMode={true} isDropdownToggleShown={isOpIconShown && !item.is_expired}
isEditIconShow={isOpIconShown && !item.is_expired} currentSelectedOption={currentSelectedPermOption}
currentPermission={currentPermission} options={this.permOptions}
permissionOptions={permissionOptions} selectOption={this.changePermission}
onPermissionChanged={this.changePerm} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
} }

View File

@@ -2,8 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { seafileAPI } from '../../../utils/seafile-api'; import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils'; import { Utils } from '../../../utils/utils';
import { gettext } from '../../../utils/constants';
import toaster from '../../../components/toast'; import toaster from '../../../components/toast';
import RoleEditor from '../../../components/select-editor/role-editor'; import RoleSelector from '../../../components/single-selector';
import UserLink from '../user-link'; import UserLink from '../user-link';
const MemberItemPropTypes = { const MemberItemPropTypes = {
@@ -20,10 +21,12 @@ class MemberItem extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
highlight: false, highlight: false
showRoleMenu: false,
}; };
this.roles = ['Admin', 'Member']; this.roleOptions = [
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
{ value: 'Member', text: gettext('Member'), isSelected: false }
];
} }
onMouseEnter = () => { onMouseEnter = () => {
@@ -36,12 +39,8 @@ class MemberItem extends React.Component {
this.setState({ highlight: false }); this.setState({ highlight: false });
}; };
toggleMemberRoleMenu = () => { onChangeUserRole = (roleOption) => {
this.setState({ showRoleMenu: !this.state.showRoleMenu }); let isAdmin = roleOption.value === 'Admin' ? true : false;
};
onChangeUserRole = (role) => {
let isAdmin = role === 'Admin' ? true : false;
seafileAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => { seafileAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => {
this.props.onMemberChanged(); this.props.onMemberChanged();
}).catch(error => { }).catch(error => {
@@ -57,25 +56,27 @@ class MemberItem extends React.Component {
const member = this.props.member; const member = this.props.member;
const highlight = this.state.highlight; const highlight = this.state.highlight;
if (member.role === 'Owner') return null; 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 ( return (
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td> <td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td>
<td><UserLink email={member.email} name={member.name} /></td> <td><UserLink email={member.email} name={member.name} /></td>
<td> <td>
<RoleEditor <RoleSelector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={highlight} currentSelectedOption={currentSelectedOption}
currentRole={member.role} options={this.roleOptions}
roles={this.roles} selectOption={this.onChangeUserRole}
onRoleChanged={this.onChangeUserRole}
toggleItemFreezed={this.props.toggleItemFreezed} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
{!this.props.isItemFreezed ?
<td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}> <td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}>
<span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span> <span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
</td> : <td></td> </td>
}
</tr> </tr>
); );
} }

View File

@@ -10,7 +10,7 @@ import Loading from '../../../components/loading';
import Paginator from '../../../components/paginator'; import Paginator from '../../../components/paginator';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import SysAdminGroupAddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-group-add-member-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 MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link'; import UserLink from '../user-link';
import GroupNav from './group-nav'; import GroupNav from './group-nav';
@@ -19,8 +19,15 @@ class Content extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
isItemFreezed: false
};
} }
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
getPreviousPageList = () => { getPreviousPageList = () => {
this.props.getListByPage(this.props.pageInfo.current_page - 1); this.props.getListByPage(this.props.pageInfo.current_page - 1);
}; };
@@ -43,7 +50,7 @@ class Content extends Component {
); );
const table = ( const table = (
<Fragment> <Fragment>
<table className="table-hover"> <table>
<thead> <thead>
<tr> <tr>
<th width="5%">{/* icon */}</th> <th width="5%">{/* icon */}</th>
@@ -57,6 +64,8 @@ class Content extends Component {
return (<Item return (<Item
key={index} key={index}
item={item} item={item}
isItemFreezed={this.state.isItemFreezed}
toggleItemFreezed={this.toggleItemFreezed}
removeMember={this.props.removeMember} removeMember={this.props.removeMember}
updateMemberRole={this.props.updateMemberRole} updateMemberRole={this.props.updateMemberRole}
/>); />);
@@ -96,18 +105,24 @@ class Item extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.roleOptions = [
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
{ value: 'Member', text: gettext('Member'), isSelected: false }
];
this.state = { this.state = {
isOpIconShown: false, highlighted: false,
isDeleteDialogOpen: false isDeleteDialogOpen: false
}; };
} }
handleMouseEnter = () => { handleMouseEnter = () => {
this.setState({isOpIconShown: true}); if (this.props.isItemFreezed) return;
this.setState({highlighted: true});
}; };
handleMouseLeave = () => { handleMouseLeave = () => {
this.setState({isOpIconShown: false}); if (this.props.isItemFreezed) return;
this.setState({highlighted: false});
}; };
toggleDeleteDialog = (e) => { toggleDeleteDialog = (e) => {
@@ -123,37 +138,44 @@ class Item extends Component {
this.toggleDeleteDialog(); this.toggleDeleteDialog();
}; };
updateMemberRole = (role) => { updateMemberRole = (roleOption) => {
this.props.updateMemberRole(this.props.item.email, role); this.props.updateMemberRole(this.props.item.email, roleOption.value);
}; };
render() { render() {
let { isOpIconShown, isDeleteDialogOpen } = this.state; let { highlighted, isDeleteDialogOpen } = this.state;
let { item } = this.props; let { item } = this.props;
let itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>'; let itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
let dialogMsg = gettext('Are you sure you want to remove {placeholder} ?').replace('{placeholder}', itemName); 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 ( return (
<Fragment> <Fragment>
<tr onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <tr className={highlighted ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td><img src={item.avatar_url} alt="" className="rounded-circle" width="24" /></td> <td><img src={item.avatar_url} alt="" className="rounded-circle" width="24" /></td>
<td><UserLink email={item.email} name={item.name} /></td> <td><UserLink email={item.email} name={item.name} /></td>
<td> <td>
{item.role == 'Owner' ? {item.role == 'Owner' ?
gettext('Owner') : gettext('Owner') :
<SysAdminGroupRoleEditor <RoleSelector
isTextMode={true} isDropdownToggleShown={highlighted}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedOption}
roleOptions={['Member', 'Admin']} options={this.roleOptions}
currentRole={item.role} selectOption={this.updateMemberRole}
onRoleChanged={this.updateMemberRole} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
} }
</td> </td>
<td> <td>
{item.role != 'Owner' && {item.role != 'Owner' &&
<a href="#" className={`action-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Remove')} onClick={this.toggleDeleteDialog}></a> <a href="#" className={`action-icon sf2-icon-x3 ${highlighted ? '' : 'invisible'}`} title={gettext('Remove')} onClick={this.toggleDeleteDialog}></a>
} }
</td> </td>
</tr> </tr>
@@ -175,6 +197,8 @@ Item.propTypes = {
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
removeMember: PropTypes.func.isRequired, removeMember: PropTypes.func.isRequired,
updateMemberRole: PropTypes.func.isRequired, updateMemberRole: PropTypes.func.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
toggleItemFreezed: PropTypes.func.isRequired
}; };
class GroupMembers extends Component { class GroupMembers extends Component {

View File

@@ -8,7 +8,7 @@ import { gettext, username } from '../../../utils/constants';
import toaster from '../../../components/toast'; import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip'; import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading'; 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 SysAdminUserMembershipEditor from '../../../components/select-editor/sysadmin-user-membership-editor';
import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog'; import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-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 = () => { onFreezedItem = () => {
this.setState({isItemFreezed: true}); this.setState({isItemFreezed: true});
}; };
@@ -67,6 +71,7 @@ class Content extends Component {
isItemFreezed={this.state.isItemFreezed} isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
toggleItemFreezed={this.toggleItemFreezed}
updateStatus={this.props.updateStatus} updateStatus={this.props.updateStatus}
updateMembership={this.props.updateMembership} updateMembership={this.props.updateMembership}
deleteUser={this.props.deleteUser} deleteUser={this.props.deleteUser}
@@ -154,8 +159,8 @@ class Item extends Component {
this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen}); this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen});
}; };
updateStatus= (statusValue) => { updateStatus= (statusOption) => {
this.props.updateStatus(this.props.item.email, statusValue); this.props.updateStatus(this.props.item.email, statusOption.value);
}; };
updateMembership= (membershipValue) => { updateMembership= (membershipValue) => {
@@ -190,25 +195,45 @@ class Item extends Component {
return translateResult; return translateResult;
}; };
translateStatus = (status) => {
switch (status) {
case 'active':
return gettext('Active');
case 'inactive':
return gettext('Inactive');
}
};
render() { render() {
const { item } = this.props; const { item } = this.props;
const { isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state; const { highlight, isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state;
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>'; const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); 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); 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 ( return (
<Fragment> <Fragment>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td><UserLink email={item.email} name={item.name} /></td> <td><UserLink email={item.email} name={item.name} /></td>
<td> <td>
<SysAdminUserStatusEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedStatusOption}
currentStatus={item.active ? 'active' : 'inactive'} options={this.statusOptions}
statusOptions={['active', 'inactive']} selectOption={this.updateStatus}
onStatusChanged={this.updateStatus} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
<td> <td>
@@ -264,6 +289,7 @@ Item.propTypes = {
isItemFreezed: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired,
onFreezedItem: PropTypes.func.isRequired, onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired,
toggleItemFreezed: PropTypes.func.isRequired,
updateStatus: PropTypes.func.isRequired, updateStatus: PropTypes.func.isRequired,
updateMembership: PropTypes.func.isRequired, updateMembership: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired, deleteUser: PropTypes.func.isRequired,

View File

@@ -8,7 +8,7 @@ import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading'; import Loading from '../../../components/loading';
import Paginator from '../../../components/paginator'; import Paginator from '../../../components/paginator';
import { seafileAPI } from '../../../utils/seafile-api'; 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 CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import UserLink from '../user-link'; import UserLink from '../user-link';
import toaster from '../../../components/toast'; import toaster from '../../../components/toast';
@@ -17,6 +17,17 @@ const { availableRoles } = window.sysadmin.pageOptions;
class Content extends Component { class Content extends Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false
};
}
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
getPreviousPage = () => { getPreviousPage = () => {
this.props.getListByPage(this.props.currentPage - 1); this.props.getListByPage(this.props.currentPage - 1);
}; };
@@ -39,7 +50,7 @@ class Content extends Component {
); );
const table = ( const table = (
<Fragment> <Fragment>
<table className="table-hover"> <table>
<thead> <thead>
<tr> <tr>
<th width="20%">{gettext('Name')}</th> <th width="20%">{gettext('Name')}</th>
@@ -57,6 +68,8 @@ class Content extends Component {
item={item} item={item}
updateRole={this.props.updateRole} updateRole={this.props.updateRole}
deleteOrg={this.props.deleteOrg} deleteOrg={this.props.deleteOrg}
isItemFreezed={this.state.isItemFreezed}
toggleItemFreezed={this.toggleItemFreezed}
/>); />);
})} })}
</tbody> </tbody>
@@ -81,7 +94,6 @@ class Content extends Component {
Content.propTypes = { Content.propTypes = {
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired, errorMsg: PropTypes.string.isRequired,
item: PropTypes.object.isRequired,
getListByPage: PropTypes.func.isRequired, getListByPage: PropTypes.func.isRequired,
currentPage: PropTypes.number, currentPage: PropTypes.number,
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
@@ -97,18 +109,20 @@ class Item extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isOpIconShown: false, highlighted: false,
isDeleteDialogOpen: false, isDeleteDialogOpen: false,
deleteDialogMsg: '', deleteDialogMsg: '',
}; };
} }
handleMouseEnter = () => { handleMouseEnter = () => {
this.setState({isOpIconShown: true}); if (this.props.isItemFreezed) return;
this.setState({highlighted: true});
}; };
handleMouseLeave = () => { handleMouseLeave = () => {
this.setState({isOpIconShown: false}); if (this.props.isItemFreezed) return;
this.setState({highlighted: false});
}; };
toggleDeleteDialog = (e) => { toggleDeleteDialog = (e) => {
@@ -135,8 +149,19 @@ class Item extends Component {
}); });
}; };
updateRole = (role) => { translateRole = (role) => {
this.props.updateRole(this.props.item.org_id, 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 = () => { deleteOrg = () => {
@@ -146,28 +171,38 @@ class Item extends Component {
render() { render() {
const { item } = this.props; 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 ( return (
<Fragment> <Fragment>
<tr onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <tr className={highlighted ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td><Link to={`${siteRoot}sys/organizations/${item.org_id}/info/`}>{item.org_name}</Link></td> <td><Link to={`${siteRoot}sys/organizations/${item.org_id}/info/`}>{item.org_name}</Link></td>
<td> <td>
<UserLink email={item.creator_email} name={item.creator_name} /> <UserLink email={item.creator_email} name={item.creator_name} />
</td> </td>
<td> <td>
<SysAdminUserRoleEditor <RoleSelector
isTextMode={true} isDropdownToggleShown={highlighted}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedOption}
currentRole={item.role} options={this.roleOptions}
roleOptions={availableRoles} selectOption={this.updateRole}
onRoleChanged={this.updateRole} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
<td>{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota > 0 ? Utils.bytesToSize(item.quota) : '--'}`}</td> <td>{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota > 0 ? Utils.bytesToSize(item.quota) : '--'}`}</td>
<td>{moment(item.ctime).format('YYYY-MM-DD HH:mm:ss')}</td> <td>{moment(item.ctime).format('YYYY-MM-DD HH:mm:ss')}</td>
<td> <td>
<a href="#" className={`action-icon sf2-icon-delete ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a> <a href="#" className={`action-icon sf2-icon-delete ${highlighted ? '' : 'invisible'}`} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
</td> </td>
</tr> </tr>
{isDeleteDialogOpen && {isDeleteDialogOpen &&
@@ -188,6 +223,8 @@ Item.propTypes = {
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
updateRole: PropTypes.func.isRequired, updateRole: PropTypes.func.isRequired,
deleteOrg: PropTypes.func.isRequired, deleteOrg: PropTypes.func.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
toggleItemFreezed: PropTypes.func.isRequired
}; };
export default Content; export default Content;

View File

@@ -9,9 +9,7 @@ import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip'; import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading'; import Loading from '../../../components/loading';
import Paginator from '../../../components/paginator'; import Paginator from '../../../components/paginator';
import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor'; import Selector from '../../../components/single-selector';
import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor';
import SelectEditor from '../../../components/select-editor/select-editor';
import OpMenu from '../../../components/dialog/op-menu'; import OpMenu from '../../../components/dialog/op-menu';
import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
@@ -28,6 +26,10 @@ class Content extends Component {
}; };
} }
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
};
onFreezedItem = () => { onFreezedItem = () => {
this.setState({isItemFreezed: true}); this.setState({isItemFreezed: true});
}; };
@@ -133,6 +135,7 @@ class Content extends Component {
isItemFreezed={this.state.isItemFreezed} isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
toggleItemFreezed={this.toggleItemFreezed}
updateUser={this.props.updateUser} updateUser={this.props.updateUser}
deleteUser={this.props.deleteUser} deleteUser={this.props.deleteUser}
updateAdminRole={this.props.updateAdminRole} updateAdminRole={this.props.updateAdminRole}
@@ -247,20 +250,31 @@ class Item extends Component {
this.props.onUserSelected(this.props.item); this.props.onUserSelected(this.props.item);
}; };
updateStatus= (value) => { updateStatus= (roleOption) => {
const isActive = value == 'active'; const isActive = roleOption.value == 'active';
if (isActive) { if (isActive) {
toaster.notify(gettext('It may take some time, please wait.')); toaster.notify(gettext('It may take some time, please wait.'));
} }
this.props.updateUser(this.props.item.email, 'is_active', isActive); this.props.updateUser(this.props.item.email, 'is_active', isActive);
}; };
updateRole = (value) => { updateRole = (roleOption) => {
this.props.updateUser(this.props.item.email, 'role', value); this.props.updateUser(this.props.item.email, 'role', roleOption.value);
}; };
updateAdminRole = (value) => { updateAdminRole = (roleOption) => {
this.props.updateAdminRole(this.props.item.email, value); 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) => { translateAdminRole = (role) => {
@@ -278,12 +292,17 @@ class Item extends Component {
} }
}; };
updateInstitution = (value) => { translateStatus = (status) => {
this.props.updateUser(this.props.item.email, 'institution', value); switch (status) {
case 'active':
return gettext('Active');
case 'inactive':
return gettext('Inactive');
}
}; };
translateInstitution = (inst) => { updateInstitution = (instOption) => {
return inst; this.props.updateUser(this.props.item.email, 'institution', instOption.value);
}; };
updateQuota = (value) => { updateQuota = (value) => {
@@ -362,6 +381,7 @@ class Item extends Component {
render() { render() {
const { item, isAdmin } = this.props; const { item, isAdmin } = this.props;
const { const {
highlight,
isOpIconShown, isOpIconShown,
isSetQuotaDialogOpen, isSetQuotaDialogOpen,
isDeleteUserDialogOpen, 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 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); 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 ( return (
<Fragment> <Fragment>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
@@ -395,31 +463,31 @@ class Item extends Component {
} }
</td> </td>
<td> <td>
<SysAdminUserStatusEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedStatusOption}
currentStatus={item.is_active ? 'active' : 'inactive'} options={this.statusOptions}
statusOptions={['active', 'inactive']} selectOption={this.updateStatus}
onStatusChanged={this.updateStatus} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
</td> </td>
{isPro && {isPro &&
<td> <td>
{isAdmin ? {isAdmin ?
<SelectEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedAdminRoleOption}
options={availableAdminRoles} options={this.adminRoleOptions}
currentOption={item.admin_role} selectOption={this.updateAdminRole}
onOptionChanged={this.updateAdminRole} toggleItemFreezed={this.props.toggleItemFreezed}
translateOption={this.translateAdminRole} />
/> : :
<SysAdminUserRoleEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight}
isEditIconShow={isOpIconShown} currentSelectedOption={currentSelectedRoleOption}
currentRole={item.role} options={this.roleOptions}
roleOptions={availableRoles} selectOption={this.updateRole}
onRoleChanged={this.updateRole} toggleItemFreezed={this.props.toggleItemFreezed}
/> />
} }
</td> </td>
@@ -434,13 +502,12 @@ class Item extends Component {
</td> </td>
{(multiInstitution && !isAdmin) && {(multiInstitution && !isAdmin) &&
<td> <td>
<SelectEditor <Selector
isTextMode={true} isDropdownToggleShown={highlight && institutions.length > 0}
isEditIconShow={isOpIconShown && institutions.length > 0} currentSelectedOption={currentSelectedInstOption}
options={institutions} options={this.instOptions}
currentOption={item.institution} selectOption={this.updateInstitution}
onOptionChanged={this.updateInstitution} toggleItemFreezed={this.props.toggleItemFreezed}
translateOption={this.translateInstitution}
/> />
</td> </td>
} }
@@ -508,6 +575,7 @@ Item.propTypes = {
isLDAPImported: PropTypes.bool, isLDAPImported: PropTypes.bool,
onFreezedItem: PropTypes.func, onFreezedItem: PropTypes.func,
onUnfreezedItem: PropTypes.func, onUnfreezedItem: PropTypes.func,
toggleItemFreezed: PropTypes.func.isRequired,
updateUser: PropTypes.func, updateUser: PropTypes.func,
deleteUser: PropTypes.func, deleteUser: PropTypes.func,
updateAdminRole: PropTypes.func, updateAdminRole: PropTypes.func,