Room List Store: Filter rooms by active space (#29399)

* Add method to await space store setup

Otherwise, the room list store will get incorrect information about
spaces and thus will produce an incorrect roomlist.

* Implement a way to filter by active space

Implement a way to filter by active space

* Fix broken jest tests

* Fix typo

* Rename `isReady` to `storeReadyPromise`

* Fix mock in test
This commit is contained in:
R Midhun Suresh
2025-03-04 16:57:41 +05:30
committed by GitHub
parent ffa8971195
commit 21e9d93e69
6 changed files with 153 additions and 2 deletions

View File

@@ -21,6 +21,8 @@ import { RecencySorter } from "./skip-list/sorters/RecencySorter";
import { AlphabeticSorter } from "./skip-list/sorters/AlphabeticSorter";
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import { EffectiveMembership, getEffectiveMembership, getEffectiveMembershipTag } from "../../utils/membership";
import SpaceStore from "../spaces/SpaceStore";
import { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
/**
* This store allows for fast retrieval of the room list in a sorted and filtered manner.
@@ -34,6 +36,10 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
public constructor(dispatcher: MatrixDispatcher) {
super(dispatcher);
this.msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, () => {
this.onActiveSpaceChanged();
});
SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, () => this.onActiveSpaceChanged());
}
/**
@@ -53,6 +59,14 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
else return [];
}
/**
* Get a list of sorted rooms that belong to the currently active space.
*/
public getSortedRoomsInActiveSpace(): Room[] {
if (this.roomSkipList?.initialized) return Array.from(this.roomSkipList.getRoomsInActiveSpace());
else return [];
}
/**
* Re-sort the list of rooms by alphabetic order.
*/
@@ -78,6 +92,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
const sorter = new RecencySorter(this.matrixClient.getSafeUserId());
this.roomSkipList = new RoomSkipList(sorter);
const rooms = this.getRooms();
await SpaceStore.instance.storeReadyPromise;
this.roomSkipList.seed(rooms);
this.emit(LISTS_UPDATE_EVENT);
}
@@ -178,6 +193,12 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
this.roomSkipList.addRoom(room);
this.emit(LISTS_UPDATE_EVENT);
}
private onActiveSpaceChanged(): void {
if (!this.roomSkipList) return;
this.roomSkipList.calculateActiveSpaceForNodes();
this.emit(LISTS_UPDATE_EVENT);
}
}
export default class RoomListStoreV3 {

View File

@@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import type { Room } from "matrix-js-sdk/src/matrix";
import SpaceStore from "../../spaces/SpaceStore";
/**
* Room skip list stores room nodes.
@@ -13,6 +14,8 @@ import type { Room } from "matrix-js-sdk/src/matrix";
* in different levels.
*/
export class RoomNode {
private _isInActiveSpace: boolean = false;
public constructor(public readonly room: Room) {}
/**
@@ -26,4 +29,23 @@ export class RoomNode {
* eg: previous[i] gives the previous room node from this room node in level i.
*/
public previous: RoomNode[] = [];
/**
* Whether the room associated with this room node belongs to
* the currently active space.
* @see {@link SpaceStoreClass#activeSpace} to understand what active
* space means.
*/
public get isInActiveSpace(): boolean {
return this._isInActiveSpace;
}
/**
* Check if this room belongs to the active space and store the result
* in {@link RoomNode#isInActiveSpace}.
*/
public checkIfRoomBelongsToActiveSpace(): void {
const activeSpace = SpaceStore.instance.activeSpace;
this._isInActiveSpace = SpaceStore.instance.isRoomInSpace(activeSpace, this.room.roomId);
}
}

View File

@@ -44,9 +44,22 @@ export class RoomSkipList implements Iterable<Room> {
this.levels[currentLevel.level] = currentLevel;
currentLevel = currentLevel.generateNextLevel();
} while (currentLevel.size > 1);
// 3. Go through the list of rooms and mark nodes in active space
this.calculateActiveSpaceForNodes();
this.initialized = true;
}
/**
* Go through all the room nodes and check if they belong to the active space.
*/
public calculateActiveSpaceForNodes(): void {
for (const node of this.roomNodeMap.values()) {
node.checkIfRoomBelongsToActiveSpace();
}
}
/**
* Change the sorting algorithm used by the skip list.
* This will reset the list and will rebuild from scratch.
@@ -81,6 +94,7 @@ export class RoomSkipList implements Iterable<Room> {
this.removeRoom(room);
const newNode = new RoomNode(room);
newNode.checkIfRoomBelongsToActiveSpace();
this.roomNodeMap.set(room.roomId, newNode);
/**
@@ -159,6 +173,10 @@ export class RoomSkipList implements Iterable<Room> {
return new SortedRoomIterator(this.levels[0].head!);
}
public getRoomsInActiveSpace(): SortedSpaceFilteredIterator {
return new SortedSpaceFilteredIterator(this.levels[0].head!);
}
/**
* The number of rooms currently in the skip list.
*/
@@ -179,3 +197,23 @@ class SortedRoomIterator implements Iterator<Room> {
};
}
}
class SortedSpaceFilteredIterator implements Iterator<Room> {
public constructor(private current: RoomNode) {}
public [Symbol.iterator](): SortedSpaceFilteredIterator {
return this;
}
public next(): IteratorResult<Room> {
let current = this.current;
while (current && !current.isInActiveSpace) {
current = current.next[0];
}
if (!current) return { value: undefined, done: true };
this.current = current.next[0];
return {
value: current.room,
};
}
}

View File

@@ -21,6 +21,7 @@ import {
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { defer } from "matrix-js-sdk/src/utils";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import defaultDispatcher from "../../dispatcher/dispatcher";
@@ -152,6 +153,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
private _enabledMetaSpaces: MetaSpace[] = [];
/** Whether the feature flag is set for MSC3946 */
private _msc3946ProcessDynamicPredecessor: boolean = SettingsStore.getValue("feature_dynamic_room_predecessors");
private _storeReadyDeferred = defer();
public constructor() {
super(defaultDispatcher, {});
@@ -162,6 +164,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
SettingsStore.monitorSetting("feature_dynamic_room_predecessors", null);
}
/**
* A promise that resolves when the space store is ready.
* This happens after an initial hierarchy of spaces and rooms has been computed.
*/
public get storeReadyPromise(): Promise<void> {
return this._storeReadyDeferred.promise;
}
/**
* Get the order of meta spaces to display in the space panel.
*
@@ -1201,6 +1211,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
} else {
this.switchSpaceIfNeeded();
}
this._storeReadyDeferred.resolve();
}
private sendUserProperties(): void {