matrix-react-sdk
Version:
SDK for matrix.org using React
204 lines (183 loc) • 7.95 kB
text/typescript
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { SortAlgorithm } from "../models";
import { sortRoomsWithAlgorithm } from "../tag-sorting";
import { OrderingAlgorithm } from "./OrderingAlgorithm";
import { RoomUpdateCause, TagID } from "../../models";
import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
type NaturalCategorizedRoomMap = {
defaultRooms: Room[];
mutedRooms: Room[];
};
/**
* Uses the natural tag sorting algorithm order to determine tag ordering. No
* additional behavioural changes are present.
*/
export class NaturalAlgorithm extends OrderingAlgorithm {
private cachedCategorizedOrderedRooms: NaturalCategorizedRoomMap = {
defaultRooms: [],
mutedRooms: [],
};
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
super(tagId, initialSortingAlgorithm);
}
public setRooms(rooms: Room[]): void {
const { defaultRooms, mutedRooms } = this.categorizeRooms(rooms);
this.cachedCategorizedOrderedRooms = {
defaultRooms: sortRoomsWithAlgorithm(defaultRooms, this.tagId, this.sortingAlgorithm),
mutedRooms: sortRoomsWithAlgorithm(mutedRooms, this.tagId, this.sortingAlgorithm),
};
this.buildCachedOrderedRooms();
}
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace =
cause === RoomUpdateCause.Timeline ||
cause === RoomUpdateCause.ReadReceipt ||
cause === RoomUpdateCause.PossibleMuteChange;
const isMuted = this.isMutedToBottom && this.getRoomIsMuted(room);
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
if (isMuted) {
this.cachedCategorizedOrderedRooms.mutedRooms = sortRoomsWithAlgorithm(
[...this.cachedCategorizedOrderedRooms.mutedRooms, room],
this.tagId,
this.sortingAlgorithm,
);
} else {
this.cachedCategorizedOrderedRooms.defaultRooms = sortRoomsWithAlgorithm(
[...this.cachedCategorizedOrderedRooms.defaultRooms, room],
this.tagId,
this.sortingAlgorithm,
);
}
this.buildCachedOrderedRooms();
return true;
} else if (cause === RoomUpdateCause.RoomRemoved) {
return this.removeRoom(room);
} else if (cause === RoomUpdateCause.PossibleMuteChange) {
if (this.isMutedToBottom) {
return this.onPossibleMuteChange(room);
} else {
return false;
}
}
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
if (isMuted) {
this.cachedCategorizedOrderedRooms.mutedRooms = sortRoomsWithAlgorithm(
this.cachedCategorizedOrderedRooms.mutedRooms,
this.tagId,
this.sortingAlgorithm,
);
} else {
this.cachedCategorizedOrderedRooms.defaultRooms = sortRoomsWithAlgorithm(
this.cachedCategorizedOrderedRooms.defaultRooms,
this.tagId,
this.sortingAlgorithm,
);
}
this.buildCachedOrderedRooms();
return true;
}
/**
* Remove a room from the cached room list
* @param room Room to remove
* @returns {boolean} true when room list should update as result
*/
private removeRoom(room: Room): boolean {
const defaultIndex = this.cachedCategorizedOrderedRooms.defaultRooms.findIndex((r) => r.roomId === room.roomId);
if (defaultIndex > -1) {
this.cachedCategorizedOrderedRooms.defaultRooms.splice(defaultIndex, 1);
this.buildCachedOrderedRooms();
return true;
}
const mutedIndex = this.cachedCategorizedOrderedRooms.mutedRooms.findIndex((r) => r.roomId === room.roomId);
if (mutedIndex > -1) {
this.cachedCategorizedOrderedRooms.mutedRooms.splice(mutedIndex, 1);
this.buildCachedOrderedRooms();
return true;
}
logger.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
// room was not in cached lists, no update
return false;
}
/**
* Sets cachedOrderedRooms from cachedCategorizedOrderedRooms
*/
private buildCachedOrderedRooms(): void {
this.cachedOrderedRooms = [
...this.cachedCategorizedOrderedRooms.defaultRooms,
...this.cachedCategorizedOrderedRooms.mutedRooms,
];
}
private getRoomIsMuted(room: Room): boolean {
// It's fine for us to call this a lot because it's cached, and we shouldn't be
// wasting anything by doing so as the store holds single references
const state = RoomNotificationStateStore.instance.getRoomState(room);
return state.muted;
}
private categorizeRooms(rooms: Room[]): NaturalCategorizedRoomMap {
if (!this.isMutedToBottom) {
return { defaultRooms: rooms, mutedRooms: [] };
}
return rooms.reduce<NaturalCategorizedRoomMap>(
(acc, room: Room) => {
if (this.getRoomIsMuted(room)) {
acc.mutedRooms.push(room);
} else {
acc.defaultRooms.push(room);
}
return acc;
},
{ defaultRooms: [], mutedRooms: [] } as NaturalCategorizedRoomMap,
);
}
private onPossibleMuteChange(room: Room): boolean {
const isMuted = this.getRoomIsMuted(room);
if (isMuted) {
const defaultIndex = this.cachedCategorizedOrderedRooms.defaultRooms.findIndex(
(r) => r.roomId === room.roomId,
);
// room has been muted
if (defaultIndex > -1) {
// remove from the default list
this.cachedCategorizedOrderedRooms.defaultRooms.splice(defaultIndex, 1);
// add to muted list and reorder
this.cachedCategorizedOrderedRooms.mutedRooms = sortRoomsWithAlgorithm(
[...this.cachedCategorizedOrderedRooms.mutedRooms, room],
this.tagId,
this.sortingAlgorithm,
);
// rebuild
this.buildCachedOrderedRooms();
return true;
}
} else {
const mutedIndex = this.cachedCategorizedOrderedRooms.mutedRooms.findIndex((r) => r.roomId === room.roomId);
// room has been unmuted
if (mutedIndex > -1) {
// remove from the muted list
this.cachedCategorizedOrderedRooms.mutedRooms.splice(mutedIndex, 1);
// add to default list and reorder
this.cachedCategorizedOrderedRooms.defaultRooms = sortRoomsWithAlgorithm(
[...this.cachedCategorizedOrderedRooms.defaultRooms, room],
this.tagId,
this.sortingAlgorithm,
);
// rebuild
this.buildCachedOrderedRooms();
return true;
}
}
return false;
}
}