Support full group membership cycle
Apart from knocking, ie. Invite / accept / reject / leave
This commit is contained in:
parent
55998028b4
commit
e77ea352e4
4 changed files with 164 additions and 7 deletions
|
@ -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 a new issue