Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

GroupView: Add a User #1402

Merged
merged 10 commits into from
Sep 21, 2017
114 changes: 95 additions & 19 deletions src/components/structures/GroupView.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.

import React from 'react';
import PropTypes from 'prop-types';
import Promise from 'bluebird';
import MatrixClientPeg from '../../MatrixClientPeg';
import sdk from '../../index';
import dis from '../../dispatcher';
Expand All @@ -38,6 +39,9 @@ const RoomSummaryType = PropTypes.shape({
const UserSummaryType = PropTypes.shape({
summaryInfo: PropTypes.shape({
user_id: PropTypes.string.isRequired,
role_id: PropTypes.string,
avatar_url: PropTypes.string,
displayname: PropTypes.string,
}).isRequired,
});

Expand All @@ -51,20 +55,32 @@ const CategoryRoomList = React.createClass({
name: PropTypes.string,
}).isRequired,
}),

// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},

render: function() {
const roomNodes = this.props.rooms.map((r) => {
return <FeaturedRoom key={r.room_id} summaryInfo={r} />;
});

let catHeader = null;
if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
}
return <div>
return <div className="mx_GroupView_featuredThings_container">
{catHeader}
{roomNodes}
</div>;
// TODO: Modify UserPickerDialog to allow picking of rooms, and then use it here
// const TintableSvg = sdk.getComponent("elements.TintableSvg");
// <AccessibleButton className="mx_GroupView_featuredThings_addButton">
// <TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
// <div className="mx_GroupView_featuredThings_addButton_label">
// {_t('Add a Room')}
// </div>
// </AccessibleButton>
},
});

Expand Down Expand Up @@ -122,19 +138,70 @@ const RoleUserList = React.createClass({
name: PropTypes.string,
}).isRequired,
}),
groupId: PropTypes.string.isRequired,

// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},

onAddUsersClicked: function(ev) {
ev.preventDefault();
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog");
Modal.createTrackedDialog('Add Users to Group Summary', '', UserPickerDialog, {
title: _t('Add users to the group summary'),
description: _t("Who would you like to add to this summary?"),
placeholder: _t("Name or matrix ID"),
button: _t("Add to summary"),
validAddressTypes: ['mx'],
groupId: this.props.groupId,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return MatrixClientPeg.get()
.addUserToGroupSummary(this.props.groupId, addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following users to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
},

render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
<div className="mx_GroupView_featuredThings_addButton_label">
{_t('Add a User')}
</div>
</AccessibleButton>) : null;
const userNodes = this.props.users.map((u) => {
return <FeaturedUser key={u.user_id} summaryInfo={u} />;
});
let roleHeader = null;
if (this.props.role && this.props.role.profile) {
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
}
return <div>
return <div className="mx_GroupView_featuredThings_container">
{roleHeader}
{userNodes}
{addButton}
</div>;
},
});
Expand All @@ -158,13 +225,16 @@ const FeaturedUser = React.createClass({
},

render: function() {
// Add avatar once we get profile info inline in the summary response
//const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;

const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
const userNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.user_id}</a>;
const userNameNode = <a href={permalink} onClick={this.onClick}>{name}</a>;
const httpUrl = MatrixClientPeg.get()
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);

