From 83b15054015c06b7201b11c22fda4592c34b24e9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Jan 2020 20:23:47 +0000 Subject: [PATCH] Add a ToastStore To store toast. Rather than them being stored in the state of the ToastContainer component, they now have a dedicated store. This mostly fixes problems involving showing toasts when the app loaded because we would otherwise have a race condition where something tries to show a toast before the ToastContainer is mounted. --- src/components/structures/MatrixChat.js | 16 +++--- src/components/structures/ToastContainer.js | 25 +++------ .../views/toasts/VerificationRequestToast.js | 9 ++-- src/stores/ToastStore.js | 52 +++++++++++++++++++ 4 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 src/stores/ToastStore.js diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index c59c44ebd8..978743ca87 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -64,6 +64,7 @@ import { ThemeWatcher } from "../../theme"; import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer } from "../../utils/promise"; import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver'; +import ToastStore from "../../stores/ToastStore"; /** constants for MatrixChat.state.view */ export const VIEWS = { @@ -1458,15 +1459,12 @@ export default createReactClass({ } if (!requestObserver || requestObserver.pending) { - dis.dispatch({ - action: "show_toast", - toast: { - key: request.event.getId(), - title: _t("Verification Request"), - icon: "verification", - props: {request, requestObserver}, - component: sdk.getComponent("toasts.VerificationRequestToast"), - }, + ToastStore.sharedInstance().addOrReplaceToast({ + key: 'verifreq_' + request.event.getId(), + title: _t("Verification Request"), + icon: "verification", + props: {request, requestObserver}, + component: sdk.getComponent("toasts.VerificationRequestToast"), }); } }); diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index a8dca35747..bc74133433 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,8 +15,8 @@ limitations under the License. */ import * as React from "react"; -import dis from "../../dispatcher"; import { _t } from '../../languageHandler'; +import ToastStore from "../../stores/ToastStore"; import classNames from "classnames"; export default class ToastContainer extends React.Component { @@ -26,26 +26,15 @@ export default class ToastContainer extends React.Component { } componentDidMount() { - this._dispatcherRef = dis.register(this.onAction); + ToastStore.sharedInstance().on('update', this._onToastStoreUpdate); } componentWillUnmount() { - dis.unregister(this._dispatcherRef); + ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate); } - onAction = (payload) => { - if (payload.action === "show_toast") { - this._addToast(payload.toast); - } - }; - - _addToast(toast) { - this.setState({toasts: this.state.toasts.concat(toast)}); - } - - dismissTopToast = () => { - const [, ...remaining] = this.state.toasts; - this.setState({toasts: remaining}); + _onToastStoreUpdate = () => { + this.setState({toasts: ToastStore.sharedInstance().getToasts()}); }; render() { @@ -62,8 +51,8 @@ export default class ToastContainer extends React.Component { const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null; const toastProps = Object.assign({}, props, { - dismiss: this.dismissTopToast, key, + toastKey: key, }); toast = (

{title}{countIndicator}

diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 6d53c23743..18db5eae66 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -23,6 +23,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import dis from "../../../dispatcher"; +import ToastStore from "../../../stores/ToastStore"; export default class VerificationRequestToast extends React.PureComponent { constructor(props) { @@ -63,12 +64,12 @@ export default class VerificationRequestToast extends React.PureComponent { _checkRequestIsPending = () => { if (!this.props.requestObserver.pending) { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); } } cancel = () => { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); try { this.props.request.cancel(); } catch (err) { @@ -77,7 +78,7 @@ export default class VerificationRequestToast extends React.PureComponent { } accept = () => { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); const {event} = this.props.request; // no room id for to_device requests if (event.getRoomId()) { @@ -119,7 +120,7 @@ export default class VerificationRequestToast extends React.PureComponent { } VerificationRequestToast.propTypes = { - dismiss: PropTypes.func.isRequired, request: PropTypes.object.isRequired, requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver), + toastKey: PropTypes.string.isRequired, }; diff --git a/src/stores/ToastStore.js b/src/stores/ToastStore.js new file mode 100644 index 0000000000..f6cc30db67 --- /dev/null +++ b/src/stores/ToastStore.js @@ -0,0 +1,52 @@ +/* +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 EventEmitter from 'events'; + +/** + * Holds the active toasts + */ +export default class ToastStore extends EventEmitter { + static sharedInstance() { + if (!global.mx_ToastStore) global.mx_ToastStore = new ToastStore(); + return global.mx_ToastStore; + } + + constructor() { + super(); + this._dispatcherRef = null; + this._toasts = []; + } + + addOrReplaceToast(newToast) { + const oldIndex = this._toasts.findIndex(t => t.key === newToast.key); + if (oldIndex === -1) { + this._toasts.push(newToast); + } else { + this._toasts[oldIndex] = newToast; + } + this.emit('update'); + } + + dismissToast(key) { + this._toasts = this._toasts.filter(t => t.key !== key); + this.emit('update'); + } + + getToasts() { + return this._toasts; + } +}