Refactor InviteDialog (#30540)
* Remove unreferenced CSS class `mx_InviteDialog_hasFooter` This is never used in the CSS (or elsewhere), so let's remove it * Move `consultConnectSection` initialization Since this only used and set when `kind === InviteKind.CallTransfer`, we can simplify * Factor out `title` logic Move the logic for caclulating the title to a separate method. I want to be able to reference it from a couple of places, so it will be easier if it is a separate method. (We'll actually be inlining it again later in this PR) * Factor out `renderMainTab` method Break the big `render` method in half by pulling the `usersSection` out into a separate method. * Split out `renderRegularDialog` and `renderCallTransferDialog` `render` is now almost entirely two separate flows, so let's spit it into two separate methods. Recommend reviewing this commit with whitespace changes hidden. * Inline `getTitle` This method has served its purpose: we can now inline it again. * Factor out `renderSuggestions` Break up `renderMainTab` a bit more: pull out a new method which renders the "suggestions" bit of the dialog, together with the associated warnings and footer.
This commit is contained in:
committed by
GitHub
parent
01c4ba8893
commit
b897006899
@@ -6,9 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, createRef, type ReactNode, type SyntheticEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import { RoomMember, type Room, MatrixError, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import React, { createRef, type JSX, type ReactNode, type SyntheticEvent } from "react";
|
||||
import { EventType, MatrixError, type Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -1260,30 +1259,89 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
});
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
private hasSelection(): boolean {
|
||||
return this.state.targets.length > 0 || (!!this.state.filterText && this.state.filterText.includes("@"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the "suggestions" section, which shows a list of people you might want to invite, together with any
|
||||
* errors from the previous iteration.
|
||||
*/
|
||||
private renderSuggestions(): JSX.Element {
|
||||
// If we're starting a DM, add a footer which showing our matrix.to link, for copying & pasting.
|
||||
let footer;
|
||||
if (this.props.kind === InviteKind.Dm) {
|
||||
const link = makeUserPermalink(MatrixClientPeg.safeGet().getSafeUserId());
|
||||
footer = (
|
||||
<div className="mx_InviteDialog_footer">
|
||||
<h3>{_t("invite|send_link_prompt")}</h3>
|
||||
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.safeGet().getSafeUserId())}>
|
||||
<a className="mx_InviteDialog_footer_link" href={link} onClick={this.onLinkClick}>
|
||||
{link}
|
||||
</a>
|
||||
</CopyableText>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let results: React.ReactNode | null = null;
|
||||
let onlyOneThreepidNote: React.ReactNode | null = null;
|
||||
|
||||
if (!this.canInviteMore() || (this.hasFilterAtLeastOneEmail() && !this.canInviteThirdParty())) {
|
||||
// We are in DM case here, because of the checks in canInviteMore() / canInviteThirdParty().
|
||||
// Show a note saying "Invites by email can only be sent one at a time".
|
||||
onlyOneThreepidNote = <div className="mx_InviteDialog_oneThreepid">{_t("invite|email_limit_one")}</div>;
|
||||
} else {
|
||||
let extraSection;
|
||||
if (this.props.kind === InviteKind.Dm) {
|
||||
// Some extra words saying "Some suggestions may be hidden for privacy"
|
||||
extraSection = (
|
||||
<div className="mx_InviteDialog_section_hidden_suggestions_disclaimer">
|
||||
<span>{_t("invite|suggestions_disclaimer")}</span>
|
||||
<p>{_t("invite|suggestions_disclaimer_prompt")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
results = (
|
||||
<div className="mx_InviteDialog_userSections">
|
||||
{this.renderSection("recents")}
|
||||
{this.renderSection("suggestions")}
|
||||
{extraSection}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.renderIdentityServerWarning()}
|
||||
<div className="error">{this.state.errorText}</div>
|
||||
{onlyOneThreepidNote}
|
||||
{results}
|
||||
{footer}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render content of the common "users" tab that is shown whether we have a regular invite dialog or a
|
||||
* "CallTransfer" one.
|
||||
*/
|
||||
private renderMainTab(): JSX.Element {
|
||||
let spinner: JSX.Element | undefined;
|
||||
if (this.state.busy) {
|
||||
spinner = <Spinner w={20} h={20} />;
|
||||
}
|
||||
|
||||
let title;
|
||||
let helpText;
|
||||
let buttonText;
|
||||
let goButtonFn: (() => Promise<void>) | null = null;
|
||||
let consultConnectSection;
|
||||
let extraSection;
|
||||
let footer;
|
||||
|
||||
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
|
||||
|
||||
const hasSelection =
|
||||
this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@"));
|
||||
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const userId = cli.getUserId()!;
|
||||
if (this.props.kind === InviteKind.Dm) {
|
||||
title = _t("space|add_existing_room_space|dm_heading");
|
||||
|
||||
if (identityServersEnabled) {
|
||||
helpText = _t(
|
||||
"invite|start_conversation_name_email_mxid_prompt",
|
||||
@@ -1316,34 +1374,10 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
|
||||
buttonText = _t("action|go");
|
||||
goButtonFn = this.checkProfileAndStartDm;
|
||||
extraSection = (
|
||||
<div className="mx_InviteDialog_section_hidden_suggestions_disclaimer">
|
||||
<span>{_t("invite|suggestions_disclaimer")}</span>
|
||||
<p>{_t("invite|suggestions_disclaimer_prompt")}</p>
|
||||
</div>
|
||||
);
|
||||
const link = makeUserPermalink(MatrixClientPeg.safeGet().getSafeUserId());
|
||||
footer = (
|
||||
<div className="mx_InviteDialog_footer">
|
||||
<h3>{_t("invite|send_link_prompt")}</h3>
|
||||
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.safeGet().getSafeUserId())}>
|
||||
<a className="mx_InviteDialog_footer_link" href={link} onClick={this.onLinkClick}>
|
||||
{link}
|
||||
</a>
|
||||
</CopyableText>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.kind === InviteKind.Invite) {
|
||||
const roomId = this.props.roomId;
|
||||
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
||||
const isSpace = room?.isSpaceRoom();
|
||||
title = isSpace
|
||||
? _t("invite|to_space", {
|
||||
spaceName: room?.name || _t("common|unnamed_space"),
|
||||
})
|
||||
: _t("invite|to_room", {
|
||||
roomName: room?.name || _t("common|unnamed_room"),
|
||||
});
|
||||
|
||||
let helpTextUntranslated;
|
||||
if (isSpace) {
|
||||
@@ -1384,31 +1418,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
|
||||
buttonText = _t("action|invite");
|
||||
goButtonFn = this.inviteUsers;
|
||||
} else if (this.props.kind === InviteKind.CallTransfer) {
|
||||
title = _t("action|transfer");
|
||||
|
||||
consultConnectSection = (
|
||||
<div className="mx_InviteDialog_transferConsultConnect">
|
||||
<label>
|
||||
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
|
||||
{_t("voip|transfer_consult_first_label")}
|
||||
</label>
|
||||
<AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={this.onCancel}
|
||||
className="mx_InviteDialog_transferConsultConnect_pushRight"
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={this.transferCall}
|
||||
disabled={!hasSelection && this.state.dialPadValue === ""}
|
||||
>
|
||||
{_t("action|transfer")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const goButton =
|
||||
@@ -1417,29 +1426,13 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
kind="primary"
|
||||
onClick={goButtonFn}
|
||||
className="mx_InviteDialog_goButton"
|
||||
disabled={this.state.busy || !hasSelection}
|
||||
disabled={this.state.busy || !this.hasSelection()}
|
||||
>
|
||||
{buttonText}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
let results: React.ReactNode | null = null;
|
||||
let onlyOneThreepidNote: React.ReactNode | null = null;
|
||||
|
||||
if (!this.canInviteMore() || (this.hasFilterAtLeastOneEmail() && !this.canInviteThirdParty())) {
|
||||
// We are in DM case here, because of the checks in canInviteMore() / canInviteThirdParty().
|
||||
onlyOneThreepidNote = <div className="mx_InviteDialog_oneThreepid">{_t("invite|email_limit_one")}</div>;
|
||||
} else {
|
||||
results = (
|
||||
<div className="mx_InviteDialog_userSections">
|
||||
{this.renderSection("recents")}
|
||||
{this.renderSection("suggestions")}
|
||||
{extraSection}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const usersSection = (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p className="mx_InviteDialog_helpText">{helpText}</p>
|
||||
<div className="mx_InviteDialog_addressBar">
|
||||
@@ -1449,102 +1442,155 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
{spinner}
|
||||
</div>
|
||||
</div>
|
||||
{this.renderIdentityServerWarning()}
|
||||
<div className="error">{this.state.errorText}</div>
|
||||
{onlyOneThreepidNote}
|
||||
{results}
|
||||
{footer}
|
||||
{this.renderSuggestions()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
let dialogContent;
|
||||
if (this.props.kind === InviteKind.CallTransfer) {
|
||||
const tabs: NonEmptyArray<Tab<TabId>> = [
|
||||
new Tab(
|
||||
TabId.UserDirectory,
|
||||
_td("invite|transfer_user_directory_tab"),
|
||||
"mx_InviteDialog_userDirectoryIcon",
|
||||
usersSection,
|
||||
),
|
||||
];
|
||||
|
||||
const backspaceButton = <DialPadBackspaceButton onBackspacePress={this.onDeletePress} />;
|
||||
|
||||
// Only show the backspace button if the field has content
|
||||
let dialPadField;
|
||||
if (this.state.dialPadValue.length !== 0) {
|
||||
dialPadField = (
|
||||
<Field
|
||||
ref={this.numberEntryFieldRef}
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
postfixComponent={backspaceButton}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
dialPadField = (
|
||||
<Field
|
||||
ref={this.numberEntryFieldRef}
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const dialPadSection = (
|
||||
<div className="mx_InviteDialog_dialPad">
|
||||
<form onSubmit={this.onDialFormSubmit}>{dialPadField}</form>
|
||||
<Dialpad hasDial={false} onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress} />
|
||||
</div>
|
||||
);
|
||||
tabs.push(
|
||||
new Tab(
|
||||
TabId.DialPad,
|
||||
_td("invite|transfer_dial_pad_tab"),
|
||||
"mx_InviteDialog_dialPadIcon",
|
||||
dialPadSection,
|
||||
),
|
||||
);
|
||||
dialogContent = (
|
||||
<React.Fragment>
|
||||
<TabbedView<TabId>
|
||||
tabs={tabs}
|
||||
activeTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP}
|
||||
onChange={this.onTabChange}
|
||||
/>
|
||||
{consultConnectSection}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
dialogContent = (
|
||||
<React.Fragment>
|
||||
{usersSection}
|
||||
{consultConnectSection}
|
||||
</React.Fragment>
|
||||
);
|
||||
/**
|
||||
* Render the complete dialog, given this is not a call transfer dialog.
|
||||
*
|
||||
* See also: {@link renderCallTransferDialog}.
|
||||
*/
|
||||
private renderRegularDialog(): React.ReactNode {
|
||||
let title;
|
||||
if (this.props.kind === InviteKind.Dm) {
|
||||
title = _t("space|add_existing_room_space|dm_heading");
|
||||
} else if (this.props.kind === InviteKind.Invite) {
|
||||
const roomId = this.props.roomId;
|
||||
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
||||
const isSpace = room?.isSpaceRoom();
|
||||
title = isSpace
|
||||
? _t("invite|to_space", {
|
||||
spaceName: room?.name || _t("common|unnamed_space"),
|
||||
})
|
||||
: _t("invite|to_room", {
|
||||
roomName: room?.name || _t("common|unnamed_room"),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className={classNames({
|
||||
mx_InviteDialog_transfer: this.props.kind === InviteKind.CallTransfer,
|
||||
mx_InviteDialog_other: this.props.kind !== InviteKind.CallTransfer,
|
||||
mx_InviteDialog_hasFooter: !!footer,
|
||||
})}
|
||||
className="mx_InviteDialog_other"
|
||||
hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={title}
|
||||
screenName={this.screenName}
|
||||
>
|
||||
<div className="mx_InviteDialog_content">{this.renderMainTab()}</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the complete call transfer dialog.
|
||||
*
|
||||
* See also: {@link renderRegularDialog}.
|
||||
*/
|
||||
private renderCallTransferDialog(): React.ReactNode {
|
||||
const usersSection = this.renderMainTab();
|
||||
|
||||
const tabs: NonEmptyArray<Tab<TabId>> = [
|
||||
new Tab(
|
||||
TabId.UserDirectory,
|
||||
_td("invite|transfer_user_directory_tab"),
|
||||
"mx_InviteDialog_userDirectoryIcon",
|
||||
usersSection,
|
||||
),
|
||||
];
|
||||
|
||||
const backspaceButton = <DialPadBackspaceButton onBackspacePress={this.onDeletePress} />;
|
||||
|
||||
// Only show the backspace button if the field has content
|
||||
let dialPadField;
|
||||
if (this.state.dialPadValue.length !== 0) {
|
||||
dialPadField = (
|
||||
<Field
|
||||
ref={this.numberEntryFieldRef}
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
postfixComponent={backspaceButton}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
dialPadField = (
|
||||
<Field
|
||||
ref={this.numberEntryFieldRef}
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const dialPadSection = (
|
||||
<div className="mx_InviteDialog_dialPad">
|
||||
<form onSubmit={this.onDialFormSubmit}>{dialPadField}</form>
|
||||
<Dialpad hasDial={false} onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress} />
|
||||
</div>
|
||||
);
|
||||
tabs.push(
|
||||
new Tab(TabId.DialPad, _td("invite|transfer_dial_pad_tab"), "mx_InviteDialog_dialPadIcon", dialPadSection),
|
||||
);
|
||||
|
||||
const consultConnectSection = (
|
||||
<div className="mx_InviteDialog_transferConsultConnect">
|
||||
<label>
|
||||
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
|
||||
{_t("voip|transfer_consult_first_label")}
|
||||
</label>
|
||||
<AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={this.onCancel}
|
||||
className="mx_InviteDialog_transferConsultConnect_pushRight"
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={this.transferCall}
|
||||
disabled={!this.hasSelection() && this.state.dialPadValue === ""}
|
||||
>
|
||||
{_t("action|transfer")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
const dialogContent = (
|
||||
<React.Fragment>
|
||||
<TabbedView<TabId>
|
||||
tabs={tabs}
|
||||
activeTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP}
|
||||
onChange={this.onTabChange}
|
||||
/>
|
||||
{consultConnectSection}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_InviteDialog_transfer"
|
||||
hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("action|transfer")}
|
||||
screenName={this.screenName}
|
||||
>
|
||||
<div className="mx_InviteDialog_content">{dialogContent}</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
if (this.props.kind === InviteKind.CallTransfer) {
|
||||
return this.renderCallTransferDialog();
|
||||
} else {
|
||||
return this.renderRegularDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user