Merge pull request #4618 from matrix-org/t3chguy/toasts3
Migrate Toasts to Typescript and to granular priority system
This commit is contained in:
commit
b7428fcf35
17 changed files with 518 additions and 441 deletions
|
@ -1570,7 +1570,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
icon: "verification",
|
||||
props: {request},
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
priority: ToastStore.PRIORITY_REALTIME,
|
||||
priority: 90,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,13 +15,21 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||
import classNames from "classnames";
|
||||
|
||||
export default class ToastContainer extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {toasts: ToastStore.sharedInstance().getToasts()};
|
||||
interface IState {
|
||||
toasts: IToast<any>[];
|
||||
countSeen: number;
|
||||
}
|
||||
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||
};
|
||||
|
||||
// Start listening here rather than in componentDidMount because
|
||||
// toasts may dismiss themselves in their didMount if they find
|
||||
|
@ -35,7 +43,10 @@ export default class ToastContainer extends React.Component {
|
|||
}
|
||||
|
||||
_onToastStoreUpdate = () => {
|
||||
this.setState({toasts: ToastStore.sharedInstance().getToasts()});
|
||||
this.setState({
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -51,8 +62,8 @@ export default class ToastContainer extends React.Component {
|
|||
});
|
||||
|
||||
let countIndicator;
|
||||
if (isStacked) {
|
||||
countIndicator = `(1/${totalCount})`;
|
||||
if (isStacked || this.state.countSeen > 0) {
|
||||
countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
|
||||
}
|
||||
|
||||
const toastProps = Object.assign({}, props, {
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.BulkUnverifiedSessionsToast")
|
||||
export default class BulkUnverifiedSessionsToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceIds: PropTypes.array,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_user_info',
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
{_t("Verify all your sessions to ensure your account & messages are safe")}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Review")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
42
src/components/views/toasts/GenericToast.tsx
Normal file
42
src/components/views/toasts/GenericToast.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {ReactChild} from "react";
|
||||
|
||||
import FormButton from "../elements/FormButton";
|
||||
|
||||
interface IProps {
|
||||
description: ReactChild;
|
||||
acceptLabel: string;
|
||||
rejectLabel?: string;
|
||||
|
||||
onAccept();
|
||||
onReject?();
|
||||
}
|
||||
|
||||
const GenericToast: React.FC<IProps> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
|
||||
return <div>
|
||||
<div className="mx_Toast_description">
|
||||
{ description }
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
{onReject && rejectLabel && <FormButton label={rejectLabel} kind="danger" onClick={onReject} /> }
|
||||
<FormButton label={acceptLabel} onClick={onAccept} />
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default GenericToast;
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from '../../../Modal';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import SetupEncryptionDialog from "../dialogs/SetupEncryptionDialog";
|
||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
export default class SetupEncryptionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf([
|
||||
'set_up_encryption',
|
||||
'verify_this_session',
|
||||
'upgrade_encryption',
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissEncryptionSetup();
|
||||
};
|
||||
|
||||
_onSetupClick = async () => {
|
||||
if (this.props.kind === "verify_this_session") {
|
||||
Modal.createTrackedDialog('Verify session', 'Verify session', SetupEncryptionDialog,
|
||||
{}, null, /* priority = */ false, /* static = */ true);
|
||||
} else {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(
|
||||
Spinner, null, 'mx_Dialog_spinner', /* priority */ false, /* static */ true,
|
||||
);
|
||||
try {
|
||||
await accessSecretStorage();
|
||||
} finally {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getDescription() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
case 'upgrade_encryption':
|
||||
return _t('Verify yourself & others to keep your chats safe');
|
||||
case 'verify_this_session':
|
||||
return _t('Other users may not trust it');
|
||||
}
|
||||
}
|
||||
|
||||
getSetupCaption() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
return _t('Set up');
|
||||
case 'upgrade_encryption':
|
||||
return _t('Upgrade');
|
||||
case 'verify_this_session':
|
||||
return _t('Verify');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{this.getDescription()}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={this.getSetupCaption()} onClick={this._onSetupClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.UnverifiedSessionToast")
|
||||
export default class UnverifiedSessionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceId: PropTypes.string,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
||||
userId: cli.getUserId(),
|
||||
device: cli.getStoredDevice(cli.getUserId(), this.props.deviceId),
|
||||
onFinished: (r) => {
|
||||
if (!r) {
|
||||
/* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
}
|
||||
},
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const device = cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
|
||||
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
{_t(
|
||||
"Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Verify")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from "react";
|
||||
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
|
@ -24,8 +24,23 @@ import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import ToastStore from "../../../stores/ToastStore";
|
||||
import Modal from "../../../Modal";
|
||||
import GenericToast from "./GenericToast";
|
||||
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
|
||||
interface IProps {
|
||||
toastKey: string;
|
||||
request: VerificationRequest;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
counter: number;
|
||||
device?: DeviceInfo;
|
||||
}
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent<IProps, IState> {
|
||||
private intervalHandle: NodeJS.Timeout;
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {counter: Math.ceil(props.request.timeout / 1000)};
|
||||
|
@ -34,7 +49,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
async componentDidMount() {
|
||||
const {request} = this.props;
|
||||
if (request.timeout && request.timeout > 0) {
|
||||
this._intervalHandle = setInterval(() => {
|
||||
this.intervalHandle = setInterval(() => {
|
||||
let {counter} = this.state;
|
||||
counter = Math.max(0, counter - 1);
|
||||
this.setState({counter});
|
||||
|
@ -56,7 +71,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this._intervalHandle);
|
||||
clearInterval(this.intervalHandle);
|
||||
const {request} = this.props;
|
||||
request.off("change", this._checkRequestIsPending);
|
||||
}
|
||||
|
@ -110,7 +125,6 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
const {request} = this.props;
|
||||
let nameLabel;
|
||||
if (request.isSelfVerification) {
|
||||
|
@ -133,20 +147,16 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
const declineLabel = this.state.counter == 0 ?
|
||||
const declineLabel = this.state.counter === 0 ?
|
||||
_t("Decline") :
|
||||
_t("Decline (%(counter)s)", {counter: this.state.counter});
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{nameLabel}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={declineLabel} kind="danger" onClick={this.cancel} />
|
||||
<FormButton label={_t("Accept")} onClick={this.accept} />
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
return <GenericToast
|
||||
description={nameLabel}
|
||||
acceptLabel={_t("Accept")}
|
||||
onAccept={this.accept}
|
||||
rejectLabel={declineLabel}
|
||||
onReject={this.cancel}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
VerificationRequestToast.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue