From ca5c2fb82e22c1c71c4f9474d6afa2f8f11be3a9 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 3 Mar 2017 13:48:37 +0000 Subject: [PATCH 1/6] Allow user to choose from existing DMs on new chat When creating a new chat with one person, show a dialog that asks the user whether they'd like to use an existing chat or actually create a new room. Fixes https://github.com/vector-im/riot-web/issues/2760 --- src/component-index.js | 2 + .../views/dialogs/ChatCreateOrReuseDialog.js | 102 ++++++++++++++++++ .../views/dialogs/ChatInviteDialog.js | 58 +++++----- src/components/views/rooms/RoomTile.js | 4 + 4 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 src/components/views/dialogs/ChatCreateOrReuseDialog.js diff --git a/src/component-index.js b/src/component-index.js index c705150e12..2644f1a379 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -75,6 +75,8 @@ import views$create_room$RoomAlias from './components/views/create_room/RoomAlia views$create_room$RoomAlias && (module.exports.components['views.create_room.RoomAlias'] = views$create_room$RoomAlias); import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog'; views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog); +import views$dialogs$ChatCreateOrReuseDialog from './components/views/dialogs/ChatCreateOrReuseDialog'; +views$dialogs$ChatCreateOrReuseDialog && (module.exports.components['views.dialogs.ChatCreateOrReuseDialog'] = views$dialogs$ChatCreateOrReuseDialog); import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog'; views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog); import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog'; diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js new file mode 100644 index 0000000000..241c7755d2 --- /dev/null +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -0,0 +1,102 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from '../../../index'; +import dis from '../../../dispatcher'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import DMRoomMap from '../../../utils/DMRoomMap'; +import AccessibleButton from '../elements/AccessibleButton'; +import Unread from '../../../Unread'; +import classNames from 'classnames'; +import createRoom from '../../../createRoom'; + +export default class CreateOrReuseChatDialog extends React.Component { + + constructor (props) { + super(props); + this._onNewDMClick = this._onNewDMClick.bind(this); + } + + _onNewDMClick () { + createRoom({dmUserId: this.props.userId}); + this.props.onFinished(true); + } + + render () { + const client = MatrixClientPeg.get(); + + const dmRoomMap = new DMRoomMap(client); + const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.userId); + + const RoomTile = sdk.getComponent("rooms.RoomTile"); + + const tiles = []; + for (const roomId of dmRooms) { + const room = client.getRoom(roomId); + if (room) { + const me = room.getMember(client.credentials.userId); + const highlight = ( + room.getUnreadNotificationCount('highlight') > 0 || + me.membership == "invite" + ); + tiles.push( + this.props.onFinished(true)} + /> + ); + } + } + + const labelClasses = classNames({ + mx_MemberInfo_createRoom_label: true, + mx_RoomTile_name: true, + }); + const startNewChat = +
+ +
+
Start new chat
+
; + + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + return ( + { + this.props.onFinished(false) + }} + title='Create a new chat or reuse an existing one' + > +

Direct chats

+ {tiles} + {startNewChat} +
+ ); + } +} + +CreateOrReuseChatDialog.propTyps = { + userId: React.PropTypes.string.isRequired, + onFinished: React.PropTypes.func.isRequired, +}; diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index ca3b07aa00..db50a12904 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -97,18 +97,27 @@ module.exports = React.createClass({ if (inviteList === null) return; } + const addrTexts = inviteList.map(addr => addr.address); if (inviteList.length > 0) { - if (this._isDmChat(inviteList)) { + if (this._isDmChat(addrTexts)) { + const userId = inviteList[0].address; // Direct Message chat - var room = this._getDirectMessageRoom(inviteList[0]); - if (room) { - // A Direct Message room already exists for this user and you - // so go straight to that room - dis.dispatch({ - action: 'view_room', - room_id: room.roomId, + const rooms = this._getDirectMessageRooms(userId); + if (rooms.length > 0) { + // A Direct Message room already exists for this user, so select a + // room from a list that is similar to the one in MemberInfo panel + const ChatCreateOrReuseDialog = sdk.getComponent( + "views.dialogs.ChatCreateOrReuseDialog" + ); + Modal.createDialog(ChatCreateOrReuseDialog, { + userId: userId, + onFinished: (success) => { + if (success) { + this.props.onFinished(true, inviteList[0]); + } + // else show this ChatInviteDialog again + } }); - this.props.onFinished(true, inviteList[0]); } else { this._startChat(inviteList); } @@ -238,22 +247,20 @@ module.exports = React.createClass({ if (this._cancelThreepidLookup) this._cancelThreepidLookup(); }, - _getDirectMessageRoom: function(addr) { + _getDirectMessageRooms: function(addr) { const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); - var dmRooms = dmRoomMap.getDMRoomsForUserId(addr); - if (dmRooms.length > 0) { - // Cycle through all the DM rooms and find the first non forgotten or parted room - for (let i = 0; i < dmRooms.length; i++) { - let room = MatrixClientPeg.get().getRoom(dmRooms[i]); - if (room) { - const me = room.getMember(MatrixClientPeg.get().credentials.userId); - if (me.membership == 'join') { - return room; - } + const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); + const rooms = []; + dmRooms.forEach(dmRoom => { + let room = MatrixClientPeg.get().getRoom(dmRoom); + if (room) { + const me = room.getMember(MatrixClientPeg.get().credentials.userId); + if (me.membership == 'join') { + rooms.push(room); } } - } - return null; + }); + return rooms; }, _startChat: function(addrs) { @@ -386,8 +393,11 @@ module.exports = React.createClass({ return false; }, - _isDmChat: function(addrs) { - if (addrs.length === 1 && getAddressType(addrs[0]) === "mx" && !this.props.roomId) { + _isDmChat: function(addrTexts) { + if (addrTexts.length === 1 && + getAddressType(addrTexts[0]) === "mx" && + !this.props.roomId + ) { return true; } else { return false; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index f6c0f7034e..485b567ddc 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -35,6 +35,7 @@ module.exports = React.createClass({ propTypes: { connectDragSource: React.PropTypes.func, connectDropTarget: React.PropTypes.func, + onClick: React.PropTypes.func, isDragging: React.PropTypes.bool, room: React.PropTypes.object.isRequired, @@ -104,6 +105,9 @@ module.exports = React.createClass({ action: 'view_room', room_id: this.props.room.roomId, }); + if (this.props.onClick) { + this.props.onClick(); + } }, onMouseEnter: function() { From a122b5d88ecaa592869baa434a3b0c36c25d6f7b Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 6 Mar 2017 14:22:12 +0000 Subject: [PATCH 2/6] Style --- src/components/views/dialogs/ChatCreateOrReuseDialog.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 241c7755d2..129dc24aac 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -26,17 +26,17 @@ import createRoom from '../../../createRoom'; export default class CreateOrReuseChatDialog extends React.Component { - constructor (props) { + constructor(props) { super(props); this._onNewDMClick = this._onNewDMClick.bind(this); } - _onNewDMClick () { + _onNewDMClick() { createRoom({dmUserId: this.props.userId}); this.props.onFinished(true); } - render () { + render() { const client = MatrixClientPeg.get(); const dmRoomMap = new DMRoomMap(client); From 4548d1b824dc869dcbe6acde201466dbb0eb4530 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 6 Mar 2017 14:51:01 +0000 Subject: [PATCH 3/6] Use dispatch instead of passing `onClick`, adjust dialog wording. --- .../views/dialogs/ChatCreateOrReuseDialog.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 129dc24aac..676ed0ce0a 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -29,6 +29,16 @@ export default class CreateOrReuseChatDialog extends React.Component { constructor(props) { super(props); this._onNewDMClick = this._onNewDMClick.bind(this); + dis.register(this._onAction.bind(this)); + } + + _onAction(payload) { + switch(payload.action) { + case 'view_room': + this.props.onFinished(true); + break; + default: + } } _onNewDMClick() { @@ -60,7 +70,6 @@ export default class CreateOrReuseChatDialog extends React.Component { unread={Unread.doesRoomHaveUnreadMessages(room)} highlight={highlight} isInvite={me.membership == "invite"} - onClick={() => this.props.onFinished(true)} /> ); } @@ -88,7 +97,7 @@ export default class CreateOrReuseChatDialog extends React.Component { }} title='Create a new chat or reuse an existing one' > -

Direct chats

+ You already have existing direct chats with this user: {tiles} {startNewChat} From 20fa36325f9ad25b6bbf4e7874411c710d092aff Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 6 Mar 2017 15:01:46 +0000 Subject: [PATCH 4/6] Remember to unregister on unmoun --- src/components/views/dialogs/ChatCreateOrReuseDialog.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 676ed0ce0a..53c5113b5d 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -29,7 +29,11 @@ export default class CreateOrReuseChatDialog extends React.Component { constructor(props) { super(props); this._onNewDMClick = this._onNewDMClick.bind(this); - dis.register(this._onAction.bind(this)); + this.dispatcherRef = dis.register(this._onAction.bind(this)); + } + + componentWillUnmount() { + dis.unregister(this.dispatcherRef); } _onAction(payload) { From bf348a0f7879dd65100cdc5fe3e06f800fd36f48 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 6 Mar 2017 17:44:29 +0000 Subject: [PATCH 5/6] Instead of listening for view_room, use a callback But make sure that nothing other than the callback is done when RoomTile is clicked. --- .../views/dialogs/ChatCreateOrReuseDialog.js | 30 ++++++++----------- src/components/views/rooms/MemberInfo.js | 8 +++++ src/components/views/rooms/RoomTile.js | 6 +--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 53c5113b5d..7761e25010 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -28,28 +28,23 @@ export default class CreateOrReuseChatDialog extends React.Component { constructor(props) { super(props); - this._onNewDMClick = this._onNewDMClick.bind(this); - this.dispatcherRef = dis.register(this._onAction.bind(this)); + this.onNewDMClick = this.onNewDMClick.bind(this); + this.onRoomTileClick = this.onRoomTileClick.bind(this); } - componentWillUnmount() { - dis.unregister(this.dispatcherRef); - } - - _onAction(payload) { - switch(payload.action) { - case 'view_room': - this.props.onFinished(true); - break; - default: - } - } - - _onNewDMClick() { + onNewDMClick() { createRoom({dmUserId: this.props.userId}); this.props.onFinished(true); } + onRoomTileClick(roomId) { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + this.props.onFinished(true); + } + render() { const client = MatrixClientPeg.get(); @@ -74,6 +69,7 @@ export default class CreateOrReuseChatDialog extends React.Component { unread={Unread.doesRoomHaveUnreadMessages(room)} highlight={highlight} isInvite={me.membership == "invite"} + onClick={this.onRoomTileClick} /> ); } @@ -85,7 +81,7 @@ export default class CreateOrReuseChatDialog extends React.Component { }); const startNewChat =
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 0c54565b9d..995453e9c1 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -553,6 +553,13 @@ module.exports = WithMatrixClient(React.createClass({ Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); }, + onRoomTileClick(roomId) { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + }, + _renderDevices: function() { if (!this._enableDevices) { return null; @@ -613,6 +620,7 @@ module.exports = WithMatrixClient(React.createClass({ unread={Unread.doesRoomHaveUnreadMessages(room)} highlight={highlight} isInvite={me.membership == "invite"} + onClick={this.onRoomTileClick} /> ); } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 485b567ddc..1fd293cae8 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -101,12 +101,8 @@ module.exports = React.createClass({ }, onClick: function() { - dis.dispatch({ - action: 'view_room', - room_id: this.props.room.roomId, - }); if (this.props.onClick) { - this.props.onClick(); + this.props.onClick(this.props.room.roomId); } }, From 391886cac46dc6ae50188b1c6c63e6bfb76a0f1f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 6 Mar 2017 17:45:25 +0000 Subject: [PATCH 6/6] Remove dis as a dep in RoomTile --- src/components/views/rooms/RoomTile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 1fd293cae8..7d9034edd2 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -19,7 +19,6 @@ limitations under the License. var React = require('react'); var ReactDOM = require("react-dom"); var classNames = require('classnames'); -var dis = require("../../../dispatcher"); var MatrixClientPeg = require('../../../MatrixClientPeg'); import DMRoomMap from '../../../utils/DMRoomMap'; var sdk = require('../../../index');