diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index be6663be67..4310c56363 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -26,12 +26,15 @@ 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'); module.exports = React.createClass({ displayName: 'MemberInfo', @@ -60,7 +63,6 @@ module.exports = React.createClass({ updating: 0, devicesLoading: true, devices: null, - existingOneToOneRoomId: null, } }, @@ -71,10 +73,6 @@ module.exports = React.createClass({ // feature is enabled in the user settings this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && UserSettingsStore.isFeatureEnabled("e2e_encryption"); - - this.setState({ - existingOneToOneRoomId: this.getExistingOneToOneRoomId() - }); }, componentDidMount: function() { @@ -98,59 +96,6 @@ module.exports = React.createClass({ } }, - 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; @@ -416,33 +361,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 +511,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( + + ); } } - else { - label = "Start direct chat"; - } - startChat = + const labelClasses = classNames({ + mx_MemberInfo_createRoom_label: true, + mx_RoomTile_name: true, + }); + const startNewChat =
+
+ +
+
Start new direct chat
+
+ + startChat =
+ {tiles} + {startNewChat} +
; } if (this.state.updating) { diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index b646b12e76..182b62fcb2 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -28,10 +28,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, @@ -39,11 +38,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, @@ -265,7 +268,7 @@ module.exports = React.createClass({ var connectDragSource = this.props.connectDragSource; var connectDropTarget = this.props.connectDropTarget; - return connectDragSource(connectDropTarget( + let ret = (
@@ -281,6 +284,11 @@ module.exports = React.createClass({ { incomingCallBox } { tooltip }
- )); + ); + + if (connectDropTarget) ret = connectDropTarget(ret); + if (connectDragSource) ret = connectDragSource(ret); + + return ret; } }); diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js index d92ae87e64..0089ebbe5b 100644 --- a/src/utils/DMRoomMap.js +++ b/src/utils/DMRoomMap.js @@ -21,18 +21,13 @@ limitations under the License. */ export default class DMRoomMap { constructor(matrixClient) { + this.roomToUser = null; + const mDirectEvent = matrixClient.getAccountData('m.direct'); if (!mDirectEvent) { this.userToRooms = {}; - this.roomToUser = {}; } else { this.userToRooms = mDirectEvent.getContent(); - this.roomToUser = {}; - for (const user of Object.keys(this.userToRooms)) { - for (const roomId of this.userToRooms[user]) { - this.roomToUser[roomId] = user; - } - } } } @@ -41,6 +36,24 @@ export default class DMRoomMap { } getUserIdForRoomId(roomId) { + if (this.roomToUser == null) { + // we lazily populate roomToUser so you can use + // this class just to call getDMRoomsForUserId + // which doesn't do very much, but is a fairly + // convenient wrapper and there's no point + // iterating through the map if getUserIdForRoomId() + // is never called. + this._populateRoomToUser(); + } return this.roomToUser[roomId]; } + + _populateRoomToUser() { + this.roomToUser = {}; + for (const user of Object.keys(this.userToRooms)) { + for (const roomId of this.userToRooms[user]) { + this.roomToUser[roomId] = user; + } + } + } }