return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
<BaseAvatar name={name} url={httpUrl} width={64} height={64} />
<div className="mx_GroupView_featuredThing_name">{userNameNode}</div>
</AccessibleButton>;
},
Expand Down Expand Up @@ -369,8 +439,6 @@ export default React.createClass({
_getFeaturedRoomsNode() {
const summary = this.state.summary;

if (summary.rooms_section.rooms.length == 0) return null;

const defaultCategoryRooms = [];
const categoryRooms = {};
summary.rooms_section.rooms.forEach((r) => {
Expand All @@ -386,13 +454,16 @@ export default React.createClass({
}
});

let defaultCategoryNode = null;
if (defaultCategoryRooms.length > 0) {
defaultCategoryNode = <CategoryRoomList rooms={defaultCategoryRooms} />;
}
const defaultCategoryNode = <CategoryRoomList
rooms={defaultCategoryRooms}
editing={this.state.editing}/>;
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
const cat = summary.rooms_section.categories[catId];
return <CategoryRoomList key={catId} rooms={categoryRooms[catId]} category={cat} />;
return <CategoryRoomList
key={catId}
rooms={categoryRooms[catId]}
category={cat}
editing={this.state.editing}/>;
});

return <div className="mx_GroupView_featuredThings">
Expand All @@ -407,8 +478,6 @@ export default React.createClass({
_getFeaturedUsersNode() {
const summary = this.state.summary;

if (summary.users_section.users.length == 0) return null;

const noRoleUsers = [];
const roleUsers = {};
summary.users_section.users.forEach((u) => {
Expand All @@ -424,13 +493,18 @@ export default React.createClass({
}
});

let noRoleNode = null;
if (noRoleUsers.length > 0) {
noRoleNode = <RoleUserList users={noRoleUsers} />;
}
const noRoleNode = <RoleUserList
users={noRoleUsers}
groupId={this.props.groupId}
editing={this.state.editing}/>;
const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
const role = summary.users_section.roles[roleId];
return <RoleUserList key={roleId} users={roleUsers[roleId]} role={role} />;
return <RoleUserList
key={roleId}
users={roleUsers[roleId]}
role={role}
groupId={this.props.groupId}
editing={this.state.editing}/>;
});

return <div className="mx_GroupView_featuredThings">
Expand Down Expand Up @@ -561,6 +635,8 @@ export default React.createClass({
onChange={this._onLongDescChange}
tabIndex="3"
/>
{this._getFeaturedRoomsNode()}
{this._getFeaturedUsersNode()}
</div>;
} else {
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
Expand Down
39 changes: 38 additions & 1 deletion src/components/views/dialogs/UserPickerDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module.exports = React.createClass({
focus: PropTypes.bool,
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
onFinished: PropTypes.func.isRequired,
groupId: PropTypes.string,
},

getDefaultProps: function() {
Expand Down Expand Up @@ -140,7 +141,9 @@ module.exports = React.createClass({
// Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => {
if (this.state.serverSupportsUserDirectory) {
if (this.props.groupId) {
this._doNaiveGroupSearch(query);
} else if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query);
} else {
this._doLocalSearch(query);
Expand Down Expand Up @@ -185,6 +188,40 @@ module.exports = React.createClass({
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},

_doNaiveGroupSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
this.setState({
busy: true,
query,
searchError: null,
});
MatrixClientPeg.get().getGroupUsers(this.props.groupId).then((resp) => {
const results = [];
resp.chunk.forEach((u) => {
const userIdMatch = u.user_id.toLowerCase().includes(lowerCaseQuery);
const displayNameMatch = (u.displayname || '').toLowerCase().includes(lowerCaseQuery);
if (!(userIdMatch || displayNameMatch)) {
return;
}
results.push({
user_id: u.user_id,
avatar_url: u.avatar_url,
display_name: u.displayname,
});
});
this._processResults(results, query);
}).catch((err) => {
console.error('Error whilst searching group users: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
}).done(() => {
this.setState({
busy: false,
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be fetching the group user list once and then doing a client-side filter when the query changes rather than waiting until you type something and then displaying the whole group user list?

Copy link
Contributor Author

@lukebarnard1 lukebarnard1 Sep 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that it's best to get the group list frequently and avoid early optimisation (so that the user doesn't have to close and open the dialog once someone has accepted the invite to a group, for example). But yes. I totally forgot to actually implement the filtering. 🤦‍♂️

},

_doUserDirectorySearch: function(query) {
this.setState({
busy: true,
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -886,5 +886,11 @@
"Leave %(groupName)s?": "Leave %(groupName)s?",
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Flair": "Flair"
"Flair": "Flair",
"Add a Room": "Add a Room",
"Add a User": "Add a User",
"Add users to the group summary": "Add users to the group summary",
"Who would you like to add to this summary?": "Who would you like to add to this summary?",
"Add to summary": "Add to summary",
"Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:"
}