Merge branch 'develop' into spaces/context-switching

This commit is contained in:
Šimon Brandner 2021-04-20 12:06:59 +02:00
commit a3d0ccf306
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
215 changed files with 7653 additions and 2562 deletions

View file

@ -121,21 +121,16 @@ export class SetupEncryptionStore extends EventEmitter {
// on the first trust check, and the key backup restore will happen
// in the background.
await new Promise((resolve, reject) => {
try {
accessSecretStorage(async () => {
await cli.checkOwnCrossSigningTrust();
resolve();
if (backupInfo) {
// A complete restore can take many minutes for large
// accounts / slow servers, so we allow the dialog
// to advance before this.
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
}
}).catch(reject);
} catch (e) {
console.error(e);
reject(e);
}
accessSecretStorage(async () => {
await cli.checkOwnCrossSigningTrust();
resolve();
if (backupInfo) {
// A complete restore can take many minutes for large
// accounts / slow servers, so we allow the dialog
// to advance before this.
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
}
}).catch(reject);
});
if (cli.getCrossSigningId()) {

View file

@ -145,7 +145,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const data = await this.fetchSuggestedRooms(space);
if (this._activeSpace === space) {
this._suggestedRooms = data.rooms.filter(roomInfo => {
return roomInfo.room_type !== RoomType.Space && !this.matrixClient.getRoom(roomInfo.room_id);
return roomInfo.room_type !== RoomType.Space
&& this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join";
});
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
}
@ -317,6 +318,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
};
private onSpaceMembersChange = (ev: MatrixEvent) => {
// skip this update if we do not have a DM with this user
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
this.onRoomsUpdate();
};
private onRoomsUpdate = throttle(() => {
// TODO resolve some updates as deltas
const visibleRooms = this.matrixClient.getVisibleRooms();
@ -392,15 +399,17 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.onRoomsUpdate();
}
// if the user was looking at the room and then joined select that space
if (room.getMyMembership() === "join" && room.roomId === RoomViewStore.getRoomId()) {
this.setActiveSpace(room);
}
const numSuggestedRooms = this._suggestedRooms.length;
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
if (numSuggestedRooms !== this._suggestedRooms.length) {
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
if (room.getMyMembership() === "join") {
if (!room.isSpaceRoom()) {
const numSuggestedRooms = this._suggestedRooms.length;
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
if (numSuggestedRooms !== this._suggestedRooms.length) {
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
}
} else if (room.roomId === RoomViewStore.getRoomId()) {
// if the user was looking at the space and then joined: select that space
this.setActiveSpace(room);
}
}
};
@ -408,18 +417,30 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return;
if (ev.getType() === EventType.SpaceChild && room.isSpaceRoom()) {
this.onSpaceUpdate();
this.emit(room.roomId);
} else if (ev.getType() === EventType.SpaceParent) {
// TODO rebuild the space parent and not the room - check permissions?
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
} else {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
switch (ev.getType()) {
case EventType.SpaceChild:
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
this.emit(room.roomId);
}
break;
case EventType.SpaceParent:
// TODO rebuild the space parent and not the room - check permissions?
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
} else {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
break;
case EventType.RoomMember:
if (room.isSpaceRoom()) {
this.onSpaceMembersChange(ev);
}
break;
}
};
@ -536,6 +557,26 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.notificationStateMap.set(key, state);
return state;
}
// traverse space tree with DFS calling fn on each space including the given root one
public traverseSpace(
spaceId: string,
fn: (roomId: string) => void,
includeRooms = false,
parentPath?: Set<string>,
) {
if (parentPath && parentPath.has(spaceId)) return; // prevent cycles
fn(spaceId);
const newPath = new Set(parentPath).add(spaceId);
const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId));
if (includeRooms) {
childRooms.forEach(r => fn(r.roomId));
}
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
}
}
export default class SpaceStore {

View file

@ -0,0 +1,80 @@
/*
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 {AsyncStoreWithClient} from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import {ActionPayload} from "../dispatcher/payloads";
import {VoiceRecording} from "../voice/VoiceRecording";
interface IState {
recording?: VoiceRecording;
}
export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
private static internalInstance: VoiceRecordingStore;
public constructor() {
super(defaultDispatcher, {});
}
/**
* Gets the active recording instance, if any.
*/
public get activeRecording(): VoiceRecording | null {
return this.state.recording;
}
public static get instance(): VoiceRecordingStore {
if (!VoiceRecordingStore.internalInstance) {
VoiceRecordingStore.internalInstance = new VoiceRecordingStore();
}
return VoiceRecordingStore.internalInstance;
}
protected async onAction(payload: ActionPayload): Promise<void> {
// Nothing to do, but we're required to override the function
return;
}
/**
* Starts a new recording if one isn't already in progress. Note that this simply
* creates a recording instance - whether or not recording is actively in progress
* can be seen via the VoiceRecording class.
* @returns {VoiceRecording} The recording.
*/
public startRecording(): VoiceRecording {
if (!this.matrixClient) throw new Error("Cannot start a recording without a MatrixClient");
if (this.state.recording) throw new Error("A recording is already in progress");
const recording = new VoiceRecording(this.matrixClient);
// noinspection JSIgnoredPromiseFromCall - we can safely run this async
this.updateState({recording});
return recording;
}
/**
* Disposes of the current recording, no matter the state of it.
* @returns {Promise<void>} Resolves when complete.
*/
public disposeRecording(): Promise<void> {
if (this.state.recording) {
this.state.recording.destroy(); // stops internally
}
return this.updateState({recording: null});
}
}

