diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 2c24c398e0..1b4b5cb809 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -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'; @@ -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, }); @@ -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 ; }); + let catHeader = null; if (this.props.category && this.props.category.profile) { catHeader =
{this.props.category.profile.name}
; } - return
+ return
{catHeader} {roomNodes}
; + // TODO: Modify UserPickerDialog to allow picking of rooms, and then use it here + // const TintableSvg = sdk.getComponent("elements.TintableSvg"); + // + // + //
+ // {_t('Add a Room')} + //
+ //
}, }); @@ -122,9 +138,59 @@ 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 ? + ( + +
+ {_t('Add a User')} +
+
) : null; const userNodes = this.props.users.map((u) => { return ; }); @@ -132,9 +198,10 @@ const RoleUserList = React.createClass({ if (this.props.role && this.props.role.profile) { roleHeader =
{this.props.role.profile.name}
; } - return
+ return
{roleHeader} {userNodes} + {addButton}
; }, }); @@ -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 = {this.props.summaryInfo.user_id}; + const userNameNode = {name}; + const httpUrl = MatrixClientPeg.get() + .mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64); return +
{userNameNode}
; }, @@ -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) => { @@ -386,13 +454,16 @@ export default React.createClass({ } }); - let defaultCategoryNode = null; - if (defaultCategoryRooms.length > 0) { - defaultCategoryNode = ; - } + const defaultCategoryNode = ; const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => { const cat = summary.rooms_section.categories[catId]; - return ; + return ; }); return
@@ -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) => { @@ -424,13 +493,18 @@ export default React.createClass({ } }); - let noRoleNode = null; - if (noRoleUsers.length > 0) { - noRoleNode = ; - } + const noRoleNode = ; const roleUserNodes = Object.keys(roleUsers).map((roleId) => { const role = summary.users_section.roles[roleId]; - return ; + return ; }); return
@@ -561,6 +635,8 @@ export default React.createClass({ onChange={this._onLongDescChange} tabIndex="3" /> + {this._getFeaturedRoomsNode()} + {this._getFeaturedUsersNode()}
; } else { const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null; diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index b2fdd7035d..415a3d910e 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -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() { @@ -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); @@ -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, + }); + }); + }, + _doUserDirectorySearch: function(query) { this.setState({ busy: true, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 464ab63683..d759547e66 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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 web browser": "Robot check is currently unavailable on desktop - please use a web browser", - "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:" }