mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 11:47:23 +03:00
Threads notifications after app startup (#7253)
This commit is contained in:
parent
b4b81a455e
commit
38e5e94ee4
10 changed files with 194 additions and 28 deletions
|
@ -72,6 +72,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.setupThread(this.props.mxEvent);
|
this.setupThread(this.props.mxEvent);
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
@ -166,10 +167,11 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateThread = (thread?: Thread) => {
|
private updateThread = (thread?: Thread) => {
|
||||||
if (thread) {
|
if (thread && this.state.thread !== thread) {
|
||||||
this.setState({
|
this.setState({
|
||||||
thread,
|
thread,
|
||||||
});
|
});
|
||||||
|
thread.emit(ThreadEvent.ViewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timelinePanelRef.current?.refreshTimeline();
|
this.timelinePanelRef.current?.refreshTimeline();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { formatCount } from "../../../utils/FormattingUtils";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { XOR } from "../../../@types/common";
|
import { XOR } from "../../../@types/common";
|
||||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState";
|
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import Tooltip from "../elements/Tooltip";
|
import Tooltip from "../elements/Tooltip";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
@ -60,7 +60,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.props.notification.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
this.props.notification.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
|
showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
|
||||||
|
@ -80,15 +80,15 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
SettingsStore.unwatchSetting(this.countWatcherRef);
|
SettingsStore.unwatchSetting(this.countWatcherRef);
|
||||||
this.props.notification.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
this.props.notification.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||||
if (prevProps.notification) {
|
if (prevProps.notification) {
|
||||||
prevProps.notification.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
prevProps.notification.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.notification.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
this.props.notification.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private countPreferenceChanged = () => {
|
private countPreferenceChanged = () => {
|
||||||
|
|
|
@ -38,8 +38,8 @@ import { ContextMenuTooltipButton } from '../../structures/ContextMenu';
|
||||||
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
||||||
import { contextMenuBelow } from './RoomTile';
|
import { contextMenuBelow } from './RoomTile';
|
||||||
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
||||||
import { NOTIFICATION_STATE_UPDATE } from '../../../stores/notifications/NotificationState';
|
|
||||||
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
|
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
|
||||||
|
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
||||||
|
|
||||||
export interface ISearchInfo {
|
export interface ISearchInfo {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
|
@ -76,7 +76,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
||||||
notiStore.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
cli.removeListener("RoomState.events", this.onRoomStateEvents);
|
cli.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
}
|
}
|
||||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
||||||
notiStore.removeListener(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
notiStore.removeListener(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomStateEvents = (event: MatrixEvent, state: RoomState) => {
|
private onRoomStateEvents = (event: MatrixEvent, state: RoomState) => {
|
||||||
|
|
|
@ -37,7 +37,7 @@ import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import RoomListActions from "../../../actions/RoomListActions";
|
import RoomListActions from "../../../actions/RoomListActions";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState";
|
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
|
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
|
||||||
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
|
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
|
||||||
|
@ -164,7 +164,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||||
this.onRoomPreviewChanged,
|
this.onRoomPreviewChanged,
|
||||||
);
|
);
|
||||||
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||||
this.props.room?.on("Room.name", this.onRoomNameUpdate);
|
this.props.room?.on("Room.name", this.onRoomNameUpdate);
|
||||||
CommunityPrototypeStore.instance.on(
|
CommunityPrototypeStore.instance.on(
|
||||||
|
@ -188,7 +188,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||||
CommunityPrototypeStore.instance.off(
|
CommunityPrototypeStore.instance.off(
|
||||||
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { TagID } from "../room-list/models";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { arrayDiff } from "../../utils/arrays";
|
import { arrayDiff } from "../../utils/arrays";
|
||||||
import { RoomNotificationState } from "./RoomNotificationState";
|
import { RoomNotificationState } from "./RoomNotificationState";
|
||||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationState";
|
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||||
|
|
||||||
export type FetchRoomFn = (room: Room) => RoomNotificationState;
|
export type FetchRoomFn = (room: Room) => RoomNotificationState;
|
||||||
|
|
||||||
|
@ -50,11 +50,11 @@ export class ListNotificationState extends NotificationState {
|
||||||
const state = this.states[oldRoom.roomId];
|
const state = this.states[oldRoom.roomId];
|
||||||
if (!state) continue; // We likely just didn't have a badge (race condition)
|
if (!state) continue; // We likely just didn't have a badge (race condition)
|
||||||
delete this.states[oldRoom.roomId];
|
delete this.states[oldRoom.roomId];
|
||||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
}
|
}
|
||||||
for (const newRoom of diff.added) {
|
for (const newRoom of diff.added) {
|
||||||
const state = this.getRoomFn(newRoom);
|
const state = this.getRoomFn(newRoom);
|
||||||
state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
this.states[newRoom.roomId] = state;
|
this.states[newRoom.roomId] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export class ListNotificationState extends NotificationState {
|
||||||
public destroy() {
|
public destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
for (const state of Object.values(this.states)) {
|
for (const state of Object.values(this.states)) {
|
||||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
}
|
}
|
||||||
this.states = {};
|
this.states = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { NotificationColor } from "./NotificationColor";
|
import { NotificationColor } from "./NotificationColor";
|
||||||
import { IDestroyable } from "../../utils/IDestroyable";
|
import { IDestroyable } from "../../utils/IDestroyable";
|
||||||
|
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||||
|
|
||||||
export const NOTIFICATION_STATE_UPDATE = "update";
|
export interface INotificationStateSnapshotParams {
|
||||||
|
symbol: string | null;
|
||||||
|
count: number;
|
||||||
|
color: NotificationColor;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class NotificationState extends EventEmitter implements IDestroyable {
|
export enum NotificationStateEvents {
|
||||||
protected _symbol: string;
|
Update = "update",
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class NotificationState extends TypedEventEmitter<NotificationStateEvents>
|
||||||
|
implements INotificationStateSnapshotParams, IDestroyable {
|
||||||
|
protected _symbol: string | null;
|
||||||
protected _count: number;
|
protected _count: number;
|
||||||
protected _color: NotificationColor;
|
protected _color: NotificationColor;
|
||||||
|
|
||||||
|
@ -55,7 +64,7 @@ export abstract class NotificationState extends EventEmitter implements IDestroy
|
||||||
|
|
||||||
protected emitIfUpdated(snapshot: NotificationStateSnapshot) {
|
protected emitIfUpdated(snapshot: NotificationStateSnapshot) {
|
||||||
if (snapshot.isDifferentFrom(this)) {
|
if (snapshot.isDifferentFrom(this)) {
|
||||||
this.emit(NOTIFICATION_STATE_UPDATE);
|
this.emit(NotificationStateEvents.Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +73,7 @@ export abstract class NotificationState extends EventEmitter implements IDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this.removeAllListeners(NOTIFICATION_STATE_UPDATE);
|
this.removeAllListeners(NotificationStateEvents.Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +82,13 @@ export class NotificationStateSnapshot {
|
||||||
private readonly count: number;
|
private readonly count: number;
|
||||||
private readonly color: NotificationColor;
|
private readonly color: NotificationColor;
|
||||||
|
|
||||||
constructor(state: NotificationState) {
|
constructor(state: INotificationStateSnapshotParams) {
|
||||||
this.symbol = state.symbol;
|
this.symbol = state.symbol;
|
||||||
this.count = state.count;
|
this.count = state.count;
|
||||||
this.color = state.color;
|
this.color = state.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isDifferentFrom(other: NotificationState): boolean {
|
public isDifferentFrom(other: INotificationStateSnapshotParams): boolean {
|
||||||
const before = { count: this.count, symbol: this.symbol, color: this.color };
|
const before = { count: this.count, symbol: this.symbol, color: this.color };
|
||||||
const after = { count: other.count, symbol: other.symbol, color: other.color };
|
const after = { count: other.count, symbol: other.symbol, color: other.color };
|
||||||
return JSON.stringify(before) !== JSON.stringify(after);
|
return JSON.stringify(before) !== JSON.stringify(after);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { RoomNotificationState } from "./RoomNotificationState";
|
import { RoomNotificationState } from "./RoomNotificationState";
|
||||||
import { SummarizedNotificationState } from "./SummarizedNotificationState";
|
import { SummarizedNotificationState } from "./SummarizedNotificationState";
|
||||||
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
|
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
|
||||||
|
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
|
||||||
|
|
||||||
interface IState {}
|
interface IState {}
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
||||||
private static internalInstance = new RoomNotificationStateStore();
|
private static internalInstance = new RoomNotificationStateStore();
|
||||||
|
|
||||||
private roomMap = new Map<Room, RoomNotificationState>();
|
private roomMap = new Map<Room, RoomNotificationState>();
|
||||||
|
private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>();
|
||||||
private listMap = new Map<TagID, ListNotificationState>();
|
private listMap = new Map<TagID, ListNotificationState>();
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
|
@ -85,10 +87,22 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
||||||
public getRoomState(room: Room): RoomNotificationState {
|
public getRoomState(room: Room): RoomNotificationState {
|
||||||
if (!this.roomMap.has(room)) {
|
if (!this.roomMap.has(room)) {
|
||||||
this.roomMap.set(room, new RoomNotificationState(room));
|
this.roomMap.set(room, new RoomNotificationState(room));
|
||||||
|
// Not very elegant, but that way we ensure that we start tracking
|
||||||
|
// threads notification at the same time at rooms.
|
||||||
|
// There are multiple entry points, and it's unclear which one gets
|
||||||
|
// called first
|
||||||
|
this.roomThreadsMap.set(room, new ThreadsRoomNotificationState(room));
|
||||||
}
|
}
|
||||||
return this.roomMap.get(room);
|
return this.roomMap.get(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getThreadsRoomState(room: Room): ThreadsRoomNotificationState {
|
||||||
|
if (!this.roomThreadsMap.has(room)) {
|
||||||
|
this.roomThreadsMap.set(room, new ThreadsRoomNotificationState(room));
|
||||||
|
}
|
||||||
|
return this.roomThreadsMap.get(room);
|
||||||
|
}
|
||||||
|
|
||||||
public static get instance(): RoomNotificationStateStore {
|
public static get instance(): RoomNotificationStateStore {
|
||||||
return RoomNotificationStateStore.internalInstance;
|
return RoomNotificationStateStore.internalInstance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { NotificationColor } from "./NotificationColor";
|
import { NotificationColor } from "./NotificationColor";
|
||||||
import { arrayDiff } from "../../utils/arrays";
|
import { arrayDiff } from "../../utils/arrays";
|
||||||
import { RoomNotificationState } from "./RoomNotificationState";
|
import { RoomNotificationState } from "./RoomNotificationState";
|
||||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationState";
|
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||||
import { FetchRoomFn } from "./ListNotificationState";
|
import { FetchRoomFn } from "./ListNotificationState";
|
||||||
|
|
||||||
export class SpaceNotificationState extends NotificationState {
|
export class SpaceNotificationState extends NotificationState {
|
||||||
|
@ -42,11 +42,11 @@ export class SpaceNotificationState extends NotificationState {
|
||||||
const state = this.states[oldRoom.roomId];
|
const state = this.states[oldRoom.roomId];
|
||||||
if (!state) continue; // We likely just didn't have a badge (race condition)
|
if (!state) continue; // We likely just didn't have a badge (race condition)
|
||||||
delete this.states[oldRoom.roomId];
|
delete this.states[oldRoom.roomId];
|
||||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
}
|
}
|
||||||
for (const newRoom of diff.added) {
|
for (const newRoom of diff.added) {
|
||||||
const state = this.getRoomFn(newRoom);
|
const state = this.getRoomFn(newRoom);
|
||||||
state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
this.states[newRoom.roomId] = state;
|
this.states[newRoom.roomId] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ export class SpaceNotificationState extends NotificationState {
|
||||||
public destroy() {
|
public destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
for (const state of Object.values(this.states)) {
|
for (const state of Object.values(this.states)) {
|
||||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
}
|
}
|
||||||
this.states = {};
|
this.states = {};
|
||||||
}
|
}
|
||||||
|
|
69
src/stores/notifications/ThreadNotificationState.ts
Normal file
69
src/stores/notifications/ThreadNotificationState.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 { NotificationColor } from "./NotificationColor";
|
||||||
|
import { IDestroyable } from "../../utils/IDestroyable";
|
||||||
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { NotificationState } from "./NotificationState";
|
||||||
|
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
export class ThreadNotificationState extends NotificationState implements IDestroyable {
|
||||||
|
protected _symbol = null;
|
||||||
|
protected _count = 0;
|
||||||
|
protected _color = NotificationColor.None;
|
||||||
|
|
||||||
|
constructor(public readonly room: Room, public readonly thread: Thread) {
|
||||||
|
super();
|
||||||
|
this.thread.on(ThreadEvent.NewReply, this.handleNewThreadReply);
|
||||||
|
this.thread.on(ThreadEvent.ViewThread, this.resetThreadNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
super.destroy();
|
||||||
|
this.thread.off(ThreadEvent.NewReply, this.handleNewThreadReply);
|
||||||
|
this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNewThreadReply(thread: Thread, event: MatrixEvent) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
const isOwn = client.getUserId() === event.getSender();
|
||||||
|
if (!isOwn) {
|
||||||
|
const actions = client.getPushActionsForEvent(event, true);
|
||||||
|
|
||||||
|
const color = !!actions.tweaks.highlight
|
||||||
|
? NotificationColor.Red
|
||||||
|
: NotificationColor.Grey;
|
||||||
|
|
||||||
|
this.updateNotificationState(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetThreadNotification = (): void => {
|
||||||
|
this.updateNotificationState(NotificationColor.None);
|
||||||
|
};
|
||||||
|
|
||||||
|
private updateNotificationState(color: NotificationColor) {
|
||||||
|
const snapshot = this.snapshot();
|
||||||
|
|
||||||
|
this._color = color;
|
||||||
|
|
||||||
|
// finally, publish an update if needed
|
||||||
|
this.emitIfUpdated(snapshot);
|
||||||
|
}
|
||||||
|
}
|
72
src/stores/notifications/ThreadsRoomNotificationState.ts
Normal file
72
src/stores/notifications/ThreadsRoomNotificationState.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 { IDestroyable } from "../../utils/IDestroyable";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||||
|
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
|
||||||
|
import { ThreadNotificationState } from "./ThreadNotificationState";
|
||||||
|
import { NotificationColor } from "./NotificationColor";
|
||||||
|
|
||||||
|
export class ThreadsRoomNotificationState extends NotificationState implements IDestroyable {
|
||||||
|
private threadsState = new Map<Thread, ThreadNotificationState>();
|
||||||
|
|
||||||
|
protected _symbol = null;
|
||||||
|
protected _count = 0;
|
||||||
|
protected _color = NotificationColor.None;
|
||||||
|
|
||||||
|
constructor(public readonly room: Room) {
|
||||||
|
super();
|
||||||
|
this.room.on(ThreadEvent.New, this.onNewThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
super.destroy();
|
||||||
|
this.room.on(ThreadEvent.New, this.onNewThread);
|
||||||
|
for (const [, notificationState] of this.threadsState) {
|
||||||
|
notificationState.off(NotificationStateEvents.Update, this.onThreadUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onNewThread = (thread: Thread): void => {
|
||||||
|
const notificationState = new ThreadNotificationState(this.room, thread);
|
||||||
|
this.threadsState.set(
|
||||||
|
thread,
|
||||||
|
notificationState,
|
||||||
|
);
|
||||||
|
notificationState.on(NotificationStateEvents.Update, this.onThreadUpdate);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onThreadUpdate = (): void => {
|
||||||
|
let color = NotificationColor.None;
|
||||||
|
for (const [, notificationState] of this.threadsState) {
|
||||||
|
if (notificationState.color === NotificationColor.Red) {
|
||||||
|
color = NotificationColor.Red;
|
||||||
|
break;
|
||||||
|
} else if (notificationState.color === NotificationColor.Grey) {
|
||||||
|
color = NotificationColor.Grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateNotificationState(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
private updateNotificationState(color: NotificationColor): void {
|
||||||
|
const snapshot = this.snapshot();
|
||||||
|
this._color = color;
|
||||||
|
// finally, publish an update if needed
|
||||||
|
this.emitIfUpdated(snapshot);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue