mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-24 21:07:17 +00:00
change search and select user (#7782)
* 01 basic function * 02 add list and add group users * 03 change style * 04 other components * 05 change className
This commit is contained in:
@@ -16,7 +16,6 @@ class UserItem extends React.Component {
|
||||
this.state = {
|
||||
isOperationShow: false
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
@@ -100,7 +99,7 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedUsers: null,
|
||||
selectedUsers: [],
|
||||
errorMsg: [],
|
||||
permission: 'rw',
|
||||
userFolderPermItems: [],
|
||||
@@ -112,8 +111,6 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
|
||||
} else {
|
||||
this.permissions = ['r', 'rw', 'cloud-edit', 'preview', 'invisible'];
|
||||
}
|
||||
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleUserSelectChange = (option) => {
|
||||
@@ -161,11 +158,10 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
userFolderPermItems: this.state.userFolderPermItems.concat(res.data.success),
|
||||
selectedUsers: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
folderPath: '',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -283,11 +279,10 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
|
||||
<tr>
|
||||
<td>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleUserSelectChange}
|
||||
value={this.state.selectedUsers}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</td>
|
||||
{showPath &&
|
||||
|
@@ -18,24 +18,23 @@ class AddOrgAdminDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errMessage: '',
|
||||
};
|
||||
this.options = [];
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({
|
||||
selectedOption: option,
|
||||
selectedUsers: option,
|
||||
errMessage: ''
|
||||
});
|
||||
this.options = [];
|
||||
};
|
||||
|
||||
addOrgAdmin = () => {
|
||||
if (!this.state.selectedOption) return;
|
||||
const userEmail = this.state.selectedOption[0].email;
|
||||
if (!this.state.selectedUsers || this.state.selectedUsers.length === 0) return;
|
||||
const userEmail = this.state.selectedUsers[0].email;
|
||||
orgAdminAPI.orgAdminSetOrgAdmin(orgID, userEmail, true).then(res => {
|
||||
let userInfo = new OrgUserInfo(res.data);
|
||||
this.props.onAddedOrgAdmin(userInfo);
|
||||
@@ -52,13 +51,13 @@ class AddOrgAdminDialog extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<SeahubModalHeader toggle={this.toggle}>{gettext('Add Admins')}</SeahubModalHeader>
|
||||
<SeahubModalHeader toggle={this.toggle}>{gettext('Add Admin')}</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={false}
|
||||
placeholder={gettext('Select a user as admin')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
|
||||
</ModalBody>
|
||||
|
@@ -21,7 +21,6 @@ class UserItem extends React.Component {
|
||||
isOperationShow: false,
|
||||
isUserDetailsPopoverOpen: false
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
@@ -225,7 +224,7 @@ class ShareToUser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errorMsg: [],
|
||||
permission: 'rw',
|
||||
sharedItems: [],
|
||||
@@ -253,7 +252,7 @@ class ShareToUser extends React.Component {
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
this.setState({ selectedUsers: option });
|
||||
this.options = [];
|
||||
};
|
||||
|
||||
@@ -287,9 +286,9 @@ class ShareToUser extends React.Component {
|
||||
let users = [];
|
||||
let path = this.props.itemPath;
|
||||
let repoID = this.props.repoID;
|
||||
if (this.state.selectedOption && this.state.selectedOption.length > 0) {
|
||||
for (let i = 0; i < this.state.selectedOption.length; i ++) {
|
||||
users[i] = this.state.selectedOption[i].email;
|
||||
if (this.state.selectedUsers && this.state.selectedUsers.length > 0) {
|
||||
for (let i = 0; i < this.state.selectedUsers.length; i ++) {
|
||||
users[i] = this.state.selectedUsers[i].email;
|
||||
}
|
||||
}
|
||||
if (this.props.isGroupOwnedRepo) {
|
||||
@@ -312,10 +311,9 @@ class ShareToUser extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
sharedItems: this.state.sharedItems.concat(items),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
let message = gettext('Library can not be shared to owner.');
|
||||
@@ -323,7 +321,7 @@ class ShareToUser extends React.Component {
|
||||
errMessage.push(message);
|
||||
this.setState({
|
||||
errorMsg: errMessage,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -338,10 +336,9 @@ class ShareToUser extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
sharedItems: this.state.sharedItems.concat(res.data.success),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
let message = gettext('Library can not be shared to owner.');
|
||||
@@ -349,7 +346,7 @@ class ShareToUser extends React.Component {
|
||||
errMessage.push(message);
|
||||
this.setState({
|
||||
errorMsg: errMessage,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -443,10 +440,9 @@ class ShareToUser extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
sharedItems: this.state.sharedItems.concat(items),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
let message = gettext('Library can not be shared to owner.');
|
||||
@@ -454,7 +450,7 @@ class ShareToUser extends React.Component {
|
||||
errMessage.push(message);
|
||||
this.setState({
|
||||
errorMsg: errMessage,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -469,10 +465,9 @@ class ShareToUser extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
sharedItems: this.state.sharedItems.concat(res.data.success),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
let message = gettext('Library can not be shared to owner.');
|
||||
@@ -480,7 +475,7 @@ class ShareToUser extends React.Component {
|
||||
errMessage.push(message);
|
||||
this.setState({
|
||||
errorMsg: errMessage,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -526,12 +521,11 @@ class ShareToUser extends React.Component {
|
||||
<td>
|
||||
<div className='add-members'>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
className={classnames('reviewer-select', { 'user-select-right-btn': showDeptBtn })}
|
||||
placeholder={gettext('Search users...')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
excludeCurrentUser={false}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
{showDeptBtn &&
|
||||
<span
|
||||
|
@@ -20,17 +20,17 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: [],
|
||||
selectedUsers: [],
|
||||
errMsgs: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (options) => {
|
||||
this.setState({ selectedOptions: options });
|
||||
this.setState({ selectedUsers: options });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
const emails = this.state.selectedOptions.map(option => option.email);
|
||||
const emails = this.state.selectedUsers.map(option => option.email);
|
||||
if (emails.length === 0) return;
|
||||
this.setState({ errMessage: '' });
|
||||
const { nodeId, orgID } = this.props;
|
||||
@@ -38,7 +38,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
orgAdminAPI.orgAdminAddGroupMember(orgID, nodeId, emails) :
|
||||
systemAdminAPI.sysAdminAddGroupMember(nodeId, emails);
|
||||
req.then((res) => {
|
||||
this.setState({ selectedOptions: [] });
|
||||
this.setState({ selectedUsers: [] });
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({ errMsgs: res.data.failed.map(item => item.error_msg) });
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
isMulti={true}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
{errMsgs.length > 0 && (
|
||||
<ul className="list-unstyled">
|
||||
|
@@ -15,17 +15,17 @@ class AddMemberDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: [],
|
||||
selectedUsers: [],
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
this.setState({ selectedUsers: option });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
if (!this.state.selectedOption) return;
|
||||
const emails = this.state.selectedOption.map(item => item.email);
|
||||
if (!this.state.selectedUsers) return;
|
||||
const emails = this.state.selectedUsers.map(item => item.email);
|
||||
this.props.addUser(emails);
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ class AddMemberDialog extends React.Component {
|
||||
onSelectChange={this.handleSelectChange}
|
||||
isMulti={true}
|
||||
className='org-add-member-select'
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -19,22 +19,21 @@ class AddMemberDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errMessage: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
this.setState({ selectedUsers: option });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
if (!this.state.selectedOption) return;
|
||||
const emails = this.state.selectedOption.map(item => item.email);
|
||||
this.refs.orgSelect.clearSelect();
|
||||
if (!this.state.selectedUsers) return;
|
||||
const emails = this.state.selectedUsers.map(item => item.email);
|
||||
this.setState({ errMessage: [] });
|
||||
systemAdminAPI.sysAdminAddGroupMember(this.props.groupID, emails).then((res) => {
|
||||
this.setState({ selectedOption: null });
|
||||
this.setState({ selectedUsers: [] });
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({ errMessage: res.data.failed[0].error_msg });
|
||||
}
|
||||
@@ -56,9 +55,9 @@ class AddMemberDialog extends React.Component {
|
||||
<UserSelect
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
ref="orgSelect"
|
||||
isMulti={true}
|
||||
className='org-add-member-select'
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
|
||||
</ModalBody>
|
||||
|
@@ -14,7 +14,7 @@ class SysAdminBatchAddAdminDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
options: null,
|
||||
selectedUsers: [],
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
}
|
||||
@@ -23,15 +23,15 @@ class SysAdminBatchAddAdminDialog extends React.Component {
|
||||
this.props.toggle();
|
||||
};
|
||||
|
||||
handleSelectChange = (options) => {
|
||||
handleSelectChange = (selectedUsers) => {
|
||||
this.setState({
|
||||
options: options,
|
||||
isSubmitBtnActive: options.length > 0
|
||||
selectedUsers: selectedUsers,
|
||||
isSubmitBtnActive: selectedUsers.length > 0
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.addAdminInBatch(this.state.options.map(item => item.email));
|
||||
this.props.addAdminInBatch(this.state.selectedUsers.map(item => item.email));
|
||||
this.toggle();
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@ class SysAdminBatchAddAdminDialog extends React.Component {
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -16,7 +16,7 @@ class SysAdminCreateGroupDialog extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
groupName: '',
|
||||
ownerEmail: '',
|
||||
selectedUsers: [],
|
||||
errMessage: '',
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
@@ -34,13 +34,12 @@ class SysAdminCreateGroupDialog extends React.Component {
|
||||
|
||||
handleSubmit = () => {
|
||||
let groupName = this.state.groupName.trim();
|
||||
this.props.createGroup(groupName, this.state.ownerEmail);
|
||||
this.props.createGroup(groupName, this.state.selectedUsers[0].email);
|
||||
};
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
// option can be `null`, `[{...}]`, or `[]`
|
||||
handleSelectChange = (selectedUsers) => {
|
||||
this.setState({
|
||||
ownerEmail: option && option.length ? option[0].email : ''
|
||||
selectedUsers: selectedUsers
|
||||
});
|
||||
};
|
||||
|
||||
@@ -72,12 +71,13 @@ class SysAdminCreateGroupDialog extends React.Component {
|
||||
/>
|
||||
<Label className="mt-2">
|
||||
{gettext('Owner')}
|
||||
<span className="small text-secondary">{gettext('(If left blank, owner will be admin)')}</span>
|
||||
<span className="small text-secondary ml-1">{gettext('(If left blank, owner will be admin)')}</span>
|
||||
</Label>
|
||||
<UserSelect
|
||||
isMulti={false}
|
||||
placeholder={gettext('Select a user')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
@@ -15,7 +15,7 @@ class SysAdminCreateRepoDialog extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
repoName: '',
|
||||
ownerEmail: '',
|
||||
selectedUsers: [],
|
||||
errMessage: '',
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
@@ -30,15 +30,14 @@ class SysAdminCreateRepoDialog extends React.Component {
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
const { repoName, ownerEmail } = this.state;
|
||||
this.props.createRepo(repoName.trim(), ownerEmail);
|
||||
const { repoName, selectedUsers } = this.state;
|
||||
this.props.createRepo(repoName.trim(), selectedUsers[0].email);
|
||||
this.toggle();
|
||||
};
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
// option can be `null`, `[{...}]`, or `[]`
|
||||
handleSelectChange = (selectedUsers) => {
|
||||
this.setState({
|
||||
ownerEmail: option && option.length ? option[0].email : ''
|
||||
selectedUsers: selectedUsers
|
||||
});
|
||||
};
|
||||
|
||||
@@ -72,13 +71,14 @@ class SysAdminCreateRepoDialog extends React.Component {
|
||||
<FormGroup>
|
||||
<Label for="userSelect">
|
||||
{gettext('Owner')}
|
||||
<span className="small text-secondary">{gettext('(If left blank, owner will be admin)')}</span>
|
||||
<span className="small text-secondary ml-1">{gettext('(If left blank, owner will be admin)')}</span>
|
||||
</Label>
|
||||
<UserSelect
|
||||
id="userSelect"
|
||||
isMulti={false}
|
||||
placeholder={gettext('Select a user')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
@@ -15,21 +15,20 @@ class SysAdminGroupAddMemberDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: null,
|
||||
selectedUsers: [],
|
||||
isSubmitBtnDisabled: true
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleSelectChange = (options) => {
|
||||
this.setState({
|
||||
selectedOptions: options,
|
||||
selectedUsers: options,
|
||||
isSubmitBtnDisabled: !options.length
|
||||
});
|
||||
};
|
||||
|
||||
addMembers = () => {
|
||||
let emails = this.state.selectedOptions.map(item => item.email);
|
||||
let emails = this.state.selectedUsers.map(item => item.email);
|
||||
this.props.addMembers(emails);
|
||||
this.props.toggle();
|
||||
};
|
||||
@@ -41,10 +40,10 @@ class SysAdminGroupAddMemberDialog extends React.Component {
|
||||
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Add Member')}</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -17,22 +17,21 @@ class SysAdminTransferGroupDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: null,
|
||||
selectedUsers: [],
|
||||
submitBtnDisabled: true
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleSelectChange = (options) => {
|
||||
this.setState({
|
||||
selectedOptions: options,
|
||||
selectedUsers: options,
|
||||
submitBtnDisabled: options == null
|
||||
});
|
||||
};
|
||||
|
||||
submit = () => {
|
||||
if (this.state.selectedOptions) {
|
||||
const receiver = this.state.selectedOptions[0].email;
|
||||
if (this.state.selectedUsers) {
|
||||
const receiver = this.state.selectedUsers[0].email;
|
||||
this.props.transferGroup(receiver);
|
||||
this.props.toggleDialog();
|
||||
}
|
||||
@@ -49,10 +48,10 @@ class SysAdminTransferGroupDialog extends React.Component {
|
||||
</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={false}
|
||||
placeholder={gettext('Select a user')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -1,62 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import UserSelect from '../../user-select';
|
||||
import SeahubModalHeader from '@/components/common/seahub-modal-header';
|
||||
|
||||
const propTypes = {
|
||||
repoName: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class SysAdminRepoTransferDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
errorMsg: [],
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
};
|
||||
|
||||
submit = () => {
|
||||
let user = this.state.selectedOption;
|
||||
this.props.submit(user);
|
||||
};
|
||||
|
||||
render() {
|
||||
const repoName = this.props.repoName;
|
||||
const innerSpan = '<span class="op-target" title=' + repoName + '>' + repoName + '</span>';
|
||||
let msg = gettext('Transfer Library {library_name}');
|
||||
let message = msg.replace('{library_name}', innerSpan);
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||
<SeahubModalHeader toggle={this.props.toggle}>
|
||||
<div dangerouslySetInnerHTML={{ __html: message }} />
|
||||
</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={false}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.submit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminRepoTransferDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminRepoTransferDialog;
|
@@ -15,7 +15,6 @@ class UserItem extends React.Component {
|
||||
this.state = {
|
||||
isOperationShow: false
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
@@ -115,7 +114,7 @@ class SysAdminShareToUser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errorMsg: [],
|
||||
permission: 'rw',
|
||||
sharedItems: []
|
||||
@@ -127,8 +126,8 @@ class SysAdminShareToUser extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
handleSelectChange = (options) => {
|
||||
this.setState({ selectedUsers: options });
|
||||
this.options = [];
|
||||
};
|
||||
|
||||
@@ -151,9 +150,9 @@ class SysAdminShareToUser extends React.Component {
|
||||
shareToUser = () => {
|
||||
let users = [];
|
||||
let repoID = this.props.repoID;
|
||||
if (this.state.selectedOption && this.state.selectedOption.length > 0) {
|
||||
for (let i = 0; i < this.state.selectedOption.length; i ++) {
|
||||
users[i] = this.state.selectedOption[i].email;
|
||||
if (this.state.selectedUsers && this.state.selectedUsers.length > 0) {
|
||||
for (let i = 0; i < this.state.selectedUsers.length; i ++) {
|
||||
users[i] = this.state.selectedUsers[i].email;
|
||||
}
|
||||
}
|
||||
systemAdminAPI.sysAdminAddRepoSharedItem(repoID, 'user', users, this.state.permission).then(res => {
|
||||
@@ -167,10 +166,9 @@ class SysAdminShareToUser extends React.Component {
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
sharedItems: this.state.sharedItems.concat(newItems),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
permission: 'rw',
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
let message = gettext('Library can not be shared to owner.');
|
||||
@@ -178,7 +176,7 @@ class SysAdminShareToUser extends React.Component {
|
||||
errMessage.push(message);
|
||||
this.setState({
|
||||
errorMsg: errMessage,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -236,10 +234,10 @@ class SysAdminShareToUser extends React.Component {
|
||||
<tr>
|
||||
<td>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@@ -34,22 +34,26 @@ class TransferDialog extends React.Component {
|
||||
this.state = {
|
||||
options: [],
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errorMsg: [],
|
||||
transferToUser: true,
|
||||
transferToGroup: false,
|
||||
reshare: false,
|
||||
activeTab: !this.props.isDepAdminTransfer ? TRANS_USER : TRANS_DEPART
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
};
|
||||
|
||||
onUsersChange = (selectedUsers) => {
|
||||
this.setState({ selectedUsers });
|
||||
};
|
||||
|
||||
submit = () => {
|
||||
const { activeTab, reshare, selectedOption } = this.state;
|
||||
const email = activeTab === TRANS_DEPART ? selectedOption.email : selectedOption[0].email;
|
||||
const { activeTab, reshare, selectedOption, selectedUsers } = this.state;
|
||||
const email = activeTab === TRANS_DEPART ? selectedOption.email : selectedUsers[0].email;
|
||||
this.props.onTransferRepo(email, reshare);
|
||||
};
|
||||
|
||||
@@ -106,6 +110,7 @@ class TransferDialog extends React.Component {
|
||||
activeTab: tab,
|
||||
reshare: false,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -158,10 +163,10 @@ class TransferDialog extends React.Component {
|
||||
<TabPane tabId="transUser" role="tabpanel" id="transfer-user-panel">
|
||||
<Label className='transfer-repo-label'>{gettext('Users')}</Label>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={false}
|
||||
placeholder={gettext('Select a user')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
onSelectChange={this.onUsersChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
<Switch
|
||||
checked={reshare}
|
||||
@@ -207,13 +212,19 @@ class TransferDialog extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedOption } = this.state;
|
||||
const { selectedOption, activeTab } = this.state;
|
||||
const { itemName: repoName } = this.props;
|
||||
let title = gettext('Transfer Library {library_name}');
|
||||
title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>');
|
||||
let buttonDisabled = false;
|
||||
if (selectedOption === null || (Array.isArray(selectedOption) && selectedOption.length === 0)) {
|
||||
buttonDisabled = true;
|
||||
if (activeTab === TRANS_DEPART) {
|
||||
if (selectedOption === null || (Array.isArray(selectedOption) && selectedOption.length === 0)) {
|
||||
buttonDisabled = true;
|
||||
}
|
||||
} else {
|
||||
if (this.state.selectedUsers.length === 0) {
|
||||
buttonDisabled = true;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={true} style={{ maxWidth: '720px' }} toggle={this.props.toggleDialog} className="transfer-dialog">
|
||||
|
@@ -21,21 +21,21 @@ class TransferGroupDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null
|
||||
selectedUsers: []
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({
|
||||
selectedOption: option
|
||||
selectedUsers: option
|
||||
});
|
||||
};
|
||||
|
||||
transferGroup = () => {
|
||||
let selectedOption = this.state.selectedOption;
|
||||
let selectedUsers = this.state.selectedUsers;
|
||||
let email;
|
||||
if (selectedOption && selectedOption[0]) {
|
||||
email = selectedOption[0].email;
|
||||
if (selectedUsers && selectedUsers[0]) {
|
||||
email = selectedUsers[0].email;
|
||||
}
|
||||
if (!email) {
|
||||
return false;
|
||||
@@ -61,10 +61,10 @@ class TransferGroupDialog extends React.Component {
|
||||
<ModalBody>
|
||||
<p>{gettext('Transfer group to')}</p>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={false}
|
||||
placeholder={gettext('Please enter 1 or more character')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -27,14 +27,13 @@ class ManageMembersDialog extends React.Component {
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
hasNextPage: false,
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
errMessage: [],
|
||||
isItemFreezed: false,
|
||||
searchActive: false,
|
||||
keyword: '',
|
||||
membersFound: []
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -66,23 +65,22 @@ class ManageMembersDialog extends React.Component {
|
||||
|
||||
onSelectChange = (option) => {
|
||||
this.setState({
|
||||
selectedOption: option,
|
||||
selectedUsers: option,
|
||||
errMessage: [],
|
||||
});
|
||||
};
|
||||
|
||||
addGroupMember = () => {
|
||||
let emails = [];
|
||||
for (let i = 0; i < this.state.selectedOption.length; i++) {
|
||||
emails.push(this.state.selectedOption[i].email);
|
||||
for (let i = 0; i < this.state.selectedUsers.length; i++) {
|
||||
emails.push(this.state.selectedUsers[i].email);
|
||||
}
|
||||
seafileAPI.addGroupMembers(this.props.groupID, emails).then((res) => {
|
||||
const newMembers = res.data.success;
|
||||
this.setState({
|
||||
groupMembers: [].concat(newMembers, this.state.groupMembers),
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({
|
||||
errMessage: res.data.failed
|
||||
@@ -186,14 +184,14 @@ class ManageMembersDialog extends React.Component {
|
||||
<UserSelect
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.onSelectChange}
|
||||
ref={this.userSelect}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
isMulti={true}
|
||||
className="add-members-select"
|
||||
/>
|
||||
{showDeptBtn &&
|
||||
<span onClick={this.onClickDeptBtn} className="sf3-font sf3-font-invite-visitors toggle-detail-btn"></span>
|
||||
}
|
||||
{this.state.selectedOption ?
|
||||
{this.state.selectedUsers.length > 0 ?
|
||||
<Button color="primary" onClick={this.addGroupMember}>{gettext('Submit')}</Button> :
|
||||
<Button color="primary" disabled>{gettext('Submit')}</Button>
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ class UserItem extends React.Component {
|
||||
isHighlighted: false,
|
||||
isOperationShow: false
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
@@ -89,7 +88,7 @@ class LinkAuthenticatedUsers extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
authUsers: []
|
||||
};
|
||||
}
|
||||
@@ -110,11 +109,11 @@ class LinkAuthenticatedUsers extends React.Component {
|
||||
|
||||
addLinkAuthUsers = () => {
|
||||
const { linkToken, path } = this.props;
|
||||
const { selectedOption, authUsers } = this.state;
|
||||
if (!selectedOption || !selectedOption.length) {
|
||||
const { selectedUsers, authUsers } = this.state;
|
||||
if (!selectedUsers || !selectedUsers.length) {
|
||||
return false;
|
||||
}
|
||||
const users = selectedOption.map((item, index) => item.email);
|
||||
const users = selectedUsers.map((item, index) => item.email);
|
||||
shareLinkAPI.addShareLinkAuthUsers(linkToken, users, path).then(res => {
|
||||
const { success, failed } = res.data;
|
||||
if (success.length) {
|
||||
@@ -130,9 +129,8 @@ class LinkAuthenticatedUsers extends React.Component {
|
||||
}
|
||||
this.setState({
|
||||
authUsers: success.concat(authUsers),
|
||||
selectedOption: null
|
||||
selectedUsers: []
|
||||
});
|
||||
this.userSelect.current.clearSelect();
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -160,7 +158,7 @@ class LinkAuthenticatedUsers extends React.Component {
|
||||
};
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
this.setState({ selectedUsers: option });
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -193,10 +191,10 @@ class LinkAuthenticatedUsers extends React.Component {
|
||||
<tr>
|
||||
<td>
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@@ -47,10 +47,9 @@ class LinkCreation extends React.Component {
|
||||
currentPermission: props.currentPermission,
|
||||
|
||||
currentScope: 'all_users',
|
||||
selectedOption: null,
|
||||
selectedUsers: [],
|
||||
inputEmails: ''
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
setExpType = (e) => {
|
||||
@@ -128,9 +127,9 @@ class LinkCreation extends React.Component {
|
||||
const autoGeneratePassword = shareLinkForceUsePassword || isShowPasswordInput;
|
||||
request = seafileAPI.batchCreateMultiShareLink(repoID, itemPath, linkAmount, autoGeneratePassword, expirationTime, permissions);
|
||||
} else {
|
||||
const { currentScope, selectedOption, inputEmails } = this.state;
|
||||
if (currentScope === 'specific_users' && selectedOption) {
|
||||
users = selectedOption.map((item, index) => item.email);
|
||||
const { currentScope, selectedUsers, inputEmails } = this.state;
|
||||
if (currentScope === 'specific_users' && selectedUsers) {
|
||||
users = selectedUsers.map((item, index) => item.email);
|
||||
}
|
||||
if (currentScope === 'specific_emails' && inputEmails) {
|
||||
users = inputEmails;
|
||||
@@ -264,11 +263,11 @@ class LinkCreation extends React.Component {
|
||||
};
|
||||
|
||||
setScope = (e) => {
|
||||
this.setState({ currentScope: e.target.value, selectedOption: null, inputEmails: '' });
|
||||
this.setState({ currentScope: e.target.value, selectedUsers: [], inputEmails: '' });
|
||||
};
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
this.setState({ selectedUsers: option });
|
||||
};
|
||||
|
||||
handleInputChange = (e) => {
|
||||
@@ -390,10 +389,10 @@ class LinkCreation extends React.Component {
|
||||
</Label>
|
||||
{this.state.currentScope === 'specific_users' &&
|
||||
<UserSelect
|
||||
ref={this.userSelect}
|
||||
isMulti={true}
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
selectedUsers={this.state.selectedUsers}
|
||||
/>
|
||||
}
|
||||
</FormGroup>
|
||||
|
59
frontend/src/components/user-item/index.css
Normal file
59
frontend/src/components/user-item/index.css
Normal file
@@ -0,0 +1,59 @@
|
||||
.user-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 2px 10px 2px 0;
|
||||
padding: 0 8px 0 2px;
|
||||
height: 20px;
|
||||
font-size: 13px;
|
||||
border-radius: 10px;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
.user-item .user-avatar,
|
||||
.user-item .user-name,
|
||||
.user-item .user-remove {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.user-item .user-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translateY(0);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-item .user-name {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.user-item .user-avatar img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-item .user-remove {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
margin: 0 -2px 0 2px;
|
||||
}
|
||||
|
||||
.user-item .user-remove .sf3-font {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #909090;
|
||||
transform: scale(.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-item .user-remove .sf3-font:hover {
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-item .user-option-email {
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
}
|
54
frontend/src/components/user-item/index.js
Normal file
54
frontend/src/components/user-item/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { gettext, enableShowContactEmailWhenSearchUser, enableShowLoginIDWhenSearchUser } from '../../utils/constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const propTypes = {
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
avatar_url: PropTypes.string.isRequired,
|
||||
email: PropTypes.string,
|
||||
contact_email: PropTypes.string,
|
||||
login_id: PropTypes.string,
|
||||
}),
|
||||
className: PropTypes.string,
|
||||
enableDeleteUser: PropTypes.bool,
|
||||
onDeleteUser: PropTypes.func,
|
||||
};
|
||||
|
||||
class UserItem extends React.Component {
|
||||
|
||||
onDeleteUser = (event) => {
|
||||
event.stopPropagation();
|
||||
event && event.nativeEvent.stopImmediatePropagation();
|
||||
this.props.onDeleteUser(this.props.user);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, user, enableDeleteUser } = this.props;
|
||||
const { name, avatar_url, contact_email, login_id } = user;
|
||||
return (
|
||||
<div className={classnames('user-item', className)} title={name}>
|
||||
<span className="user-avatar">
|
||||
<img className="user-avatar-icon" alt={name} src={avatar_url} />
|
||||
</span>
|
||||
<div className="d-flex align-items-center">
|
||||
<span className="user-name">{name}</span>
|
||||
{(enableShowContactEmailWhenSearchUser && !enableDeleteUser) && <span className="user-option-email">({contact_email})</span>}
|
||||
{(enableShowLoginIDWhenSearchUser && !enableDeleteUser) && <span className="user-option-email">({login_id})</span>}
|
||||
</div>
|
||||
{enableDeleteUser && (
|
||||
<span className="user-remove ml-2" onClick={this.onDeleteUser} title={gettext('Remove')}>
|
||||
<i className="sf3-font sf3-font-x-01" aria-hidden="true"></i>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserItem.propTypes = propTypes;
|
||||
|
||||
export default UserItem;
|
@@ -1,114 +1,265 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import classnames from 'classnames';
|
||||
import { Popover } from 'reactstrap';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import { gettext, enableShowContactEmailWhenSearchUser, enableShowLoginIDWhenSearchUser } from '../utils/constants';
|
||||
import { gettext } from '../utils/constants';
|
||||
import { Utils } from '../utils/utils';
|
||||
import toaster from './toast';
|
||||
import { UserSelectStyle, NoOptionsStyle } from './common/select';
|
||||
import KeyCodes from '../constants/keyCodes';
|
||||
import SearchInput from './search-input';
|
||||
import UserItem from '../components/user-item';
|
||||
import ClickOutside from './click-outside';
|
||||
|
||||
import '../css/user-select.css';
|
||||
|
||||
const propTypes = {
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
onSelectChange: PropTypes.func.isRequired,
|
||||
isMulti: PropTypes.bool.isRequired,
|
||||
isMulti: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
};
|
||||
|
||||
class UserSelect extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.options = [];
|
||||
this.finalValue = '';
|
||||
this.state = {
|
||||
searchValue: ''
|
||||
maxItemNum: 0,
|
||||
itemHeight: 0,
|
||||
searchedUsers: [],
|
||||
searchValue: '',
|
||||
highlightIndex: -1,
|
||||
};
|
||||
this.userSelect = React.createRef();
|
||||
}
|
||||
|
||||
onInputChange = (searchValue) => {
|
||||
if (!this.props.isMulti && searchValue.trim()) {
|
||||
this.handleSelectChange(null);
|
||||
this.clearSelect();
|
||||
}
|
||||
this.setState({ searchValue });
|
||||
};
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.options = [];
|
||||
this.props.onSelectChange(option);
|
||||
};
|
||||
|
||||
loadOptions = (input, callback) => {
|
||||
const value = input.trim();
|
||||
this.finalValue = value;
|
||||
setTimeout(() => {
|
||||
if (this.finalValue === value && value.length > 0) {
|
||||
seafileAPI.searchUsers(value).then((res) => {
|
||||
this.options = [];
|
||||
for (let i = 0 ; i < res.data.users.length; i++) {
|
||||
const item = res.data.users[i];
|
||||
let obj = {};
|
||||
obj.value = item.name;
|
||||
obj.email = item.email;
|
||||
obj.label = (enableShowContactEmailWhenSearchUser || enableShowLoginIDWhenSearchUser) ? (
|
||||
<div className="d-flex">
|
||||
<img src={item.avatar_url} className="avatar" width="24" alt="" />
|
||||
<div className="ml-2">
|
||||
<span className="user-option-name">{item.name}</span><br />
|
||||
{enableShowContactEmailWhenSearchUser && <span className="user-option-email">{item.contact_email}</span>}
|
||||
{enableShowLoginIDWhenSearchUser && <span className="user-option-email">{item.login_id}</span>}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<img src={item.avatar_url} className="select-module select-module-icon avatar" alt=""/>
|
||||
<span className='select-module select-module-name'>{item.name}</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
this.options.push(obj);
|
||||
}
|
||||
callback(this.options);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
onValueChanged = (newSearchValue) => {
|
||||
this.setState({
|
||||
searchValue: newSearchValue
|
||||
});
|
||||
const searchValue = newSearchValue.trim();
|
||||
if (searchValue.length === 0) {
|
||||
this.setState({
|
||||
searchedUsers: [],
|
||||
highlightIndex: -1,
|
||||
});
|
||||
} else {
|
||||
seafileAPI.searchUsers(newSearchValue.trim()).then((res) => {
|
||||
this.setState({
|
||||
searchedUsers: res.data.users,
|
||||
highlightIndex: res.data.users.length > 0 ? 0 : -1,
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
clearSelect = () => {
|
||||
this.userSelect.current.onChange([], { action: 'clear' });
|
||||
componentDidMount() {
|
||||
if (this.ref) {
|
||||
const { bottom } = this.ref.getBoundingClientRect();
|
||||
if (bottom > window.innerHeight) {
|
||||
this.ref.style.top = `${window.innerHeight - bottom}px`;
|
||||
}
|
||||
}
|
||||
if (this.container && this.userItem) {
|
||||
this.setState({
|
||||
maxItemNum: this.getMaxItemNum(),
|
||||
itemHeight: parseInt(getComputedStyle(this.userItem, null).height)
|
||||
});
|
||||
}
|
||||
document.addEventListener('keydown', this.onHotKey, true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.onHotKey, true);
|
||||
}
|
||||
|
||||
onClickOutside = (e) => {
|
||||
if (e.target.id !== 'user-select' && this.state.isPopoverOpen) {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
searchedUsers: [],
|
||||
searchValue: '',
|
||||
highlightIndex: -1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getMaxItemNum = () => {
|
||||
let userContainerStyle = getComputedStyle(this.container, null);
|
||||
let userItemStyle = getComputedStyle(this.userItem, null);
|
||||
let maxContainerItemNum = Math.floor(parseInt(userContainerStyle.maxHeight) / parseInt(userItemStyle.height));
|
||||
return maxContainerItemNum - 1;
|
||||
};
|
||||
|
||||
onHotKey = (e) => {
|
||||
if (e.keyCode === KeyCodes.Enter) {
|
||||
this.onEnter(e);
|
||||
} else if (e.keyCode === KeyCodes.UpArrow) {
|
||||
this.onUpArrow(e);
|
||||
} else if (e.keyCode === KeyCodes.DownArrow) {
|
||||
this.onDownArrow(e);
|
||||
} else if (e.keyCode === KeyCodes.Escape) {
|
||||
this.onEsc(e);
|
||||
}
|
||||
};
|
||||
|
||||
onEnter = (e) => {
|
||||
e.preventDefault();
|
||||
let user;
|
||||
if (this.state.searchedUsers.length === 1) {
|
||||
user = this.state.searchedUsers[0];
|
||||
} else if (this.state.highlightIndex > -1) {
|
||||
user = this.state.searchedUsers[this.state.highlightIndex];
|
||||
}
|
||||
if (user) {
|
||||
this.onUserClick(user);
|
||||
}
|
||||
};
|
||||
|
||||
onUpArrow = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let { highlightIndex, maxItemNum, itemHeight } = this.state;
|
||||
if (highlightIndex > 0) {
|
||||
this.setState({ highlightIndex: highlightIndex - 1 }, () => {
|
||||
if (highlightIndex < this.state.searchedUsers.length - maxItemNum) {
|
||||
this.container.scrollTop -= itemHeight;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({ highlightIndex: this.state.searchedUsers.length - 1 }, () => {
|
||||
this.container.scrollTop = this.container.scrollHeight;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onDownArrow = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let { highlightIndex, maxItemNum, itemHeight } = this.state;
|
||||
if (highlightIndex < this.state.searchedUsers.length - 1) {
|
||||
this.setState({ highlightIndex: highlightIndex + 1 }, () => {
|
||||
if (highlightIndex >= maxItemNum) {
|
||||
this.container.scrollTop += itemHeight;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({ highlightIndex: 0 }, () => {
|
||||
this.container.scrollTop = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onEsc = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ isPopoverOpen: false });
|
||||
};
|
||||
|
||||
onUserClick = (user) => {
|
||||
const { isMulti = true } = this.props;
|
||||
let selectedUsers = this.props.selectedUsers.slice(0);
|
||||
if (isMulti) {
|
||||
const index = selectedUsers.findIndex(item => item.email === user.email);
|
||||
if (index > -1) {
|
||||
selectedUsers.splice(index, 1);
|
||||
} else {
|
||||
selectedUsers.push(user);
|
||||
}
|
||||
} else {
|
||||
selectedUsers = [user];
|
||||
}
|
||||
this.props.onSelectChange(selectedUsers);
|
||||
};
|
||||
|
||||
onKeyDown = (e) => {
|
||||
if (e.keyCode === KeyCodes.LeftArrow || e.keyCode === KeyCodes.RightArrow) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
onDeleteSelectedCollaborator = (user) => {
|
||||
const { selectedUsers = [] } = this.props;
|
||||
const newSelectedCollaborator = selectedUsers.filter(item => item.email !== user.email);
|
||||
this.props.onSelectChange(newSelectedCollaborator);
|
||||
};
|
||||
|
||||
onTogglePopover = () => {
|
||||
this.setState({ isPopoverOpen: !this.state.isPopoverOpen });
|
||||
if (!this.state.isPopoverOpen) {
|
||||
this.onValueChanged(this.state.searchValue);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const searchValue = this.state.searchValue;
|
||||
const { searchValue, highlightIndex, searchedUsers } = this.state;
|
||||
const { className = '', selectedUsers = [] } = this.props;
|
||||
return (
|
||||
<AsyncSelect
|
||||
isClearable
|
||||
classNamePrefix
|
||||
components={{
|
||||
NoOptionsMessage: (props) => {
|
||||
return (
|
||||
<div {...props.innerProps} style={NoOptionsStyle}>
|
||||
{searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}
|
||||
<ClickOutside onClickOutside={this.onClickOutside}>
|
||||
<>
|
||||
<div className={classnames('selected-user-item-container form-control d-flex align-items-center', className, { 'focus': this.state.isPopoverOpen })} id="user-select" onClick={this.onTogglePopover}>
|
||||
{selectedUsers.map((user, index) => {
|
||||
return (
|
||||
<UserItem
|
||||
key={index}
|
||||
user={user}
|
||||
enableDeleteUser={true}
|
||||
onDeleteUser={this.onDeleteSelectedCollaborator}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selectedUsers.length === 0 && (
|
||||
<div className="user-select-placeholder">
|
||||
{this.props.placeholder || gettext('Select users')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}}
|
||||
isMulti={true}
|
||||
loadOptions={this.loadOptions}
|
||||
onChange={this.handleSelectChange}
|
||||
onInputChange={this.onInputChange}
|
||||
placeholder={this.props.placeholder}
|
||||
className={`user-select ${this.props.className || ''}`}
|
||||
value={this.props.value}
|
||||
ref={this.userSelect}
|
||||
styles={UserSelectStyle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Popover
|
||||
placement="bottom-start"
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
target={'user-select'}
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
className="user-select-popover"
|
||||
>
|
||||
<div className="user-select-container" ref={ref => this.ref = ref} onMouseDown={e => e.stopPropagation()}>
|
||||
<div className="user-search-container">
|
||||
<SearchInput
|
||||
autoFocus={true}
|
||||
placeholder={this.props.placeholder || gettext('Search users')}
|
||||
value={searchValue}
|
||||
onChange={this.onValueChanged}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="user-list-container" ref={ref => this.container = ref}>
|
||||
{searchedUsers.length > 0 && (
|
||||
searchedUsers.map((user, index) => {
|
||||
return (
|
||||
<div
|
||||
key={user.email}
|
||||
className={classnames('user-item-container', { 'user-item-container-highlight': index === highlightIndex })}
|
||||
ref={ref => this.userItem = ref}
|
||||
onClick={this.onUserClick.bind(this, user)}
|
||||
>
|
||||
<UserItem user={user} enableDeleteUser={false} />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
{searchedUsers.length === 0 &&
|
||||
<div className="no-search-result">
|
||||
{searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
</ClickOutside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +1,59 @@
|
||||
.user-option-name {
|
||||
.selected-user-item-container {
|
||||
flex-wrap: wrap;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.selected-user-item-container .user-select-placeholder {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.user-select-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-select-container .user-search-container {
|
||||
padding: 10px 10px 0 10px;
|
||||
}
|
||||
|
||||
.user-select-container .user-search-container input {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.user-select-container .user-list-container {
|
||||
min-height: 160px;
|
||||
max-height: 200px;
|
||||
margin: 10px 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.user-select-container .user-list-container .user-item-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-option-email {
|
||||
font-size: 12px;
|
||||
.user-select-container .user-list-container .user-item-container:hover,
|
||||
.user-select-container .user-list-container .user-item-container-highlight {
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* dropdown menu avatar is 24*24px, selection box avatar is 16*16px */
|
||||
.user-select .true__value-container .select-module.select-module-icon.avatar {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
.no-search-result {
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
padding-left: 10px;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.user-select .true__value-container .true__multi-value__label {
|
||||
padding: 0px;
|
||||
.user-select-popover .popover {
|
||||
width: 385px;
|
||||
max-width: 385px;
|
||||
}
|
||||
|
||||
.user-select .true__value-container .true__multi-value__label .select-module.select-module-icon.avatar {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.user-select .true__value-container .select-module.select-module-name {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1;
|
||||
line-height: 20px;
|
||||
margin-left: 5px;
|
||||
.user-select-popover .popover .user-item {
|
||||
background: transparent;
|
||||
}
|
||||
|
Reference in New Issue
Block a user