View file

@ -22,6 +22,7 @@ import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomNotificationState } from "./RoomNotificationState";
import { SummarizedNotificationState } from "./SummarizedNotificationState";
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
interface IState {}
@ -47,7 +48,9 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
// This will include highlights from the previous version of the room internally
const globalState = new SummarizedNotificationState();
for (const room of this.matrixClient.getVisibleRooms()) {
globalState.add(this.getRoomState(room));
if (VisibilityProvider.instance.isRoomVisible(room)) {
globalState.add(this.getRoomState(room));
}
}
return globalState;
}

View file

@ -82,7 +82,7 @@ export class ListLayout {
public get defaultVisibleTiles(): number {
// This number is what "feels right", and mostly subject to design's opinion.
return 5;
return 8;
}
public tilesWithPadding(n: number, paddingPx: number): number {

View file

@ -1,6 +1,5 @@
/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2018-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.
@ -15,27 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "matrix-js-sdk/src/client";
import {MatrixClient} from "matrix-js-sdk/src/client";
import SettingsStore from "../../settings/SettingsStore";
import { DefaultTagID, isCustomTag, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models";
import { Room } from "matrix-js-sdk/src/models/room";
import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
import { ActionPayload } from "../../dispatcher/payloads";
import {DefaultTagID, isCustomTag, OrderedDefaultTagIDs, RoomUpdateCause, TagID} from "./models";
import {Room} from "matrix-js-sdk/src/models/room";
import {IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm} from "./algorithms/models";
import {ActionPayload} from "../../dispatcher/payloads";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
import { TagWatcher } from "./TagWatcher";
import {readReceiptChangeIsFor} from "../../utils/read-receipts";
import {FILTER_CHANGED, FilterKind, IFilterCondition} from "./filters/IFilterCondition";
import {TagWatcher} from "./TagWatcher";
import RoomViewStore from "../RoomViewStore";
import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import {Algorithm, LIST_UPDATED_EVENT} from "./algorithms/Algorithm";
import {EffectiveMembership, getEffectiveMembership} from "../../utils/membership";
import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
import RoomListLayoutStore from "./RoomListLayoutStore";
import { MarkedExecution } from "../../utils/MarkedExecution";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import { NameFilterCondition } from "./filters/NameFilterCondition";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { VisibilityProvider } from "./filters/VisibilityProvider";
import { SpaceWatcher } from "./SpaceWatcher";
import {MarkedExecution} from "../../utils/MarkedExecution";
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;
@ -57,6 +56,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
private initialListsGenerated = false;
private algorithm = new Algorithm();
private filterConditions: IFilterCondition[] = [];
private prefilterConditions: IFilterCondition[] = [];
private tagWatcher: TagWatcher;
private spaceWatcher: SpaceWatcher;
private updateFn = new MarkedExecution(() => {
@ -104,6 +104,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
public async resetStore() {
await this.reset();
this.filterConditions = [];
this.prefilterConditions = [];
this.initialListsGenerated = false;
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) {
await this.setAndPersistTagSorting(tagId, sort);
this.updateFn.trigger();
@ -557,6 +591,34 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
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.
*
@ -568,11 +630,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
public async regenerateAllLists({trigger = true}) {
console.warn("Regenerating all room lists");
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 rooms = this.getPlausibleRooms();
const customTags = new Set<TagID>();
if (this.state.tagsEnabled) {
@ -601,24 +659,44 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
if (trigger) this.updateFn.trigger();
}
/**
* Adds a filter condition to the room list store. Filters may be applied async,
* and thus might not cause an update to the store immediately.
* @param {IFilterCondition} filter The filter condition to add.
*/
public addFilter(filter: IFilterCondition): void {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log("Adding filter condition:", filter);
}
this.filterConditions.push(filter);
if (this.algorithm) {
this.algorithm.addFilterCondition(filter);
let promise = Promise.resolve();
if (filter.kind === FilterKind.Prefilter) {
filter.on(FILTER_CHANGED, this.onPrefilterUpdated);
this.prefilterConditions.push(filter);
promise = this.recalculatePrefiltering();
} else {
this.filterConditions.push(filter);
if (this.algorithm) {
this.algorithm.addFilterCondition(filter);
}
}
this.updateFn.trigger();
promise.then(() => this.updateFn.trigger());
}
/**
* Removes a filter condition from the room list store. If the filter was
* not previously added to the room list store, this will no-op. The effects
* of removing a filter may be applied async and therefore might not cause
* an update right away.
* @param {IFilterCondition} filter The filter condition to remove.
*/
public removeFilter(filter: IFilterCondition): void {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log("Removing filter condition:", filter);
}
const idx = this.filterConditions.indexOf(filter);
let promise = Promise.resolve();
let idx = this.filterConditions.indexOf(filter);
if (idx >= 0) {
this.filterConditions.splice(idx, 1);
@ -626,7 +704,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
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());
}
/**

View file

@ -28,12 +28,22 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) {
this.filter.updateSpace(this.activeSpace); // get the filter into a consistent state
this.updateFilter(); // 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);
private onSelectedSpaceUpdated = (activeSpace: Room) => {
this.activeSpace = activeSpace;
this.updateFilter();
};
private updateFilter = () => {
if (this.activeSpace) {
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
});
}
this.filter.updateSpace(this.activeSpace);
};
}

View file

@ -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");
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 DMRoomMap from "../../../utils/DMRoomMap";
import { EventEmitter } from "events";
import { arrayDiff, arrayHasDiff, ArrayUtil } from "../../../utils/arrays";
import { getEnumValues } from "../../../utils/enums";
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
import {
IListOrderingMap,
@ -29,7 +28,7 @@ import {
ListAlgorithm,
SortAlgorithm,
} from "./models";
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "../filters/IFilterCondition";
import { FILTER_CHANGED, IFilterCondition } from "../filters/IFilterCondition";
import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../../../utils/membership";
import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
@ -79,6 +78,11 @@ export class Algorithm extends EventEmitter {
private allowedByFilter: Map<IFilterCondition, Room[]> = new Map<IFilterCondition, Room[]>();
private allowedRoomsByFilters: Set<Room> = new Set<Room>();
/**
* Set to true to suspend emissions of algorithm updates.
*/
public updatesInhibited = false;
public constructor() {
super();
}
@ -87,6 +91,14 @@ export class Algorithm extends EventEmitter {
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 {
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
// view. This will trick them into getting the complete room list.
if (!this.hasFilters) {
if (!this.hasFilters && !this.updatesInhibited) {
this.emit(LIST_UPDATED_EVENT);
}
}
@ -174,6 +186,7 @@ export class Algorithm extends EventEmitter {
await this.recalculateFilteredRooms();
// re-emit the update so the list store can fire an off-cycle update if needed
if (this.updatesInhibited) return;
this.emit(FILTER_CHANGED);
}
@ -299,6 +312,7 @@ export class Algorithm extends EventEmitter {
this.recalculateStickyRoom();
// Finally, trigger an update
if (this.updatesInhibited) return;
this.emit(LIST_UPDATED_EVENT);
}
@ -309,10 +323,6 @@ export class Algorithm extends EventEmitter {
console.warn("Recalculating filtered room list");
const filters = Array.from(this.allowedByFilter.keys());
const orderedFilters = new ArrayUtil(filters)
.groupBy(f => f.relativePriority)
.orderBy(getEnumValues(FilterPriority))
.value;
const newMap: ITagMap = {};
for (const tagId of Object.keys(this.cachedRooms)) {
// Cheaply clone the rooms so we can more easily do operations on the list.
@ -320,18 +330,9 @@ export class Algorithm extends EventEmitter {
// to the rooms we know will be deduped by the Set.
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
let remainingRooms = rooms.map(r => r);
let allowedRoomsInThisTag = [];
let lastFilterPriority = orderedFilters[0].relativePriority;
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 remainingRooms = rooms.map(r => r);
const allowedRoomsInThisTag = [];
for (const filter of filters) {
const filteredRooms = remainingRooms.filter(r => filter.isVisible(r));
for (const room of filteredRooms) {
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[]>[]);
this.allowedRoomsByFilters = new Set(allowedRooms);
this.filteredRooms = newMap;
if (this.updatesInhibited) return;
this.emit(LIST_UPDATED_EVENT);
}
@ -404,6 +406,7 @@ export class Algorithm extends EventEmitter {
if (!!this._cachedStickyRooms) {
// Clear the cache if we won't be needing it
this._cachedStickyRooms = null;
if (this.updatesInhibited) return;
this.emit(LIST_UPDATED_EVENT);
}
return;
@ -446,6 +449,7 @@ export class Algorithm extends EventEmitter {
}
// Finally, trigger an update
if (this.updatesInhibited) return;
this.emit(LIST_UPDATED_EVENT);
}
@ -512,7 +516,12 @@ export class Algorithm extends EventEmitter {
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
console.warn("Resetting known rooms, initiating regeneration");
if (!this.updatesInhibited) {
// We only log this if we're expecting to be publishing updates, which means that
// this could be an unexpected invocation. If we're inhibited, then this is probably
// an intentional invocation.
console.warn("Resetting known rooms, initiating regeneration");
}
// Before we go any further we need to clear (but remember) the sticky room to
// avoid accidentally duplicating it in the list.

View file

@ -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");
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 { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { Group } from "matrix-js-sdk/src/models/group";
import { EventEmitter } from "events";
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
}
public get relativePriority(): FilterPriority {
// Lowest priority so we can coarsely find rooms.
return FilterPriority.Lowest;
public get kind(): FilterKind {
return FilterKind.Prefilter;
}
public isVisible(room: Room): boolean {

View file

@ -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");
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 enum FilterPriority {
Lowest,
// in the middle would be Low, Normal, and High if we had a need
Highest,
export enum FilterKind {
/**
* A prefilter is one which coarsely determines which rooms are
* 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 {
/**
* The relative priority that this filter should be applied with.
* Lower priorities get applied first.
* The kind of filter this presents.
*/
relativePriority: FilterPriority;
kind: FilterKind;
/**
* Determines if a given room should be visible under this

View file

@ -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");
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 { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { EventEmitter } from "events";
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
import { throttle } from "lodash";
@ -31,9 +31,8 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
super();
}
public get relativePriority(): FilterPriority {
// We want this one to be at the highest priority so it can search within other filters.
return FilterPriority.Highest;
public get kind(): FilterKind {
return FilterKind.Runtime;
}
public get search(): string {
@ -66,12 +65,17 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
return this.matches(room.name);
}
public matches(val: string): boolean {
private normalize(val: string): string {
// Note: we have to match the filter with the removeHiddenChars() room name because the
// function strips spaces and other characters (M becomes RN for example, in lowercase).
// We also doubly convert to lowercase to work around oddities of the library.
const noSecretsFilter = removeHiddenChars(this.search.toLowerCase()).toLowerCase();
const noSecretsName = removeHiddenChars(val.toLowerCase()).toLowerCase();
return noSecretsName.includes(noSecretsFilter);
return removeHiddenChars(val.toLowerCase())
// Strip all punctuation
.replace(/[\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~\u2000-\u206f\u2e00-\u2e7f]/g, "")
// We also doubly convert to lowercase to work around oddities of the library.
.toLowerCase();
}
public matches(val: string): boolean {
return this.normalize(val).includes(this.normalize(this.search));
}
}

View file

@ -17,7 +17,7 @@ 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 { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable";
import SpaceStore, {HOME_SPACE} from "../../SpaceStore";
import { setHasDiff } from "../../../utils/sets";
@ -32,9 +32,8 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
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 get kind(): FilterKind {
return FilterKind.Prefilter;
}
public isVisible(room: Room): boolean {
@ -46,12 +45,7 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
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);
}
};