Restabilize room list ordering with prefiltering on spaces/communities
Fixes https://github.com/vector-im/element-web/issues/16799 This change replaces the "relative priority" system for filters with a kind model. The kind is used to differentiate and optimize when/where a filter condition is applied, resulting in a more stable ordering of the room list. The included documentation describes what this means in detail. This also introduces a way to inhibit updates being emitted from the Algorithm class given what we're doing to the poor thing will cause it to do a bunch of recalculation. Inhibiting the update and implicitly applying it (as part of our updateFn.mark()/trigger steps) results in much better performance. This has been tested on my own account with both communities and spaces of varying complexity: it feels faster, though the measurements appear to be within an error tolerance of each other (read: there's no performance impact of this).
This commit is contained in:
parent
2ab304189d
commit
70db749430
7 changed files with 192 additions and 102 deletions
|
@ -124,12 +124,19 @@ all kinds of filtering.
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
Filters are provided to the store as condition classes, which are then passed along to the algorithm
|
Filters are provided to the store as condition classes and have two major kinds: Prefilters and Runtime.
|
||||||
implementations. The implementations then get to decide how to actually filter the rooms, however in
|
|
||||||
practice the base `Algorithm` class deals with the filtering in a more optimized/generic way.
|
|
||||||
|
|
||||||
The results of filters get cached to avoid needlessly iterating over potentially thousands of rooms,
|
Prefilters flush out rooms which shouldn't appear to the algorithm implementations. Typically this is
|
||||||
as the old room list store does. When a filter condition changes, it emits an update which (in this
|
due to some higher order room list filtering (such as spaces or tags) deliberately exposing a subset of
|
||||||
|
rooms to the user. The algorithm implementations will not see a room being prefiltered out.
|
||||||
|
|
||||||
|
Runtime filters are used for more dynamic filtering, such as the user filtering by room name. These
|
||||||
|
filters are passed along to the algorithm implementations where those implementations decide how and
|
||||||
|
when to apply the filter. In practice, the base `Algorithm` class ends up doing the heavy lifting for
|
||||||
|
optimization reasons.
|
||||||
|
|
||||||
|
The results of runtime filters get cached to avoid needlessly iterating over potentially thousands of
|
||||||
|
rooms, as the old room list store does. When a filter condition changes, it emits an update which (in this
|
||||||
case) the `Algorithm` class will pick up and act accordingly. Typically, this also means filtering a
|
case) the `Algorithm` class will pick up and act accordingly. Typically, this also means filtering a
|
||||||
minor subset where possible to avoid over-iterating rooms.
|
minor subset where possible to avoid over-iterating rooms.
|
||||||
|
|
||||||
|
@ -137,6 +144,13 @@ All filter conditions are considered "stable" by the consumers, meaning that the
|
||||||
expect a change in the condition unless the condition says it has changed. This is intentional to
|
expect a change in the condition unless the condition says it has changed. This is intentional to
|
||||||
maintain the caching behaviour described above.
|
maintain the caching behaviour described above.
|
||||||
|
|
||||||
|
One might ask why we don't just use prefilter conditions for everything, and the answer is one of slight
|
||||||
|
subtly: in the cases of prefilters we are knowingly exposing the user to a workspace-style UX where
|
||||||
|
room notifications are self-contained within that workspace. Runtime filters tend to not want to affect
|
||||||
|
visible notification counts (as it doesn't want the room header to suddenly be confusing to the user as
|
||||||
|
they type), and occasionally UX like "found 2/12 rooms" is desirable. If prefiltering were used instead,
|
||||||
|
the notification counts would vary while the user was typing and "found 2/12" UX would not be possible.
|
||||||
|
|
||||||
## Class breakdowns
|
## Class breakdowns
|
||||||
|
|
||||||
The `RoomListStore` is the major coordinator of various algorithm implementations, which take care
|
The `RoomListStore` is the major coordinator of various algorithm implementations, which take care
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
Copyright 2018-2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -23,7 +22,7 @@ import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm
|
||||||
import {ActionPayload} from "../../dispatcher/payloads";
|
import {ActionPayload} from "../../dispatcher/payloads";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import {readReceiptChangeIsFor} from "../../utils/read-receipts";
|
import {readReceiptChangeIsFor} from "../../utils/read-receipts";
|
||||||
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
|
import {FILTER_CHANGED, FilterKind, IFilterCondition} from "./filters/IFilterCondition";
|
||||||
import {TagWatcher} from "./TagWatcher";
|
import {TagWatcher} from "./TagWatcher";
|
||||||
import RoomViewStore from "../RoomViewStore";
|
import RoomViewStore from "../RoomViewStore";
|
||||||
import {Algorithm, LIST_UPDATED_EVENT} from "./algorithms/Algorithm";
|
import {Algorithm, LIST_UPDATED_EVENT} from "./algorithms/Algorithm";
|
||||||
|
@ -57,6 +56,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
private initialListsGenerated = false;
|
private initialListsGenerated = false;
|
||||||
private algorithm = new Algorithm();
|
private algorithm = new Algorithm();
|
||||||
private filterConditions: IFilterCondition[] = [];
|
private filterConditions: IFilterCondition[] = [];
|
||||||
|
private prefilterConditions: IFilterCondition[] = [];
|
||||||
private tagWatcher: TagWatcher;
|
private tagWatcher: TagWatcher;
|
||||||
private spaceWatcher: SpaceWatcher;
|
private spaceWatcher: SpaceWatcher;
|
||||||
private updateFn = new MarkedExecution(() => {
|
private updateFn = new MarkedExecution(() => {
|
||||||
|
@ -104,6 +104,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
public async resetStore() {
|
public async resetStore() {
|
||||||
await this.reset();
|
await this.reset();
|
||||||
this.filterConditions = [];
|
this.filterConditions = [];
|
||||||
|
this.prefilterConditions = [];
|
||||||
this.initialListsGenerated = false;
|
this.initialListsGenerated = false;
|
||||||
this.setupWatchers();
|
this.setupWatchers();
|
||||||
|
|
||||||
|
@ -435,6 +436,39 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async recalculatePrefiltering() {
|
||||||
|
if (!this.algorithm) return;
|
||||||
|
if (!this.algorithm.hasTagSortingMap) return; // we're still loading
|
||||||
|
|
||||||
|
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||||
|
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||||
|
console.log("Calculating new prefiltered room list");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inhibit updates because we're about to lie heavily to the algorithm
|
||||||
|
this.algorithm.updatesInhibited = true;
|
||||||
|
|
||||||
|
// Figure out which rooms are about to be valid, and the state of affairs
|
||||||
|
const rooms = this.getPlausibleRooms();
|
||||||
|
const currentSticky = this.algorithm.stickyRoom;
|
||||||
|
const stickyIsStillPresent = currentSticky && rooms.includes(currentSticky);
|
||||||
|
|
||||||
|
// Reset the sticky room before resetting the known rooms so the algorithm
|
||||||
|
// doesn't freak out.
|
||||||
|
await this.algorithm.setStickyRoom(null);
|
||||||
|
await this.algorithm.setKnownRooms(rooms);
|
||||||
|
|
||||||
|
// Set the sticky room back, if needed, now that we have updated the store.
|
||||||
|
// This will use relative stickyness to the new room set.
|
||||||
|
if (stickyIsStillPresent) {
|
||||||
|
await this.algorithm.setStickyRoom(currentSticky);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, mark an update and resume updates from the algorithm
|
||||||
|
this.updateFn.mark();
|
||||||
|
this.algorithm.updatesInhibited = false;
|
||||||
|
}
|
||||||
|
|
||||||
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||||
await this.setAndPersistTagSorting(tagId, sort);
|
await this.setAndPersistTagSorting(tagId, sort);
|
||||||
this.updateFn.trigger();
|
this.updateFn.trigger();
|
||||||
|
@ -557,6 +591,34 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.updateFn.trigger();
|
this.updateFn.trigger();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onPrefilterUpdated = async () => {
|
||||||
|
await this.recalculatePrefiltering();
|
||||||
|
this.updateFn.trigger();
|
||||||
|
};
|
||||||
|
|
||||||
|
private getPlausibleRooms(): Room[] {
|
||||||
|
if (!this.matrixClient) return [];
|
||||||
|
|
||||||
|
let 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));
|
||||||
|
|
||||||
|
if (this.prefilterConditions.length > 0) {
|
||||||
|
rooms = rooms.filter(r => {
|
||||||
|
for (const filter of this.prefilterConditions) {
|
||||||
|
if (!filter.isVisible(r)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regenerates the room whole room list, discarding any previous results.
|
* Regenerates the room whole room list, discarding any previous results.
|
||||||
*
|
*
|
||||||
|
@ -568,11 +630,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
public async regenerateAllLists({trigger = true}) {
|
public async regenerateAllLists({trigger = true}) {
|
||||||
console.warn("Regenerating all room lists");
|
console.warn("Regenerating all room lists");
|
||||||
|
|
||||||
const rooms = [
|
const rooms = this.getPlausibleRooms();
|
||||||
...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>();
|
const customTags = new Set<TagID>();
|
||||||
if (this.state.tagsEnabled) {
|
if (this.state.tagsEnabled) {
|
||||||
|
@ -606,11 +664,18 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||||
console.log("Adding filter condition:", filter);
|
console.log("Adding filter condition:", filter);
|
||||||
}
|
}
|
||||||
|
let promise = Promise.resolve(); // use a promise to maintain sync API contract
|
||||||
|
if (filter.kind === FilterKind.Prefilter) {
|
||||||
|
filter.on(FILTER_CHANGED, this.onPrefilterUpdated);
|
||||||
|
this.prefilterConditions.push(filter);
|
||||||
|
promise = this.recalculatePrefiltering();
|
||||||
|
} else {
|
||||||
this.filterConditions.push(filter);
|
this.filterConditions.push(filter);
|
||||||
if (this.algorithm) {
|
if (this.algorithm) {
|
||||||
this.algorithm.addFilterCondition(filter);
|
this.algorithm.addFilterCondition(filter);
|
||||||
}
|
}
|
||||||
this.updateFn.trigger();
|
}
|
||||||
|
promise.then(() => this.updateFn.trigger());
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(filter: IFilterCondition): void {
|
public removeFilter(filter: IFilterCondition): void {
|
||||||
|
@ -618,7 +683,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||||
console.log("Removing filter condition:", filter);
|
console.log("Removing filter condition:", filter);
|
||||||
}
|
}
|
||||||
const idx = this.filterConditions.indexOf(filter);
|
let promise = Promise.resolve(); // use a promise to maintain sync API contract
|
||||||
|
let idx = this.filterConditions.indexOf(filter);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
this.filterConditions.splice(idx, 1);
|
this.filterConditions.splice(idx, 1);
|
||||||
|
|
||||||
|
@ -626,7 +692,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.algorithm.removeFilterCondition(filter);
|
this.algorithm.removeFilterCondition(filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.updateFn.trigger();
|
idx = this.prefilterConditions.indexOf(filter);
|
||||||
|
if (idx >= 0) {
|
||||||
|
filter.off(FILTER_CHANGED, this.onPrefilterUpdated);
|
||||||
|
this.prefilterConditions.splice(idx, 1);
|
||||||
|
promise = this.recalculatePrefiltering();
|
||||||
|
}
|
||||||
|
promise.then(() => this.updateFn.trigger());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,8 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { arrayDiff, arrayHasDiff, ArrayUtil } from "../../../utils/arrays";
|
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
|
||||||
import { getEnumValues } from "../../../utils/enums";
|
|
||||||
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
|
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
|
||||||
import {
|
import {
|
||||||
IListOrderingMap,
|
IListOrderingMap,
|
||||||
|
@ -29,7 +28,7 @@ import {
|
||||||
ListAlgorithm,
|
ListAlgorithm,
|
||||||
SortAlgorithm,
|
SortAlgorithm,
|
||||||
} from "./models";
|
} from "./models";
|
||||||
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "../filters/IFilterCondition";
|
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "../filters/IFilterCondition";
|
||||||
import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../../../utils/membership";
|
import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../../../utils/membership";
|
||||||
import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
|
import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
|
||||||
import { getListAlgorithmInstance } from "./list-ordering";
|
import { getListAlgorithmInstance } from "./list-ordering";
|
||||||
|
@ -79,6 +78,11 @@ export class Algorithm extends EventEmitter {
|
||||||
private allowedByFilter: Map<IFilterCondition, Room[]> = new Map<IFilterCondition, Room[]>();
|
private allowedByFilter: Map<IFilterCondition, Room[]> = new Map<IFilterCondition, Room[]>();
|
||||||
private allowedRoomsByFilters: Set<Room> = new Set<Room>();
|
private allowedRoomsByFilters: Set<Room> = new Set<Room>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to suspend emissions of algorithm updates.
|
||||||
|
*/
|
||||||
|
public updatesInhibited = false;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -87,6 +91,14 @@ export class Algorithm extends EventEmitter {
|
||||||
return this._stickyRoom ? this._stickyRoom.room : null;
|
return this._stickyRoom ? this._stickyRoom.room : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get knownRooms(): Room[] {
|
||||||
|
return this.rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hasTagSortingMap(): boolean {
|
||||||
|
return !!this.sortAlgorithms;
|
||||||
|
}
|
||||||
|
|
||||||
protected get hasFilters(): boolean {
|
protected get hasFilters(): boolean {
|
||||||
return this.allowedByFilter.size > 0;
|
return this.allowedByFilter.size > 0;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +176,7 @@ export class Algorithm extends EventEmitter {
|
||||||
|
|
||||||
// If we removed the last filter, tell consumers that we've "updated" our filtered
|
// If we removed the last filter, tell consumers that we've "updated" our filtered
|
||||||
// view. This will trick them into getting the complete room list.
|
// view. This will trick them into getting the complete room list.
|
||||||
if (!this.hasFilters) {
|
if (!this.hasFilters && !this.updatesInhibited) {
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +186,7 @@ export class Algorithm extends EventEmitter {
|
||||||
await this.recalculateFilteredRooms();
|
await this.recalculateFilteredRooms();
|
||||||
|
|
||||||
// re-emit the update so the list store can fire an off-cycle update if needed
|
// re-emit the update so the list store can fire an off-cycle update if needed
|
||||||
|
if (this.updatesInhibited) return;
|
||||||
this.emit(FILTER_CHANGED);
|
this.emit(FILTER_CHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +312,7 @@ export class Algorithm extends EventEmitter {
|
||||||
this.recalculateStickyRoom();
|
this.recalculateStickyRoom();
|
||||||
|
|
||||||
// Finally, trigger an update
|
// Finally, trigger an update
|
||||||
|
if (this.updatesInhibited) return;
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,10 +323,6 @@ export class Algorithm extends EventEmitter {
|
||||||
|
|
||||||
console.warn("Recalculating filtered room list");
|
console.warn("Recalculating filtered room list");
|
||||||
const filters = Array.from(this.allowedByFilter.keys());
|
const filters = Array.from(this.allowedByFilter.keys());
|
||||||
const orderedFilters = new ArrayUtil(filters)
|
|
||||||
.groupBy(f => f.relativePriority)
|
|
||||||
.orderBy(getEnumValues(FilterPriority))
|
|
||||||
.value;
|
|
||||||
const newMap: ITagMap = {};
|
const newMap: ITagMap = {};
|
||||||
for (const tagId of Object.keys(this.cachedRooms)) {
|
for (const tagId of Object.keys(this.cachedRooms)) {
|
||||||
// Cheaply clone the rooms so we can more easily do operations on the list.
|
// Cheaply clone the rooms so we can more easily do operations on the list.
|
||||||
|
@ -322,16 +332,7 @@ export class Algorithm extends EventEmitter {
|
||||||
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
|
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
|
||||||
let remainingRooms = rooms.map(r => r);
|
let remainingRooms = rooms.map(r => r);
|
||||||
let allowedRoomsInThisTag = [];
|
let allowedRoomsInThisTag = [];
|
||||||
let lastFilterPriority = orderedFilters[0].relativePriority;
|
for (const filter of filters) {
|
||||||
for (const filter of orderedFilters) {
|
|
||||||
if (filter.relativePriority !== lastFilterPriority) {
|
|
||||||
// Every time the filter changes priority, we want more specific filtering.
|
|
||||||
// To accomplish that, reset the variables to make it look like the process
|
|
||||||
// has started over, but using the filtered rooms as the seed.
|
|
||||||
remainingRooms = allowedRoomsInThisTag;
|
|
||||||
allowedRoomsInThisTag = [];
|
|
||||||
lastFilterPriority = filter.relativePriority;
|
|
||||||
}
|
|
||||||
const filteredRooms = remainingRooms.filter(r => filter.isVisible(r));
|
const filteredRooms = remainingRooms.filter(r => filter.isVisible(r));
|
||||||
for (const room of filteredRooms) {
|
for (const room of filteredRooms) {
|
||||||
const idx = remainingRooms.indexOf(room);
|
const idx = remainingRooms.indexOf(room);
|
||||||
|
@ -350,6 +351,7 @@ export class Algorithm extends EventEmitter {
|
||||||
const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]);
|
const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]);
|
||||||
this.allowedRoomsByFilters = new Set(allowedRooms);
|
this.allowedRoomsByFilters = new Set(allowedRooms);
|
||||||
this.filteredRooms = newMap;
|
this.filteredRooms = newMap;
|
||||||
|
if (this.updatesInhibited) return;
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,6 +406,7 @@ export class Algorithm extends EventEmitter {
|
||||||
if (!!this._cachedStickyRooms) {
|
if (!!this._cachedStickyRooms) {
|
||||||
// Clear the cache if we won't be needing it
|
// Clear the cache if we won't be needing it
|
||||||
this._cachedStickyRooms = null;
|
this._cachedStickyRooms = null;
|
||||||
|
if (this.updatesInhibited) return;
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -446,6 +449,7 @@ export class Algorithm extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, trigger an update
|
// Finally, trigger an update
|
||||||
|
if (this.updatesInhibited) return;
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||||
import { Group } from "matrix-js-sdk/src/models/group";
|
import { Group } from "matrix-js-sdk/src/models/group";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import GroupStore from "../../GroupStore";
|
import GroupStore from "../../GroupStore";
|
||||||
|
@ -39,9 +39,8 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon
|
||||||
this.onStoreUpdate(); // trigger a false update to seed the store
|
this.onStoreUpdate(); // trigger a false update to seed the store
|
||||||
}
|
}
|
||||||
|
|
||||||
public get relativePriority(): FilterPriority {
|
public get kind(): FilterKind {
|
||||||
// Lowest priority so we can coarsely find rooms.
|
return FilterKind.Prefilter;
|
||||||
return FilterPriority.Lowest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVisible(room: Room): boolean {
|
public isVisible(room: Room): boolean {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -19,10 +19,19 @@ import { EventEmitter } from "events";
|
||||||
|
|
||||||
export const FILTER_CHANGED = "filter_changed";
|
export const FILTER_CHANGED = "filter_changed";
|
||||||
|
|
||||||
export enum FilterPriority {
|
export enum FilterKind {
|
||||||
Lowest,
|
/**
|
||||||
// in the middle would be Low, Normal, and High if we had a need
|
* A prefilter is one which coarsely determines which rooms are
|
||||||
Highest,
|
* available for runtime filtering/rendering. Typically this will
|
||||||
|
* be things like Space selection.
|
||||||
|
*/
|
||||||
|
Prefilter,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime filters operate on the data set exposed by prefilters.
|
||||||
|
* Typically these are dynamic values like room name searching.
|
||||||
|
*/
|
||||||
|
Runtime,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,10 +48,9 @@ export enum FilterPriority {
|
||||||
*/
|
*/
|
||||||
export interface IFilterCondition extends EventEmitter {
|
export interface IFilterCondition extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* The relative priority that this filter should be applied with.
|
* The kind of filter this presents.
|
||||||
* Lower priorities get applied first.
|
|
||||||
*/
|
*/
|
||||||
relativePriority: FilterPriority;
|
kind: FilterKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a given room should be visible under this
|
* Determines if a given room should be visible under this
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
||||||
import { throttle } from "lodash";
|
import { throttle } from "lodash";
|
||||||
|
@ -31,9 +31,8 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get relativePriority(): FilterPriority {
|
public get kind(): FilterKind {
|
||||||
// We want this one to be at the highest priority so it can search within other filters.
|
return FilterKind.Runtime;
|
||||||
return FilterPriority.Highest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get search(): string {
|
public get search(): string {
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||||
import { IDestroyable } from "../../../utils/IDestroyable";
|
import { IDestroyable } from "../../../utils/IDestroyable";
|
||||||
import SpaceStore, {HOME_SPACE} from "../../SpaceStore";
|
import SpaceStore, {HOME_SPACE} from "../../SpaceStore";
|
||||||
import { setHasDiff } from "../../../utils/sets";
|
import { setHasDiff } from "../../../utils/sets";
|
||||||
|
@ -32,9 +32,8 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
|
||||||
private roomIds = new Set<Room>();
|
private roomIds = new Set<Room>();
|
||||||
private space: Room = null;
|
private space: Room = null;
|
||||||
|
|
||||||
public get relativePriority(): FilterPriority {
|
public get kind(): FilterKind {
|
||||||
// Lowest priority so we can coarsely find rooms.
|
return FilterKind.Prefilter;
|
||||||
return FilterPriority.Lowest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVisible(room: Room): boolean {
|
public isVisible(room: Room): boolean {
|
||||||
|
@ -46,12 +45,7 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
|
||||||
this.roomIds = SpaceStore.instance.getSpaceFilteredRoomIds(this.space);
|
this.roomIds = SpaceStore.instance.getSpaceFilteredRoomIds(this.space);
|
||||||
|
|
||||||
if (setHasDiff(beforeRoomIds, this.roomIds)) {
|
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);
|
this.emit(FILTER_CHANGED);
|
||||||
setTimeout(() => {
|
|
||||||
this.emit(FILTER_CHANGED);
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue