- {gettext('Add Admins')}
+ {gettext('Add Admin')}
{this.state.errMessage && {this.state.errMessage}}
diff --git a/frontend/src/components/dialog/share-to-user.js b/frontend/src/components/dialog/share-to-user.js
index 49304aeef8..79c8e8dd74 100644
--- a/frontend/src/components/dialog/share-to-user.js
+++ b/frontend/src/components/dialog/share-to-user.js
@@ -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 {
{showDeptBtn &&
{
- 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 && (
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-institution-member-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-institution-member-dialog.js
index c898efaa25..f222017c01 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-institution-member-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-institution-member-dialog.js
@@ -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}
/>
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-member-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-member-dialog.js
index 68529ecb5d..df94e50062 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-member-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-member-dialog.js
@@ -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 {
{ this.state.errMessage && {this.state.errMessage} }
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-batch-add-admin-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-batch-add-admin-dialog.js
index 2109e6b82e..46cd99d9dc 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-batch-add-admin-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-batch-add-admin-dialog.js
@@ -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}
/>
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-group-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-group-dialog.js
index 2b57c1271b..a8426287bb 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-group-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-group-dialog.js
@@ -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 {
/>
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-repo-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-repo-dialog.js
index a4e9e0c1cd..b33d52c4a5 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-repo-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-create-repo-dialog.js
@@ -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 {
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog.js
index 23c8ae8938..64a08b939f 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog.js
@@ -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 {
{gettext('Add Member')}
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-transfer-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-transfer-dialog.js
index 48ec2f3a1b..0b869e3b7d 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-transfer-dialog.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-group-transfer-dialog.js
@@ -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 {
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-repo-transfer-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-repo-transfer-dialog.js
deleted file mode 100644
index 19eeb528fc..0000000000
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-repo-transfer-dialog.js
+++ /dev/null
@@ -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 = '' + repoName + '';
- let msg = gettext('Transfer Library {library_name}');
- let message = msg.replace('{library_name}', innerSpan);
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-SysAdminRepoTransferDialog.propTypes = propTypes;
-
-export default SysAdminRepoTransferDialog;
diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-share-to-user.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-share-to-user.js
index 9a610664d4..a8cfc141aa 100644
--- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-share-to-user.js
+++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-share-to-user.js
@@ -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 {
|
diff --git a/frontend/src/components/dialog/transfer-dialog.js b/frontend/src/components/dialog/transfer-dialog.js
index e1ddf626d0..67eefaf280 100644
--- a/frontend/src/components/dialog/transfer-dialog.js
+++ b/frontend/src/components/dialog/transfer-dialog.js
@@ -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 {
' + Utils.HTMLescape(repoName) + '');
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 (
diff --git a/frontend/src/components/dialog/transfer-group-dialog.js b/frontend/src/components/dialog/transfer-group-dialog.js
index 145f6415eb..80bcd520a8 100644
--- a/frontend/src/components/dialog/transfer-group-dialog.js
+++ b/frontend/src/components/dialog/transfer-group-dialog.js
@@ -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 {
{gettext('Transfer group to')}
diff --git a/frontend/src/components/list-and-add-group-members.js b/frontend/src/components/list-and-add-group-members.js
index f2f473087a..4fe9eb7982 100644
--- a/frontend/src/components/list-and-add-group-members.js
+++ b/frontend/src/components/list-and-add-group-members.js
@@ -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 {
{showDeptBtn &&
}
- {this.state.selectedOption ?
+ {this.state.selectedUsers.length > 0 ?
:
}
diff --git a/frontend/src/components/share-link-panel/link-authenticated-users.js b/frontend/src/components/share-link-panel/link-authenticated-users.js
index 6b8168cb7a..add1bc248e 100644
--- a/frontend/src/components/share-link-panel/link-authenticated-users.js
+++ b/frontend/src/components/share-link-panel/link-authenticated-users.js
@@ -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 {
|
diff --git a/frontend/src/components/share-link-panel/link-creation.js b/frontend/src/components/share-link-panel/link-creation.js
index f17bae2e8b..f877630659 100644
--- a/frontend/src/components/share-link-panel/link-creation.js
+++ b/frontend/src/components/share-link-panel/link-creation.js
@@ -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 {
{this.state.currentScope === 'specific_users' &&
}
diff --git a/frontend/src/components/user-item/index.css b/frontend/src/components/user-item/index.css
new file mode 100644
index 0000000000..622c986c64
--- /dev/null
+++ b/frontend/src/components/user-item/index.css
@@ -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;
+}
diff --git a/frontend/src/components/user-item/index.js b/frontend/src/components/user-item/index.js
new file mode 100644
index 0000000000..88cc321baa
--- /dev/null
+++ b/frontend/src/components/user-item/index.js
@@ -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 (
+
+
+
+
+
+ {name}
+ {(enableShowContactEmailWhenSearchUser && !enableDeleteUser) && ({contact_email})}
+ {(enableShowLoginIDWhenSearchUser && !enableDeleteUser) && ({login_id})}
+
+ {enableDeleteUser && (
+
+
+
+ )}
+
+ );
+ }
+}
+
+UserItem.propTypes = propTypes;
+
+export default UserItem;
diff --git a/frontend/src/components/user-select.js b/frontend/src/components/user-select.js
index abf213ea3b..0edfd8ccc6 100644
--- a/frontend/src/components/user-select.js
+++ b/frontend/src/components/user-select.js
@@ -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) ? (
-
- 
-
- {item.name}
- {enableShowContactEmailWhenSearchUser && {item.contact_email}}
- {enableShowLoginIDWhenSearchUser && {item.login_id}}
-
-
- ) : (
-
-
- {item.name}
-
- );
- 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 (
- {
- return (
-
- {searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}
+
+ <>
+
+ {selectedUsers.map((user, index) => {
+ return (
+
+ );
+ })}
+ {selectedUsers.length === 0 && (
+
+ {this.props.placeholder || gettext('Select users')}
- );
- }
- }}
- 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}
- />
+ )}
+
+
+ this.ref = ref} onMouseDown={e => e.stopPropagation()}>
+
+
+
+ this.container = ref}>
+ {searchedUsers.length > 0 && (
+ searchedUsers.map((user, index) => {
+ return (
+ this.userItem = ref}
+ onClick={this.onUserClick.bind(this, user)}
+ >
+
+
+ );
+ })
+ )}
+ {searchedUsers.length === 0 &&
+
+ {searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}
+
+ }
+
+
+
+ >
+
);
}
}
diff --git a/frontend/src/css/user-select.css b/frontend/src/css/user-select.css
index 7421482900..0e46b5b12a 100644
--- a/frontend/src/css/user-select.css
+++ b/frontend/src/css/user-select.css
@@ -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;
}
| | |