Merge pull request #455 from matrix-org/dbkr/memberinfo_list_rooms

Add DM rooms with that person to the MemberInfo panel
This commit is contained in:
David Baker 2016-09-09 17:30:32 +01:00 committed by GitHub
commit 78f53bd403
5 changed files with 181 additions and 120 deletions

View file

@ -26,12 +26,16 @@ limitations under the License.
* 'isTargetMod': boolean
*/
var React = require('react');
var classNames = require('classnames');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var dis = require("../../../dispatcher");
var Modal = require("../../../Modal");
var sdk = require('../../../index');
var UserSettingsStore = require('../../../UserSettingsStore');
var createRoom = require('../../../createRoom');
var DMRoomMap = require('../../../utils/DMRoomMap');
var Unread = require('../../../Unread');
var Receipt = require('../../../utils/Receipt');
module.exports = React.createClass({
displayName: 'MemberInfo',
@ -60,7 +64,6 @@ module.exports = React.createClass({
updating: 0,
devicesLoading: true,
devices: null,
existingOneToOneRoomId: null,
}
},
@ -72,14 +75,20 @@ module.exports = React.createClass({
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
UserSettingsStore.isFeatureEnabled("e2e_encryption");
this.setState({
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
});
const cli = MatrixClientPeg.get();
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
cli.on("Room", this.onRoom);
cli.on("deleteRoom", this.onDeleteRoom);
cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName);
cli.on("Room.receipt", this.onRoomReceipt);
cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("accountData", this.onAccountData);
},
componentDidMount: function() {
this._updateStateForNewMember(this.props.member);
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
},
componentWillReceiveProps: function(newProps) {
@ -92,65 +101,20 @@ module.exports = React.createClass({
var client = MatrixClientPeg.get();
if (client) {
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("Room", this.onRoom);
client.removeListener("deleteRoom", this.onDeleteRoom);
client.removeListener("Room.timeline", this.onRoomTimeline);
client.removeListener("Room.name", this.onRoomName);
client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("RoomState.events", this.onRoomStateEvents);
client.removeListener("RoomMember.name", this.onRoomMemberName);
client.removeListener("accountData", this.onAccountData);
}
if (this._cancelDeviceList) {
this._cancelDeviceList();
}
},
getExistingOneToOneRoomId: function() {
const rooms = MatrixClientPeg.get().getRooms();
const userIds = [
this.props.member.userId,
MatrixClientPeg.get().credentials.userId
];
let existingRoomId = null;
let invitedRoomId = null;
// roomId can be null here because of a hack in MatrixChat.onUserClick where we
// abuse this to view users rather than room members.
let currentMembers;
if (this.props.member.roomId) {
const currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
currentMembers = currentRoom.getJoinedMembers();
}
// reuse the first private 1:1 we find
existingRoomId = null;
for (let i = 0; i < rooms.length; i++) {
// don't try to reuse public 1:1 rooms
const join_rules = rooms[i].currentState.getStateEvents("m.room.join_rules", '');
if (join_rules && join_rules.getContent().join_rule === 'public') continue;
const members = rooms[i].getJoinedMembers();
if (members.length === 2 &&
userIds.indexOf(members[0].userId) !== -1 &&
userIds.indexOf(members[1].userId) !== -1)
{
existingRoomId = rooms[i].roomId;
break;
}
const invited = rooms[i].getMembersWithMembership('invite');
if (members.length === 1 &&
invited.length === 1 &&
userIds.indexOf(members[0].userId) !== -1 &&
userIds.indexOf(invited[0].userId) !== -1 &&
invitedRoomId === null)
{
invitedRoomId = rooms[i].roomId;
// keep looking: we'll use this one if there's nothing better
}
}
if (existingRoomId === null) {
existingRoomId = invitedRoomId;
}
return existingRoomId;
},
onDeviceVerificationChanged: function(userId, device) {
if (!this._enableDevices) {
return;
@ -164,6 +128,45 @@ module.exports = React.createClass({
}
},
onRoom: function(room) {
this.forceUpdate();
},
onDeleteRoom: function(roomId) {
this.forceUpdate();
},
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (toStartOfTimeline) return;
this.forceUpdate();
},
onRoomName: function(room) {
this.forceUpdate();
},
onRoomReceipt: function(receiptEvent, room) {
// because if we read a notification, it will affect notification count
// only bother updating if there's a receipt from us
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
this.forceUpdate();
}
},
onRoomStateEvents: function(ev, state) {
this.forceUpdate();
},
onRoomMemberName: function(ev, member) {
this.forceUpdate();
},
onAccountData: function(ev) {
if (ev.getType() == 'm.direct') {
this.forceUpdate();
}
},
_updateStateForNewMember: function(member) {
var newState = this._calculateOpsPermissions(member);
newState.devicesLoading = true;
@ -416,33 +419,16 @@ module.exports = React.createClass({
}
},
onChatClick: function() {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// TODO: keep existingOneToOneRoomId updated if we see any room member changes anywhere
const useExistingOneToOneRoom = this.state.existingOneToOneRoomId && (this.state.existingOneToOneRoomId !== this.props.member.roomId);
// check if there are any existing rooms with just us and them (1:1)
// If so, just view that room. If not, create a private room with them.
if (useExistingOneToOneRoom) {
dis.dispatch({
action: 'view_room',
room_id: this.state.existingOneToOneRoomId,
});
onNewDMClick: function() {
this.setState({ updating: this.state.updating + 1 });
createRoom({
createOpts: {
invite: [this.props.member.userId],
},
}).finally(() => {
this.props.onFinished();
}
else {
this.setState({ updating: this.state.updating + 1 });
createRoom({
createOpts: {
invite: [this.props.member.userId],
},
}).finally(() => {
this.props.onFinished();
this.setState({ updating: this.state.updating - 1 });
}).done();
}
this.setState({ updating: this.state.updating - 1 });
}).done();
},
onLeaveClick: function() {
@ -583,24 +569,50 @@ module.exports = React.createClass({
render: function() {
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
// FIXME: we're referring to a vector component from react-sdk
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId);
var label;
if (this.state.existingOneToOneRoomId) {
if (this.state.existingOneToOneRoomId == this.props.member.roomId) {
label = "Start new direct chat";
}
else {
label = "Go to direct chat";
const RoomTile = sdk.getComponent("rooms.RoomTile");
const tiles = [];
for (const roomId of dmRooms) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (room) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
const highlight = (
room.getUnreadNotificationCount('highlight') > 0 ||
me.membership == "invite"
);
tiles.push(
<RoomTile key={room.roomId} room={room}
collapsed={false}
selected={false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={highlight}
isInvite={me.membership == "invite"}
/>
);
}
}
else {
label = "Start direct chat";
}
startChat = <BottomLeftMenuTile collapsed={ false } img="img/create-big.svg"
label={ label } onClick={ this.onChatClick }/>
const labelClasses = classNames({
mx_MemberInfo_createRoom_label: true,
mx_RoomTile_name: true,
});
const startNewChat = <div
className="mx_MemberInfo_createRoom"
onClick={this.onNewDMClick}
>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>Start new direct chat</i></div>
</div>
startChat = <div>
{tiles}
{startNewChat}
</div>;
}
if (this.state.updating) {

View file

@ -27,6 +27,7 @@ var sdk = require('../../../index');
var rate_limited_func = require('../../../ratelimitedfunc');
var Rooms = require('../../../Rooms');
var DMRoomMap = require('../../../utils/DMRoomMap');
var Receipt = require('../../../utils/Receipt');
var HIDE_CONFERENCE_CHANS = true;
@ -154,13 +155,8 @@ module.exports = React.createClass({
onRoomReceipt: function(receiptEvent, room) {
// because if we read a notification, it will affect notification count
// only bother updating if there's a receipt from us
var receiptKeys = Object.keys(receiptEvent.getContent());
for (var i = 0; i < receiptKeys.length; ++i) {
var rcpt = receiptEvent.getContent()[receiptKeys[i]];
if (rcpt['m.read'] && rcpt['m.read'][MatrixClientPeg.get().credentials.userId]) {
this._delayedRefreshRoomList();
break;
}
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
this._delayedRefreshRoomList();
}
},

View file

@ -30,10 +30,9 @@ module.exports = React.createClass({
displayName: 'RoomTile',
propTypes: {
// TODO: We should *optionally* support DND stuff and ideally be impl agnostic about it
connectDragSource: React.PropTypes.func.isRequired,
connectDropTarget: React.PropTypes.func.isRequired,
isDragging: React.PropTypes.bool.isRequired,
connectDragSource: React.PropTypes.func,
connectDropTarget: React.PropTypes.func,
isDragging: React.PropTypes.bool,
room: React.PropTypes.object.isRequired,
collapsed: React.PropTypes.bool.isRequired,
@ -41,11 +40,15 @@ module.exports = React.createClass({
unread: React.PropTypes.bool.isRequired,
highlight: React.PropTypes.bool.isRequired,
isInvite: React.PropTypes.bool.isRequired,
roomSubList: React.PropTypes.object.isRequired,
refreshSubList: React.PropTypes.func.isRequired,
incomingCall: React.PropTypes.object,
},
getDefaultProps: function() {
return {
isDragging: false,
};
},
getInitialState: function() {
return({
hover : false,
@ -281,7 +284,7 @@ module.exports = React.createClass({
var connectDragSource = this.props.connectDragSource;
var connectDropTarget = this.props.connectDropTarget;
return connectDragSource(connectDropTarget(
let ret = (
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className={avatarClasses}>
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
@ -298,6 +301,11 @@ module.exports = React.createClass({
{ incomingCallBox }
{ tooltip }
</div>
));
);
if (connectDropTarget) ret = connectDropTarget(ret);
if (connectDragSource) ret = connectDragSource(ret);
return ret;
}
});