Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/spacesperf
Conflicts: src/components/views/dialogs/InviteDialog.tsx
This commit is contained in:
commit
50b6c5c557
45 changed files with 902 additions and 276 deletions
|
@ -54,7 +54,7 @@ export default class InteractiveAuthComponent extends React.Component {
|
|||
// * emailSid {string} If email auth was performed, the sid of
|
||||
// the auth session.
|
||||
// * clientSecret {string} The client secret used in auth
|
||||
// sessions with the ID server.
|
||||
// sessions with the identity server.
|
||||
onAuthFinished: PropTypes.func.isRequired,
|
||||
|
||||
// Inputs provided by the user to the auth process
|
||||
|
|
|
@ -561,7 +561,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
switch (payload.action) {
|
||||
case 'MatrixActions.accountData':
|
||||
// XXX: This is a collection of several hacks to solve a minor problem. We want to
|
||||
// update our local state when the ID server changes, but don't want to put that in
|
||||
// update our local state when the identity server changes, but don't want to put that in
|
||||
// the js-sdk as we'd be then dictating how all consumers need to behave. However,
|
||||
// this component is already bloated and we probably don't want this tiny logic in
|
||||
// here, but there's no better place in the react-sdk for it. Additionally, we're
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as React from "react";
|
|||
import { _t } from '../../languageHandler';
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import classNames from "classnames";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
|
||||
/**
|
||||
|
@ -37,9 +38,16 @@ export class Tab {
|
|||
}
|
||||
}
|
||||
|
||||
export enum TabLocation {
|
||||
LEFT = 'left',
|
||||
TOP = 'top',
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
tabs: Tab[];
|
||||
initialTabId?: string;
|
||||
tabLocation: TabLocation;
|
||||
onChange?: (tabId: string) => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -62,6 +70,10 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
};
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
tabLocation: TabLocation.LEFT,
|
||||
};
|
||||
|
||||
private _getActiveTabIndex() {
|
||||
if (!this.state || !this.state.activeTabIndex) return 0;
|
||||
return this.state.activeTabIndex;
|
||||
|
@ -75,6 +87,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
private _setActiveTab(tab: Tab) {
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx !== -1) {
|
||||
if (this.props.onChange) this.props.onChange(tab.id);
|
||||
this.setState({ activeTabIndex: idx });
|
||||
} else {
|
||||
console.error("Could not find tab " + tab.label + " in tabs");
|
||||
|
@ -119,8 +132,14 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
|
||||
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);
|
||||
|
||||
const tabbedViewClasses = classNames({
|
||||
'mx_TabbedView': true,
|
||||
'mx_TabbedView_tabsOnLeft': this.props.tabLocation == TabLocation.LEFT,
|
||||
'mx_TabbedView_tabsOnTop': this.props.tabLocation == TabLocation.TOP,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_TabbedView">
|
||||
<div className={tabbedViewClasses}>
|
||||
<div className="mx_TabbedView_tabLabels">
|
||||
{labels}
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ import CaptchaForm from "./CaptchaForm";
|
|||
* one HS whilst beign a guest on another).
|
||||
* loginType: the login type of the auth stage being attempted
|
||||
* authSessionId: session id from the server
|
||||
* clientSecret: The client secret in use for ID server auth sessions
|
||||
* clientSecret: The client secret in use for identity server auth sessions
|
||||
* stageParams: params from the server for the stage being attempted
|
||||
* errorText: error message from a previous attempt to authenticate
|
||||
* submitAuthDict: a function which will be called with the new auth dict
|
||||
|
@ -54,8 +54,8 @@ import CaptchaForm from "./CaptchaForm";
|
|||
* Defined keys for stages are:
|
||||
* m.login.email.identity:
|
||||
* * emailSid: string representing the sid of the active
|
||||
* verification session from the ID server, or
|
||||
* null if no session is active.
|
||||
* verification session from the identity server,
|
||||
* or null if no session is active.
|
||||
* fail: a function which should be called with an error object if an
|
||||
* error occurred during the auth stage. This will cause the auth
|
||||
* session to be failed and the process to go back to the start.
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class CallContextMenu extends React.Component<IProps> {
|
|||
onTransferClick = () => {
|
||||
Modal.createTrackedDialog(
|
||||
'Transfer Call', '', InviteDialog, { kind: KIND_CALL_TRANSFER, call: this.props.call },
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||
/*className=*/"mx_InviteDialog_transferWrapper", /*isPriority=*/false, /*isStatic=*/true,
|
||||
);
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
|
|
@ -15,11 +15,11 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import Field from "../elements/Field";
|
||||
import Dialpad from '../voip/DialPad';
|
||||
import DialPad from '../voip/DialPad';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
|
@ -45,24 +45,29 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
|
|||
this.setState({ value: this.state.value + digit });
|
||||
};
|
||||
|
||||
onCancelClick = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
onChange = (ev) => {
|
||||
this.setState({ value: ev.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ContextMenu {...this.props}>
|
||||
<div className="mx_DialPadContextMenu_header">
|
||||
<div className="mx_DialPadContextMenuWrapper">
|
||||
<div>
|
||||
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
|
||||
<AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} />
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_header">
|
||||
<Field className="mx_DialPadContextMenu_dialled"
|
||||
value={this.state.value} autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_dialPad">
|
||||
<DialPad onDigitPress={this.onDigitPress} hasDial={false} />
|
||||
</div>
|
||||
<Field className="mx_DialPadContextMenu_dialled"
|
||||
value={this.state.value} autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_horizSep" />
|
||||
<div className="mx_DialPadContextMenu_dialPad">
|
||||
<Dialpad onDigitPress={this.onDigitPress} hasDialAndDelete={false} />
|
||||
</div>
|
||||
</ContextMenu>;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export default class IntegrationsImpossibleDialog extends React.Component {
|
|||
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
"Your %(brand)s doesn't allow you to use an Integration Manager to do this. " +
|
||||
"Your %(brand)s doesn't allow you to use an integration manager to do this. " +
|
||||
"Please contact an admin.",
|
||||
{ brand },
|
||||
)}
|
||||
|
|
|
@ -32,7 +32,6 @@ import Modal from "../../../Modal";
|
|||
import { humanizeTime } from "../../../utils/humanize";
|
||||
import createRoom, {
|
||||
canEncryptToAllUsers,
|
||||
ensureDMExists,
|
||||
findDMForUser,
|
||||
privateShouldBeEncrypted,
|
||||
} from "../../../createRoom";
|
||||
|
@ -64,9 +63,14 @@ import { copyPlaintext, selectText } from "../../../utils/strings";
|
|||
import * as ContextMenu from "../../structures/ContextMenu";
|
||||
import { toRightOf } from "../../structures/ContextMenu";
|
||||
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
|
||||
import { TransferCallPayload } from '../../../dispatcher/payloads/TransferCallPayload';
|
||||
import Field from '../elements/Field';
|
||||
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
|
||||
import Dialpad from '../voip/DialPad';
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
|
@ -80,11 +84,19 @@ interface IRecentUser {
|
|||
|
||||
export const KIND_DM = "dm";
|
||||
export const KIND_INVITE = "invite";
|
||||
// NB. This dialog needs the 'mx_InviteDialog_transferWrapper' wrapper class to have the correct
|
||||
// padding on the bottom (because all modals have 24px padding on all sides), so this needs to
|
||||
// be passed when creating the modal
|
||||
export const KIND_CALL_TRANSFER = "call_transfer";
|
||||
|
||||
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
|
||||
|
||||
enum TabId {
|
||||
UserDirectory = 'users',
|
||||
DialPad = 'dialpad',
|
||||
}
|
||||
|
||||
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
|
||||
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
|
||||
// for 3PIDs/email addresses.
|
||||
|
@ -357,6 +369,8 @@ interface IInviteDialogState {
|
|||
canUseIdentityServer: boolean;
|
||||
tryingIdentityServer: boolean;
|
||||
consultFirst: boolean;
|
||||
dialPadValue: string;
|
||||
currentTabId: TabId;
|
||||
|
||||
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||
busy: boolean;
|
||||
|
@ -408,6 +422,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
tryingIdentityServer: false,
|
||||
consultFirst: false,
|
||||
dialPadValue: '',
|
||||
currentTabId: TabId.UserDirectory,
|
||||
|
||||
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||
busy: false,
|
||||
|
@ -769,44 +785,32 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
};
|
||||
|
||||
private transferCall = async () => {
|
||||
this.convertFilter();
|
||||
const targets = this.convertFilter();
|
||||
const targetIds = targets.map(t => t.userId);
|
||||
if (targetIds.length > 1) {
|
||||
this.setState({
|
||||
errorText: _t("A call can only be transferred to a single user."),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.consultFirst) {
|
||||
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), targetIds[0]);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: this.props.call.type,
|
||||
room_id: dmRoomId,
|
||||
transferee: this.props.call,
|
||||
});
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: dmRoomId,
|
||||
should_peek: false,
|
||||
joining: false,
|
||||
});
|
||||
this.props.onFinished();
|
||||
} else {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.props.call.transfer(targetIds[0]);
|
||||
this.setState({ busy: false });
|
||||
this.props.onFinished();
|
||||
} catch (e) {
|
||||
if (this.state.currentTabId == TabId.UserDirectory) {
|
||||
this.convertFilter();
|
||||
const targets = this.convertFilter();
|
||||
const targetIds = targets.map(t => t.userId);
|
||||
if (targetIds.length > 1) {
|
||||
this.setState({
|
||||
busy: false,
|
||||
errorText: _t("Failed to transfer call"),
|
||||
errorText: _t("A call can only be transferred to a single user."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: Action.TransferCallToMatrixID,
|
||||
call: this.props.call,
|
||||
destination: targetIds[0],
|
||||
consultFirst: this.state.consultFirst,
|
||||
} as TransferCallPayload);
|
||||
} else {
|
||||
dis.dispatch({
|
||||
action: Action.TransferCallToPhoneNumber,
|
||||
call: this.props.call,
|
||||
destination: this.state.dialPadValue,
|
||||
consultFirst: this.state.consultFirst,
|
||||
} as TransferCallPayload);
|
||||
}
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
private onKeyDown = (e) => {
|
||||
|
@ -828,6 +832,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
}
|
||||
};
|
||||
|
||||
private onCancel = () => {
|
||||
this.props.onFinished([]);
|
||||
};
|
||||
|
||||
private updateSuggestions = async (term) => {
|
||||
MatrixClientPeg.get().searchUserDirectory({ term }).then(async r => {
|
||||
if (term !== this.state.filterText) {
|
||||
|
@ -963,11 +971,14 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
private toggleMember = (member: Member) => {
|
||||
if (!this.state.busy) {
|
||||
let filterText = this.state.filterText;
|
||||
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||
let targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||
const idx = targets.indexOf(member);
|
||||
if (idx >= 0) {
|
||||
targets.splice(idx, 1);
|
||||
} else {
|
||||
if (this.props.kind === KIND_CALL_TRANSFER && targets.length > 0) {
|
||||
targets = [];
|
||||
}
|
||||
targets.push(member);
|
||||
filterText = ""; // clear the filter when the user accepts a suggestion
|
||||
}
|
||||
|
@ -1190,6 +1201,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
}
|
||||
|
||||
private renderEditor() {
|
||||
const hasPlaceholder = (
|
||||
this.props.kind == KIND_CALL_TRANSFER &&
|
||||
this.state.targets.length === 0 &&
|
||||
this.state.filterText.length === 0
|
||||
);
|
||||
const targets = this.state.targets.map(t => (
|
||||
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
|
||||
));
|
||||
|
@ -1202,8 +1218,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
ref={this.editorRef}
|
||||
onPaste={this.onPaste}
|
||||
autoFocus={true}
|
||||
disabled={this.state.busy}
|
||||
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
|
||||
autoComplete="off"
|
||||
placeholder={hasPlaceholder ? _t("Search") : null}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
|
@ -1250,6 +1267,28 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
}
|
||||
}
|
||||
|
||||
private onDialFormSubmit = ev => {
|
||||
ev.preventDefault();
|
||||
this.transferCall();
|
||||
};
|
||||
|
||||
private onDialChange = ev => {
|
||||
this.setState({ dialPadValue: ev.currentTarget.value });
|
||||
};
|
||||
|
||||
private onDigitPress = digit => {
|
||||
this.setState({ dialPadValue: this.state.dialPadValue + digit });
|
||||
};
|
||||
|
||||
private onDeletePress = () => {
|
||||
if (this.state.dialPadValue.length === 0) return;
|
||||
this.setState({ dialPadValue: this.state.dialPadValue.slice(0, -1) });
|
||||
};
|
||||
|
||||
private onTabChange = (tabId: TabId) => {
|
||||
this.setState({ currentTabId: tabId });
|
||||
};
|
||||
|
||||
private async onLinkClick(e) {
|
||||
e.preventDefault();
|
||||
selectText(e.target);
|
||||
|
@ -1279,12 +1318,16 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
let helpText;
|
||||
let buttonText;
|
||||
let goButtonFn;
|
||||
let consultConnectSection;
|
||||
let extraSection;
|
||||
let footer;
|
||||
let keySharingWarning = <span />;
|
||||
|
||||
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
|
||||
|
||||
const hasSelection = this.state.targets.length > 0
|
||||
|| (this.state.filterText && this.state.filterText.includes('@'));
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
if (this.props.kind === KIND_DM) {
|
||||
|
@ -1422,23 +1465,116 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
}
|
||||
} else if (this.props.kind === KIND_CALL_TRANSFER) {
|
||||
title = _t("Transfer");
|
||||
buttonText = _t("Transfer");
|
||||
goButtonFn = this.transferCall;
|
||||
footer = <div>
|
||||
|
||||
consultConnectSection = <div className="mx_InviteDialog_transferConsultConnect">
|
||||
<label>
|
||||
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
|
||||
{_t("Consult first")}
|
||||
</label>
|
||||
<AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={this.onCancel}
|
||||
className='mx_InviteDialog_transferConsultConnect_pushRight'
|
||||
>
|
||||
{_t("Cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={this.transferCall}
|
||||
className='mx_InviteDialog_transferButton'
|
||||
disabled={!hasSelection && this.state.dialPadValue === ''}
|
||||
>
|
||||
{_t("Transfer")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else {
|
||||
console.error("Unknown kind of InviteDialog: " + this.props.kind);
|
||||
}
|
||||
|
||||
const hasSelection = this.state.targets.length > 0
|
||||
|| (this.state.filterText && this.state.filterText.includes('@'));
|
||||
const goButton = this.props.kind == KIND_CALL_TRANSFER ? null : <AccessibleButton
|
||||
kind="primary"
|
||||
onClick={goButtonFn}
|
||||
className='mx_InviteDialog_goButton'
|
||||
disabled={this.state.busy || !hasSelection}
|
||||
>
|
||||
{buttonText}
|
||||
</AccessibleButton>;
|
||||
|
||||
const usersSection = <React.Fragment>
|
||||
<p className='mx_InviteDialog_helpText'>{helpText}</p>
|
||||
<div className='mx_InviteDialog_addressBar'>
|
||||
{this.renderEditor()}
|
||||
<div className='mx_InviteDialog_buttonAndSpinner'>
|
||||
{goButton}
|
||||
{spinner}
|
||||
</div>
|
||||
</div>
|
||||
{keySharingWarning}
|
||||
{this.renderIdentityServerWarning()}
|
||||
<div className='error'>{this.state.errorText}</div>
|
||||
<div className='mx_InviteDialog_userSections'>
|
||||
{this.renderSection('recents')}
|
||||
{this.renderSection('suggestions')}
|
||||
{extraSection}
|
||||
</div>
|
||||
{footer}
|
||||
</React.Fragment>;
|
||||
|
||||
let dialogContent;
|
||||
if (this.props.kind === KIND_CALL_TRANSFER) {
|
||||
const tabs = [];
|
||||
tabs.push(new Tab(
|
||||
TabId.UserDirectory, _td("User Directory"), '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 className="mx_InviteDialog_dialPadField" id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
postfixComponent={backspaceButton}
|
||||
/>;
|
||||
} else {
|
||||
dialPadField = <Field 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("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
|
||||
dialogContent = <React.Fragment>
|
||||
<TabbedView tabs={tabs} initialTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP} onChange={this.onTabChange}
|
||||
/>
|
||||
{consultConnectSection}
|
||||
</React.Fragment>;
|
||||
} else {
|
||||
dialogContent = <React.Fragment>
|
||||
{usersSection}
|
||||
{consultConnectSection}
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className={classNames("mx_InviteDialog", {
|
||||
className={classNames({
|
||||
mx_InviteDialog_transfer: this.props.kind === KIND_CALL_TRANSFER,
|
||||
mx_InviteDialog_other: this.props.kind !== KIND_CALL_TRANSFER,
|
||||
mx_InviteDialog_hasFooter: !!footer,
|
||||
})}
|
||||
hasCancel={true}
|
||||
|
@ -1446,30 +1582,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
title={title}
|
||||
>
|
||||
<div className='mx_InviteDialog_content'>
|
||||
<p className='mx_InviteDialog_helpText'>{helpText}</p>
|
||||
<div className='mx_InviteDialog_addressBar'>
|
||||
{this.renderEditor()}
|
||||
<div className='mx_InviteDialog_buttonAndSpinner'>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={goButtonFn}
|
||||
className='mx_InviteDialog_goButton'
|
||||
disabled={this.state.busy || !hasSelection}
|
||||
>
|
||||
{buttonText}
|
||||
</AccessibleButton>
|
||||
{spinner}
|
||||
</div>
|
||||
</div>
|
||||
{keySharingWarning}
|
||||
{this.renderIdentityServerWarning()}
|
||||
<div className='error'>{this.state.errorText}</div>
|
||||
<div className='mx_InviteDialog_userSections'>
|
||||
{this.renderSection('recents')}
|
||||
{this.renderSection('suggestions')}
|
||||
{extraSection}
|
||||
</div>
|
||||
{footer}
|
||||
{dialogContent}
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -90,9 +90,9 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
|||
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
|
||||
switch (serviceType) {
|
||||
case SERVICE_TYPES.IS:
|
||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||
return <div>{_t("Identity server")}<br />({host})</div>;
|
||||
case SERVICE_TYPES.IM:
|
||||
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
||||
return <div>{_t("Integration manager")}<br />({host})</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ export default class AppPermission extends React.Component {
|
|||
|
||||
// Due to i18n limitations, we can't dedupe the code for variables in these two messages.
|
||||
const warning = this.state.isWrapped
|
||||
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
|
||||
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.",
|
||||
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip })
|
||||
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip });
|
||||
|
|
31
src/components/views/elements/DialPadBackspaceButton.tsx
Normal file
31
src/components/views/elements/DialPadBackspaceButton.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 * as React from "react";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
|
||||
interface IProps {
|
||||
// Callback for when the button is pressed
|
||||
onBackspacePress: () => void;
|
||||
}
|
||||
|
||||
export default class DialPadBackspaceButton extends React.PureComponent<IProps> {
|
||||
render() {
|
||||
return <div className="mx_DialPadBackspaceButtonWrapper">
|
||||
<AccessibleButton className="mx_DialPadBackspaceButton" onClick={this.props.onBackspacePress} />
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -224,7 +224,7 @@ export default class Stickerpicker extends React.PureComponent {
|
|||
}
|
||||
|
||||
_getStickerpickerContent() {
|
||||
// Handle Integration Manager errors
|
||||
// Handle integration manager errors
|
||||
if (this.state._imError) {
|
||||
return this._errorStickerpickerContent();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ const REACHABILITY_TIMEOUT = 10000; // ms
|
|||
async function checkIdentityServerUrl(u) {
|
||||
const parsedUrl = url.parse(u);
|
||||
|
||||
if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS");
|
||||
if (parsedUrl.protocol !== 'https:') return _t("Identity server URL must be HTTPS");
|
||||
|
||||
// XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the
|
||||
// js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it
|
||||
|
@ -53,17 +53,17 @@ async function checkIdentityServerUrl(u) {
|
|||
if (response.ok) {
|
||||
return null;
|
||||
} else if (response.status < 200 || response.status >= 300) {
|
||||
return _t("Not a valid Identity Server (status code %(code)s)", { code: response.status });
|
||||
return _t("Not a valid identity server (status code %(code)s)", { code: response.status });
|
||||
} else {
|
||||
return _t("Could not connect to Identity Server");
|
||||
return _t("Could not connect to identity server");
|
||||
}
|
||||
} catch (e) {
|
||||
return _t("Could not connect to Identity Server");
|
||||
return _t("Could not connect to identity server");
|
||||
}
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
// Whether or not the ID server is missing terms. This affects the text
|
||||
// Whether or not the identity server is missing terms. This affects the text
|
||||
// shown to the user.
|
||||
missingTerms: boolean;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
|
||||
let defaultIdServer = '';
|
||||
if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
|
||||
// If no ID server is configured but there's one in the config, prepopulate
|
||||
// If no identity server is configured but there's one in the config, prepopulate
|
||||
// the field to help the user.
|
||||
defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
// We react to changes in the ID server in the event the user is staring at this form
|
||||
// We react to changes in the identity server in the event the user is staring at this form
|
||||
// when changing their identity server on another device.
|
||||
if (payload.action !== "id_server_changed") return;
|
||||
|
||||
|
@ -356,7 +356,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
let sectionTitle;
|
||||
let bodyText;
|
||||
if (idServerUrl) {
|
||||
sectionTitle = _t("Identity Server (%(server)s)", { server: abbreviateUrl(idServerUrl) });
|
||||
sectionTitle = _t("Identity server (%(server)s)", { server: abbreviateUrl(idServerUrl) });
|
||||
bodyText = _t(
|
||||
"You are currently using <server></server> to discover and be discoverable by " +
|
||||
"existing contacts you know. You can change your identity server below.",
|
||||
|
@ -371,7 +371,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
} else {
|
||||
sectionTitle = _t("Identity Server");
|
||||
sectionTitle = _t("Identity server");
|
||||
bodyText = _t(
|
||||
"You are not currently using an identity server. " +
|
||||
"To discover and be discoverable by existing contacts you know, " +
|
||||
|
|
|
@ -65,13 +65,13 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
|||
if (currentManager) {
|
||||
managerName = `(${currentManager.name})`;
|
||||
bodyText = _t(
|
||||
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||
"Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
{ serverName: currentManager.name },
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
);
|
||||
} else {
|
||||
bodyText = _t("Use an Integration Manager to manage bots, widgets, and sticker packs.");
|
||||
bodyText = _t("Use an integration manager to manage bots, widgets, and sticker packs.");
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -86,7 +86,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
|||
<br />
|
||||
<br />
|
||||
{_t(
|
||||
"Integration Managers receive configuration data, and can modify widgets, " +
|
||||
"Integration managers receive configuration data, and can modify widgets, " +
|
||||
"send room invites, and set power levels on your behalf.",
|
||||
)}
|
||||
</span>
|
||||
|
|
|
@ -364,7 +364,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
onFinished={this.state.requiredPolicyInfo.resolve}
|
||||
introElement={intro}
|
||||
/>
|
||||
{ /* has its own heading as it includes the current ID server */ }
|
||||
{ /* has its own heading as it includes the current identity server */ }
|
||||
<SetIdServer missingTerms={true} />
|
||||
</div>
|
||||
);
|
||||
|
@ -387,7 +387,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
{threepidSection}
|
||||
{ /* has its own heading as it includes the current ID server */ }
|
||||
{ /* has its own heading as it includes the current identity server */ }
|
||||
<SetIdServer />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -290,7 +290,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
<span className='mx_SettingsTab_subheading'>{_t("Advanced")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Homeserver is")} <code>{MatrixClientPeg.get().getHomeserverUrl()}</code><br />
|
||||
{_t("Identity Server is")} <code>{MatrixClientPeg.get().getIdentityServerUrl()}</code><br />
|
||||
{_t("Identity server is")} <code>{MatrixClientPeg.get().getIdentityServerUrl()}</code><br />
|
||||
<br />
|
||||
<details>
|
||||
<summary>{_t("Access Token")}</summary><br />
|
||||
|
|
|
@ -19,16 +19,17 @@ import AccessibleButton from "../elements/AccessibleButton";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
const BUTTONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'];
|
||||
const BUTTON_LETTERS = ['', 'ABC', 'DEF', 'GHI', 'JKL', 'MNO', 'PQRS', 'TUV', 'WXYZ', '', '+', ''];
|
||||
|
||||
enum DialPadButtonKind {
|
||||
Digit,
|
||||
Delete,
|
||||
Dial,
|
||||
}
|
||||
|
||||
interface IButtonProps {
|
||||
kind: DialPadButtonKind;
|
||||
digit?: string;
|
||||
digitSubtext?: string;
|
||||
onButtonPress: (string) => void;
|
||||
}
|
||||
|
||||
|
@ -42,11 +43,10 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
|
|||
case DialPadButtonKind.Digit:
|
||||
return <AccessibleButton className="mx_DialPad_button" onClick={this.onClick}>
|
||||
{this.props.digit}
|
||||
<div className="mx_DialPad_buttonSubText">
|
||||
{this.props.digitSubtext}
|
||||
</div>
|
||||
</AccessibleButton>;
|
||||
case DialPadButtonKind.Delete:
|
||||
return <AccessibleButton className="mx_DialPad_button mx_DialPad_deleteButton"
|
||||
onClick={this.onClick}
|
||||
/>;
|
||||
case DialPadButtonKind.Dial:
|
||||
return <AccessibleButton className="mx_DialPad_button mx_DialPad_dialButton" onClick={this.onClick} />;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
|
|||
|
||||
interface IProps {
|
||||
onDigitPress: (string) => void;
|
||||
hasDialAndDelete: boolean;
|
||||
hasDial: boolean;
|
||||
onDeletePress?: (string) => void;
|
||||
onDialPress?: (string) => void;
|
||||
}
|
||||
|
@ -65,16 +65,15 @@ export default class Dialpad extends React.PureComponent<IProps> {
|
|||
render() {
|
||||
const buttonNodes = [];
|
||||
|
||||
for (const button of BUTTONS) {
|
||||
for (let i = 0; i < BUTTONS.length; i++) {
|
||||
const button = BUTTONS[i];
|
||||
const digitSubtext = BUTTON_LETTERS[i];
|
||||
buttonNodes.push(<DialPadButton key={button} kind={DialPadButtonKind.Digit}
|
||||
digit={button} onButtonPress={this.props.onDigitPress}
|
||||
digit={button} digitSubtext={digitSubtext} onButtonPress={this.props.onDigitPress}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (this.props.hasDialAndDelete) {
|
||||
buttonNodes.push(<DialPadButton key="del" kind={DialPadButtonKind.Delete}
|
||||
onButtonPress={this.props.onDeletePress}
|
||||
/>);
|
||||
if (this.props.hasDial) {
|
||||
buttonNodes.push(<DialPadButton key="dial" kind={DialPadButtonKind.Dial}
|
||||
onButtonPress={this.props.onDialPress}
|
||||
/>);
|
||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Field from "../elements/Field";
|
||||
import DialPad from './DialPad';
|
||||
|
@ -23,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher';
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||
|
||||
interface IProps {
|
||||
onFinished: (boolean) => void;
|
||||
|
@ -74,22 +74,38 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const backspaceButton = (
|
||||
<DialPadBackspaceButton onBackspacePress={this.onDeletePress} />
|
||||
);
|
||||
|
||||
// Only show the backspace button if the field has content
|
||||
let dialPadField;
|
||||
if (this.state.value.length !== 0) {
|
||||
dialPadField = <Field className="mx_DialPadModal_field" id="dialpad_number"
|
||||
value={this.state.value}
|
||||
autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
postfixComponent={backspaceButton}
|
||||
/>;
|
||||
} else {
|
||||
dialPadField = <Field className="mx_DialPadModal_field" id="dialpad_number"
|
||||
value={this.state.value}
|
||||
autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_DialPadModal">
|
||||
<div>
|
||||
<AccessibleButton className="mx_DialPadModal_cancel" onClick={this.onCancelClick} />
|
||||
</div>
|
||||
<div className="mx_DialPadModal_header">
|
||||
<div>
|
||||
<span className="mx_DialPadModal_title">{_t("Dial pad")}</span>
|
||||
<AccessibleButton className="mx_DialPadModal_cancel" onClick={this.onCancelClick} />
|
||||
</div>
|
||||
<form onSubmit={this.onFormSubmit}>
|
||||
<Field className="mx_DialPadModal_field" id="dialpad_number"
|
||||
value={this.state.value} autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
{dialPadField}
|
||||
</form>
|
||||
</div>
|
||||
<div className="mx_DialPadModal_horizSep" />
|
||||
<div className="mx_DialPadModal_dialPad">
|
||||
<DialPad hasDialAndDelete={true}
|
||||
<DialPad hasDial={true}
|
||||
onDigitPress={this.onDigitPress}
|
||||
onDeletePress={this.onDeletePress}
|
||||
onDialPress={this.onDialPress}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue