Add Space Panel with Room List filtering

This commit is contained in:
Michael Telatynski 2021-02-26 10:23:09 +00:00
parent 7030c636f0
commit f21aedc6cf
14 changed files with 796 additions and 31 deletions

View file

@ -35,6 +35,7 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import { NameFilterCondition } from "./filters/NameFilterCondition";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { VisibilityProvider } from "./filters/VisibilityProvider";
import { SpaceWatcher } from "./SpaceWatcher";
interface IState {
tagsEnabled?: boolean;
@ -56,7 +57,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
private initialListsGenerated = false;
private algorithm = new Algorithm();
private filterConditions: IFilterCondition[] = [];
private tagWatcher = new TagWatcher(this);
private tagWatcher: TagWatcher;
private spaceWatcher: SpaceWatcher;
private updateFn = new MarkedExecution(() => {
for (const tagId of Object.keys(this.orderedLists)) {
RoomNotificationStateStore.instance.getListState(tagId).setRooms(this.orderedLists[tagId]);
@ -77,6 +79,15 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
RoomViewStore.addListener(() => this.handleRVSUpdate({}));
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
this.algorithm.on(FILTER_CHANGED, this.onAlgorithmFilterUpdated);
this.setupWatchers();
}
private setupWatchers() {
if (SettingsStore.getValue("feature_spaces")) {
this.spaceWatcher = new SpaceWatcher(this);
} else {
this.tagWatcher = new TagWatcher(this);
}
}
public get unfilteredLists(): ITagMap {
@ -92,9 +103,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// Intended for test usage
public async resetStore() {
await this.reset();
this.tagWatcher = new TagWatcher(this);
this.filterConditions = [];
this.initialListsGenerated = false;
this.setupWatchers();
this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
this.algorithm.off(FILTER_CHANGED, this.onAlgorithmListUpdated);
@ -554,8 +565,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
public async regenerateAllLists({trigger = true}) {
console.warn("Regenerating all room lists");
const rooms = this.matrixClient.getVisibleRooms()
.filter(r => VisibilityProvider.instance.isRoomVisible(r));
const rooms = [
...this.matrixClient.getVisibleRooms(),
// also show space invites in the room list
...this.matrixClient.getRooms().filter(r => r.isSpaceRoom() && r.getMyMembership() === "invite"),
].filter(r => VisibilityProvider.instance.isRoomVisible(r));
const customTags = new Set<TagID>();
if (this.state.tagsEnabled) {
for (const room of rooms) {

View file

@ -0,0 +1,39 @@
/*
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 { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
*/
export class SpaceWatcher {
private filter = new SpaceFilterCondition();
private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) {
this.filter.updateSpace(this.activeSpace); // get the filter into a consistent state
store.addFilter(this.filter);
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
}
private onSelectedSpaceUpdated = (activeSpace) => {
this.filter.updateSpace(this.activeSpace = activeSpace);
};
}

View file

@ -186,6 +186,9 @@ export class Algorithm extends EventEmitter {
}
private async doUpdateStickyRoom(val: Room) {
// no-op sticky rooms
if (val?.isSpaceRoom() && val.getMyMembership() !== "invite") val = null;
// Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
// otherwise we risk duplicating rooms.

View 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 { EventEmitter } from "events";
import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable";
import SpaceStore, {HOME_SPACE} from "../../SpaceStore";
import { setHasDiff } from "../../../utils/sets";
/**
* A filter condition for the room list which reveals rooms which
* are a member of a given space or if no space is selected shows:
* + Orphaned rooms (ones not in any space you are a part of)
* + All DMs
*/
export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
private roomIds = new Set<Room>();
private space: Room = null;
public get relativePriority(): FilterPriority {
// Lowest priority so we can coarsely find rooms.
return FilterPriority.Lowest;
}
public isVisible(room: Room): boolean {
return this.roomIds.has(room.roomId);
}
private onStoreUpdate = async (): Promise<void> => {
const beforeRoomIds = this.roomIds;
this.roomIds = SpaceStore.instance.getSpaceFilteredRoomIds(this.space);
if (setHasDiff(beforeRoomIds, this.roomIds)) {
// XXX: Room List Store has a bug where rooms which are synced after the filter is set
// are excluded from the filter, this is a workaround for it.
this.emit(FILTER_CHANGED);
setTimeout(() => {
this.emit(FILTER_CHANGED);
}, 500);
}
};
private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
public updateSpace(space: Room) {
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
this.onStoreUpdate(); // initial update from the change to the space
}
public destroy(): void {
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
}
}