UNPKG

matrix-react-sdk

Version:
958 lines (755 loc) 114 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.RoomListStoreClass = exports.LISTS_UPDATE_EVENT = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _SettingsStore = _interopRequireDefault(require("../../settings/SettingsStore")); var _models = require("./models"); var _models2 = require("./algorithms/models"); var _dispatcher = _interopRequireDefault(require("../../dispatcher/dispatcher")); var _readReceipts = require("../../utils/read-receipts"); var _IFilterCondition = require("./filters/IFilterCondition"); var _TagWatcher = require("./TagWatcher"); var _RoomViewStore = _interopRequireDefault(require("../RoomViewStore")); var _Algorithm = require("./algorithms/Algorithm"); var _membership = require("../../utils/membership"); var _utils = require("matrix-js-sdk/src/utils"); var _RoomListLayoutStore = _interopRequireDefault(require("./RoomListLayoutStore")); var _MarkedExecution = require("../../utils/MarkedExecution"); var _AsyncStoreWithClient = require("../AsyncStoreWithClient"); var _NameFilterCondition = require("./filters/NameFilterCondition"); var _RoomNotificationStateStore = require("../notifications/RoomNotificationStateStore"); var _VisibilityProvider = require("./filters/VisibilityProvider"); var _SpaceWatcher = require("./SpaceWatcher"); /* 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. 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. */ /** * The event/channel which is called when the room lists have been changed. Raised * with one argument: the instance of the store. */ const LISTS_UPDATE_EVENT = "lists_update"; exports.LISTS_UPDATE_EVENT = LISTS_UPDATE_EVENT; class RoomListStoreClass extends _AsyncStoreWithClient.AsyncStoreWithClient /*:: <IState>*/ { /** * Set to true if you're running tests on the store. Should not be touched in * any other environment. */ constructor() { super(_dispatcher.default); (0, _defineProperty2.default)(this, "initialListsGenerated", false); (0, _defineProperty2.default)(this, "algorithm", new _Algorithm.Algorithm()); (0, _defineProperty2.default)(this, "filterConditions", []); (0, _defineProperty2.default)(this, "prefilterConditions", []); (0, _defineProperty2.default)(this, "tagWatcher", void 0); (0, _defineProperty2.default)(this, "spaceWatcher", void 0); (0, _defineProperty2.default)(this, "updateFn", new _MarkedExecution.MarkedExecution(() => { for (const tagId of Object.keys(this.orderedLists)) { _RoomNotificationStateStore.RoomNotificationStateStore.instance.getListState(tagId).setRooms(this.orderedLists[tagId]); } this.emit(LISTS_UPDATE_EVENT); })); (0, _defineProperty2.default)(this, "watchedSettings", ['feature_custom_tags', 'advancedRoomListLogging' // TODO: Remove watch: https://github.com/vector-im/element-web/issues/14602 ]); (0, _defineProperty2.default)(this, "onAlgorithmListUpdated", () => { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log("Underlying algorithm has triggered a list update - marking"); } this.updateFn.mark(); }); (0, _defineProperty2.default)(this, "onAlgorithmFilterUpdated", () => { // The filter can happen off-cycle, so trigger an update. The filter will have // already caused a mark. this.updateFn.trigger(); }); (0, _defineProperty2.default)(this, "onPrefilterUpdated", async () => { await this.recalculatePrefiltering(); this.updateFn.trigger(); }); this.checkLoggingEnabled(); for (const settingName of this.watchedSettings) _SettingsStore.default.monitorSetting(settingName, null); _RoomViewStore.default.addListener(() => this.handleRVSUpdate({})); this.algorithm.on(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmFilterUpdated); this.setupWatchers(); } setupWatchers() { if (_SettingsStore.default.getValue("feature_spaces")) { this.spaceWatcher = new _SpaceWatcher.SpaceWatcher(this); } else { this.tagWatcher = new _TagWatcher.TagWatcher(this); } } get unfilteredLists() /*: ITagMap*/ { if (!this.algorithm) return {}; // No tags yet. return this.algorithm.getUnfilteredRooms(); } get orderedLists() /*: ITagMap*/ { if (!this.algorithm) return {}; // No tags yet. return this.algorithm.getOrderedRooms(); } // Intended for test usage async resetStore() { await this.reset(); this.filterConditions = []; this.prefilterConditions = []; this.initialListsGenerated = false; this.setupWatchers(); this.algorithm.off(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.off(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmListUpdated); this.algorithm = new _Algorithm.Algorithm(); this.algorithm.on(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmListUpdated); // Reset state without causing updates as the client will have been destroyed // and downstream code will throw NPE errors. await this.reset(null, true); } // Public for test usage. Do not call this. async makeReady(forcedClient /*: MatrixClient*/ ) { if (forcedClient) { this.readyStore.useUnitTestClient(forcedClient); } this.checkLoggingEnabled(); // Update any settings here, as some may have happened before we were logically ready. // Update any settings here, as some may have happened before we were logically ready. console.log("Regenerating room lists: Startup"); await this.readAndCacheSettingsFromStore(); await this.regenerateAllLists({ trigger: false }); await this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed this.updateFn.mark(); // we almost certainly want to trigger an update. this.updateFn.trigger(); } checkLoggingEnabled() { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { console.warn("Advanced room list logging is enabled"); } } async readAndCacheSettingsFromStore() { const tagsEnabled = _SettingsStore.default.getValue("feature_custom_tags"); await this.updateState({ tagsEnabled }); await this.updateAlgorithmInstances(); } /** * Handles suspected RoomViewStore changes. * @param trigger Set to false to prevent a list update from being sent. Should only * be used if the calling code will manually trigger the update. */ async handleRVSUpdate({ trigger = true }) { if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = _RoomViewStore.default.getRoomId(); if (!activeRoomId && this.algorithm.stickyRoom) { await this.algorithm.setStickyRoom(null); } else if (activeRoomId) { const activeRoom = this.matrixClient.getRoom(activeRoomId); if (!activeRoom) { console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); await this.algorithm.setStickyRoom(null); } else if (activeRoom !== this.algorithm.stickyRoom) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`Changing sticky room to ${activeRoomId}`); } await this.algorithm.setStickyRoom(activeRoom); } } if (trigger) this.updateFn.trigger(); } async onReady() /*: Promise<any>*/ { await this.makeReady(); } async onNotReady() /*: Promise<any>*/ { await this.resetStore(); } async onAction(payload /*: ActionPayload*/ ) { // If we're not remotely ready, don't even bother scheduling the dispatch handling. // This is repeated in the handler just in case things change between a decision here and // when the timer fires. const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!logicallyReady) return; // When we're running tests we can't reliably use setImmediate out of timing concerns. // As such, we use a more synchronous model. if (RoomListStoreClass.TEST_MODE) { await this.onDispatchAsync(payload); return; } // We do this to intentionally break out of the current event loop task, allowing // us to instead wait for a more convenient time to run our updates. setImmediate(() => this.onDispatchAsync(payload)); } async onDispatchAsync(payload /*: ActionPayload*/ ) { // Everything here requires a MatrixClient or some sort of logical readiness. const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!logicallyReady) return; if (payload.action === 'setting_updated') { if (this.watchedSettings.includes(payload.settingName)) { // TODO: Remove with https://github.com/vector-im/element-web/issues/14602 if (payload.settingName === "advancedRoomListLogging") { // Log when the setting changes so we know when it was turned on in the rageshake const enabled = _SettingsStore.default.getValue("advancedRoomListLogging"); console.warn("Advanced room list logging is enabled? " + enabled); return; } console.log("Regenerating room lists: Settings changed"); await this.readAndCacheSettingsFromStore(); await this.regenerateAllLists({ trigger: false }); // regenerate the lists now this.updateFn.trigger(); } } if (!this.algorithm) { // This shouldn't happen because `initialListsGenerated` implies we have an algorithm. throw new Error("Room list store has no algorithm to process dispatcher update with"); } if (payload.action === 'MatrixActions.Room.receipt') { // First see if the receipt event is for our own user. If it was, trigger // a room update (we probably read the room on a different device). if ((0, _readReceipts.readReceiptChangeIsFor)(payload.event, this.matrixClient)) { const room = payload.room; if (!room) { console.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); } await this.handleRoomUpdate(room, _models.RoomUpdateCause.ReadReceipt); this.updateFn.trigger(); return; } } else if (payload.action === 'MatrixActions.Room.tags') { const roomPayload = payload; // TODO: Type out the dispatcher types if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`); } await this.handleRoomUpdate(roomPayload.room, _models.RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.Room.timeline') { const eventPayload = payload; // TODO: Type out the dispatcher types // Ignore non-live events (backfill) if (!eventPayload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent) return; const roomId = eventPayload.event.getRoomId(); const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom /*: Room*/ ) => { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + ` in ${updatedRoom.roomId}`); } if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); } const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']); if (newRoom) { // If we have the new room, then the new room check will have seen the predecessor // and did the required updates, so do nothing here. return; } } await this.handleRoomUpdate(updatedRoom, _models.RoomUpdateCause.Timeline); this.updateFn.trigger(); }; if (!room) { console.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); console.warn(`Queuing failed room update for retry as a result.`); setTimeout(async () => { const updatedRoom = this.matrixClient.getRoom(roomId); await tryUpdate(updatedRoom); }, 100); // 100ms should be enough for the room to show up return; } else { await tryUpdate(room); } } else if (payload.action === 'MatrixActions.Event.decrypted') { const eventPayload = payload; // TODO: Type out the dispatcher types const roomId = eventPayload.event.getRoomId(); if (!roomId) { return; } const room = this.matrixClient.getRoom(roomId); if (!room) { console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); return; } if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`); } await this.handleRoomUpdate(room, _models.RoomUpdateCause.Timeline); this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { const eventPayload = payload; // TODO: Type out the dispatcher types if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Received updated DM map`); } const dmMap = eventPayload.event.getContent(); for (const userId of Object.keys(dmMap)) { const roomIds = dmMap[userId]; for (const roomId of roomIds) { const room = this.matrixClient.getRoom(roomId); if (!room) { console.warn(`${roomId} was found in DMs but the room is not in the store`); continue; } // We expect this RoomUpdateCause to no-op if there's no change, and we don't expect // the user to have hundreds of rooms to update in one event. As such, we just hammer // away at updates until the problem is solved. If we were expecting more than a couple // of rooms to be updated at once, we would consider batching the rooms up. await this.handleRoomUpdate(room, _models.RoomUpdateCause.PossibleTagChange); } } this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.Room.myMembership') { const membershipPayload = payload; // TODO: Type out the dispatcher types const oldMembership = (0, _membership.getEffectiveMembership)(membershipPayload.oldMembership); const newMembership = (0, _membership.getEffectiveMembership)(membershipPayload.membership); if (oldMembership !== _membership.EffectiveMembership.Join && newMembership === _membership.EffectiveMembership.Join) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); } // If we're joining an upgraded room, we'll want to make sure we don't proliferate // the dead room in the list. const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", ""); if (createEvent && createEvent.getContent()['predecessor']) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Room has a predecessor`); } const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']); if (prevRoom) { const isSticky = this.algorithm.stickyRoom === prevRoom; if (isSticky) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`); } await this.algorithm.setStickyRoom(null); } // Note: we hit the algorithm instead of our handleRoomUpdate() function to // avoid redundant updates. if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Removing previous room from room list`); } await this.algorithm.handleRoomUpdate(prevRoom, _models.RoomUpdateCause.RoomRemoved); } } if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Adding new room to room list`); } await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; } if (oldMembership !== _membership.EffectiveMembership.Invite && newMembership === _membership.EffectiveMembership.Invite) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`); } await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; } // If it's not a join, it's transitioning into a different list (possibly historical) if (oldMembership !== newMembership) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); } await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); return; } } } async handleRoomUpdate(room /*: Room*/ , cause /*: RoomUpdateCause*/ ) /*: Promise<any>*/ { if (cause === _models.RoomUpdateCause.NewRoom && room.getMyMembership() === "invite") { // Let the visibility provider know that there is a new invited room. It would be nice // if this could just be an event that things listen for but the point of this is that // we delay doing anything about this room until the VoipUserMapper had had a chance // to do the things it needs to do to decide if we should show this room or not, so // an even wouldn't et us do that. await _VisibilityProvider.VisibilityProvider.instance.onNewInvitedRoom(room); } if (!_VisibilityProvider.VisibilityProvider.instance.isRoomVisible(room)) { return; // don't do anything on rooms that aren't visible } if (cause === _models.RoomUpdateCause.NewRoom && !this.prefilterConditions.every(c => c.isVisible(room))) { return; // don't do anything on new rooms which ought not to be shown } const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause); if (shouldUpdate) { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); } this.updateFn.mark(); } } async recalculatePrefiltering() { if (!this.algorithm) return; if (!this.algorithm.hasTagSortingMap) return; // we're still loading if (_SettingsStore.default.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; } async setTagSorting(tagId /*: TagID*/ , sort /*: SortAlgorithm*/ ) { await this.setAndPersistTagSorting(tagId, sort); this.updateFn.trigger(); } async setAndPersistTagSorting(tagId /*: TagID*/ , sort /*: SortAlgorithm*/ ) { await this.algorithm.setTagSorting(tagId, sort); // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 localStorage.setItem(`mx_tagSort_${tagId}`, sort); } getTagSorting(tagId /*: TagID*/ ) /*: SortAlgorithm*/ { return this.algorithm.getTagSorting(tagId); } // noinspection JSMethodCanBeStatic getStoredTagSorting(tagId /*: TagID*/ ) /*: SortAlgorithm*/ { // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 return localStorage.getItem(`mx_tagSort_${tagId}`); } // logic must match calculateListOrder calculateTagSorting(tagId /*: TagID*/ ) /*: SortAlgorithm*/ { const isDefaultRecent = tagId === _models.DefaultTagID.Invite || tagId === _models.DefaultTagID.DM; const defaultSort = isDefaultRecent ? _models2.SortAlgorithm.Recent : _models2.SortAlgorithm.Alphabetic; const settingAlphabetical = _SettingsStore.default.getValue("RoomList.orderAlphabetically", null, true); const definedSort = this.getTagSorting(tagId); const storedSort = this.getStoredTagSorting(tagId); // We use the following order to determine which of the 4 flags to use: // Stored > Settings > Defined > Default let tagSort = defaultSort; if (storedSort) { tagSort = storedSort; } else if (!(0, _utils.isNullOrUndefined)(settingAlphabetical)) { tagSort = settingAlphabetical ? _models2.SortAlgorithm.Alphabetic : _models2.SortAlgorithm.Recent; } else if (definedSort) { tagSort = definedSort; } // else default (already set) return tagSort; } async setListOrder(tagId /*: TagID*/ , order /*: ListAlgorithm*/ ) { await this.setAndPersistListOrder(tagId, order); this.updateFn.trigger(); } async setAndPersistListOrder(tagId /*: TagID*/ , order /*: ListAlgorithm*/ ) { await this.algorithm.setListOrdering(tagId, order); // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 localStorage.setItem(`mx_listOrder_${tagId}`, order); } getListOrder(tagId /*: TagID*/ ) /*: ListAlgorithm*/ { return this.algorithm.getListOrdering(tagId); } // noinspection JSMethodCanBeStatic getStoredListOrder(tagId /*: TagID*/ ) /*: ListAlgorithm*/ { // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 return localStorage.getItem(`mx_listOrder_${tagId}`); } // logic must match calculateTagSorting calculateListOrder(tagId /*: TagID*/ ) /*: ListAlgorithm*/ { const defaultOrder = _models2.ListAlgorithm.Natural; const settingImportance = _SettingsStore.default.getValue("RoomList.orderByImportance", null, true); const definedOrder = this.getListOrder(tagId); const storedOrder = this.getStoredListOrder(tagId); // We use the following order to determine which of the 4 flags to use: // Stored > Settings > Defined > Default let listOrder = defaultOrder; if (storedOrder) { listOrder = storedOrder; } else if (!(0, _utils.isNullOrUndefined)(settingImportance)) { listOrder = settingImportance ? _models2.ListAlgorithm.Importance : _models2.ListAlgorithm.Natural; } else if (definedOrder) { listOrder = definedOrder; } // else default (already set) return listOrder; } async updateAlgorithmInstances() { // We'll require an update, so mark for one. Marking now also prevents the calls // to setTagSorting and setListOrder from causing triggers. this.updateFn.mark(); for (const tag of Object.keys(this.orderedLists)) { const definedSort = this.getTagSorting(tag); const definedOrder = this.getListOrder(tag); const tagSort = this.calculateTagSorting(tag); const listOrder = this.calculateListOrder(tag); if (tagSort !== definedSort) { await this.setAndPersistTagSorting(tag, tagSort); } if (listOrder !== definedOrder) { await this.setAndPersistListOrder(tag, listOrder); } } } getPlausibleRooms() /*: Room[]*/ { if (!this.matrixClient) return []; let rooms = this.matrixClient.getVisibleRooms().filter(r => _VisibilityProvider.VisibilityProvider.instance.isRoomVisible(r)); // if spaces are enabled only consider the prefilter conditions when there are no runtime conditions // for the search all spaces feature if (this.prefilterConditions.length > 0 && (!_SettingsStore.default.getValue("feature_spaces") || !this.filterConditions.length)) { 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. * * Note: This is only exposed externally for the tests. Do not call this from within * the app. * @param trigger Set to false to prevent a list update from being sent. Should only * be used if the calling code will manually trigger the update. */ async regenerateAllLists({ trigger = true }) { console.warn("Regenerating all room lists"); const rooms = this.getPlausibleRooms(); const customTags = new Set(); if (this.state.tagsEnabled) { for (const room of rooms) { if (!room.tags) continue; const tags = Object.keys(room.tags).filter(t => (0, _models.isCustomTag)(t)); tags.forEach(t => customTags.add(t)); } } const sorts /*: ITagSortingMap*/ = {}; const orders /*: IListOrderingMap*/ = {}; const allTags = [..._models.OrderedDefaultTagIDs, ...Array.from(customTags)]; for (const tagId of allTags) { sorts[tagId] = this.calculateTagSorting(tagId); orders[tagId] = this.calculateListOrder(tagId); _RoomListLayoutStore.default.instance.ensureLayoutExists(tagId); } await this.algorithm.populateTags(sorts, orders); await this.algorithm.setKnownRooms(rooms); this.initialListsGenerated = true; 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. */ async addFilter(filter /*: IFilterCondition*/ ) /*: Promise<void>*/ { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log("Adding filter condition:", filter); } let promise = Promise.resolve(); if (filter.kind === _IFilterCondition.FilterKind.Prefilter) { filter.on(_IFilterCondition.FILTER_CHANGED, this.onPrefilterUpdated); this.prefilterConditions.push(filter); promise = this.recalculatePrefiltering(); } else { this.filterConditions.push(filter); // Runtime filters with spaces disable prefiltering for the search all spaces feature if (_SettingsStore.default.getValue("feature_spaces")) { // this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below // this way the runtime filters are only evaluated on one dataset and not both. await this.recalculatePrefiltering(); } if (this.algorithm) { this.algorithm.addFilterCondition(filter); } } 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. */ removeFilter(filter /*: IFilterCondition*/ ) /*: void*/ { if (_SettingsStore.default.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 console.log("Removing filter condition:", filter); } let promise = Promise.resolve(); let idx = this.filterConditions.indexOf(filter); if (idx >= 0) { this.filterConditions.splice(idx, 1); if (this.algorithm) { this.algorithm.removeFilterCondition(filter); } // Runtime filters with spaces disable prefiltering for the search all spaces feature if (_SettingsStore.default.getValue("feature_spaces")) { promise = this.recalculatePrefiltering(); } } idx = this.prefilterConditions.indexOf(filter); if (idx >= 0) { filter.off(_IFilterCondition.FILTER_CHANGED, this.onPrefilterUpdated); this.prefilterConditions.splice(idx, 1); promise = this.recalculatePrefiltering(); } promise.then(() => this.updateFn.trigger()); } /** * Gets the first (and ideally only) name filter condition. If one isn't present, * this returns null. * @returns The first name filter condition, or null if none. */ getFirstNameFilterCondition() /*: NameFilterCondition | null*/ { for (const filter of this.filterConditions) { if (filter instanceof _NameFilterCondition.NameFilterCondition) { return filter; } } return null; } /** * Gets the tags for a room identified by the store. The returned set * should never be empty, and will contain DefaultTagID.Untagged if * the store is not aware of any tags. * @param room The room to get the tags for. * @returns The tags for the room. */ getTagsForRoom(room /*: Room*/ ) /*: TagID[]*/ { const algorithmTags = this.algorithm.getTagsForRoom(room); if (!algorithmTags) return [_models.DefaultTagID.Untagged]; return algorithmTags; } /** * Manually update a room with a given cause. This should only be used if the * room list store would otherwise be incapable of doing the update itself. Note * that this may race with the room list's regular operation. * @param {Room} room The room to update. * @param {RoomUpdateCause} cause The cause to update for. */ async manualRoomUpdate(room /*: Room*/ , cause /*: RoomUpdateCause*/ ) { await this.handleRoomUpdate(room, cause); this.updateFn.trigger(); } } exports.RoomListStoreClass = RoomListStoreClass; (0, _defineProperty2.default)(RoomListStoreClass, "TEST_MODE", false); class RoomListStore { static get instance() /*: RoomListStoreClass*/ { if (!RoomListStore.internalInstance) { RoomListStore.internalInstance = new RoomListStoreClass(); } return RoomListStore.internalInstance; } } exports.default = RoomListStore; (0, _defineProperty2.default)(RoomListStore, "internalInstance", void 0); window.mxRoomListStore = RoomListStore.instance; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yZXMvcm9vbS1saXN0L1Jvb21MaXN0U3RvcmUudHMiXSwibmFtZXMiOlsiTElTVFNfVVBEQVRFX0VWRU5UIiwiUm9vbUxpc3RTdG9yZUNsYXNzIiwiQXN5bmNTdG9yZVdpdGhDbGllbnQiLCJjb25zdHJ1Y3RvciIsImRlZmF1bHREaXNwYXRjaGVyIiwiQWxnb3JpdGhtIiwiTWFya2VkRXhlY3V0aW9uIiwidGFnSWQiLCJPYmplY3QiLCJrZXlzIiwib3JkZXJlZExpc3RzIiwiUm9vbU5vdGlmaWNhdGlvblN0YXRlU3RvcmUiLCJpbnN0YW5jZSIsImdldExpc3RTdGF0ZSIsInNldFJvb21zIiwiZW1pdCIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsImNvbnNvbGUiLCJsb2ciLCJ1cGRhdGVGbiIsIm1hcmsiLCJ0cmlnZ2VyIiwicmVjYWxjdWxhdGVQcmVmaWx0ZXJpbmciLCJjaGVja0xvZ2dpbmdFbmFibGVkIiwic2V0dGluZ05hbWUiLCJ3YXRjaGVkU2V0dGluZ3MiLCJtb25pdG9yU2V0dGluZyIsIlJvb21WaWV3U3RvcmUiLCJhZGRMaXN0ZW5lciIsImhhbmRsZVJWU1VwZGF0ZSIsImFsZ29yaXRobSIsIm9uIiwiTElTVF9VUERBVEVEX0VWRU5UIiwib25BbGdvcml0aG1MaXN0VXBkYXRlZCIsIkZJTFRFUl9DSEFOR0VEIiwib25BbGdvcml0aG1GaWx0ZXJVcGRhdGVkIiwic2V0dXBXYXRjaGVycyIsInNwYWNlV2F0Y2hlciIsIlNwYWNlV2F0Y2hlciIsInRhZ1dhdGNoZXIiLCJUYWdXYXRjaGVyIiwidW5maWx0ZXJlZExpc3RzIiwiZ2V0VW5maWx0ZXJlZFJvb21zIiwiZ2V0T3JkZXJlZFJvb21zIiwicmVzZXRTdG9yZSIsInJlc2V0IiwiZmlsdGVyQ29uZGl0aW9ucyIsInByZWZpbHRlckNvbmRpdGlvbnMiLCJpbml0aWFsTGlzdHNHZW5lcmF0ZWQiLCJvZmYiLCJtYWtlUmVhZHkiLCJmb3JjZWRDbGllbnQiLCJyZWFkeVN0b3JlIiwidXNlVW5pdFRlc3RDbGllbnQiLCJyZWFkQW5kQ2FjaGVTZXR0aW5nc0Zyb21TdG9yZSIsInJlZ2VuZXJhdGVBbGxMaXN0cyIsIndhcm4iLCJ0YWdzRW5hYmxlZCIsInVwZGF0ZVN0YXRlIiwidXBkYXRlQWxnb3JpdGhtSW5zdGFuY2VzIiwibWF0cml4Q2xpZW50IiwiYWN0aXZlUm9vbUlkIiwiZ2V0Um9vbUlkIiwic3RpY2t5Um9vbSIsInNldFN0aWNreVJvb20iLCJhY3RpdmVSb29tIiwiZ2V0Um9vbSIsIm9uUmVhZHkiLCJvbk5vdFJlYWR5Iiwib25BY3Rpb24iLCJwYXlsb2FkIiwibG9naWNhbGx5UmVhZHkiLCJURVNUX01PREUiLCJvbkRpc3BhdGNoQXN5bmMiLCJzZXRJbW1lZGlhdGUiLCJhY3Rpb24iLCJpbmNsdWRlcyIsImVuYWJsZWQiLCJFcnJvciIsImV2ZW50Iiwicm9vbSIsInJvb21JZCIsImhhbmRsZVJvb21VcGRhdGUiLCJSb29tVXBkYXRlQ2F1c2UiLCJSZWFkUmVjZWlwdCIsInJvb21QYXlsb2FkIiwiUG9zc2libGVUYWdDaGFuZ2UiLCJldmVudFBheWxvYWQiLCJpc0xpdmVFdmVudCIsImlzTGl2ZVVuZmlsdGVyZWRSb29tVGltZWxpbmVFdmVudCIsInRyeVVwZGF0ZSIsInVwZGF0ZWRSb29tIiwiZ2V0SWQiLCJnZXRUeXBlIiwiZ2V0U3RhdGVLZXkiLCJuZXdSb29tIiwiZ2V0Q29udGVudCIsIlRpbWVsaW5lIiwic2V0VGltZW91dCIsImV2ZW50X3R5cGUiLCJkbU1hcCIsInVzZXJJZCIsInJvb21JZHMiLCJtZW1iZXJzaGlwUGF5bG9hZCIsIm9sZE1lbWJlcnNoaXAiLCJuZXdNZW1iZXJzaGlwIiwibWVtYmVyc2hpcCIsIkVmZmVjdGl2ZU1lbWJlcnNoaXAiLCJKb2luIiwiY3JlYXRlRXZlbnQiLCJjdXJyZW50U3RhdGUiLCJnZXRTdGF0ZUV2ZW50cyIsInByZXZSb29tIiwiaXNTdGlja3kiLCJSb29tUmVtb3ZlZCIsIk5ld1Jvb20iLCJJbnZpdGUiLCJjYXVzZSIsImdldE15TWVtYmVyc2hpcCIsIlZpc2liaWxpdHlQcm92aWRlciIsIm9uTmV3SW52aXRlZFJvb20iLCJpc1Jvb21WaXNpYmxlIiwiZXZlcnkiLCJjIiwiaXNWaXNpYmxlIiwic2hvdWxkVXBkYXRlIiwibmFtZSIsImhhc1RhZ1NvcnRpbmdNYXAiLCJ1cGRhdGVzSW5oaWJpdGVkIiwicm9vbXMiLCJnZXRQbGF1c2libGVSb29tcyIsImN1cnJlbnRTdGlja3kiLCJzdGlja3lJc1N0aWxsUHJlc2VudCIsInNldEtub3duUm9vbXMiLCJzZXRUYWdTb3J0aW5nIiwic29ydCIsInNldEFuZFBlcnNpc3RUYWdTb3J0aW5nIiwibG9jYWxTdG9yYWdlIiwic2V0SXRlbSIsImdldFRhZ1NvcnRpbmciLCJnZXRTdG9yZWRUYWdTb3J0aW5nIiwiZ2V0SXRlbSIsImNhbGN1bGF0ZVRhZ1NvcnRpbmciLCJpc0RlZmF1bHRSZWNlbnQiLCJEZWZhdWx0VGFnSUQiLCJETSIsImRlZmF1bHRTb3J0IiwiU29ydEFsZ29yaXRobSIsIlJlY2VudCIsIkFscGhhYmV0aWMiLCJzZXR0aW5nQWxwaGFiZXRpY2FsIiwiZGVmaW5lZFNvcnQiLCJzdG9yZWRTb3J0IiwidGFnU29ydCIsInNldExpc3RPcmRlciIsIm9yZGVyIiwic2V0QW5kUGVyc2lzdExpc3RPcmRlciIsInNldExpc3RPcmRlcmluZyIsImdldExpc3RPcmRlciIsImdldExpc3RPcmRlcmluZyIsImdldFN0b3JlZExpc3RPcmRlciIsImNhbGN1bGF0ZUxpc3RPcmRlciIsImRlZmF1bHRPcmRlciIsIkxpc3RBbGdvcml0aG0iLCJOYXR1cmFsIiwic2V0dGluZ0ltcG9ydGFuY2UiLCJkZWZpbmVkT3JkZXIiLCJzdG9yZWRPcmRlciIsImxpc3RPcmRlciIsIkltcG9ydGFuY2UiLCJ0YWciLCJnZXRWaXNpYmxlUm9vbXMiLCJmaWx0ZXIiLCJyIiwibGVuZ3RoIiwiY3VzdG9tVGFncyIsIlNldCIsInN0YXRlIiwidGFncyIsInQiLCJmb3JFYWNoIiwiYWRkIiwic29ydHMiLCJvcmRlcnMiLCJhbGxUYWdzIiwiT3JkZXJlZERlZmF1bHRUYWdJRHMiLCJBcnJheSIsImZyb20iLCJSb29tTGlzdExheW91dFN0b3JlIiwiZW5zdXJlTGF5b3V0RXhpc3RzIiwicG9wdWxhdGVUYWdzIiwiYWRkRmlsdGVyIiwicHJvbWlzZSIsIlByb21pc2UiLCJyZXNvbHZlIiwia2luZCIsIkZpbHRlcktpbmQiLCJQcmVmaWx0ZXIiLCJvblByZWZpbHRlclVwZGF0ZWQiLCJwdXNoIiwiYWRkRmlsdGVyQ29uZGl0aW9uIiwidGhlbiIsInJlbW92ZUZpbHRlciIsImlkeCIsImluZGV4T2YiLCJzcGxpY2UiLCJyZW1vdmVGaWx0ZXJDb25kaXRpb24iLCJnZXRGaXJzdE5hbWVGaWx0ZXJDb25kaXRpb24iLCJOYW1lRmlsdGVyQ29uZGl0aW9uIiwiZ2V0VGFnc0ZvclJvb20iLCJhbGdvcml0aG1UYWdzIiwiVW50YWdnZWQiLCJtYW51YWxSb29tVXBkYXRlIiwiUm9vbUxpc3RTdG9yZSIsImludGVybmFsSW5zdGFuY2UiLCJ3aW5kb3ciLCJteFJvb21MaXN0U3RvcmUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBaUJBOztBQUNBOztBQUVBOztBQUVBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQXBDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBNEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sTUFBTUEsa0JBQWtCLEdBQUcsY0FBM0I7OztBQUVBLE1BQU1DLGtCQUFOLFNBQWlDQztBQUFqQztBQUE4RDtBQUNqRTtBQUNKO0FBQ0E7QUFDQTtBQXFCSUMsRUFBQUEsV0FBVyxHQUFHO0FBQ1YsVUFBTUMsbUJBQU47QUFEVSxpRUFsQmtCLEtBa0JsQjtBQUFBLHFEQWpCTSxJQUFJQyxvQkFBSixFQWlCTjtBQUFB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