diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 18ef247b5f0..7f66279158f 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -217,14 +217,6 @@ $roomListCollapsedWidth: 68px; } } - .mx_LeftPanel_roomListFilterCount { - font-size: $font-13px; - font-weight: $font-semi-bold; - margin-left: 12px; - margin-top: 14px; - margin-bottom: -4px; // to counteract the normal roomListWrapper margin-top - } - .mx_LeftPanel_roomListWrapper { // Make the y-scrollbar more responsive padding-right: 2px; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 45e0c46bf00..fb90941ee41 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -14,35 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentType, createRef, ReactComponentElement, RefObject } from "react"; -import { Room } from "matrix-js-sdk/src/models/room"; -import { RoomType, EventType } from "matrix-js-sdk/src/@types/event"; import * as fbEmitter from "fbemitter"; +import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import React, { ComponentType, createRef, ReactComponentElement, RefObject } from "react"; -import { _t, _td } from "../../../languageHandler"; import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; -import ResizeNotifier from "../../../utils/ResizeNotifier"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; -import { RoomViewStore } from "../../../stores/RoomViewStore"; -import { ITagMap } from "../../../stores/room-list/algorithms/models"; -import { DefaultTagID, TagID } from "../../../stores/room-list/models"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; +import { Action } from "../../../dispatcher/actions"; import defaultDispatcher from "../../../dispatcher/dispatcher"; -import RoomSublist, { IAuxButtonProps } from "./RoomSublist"; import { ActionPayload } from "../../../dispatcher/payloads"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import ExtraTile from "./ExtraTile"; -import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; +import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { useEventEmitterState } from "../../../hooks/useEventEmitter"; +import { _t, _td } from "../../../languageHandler"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import PosthogTrackers from "../../../PosthogTrackers"; +import SettingsStore from "../../../settings/SettingsStore"; +import { UIComponent } from "../../../settings/UIFeature"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays"; -import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; -import IconizedContextMenu, { - IconizedContextMenuOption, - IconizedContextMenuOptionList, -} from "../context_menus/IconizedContextMenu"; -import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; -import { BetaPill } from "../beta/BetaCard"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { ITagMap } from "../../../stores/room-list/algorithms/models"; +import { DefaultTagID, TagID } from "../../../stores/room-list/models"; +import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; +import { RoomViewStore } from "../../../stores/RoomViewStore"; import { isMetaSpace, ISuggestedRoom, @@ -51,17 +46,21 @@ import { UPDATE_SELECTED_SPACE, UPDATE_SUGGESTED_ROOMS, } from "../../../stores/spaces"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays"; +import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; +import ResizeNotifier from "../../../utils/ResizeNotifier"; import { shouldShowSpaceInvite, showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; +import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; import RoomAvatar from "../avatars/RoomAvatar"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../../settings/UIFeature"; +import { BetaPill } from "../beta/BetaCard"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import SettingsStore from "../../../settings/SettingsStore"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import ExtraTile from "./ExtraTile"; +import RoomSublist, { IAuxButtonProps } from "./RoomSublist"; interface IProps { onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; @@ -76,7 +75,6 @@ interface IProps { interface IState { sublists: ITagMap; - isNameFiltering: boolean; currentRoomId?: string; suggestedRooms: ISuggestedRoom[]; } @@ -403,7 +401,6 @@ export default class RoomList extends React.PureComponent { this.state = { sublists: {}, - isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(), suggestedRooms: SpaceStore.instance.suggestedRooms, }; } @@ -480,8 +477,7 @@ export default class RoomList extends React.PureComponent { const previousListIds = Object.keys(this.state.sublists); const newListIds = Object.keys(newLists); - const isNameFiltering = !!RoomListStore.instance.getFirstNameFilterCondition(); - let doUpdate = this.state.isNameFiltering !== isNameFiltering || arrayHasDiff(previousListIds, newListIds); + let doUpdate = arrayHasDiff(previousListIds, newListIds); if (!doUpdate) { // so we didn't have the visible sublists change, but did the contents of those // sublists change significantly enough to break the sticky headers? Probably, so @@ -503,33 +499,12 @@ export default class RoomList extends React.PureComponent { const newSublists = objectWithOnly(newLists, newListIds); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - this.setState({ sublists, isNameFiltering }, () => { + this.setState({ sublists }, () => { this.props.onResize(); }); } }; - private onStartChat = (ev: ButtonEvent) => { - const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; - defaultDispatcher.dispatch({ action: "view_create_chat", initialText }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateChatItem", ev); - }; - - private onExplore = (ev: ButtonEvent) => { - if (!isMetaSpace(this.props.activeSpace)) { - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: this.props.activeSpace, - metricsTrigger: undefined, // other - }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", ev); - } else { - const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; - defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory, initialText }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", ev); - } - }; - private renderSuggestedRooms(): ReactComponentElement[] { return this.state.suggestedRooms.map(room => { const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"); @@ -573,8 +548,8 @@ export default class RoomList extends React.PureComponent { private renderSublists(): React.ReactElement[] { // show a skeleton UI if the user is in no rooms and they are not filtering and have no suggested rooms - const showSkeleton = !this.state.isNameFiltering && !this.state.suggestedRooms?.length && - Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length); + const showSkeleton = !this.state.suggestedRooms?.length && + Object.values(RoomListStore.instance.orderedLists).every(list => !list?.length); return TAG_ORDER .map(orderedTagId => { @@ -636,29 +611,6 @@ export default class RoomList extends React.PureComponent { } public render() { - let explorePrompt: JSX.Element; - if (!this.props.isMinimized) { - if (this.state.isNameFiltering) { - explorePrompt =
-
{ _t("Can't see what you're looking for?") }
- - { _t("Start a new chat") } - - - { !isMetaSpace(this.props.activeSpace) ? _t("Explore rooms") : _t("Explore all public rooms") } - -
; - } - } - const sublists = this.renderSublists(); return ( @@ -673,7 +625,6 @@ export default class RoomList extends React.PureComponent { ref={this.treeRef} > { sublists } - { explorePrompt } ) } diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/RoomListHeader.tsx index 8cba91def02..1fa6805ed33 100644 --- a/src/components/views/rooms/RoomListHeader.tsx +++ b/src/components/views/rooms/RoomListHeader.tsx @@ -14,23 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useEffect, useState } from "react"; -import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { ClientEvent } from "matrix-js-sdk/src/client"; +import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; +import React, { useContext, useEffect, useState } from "react"; -import { _t } from "../../../languageHandler"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; +import { Action } from "../../../dispatcher/actions"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { useDispatcher } from "../../../hooks/useDispatcher"; import { useEventEmitterState, useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; import { useFeatureEnabled } from "../../../hooks/useSettings"; +import { _t } from "../../../languageHandler"; +import PosthogTrackers from "../../../PosthogTrackers"; +import { UIComponent } from "../../../settings/UIFeature"; +import { + getMetaSpaceName, + MetaSpace, + SpaceKey, + UPDATE_HOME_BEHAVIOUR, + UPDATE_SELECTED_SPACE, +} from "../../../stores/spaces"; import SpaceStore from "../../../stores/spaces/SpaceStore"; -import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; -import SpaceContextMenu from "../context_menus/SpaceContextMenu"; -import { HomeButtonContextMenu } from "../spaces/SpacePanel"; -import IconizedContextMenu, { - IconizedContextMenuOption, - IconizedContextMenuOptionList, -} from "../context_menus/IconizedContextMenu"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; import { shouldShowSpaceInvite, showAddExistingRooms, @@ -38,25 +45,16 @@ import { showCreateNewSubspace, showSpaceInvite, } from "../../../utils/space"; -import { Action } from "../../../dispatcher/actions"; -import { useDispatcher } from "../../../hooks/useDispatcher"; +import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; +import { BetaPill } from "../beta/BetaCard"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import SpaceContextMenu from "../context_menus/SpaceContextMenu"; import InlineSpinner from "../elements/InlineSpinner"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; -import { - getMetaSpaceName, - MetaSpace, - SpaceKey, - UPDATE_HOME_BEHAVIOUR, - UPDATE_SELECTED_SPACE, -} from "../../../stores/spaces"; import TooltipTarget from "../elements/TooltipTarget"; -import { BetaPill } from "../beta/BetaCard"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { useWebSearchMetrics } from "../dialogs/spotlight/SpotlightDialog"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../../settings/UIFeature"; +import { HomeButtonContextMenu } from "../spaces/SpacePanel"; const contextMenuBelow = (elementRect: DOMRect) => { // align the context menu's icons with the icon which opened the context menu @@ -131,15 +129,6 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); const pendingActions = usePendingActions(); - const filterCondition = RoomListStore.instance.getFirstNameFilterCondition(); - const count = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => { - if (filterCondition) { - return Object.values(RoomListStore.instance.orderedLists).flat(1).length; - } else { - return null; - } - }); - const canShowMainMenu = activeSpace || spaceKey === MetaSpace.Home; useEffect(() => { @@ -149,22 +138,13 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { } }, [closeMainMenu, canShowMainMenu, mainMenuDisplayed]); - // we pass null for the queryLength to inhibit the metrics hook for when there is no filterCondition - useWebSearchMetrics(count, filterCondition ? filterCondition.search.length : null, false); - const spaceName = useTypedEventEmitterState(activeSpace, RoomEvent.Name, () => activeSpace?.name); useEffect(() => { if (onVisibilityChange) { onVisibilityChange(); } - }, [count, onVisibilityChange]); - - if (typeof count === "number") { - return
- { _t("%(count)s results", { count }) } -
; - } + }, [onVisibilityChange]); const canAddRooms = activeSpace?.currentState?.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index df43341bf73..c310ff92829 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -16,45 +16,44 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from "react"; -import { ComponentType, createRef, ReactComponentElement } from "react"; -import { normalize } from "matrix-js-sdk/src/utils"; -import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; +import { Dispatcher } from "flux"; +import { Room } from "matrix-js-sdk/src/models/room"; import { Enable, Resizable } from "re-resizable"; import { Direction } from "re-resizable/lib/resizer"; -import { Dispatcher } from "flux"; +import * as React from "react"; +import { ComponentType, createRef, ReactComponentElement } from "react"; +import { polyfillTouchEvent } from "../../../@types/polyfill"; +import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; +import { Action } from "../../../dispatcher/actions"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { ActionPayload } from "../../../dispatcher/payloads"; +import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { _t } from "../../../languageHandler"; -import AccessibleButton from "../../views/elements/AccessibleButton"; -import RoomTile from "./RoomTile"; +import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; +import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; +import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { ListLayout } from "../../../stores/room-list/ListLayout"; +import { DefaultTagID, TagID } from "../../../stores/room-list/models"; +import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; +import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; +import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays"; +import { objectExcluding, objectHasDiff } from "../../../utils/objects"; +import ResizeNotifier from "../../../utils/ResizeNotifier"; import ContextMenu, { ChevronFace, ContextMenuTooltipButton, StyledMenuItemCheckbox, StyledMenuItemRadio, } from "../../structures/ContextMenu"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; -import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; -import { DefaultTagID, TagID } from "../../../stores/room-list/models"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; -import NotificationBadge from "./NotificationBadge"; +import AccessibleButton from "../../views/elements/AccessibleButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { ActionPayload } from "../../../dispatcher/payloads"; -import { polyfillTouchEvent } from "../../../@types/polyfill"; -import ResizeNotifier from "../../../utils/ResizeNotifier"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; -import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays"; -import { objectExcluding, objectHasDiff } from "../../../utils/objects"; import ExtraTile from "./ExtraTile"; -import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; -import { getKeyBindingsManager } from "../../../KeyBindingsManager"; -import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; -import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import NotificationBadge from "./NotificationBadge"; +import RoomTile from "./RoomTile"; const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS @@ -99,7 +98,6 @@ interface IState { isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered height: number; rooms: Room[]; - filteredExtraTiles?: ReactComponentElement[]; } export default class RoomSublist extends React.Component { @@ -109,7 +107,6 @@ export default class RoomSublist extends React.Component { private dispatcherRef: string; private layout: ListLayout; private heightAtStart: number; - private isBeingFiltered: boolean; private notificationState: ListNotificationState; constructor(props: IProps) { @@ -117,12 +114,11 @@ export default class RoomSublist extends React.Component { this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId); this.heightAtStart = 0; - this.isBeingFiltered = !!RoomListStore.instance.getFirstNameFilterCondition(); this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId); this.state = { contextMenuPosition: null, isResizing: false, - isExpanded: this.isBeingFiltered ? this.isBeingFiltered : !this.layout.isCollapsed, + isExpanded: !this.layout.isCollapsed, height: 0, // to be fixed in a moment, we need `rooms` to calculate this. rooms: arrayFastClone(RoomListStore.instance.orderedLists[this.props.tagId] || []), }; @@ -156,9 +152,6 @@ export default class RoomSublist extends React.Component { } private get extraTiles(): ReactComponentElement[] | null { - if (this.state.filteredExtraTiles) { - return this.state.filteredExtraTiles; - } if (this.props.extraTiles) { return this.props.extraTiles; } @@ -179,7 +172,7 @@ export default class RoomSublist extends React.Component { } public componentDidUpdate(prevProps: Readonly, prevState: Readonly) { - const prevExtraTiles = prevState.filteredExtraTiles || prevProps.extraTiles; + const prevExtraTiles = prevProps.extraTiles; // as the rooms can come in one by one we need to reevaluate // the amount of available rooms to cap the amount of requested visible rooms by the layout if (RoomSublist.calcNumTiles(prevState.rooms, prevExtraTiles) !== this.numTiles) { @@ -203,7 +196,7 @@ export default class RoomSublist extends React.Component { // If we're supposed to handle extra tiles, take the performance hit and re-render all the // time so we don't have to consider them as part of the visible room optimization. const prevExtraTiles = this.props.extraTiles || []; - const nextExtraTiles = (nextState.filteredExtraTiles || nextProps.extraTiles) || []; + const nextExtraTiles = nextProps.extraTiles || []; if (prevExtraTiles.length > 0 || nextExtraTiles.length > 0) { return true; } @@ -260,32 +253,12 @@ export default class RoomSublist extends React.Component { private onListsUpdated = () => { const stateUpdates: IState & any = {}; // &any is to avoid a cast on the initializer - if (this.props.extraTiles) { - const nameCondition = RoomListStore.instance.getFirstNameFilterCondition(); - if (nameCondition) { - stateUpdates.filteredExtraTiles = this.props.extraTiles - .filter(t => nameCondition.matches(normalize(t.props.displayName || ""))); - } else if (this.state.filteredExtraTiles) { - stateUpdates.filteredExtraTiles = null; - } - } - const currentRooms = this.state.rooms; const newRooms = arrayFastClone(RoomListStore.instance.orderedLists[this.props.tagId] || []); if (arrayHasOrderChange(currentRooms, newRooms)) { stateUpdates.rooms = newRooms; } - const isStillBeingFiltered = !!RoomListStore.instance.getFirstNameFilterCondition(); - if (isStillBeingFiltered !== this.isBeingFiltered) { - this.isBeingFiltered = isStillBeingFiltered; - if (isStillBeingFiltered) { - stateUpdates.isExpanded = true; - } else { - stateUpdates.isExpanded = !this.layout.isCollapsed; - } - } - if (Object.keys(stateUpdates).length > 0) { this.setState(stateUpdates); } @@ -418,7 +391,7 @@ export default class RoomSublist extends React.Component { room = this.state.rooms && this.state.rooms[0]; } else { // find the first room with a count of the same colour as the badge count - room = RoomListStore.instance.unfilteredLists[this.props.tagId].find((r: Room) => { + room = RoomListStore.instance.orderedLists[this.props.tagId].find((r: Room) => { const notifState = this.notificationState.getForRoom(r); return notifState.count > 0 && notifState.color === this.notificationState.color; }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8e40a870cb5..906d2358d1b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1791,11 +1791,6 @@ "Historical": "Historical", "Suggested Rooms": "Suggested Rooms", "Empty room": "Empty room", - "Can't see what you're looking for?": "Can't see what you're looking for?", - "Start a new chat": "Start a new chat", - "Explore all public rooms": "Explore all public rooms", - "%(count)s results|other": "%(count)s results", - "%(count)s results|one": "%(count)s result", "Add space": "Add space", "You do not have permissions to add spaces to this space": "You do not have permissions to add spaces to this space", "Join public room": "Join public room", diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index dc7f405c62d..c15567afdc8 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -26,14 +26,13 @@ import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm import { ActionPayload } from "../../dispatcher/payloads"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; -import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./filters/IFilterCondition"; +import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition"; import { RoomViewStore } from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; 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"; @@ -58,7 +57,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { private initialListsGenerated = false; private algorithm = new Algorithm(); - private filterConditions: IFilterCondition[] = []; private prefilterConditions: IFilterCondition[] = []; private updateFn = new MarkedExecution(() => { for (const tagId of Object.keys(this.orderedLists)) { @@ -78,11 +76,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { new SpaceWatcher(this); } - public get unfilteredLists(): ITagMap { - if (!this.algorithm) return {}; // No tags yet. - return this.algorithm.getUnfilteredRooms(); - } - public get orderedLists(): ITagMap { if (!this.algorithm) return {}; // No tags yet. return this.algorithm.getOrderedRooms(); @@ -91,7 +84,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { // Intended for test usage public async resetStore() { await this.reset(); - this.filterConditions = []; this.prefilterConditions = []; this.initialListsGenerated = false; @@ -502,9 +494,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 && !this.filterConditions.length) { + if (this.prefilterConditions.length > 0) { rooms = rooms.filter(r => { for (const filter of this.prefilterConditions) { if (!filter.isVisible(r)) { @@ -556,20 +546,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient { */ public async addFilter(filter: IFilterCondition): Promise { 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); - // Runtime filters with spaces disable prefiltering for the search all spaces feature. - // 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); - } - } + filter.on(FILTER_CHANGED, this.onPrefilterUpdated); + this.prefilterConditions.push(filter); + promise = this.recalculatePrefiltering(); promise.then(() => this.updateFn.trigger()); } @@ -582,20 +561,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient { */ public removeFilter(filter: IFilterCondition): void { let promise = Promise.resolve(); - let idx = this.filterConditions.indexOf(filter); let removed = false; - 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 - promise = this.recalculatePrefiltering(); - removed = true; - } - - idx = this.prefilterConditions.indexOf(filter); + const idx = this.prefilterConditions.indexOf(filter); if (idx >= 0) { filter.off(FILTER_CHANGED, this.onPrefilterUpdated); this.prefilterConditions.splice(idx, 1); @@ -608,20 +575,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } } - /** - * 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. - */ - public getFirstNameFilterCondition(): NameFilterCondition | null { - for (const filter of this.filterConditions) { - if (filter instanceof 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 diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index edd406fd07b..9810c386f79 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -30,7 +30,6 @@ import { ListAlgorithm, SortAlgorithm, } from "./models"; -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"; @@ -67,7 +66,6 @@ interface IStickyRoom { export class Algorithm extends EventEmitter { private _cachedRooms: ITagMap = {}; private _cachedStickyRooms: ITagMap = {}; // a clone of the _cachedRooms, with the sticky room - private filteredRooms: ITagMap = {}; private _stickyRoom: IStickyRoom = null; private _lastStickyRoom: IStickyRoom = null; // only not-null when changing the sticky room private sortAlgorithms: ITagSortingMap; @@ -77,8 +75,6 @@ export class Algorithm extends EventEmitter { private roomIdsToTags: { [roomId: string]: TagID[]; } = {}; - private allowedByFilter: Map = new Map(); - private allowedRoomsByFilters: Set = new Set(); /** * Set to true to suspend emissions of algorithm updates. @@ -107,13 +103,8 @@ export class Algorithm extends EventEmitter { return !!this.sortAlgorithms; } - protected get hasFilters(): boolean { - return this.allowedByFilter.size > 0; - } - protected set cachedRooms(val: ITagMap) { this._cachedRooms = val; - this.recalculateFilteredRooms(); this.recalculateStickyRoom(); this.recalculateVideoRoom(); } @@ -151,7 +142,6 @@ export class Algorithm extends EventEmitter { const algorithm: OrderingAlgorithm = this.algorithms[tagId]; algorithm.setSortAlgorithm(sort); this._cachedRooms[tagId] = algorithm.orderedRooms; - this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed this.recalculateVideoRoom(tagId); } @@ -171,40 +161,10 @@ export class Algorithm extends EventEmitter { algorithm.setRooms(this._cachedRooms[tagId]); this._cachedRooms[tagId] = algorithm.orderedRooms; - this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed this.recalculateVideoRoom(tagId); } - public addFilterCondition(filterCondition: IFilterCondition): void { - // Populate the cache of the new filter - this.allowedByFilter.set(filterCondition, this.rooms.filter(r => filterCondition.isVisible(r))); - this.recalculateFilteredRooms(); - filterCondition.on(FILTER_CHANGED, this.handleFilterChange.bind(this)); - } - - public removeFilterCondition(filterCondition: IFilterCondition): void { - filterCondition.off(FILTER_CHANGED, this.handleFilterChange.bind(this)); - if (this.allowedByFilter.has(filterCondition)) { - this.allowedByFilter.delete(filterCondition); - this.recalculateFilteredRooms(); - - // 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 && !this.updatesInhibited) { - this.emit(LIST_UPDATED_EVENT); - } - } - } - - private handleFilterChange() { - 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); - } - private updateStickyRoom(val: Room) { this.doUpdateStickyRoom(val); this._lastStickyRoom = null; // clear to indicate we're done changing @@ -318,8 +278,6 @@ export class Algorithm extends EventEmitter { // We update the filtered rooms just in case, as otherwise users will end up visiting // a room while filtering and it'll disappear. We don't update the filter earlier in // this function simply because we don't have to. - this.recalculateFilteredRoomsForTag(tag); - if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateFilteredRoomsForTag(lastStickyRoom.tag); this.recalculateStickyRoom(); this.recalculateVideoRoom(tag); if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateVideoRoom(lastStickyRoom.tag); @@ -334,7 +292,6 @@ export class Algorithm extends EventEmitter { */ public updateVideoRoom = () => { // In case we're unsticking a video room, sort it back into natural order - this.recalculateFilteredRooms(); this.recalculateStickyRoom(); this.recalculateVideoRoom(); @@ -345,63 +302,6 @@ export class Algorithm extends EventEmitter { this.emit(LIST_UPDATED_EVENT, true); }; - protected recalculateFilteredRooms() { - if (!this.hasFilters) { - return; - } - - logger.warn("Recalculating filtered room list"); - const filters = Array.from(this.allowedByFilter.keys()); - 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. - // 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. - const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone - this.tryInsertStickyRoomToFilterSet(rooms, tagId); - 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); - if (idx >= 0) remainingRooms.splice(idx, 1); - allowedRoomsInThisTag.push(room); - } - } - newMap[tagId] = allowedRoomsInThisTag; - } - - const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, []); - this.allowedRoomsByFilters = new Set(allowedRooms); - this.filteredRooms = newMap; - if (this.updatesInhibited) return; - this.emit(LIST_UPDATED_EVENT); - } - - protected recalculateFilteredRoomsForTag(tagId: TagID): void { - if (!this.hasFilters) return; // don't bother doing work if there's nothing to do - - delete this.filteredRooms[tagId]; - const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone - this.tryInsertStickyRoomToFilterSet(rooms, tagId); - const filteredRooms = rooms.filter(r => this.allowedRoomsByFilters.has(r)); - if (filteredRooms.length > 0) { - this.filteredRooms[tagId] = filteredRooms; - } - } - - protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { - if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return; - - const position = this._stickyRoom.position; - if (position >= rooms.length) { - rooms.push(this._stickyRoom.room); - } else { - rooms.splice(position, 0, this._stickyRoom.room); - } - } - private initCachedStickyRooms() { this._cachedStickyRooms = {}; for (const tagId of Object.keys(this.cachedRooms)) { @@ -520,18 +420,11 @@ export class Algorithm extends EventEmitter { } /** - * Gets an ordered set of rooms for the all known tags, filtered. + * Gets an ordered set of rooms for the all known tags. * @returns {ITagMap} The cached list of rooms, ordered, * for each tag. May be empty, but never null/undefined. */ public getOrderedRooms(): ITagMap { - if (!this.hasFilters) { - return this._cachedStickyRooms || this.cachedRooms; - } - return this.filteredRooms; - } - - public getUnfilteredRooms(): ITagMap { return this._cachedStickyRooms || this.cachedRooms; } @@ -543,10 +436,7 @@ export class Algorithm extends EventEmitter { * for each tag. May be empty, but never null/undefined. */ private getOrderedRoomsWithoutSticky(): ITagMap { - if (!this.hasFilters) { - return this.cachedRooms; - } - return this.filteredRooms; + return this.cachedRooms; } /** @@ -775,7 +665,6 @@ export class Algorithm extends EventEmitter { if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); this._cachedRooms[rmTag] = algorithm.orderedRooms; - this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed this.recalculateVideoRoom(rmTag); } @@ -852,7 +741,6 @@ export class Algorithm extends EventEmitter { this._cachedRooms[tag] = algorithm.orderedRooms; // Flag that we've done something - this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed this.recalculateVideoRoom(tag); changed = true; diff --git a/src/stores/room-list/filters/IFilterCondition.ts b/src/stores/room-list/filters/IFilterCondition.ts index cb9841a3c94..28a30ae8c09 100644 --- a/src/stores/room-list/filters/IFilterCondition.ts +++ b/src/stores/room-list/filters/IFilterCondition.ts @@ -19,21 +19,6 @@ import { EventEmitter } from "events"; export const FILTER_CHANGED = "filter_changed"; -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, -} - /** * A filter condition for the room list, determining if a room * should be shown or not. @@ -47,11 +32,6 @@ export enum FilterKind { * as a change in the user's input), this emits FILTER_CHANGED. */ export interface IFilterCondition extends EventEmitter { - /** - * The kind of filter this presents. - */ - kind: FilterKind; - /** * Determines if a given room should be visible under this * condition. diff --git a/src/stores/room-list/filters/NameFilterCondition.ts b/src/stores/room-list/filters/NameFilterCondition.ts deleted file mode 100644 index c2dd40f4c22..00000000000 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* -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. -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 { Room } from "matrix-js-sdk/src/models/room"; -import { EventEmitter } from "events"; -import { normalize } from "matrix-js-sdk/src/utils"; -import { throttle } from "lodash"; - -import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; - -/** - * A filter condition for the room list which reveals rooms of a particular - * name, or associated name (like a room alias). - */ -export class NameFilterCondition extends EventEmitter implements IFilterCondition { - private _search = ""; - - constructor() { - super(); - } - - public get kind(): FilterKind { - return FilterKind.Runtime; - } - - public get search(): string { - return this._search; - } - - public set search(val: string) { - this._search = val; - this.callUpdate(); - } - - private callUpdate = throttle(() => { - this.emit(FILTER_CHANGED); - }, 200, { trailing: true, leading: true }); - - public isVisible(room: Room): boolean { - const lcFilter = this.search.toLowerCase(); - if (this.search[0] === '#') { - // Try and find rooms by alias - if (room.getCanonicalAlias() && room.getCanonicalAlias().toLowerCase().startsWith(lcFilter)) { - return true; - } - if (room.getAltAliases().some(a => a.toLowerCase().startsWith(lcFilter))) { - return true; - } - } - - if (!room.name) return false; // should realistically not happen: the js-sdk always calculates a name - - return this.matches(room.normalizedName); - } - - public matches(normalizedName: string): boolean { - return normalizedName.includes(normalize(this.search)); - } -} diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 90bf22ed207..1f5066d8a98 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -17,7 +17,7 @@ limitations under the License. import { EventEmitter } from "events"; import { Room } from "matrix-js-sdk/src/models/room"; -import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; +import { FILTER_CHANGED, IFilterCondition } from "./IFilterCondition"; import { IDestroyable } from "../../../utils/IDestroyable"; import SpaceStore from "../../spaces/SpaceStore"; import { isMetaSpace, MetaSpace, SpaceKey } from "../../spaces"; @@ -36,10 +36,6 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi private showPeopleInSpace = true; private space: SpaceKey = MetaSpace.Home; - public get kind(): FilterKind { - return FilterKind.Prefilter; - } - public isVisible(room: Room): boolean { return SpaceStore.instance.isRoomInSpace(this.space, room.roomId); } diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 605e9d2a364..ec4556f48c2 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -187,7 +187,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { metricsTrigger: "WebSpacePanelNotificationBadge", }); } else { - const lists = RoomListStore.instance.unfilteredLists; + const lists = RoomListStore.instance.orderedLists; for (let i = 0; i < TAG_ORDER.length; i++) { const t = TAG_ORDER[i]; const listRooms = lists[t];