Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Move DM creation logic into DMInviteDialog
Browse files Browse the repository at this point in the history
Fixes element-hq/element-web#11645

The copy hasn't been reviewed by anyone and could probably use some work.
  • Loading branch information
turt2live committed Jan 15, 2020
1 parent fa17451 commit 4437447
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 18 deletions.
11 changes: 11 additions & 0 deletions res/css/views/dialogs/_DMInviteDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 6 additions & 8 deletions src/RoomInvite.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
92 changes: 82 additions & 10 deletions src/components/views/dialogs/DMInviteDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 = <Spinner w={20} h={20} />;
}

const userId = MatrixClientPeg.get().getUserId();
return (
Expand All @@ -755,15 +822,20 @@ export default class DMInviteDialog extends React.PureComponent {
</p>
<div className='mx_DMInviteDialog_addressBar'>
{this._renderEditor()}
{this._renderIdentityServerWarning()}
<AccessibleButton
kind="primary"
onClick={this._startDm}
className='mx_DMInviteDialog_goButton'
>
{_t("Go")}
</AccessibleButton>
<div className='mx_DMInviteDialog_buttonAndSpinner'>
<AccessibleButton
kind="primary"
onClick={this._startDm}
className='mx_DMInviteDialog_goButton'
disabled={this.state.busy}
>
{_t("Go")}
</AccessibleButton>
{spinner}
</div>
</div>
{this._renderIdentityServerWarning()}
<div className='error'>{this.state.errorText}</div>
{this._renderSection('recents')}
{this._renderSection('suggestions')}
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 21 additions & 0 deletions src/utils/DMRoomMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4437447

Please sign in to comment.