From 14757cacd54258c4a648bb9c8e4d86103b605f56 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 29 Jul 2020 12:43:35 -0600 Subject: [PATCH] Introduce a concept of "non-urgent" toasts This is somewhat expected to be temporary. --- res/css/_components.scss | 1 + .../structures/_NonUrgentToastContainer.scss | 35 +++++++++++ src/@types/common.ts | 4 ++ src/components/structures/LoggedInView.tsx | 2 + .../structures/NonUrgentToastContainer.tsx | 63 +++++++++++++++++++ src/stores/NonUrgentToastStore.ts | 50 +++++++++++++++ src/stores/ToastStore.ts | 7 ++- 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 res/css/structures/_NonUrgentToastContainer.scss create mode 100644 src/components/structures/NonUrgentToastContainer.tsx create mode 100644 src/stores/NonUrgentToastStore.ts diff --git a/res/css/_components.scss b/res/css/_components.scss index 23e4af780a..28e1332d08 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -15,6 +15,7 @@ @import "./structures/_MainSplit.scss"; @import "./structures/_MatrixChat.scss"; @import "./structures/_MyGroups.scss"; +@import "./structures/_NonUrgentToastContainer.scss"; @import "./structures/_NotificationPanel.scss"; @import "./structures/_RightPanel.scss"; @import "./structures/_RoomDirectory.scss"; diff --git a/res/css/structures/_NonUrgentToastContainer.scss b/res/css/structures/_NonUrgentToastContainer.scss new file mode 100644 index 0000000000..26715c6048 --- /dev/null +++ b/res/css/structures/_NonUrgentToastContainer.scss @@ -0,0 +1,35 @@ +/* +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. +*/ + +.mx_NonUrgentToastContainer { + position: absolute; + bottom: 30px; + left: 28px; + z-index: 101; // same level as other toasts + + .mx_NonUrgentToastContainer_toast { + padding: 10px 12px; + border-radius: 8px; + width: 320px; + font-size: $font-13px; + margin-top: 8px; + + // We don't use variables on the colours because we want it to be the same + // in all themes. + background-color: #17191C; + color: #fff; + } +} diff --git a/src/@types/common.ts b/src/@types/common.ts index a24d47ac9e..b887bd4090 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { JSXElementConstructor } from "react"; + // Based on https://stackoverflow.com/a/53229857/3532235 export type Without = {[P in Exclude] ? : never}; export type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; export type Writeable = { -readonly [P in keyof T]: T[P] }; + +export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 1f561e68ef..431b7307d8 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -54,6 +54,7 @@ import LeftPanel from "./LeftPanel"; import CallContainer from '../views/voip/CallContainer'; import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; import RoomListStore from "../../stores/room-list/RoomListStore"; +import NonUrgentToastContainer from "./NonUrgentToastContainer"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -687,6 +688,7 @@ class LoggedInView extends React.Component { + ); } diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx new file mode 100644 index 0000000000..8d415df4dd --- /dev/null +++ b/src/components/structures/NonUrgentToastContainer.tsx @@ -0,0 +1,63 @@ +/* +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 * as React from "react"; +import { ComponentClass } from "../../@types/common"; +import NonUrgentToastStore from "../../stores/NonUrgentToastStore"; +import { UPDATE_EVENT } from "../../stores/AsyncStore"; + +interface IProps { +} + +interface IState { + toasts: ComponentClass[], +} + +export default class NonUrgentToastContainer extends React.PureComponent { + public constructor(props, context) { + super(props, context); + + this.state = { + toasts: NonUrgentToastStore.instance.components, + }; + + NonUrgentToastStore.instance.on(UPDATE_EVENT, this.onUpdateToasts); + } + + public componentWillUnmount() { + NonUrgentToastStore.instance.off(UPDATE_EVENT, this.onUpdateToasts); + } + + private onUpdateToasts = () => { + this.setState({toasts: NonUrgentToastStore.instance.components}); + }; + + public render() { + const toasts = this.state.toasts.map((t, i) => { + return ( +
+ {React.createElement(t, {})} +
+ ); + }); + + return ( +
+ {toasts} +
+ ); + } +} diff --git a/src/stores/NonUrgentToastStore.ts b/src/stores/NonUrgentToastStore.ts new file mode 100644 index 0000000000..72f896749c --- /dev/null +++ b/src/stores/NonUrgentToastStore.ts @@ -0,0 +1,50 @@ +/* +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"; +import { ComponentClass } from "../@types/common"; +import { UPDATE_EVENT } from "./AsyncStore"; + +export type ToastReference = symbol; + +export default class NonUrgentToastStore extends EventEmitter { + private static _instance: NonUrgentToastStore; + + private toasts = new Map(); + + public static get instance(): NonUrgentToastStore { + if (!NonUrgentToastStore._instance) { + NonUrgentToastStore._instance = new NonUrgentToastStore(); + } + return NonUrgentToastStore._instance; + } + + public get components(): ComponentClass[] { + return Array.from(this.toasts.values()); + } + + public addToast(c: ComponentClass): ToastReference { + const ref: ToastReference = Symbol(); + this.toasts.set(ref, c); + this.emit(UPDATE_EVENT); + return ref; + } + + public removeToast(ref: ToastReference) { + this.toasts.delete(ref); + this.emit(UPDATE_EVENT); + } +} diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index afb9fe1f8c..038aebc7c9 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -15,9 +15,10 @@ limitations under the License. */ import EventEmitter from "events"; -import React, {JSXElementConstructor} from "react"; +import React from "react"; +import { ComponentClass } from "../@types/common"; -export interface IToast> { +export interface IToast { key: string; // higher priority number will be shown on top of lower priority priority: number; @@ -55,7 +56,7 @@ export default class ToastStore extends EventEmitter { * * @param {object} newToast The new toast */ - addOrReplaceToast>(newToast: IToast) { + addOrReplaceToast(newToast: IToast) { const oldIndex = this.toasts.findIndex(t => t.key === newToast.key); if (oldIndex === -1) { let newIndex = this.toasts.length;