mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge pull request #4737 from matrix-org/travis/room-list/filter-priority
Support prioritized room list filters
This commit is contained in:
commit
82f2551f85
@ -20,9 +20,11 @@ import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
|||||||
import { EffectiveMembership, splitRoomsByMembership } from "../../membership";
|
import { EffectiveMembership, splitRoomsByMembership } from "../../membership";
|
||||||
import { ITagMap, ITagSortingMap } from "../models";
|
import { ITagMap, ITagSortingMap } from "../models";
|
||||||
import DMRoomMap from "../../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../../utils/DMRoomMap";
|
||||||
import { FILTER_CHANGED, IFilterCondition } from "../../filters/IFilterCondition";
|
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "../../filters/IFilterCondition";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { UPDATE_EVENT } from "../../../AsyncStore";
|
import { UPDATE_EVENT } from "../../../AsyncStore";
|
||||||
|
import { ArrayUtil } from "../../../../utils/arrays";
|
||||||
|
import { getEnumValues } from "../../../../utils/enums";
|
||||||
|
|
||||||
// TODO: Add locking support to avoid concurrent writes?
|
// TODO: Add locking support to avoid concurrent writes?
|
||||||
|
|
||||||
@ -184,22 +186,33 @@ export abstract class Algorithm extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.warn("Recalculating filtered room list");
|
console.warn("Recalculating filtered room list");
|
||||||
const allowedByFilters = new Set<Room>();
|
|
||||||
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.
|
||||||
// We optimize our lookups by trying to reduce sample size as much as possible
|
// We optimize our lookups by trying to reduce sample size as much as possible
|
||||||
// to the rooms we know will be deduped by the Set.
|
// to the rooms we know will be deduped by the Set.
|
||||||
const rooms = this.cachedRooms[tagId];
|
const rooms = this.cachedRooms[tagId];
|
||||||
const remainingRooms = rooms.map(r => r).filter(r => !allowedByFilters.has(r));
|
let remainingRooms = rooms.map(r => r);
|
||||||
const allowedRoomsInThisTag = [];
|
let allowedRoomsInThisTag = [];
|
||||||
for (const filter of filters) {
|
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 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);
|
||||||
if (idx >= 0) remainingRooms.splice(idx, 1);
|
if (idx >= 0) remainingRooms.splice(idx, 1);
|
||||||
allowedByFilters.add(room);
|
|
||||||
allowedRoomsInThisTag.push(room);
|
allowedRoomsInThisTag.push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +220,8 @@ export abstract class Algorithm extends EventEmitter {
|
|||||||
console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`);
|
console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.allowedRoomsByFilters = allowedByFilters;
|
const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]);
|
||||||
|
this.allowedRoomsByFilters = new Set(allowedRooms);
|
||||||
this.filteredRooms = newMap;
|
this.filteredRooms = newMap;
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
|
@ -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, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterPriority, 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";
|
||||||
@ -37,6 +37,11 @@ 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 {
|
||||||
|
// Lowest priority so we can coarsely find rooms.
|
||||||
|
return FilterPriority.Lowest;
|
||||||
|
}
|
||||||
|
|
||||||
public isVisible(room: Room): boolean {
|
public isVisible(room: Room): boolean {
|
||||||
return this.roomIds.includes(room.roomId);
|
return this.roomIds.includes(room.roomId);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,12 @@ import { EventEmitter } from "events";
|
|||||||
|
|
||||||
export const FILTER_CHANGED = "filter_changed";
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter condition for the room list, determining if a room
|
* A filter condition for the room list, determining if a room
|
||||||
* should be shown or not.
|
* should be shown or not.
|
||||||
@ -32,6 +38,12 @@ export const FILTER_CHANGED = "filter_changed";
|
|||||||
* as a change in the user's input), this emits FILTER_CHANGED.
|
* as a change in the user's input), this emits FILTER_CHANGED.
|
||||||
*/
|
*/
|
||||||
export interface IFilterCondition extends EventEmitter {
|
export interface IFilterCondition extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* The relative priority that this filter should be applied with.
|
||||||
|
* Lower priorities get applied first.
|
||||||
|
*/
|
||||||
|
relativePriority: FilterPriority;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a given room should be visible under this
|
* Determines if a given room should be visible under this
|
||||||
* condition.
|
* condition.
|
||||||
|
@ -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, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +29,11 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
|
|||||||
super();
|
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 search(): string {
|
public get search(): string {
|
||||||
return this._search;
|
return this._search;
|
||||||
}
|
}
|
||||||
|
@ -45,3 +45,63 @@ export function arrayDiff<T>(a: T[], b: T[]): { added: T[], removed: T[] } {
|
|||||||
removed: a.filter(i => !b.includes(i)),
|
removed: a.filter(i => !b.includes(i)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions to perform LINQ-like queries on arrays.
|
||||||
|
*/
|
||||||
|
export class ArrayUtil<T> {
|
||||||
|
/**
|
||||||
|
* Create a new array helper.
|
||||||
|
* @param a The array to help. Can be modified in-place.
|
||||||
|
*/
|
||||||
|
constructor(private a: T[]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of this array, after all appropriate alterations.
|
||||||
|
*/
|
||||||
|
public get value(): T[] {
|
||||||
|
return this.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups an array by keys.
|
||||||
|
* @param fn The key-finding function.
|
||||||
|
* @returns This.
|
||||||
|
*/
|
||||||
|
public groupBy<K>(fn: (a: T) => K): GroupedArray<K, T> {
|
||||||
|
const obj = this.a.reduce((rv: Map<K, T[]>, val: T) => {
|
||||||
|
const k = fn(val);
|
||||||
|
if (!rv.has(k)) rv.set(k, []);
|
||||||
|
rv.get(k).push(val);
|
||||||
|
return rv;
|
||||||
|
}, new Map<K, T[]>());
|
||||||
|
return new GroupedArray(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions to perform LINQ-like queries on groups (maps).
|
||||||
|
*/
|
||||||
|
export class GroupedArray<K, T> {
|
||||||
|
/**
|
||||||
|
* Creates a new group helper.
|
||||||
|
* @param val The group to help. Can be modified in-place.
|
||||||
|
*/
|
||||||
|
constructor(private val: Map<K, T[]>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orders the grouping into an array using the provided key order.
|
||||||
|
* @param keyOrder The key order.
|
||||||
|
* @returns An array helper of the result.
|
||||||
|
*/
|
||||||
|
public orderBy(keyOrder: K[]): ArrayUtil<T> {
|
||||||
|
const a: T[] = [];
|
||||||
|
for (const k of keyOrder) {
|
||||||
|
if (!this.val.has(k)) continue;
|
||||||
|
a.push(...this.val.get(k));
|
||||||
|
}
|
||||||
|
return new ArrayUtil(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
27
src/utils/enums.ts
Normal file
27
src/utils/enums.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the values for an enum.
|
||||||
|
* @param e The enum.
|
||||||
|
* @returns The enum values.
|
||||||
|
*/
|
||||||
|
export function getEnumValues<T>(e: any): T[] {
|
||||||
|
const keys = Object.keys(e);
|
||||||
|
return keys
|
||||||
|
.filter(k => ['string', 'number'].includes(typeof(e[k])))
|
||||||
|
.map(k => e[k]);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user