Introduce a concept of "non-urgent" toasts
This is somewhat expected to be temporary.
This commit is contained in:
parent
6a9fe35fa8
commit
14757cacd5
7 changed files with 159 additions and 3 deletions
|
@ -15,6 +15,7 @@
|
||||||
@import "./structures/_MainSplit.scss";
|
@import "./structures/_MainSplit.scss";
|
||||||
@import "./structures/_MatrixChat.scss";
|
@import "./structures/_MatrixChat.scss";
|
||||||
@import "./structures/_MyGroups.scss";
|
@import "./structures/_MyGroups.scss";
|
||||||
|
@import "./structures/_NonUrgentToastContainer.scss";
|
||||||
@import "./structures/_NotificationPanel.scss";
|
@import "./structures/_NotificationPanel.scss";
|
||||||
@import "./structures/_RightPanel.scss";
|
@import "./structures/_RightPanel.scss";
|
||||||
@import "./structures/_RoomDirectory.scss";
|
@import "./structures/_RoomDirectory.scss";
|
||||||
|
|
35
res/css/structures/_NonUrgentToastContainer.scss
Normal file
35
res/css/structures/_NonUrgentToastContainer.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { JSXElementConstructor } from "react";
|
||||||
|
|
||||||
// Based on https://stackoverflow.com/a/53229857/3532235
|
// Based on https://stackoverflow.com/a/53229857/3532235
|
||||||
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
|
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
|
||||||
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
|
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
|
||||||
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||||
|
|
||||||
|
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
|
||||||
|
|
|
@ -54,6 +54,7 @@ import LeftPanel from "./LeftPanel";
|
||||||
import CallContainer from '../views/voip/CallContainer';
|
import CallContainer from '../views/voip/CallContainer';
|
||||||
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
|
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
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)
|
// 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.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
|
@ -687,6 +688,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</div>
|
</div>
|
||||||
<CallContainer />
|
<CallContainer />
|
||||||
|
<NonUrgentToastContainer />
|
||||||
</MatrixClientContext.Provider>
|
</MatrixClientContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
63
src/components/structures/NonUrgentToastContainer.tsx
Normal file
63
src/components/structures/NonUrgentToastContainer.tsx
Normal file
|
@ -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<IProps, IState> {
|
||||||
|
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 (
|
||||||
|
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
|
||||||
|
{React.createElement(t, {})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_NonUrgentToastContainer" role="alert">
|
||||||
|
{toasts}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
src/stores/NonUrgentToastStore.ts
Normal file
50
src/stores/NonUrgentToastStore.ts
Normal file
|
@ -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<ToastReference, ComponentClass>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import React, {JSXElementConstructor} from "react";
|
import React from "react";
|
||||||
|
import { ComponentClass } from "../@types/common";
|
||||||
|
|
||||||
export interface IToast<C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> {
|
export interface IToast<C extends ComponentClass> {
|
||||||
key: string;
|
key: string;
|
||||||
// higher priority number will be shown on top of lower priority
|
// higher priority number will be shown on top of lower priority
|
||||||
priority: number;
|
priority: number;
|
||||||
|
@ -55,7 +56,7 @@ export default class ToastStore extends EventEmitter {
|
||||||
*
|
*
|
||||||
* @param {object} newToast The new toast
|
* @param {object} newToast The new toast
|
||||||
*/
|
*/
|
||||||
addOrReplaceToast<C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>>(newToast: IToast<C>) {
|
addOrReplaceToast<C extends ComponentClass>(newToast: IToast<C>) {
|
||||||
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
|
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
|
||||||
if (oldIndex === -1) {
|
if (oldIndex === -1) {
|
||||||
let newIndex = this.toasts.length;
|
let newIndex = this.toasts.length;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue