From 443744733dc8fbea8c3bb805c29b1dbcb80a2a98 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Tue, 14 Jan 2020 23:32:00 -0700
Subject: [PATCH] Move DM creation logic into DMInviteDialog
Fixes https://github.com/vector-im/riot-web/issues/11645
The copy hasn't been reviewed by anyone and could probably use some work.
---
res/css/views/dialogs/_DMInviteDialog.scss | 11 +++
src/RoomInvite.js | 14 ++-
.../views/dialogs/DMInviteDialog.js | 92 +++++++++++++++++--
src/i18n/strings/en_EN.json | 2 +
src/utils/DMRoomMap.js | 21 +++++
5 files changed, 122 insertions(+), 18 deletions(-)
diff --git a/res/css/views/dialogs/_DMInviteDialog.scss b/res/css/views/dialogs/_DMInviteDialog.scss
index f806e85120c..5d58f3ae8bc 100644
--- a/res/css/views/dialogs/_DMInviteDialog.scss
+++ b/res/css/views/dialogs/_DMInviteDialog.scss
@@ -67,6 +67,17 @@ limitations under the License.
height: 25px;
line-height: 25px;
}
+
+ .mx_DMInviteDialog_buttonAndSpinner {
+ .mx_Spinner {
+ // Width and height are required to trick the layout engine.
+ width: 20px;
+ height: 20px;
+ margin-left: 5px;
+ display: inline-block;
+ vertical-align: middle;
+ }
+ }
}
.mx_DMInviteDialog_section {
diff --git a/src/RoomInvite.js b/src/RoomInvite.js
index ba9fe1f5410..675efe53c85 100644
--- a/src/RoomInvite.js
+++ b/src/RoomInvite.js
@@ -36,21 +36,19 @@ import SettingsStore from "./settings/SettingsStore";
* @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
-function inviteMultipleToRoom(roomId, addrs) {
+export function inviteMultipleToRoom(roomId, addrs) {
const inviter = new MultiInviter(roomId);
return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
}
export function showStartChatInviteDialog() {
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
+ // This new dialog handles the room creation internally - we don't need to worry about it.
const DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog");
- Modal.createTrackedDialog('Start DM', '', DMInviteDialog, {
- onFinished: (inviteIds) => {
- // TODO: Replace _onStartDmFinished with less hacks
- if (inviteIds.length > 0) _onStartDmFinished(true, inviteIds.map(i => ({address: i})));
- // else ignore and just do nothing
- },
- }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
+ Modal.createTrackedDialog(
+ 'Start DM', '', DMInviteDialog, {},
+ /*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
+ );
return;
}
diff --git a/src/components/views/dialogs/DMInviteDialog.js b/src/components/views/dialogs/DMInviteDialog.js
index 371768eb4e0..e82d63acad3 100644
--- a/src/components/views/dialogs/DMInviteDialog.js
+++ b/src/components/views/dialogs/DMInviteDialog.js
@@ -31,6 +31,8 @@ import {abbreviateUrl} from "../../../utils/UrlUtils";
import dis from "../../../dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
+import createRoom from "../../../createRoom";
+import {inviteMultipleToRoom} from "../../../RoomInvite";
// TODO: [TravisR] Make this generic for all kinds of invites
@@ -295,6 +297,10 @@ export default class DMInviteDialog extends React.PureComponent {
threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
tryingIdentityServer: false,
+
+ // These two flags are used for the 'Go' button to communicate what is going on.
+ busy: true,
+ errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."),
};
this._editorRef = createRef();
@@ -381,11 +387,66 @@ export default class DMInviteDialog extends React.PureComponent {
}
_startDm = () => {
- this.props.onFinished(this.state.targets.map(t => t.userId));
+ this.setState({busy: true});
+ const targetIds = this.state.targets.map(t => t.userId);
+
+ // Check if there is already a DM with these people and reuse it if possible.
+ const existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
+ if (existingRoom) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: existingRoom.roomId,
+ should_peek: false,
+ joining: false,
+ });
+ this.props.onFinished();
+ return;
+ }
+
+ // Check if it's a traditional DM and create the room if required.
+ // TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
+ let createRoomPromise = Promise.resolve();
+ if (targetIds.length === 1) {
+ createRoomPromise = createRoom({dmUserId: targetIds[0]})
+ } else {
+ // Create a boring room and try to invite the targets manually.
+ let room;
+ createRoomPromise = createRoom().then(roomId => {
+ room = MatrixClientPeg.get().getRoom(roomId);
+ return inviteMultipleToRoom(roomId, targetIds);
+ }).then(result => {
+ const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
+ if (failedUsers.length > 0) {
+ console.log("Failed to invite users: ", result);
+ this.setState({
+ busy: false,
+ errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
+ csvUsers: failedUsers.join(", "),
+ }),
+ });
+ return true; // abort
+ }
+ });
+ }
+
+ // the createRoom call will show the room for us, so we don't need to worry about that.
+ createRoomPromise.then(abort => {
+ if (abort === true) return; // only abort on true booleans, not roomIds or something
+ this.props.onFinished();
+ }).catch(err => {
+ console.error(err);
+ this.setState({
+ busy: false,
+ errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."),
+ });
+ });
};
_cancel = () => {
- this.props.onFinished([]);
+ // We do not want the user to close the dialog while an action is in progress
+ if (this.state.busy) return;
+
+ this.props.onFinished();
};
_updateFilter = (e) => {
@@ -735,6 +796,12 @@ export default class DMInviteDialog extends React.PureComponent {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
+ const Spinner = sdk.getComponent("elements.Spinner");
+
+ let spinner = null;
+ if (this.state.busy) {
+ spinner = ;
+ }
const userId = MatrixClientPeg.get().getUserId();
return (
@@ -755,15 +822,20 @@ export default class DMInviteDialog extends React.PureComponent {
{this._renderEditor()}
- {this._renderIdentityServerWarning()}
-
- {_t("Go")}
-
+
+
+ {_t("Go")}
+
+ {spinner}
+
+ {this._renderIdentityServerWarning()}
+ {this.state.errorText}
{this._renderSection('recents')}
{this._renderSection('suggestions')}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 9627ac0e14a..82f0cf8521e 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1423,6 +1423,8 @@
"View Servers in Room": "View Servers in Room",
"Toolbox": "Toolbox",
"Developer Tools": "Developer Tools",
+ "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
+ "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
"Failed to find the following users": "Failed to find the following users",
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
"Recent Conversations": "Recent Conversations",
diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js
index 498c073e0e6..e42d7247486 100644
--- a/src/utils/DMRoomMap.js
+++ b/src/utils/DMRoomMap.js
@@ -124,6 +124,27 @@ export default class DMRoomMap {
return this._getUserToRooms()[userId] || [];
}
+ /**
+ * Gets the DM room which the given IDs share, if any.
+ * @param {string[]} ids The identifiers (user IDs and email addresses) to look for.
+ * @returns {Room} The DM room which all IDs given share, or falsey if no common room.
+ */
+ getDMRoomForIdentifiers(ids) {
+ // TODO: [Canonical DMs] Handle lookups for email addresses.
+ // For now we'll pretend we only get user IDs and end up returning nothing for email addresses
+
+ let commonRooms = this.getDMRoomsForUserId(ids[0]);
+ for (let i = 1; i < ids.length; i++) {
+ const userRooms = this.getDMRoomsForUserId(ids[i]);
+ commonRooms = commonRooms.filter(r => userRooms.includes(r));
+ }
+
+ const joinedRooms = commonRooms.map(r => MatrixClientPeg.get().getRoom(r))
+ .filter(r => r && r.getMyMembership() === 'join');
+
+ return joinedRooms[0];
+ }
+
getUserIdForRoomId(roomId) {
if (this.roomToUser == null) {
// we lazily populate roomToUser so you can use