11/*
2- Copyright 2018, 2019 New Vector Ltd
3- Copyright 2020 The Matrix.org Foundation C.I.C.
2+ Copyright 2018-2021 The Matrix.org Foundation C.I.C.
43
54Licensed under the Apache License, Version 2.0 (the "License");
65you 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
1514limitations under the License.
1615*/
1716
18- import { MatrixClient } from "matrix-js-sdk/src/client" ;
17+ import { MatrixClient } from "matrix-js-sdk/src/client" ;
1918import SettingsStore from "../../settings/SettingsStore" ;
20- import { DefaultTagID , isCustomTag , OrderedDefaultTagIDs , RoomUpdateCause , TagID } from "./models" ;
21- import { Room } from "matrix-js-sdk/src/models/room" ;
22- import { IListOrderingMap , ITagMap , ITagSortingMap , ListAlgorithm , SortAlgorithm } from "./algorithms/models" ;
23- import { ActionPayload } from "../../dispatcher/payloads" ;
19+ import { DefaultTagID , isCustomTag , OrderedDefaultTagIDs , RoomUpdateCause , TagID } from "./models" ;
20+ import { Room } from "matrix-js-sdk/src/models/room" ;
21+ import { IListOrderingMap , ITagMap , ITagSortingMap , ListAlgorithm , SortAlgorithm } from "./algorithms/models" ;
22+ import { ActionPayload } from "../../dispatcher/payloads" ;
2423import defaultDispatcher from "../../dispatcher/dispatcher" ;
25- import { readReceiptChangeIsFor } from "../../utils/read-receipts" ;
26- import { FILTER_CHANGED , IFilterCondition } from "./filters/IFilterCondition" ;
27- import { TagWatcher } from "./TagWatcher" ;
24+ import { readReceiptChangeIsFor } from "../../utils/read-receipts" ;
25+ import { FILTER_CHANGED , FilterKind , IFilterCondition } from "./filters/IFilterCondition" ;
26+ import { TagWatcher } from "./TagWatcher" ;
2827import RoomViewStore from "../RoomViewStore" ;
29- import { Algorithm , LIST_UPDATED_EVENT } from "./algorithms/Algorithm" ;
30- import { EffectiveMembership , getEffectiveMembership } from "../../utils/membership" ;
31- import { isNullOrUndefined } from "matrix-js-sdk/src/utils" ;
28+ import { Algorithm , LIST_UPDATED_EVENT } from "./algorithms/Algorithm" ;
29+ import { EffectiveMembership , getEffectiveMembership } from "../../utils/membership" ;
30+ import { isNullOrUndefined } from "matrix-js-sdk/src/utils" ;
3231import RoomListLayoutStore from "./RoomListLayoutStore" ;
33- import { MarkedExecution } from "../../utils/MarkedExecution" ;
34- import { AsyncStoreWithClient } from "../AsyncStoreWithClient" ;
35- import { NameFilterCondition } from "./filters/NameFilterCondition" ;
36- import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore" ;
37- import { VisibilityProvider } from "./filters/VisibilityProvider" ;
38- import { SpaceWatcher } from "./SpaceWatcher" ;
32+ import { MarkedExecution } from "../../utils/MarkedExecution" ;
33+ import { AsyncStoreWithClient } from "../AsyncStoreWithClient" ;
34+ import { NameFilterCondition } from "./filters/NameFilterCondition" ;
35+ import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore" ;
36+ import { VisibilityProvider } from "./filters/VisibilityProvider" ;
37+ import { SpaceWatcher } from "./SpaceWatcher" ;
3938
4039interface IState {
4140 tagsEnabled ?: boolean ;
@@ -57,6 +56,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
5756 private initialListsGenerated = false ;
5857 private algorithm = new Algorithm ( ) ;
5958 private filterConditions : IFilterCondition [ ] = [ ] ;
59+ private prefilterConditions : IFilterCondition [ ] = [ ] ;
6060 private tagWatcher : TagWatcher ;
6161 private spaceWatcher : SpaceWatcher ;
6262 private updateFn = new MarkedExecution ( ( ) => {
@@ -104,6 +104,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
104104 public async resetStore ( ) {
105105 await this . reset ( ) ;
106106 this . filterConditions = [ ] ;
107+ this . prefilterConditions = [ ] ;
107108 this . initialListsGenerated = false ;
108109 this . setupWatchers ( ) ;
109110
@@ -435,6 +436,39 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
435436 }
436437 }
437438
439+ private async recalculatePrefiltering ( ) {
440+ if ( ! this . algorithm ) return ;
441+ if ( ! this . algorithm . hasTagSortingMap ) return ; // we're still loading
442+
443+ if ( SettingsStore . getValue ( "advancedRoomListLogging" ) ) {
444+ // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
445+ console . log ( "Calculating new prefiltered room list" ) ;
446+ }
447+
448+ // Inhibit updates because we're about to lie heavily to the algorithm
449+ this . algorithm . updatesInhibited = true ;
450+
451+ // Figure out which rooms are about to be valid, and the state of affairs
452+ const rooms = this . getPlausibleRooms ( ) ;
453+ const currentSticky = this . algorithm . stickyRoom ;
454+ const stickyIsStillPresent = currentSticky && rooms . includes ( currentSticky ) ;
455+
456+ // Reset the sticky room before resetting the known rooms so the algorithm
457+ // doesn't freak out.
458+ await this . algorithm . setStickyRoom ( null ) ;
459+ await this . algorithm . setKnownRooms ( rooms ) ;
460+
461+ // Set the sticky room back, if needed, now that we have updated the store.
462+ // This will use relative stickyness to the new room set.
463+ if ( stickyIsStillPresent ) {
464+ await this . algorithm . setStickyRoom ( currentSticky ) ;
465+ }
466+
467+ // Finally, mark an update and resume updates from the algorithm
468+ this . updateFn . mark ( ) ;
469+ this . algorithm . updatesInhibited = false ;
470+ }
471+
438472 public async setTagSorting ( tagId : TagID , sort : SortAlgorithm ) {
439473 await this . setAndPersistTagSorting ( tagId , sort ) ;
440474 this . updateFn . trigger ( ) ;
@@ -557,6 +591,34 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
557591 this . updateFn . trigger ( ) ;
558592 } ;
559593
594+ private onPrefilterUpdated = async ( ) => {
595+ await this . recalculatePrefiltering ( ) ;
596+ this . updateFn . trigger ( ) ;
597+ } ;
598+
599+ private getPlausibleRooms ( ) : Room [ ] {
600+ if ( ! this . matrixClient ) return [ ] ;
601+
602+ let rooms = [
603+ ...this . matrixClient . getVisibleRooms ( ) ,
604+ // also show space invites in the room list
605+ ...this . matrixClient . getRooms ( ) . filter ( r => r . isSpaceRoom ( ) && r . getMyMembership ( ) === "invite" ) ,
606+ ] . filter ( r => VisibilityProvider . instance . isRoomVisible ( r ) ) ;
607+
608+ if ( this . prefilterConditions . length > 0 ) {
609+ rooms = rooms . filter ( r => {
610+ for ( const filter of this . prefilterConditions ) {
611+ if ( ! filter . isVisible ( r ) ) {
612+ return false ;
613+ }
614+ }
615+ return true ;
616+ } ) ;
617+ }
618+
619+ return rooms ;
620+ }
621+
560622 /**
561623 * Regenerates the room whole room list, discarding any previous results.
562624 *
@@ -568,11 +630,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
568630 public async regenerateAllLists ( { trigger = true } ) {
569631 console . warn ( "Regenerating all room lists" ) ;
570632
571- const rooms = [
572- ...this . matrixClient . getVisibleRooms ( ) ,
573- // also show space invites in the room list
574- ...this . matrixClient . getRooms ( ) . filter ( r => r . isSpaceRoom ( ) && r . getMyMembership ( ) === "invite" ) ,
575- ] . filter ( r => VisibilityProvider . instance . isRoomVisible ( r ) ) ;
633+ const rooms = this . getPlausibleRooms ( ) ;
576634
577635 const customTags = new Set < TagID > ( ) ;
578636 if ( this . state . tagsEnabled ) {
@@ -601,32 +659,58 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
601659 if ( trigger ) this . updateFn . trigger ( ) ;
602660 }
603661
662+ /**
663+ * Adds a filter condition to the room list store. Filters may be applied async,
664+ * and thus might not cause an update to the store immediately.
665+ * @param {IFilterCondition } filter The filter condition to add.
666+ */
604667 public addFilter ( filter : IFilterCondition ) : void {
605668 if ( SettingsStore . getValue ( "advancedRoomListLogging" ) ) {
606669 // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
607670 console . log ( "Adding filter condition:" , filter ) ;
608671 }
609- this . filterConditions . push ( filter ) ;
610- if ( this . algorithm ) {
611- this . algorithm . addFilterCondition ( filter ) ;
672+ let promise = Promise . resolve ( ) ;
673+ if ( filter . kind === FilterKind . Prefilter ) {
674+ filter . on ( FILTER_CHANGED , this . onPrefilterUpdated ) ;
675+ this . prefilterConditions . push ( filter ) ;
676+ promise = this . recalculatePrefiltering ( ) ;
677+ } else {
678+ this . filterConditions . push ( filter ) ;
679+ if ( this . algorithm ) {
680+ this . algorithm . addFilterCondition ( filter ) ;
681+ }
612682 }
613- this . updateFn . trigger ( ) ;
683+ promise . then ( ( ) => this . updateFn . trigger ( ) ) ;
614684 }
615685
686+ /**
687+ * Removes a filter condition from the room list store. If the filter was
688+ * not previously added to the room list store, this will no-op. The effects
689+ * of removing a filter may be applied async and therefore might not cause
690+ * an update right away.
691+ * @param {IFilterCondition } filter The filter condition to remove.
692+ */
616693 public removeFilter ( filter : IFilterCondition ) : void {
617694 if ( SettingsStore . getValue ( "advancedRoomListLogging" ) ) {
618695 // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
619696 console . log ( "Removing filter condition:" , filter ) ;
620697 }
621- const idx = this . filterConditions . indexOf ( filter ) ;
698+ let promise = Promise . resolve ( ) ;
699+ let idx = this . filterConditions . indexOf ( filter ) ;
622700 if ( idx >= 0 ) {
623701 this . filterConditions . splice ( idx , 1 ) ;
624702
625703 if ( this . algorithm ) {
626704 this . algorithm . removeFilterCondition ( filter ) ;
627705 }
628706 }
629- this . updateFn . trigger ( ) ;
707+ idx = this . prefilterConditions . indexOf ( filter ) ;
708+ if ( idx >= 0 ) {
709+ filter . off ( FILTER_CHANGED , this . onPrefilterUpdated ) ;
710+ this . prefilterConditions . splice ( idx , 1 ) ;
711+ promise = this . recalculatePrefiltering ( ) ;
712+ }
713+ promise . then ( ( ) => this . updateFn . trigger ( ) ) ;
630714 }
631715
632716 /**
0 commit comments