From e77ea352e4113c31a76d18f6a99d78438267ba68 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 21 Aug 2017 19:18:32 +0100 Subject: [PATCH] Support full group membership cycle Apart from knocking, ie. Invite / accept / reject / leave --- src/components/structures/GroupView.js | 116 +++++++++++++++++- .../views/dialogs/QuestionDialog.js | 10 +- .../views/groups/GroupMemberInfo.js | 31 ++++- src/i18n/strings/en_EN.json | 14 ++- 4 files changed, 164 insertions(+), 7 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 14d5d24bb5..e7e1e82b82 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd. +Copyright 2017 New Vector Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -183,12 +184,19 @@ export default React.createClass({ editing: false, saving: false, uploadingAvatar: false, + membershipBusy: false, }; }, componentWillMount: function() { this._changeAvatarComponent = null; this._loadGroupFromServer(this.props.groupId); + + MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership); + }, + + componentWillUnmount: function() { + MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership); }, componentWillReceiveProps: function(newProps) { @@ -202,6 +210,12 @@ export default React.createClass({ } }, + _onGroupMyMembership: function(group) { + if (group.groupId !== this.props.groupId) return; + + this.setState({membershipBusy: false}); + }, + _loadGroupFromServer: function(groupId) { MatrixClientPeg.get().getGroupSummary(groupId).done((res) => { this.setState({ @@ -299,6 +313,59 @@ export default React.createClass({ }).done(); }, + _onAcceptInviteClick: function() { + this.setState({membershipBusy: true}); + MatrixClientPeg.get().acceptGroupInvite(this.props.groupId).then(() => { + // don't reset membershipBusy here: wait for the membership change to come down the sync + }).catch((e) => { + this.setState({membershipBusy: false}); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Error accepting invite', '', ErrorDialog, { + title: _t("Error"), + description: _t("Unable to accept invite"), + }); + }); + }, + + _onRejectInviteClick: function() { + this.setState({membershipBusy: true}); + MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => { + // don't reset membershipBusy here: wait for the membership change to come down the sync + }).catch((e) => { + this.setState({membershipBusy: false}); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, { + title: _t("Error"), + description: _t("Unable to reject invite"), + }); + }); + }, + + _onLeaveClick: function() { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('Leave Group', '', QuestionDialog, { + title: _t("Leave Group"), + description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}), + button: _t("Leave"), + danger: true, + onFinished: (confirmed) => { + if (!confirmed) return; + + this.setState({membershipBusy: true}); + MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => { + // don't reset membershipBusy here: wait for the membership change to come down the sync + }).catch((e) => { + this.setState({membershipBusy: false}); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Error leaving room', '', ErrorDialog, { + title: _t("Error"), + description: _t("Unable to leave room"), + }); + }); + } + }); + }, + _getFeaturedRoomsNode() { const summary = this.state.summary; @@ -375,6 +442,50 @@ export default React.createClass({ ; }, + _getMembershipSection: function() { + const group = MatrixClientPeg.get().getGroup(this.props.groupId); + if (!group) return null; + + if (group.myMembership === 'invite') { + const Spinner = sdk.getComponent("elements.Spinner"); + + if (this.state.membershipBusy) { + return
+ +
; + } + + return
+ {_t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId})} +
+ + {_t('Accept')} + + + {_t('Decline')} + +
+
; + } else if (group.myMembership === 'join') { + return
+ {_t("You are a member of this group")} +
+ + {_t('Leave')} + +
+
; + } + + return null; + }, + render: function() { const GroupAvatar = sdk.getComponent("avatars.GroupAvatar"); const Loader = sdk.getComponent("elements.Spinner"); @@ -433,14 +544,14 @@ export default React.createClass({ tabIndex="2" />; rightButtons.push( - {_t('Save')} ); rightButtons.push( - + {_t("Cancel")}/ @@ -475,6 +586,7 @@ export default React.createClass({ description = sanitizedHtmlNode(summary.profile.long_description); } roomBody =
+ {this._getMembershipSection()}
{description}
{this._getFeaturedRoomsNode()} {this._getFeaturedUsersNode()} diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index ec9b95d7f7..22e68e3ac3 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ limitations under the License. import React from 'react'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; +import classnames from 'classnames'; export default React.createClass({ displayName: 'QuestionDialog', @@ -25,6 +27,7 @@ export default React.createClass({ description: React.PropTypes.node, extraButtons: React.PropTypes.node, button: React.PropTypes.string, + danger: React.PropTypes.bool, focus: React.PropTypes.bool, onFinished: React.PropTypes.func.isRequired, }, @@ -36,6 +39,7 @@ export default React.createClass({ extraButtons: null, focus: true, hasCancelButton: true, + danger: false, }; }, @@ -54,6 +58,10 @@ export default React.createClass({ {_t("Cancel")} ) : null; + const buttonClasses = classnames({ + mx_Dialog_primary: true, + danger: this.props.danger, + }); return (
- {this.props.extraButtons} diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index d37afb70a9..7083dff657 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -41,6 +41,14 @@ module.exports = withMatrixClient(React.createClass({ member: GroupMemberType, }, + getInitialState: function() { + return { + fetching: false, + removingUser: false, + members: null, + } + }, + componentWillMount: function() { this._fetchMembers(); }, @@ -67,11 +75,28 @@ module.exports = withMatrixClient(React.createClass({ action: _t('Remove from group'), danger: true, onFinished: (proceed) => { + if (!proceed) return; + + this.setState({removingUser: true}); + this.props.matrixClient.removeUserFromGroup(this.props.groupId, this.props.member.userId).then(() => { + dis.dispatch({ + action: "view_user", + member: null + }); + }).catch((e) => { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, { + title: _t('Error'), + description: _t('Failed to remove user from group'), + }); + }).finally(() => { + this.setState({removingUser: false}); + }); }, }); }, - onCancel: function(e) { + _onCancel: function(e) { dis.dispatch({ action: "view_user", member: null @@ -86,7 +111,7 @@ module.exports = withMatrixClient(React.createClass({ }, render: function() { - if (this.state.fetching) { + if (this.state.fetching || this.state.removingUser) { const Loader = sdk.getComponent("elements.Spinner"); return ; } @@ -140,7 +165,7 @@ module.exports = withMatrixClient(React.createClass({ return (
- +
{avatar}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 87ca75057c..fd6bbafd2b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -976,5 +976,17 @@ "Invite new group members": "Invite new group members", "Who would you like to add to this group?": "Who would you like to add to this group?", "Name or matrix ID": "Name or matrix ID", - "Invite to Group": "Invite to Group" + "Invite to Group": "Invite to Group", + "Unable to accept invite": "Unable to accept invite", + "Unable to leave room": "Unable to leave room", + "%(inviter)s has invited you to join this group": "%(inviter)s has invited you to join this group", + "You are a member of this group": "You are a member of this group", + "Leave": "Leave", + "Failed to remove user from group": "Failed to remove user from group", + "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", + "Failed to invite users group": "Failed to invite users group", + "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", + "Unable to reject invite": "Unable to reject invite", + "Leave Group": "Leave Group", + "Leave %(groupName)s?": "Leave %(groupName)s?" }