Merge pull request #6185 from matrix-org/gsouquet/dialogs-ts-migration
commit
2fd919e5c7
|
@ -44,6 +44,7 @@ import { EventIndexPeg } from "../indexing/EventIndexPeg";
|
||||||
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
|
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
|
||||||
import PerformanceMonitor from "../performance";
|
import PerformanceMonitor from "../performance";
|
||||||
import UIStore from "../stores/UIStore";
|
import UIStore from "../stores/UIStore";
|
||||||
|
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -84,6 +85,7 @@ declare global {
|
||||||
mxPerformanceMonitor: PerformanceMonitor;
|
mxPerformanceMonitor: PerformanceMonitor;
|
||||||
mxPerformanceEntryNames: any;
|
mxPerformanceEntryNames: any;
|
||||||
mxUIStore: UIStore;
|
mxUIStore: UIStore;
|
||||||
|
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -59,7 +59,7 @@ import IconizedContextMenu, {
|
||||||
} from "../views/context_menus/IconizedContextMenu";
|
} from "../views/context_menus/IconizedContextMenu";
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import {BetaPill} from "../views/beta/BetaCard";
|
import {BetaPill} from "../views/beta/BetaCard";
|
||||||
import {USER_LABS_TAB} from "../views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
|
@ -166,7 +166,7 @@ const SpaceInfo = ({ space }) => {
|
||||||
const onBetaClick = () => {
|
const onBetaClick = () => {
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: USER_LABS_TAB,
|
initialTabId: UserTab.Labs,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import { ContextMenuButton } from "./ContextMenu";
|
import { ContextMenuButton } from "./ContextMenu";
|
||||||
import { USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB } from "../views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
|
@ -408,12 +408,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_UserMenu_iconBell"
|
iconClassName="mx_UserMenu_iconBell"
|
||||||
label={_t("Notification settings")}
|
label={_t("Notification settings")}
|
||||||
onClick={(e) => this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}
|
onClick={(e) => this.onSettingsOpen(e, UserTab.Notifications)}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_UserMenu_iconLock"
|
iconClassName="mx_UserMenu_iconLock"
|
||||||
label={_t("Security & privacy")}
|
label={_t("Security & privacy")}
|
||||||
onClick={(e) => this.onSettingsOpen(e, USER_SECURITY_TAB)}
|
onClick={(e) => this.onSettingsOpen(e, UserTab.Security)}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_UserMenu_iconSettings"
|
iconClassName="mx_UserMenu_iconSettings"
|
||||||
|
|
|
@ -269,7 +269,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUIAuthFinished = async (success, response, extra) => {
|
private onUIAuthFinished = async (success: boolean, response: any) => {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
let msg = response.message || response.toString();
|
let msg = response.message || response.toString();
|
||||||
// can we give a better error message?
|
// can we give a better error message?
|
||||||
|
|
|
@ -15,39 +15,41 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.AskInviteAnywayDialog")
|
interface IProps {
|
||||||
export default class AskInviteAnywayDialog extends React.Component {
|
unknownProfileUsers: Array<{
|
||||||
static propTypes = {
|
userId: string;
|
||||||
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
|
errorText: string;
|
||||||
onInviteAnyways: PropTypes.func.isRequired,
|
}>;
|
||||||
onGiveUp: PropTypes.func.isRequired,
|
onInviteAnyways: () => void;
|
||||||
onFinished: PropTypes.func.isRequired,
|
onGiveUp: () => void;
|
||||||
};
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
_onInviteClicked = () => {
|
@replaceableComponent("views.dialogs.AskInviteAnywayDialog")
|
||||||
|
export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
||||||
|
private onInviteClicked = (): void => {
|
||||||
this.props.onInviteAnyways();
|
this.props.onInviteAnyways();
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onInviteNeverWarnClicked = () => {
|
private onInviteNeverWarnClicked = (): void => {
|
||||||
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
|
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
|
||||||
this.props.onInviteAnyways();
|
this.props.onInviteAnyways();
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onGiveUpClicked = () => {
|
private onGiveUpClicked = (): void => {
|
||||||
this.props.onGiveUp();
|
this.props.onGiveUp();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
const errorList = this.props.unknownProfileUsers
|
const errorList = this.props.unknownProfileUsers
|
||||||
|
@ -55,11 +57,12 @@ export default class AskInviteAnywayDialog extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_RetryInvitesDialog'
|
<BaseDialog className='mx_RetryInvitesDialog'
|
||||||
onFinished={this._onGiveUpClicked}
|
onFinished={this.onGiveUpClicked}
|
||||||
title={_t('The following users may not exist')}
|
title={_t('The following users may not exist')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
<div id='mx_Dialog_content'>
|
<div id='mx_Dialog_content'>
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
||||||
<ul>
|
<ul>
|
||||||
{ errorList }
|
{ errorList }
|
||||||
|
@ -67,13 +70,13 @@ export default class AskInviteAnywayDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this._onGiveUpClicked}>
|
<button onClick={this.onGiveUpClicked}>
|
||||||
{ _t('Close') }
|
{ _t('Close') }
|
||||||
</button>
|
</button>
|
||||||
<button onClick={this._onInviteNeverWarnClicked}>
|
<button onClick={this.onInviteNeverWarnClicked}>
|
||||||
{ _t('Invite anyway and never warn me again') }
|
{ _t('Invite anyway and never warn me again') }
|
||||||
</button>
|
</button>
|
||||||
<button onClick={this._onInviteClicked} autoFocus={true}>
|
<button onClick={this.onInviteClicked} autoFocus={true}>
|
||||||
{ _t('Invite anyway') }
|
{ _t('Invite anyway') }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
|
@ -29,7 +29,7 @@ import InfoDialog from "./InfoDialog";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {USER_LABS_TAB} from "./UserSettingsDialog";
|
import { UserTab } from "./UserSettingsDialog";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
featureId: string;
|
featureId: string;
|
||||||
|
@ -70,7 +70,7 @@ const BetaFeedbackDialog: React.FC<IProps> = ({featureId, onFinished}) => {
|
||||||
onFinished(false);
|
onFinished(false);
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: USER_LABS_TAB,
|
initialTabId: UserTab.Labs,
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
{ _t("To leave the beta, visit your settings.") }
|
{ _t("To leave the beta, visit your settings.") }
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -27,8 +26,27 @@ import sendBugReport, {downloadBugReport} from '../../../rageshake/submit-ragesh
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
initialText?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
sendLogs: boolean;
|
||||||
|
busy: boolean;
|
||||||
|
err: string;
|
||||||
|
issueUrl: string;
|
||||||
|
text: string;
|
||||||
|
progress: string;
|
||||||
|
downloadBusy: boolean;
|
||||||
|
downloadProgress: string;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.BugReportDialog")
|
@replaceableComponent("views.dialogs.BugReportDialog")
|
||||||
export default class BugReportDialog extends React.Component {
|
export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
|
private unmounted: boolean;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -41,25 +59,18 @@ export default class BugReportDialog extends React.Component {
|
||||||
downloadBusy: false,
|
downloadBusy: false,
|
||||||
downloadProgress: null,
|
downloadProgress: null,
|
||||||
};
|
};
|
||||||
this._unmounted = false;
|
this.unmounted = false;
|
||||||
this._onSubmit = this._onSubmit.bind(this);
|
|
||||||
this._onCancel = this._onCancel.bind(this);
|
|
||||||
this._onTextChange = this._onTextChange.bind(this);
|
|
||||||
this._onIssueUrlChange = this._onIssueUrlChange.bind(this);
|
|
||||||
this._onSendLogsChange = this._onSendLogsChange.bind(this);
|
|
||||||
this._sendProgressCallback = this._sendProgressCallback.bind(this);
|
|
||||||
this._downloadProgressCallback = this._downloadProgressCallback.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this.unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancel(ev) {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSubmit(ev) {
|
private onSubmit = (): void => {
|
||||||
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
||||||
this.setState({
|
this.setState({
|
||||||
err: _t("Please tell us what went wrong or, better, create a GitHub issue that describes the problem."),
|
err: _t("Please tell us what went wrong or, better, create a GitHub issue that describes the problem."),
|
||||||
|
@ -72,15 +83,15 @@ export default class BugReportDialog extends React.Component {
|
||||||
(this.state.issueUrl.length > 0 ? this.state.issueUrl : 'No issue link given');
|
(this.state.issueUrl.length > 0 ? this.state.issueUrl : 'No issue link given');
|
||||||
|
|
||||||
this.setState({ busy: true, progress: null, err: null });
|
this.setState({ busy: true, progress: null, err: null });
|
||||||
this._sendProgressCallback(_t("Preparing to send logs"));
|
this.sendProgressCallback(_t("Preparing to send logs"));
|
||||||
|
|
||||||
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
|
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
|
||||||
userText,
|
userText,
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
progressCallback: this._sendProgressCallback,
|
progressCallback: this.sendProgressCallback,
|
||||||
label: this.props.label,
|
label: this.props.label,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (!this._unmounted) {
|
if (!this.unmounted) {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
// N.B. first param is passed to piwik and so doesn't want i18n
|
// N.B. first param is passed to piwik and so doesn't want i18n
|
||||||
|
@ -91,7 +102,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (!this._unmounted) {
|
if (!this.unmounted) {
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
progress: null,
|
progress: null,
|
||||||
|
@ -101,14 +112,14 @@ export default class BugReportDialog extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDownload = async (ev) => {
|
private onDownload = async (): Promise<void> => {
|
||||||
this.setState({ downloadBusy: true });
|
this.setState({ downloadBusy: true });
|
||||||
this._downloadProgressCallback(_t("Preparing to download logs"));
|
this.downloadProgressCallback(_t("Preparing to download logs"));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await downloadBugReport({
|
await downloadBugReport({
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
progressCallback: this._downloadProgressCallback,
|
progressCallback: this.downloadProgressCallback,
|
||||||
label: this.props.label,
|
label: this.props.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,7 +128,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
downloadProgress: null,
|
downloadProgress: null,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!this._unmounted) {
|
if (!this.unmounted) {
|
||||||
this.setState({
|
this.setState({
|
||||||
downloadBusy: false,
|
downloadBusy: false,
|
||||||
downloadProgress: _t("Failed to send logs: ") + `${err.message}`,
|
downloadProgress: _t("Failed to send logs: ") + `${err.message}`,
|
||||||
|
@ -126,33 +137,29 @@ export default class BugReportDialog extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onTextChange(ev) {
|
private onTextChange = (ev: React.FormEvent<HTMLTextAreaElement>): void => {
|
||||||
this.setState({ text: ev.target.value });
|
this.setState({ text: ev.currentTarget.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onIssueUrlChange(ev) {
|
private onIssueUrlChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({ issueUrl: ev.target.value });
|
this.setState({ issueUrl: ev.currentTarget.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSendLogsChange(ev) {
|
private sendProgressCallback = (progress: string): void => {
|
||||||
this.setState({ sendLogs: ev.target.checked });
|
if (this.unmounted) {
|
||||||
}
|
|
||||||
|
|
||||||
_sendProgressCallback(progress) {
|
|
||||||
if (this._unmounted) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({progress: progress});
|
this.setState({ progress });
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadProgressCallback(downloadProgress) {
|
private downloadProgressCallback = (downloadProgress: string): void => {
|
||||||
if (this._unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ downloadProgress });
|
this.setState({ downloadProgress });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
@ -183,7 +190,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_BugReportDialog" onFinished={this._onCancel}
|
<BaseDialog className="mx_BugReportDialog" onFinished={this.onCancel}
|
||||||
title={_t('Submit debug logs')}
|
title={_t('Submit debug logs')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
|
@ -213,7 +220,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
</b></p>
|
</b></p>
|
||||||
|
|
||||||
<div className="mx_BugReportDialog_download">
|
<div className="mx_BugReportDialog_download">
|
||||||
<AccessibleButton onClick={this._onDownload} kind="link" disabled={this.state.downloadBusy}>
|
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
|
||||||
{ _t("Download logs") }
|
{ _t("Download logs") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
||||||
|
@ -223,7 +230,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
className="mx_BugReportDialog_field_input"
|
className="mx_BugReportDialog_field_input"
|
||||||
label={_t("GitHub issue")}
|
label={_t("GitHub issue")}
|
||||||
onChange={this._onIssueUrlChange}
|
onChange={this.onIssueUrlChange}
|
||||||
value={this.state.issueUrl}
|
value={this.state.issueUrl}
|
||||||
placeholder="https://github.com/vector-im/element-web/issues/..."
|
placeholder="https://github.com/vector-im/element-web/issues/..."
|
||||||
/>
|
/>
|
||||||
|
@ -232,7 +239,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
element="textarea"
|
element="textarea"
|
||||||
label={_t("Notes")}
|
label={_t("Notes")}
|
||||||
rows={5}
|
rows={5}
|
||||||
onChange={this._onTextChange}
|
onChange={this.onTextChange}
|
||||||
value={this.state.text}
|
value={this.state.text}
|
||||||
placeholder={_t(
|
placeholder={_t(
|
||||||
"If there is additional context that would help in " +
|
"If there is additional context that would help in " +
|
||||||
|
@ -245,17 +252,12 @@ export default class BugReportDialog extends React.Component {
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons primaryButton={_t("Send logs")}
|
<DialogButtons primaryButton={_t("Send logs")}
|
||||||
onPrimaryButtonClick={this._onSubmit}
|
onPrimaryButtonClick={this.onSubmit}
|
||||||
focus={true}
|
focus={true}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
disabled={this.state.busy}
|
disabled={this.state.busy}
|
||||||
/>
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BugReportDialog.propTypes = {
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
initialText: PropTypes.string,
|
|
||||||
};
|
|
|
@ -16,21 +16,26 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
newVersion: string;
|
||||||
|
version: string;
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const REPOS = ['vector-im/element-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
|
const REPOS = ['vector-im/element-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
|
||||||
|
|
||||||
export default class ChangelogDialog extends React.Component {
|
export default class ChangelogDialog extends React.Component<IProps> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
const version = this.props.newVersion.split('-');
|
const version = this.props.newVersion.split('-');
|
||||||
const version2 = this.props.version.split('-');
|
const version2 = this.props.version.split('-');
|
||||||
if (version == null || version2 == null) return;
|
if (version == null || version2 == null) return;
|
||||||
|
@ -49,7 +54,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_elementsForCommit(commit) {
|
private elementsForCommit(commit): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<li key={commit.sha} className="mx_ChangelogDialog_li">
|
<li key={commit.sha} className="mx_ChangelogDialog_li">
|
||||||
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
|
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
|
||||||
|
@ -59,7 +64,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||||
|
|
||||||
|
@ -72,7 +77,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
msg: this.state[repo],
|
msg: this.state[repo],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
content = this.state[repo].map(this._elementsForCommit);
|
content = this.state[repo].map(this.elementsForCommit);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={repo}>
|
<div key={repo}>
|
||||||
|
@ -99,9 +104,3 @@ export default class ChangelogDialog extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangelogDialog.propTypes = {
|
|
||||||
version: PropTypes.string.isRequired,
|
|
||||||
newVersion: PropTypes.string.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
|
@ -19,6 +19,16 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
redact: () => Promise<void>;
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
isRedacting: boolean;
|
||||||
|
redactionErrorCode: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A dialog for confirming a redaction.
|
* A dialog for confirming a redaction.
|
||||||
* Also shows a spinner (and possible error) while the redaction is ongoing,
|
* Also shows a spinner (and possible error) while the redaction is ongoing,
|
||||||
|
@ -32,7 +42,7 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
* To avoid this, we keep the dialog open as long as /redact is in progress.
|
* To avoid this, we keep the dialog open as long as /redact is in progress.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.dialogs.ConfirmAndWaitRedactDialog")
|
@replaceableComponent("views.dialogs.ConfirmAndWaitRedactDialog")
|
||||||
export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
|
export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IProps, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -41,7 +51,7 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onParentFinished = async (proceed) => {
|
public onParentFinished = async (proceed: boolean): Promise<void> => {
|
||||||
if (proceed) {
|
if (proceed) {
|
||||||
this.setState({isRedacting: true});
|
this.setState({isRedacting: true});
|
||||||
try {
|
try {
|
||||||
|
@ -60,7 +70,7 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (this.state.isRedacting) {
|
if (this.state.isRedacting) {
|
||||||
if (this.state.redactionErrorCode) {
|
if (this.state.redactionErrorCode) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
@ -19,11 +19,15 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A dialog for confirming a redaction.
|
* A dialog for confirming a redaction.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
|
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
|
||||||
export default class ConfirmRedactDialog extends React.Component {
|
export default class ConfirmRedactDialog extends React.Component<IProps> {
|
||||||
render() {
|
render() {
|
||||||
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
|
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
|
||||||
return (
|
return (
|
|
@ -15,14 +15,32 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
||||||
|
member: RoomMember;
|
||||||
|
// group member object. Supply either this or 'member'
|
||||||
|
groupMember: GroupMemberType;
|
||||||
|
// needed if a group member is specified
|
||||||
|
matrixClient?: MatrixClient,
|
||||||
|
action: string; // eg. 'Ban'
|
||||||
|
title: string; // eg. 'Ban this user?'
|
||||||
|
|
||||||
|
// Whether to display a text field for a reason
|
||||||
|
// If true, the second argument to onFinished will
|
||||||
|
// be the string entered.
|
||||||
|
askReason?: boolean;
|
||||||
|
danger?: boolean;
|
||||||
|
onFinished: (success: boolean, reason?: HTMLInputElement) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A dialog for confirming an operation on another user.
|
* A dialog for confirming an operation on another user.
|
||||||
* Takes a user ID and a verb, displays the target user prominently
|
* Takes a user ID and a verb, displays the target user prominently
|
||||||
|
@ -32,53 +50,27 @@ import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.dialogs.ConfirmUserActionDialog")
|
@replaceableComponent("views.dialogs.ConfirmUserActionDialog")
|
||||||
export default class ConfirmUserActionDialog extends React.Component {
|
export default class ConfirmUserActionDialog extends React.Component<IProps> {
|
||||||
static propTypes = {
|
private reasonField: React.RefObject<HTMLInputElement> = React.createRef();
|
||||||
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
|
||||||
member: PropTypes.object,
|
|
||||||
// group member object. Supply either this or 'member'
|
|
||||||
groupMember: GroupMemberType,
|
|
||||||
// needed if a group member is specified
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
action: PropTypes.string.isRequired, // eg. 'Ban'
|
|
||||||
title: PropTypes.string.isRequired, // eg. 'Ban this user?'
|
|
||||||
|
|
||||||
// Whether to display a text field for a reason
|
|
||||||
// If true, the second argument to onFinished will
|
|
||||||
// be the string entered.
|
|
||||||
askReason: PropTypes.bool,
|
|
||||||
danger: PropTypes.bool,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
danger: false,
|
danger: false,
|
||||||
askReason: false,
|
askReason: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
public onOk = (): void => {
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._reasonField = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
onOk = () => {
|
|
||||||
let reason;
|
let reason;
|
||||||
if (this._reasonField) {
|
if (this.reasonField) {
|
||||||
reason = this._reasonField.value;
|
reason = this.reasonField.current;
|
||||||
}
|
}
|
||||||
this.props.onFinished(true, reason);
|
this.props.onFinished(true, reason);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCancel = () => {
|
public onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
_collectReasonField = e => {
|
public render() {
|
||||||
this._reasonField = e;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||||
|
@ -92,7 +84,7 @@ export default class ConfirmUserActionDialog extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onOk}>
|
<form onSubmit={this.onOk}>
|
||||||
<input className="mx_ConfirmUserActionDialog_reasonField"
|
<input className="mx_ConfirmUserActionDialog_reasonField"
|
||||||
ref={this._collectReasonField}
|
ref={this.reasonField}
|
||||||
placeholder={_t("Reason")}
|
placeholder={_t("Reason")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
|
@ -15,22 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.ConfirmWipeDeviceDialog")
|
interface IProps {
|
||||||
export default class ConfirmWipeDeviceDialog extends React.Component {
|
onFinished: (success: boolean) => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
_onConfirm = () => {
|
@replaceableComponent("views.dialogs.ConfirmWipeDeviceDialog")
|
||||||
|
export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
||||||
|
private onConfirm = (): void => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onDecline = () => {
|
private onDecline = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,10 +54,10 @@ export default class ConfirmWipeDeviceDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Clear all data")}
|
primaryButton={_t("Clear all data")}
|
||||||
onPrimaryButtonClick={this._onConfirm}
|
onPrimaryButtonClick={this.onConfirm}
|
||||||
primaryButtonClass="danger"
|
primaryButtonClass="danger"
|
||||||
cancelButton={_t("Cancel")}
|
cancelButton={_t("Cancel")}
|
||||||
onCancel={this._onDecline}
|
onCancel={this.onDecline}
|
||||||
/>
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
|
@ -15,44 +15,51 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.CreateGroupDialog")
|
interface IProps {
|
||||||
export default class CreateGroupDialog extends React.Component {
|
onFinished: (success: boolean) => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
interface IState {
|
||||||
|
groupName: string;
|
||||||
|
groupId: string;
|
||||||
|
groupIdError: string;
|
||||||
|
creating: boolean;
|
||||||
|
createError: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.dialogs.CreateGroupDialog")
|
||||||
|
export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
||||||
|
public state = {
|
||||||
groupName: '',
|
groupName: '',
|
||||||
groupId: '',
|
groupId: '',
|
||||||
groupError: null,
|
groupIdError: '',
|
||||||
creating: false,
|
creating: false,
|
||||||
createError: null,
|
createError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
_onGroupNameChange = e => {
|
private onGroupNameChange = (e: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
groupName: e.target.value,
|
groupName: e.currentTarget.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onGroupIdChange = e => {
|
private onGroupIdChange = (e: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
groupId: e.target.value,
|
groupId: e.currentTarget.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onGroupIdBlur = e => {
|
private onGroupIdBlur = (): void => {
|
||||||
this._checkGroupId();
|
this.checkGroupId();
|
||||||
};
|
};
|
||||||
|
|
||||||
_checkGroupId(e) {
|
private checkGroupId() {
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!this.state.groupId) {
|
if (!this.state.groupId) {
|
||||||
error = _t("Community IDs cannot be empty.");
|
error = _t("Community IDs cannot be empty.");
|
||||||
|
@ -67,12 +74,12 @@ export default class CreateGroupDialog extends React.Component {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFormSubmit = e => {
|
private onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this._checkGroupId()) return;
|
if (this.checkGroupId()) return;
|
||||||
|
|
||||||
const profile = {};
|
const profile: any = {};
|
||||||
if (this.state.groupName !== '') {
|
if (this.state.groupName !== '') {
|
||||||
profile.name = this.state.groupName;
|
profile.name = this.state.groupName;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +128,7 @@ export default class CreateGroupDialog extends React.Component {
|
||||||
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
|
||||||
title={_t('Create Community')}
|
title={_t('Create Community')}
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<div className="mx_CreateGroupDialog_inputRow">
|
<div className="mx_CreateGroupDialog_inputRow">
|
||||||
<div className="mx_CreateGroupDialog_label">
|
<div className="mx_CreateGroupDialog_label">
|
||||||
|
@ -129,9 +136,9 @@ export default class CreateGroupDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="groupname" className="mx_CreateGroupDialog_input"
|
<input id="groupname" className="mx_CreateGroupDialog_input"
|
||||||
autoFocus={true} size="64"
|
autoFocus={true} size={64}
|
||||||
placeholder={_t('Example')}
|
placeholder={_t('Example')}
|
||||||
onChange={this._onGroupNameChange}
|
onChange={this.onGroupNameChange}
|
||||||
value={this.state.groupName}
|
value={this.state.groupName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,10 +151,10 @@ export default class CreateGroupDialog extends React.Component {
|
||||||
<span className="mx_CreateGroupDialog_prefix">+</span>
|
<span className="mx_CreateGroupDialog_prefix">+</span>
|
||||||
<input id="groupid"
|
<input id="groupid"
|
||||||
className="mx_CreateGroupDialog_input mx_CreateGroupDialog_input_hasPrefixAndSuffix"
|
className="mx_CreateGroupDialog_input mx_CreateGroupDialog_input_hasPrefixAndSuffix"
|
||||||
size="32"
|
size={32}
|
||||||
placeholder={_t('example')}
|
placeholder={_t('example')}
|
||||||
onChange={this._onGroupIdChange}
|
onChange={this.onGroupIdChange}
|
||||||
onBlur={this._onGroupIdBlur}
|
onBlur={this.onGroupIdBlur}
|
||||||
value={this.state.groupId}
|
value={this.state.groupId}
|
||||||
/>
|
/>
|
||||||
<span className="mx_CreateGroupDialog_suffix">
|
<span className="mx_CreateGroupDialog_suffix">
|
|
@ -22,7 +22,11 @@ import { _t } from '../../../languageHandler';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
export default (props) => {
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: IProps) => {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
|
|
||||||
const _onLogoutClicked = () => {
|
const _onLogoutClicked = () => {
|
||||||
|
@ -40,7 +44,7 @@ export default (props) => {
|
||||||
onFinished: (doLogout) => {
|
onFinished: (doLogout) => {
|
||||||
if (doLogout) {
|
if (doLogout) {
|
||||||
dis.dispatch({action: 'logout'});
|
dis.dispatch({action: 'logout'});
|
||||||
props.onFinished();
|
props.onFinished(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Analytics from '../../../Analytics';
|
import Analytics from '../../../Analytics';
|
||||||
|
@ -28,8 +27,25 @@ import {DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry} from "../auth/Interactiv
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
shouldErase: boolean;
|
||||||
|
errStr: string;
|
||||||
|
authData: any; // for UIA
|
||||||
|
authEnabled: boolean; // see usages for information
|
||||||
|
|
||||||
|
// A few strings that are passed to InteractiveAuth for design or are displayed
|
||||||
|
// next to the InteractiveAuth component.
|
||||||
|
bodyText: string;
|
||||||
|
continueText: string;
|
||||||
|
continueKind: string;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.DeactivateAccountDialog")
|
@replaceableComponent("views.dialogs.DeactivateAccountDialog")
|
||||||
export default class DeactivateAccountDialog extends React.Component {
|
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -46,10 +62,10 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
continueKind: null,
|
continueKind: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._initAuth(/* shouldErase= */false);
|
this.initAuth(/* shouldErase= */false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onStagePhaseChange = (stage, phase) => {
|
private onStagePhaseChange = (stage: string, phase: string): void => {
|
||||||
const dialogAesthetics = {
|
const dialogAesthetics = {
|
||||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."),
|
body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."),
|
||||||
|
@ -87,19 +103,19 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
this.setState({bodyText, continueText, continueKind});
|
this.setState({bodyText, continueText, continueKind});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onUIAuthFinished = (success, result, extra) => {
|
private onUIAuthFinished = (success: boolean, result: Error) => {
|
||||||
if (success) return; // great! makeRequest() will be called too.
|
if (success) return; // great! makeRequest() will be called too.
|
||||||
|
|
||||||
if (result === ERROR_USER_CANCELLED) {
|
if (result === ERROR_USER_CANCELLED) {
|
||||||
this._onCancel();
|
this.onCancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("Error during UI Auth:", {result, extra});
|
console.error("Error during UI Auth:", { result });
|
||||||
this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")});
|
this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onUIAuthComplete = (auth) => {
|
private onUIAuthComplete = (auth: any): void => {
|
||||||
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
|
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
|
||||||
// Deactivation worked - logout & close this dialog
|
// Deactivation worked - logout & close this dialog
|
||||||
Analytics.trackEvent('Account', 'Deactivate Account');
|
Analytics.trackEvent('Account', 'Deactivate Account');
|
||||||
|
@ -111,9 +127,9 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onEraseFieldChange = (ev) => {
|
private onEraseFieldChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
shouldErase: ev.target.checked,
|
shouldErase: ev.currentTarget.checked,
|
||||||
|
|
||||||
// Disable the auth form because we're going to have to reinitialize the auth
|
// Disable the auth form because we're going to have to reinitialize the auth
|
||||||
// information. We do this because we can't modify the parameters in the UIA
|
// information. We do this because we can't modify the parameters in the UIA
|
||||||
|
@ -123,14 +139,14 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
// As mentioned above, set up for auth again to get updated UIA session info
|
// As mentioned above, set up for auth again to get updated UIA session info
|
||||||
this._initAuth(/* shouldErase= */ev.target.checked);
|
this.initAuth(/* shouldErase= */ev.currentTarget.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCancel() {
|
private onCancel(): void {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_initAuth(shouldErase) {
|
private initAuth(shouldErase: boolean): void {
|
||||||
MatrixClientPeg.get().deactivateAccount(null, shouldErase).then(r => {
|
MatrixClientPeg.get().deactivateAccount(null, shouldErase).then(r => {
|
||||||
// If we got here, oops. The server didn't require any auth.
|
// If we got here, oops. The server didn't require any auth.
|
||||||
// Our application lifecycle will catch the error and do the logout bits.
|
// Our application lifecycle will catch the error and do the logout bits.
|
||||||
|
@ -148,7 +164,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
@ -166,9 +182,9 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
<InteractiveAuth
|
<InteractiveAuth
|
||||||
matrixClient={MatrixClientPeg.get()}
|
matrixClient={MatrixClientPeg.get()}
|
||||||
authData={this.state.authData}
|
authData={this.state.authData}
|
||||||
makeRequest={this._onUIAuthComplete}
|
makeRequest={this.onUIAuthComplete}
|
||||||
onAuthFinished={this._onUIAuthFinished}
|
onAuthFinished={this.onUIAuthFinished}
|
||||||
onStagePhaseChange={this._onStagePhaseChange}
|
onStagePhaseChange={this.onStagePhaseChange}
|
||||||
continueText={this.state.continueText}
|
continueText={this.state.continueText}
|
||||||
continueKind={this.state.continueKind}
|
continueKind={this.state.continueKind}
|
||||||
/>
|
/>
|
||||||
|
@ -214,7 +230,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
<p>
|
<p>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
checked={this.state.shouldErase}
|
checked={this.state.shouldErase}
|
||||||
onChange={this._onEraseFieldChange}
|
onChange={this.onEraseFieldChange}
|
||||||
>
|
>
|
||||||
{_t(
|
{_t(
|
||||||
"Please forget all messages I have sent when my account is deactivated " +
|
"Please forget all messages I have sent when my account is deactivated " +
|
||||||
|
@ -235,7 +251,3 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DeactivateAccountDialog.propTypes = {
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
|
@ -26,37 +26,37 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.ErrorDialog")
|
interface IProps {
|
||||||
export default class ErrorDialog extends React.Component {
|
onFinished: (success: boolean) => void;
|
||||||
static propTypes = {
|
title?: string;
|
||||||
title: PropTypes.string,
|
description?: React.ReactNode;
|
||||||
description: PropTypes.oneOfType([
|
button?: string;
|
||||||
PropTypes.element,
|
focus?: boolean;
|
||||||
PropTypes.string,
|
headerImage?: string;
|
||||||
]),
|
}
|
||||||
button: PropTypes.string,
|
|
||||||
focus: PropTypes.bool,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
headerImage: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
interface IState {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.dialogs.ErrorDialog")
|
||||||
|
export default class ErrorDialog extends React.Component<IProps, IState> {
|
||||||
|
public static defaultProps = {
|
||||||
focus: true,
|
focus: true,
|
||||||
title: null,
|
title: null,
|
||||||
description: null,
|
description: null,
|
||||||
button: null,
|
button: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
onClick = () => {
|
private onClick = () => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
|
@ -16,22 +16,21 @@ limitations under the License.
|
||||||
|
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t, pickBestLanguage } from '../../../languageHandler';
|
import { _t, pickBestLanguage } from '../../../languageHandler';
|
||||||
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
||||||
|
|
||||||
class TermsCheckbox extends React.PureComponent {
|
interface ITermsCheckboxProps {
|
||||||
static propTypes = {
|
onChange: (url: string, checked: boolean) => void;
|
||||||
onChange: PropTypes.func.isRequired,
|
url: string;
|
||||||
url: PropTypes.string.isRequired,
|
checked: boolean;
|
||||||
checked: PropTypes.bool.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (ev) => {
|
class TermsCheckbox extends React.PureComponent<ITermsCheckboxProps> {
|
||||||
this.props.onChange(this.props.url, ev.target.checked);
|
private onChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
||||||
|
this.props.onChange(this.props.url, ev.currentTarget.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -42,30 +41,34 @@ class TermsCheckbox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.TermsDialog")
|
interface ITermsDialogProps {
|
||||||
export default class TermsDialog extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
/**
|
/**
|
||||||
* Array of [Service, policies] pairs, where policies is the response from the
|
* Array of [Service, policies] pairs, where policies is the response from the
|
||||||
* /terms endpoint for that service
|
* /terms endpoint for that service
|
||||||
*/
|
*/
|
||||||
policiesAndServicePairs: PropTypes.array.isRequired,
|
policiesAndServicePairs: any[],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* urls that the user has already agreed to
|
* urls that the user has already agreed to
|
||||||
*/
|
*/
|
||||||
agreedUrls: PropTypes.arrayOf(PropTypes.string),
|
agreedUrls?: string[],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with:
|
* Called with:
|
||||||
* * success {bool} True if the user accepted any douments, false if cancelled
|
* * success {bool} True if the user accepted any douments, false if cancelled
|
||||||
* * agreedUrls {string[]} List of agreed URLs
|
* * agreedUrls {string[]} List of agreed URLs
|
||||||
*/
|
*/
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: (success: boolean, agreedUrls?: string[]) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
agreedUrls: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.dialogs.TermsDialog")
|
||||||
|
export default class TermsDialog extends React.PureComponent<ITermsDialogProps, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
// url -> boolean
|
// url -> boolean
|
||||||
agreedUrls: {},
|
agreedUrls: {},
|
||||||
|
@ -75,15 +78,15 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick = () => {
|
private onCancelClick = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNextClick = () => {
|
private onNextClick = (): void => {
|
||||||
this.props.onFinished(true, Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]));
|
this.props.onFinished(true, Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]));
|
||||||
}
|
}
|
||||||
|
|
||||||
_nameForServiceType(serviceType, host) {
|
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
|
||||||
switch (serviceType) {
|
switch (serviceType) {
|
||||||
case SERVICE_TYPES.IS:
|
case SERVICE_TYPES.IS:
|
||||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||||
|
@ -92,7 +95,7 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_summaryForServiceType(serviceType) {
|
private summaryForServiceType(serviceType: SERVICE_TYPES): JSX.Element {
|
||||||
switch (serviceType) {
|
switch (serviceType) {
|
||||||
case SERVICE_TYPES.IS:
|
case SERVICE_TYPES.IS:
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -107,13 +110,13 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTermsCheckboxChange = (url, checked) => {
|
private onTermsCheckboxChange = (url: string, checked: boolean) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
agreedUrls: Object.assign({}, this.state.agreedUrls, { [url]: checked }),
|
agreedUrls: Object.assign({}, this.state.agreedUrls, { [url]: checked }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
|
@ -128,8 +131,8 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
let serviceName;
|
let serviceName;
|
||||||
let summary;
|
let summary;
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
serviceName = this._nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
|
serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
|
||||||
summary = this._summaryForServiceType(
|
summary = this.summaryForServiceType(
|
||||||
policiesAndService.service.serviceType,
|
policiesAndService.service.serviceType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -137,12 +140,15 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
rows.push(<tr key={termDoc[termsLang].url}>
|
rows.push(<tr key={termDoc[termsLang].url}>
|
||||||
<td className="mx_TermsDialog_service">{serviceName}</td>
|
<td className="mx_TermsDialog_service">{serviceName}</td>
|
||||||
<td className="mx_TermsDialog_summary">{summary}</td>
|
<td className="mx_TermsDialog_summary">{summary}</td>
|
||||||
<td>{termDoc[termsLang].name} <a rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
|
<td>
|
||||||
|
{termDoc[termsLang].name}
|
||||||
|
<a rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
|
||||||
<span className="mx_TermsDialog_link" />
|
<span className="mx_TermsDialog_link" />
|
||||||
</a></td>
|
</a>
|
||||||
|
</td>
|
||||||
<td><TermsCheckbox
|
<td><TermsCheckbox
|
||||||
url={termDoc[termsLang].url}
|
url={termDoc[termsLang].url}
|
||||||
onChange={this._onTermsCheckboxChange}
|
onChange={this.onTermsCheckboxChange}
|
||||||
checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
|
checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
|
||||||
/></td>
|
/></td>
|
||||||
</tr>);
|
</tr>);
|
||||||
|
@ -176,7 +182,7 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
onFinished={this._onCancelClick}
|
onFinished={this.onCancelClick}
|
||||||
title={_t("Terms of Service")}
|
title={_t("Terms of Service")}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
|
@ -197,8 +203,8 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
|
|
||||||
<DialogButtons primaryButton={_t('Next')}
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onCancel={this._onCancelClick}
|
onCancel={this.onCancelClick}
|
||||||
onPrimaryButtonClick={this._onNextClick}
|
onPrimaryButtonClick={this.onNextClick}
|
||||||
primaryDisabled={!enableSubmit}
|
primaryDisabled={!enableSubmit}
|
||||||
/>
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
|
@ -16,11 +16,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import TabbedView, {Tab} from "../../structures/TabbedView";
|
import TabbedView, {Tab} from "../../structures/TabbedView";
|
||||||
import {_t, _td} from "../../../languageHandler";
|
import {_t, _td} from "../../../languageHandler";
|
||||||
import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab";
|
import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore, { CallbackFn } from "../../../settings/SettingsStore";
|
||||||
import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab";
|
import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab";
|
||||||
import AppearanceUserSettingsTab from "../settings/tabs/user/AppearanceUserSettingsTab";
|
import AppearanceUserSettingsTab from "../settings/tabs/user/AppearanceUserSettingsTab";
|
||||||
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
|
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
|
||||||
|
@ -35,41 +34,49 @@ import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab
|
||||||
import {UIFeature} from "../../../settings/UIFeature";
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
export const USER_GENERAL_TAB = "USER_GENERAL_TAB";
|
export enum UserTab {
|
||||||
export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB";
|
General = "USER_GENERAL_TAB",
|
||||||
export const USER_FLAIR_TAB = "USER_FLAIR_TAB";
|
Appearance = "USER_APPEARANCE_TAB",
|
||||||
export const USER_NOTIFICATIONS_TAB = "USER_NOTIFICATIONS_TAB";
|
Flair = "USER_FLAIR_TAB",
|
||||||
export const USER_PREFERENCES_TAB = "USER_PREFERENCES_TAB";
|
Notifications = "USER_NOTIFICATIONS_TAB",
|
||||||
export const USER_VOICE_TAB = "USER_VOICE_TAB";
|
Preferences = "USER_PREFERENCES_TAB",
|
||||||
export const USER_SECURITY_TAB = "USER_SECURITY_TAB";
|
Voice = "USER_VOICE_TAB",
|
||||||
export const USER_LABS_TAB = "USER_LABS_TAB";
|
Security = "USER_SECURITY_TAB",
|
||||||
export const USER_MJOLNIR_TAB = "USER_MJOLNIR_TAB";
|
Labs = "USER_LABS_TAB",
|
||||||
export const USER_HELP_TAB = "USER_HELP_TAB";
|
Mjolnir = "USER_MJOLNIR_TAB",
|
||||||
|
Help = "USER_HELP_TAB",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
initialTabId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
mjolnirEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.UserSettingsDialog")
|
@replaceableComponent("views.dialogs.UserSettingsDialog")
|
||||||
export default class UserSettingsDialog extends React.Component {
|
export default class UserSettingsDialog extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
private mjolnirWatcher: string;
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
initialTabId: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
|
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this._mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this._mjolnirChanged.bind(this));
|
this.mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this.mjolnirChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
SettingsStore.unwatchSetting(this._mjolnirWatcher);
|
SettingsStore.unwatchSetting(this.mjolnirWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mjolnirChanged(settingName, roomId, atLevel, newValue) {
|
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
|
||||||
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
||||||
this.setState({mjolnirEnabled: newValue});
|
this.setState({mjolnirEnabled: newValue});
|
||||||
}
|
}
|
||||||
|
@ -78,33 +85,33 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_GENERAL_TAB,
|
UserTab.General,
|
||||||
_td("General"),
|
_td("General"),
|
||||||
"mx_UserSettingsDialog_settingsIcon",
|
"mx_UserSettingsDialog_settingsIcon",
|
||||||
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_APPEARANCE_TAB,
|
UserTab.Appearance,
|
||||||
_td("Appearance"),
|
_td("Appearance"),
|
||||||
"mx_UserSettingsDialog_appearanceIcon",
|
"mx_UserSettingsDialog_appearanceIcon",
|
||||||
<AppearanceUserSettingsTab />,
|
<AppearanceUserSettingsTab />,
|
||||||
));
|
));
|
||||||
if (SettingsStore.getValue(UIFeature.Flair)) {
|
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_FLAIR_TAB,
|
UserTab.Flair,
|
||||||
_td("Flair"),
|
_td("Flair"),
|
||||||
"mx_UserSettingsDialog_flairIcon",
|
"mx_UserSettingsDialog_flairIcon",
|
||||||
<FlairUserSettingsTab />,
|
<FlairUserSettingsTab />,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_NOTIFICATIONS_TAB,
|
UserTab.Notifications,
|
||||||
_td("Notifications"),
|
_td("Notifications"),
|
||||||
"mx_UserSettingsDialog_bellIcon",
|
"mx_UserSettingsDialog_bellIcon",
|
||||||
<NotificationUserSettingsTab />,
|
<NotificationUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_PREFERENCES_TAB,
|
UserTab.Preferences,
|
||||||
_td("Preferences"),
|
_td("Preferences"),
|
||||||
"mx_UserSettingsDialog_preferencesIcon",
|
"mx_UserSettingsDialog_preferencesIcon",
|
||||||
<PreferencesUserSettingsTab />,
|
<PreferencesUserSettingsTab />,
|
||||||
|
@ -112,7 +119,7 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
|
|
||||||
if (SettingsStore.getValue(UIFeature.Voip)) {
|
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_VOICE_TAB,
|
UserTab.Voice,
|
||||||
_td("Voice & Video"),
|
_td("Voice & Video"),
|
||||||
"mx_UserSettingsDialog_voiceIcon",
|
"mx_UserSettingsDialog_voiceIcon",
|
||||||
<VoiceUserSettingsTab />,
|
<VoiceUserSettingsTab />,
|
||||||
|
@ -120,7 +127,7 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_SECURITY_TAB,
|
UserTab.Security,
|
||||||
_td("Security & Privacy"),
|
_td("Security & Privacy"),
|
||||||
"mx_UserSettingsDialog_securityIcon",
|
"mx_UserSettingsDialog_securityIcon",
|
||||||
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
|
@ -130,7 +137,7 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))
|
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))
|
||||||
) {
|
) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_LABS_TAB,
|
UserTab.Labs,
|
||||||
_td("Labs"),
|
_td("Labs"),
|
||||||
"mx_UserSettingsDialog_labsIcon",
|
"mx_UserSettingsDialog_labsIcon",
|
||||||
<LabsUserSettingsTab />,
|
<LabsUserSettingsTab />,
|
||||||
|
@ -138,17 +145,17 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
}
|
}
|
||||||
if (this.state.mjolnirEnabled) {
|
if (this.state.mjolnirEnabled) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_MJOLNIR_TAB,
|
UserTab.Mjolnir,
|
||||||
_td("Ignored users"),
|
_td("Ignored users"),
|
||||||
"mx_UserSettingsDialog_mjolnirIcon",
|
"mx_UserSettingsDialog_mjolnirIcon",
|
||||||
<MjolnirUserSettingsTab />,
|
<MjolnirUserSettingsTab />,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_HELP_TAB,
|
UserTab.Help,
|
||||||
_td("Help & About"),
|
_td("Help & About"),
|
||||||
"mx_UserSettingsDialog_helpIcon",
|
"mx_UserSettingsDialog_helpIcon",
|
||||||
<HelpUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<HelpUserSettingsTab closeSettingsFn={() => this.props.onFinished(true)} />,
|
||||||
));
|
));
|
||||||
|
|
||||||
return tabs;
|
return tabs;
|
|
@ -15,22 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import * as sdk from "../../../../index";
|
import * as sdk from "../../../../index";
|
||||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.security.ConfirmDestroyCrossSigningDialog")
|
interface IProps {
|
||||||
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
onFinished: (success: boolean) => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
_onConfirm = () => {
|
@replaceableComponent("views.dialogs.security.ConfirmDestroyCrossSigningDialog")
|
||||||
|
export default class ConfirmDestroyCrossSigningDialog extends React.Component<IProps> {
|
||||||
|
private onConfirm = (): void => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onDecline = () => {
|
private onDecline = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,10 +56,10 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Clear cross-signing keys")}
|
primaryButton={_t("Clear cross-signing keys")}
|
||||||
onPrimaryButtonClick={this._onConfirm}
|
onPrimaryButtonClick={this.onConfirm}
|
||||||
primaryButtonClass="danger"
|
primaryButtonClass="danger"
|
||||||
cancelButton={_t("Cancel")}
|
cancelButton={_t("Cancel")}
|
||||||
onCancel={this._onDecline}
|
onCancel={this.onDecline}
|
||||||
/>
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
|
@ -27,45 +26,50 @@ import Spinner from '../../elements/Spinner';
|
||||||
import InteractiveAuthDialog from '../InteractiveAuthDialog';
|
import InteractiveAuthDialog from '../InteractiveAuthDialog';
|
||||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
accountPassword?: string;
|
||||||
|
tokenLogin?: boolean;
|
||||||
|
onFinished?: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
error: Error | null;
|
||||||
|
canUploadKeysWithPasswordOnly?: boolean;
|
||||||
|
accountPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Walks the user through the process of creating a cross-signing keys. In most
|
* Walks the user through the process of creating a cross-signing keys. In most
|
||||||
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
||||||
* may need to complete some steps to proceed.
|
* may need to complete some steps to proceed.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.dialogs.security.CreateCrossSigningDialog")
|
@replaceableComponent("views.dialogs.security.CreateCrossSigningDialog")
|
||||||
export default class CreateCrossSigningDialog extends React.PureComponent {
|
export default class CreateCrossSigningDialog extends React.PureComponent<IProps, IState> {
|
||||||
static propTypes = {
|
constructor(props: IProps) {
|
||||||
accountPassword: PropTypes.string,
|
|
||||||
tokenLogin: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
error: null,
|
error: null,
|
||||||
// Does the server offer a UI auth flow with just m.login.password
|
// Does the server offer a UI auth flow with just m.login.password
|
||||||
// for /keys/device_signing/upload?
|
// for /keys/device_signing/upload?
|
||||||
canUploadKeysWithPasswordOnly: null,
|
|
||||||
accountPassword: props.accountPassword || "",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.state.accountPassword) {
|
|
||||||
// If we have an account password in memory, let's simplify and
|
// If we have an account password in memory, let's simplify and
|
||||||
// assume it means password auth is also supported for device
|
// assume it means password auth is also supported for device
|
||||||
// signing key upload as well. This avoids hitting the server to
|
// signing key upload as well. This avoids hitting the server to
|
||||||
// test auth flows, which may be slow under high load.
|
// test auth flows, which may be slow under high load.
|
||||||
this.state.canUploadKeysWithPasswordOnly = true;
|
canUploadKeysWithPasswordOnly: props.accountPassword ? true : null,
|
||||||
} else {
|
accountPassword: props.accountPassword || "",
|
||||||
this._queryKeyUploadAuth();
|
};
|
||||||
|
|
||||||
|
if (!this.state.accountPassword) {
|
||||||
|
this.queryKeyUploadAuth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount(): void {
|
||||||
this._bootstrapCrossSigning();
|
this.bootstrapCrossSigning();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _queryKeyUploadAuth() {
|
private async queryKeyUploadAuth(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
||||||
// We should never get here: the server should always require
|
// We should never get here: the server should always require
|
||||||
|
@ -86,7 +90,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_doBootstrapUIAuth = async (makeRequest) => {
|
private doBootstrapUIAuth = async (makeRequest: (authData: any) => void): Promise<void> => {
|
||||||
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
||||||
await makeRequest({
|
await makeRequest({
|
||||||
type: 'm.login.password',
|
type: 'm.login.password',
|
||||||
|
@ -137,7 +141,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_bootstrapCrossSigning = async () => {
|
private bootstrapCrossSigning = async (): Promise<void> => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
@ -146,13 +150,13 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cli.bootstrapCrossSigning({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
authUploadDeviceSigningKeys: this.doBootstrapUIAuth,
|
||||||
});
|
});
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.props.tokenLogin) {
|
if (this.props.tokenLogin) {
|
||||||
// ignore any failures, we are relying on grace period here
|
// ignore any failures, we are relying on grace period here
|
||||||
this.props.onFinished();
|
this.props.onFinished(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +165,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancel = () => {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +176,8 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
<p>{_t("Unable to set up keys")}</p>
|
<p>{_t("Unable to set up keys")}</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._bootstrapCrossSigning}
|
onPrimaryButtonClick={this.bootstrapCrossSigning}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
|
@ -15,47 +15,52 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
|
import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
|
||||||
import BaseDialog from '../BaseDialog';
|
import BaseDialog from '../BaseDialog';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore';
|
import { SetupEncryptionStore, Phase } from '../../../../stores/SetupEncryptionStore';
|
||||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||||
|
|
||||||
function iconFromPhase(phase) {
|
function iconFromPhase(phase: Phase) {
|
||||||
if (phase === PHASE_DONE) {
|
if (phase === Phase.Done) {
|
||||||
return require("../../../../../res/img/e2e/verified.svg");
|
return require("../../../../../res/img/e2e/verified.svg");
|
||||||
} else {
|
} else {
|
||||||
return require("../../../../../res/img/e2e/warning.svg");
|
return require("../../../../../res/img/e2e/warning.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.security.SetupEncryptionDialog")
|
interface IProps {
|
||||||
export default class SetupEncryptionDialog extends React.Component {
|
onFinished: (success: boolean) => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
interface IState {
|
||||||
super();
|
icon: Phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.dialogs.security.SetupEncryptionDialog")
|
||||||
|
export default class SetupEncryptionDialog extends React.Component<IProps, IState> {
|
||||||
|
private store: SetupEncryptionStore;
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.store = SetupEncryptionStore.sharedInstance();
|
this.store = SetupEncryptionStore.sharedInstance();
|
||||||
this.state = {icon: iconFromPhase(this.store.phase)};
|
this.state = {icon: iconFromPhase(this.store.phase)};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
this.store.on("update", this._onStoreUpdate);
|
this.store.on("update", this.onStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.store.removeListener("update", this._onStoreUpdate);
|
this.store.removeListener("update", this.onStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onStoreUpdate = () => {
|
private onStoreUpdate = (): void => {
|
||||||
this.setState({icon: iconFromPhase(this.store.phase)});
|
this.setState({icon: iconFromPhase(this.store.phase)});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
return <BaseDialog
|
return <BaseDialog
|
||||||
headerImage={this.state.icon}
|
headerImage={this.state.icon}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
|
@ -48,7 +48,7 @@ import EncryptionPanel from "./EncryptionPanel";
|
||||||
import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
|
import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
|
||||||
import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification';
|
import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification';
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { USER_SECURITY_TAB } from "../dialogs/UserSettingsDialog";
|
import { UserTab } from "../dialogs/UserSettingsDialog";
|
||||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||||
import BaseCard from "./BaseCard";
|
import BaseCard from "./BaseCard";
|
||||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||||
|
@ -1381,7 +1381,7 @@ const BasicUserInfo: React.FC<{
|
||||||
<AccessibleButton className="mx_UserInfo_field" onClick={() => {
|
<AccessibleButton className="mx_UserInfo_field" onClick={() => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: USER_SECURITY_TAB,
|
initialTabId: UserTab.Security,
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
{ _t("Edit devices") }
|
{ _t("Edit devices") }
|
||||||
|
|
|
@ -32,7 +32,7 @@ import * as ContextMenu from "../../../../structures/ContextMenu";
|
||||||
import { toRightOf } from "../../../../structures/ContextMenu";
|
import { toRightOf } from "../../../../structures/ContextMenu";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeSettingsFn: () => {};
|
closeSettingsFn: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import {BetaPill} from "../beta/BetaCard";
|
import {BetaPill} from "../beta/BetaCard";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog";
|
import { UserTab } from "../dialogs/UserSettingsDialog";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import withValidation from "../elements/Validation";
|
import withValidation from "../elements/Validation";
|
||||||
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
||||||
|
@ -224,7 +224,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
onFinished();
|
onFinished();
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: USER_LABS_TAB,
|
initialTabId: UserTab.Labs,
|
||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
{ body }
|
{ body }
|
||||||
|
|
|
@ -15,29 +15,42 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
import { IKeyBackupVersion } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
|
import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
|
||||||
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
|
||||||
export const PHASE_LOADING = 0;
|
export enum Phase {
|
||||||
export const PHASE_INTRO = 1;
|
Loading = 0,
|
||||||
export const PHASE_BUSY = 2;
|
Intro = 1,
|
||||||
export const PHASE_DONE = 3; //final done stage, but still showing UX
|
Busy = 2,
|
||||||
export const PHASE_CONFIRM_SKIP = 4;
|
Done = 3, // final done stage, but still showing UX
|
||||||
export const PHASE_FINISHED = 5; //UX can be closed
|
ConfirmSkip = 4,
|
||||||
|
Finished = 5, // UX can be closed
|
||||||
|
}
|
||||||
|
|
||||||
export class SetupEncryptionStore extends EventEmitter {
|
export class SetupEncryptionStore extends EventEmitter {
|
||||||
static sharedInstance() {
|
private started: boolean;
|
||||||
if (!global.mx_SetupEncryptionStore) global.mx_SetupEncryptionStore = new SetupEncryptionStore();
|
public phase: Phase;
|
||||||
return global.mx_SetupEncryptionStore;
|
public verificationRequest: VerificationRequest;
|
||||||
|
public backupInfo: IKeyBackupVersion;
|
||||||
|
public keyId: string;
|
||||||
|
public keyInfo: ISecretStorageKeyInfo;
|
||||||
|
public hasDevicesToVerifyAgainst: boolean;
|
||||||
|
|
||||||
|
public static sharedInstance() {
|
||||||
|
if (!window.mxSetupEncryptionStore) window.mxSetupEncryptionStore = new SetupEncryptionStore();
|
||||||
|
return window.mxSetupEncryptionStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
public start(): void {
|
||||||
if (this._started) {
|
if (this.started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._started = true;
|
this.started = true;
|
||||||
this.phase = PHASE_LOADING;
|
this.phase = Phase.Loading;
|
||||||
this.verificationRequest = null;
|
this.verificationRequest = null;
|
||||||
this.backupInfo = null;
|
this.backupInfo = null;
|
||||||
|
|
||||||
|
@ -48,34 +61,34 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
cli.on("crypto.verification.request", this.onVerificationRequest);
|
cli.on("crypto.verification.request", this.onVerificationRequest);
|
||||||
cli.on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
cli.on('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||||
|
|
||||||
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId());
|
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId());
|
||||||
if (requestsInProgress.length) {
|
if (requestsInProgress.length) {
|
||||||
// If there are multiple, we take the most recent. Equally if the user sends another request from
|
// If there are multiple, we take the most recent. Equally if the user sends another request from
|
||||||
// another device after this screen has been shown, we'll switch to the new one, so this
|
// another device after this screen has been shown, we'll switch to the new one, so this
|
||||||
// generally doesn't support multiple requests.
|
// generally doesn't support multiple requests.
|
||||||
this._setActiveVerificationRequest(requestsInProgress[requestsInProgress.length - 1]);
|
this.setActiveVerificationRequest(requestsInProgress[requestsInProgress.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchKeyInfo();
|
this.fetchKeyInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
public stop(): void {
|
||||||
if (!this._started) {
|
if (!this.started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._started = false;
|
this.started = false;
|
||||||
if (this.verificationRequest) {
|
if (this.verificationRequest) {
|
||||||
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||||
}
|
}
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
||||||
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchKeyInfo() {
|
public async fetchKeyInfo(): Promise<void> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const keys = await cli.isSecretStored('m.cross_signing.master', false);
|
const keys = await cli.isSecretStored('m.cross_signing.master', false);
|
||||||
if (keys === null || Object.keys(keys).length === 0) {
|
if (keys === null || Object.keys(keys).length === 0) {
|
||||||
|
@ -97,15 +110,15 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
|
|
||||||
if (!this.hasDevicesToVerifyAgainst && !this.keyInfo) {
|
if (!this.hasDevicesToVerifyAgainst && !this.keyInfo) {
|
||||||
// skip before we can even render anything.
|
// skip before we can even render anything.
|
||||||
this.phase = PHASE_FINISHED;
|
this.phase = Phase.Finished;
|
||||||
} else {
|
} else {
|
||||||
this.phase = PHASE_INTRO;
|
this.phase = Phase.Intro;
|
||||||
}
|
}
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
async usePassPhrase() {
|
public async usePassPhrase(): Promise<void> {
|
||||||
this.phase = PHASE_BUSY;
|
this.phase = Phase.Busy;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
try {
|
try {
|
||||||
|
@ -120,7 +133,7 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
// passphase cached for that work. This dialog itself will only wait
|
// passphase cached for that work. This dialog itself will only wait
|
||||||
// on the first trust check, and the key backup restore will happen
|
// on the first trust check, and the key backup restore will happen
|
||||||
// in the background.
|
// in the background.
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve: (value?: unknown) => void, reject: (reason?: any) => void) => {
|
||||||
accessSecretStorage(async () => {
|
accessSecretStorage(async () => {
|
||||||
await cli.checkOwnCrossSigningTrust();
|
await cli.checkOwnCrossSigningTrust();
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -134,7 +147,7 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cli.getCrossSigningId()) {
|
if (cli.getCrossSigningId()) {
|
||||||
this.phase = PHASE_DONE;
|
this.phase = Phase.Done;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -142,25 +155,25 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
// this will throw if the user hits cancel, so ignore
|
// this will throw if the user hits cancel, so ignore
|
||||||
this.phase = PHASE_INTRO;
|
this.phase = Phase.Intro;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserTrustStatusChanged = (userId) => {
|
private onUserTrustStatusChanged = (userId: string) => {
|
||||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
|
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
|
||||||
if (publicKeysTrusted) {
|
if (publicKeysTrusted) {
|
||||||
this.phase = PHASE_DONE;
|
this.phase = Phase.Done;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onVerificationRequest = (request) => {
|
public onVerificationRequest = (request: VerificationRequest): void => {
|
||||||
this._setActiveVerificationRequest(request);
|
this.setActiveVerificationRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
onVerificationRequestChange = () => {
|
public onVerificationRequestChange = (): void => {
|
||||||
if (this.verificationRequest.cancelled) {
|
if (this.verificationRequest.cancelled) {
|
||||||
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||||
this.verificationRequest = null;
|
this.verificationRequest = null;
|
||||||
|
@ -172,34 +185,34 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
// cross signing to be ready to use, so wait for the user trust status to
|
// cross signing to be ready to use, so wait for the user trust status to
|
||||||
// change (or change to DONE if it's already ready).
|
// change (or change to DONE if it's already ready).
|
||||||
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
|
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
|
||||||
this.phase = publicKeysTrusted ? PHASE_DONE : PHASE_BUSY;
|
this.phase = publicKeysTrusted ? Phase.Done : Phase.Busy;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skip() {
|
public skip(): void {
|
||||||
this.phase = PHASE_CONFIRM_SKIP;
|
this.phase = Phase.ConfirmSkip;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
skipConfirm() {
|
public skipConfirm(): void {
|
||||||
this.phase = PHASE_FINISHED;
|
this.phase = Phase.Finished;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
returnAfterSkip() {
|
public returnAfterSkip(): void {
|
||||||
this.phase = PHASE_INTRO;
|
this.phase = Phase.Intro;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
done() {
|
public done(): void {
|
||||||
this.phase = PHASE_FINISHED;
|
this.phase = Phase.Finished;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
// async - ask other clients for keys, if necessary
|
// async - ask other clients for keys, if necessary
|
||||||
MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests();
|
MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _setActiveVerificationRequest(request) {
|
private async setActiveVerificationRequest(request: VerificationRequest): Promise<void> {
|
||||||
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
|
|
||||||
if (this.verificationRequest) {
|
if (this.verificationRequest) {
|
|
@ -21,7 +21,7 @@ import DeviceListener from '../DeviceListener';
|
||||||
import ToastStore from "../stores/ToastStore";
|
import ToastStore from "../stores/ToastStore";
|
||||||
import GenericToast from "../components/views/toasts/GenericToast";
|
import GenericToast from "../components/views/toasts/GenericToast";
|
||||||
import { Action } from "../dispatcher/actions";
|
import { Action } from "../dispatcher/actions";
|
||||||
import { USER_SECURITY_TAB } from "../components/views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../components/views/dialogs/UserSettingsDialog";
|
||||||
|
|
||||||
function toastKey(deviceId: string) {
|
function toastKey(deviceId: string) {
|
||||||
return "unverified_session_" + deviceId;
|
return "unverified_session_" + deviceId;
|
||||||
|
@ -34,7 +34,7 @@ export const showToast = async (deviceId: string) => {
|
||||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: USER_SECURITY_TAB,
|
initialTabId: UserTab.Security,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue