From 1c6cecbd4310223ea2ec86c0697a72ca57924bec Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 14:41:29 +0100 Subject: [PATCH 01/10] Add "Add a User/Room" buttons and always display default lists --- src/components/structures/GroupView.js | 33 +++++++++++++++----------- src/i18n/strings/en_EN.json | 4 +++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 2c24c398e0..c49f840561 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -54,16 +54,24 @@ const CategoryRoomList = React.createClass({ }, render: function() { + const TintableSvg = sdk.getComponent("elements.TintableSvg"); 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} +
+ +
+ {_t('Add a Room')} +
+
; }, }); @@ -125,6 +133,7 @@ const RoleUserList = React.createClass({ }, render: function() { + const TintableSvg = sdk.getComponent("elements.TintableSvg"); const userNodes = this.props.users.map((u) => { return ; }); @@ -132,9 +141,15 @@ const RoleUserList = React.createClass({ if (this.props.role && this.props.role.profile) { roleHeader =
{this.props.role.profile.name}
; } - return
+ return
{roleHeader} {userNodes} +
+ +
+ {_t('Add a User')} +
+
; }, }); @@ -369,8 +384,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,10 +399,7 @@ 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 ; @@ -407,8 +417,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,10 +432,7 @@ 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 ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 464ab63683..2d1361b55e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -886,5 +886,7 @@ "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" } From 44c38652ab41f1cbca291f06bd8b247ba4723461 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 15:29:31 +0100 Subject: [PATCH 02/10] Implement UserPickerDialog for adding users Also, use AccessibleButtons. --- src/components/structures/GroupView.js | 38 ++++++++++++++++--- .../views/dialogs/UserPickerDialog.js | 25 +++++++++++- src/i18n/strings/en_EN.json | 5 ++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index c49f840561..e7d7e145c3 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -66,12 +66,12 @@ const CategoryRoomList = React.createClass({ return
{catHeader} {roomNodes} -
+
{_t('Add a Room')}
-
+
; }, }); @@ -130,6 +130,32 @@ const RoleUserList = React.createClass({ name: PropTypes.string, }).isRequired, }), + groupId: PropTypes.string.isRequired, + }, + + onUsersSelected: function(addrs) { + addrs.forEach((addr) => { + // const userId = addr.address; + // TODO: Add user to the group via API hit + }); + }, + + 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; + + this.onUsersSelected(addrs); + }, + }); }, render: function() { @@ -144,12 +170,12 @@ const RoleUserList = React.createClass({ return
{roleHeader} {userNodes} -
+
{_t('Add a User')}
-
+
; }, }); @@ -432,10 +458,10 @@ export default React.createClass({ } }); - const noRoleNode = ; + const noRoleNode = ; const roleUserNodes = Object.keys(roleUsers).map((roleId) => { const role = summary.users_section.roles[roleId]; - return ; + return ; }); return
diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index b2fdd7035d..2bd2320b21 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,26 @@ module.exports = React.createClass({ if (this._cancelThreepidLookup) this._cancelThreepidLookup(); }, + _doNaiveGroupSearch: function(query) { + this.setState({ + busy: true, + query, + searchError: null, + }); + MatrixClientPeg.get().getGroupUsers(this.props.groupId).then((resp) => { + this._processResults(resp.chunk, query); + }).catch((err) => { + console.error('Error whilst searching user directory: ', 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 2d1361b55e..8c8c2e4ac0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -888,5 +888,8 @@ "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", "Add a Room": "Add a Room", - "Add a User": "Add a User" + "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" } From 1c1bf82c2aa77756c16a4e5ca31b8e5daf5660a4 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 16:32:02 +0100 Subject: [PATCH 03/10] Add users to group summary using new API --- src/components/structures/GroupView.js | 13 ++++--------- src/components/views/dialogs/UserPickerDialog.js | 9 ++++++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index e7d7e145c3..f5ee35c1ea 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -133,13 +133,6 @@ const RoleUserList = React.createClass({ groupId: PropTypes.string.isRequired, }, - onUsersSelected: function(addrs) { - addrs.forEach((addr) => { - // const userId = addr.address; - // TODO: Add user to the group via API hit - }); - }, - onAddUsersClicked: function(ev) { ev.preventDefault(); const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); @@ -152,8 +145,10 @@ const RoleUserList = React.createClass({ groupId: this.props.groupId, onFinished: (success, addrs) => { if (!success) return; - - this.onUsersSelected(addrs); + addrs.map((addr) => { + return MatrixClientPeg.get() + .addUserToGroupSummary(this.props.groupId, addr.address); + }); }, }); }, diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 2bd2320b21..718780ace4 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -195,7 +195,14 @@ module.exports = React.createClass({ searchError: null, }); MatrixClientPeg.get().getGroupUsers(this.props.groupId).then((resp) => { - this._processResults(resp.chunk, query); + const results = resp.chunk.map((u) => { + return { + 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 user directory: ', err); this.setState({ From adf0a79585064c71eabbfd447fded71332ee0c39 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 16:54:12 +0100 Subject: [PATCH 04/10] Implement avatar, displayname for featured users Profile data has been added to the API response for users in the group summary --- src/components/structures/GroupView.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index f5ee35c1ea..bced15f4e5 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -38,6 +38,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, }); @@ -194,13 +197,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.slice(1); 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}
; }, From 5471431ee5bb204eeaec1510dc88cbaf18642d78 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 17:02:20 +0100 Subject: [PATCH 05/10] Disable "Add a Room" button for when we have a room picker --- src/components/structures/GroupView.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index bced15f4e5..3dea220111 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -57,7 +57,6 @@ const CategoryRoomList = React.createClass({ }, render: function() { - const TintableSvg = sdk.getComponent("elements.TintableSvg"); const roomNodes = this.props.rooms.map((r) => { return ; }); @@ -69,13 +68,15 @@ const CategoryRoomList = React.createClass({ return
{catHeader} {roomNodes} - - -
- {_t('Add a Room')} -
-
; + // TODO: Modify UserPickerDialog to allow picking of rooms, and then use it here + // const TintableSvg = sdk.getComponent("elements.TintableSvg"); + // + // + //
+ // {_t('Add a Room')} + //
+ //
}, }); From 7ef55946dabc7d273e3369983ebf64ec8c73ceed Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 20 Sep 2017 17:04:05 +0100 Subject: [PATCH 06/10] Fix console error log statement --- src/components/views/dialogs/UserPickerDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 718780ace4..606d70b17c 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -204,7 +204,7 @@ module.exports = React.createClass({ }); this._processResults(results, query); }).catch((err) => { - console.error('Error whilst searching user directory: ', err); + console.error('Error whilst searching group users: ', err); this.setState({ searchError: err.errcode ? err.message : _t('Something went wrong!'), }); From 03ddb63507d5a1239e63d7c3c21bc44421829122 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 21 Sep 2017 10:34:11 +0100 Subject: [PATCH 07/10] Remove redundant slice --- src/components/structures/GroupView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 3dea220111..57bce7cba9 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -199,7 +199,7 @@ const FeaturedUser = React.createClass({ render: function() { const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); - const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id.slice(1); + const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id; const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id; const userNameNode = {name}; From 9cd4cdf6df81442e31367bda5251bafc90c3f232 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 21 Sep 2017 10:52:28 +0100 Subject: [PATCH 08/10] Filter group users results based on query --- src/components/views/dialogs/UserPickerDialog.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 606d70b17c..415a3d910e 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -189,18 +189,25 @@ module.exports = React.createClass({ }, _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.map((u) => { - return { + 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) => { From 4d9c43b3c8c9cad85548fc0dd4002f2c568f8fd3 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 21 Sep 2017 12:34:16 +0100 Subject: [PATCH 09/10] Display dialog when errors occur whilst featuring users --- src/components/structures/GroupView.js | 23 +++++++++++++++++++++-- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 57bce7cba9..7d64820c5e 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'; @@ -149,9 +150,27 @@ const RoleUserList = React.createClass({ groupId: this.props.groupId, onFinished: (success, addrs) => { if (!success) return; - addrs.map((addr) => { + const errorList = []; + Promise.all(addrs.map((addr) => { return MatrixClientPeg.get() - .addUserToGroupSummary(this.props.groupId, addr.address); + .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(", "), + }); }); }, }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8c8c2e4ac0..d759547e66 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -891,5 +891,6 @@ "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" + "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:" } From 3f0e596e97b7c3c36ec16ab3568791497ee67d7e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 21 Sep 2017 12:44:17 +0100 Subject: [PATCH 10/10] Only show "Add" button when editing the group --- src/components/structures/GroupView.js | 44 ++++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 7d64820c5e..1b4b5cb809 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -55,6 +55,9 @@ const CategoryRoomList = React.createClass({ name: PropTypes.string, }).isRequired, }), + + // Whether the list should be editable + editing: PropTypes.bool.isRequired, }, render: function() { @@ -136,6 +139,9 @@ const RoleUserList = React.createClass({ }).isRequired, }), groupId: PropTypes.string.isRequired, + + // Whether the list should be editable + editing: PropTypes.bool.isRequired, }, onAddUsersClicked: function(ev) { @@ -178,6 +184,13 @@ const RoleUserList = React.createClass({ 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 ; }); @@ -188,12 +201,7 @@ const RoleUserList = React.createClass({ return
{roleHeader} {userNodes} - - -
- {_t('Add a User')} -
-
+ {addButton}
; }, }); @@ -446,10 +454,16 @@ export default React.createClass({ } }); - const defaultCategoryNode = ; + const defaultCategoryNode = ; const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => { const cat = summary.rooms_section.categories[catId]; - return ; + return ; }); return
@@ -479,10 +493,18 @@ export default React.createClass({ } }); - const noRoleNode = ; + const noRoleNode = ; const roleUserNodes = Object.keys(roleUsers).map((roleId) => { const role = summary.users_section.roles[roleId]; - return ; + return ; }); return
@@ -613,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;