Support full group membership cycle
Apart from knocking, ie. Invite / accept / reject / leavepull/21833/head
							parent
							
								
									55998028b4
								
							
						
					
					
						commit
						e77ea352e4
					
				|  | @ -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({ | |||
|         </div>; | ||||
|     }, | ||||
| 
 | ||||
|     _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 <div className="mx_GroupView_invitedSection"> | ||||
|                     <Spinner /> | ||||
|                 </div>; | ||||
|             } | ||||
| 
 | ||||
|             return <div className="mx_GroupView_invitedSection"> | ||||
|                 {_t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId})} | ||||
|                 <div className="mx_GroupView_membership_buttonContainer"> | ||||
|                     <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton" | ||||
|                         onClick={this._onAcceptInviteClick} | ||||
|                     > | ||||
|                         {_t('Accept')} | ||||
|                     </AccessibleButton> | ||||
|                     <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton" | ||||
|                         onClick={this._onRejectInviteClick} | ||||
|                     > | ||||
|                         {_t('Decline')} | ||||
|                     </AccessibleButton> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } else if (group.myMembership === 'join') { | ||||
|             return <div className="mx_GroupView_invitedSection"> | ||||
|                 {_t("You are a member of this group")} | ||||
|                 <div className="mx_GroupView_membership_buttonContainer"> | ||||
|                     <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton" | ||||
|                         onClick={this._onLeaveClick} | ||||
|                     > | ||||
|                         {_t('Leave')} | ||||
|                     </AccessibleButton> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } | ||||
| 
 | ||||
|         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( | ||||
|                     <AccessibleButton className="mx_GroupView_saveButton mx_RoomHeader_textButton" | ||||
|                     <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton" | ||||
|                         onClick={this._onSaveClick} key="_saveButton" | ||||
|                     > | ||||
|                         {_t('Save')} | ||||
|                     </AccessibleButton> | ||||
|                 ); | ||||
|                 rightButtons.push( | ||||
|                     <AccessibleButton className='mx_GroupView_cancelButton' onClick={this._onCancelClick} key="_cancelButton"> | ||||
|                     <AccessibleButton className='mx_GroupView_textButton' onClick={this._onCancelClick} key="_cancelButton"> | ||||
|                         <img src="img/cancel.svg" className='mx_filterFlipColor' | ||||
|                             width="18" height="18" alt={_t("Cancel")}/> | ||||
|                     </AccessibleButton> | ||||
|  | @ -475,6 +586,7 @@ export default React.createClass({ | |||
|                     description = sanitizedHtmlNode(summary.profile.long_description); | ||||
|                 } | ||||
|                 roomBody = <div> | ||||
|                     {this._getMembershipSection()} | ||||
|                     <div className="mx_GroupView_groupDesc">{description}</div> | ||||
|                     {this._getFeaturedRoomsNode()} | ||||
|                     {this._getFeaturedUsersNode()} | ||||
|  |  | |||
|  | @ -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")} | ||||
|             </button> | ||||
|         ) : null; | ||||
|         const buttonClasses = classnames({ | ||||
|             mx_Dialog_primary: true, | ||||
|             danger: this.props.danger, | ||||
|         }); | ||||
|         return ( | ||||
|             <BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={ this.onOk } | ||||
|  | @ -63,7 +71,7 @@ export default React.createClass({ | |||
|                     {this.props.description} | ||||
|                 </div> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}> | ||||
|                     <button className={buttonClasses} onClick={this.onOk} autoFocus={this.props.focus}> | ||||
|                         {this.props.button || _t('OK')} | ||||
|                     </button> | ||||
|                     {this.props.extraButtons} | ||||
|  |  | |||
|  | @ -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 <Loader />; | ||||
|         } | ||||
|  | @ -140,7 +165,7 @@ module.exports = withMatrixClient(React.createClass({ | |||
|         return ( | ||||
|             <div className="mx_MemberInfo"> | ||||
|                 <GeminiScrollbar autoshow={true}> | ||||
|                     <AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton> | ||||
|                     <AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton> | ||||
|                     <div className="mx_MemberInfo_avatar"> | ||||
|                         {avatar} | ||||
|                     </div> | ||||
|  |  | |||
|  | @ -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?" | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker