RoomListViewModel: Support primary filters in the view model (#29454)

* Track available filters and expose this info from the vm

- Adds a separate hook that tracks the filtered rooms and the available
  filters.
- When secondary filters are added, some of the primary filters will be
  selectively hidden. So track this info in the vm.

* Write tests

* Fix typescript error

* Fix translation

* Explain what a primary filter is
This commit is contained in:
R Midhun Suresh
2025-03-10 18:53:38 +05:30
committed by GitHub
parent af476905b6
commit fd91e78152
4 changed files with 145 additions and 14 deletions

View File

@@ -5,15 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import type { Room } from "matrix-js-sdk/src/matrix";
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import type { TranslationKey } from "../../../languageHandler";
import RoomListStoreV3 from "../../../stores/room-list-v3/RoomListStoreV3";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters";
import { _t, _td } from "../../../languageHandler";
export interface RoomListViewState {
/**
@@ -25,6 +28,12 @@ export interface RoomListViewState {
* Open the room having given roomId.
*/
openRoom: (roomId: string) => void;
/**
* A list of objects that provide the view enough information
* to render primary room filters.
*/
primaryFilters: PrimaryFilter[];
}
/**
@@ -32,12 +41,7 @@ export interface RoomListViewState {
* @see {@link RoomListViewState} for more information about what this view model returns.
*/
export function useRoomListViewModel(): RoomListViewState {
const [rooms, setRooms] = useState(RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace();
setRooms(newRooms);
});
const { primaryFilters, rooms } = useFilteredRooms();
const openRoom = useCallback((roomId: string): void => {
dispatcher.dispatch<ViewRoomPayload>({
@@ -47,5 +51,77 @@ export function useRoomListViewModel(): RoomListViewState {
});
}, []);
return { rooms, openRoom };
return {
rooms,
openRoom,
primaryFilters,
};
}
/**
* Provides information about a primary filter.
* A primary filter is a commonly used filter that is given
* more precedence in the UI. For eg, primary filters may be
* rendered as pills above the room list.
*/
interface PrimaryFilter {
// A function to toggle this filter on and off.
toggle: () => void;
// Whether this filter is currently applied
active: boolean;
// Text that can be used in the UI to represent this filter.
name: string;
}
interface FilteredRooms {
primaryFilters: PrimaryFilter[];
rooms: Room[];
}
const filterKeyToNameMap: Map<FilterKey, TranslationKey> = new Map([
[FilterKey.UnreadFilter, _td("room_list|filters|unread")],
[FilterKey.FavouriteFilter, _td("room_list|filters|favourite")],
[FilterKey.PeopleFilter, _td("room_list|filters|people")],
[FilterKey.RoomsFilter, _td("room_list|filters|rooms")],
]);
/**
* Track available filters and provide a filtered list of rooms.
*/
function useFilteredRooms(): FilteredRooms {
const [primaryFilter, setPrimaryFilter] = useState<FilterKey | undefined>();
const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
const updateRoomsFromStore = useCallback((filter?: FilterKey): void => {
const filters = filter !== undefined ? [filter] : [];
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
setRooms(newRooms);
}, []);
useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
updateRoomsFromStore(primaryFilter);
});
const primaryFilters = useMemo(() => {
const createPrimaryFilter = (key: FilterKey, name: string): PrimaryFilter => {
return {
toggle: () => {
setPrimaryFilter((currentFilter) => {
const filter = currentFilter === key ? undefined : key;
updateRoomsFromStore(filter);
return filter;
});
},
active: primaryFilter === key,
name,
};
};
const filters: PrimaryFilter[] = [];
for (const [key, name] of filterKeyToNameMap.entries()) {
filters.push(createPrimaryFilter(key, _t(name)));
}
return filters;
}, [primaryFilter, updateRoomsFromStore]);
return { primaryFilters, rooms };
}

View File

@@ -2099,6 +2099,12 @@
"failed_add_tag": "Failed to add tag %(tagName)s to room",
"failed_remove_tag": "Failed to remove tag %(tagName)s from room",
"failed_set_dm_tag": "Failed to set direct message tag",
"filters": {
"favourite": "Favourites",
"people": "People",
"rooms": "Rooms",
"unread": "Unread"
},
"home_menu_label": "Home options",
"join_public_room_label": "Join public room",
"joining_rooms_status": {