Support using the InviteDialog for both DMs and invites

For https://github.com/vector-im/riot-web/issues/11201
This commit is contained in:
Travis Ralston 2020-01-16 14:40:12 -07:00
parent 73fc91aa20
commit f350167408
3 changed files with 129 additions and 38 deletions

View file

@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd Copyright 2017, 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -26,6 +27,7 @@ import dis from './dispatcher';
import DMRoomMap from './utils/DMRoomMap'; import DMRoomMap from './utils/DMRoomMap';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
/** /**
* Invites multiple addresses to a room * Invites multiple addresses to a room
@ -46,7 +48,7 @@ export function showStartChatInviteDialog() {
// This new dialog handles the room creation internally - we don't need to worry about it. // This new dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Start DM', '', InviteDialog, {}, 'Start DM', '', InviteDialog, {kind: KIND_DM},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
); );
return; return;
@ -72,6 +74,16 @@ export function showStartChatInviteDialog() {
} }
export function showRoomInviteDialog(roomId) { export function showRoomInviteDialog(roomId) {
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
// This new dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
);
return;
}
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {

View file

@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/src/matrix"; import {RoomMember} from "matrix-js-sdk/src/matrix";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
@ -34,7 +34,8 @@ import {humanizeTime} from "../../../utils/humanize";
import createRoom from "../../../createRoom"; import createRoom from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite"; import {inviteMultipleToRoom} from "../../../RoomInvite";
// TODO: [TravisR] Make this generic for all kinds of invites export const KIND_DM = "dm";
export const KIND_INVITE = "invite";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
@ -276,13 +277,28 @@ export default class InviteDialog extends React.PureComponent {
static propTypes = { static propTypes = {
// Takes an array of user IDs/emails to invite. // Takes an array of user IDs/emails to invite.
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
// The kind of invite being performed. Assumed to be KIND_DM if
// not provided.
kind: PropTypes.string,
// The room ID this dialog is for. Only required for KIND_INVITE.
roomId: PropTypes.string,
};
static defaultProps = {
kind: KIND_DM,
}; };
_debounceTimer: number = null; _debounceTimer: number = null;
_editorRef: any = null; _editorRef: any = null;
constructor() { constructor(props) {
super(); super(props);
if (props.kind === KIND_INVITE && !props.roomId) {
throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog");
}
this.state = { this.state = {
targets: [], // array of Member objects (see interface above) targets: [], // array of Member objects (see interface above)
@ -390,6 +406,21 @@ export default class InviteDialog extends React.PureComponent {
return members.map(m => ({userId: m.member.userId, user: m.member})); return members.map(m => ({userId: m.member.userId, user: m.member}));
} }
_shouldAbortAfterInviteError(result): boolean {
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
}
return false;
}
_startDm = () => { _startDm = () => {
this.setState({busy: true}); this.setState({busy: true});
const targetIds = this.state.targets.map(t => t.userId); const targetIds = this.state.targets.map(t => t.userId);
@ -417,15 +448,7 @@ export default class InviteDialog extends React.PureComponent {
createRoomPromise = createRoom().then(roomId => { createRoomPromise = createRoom().then(roomId => {
return inviteMultipleToRoom(roomId, targetIds); return inviteMultipleToRoom(roomId, targetIds);
}).then(result => { }).then(result => {
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); if (this._shouldAbortAfterInviteError(result)) {
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 return true; // abort
} }
}); });
@ -444,6 +467,33 @@ export default class InviteDialog extends React.PureComponent {
}); });
}; };
_inviteUsers = () => {
this.setState({busy: true});
const targetIds = this.state.targets.map(t => t.userId);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) {
console.error("Failed to find the room to invite users to");
this.setState({
busy: false,
errorText: _t("Something went wrong trying to invite the users."),
});
return;
}
inviteMultipleToRoom(this.props.roomId, targetIds).then(result => {
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
this.props.onFinished();
}
}).catch(err => {
console.error(err);
this.setState({
busy: false,
errorText: _t("We couldn't invite those users. Please check the users you want to invite and try again."),
});
});
};
_cancel = () => { _cancel = () => {
// We do not want the user to close the dialog while an action is in progress // We do not want the user to close the dialog while an action is in progress
if (this.state.busy) return; if (this.state.busy) return;
@ -658,7 +708,11 @@ export default class InviteDialog extends React.PureComponent {
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null; const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
if (this.props.kind === KIND_INVITE) {
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
}
// Mix in the server results if we have any, but only if we're searching. We track the additional // Mix in the server results if we have any, but only if we're searching. We track the additional
// members separately because we want to filter sourceMembers but trust the mixin arrays to have // members separately because we want to filter sourceMembers but trust the mixin arrays to have
@ -805,33 +859,54 @@ export default class InviteDialog extends React.PureComponent {
spinner = <Spinner w={20} h={20} />; spinner = <Spinner w={20} h={20} />;
} }
const userId = MatrixClientPeg.get().getUserId();
let title;
let helpText;
let buttonText;
let goButtonFn;
if (this.props.kind === KIND_DM) {
const userId = MatrixClientPeg.get().getUserId();
title = _t("Direct Messages");
helpText = _t(
"If you can't find someone, ask them for their username, or share your " +
"username (%(userId)s) or <a>profile link</a>.",
{userId},
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
);
buttonText = _t("Go");
goButtonFn = this._startDm;
} else { // KIND_INVITE
title = _t("Invite to this room");
helpText = _t(
"If you can't find someone, ask them for their username (e.g. @user:server.com) or " +
"<a>share this room</a>.", {},
{a: (sub) => <a href={makeRoomPermalink(this.props.roomId)} rel="noopener" target="_blank">{sub}</a>},
);
buttonText = _t("Invite");
goButtonFn = this._inviteUsers;
}
return ( return (
<BaseDialog <BaseDialog
className='mx_InviteDialog' className='mx_InviteDialog'
hasCancel={true} hasCancel={true}
onFinished={this._cancel} onFinished={this._cancel}
title={_t("Direct Messages")} title={title}
> >
<div className='mx_InviteDialog_content'> <div className='mx_InviteDialog_content'>
<p> <p>{helpText}</p>
{_t(
"If you can't find someone, ask them for their username, or share your " +
"username (%(userId)s) or <a>profile link</a>.",
{userId},
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
)}
</p>
<div className='mx_InviteDialog_addressBar'> <div className='mx_InviteDialog_addressBar'>
{this._renderEditor()} {this._renderEditor()}
<div className='mx_InviteDialog_buttonAndSpinner'> <div className='mx_InviteDialog_buttonAndSpinner'>
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
onClick={this._startDm} onClick={goButtonFn}
className='mx_InviteDialog_goButton' className='mx_InviteDialog_goButton'
disabled={this.state.busy} disabled={this.state.busy}
> >
{_t("Go")} {buttonText}
</AccessibleButton> </AccessibleButton>
{spinner} {spinner}
</div> </div>

View file

@ -372,7 +372,7 @@
"Render simple counters in room header": "Render simple counters in room header", "Render simple counters in room header": "Render simple counters in room header",
"Multiple integration managers": "Multiple integration managers", "Multiple integration managers": "Multiple integration managers",
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"New DM invite dialog (under development)": "New DM invite dialog (under development)", "New invite dialog": "New invite dialog",
"Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list",
"Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)",
"Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
@ -1438,16 +1438,6 @@
"View Servers in Room": "View Servers in Room", "View Servers in Room": "View Servers in Room",
"Toolbox": "Toolbox", "Toolbox": "Toolbox",
"Developer Tools": "Developer Tools", "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",
"Suggestions": "Suggestions",
"Show more": "Show more",
"Direct Messages": "Direct Messages",
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
"Go": "Go",
"An error has occurred.": "An error has occurred.", "An error has occurred.": "An error has occurred.",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
@ -1457,6 +1447,20 @@
"Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.",
"Integrations not allowed": "Integrations not allowed", "Integrations not allowed": "Integrations not allowed",
"Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.",
"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.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. 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",
"Suggestions": "Suggestions",
"Recently Direct Messaged": "Recently Direct Messaged",
"Show more": "Show more",
"Direct Messages": "Direct Messages",
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
"Go": "Go",
"If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Start verification": "Start verification", "Start verification": "Start verification",