diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 34682877e0..b3ceb1588f 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -17,7 +17,6 @@ limitations under the License. import * as React from "react"; import { createRef } from "react"; import classNames from "classnames"; -import { Room } from "matrix-js-sdk/src/models/room"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; @@ -27,8 +26,8 @@ import { Action } from "../../dispatcher/actions"; import RoomListStore from "../../stores/room-list/RoomListStore"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; -import {replaceableComponent} from "../../utils/replaceableComponent"; -import SpaceStore, {UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../stores/SpaceStore"; +import { replaceableComponent } from "../../utils/replaceableComponent"; +import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; interface IProps { isMinimized: boolean; @@ -42,7 +41,6 @@ interface IProps { interface IState { query: string; focused: boolean; - inSpaces: boolean; } @replaceableComponent("structures.RoomSearch") @@ -57,13 +55,11 @@ export default class RoomSearch extends React.PureComponent { this.state = { query: "", focused: false, - inSpaces: false, }; this.dispatcherRef = defaultDispatcher.register(this.onAction); // clear filter when changing spaces, in future we may wish to maintain a filter per-space SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput); - SpaceStore.instance.on(UPDATE_TOP_LEVEL_SPACES, this.onSpaces); } public componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { @@ -84,15 +80,8 @@ export default class RoomSearch extends React.PureComponent { public componentWillUnmount() { defaultDispatcher.unregister(this.dispatcherRef); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput); - SpaceStore.instance.off(UPDATE_TOP_LEVEL_SPACES, this.onSpaces); } - private onSpaces = (spaces: Room[]) => { - this.setState({ - inSpaces: spaces.length > 0, - }); - }; - private onAction = (payload: ActionPayload) => { if (payload.action === 'view_room' && payload.clear_search) { this.clearInput(); @@ -164,11 +153,6 @@ export default class RoomSearch extends React.PureComponent { 'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused, }); - let placeholder = _t("Filter"); - if (this.state.inSpaces) { - placeholder = _t("Filter all spaces"); - } - let icon = (
); @@ -182,7 +166,7 @@ export default class RoomSearch extends React.PureComponent { onBlur={this.onBlur} onChange={this.onChange} onKeyDown={this.onKeyDown} - placeholder={placeholder} + placeholder={_t("Filter")} autoComplete="off" /> ); diff --git a/src/components/views/rooms/RoomListNumResults.tsx b/src/components/views/rooms/RoomListNumResults.tsx index 01cbecf05d..4215e5644c 100644 --- a/src/components/views/rooms/RoomListNumResults.tsx +++ b/src/components/views/rooms/RoomListNumResults.tsx @@ -19,7 +19,6 @@ import React, {useState} from "react"; import { _t } from "../../../languageHandler"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; -import SpaceStore from "../../../stores/SpaceStore"; const RoomListNumResults: React.FC = () => { const [count, setCount] = useState(null); @@ -35,10 +34,7 @@ const RoomListNumResults: React.FC = () => { if (typeof count !== "number") return null; return
- { SpaceStore.instance.spacePanelSpaces.length - ? _t("%(count)s results in all spaces", { count }) - : _t("%(count)s results", { count }) - } + { _t("%(count)s results", { count }) }
; }; diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 36ab423885..411b0f9b5e 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -26,13 +26,11 @@ import {SpaceItem} from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; import SpaceStore, { - HOME_SPACE, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, } from "../../../stores/SpaceStore"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {SpaceNotificationState} from "../../../stores/notifications/SpaceNotificationState"; import NotificationBadge from "../rooms/NotificationBadge"; import { RovingAccessibleButton, @@ -40,13 +38,15 @@ import { RovingTabIndexProvider, } from "../../../accessibility/RovingTabIndex"; import {Key} from "../../../Keyboard"; +import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore"; +import {NotificationState} from "../../../stores/notifications/NotificationState"; interface IButtonProps { space?: Room; className?: string; selected?: boolean; tooltip?: string; - notificationState?: SpaceNotificationState; + notificationState?: NotificationState; isNarrow?: boolean; onClick(): void; } @@ -212,8 +212,8 @@ const SpacePanel = () => { className="mx_SpaceButton_home" onClick={() => SpaceStore.instance.setActiveSpace(null)} selected={!activeSpace} - tooltip={_t("Home")} - notificationState={SpaceStore.instance.getNotificationState(HOME_SPACE)} + tooltip={_t("All rooms")} + notificationState={RoomNotificationStateStore.instance.globalState} isNarrow={isPanelCollapsed} /> { invites.map(s => %(serverName)s": "Are you sure you want to remove %(serverName)s", "Remove server": "Remove server", @@ -2619,7 +2617,6 @@ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", "Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s", "Filter": "Filter", - "Filter all spaces": "Filter all spaces", "Clear filter": "Clear filter", "Filter rooms and people": "Filter rooms and people", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 22307d9f2e..50c5ea56f7 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -31,28 +31,23 @@ import {RoomNotificationStateStore} from "./notifications/RoomNotificationStateS import {DefaultTagID} from "./room-list/models"; import {EnhancedMap, mapDiff} from "../utils/maps"; import {setHasDiff} from "../utils/sets"; -import {objectDiff} from "../utils/objects"; -import {arrayHasDiff} from "../utils/arrays"; import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; import RoomViewStore from "./RoomViewStore"; -type SpaceKey = string | symbol; - interface IState {} const ACTIVE_SPACE_LS_KEY = "mx_active_space"; -export const HOME_SPACE = Symbol("home-space"); export const SUGGESTED_ROOMS = Symbol("suggested-rooms"); export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces"); export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); -// Space Room ID/HOME_SPACE will be emitted when a Space's children change +// Space Room ID will be emitted when a Space's children change const MAX_SUGGESTED_ROOMS = 20; -const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "home_space"}`; +const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`; const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms] return arr.reduce((result, room: Room) => { @@ -83,15 +78,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // The spaces representing the roots of the various tree-like hierarchies private rootSpaces: Room[] = []; - // The list of rooms not present in any currently joined spaces - private orphanedRooms = new Set(); // Map from room ID to set of spaces which list it as a child private parentMap = new EnhancedMap>(); - // Map from space key to SpaceNotificationState instance representing that space - private notificationStateMap = new Map(); + // Map from spaceId to SpaceNotificationState instance representing that space + private notificationStateMap = new Map(); // Map from space key to Set of room IDs that should be shown as part of that space's filter - private spaceFilteredRooms = new Map>(); - // The space currently selected in the Space Panel - if null then `Home` is selected + private spaceFilteredRooms = new Map>(); + // The space currently selected in the Space Panel - if null then All Rooms is selected private _activeSpace?: Room = null; private _suggestedRooms: ISpaceSummaryRoom[] = []; private _invitedSpaces = new Set(); @@ -227,7 +220,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } public getSpaceFilteredRoomIds = (space: Room | null): Set => { - return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set(); + if (!space) { + return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); + } + return this.spaceFilteredRooms.get(space.roomId) || new Set(); }; private rebuild = throttle(() => { @@ -258,7 +254,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); }); - const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren)); + const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren)); // somewhat algorithm to handle full-cycles const detachedNodes = new Set(spaces); @@ -299,7 +295,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // rootSpaces.push(space); // }); - this.orphanedRooms = new Set(orphanedRooms); this.rootSpaces = rootSpaces; this.parentMap = backrefs; @@ -320,25 +315,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.rebuild(); } - private showInHomeSpace = (room: Room) => { - if (room.isSpaceRoom()) return false; - return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space - || DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space - || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite) // show all favourites - }; - - // Update a given room due to its tag changing (e.g DM-ness or Fav-ness) - // This can only change whether it shows up in the HOME_SPACE or not - private onRoomUpdate = (room: Room) => { - if (this.showInHomeSpace(room)) { - this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId); - this.emit(HOME_SPACE); - } else if (!this.orphanedRooms.has(room.roomId)) { - this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId); - this.emit(HOME_SPACE); - } - }; - 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; @@ -352,16 +328,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const oldFilteredRooms = this.spaceFilteredRooms; this.spaceFilteredRooms = new Map(); - // put all room invites in the Home Space - const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite"); - this.spaceFilteredRooms.set(HOME_SPACE, new Set(invites.map(room => room.roomId))); - - visibleRooms.forEach(room => { - if (this.showInHomeSpace(room)) { - this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId); - } - }); - this.rootSpaces.forEach(s => { // traverse each space tree in DFS to build up the supersets as you go up, // reusing results from like subtrees. @@ -408,13 +374,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Update NotificationStates this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { if (roomIds.has(room.roomId)) { - // Don't aggregate notifications for DMs except in the Home Space - if (s !== HOME_SPACE) { - return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) - || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); - } - - return true; + return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) + || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); } return false; @@ -475,8 +436,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // TODO confirm this after implementing parenting behaviour if (room.isSpaceRoom()) { this.onSpaceUpdate(); - } else { - this.onRoomUpdate(room); } this.emit(room.roomId); break; @@ -489,38 +448,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } }; - private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => { - if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) { - // If the room was in favourites and now isn't or the opposite then update its position in the trees - const oldTags = lastEvent?.getContent()?.tags || {}; - const newTags = ev.getContent()?.tags || {}; - if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) { - this.onRoomUpdate(room); - } - } - } - - private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => { - if (ev.getType() === EventType.Direct) { - const lastContent = lastEvent.getContent(); - const content = ev.getContent(); - - const diff = objectDiff>(lastContent, content); - // filter out keys which changed by reference only by checking whether the sets differ - const changed = diff.changed.filter(k => arrayHasDiff(lastContent[k], content[k])); - // DM tag changes, refresh relevant rooms - new Set([...diff.added, ...diff.removed, ...changed]).forEach(roomId => { - const room = this.matrixClient?.getRoom(roomId); - if (room) { - this.onRoomUpdate(room); - } - }); - } - }; - protected async reset() { this.rootSpaces = []; - this.orphanedRooms = new Set(); this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); @@ -535,8 +464,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.removeListener("Room", this.onRoom); this.matrixClient.removeListener("Room.myMembership", this.onRoom); this.matrixClient.removeListener("RoomState.events", this.onRoomState); - this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData); - this.matrixClient.removeListener("accountData", this.onAccountData); } await this.reset(); } @@ -546,8 +473,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.on("Room", this.onRoom); this.matrixClient.on("Room.myMembership", this.onRoom); this.matrixClient.on("RoomState.events", this.onRoomState); - this.matrixClient.on("Room.accountData", this.onRoomAccountData); - this.matrixClient.on("accountData", this.onAccountData); await this.onSpaceUpdate(); // trigger an initial update @@ -602,7 +527,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } } - public getNotificationState(key: SpaceKey): SpaceNotificationState { + public getNotificationState(key: string): SpaceNotificationState { if (this.notificationStateMap.has(key)) { return this.notificationStateMap.get(key); } diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 6e9216423a..caab46a0c2 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -601,11 +601,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { let rooms = this.matrixClient.getVisibleRooms().filter(r => 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.getValue("feature_spaces") || !this.filterConditions.length) - ) { + if (this.prefilterConditions.length > 0) { rooms = rooms.filter(r => { for (const filter of this.prefilterConditions) { if (!filter.isVisible(r)) { @@ -679,10 +675,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { if (this.algorithm) { this.algorithm.addFilterCondition(filter); } - // Runtime filters with spaces disable prefiltering for the search all spaces effect - if (SettingsStore.getValue("feature_spaces")) { - promise = this.recalculatePrefiltering(); - } } promise.then(() => this.updateFn.trigger()); } @@ -706,10 +698,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { if (this.algorithm) { this.algorithm.removeFilterCondition(filter); - // Runtime filters with spaces disable prefiltering for the search all spaces effect - if (SettingsStore.getValue("feature_spaces")) { - promise = this.recalculatePrefiltering(); - } } } idx = this.prefilterConditions.indexOf(filter); diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index 13e1d83901..0b1b78bc75 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -24,26 +24,34 @@ import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore"; * Watches for changes in spaces to manage the filter on the provided RoomListStore */ export class SpaceWatcher { - private filter = new SpaceFilterCondition(); + private filter: SpaceFilterCondition; private activeSpace: Room = SpaceStore.instance.activeSpace; constructor(private store: RoomListStoreClass) { - this.updateFilter(); // get the filter into a consistent state - store.addFilter(this.filter); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated); } - private onSelectedSpaceUpdated = (activeSpace: Room) => { + private onSelectedSpaceUpdated = (activeSpace?: Room) => { this.activeSpace = activeSpace; - this.updateFilter(); + + if (this.filter) { + if (activeSpace) { + this.updateFilter(); + } else { + this.store.removeFilter(this.filter); + this.filter = null; + } + } else if (activeSpace) { + this.filter = new SpaceFilterCondition(); + this.updateFilter(); + this.store.addFilter(this.filter); + } }; private updateFilter = () => { - if (this.activeSpace) { - SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { - this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); - }); - } + SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { + this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); + }); this.filter.updateSpace(this.activeSpace); }; } diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 43bdcb3879..6a06bee0d8 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { IDestroyable } from "../../../utils/IDestroyable"; -import SpaceStore, {HOME_SPACE} from "../../SpaceStore"; +import SpaceStore from "../../SpaceStore"; import { setHasDiff } from "../../../utils/sets"; /** @@ -55,10 +55,12 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi } }; - private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE; + private getSpaceEventKey = (space: Room) => space.roomId; public updateSpace(space: Room) { - SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate); + if (this.space) { + SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate); + } SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate); this.onStoreUpdate(); // initial update from the change to the space }