Move the room list to the new ListView(backed by react-virtuoso) (#30515)
* Move Room List to ListView - Also remove Space/Enter handing from keyboard navigation we can just leave the default behaviour of those keys and handle via onClick * Update rooms when the primary filter changes Otherwise when changing spaces, the filter does not reset until the next update to the RVS is made. * Fix stickyRow/scrollIntoView when switiching space or changing filters - Also remove the rest of space/enter keyboard handling use * Remove the rest of space/enter keyboard handling use * Remove useCombinedRef and add @radix-ui/react-compose-refs as we already depend on it - Also remove eact-virtualized dep * Update RoomList unit test * Update snapshots and unit tests * Fix e2e tests * Remove react-virtualized from tests * Fix e2e flake * Update more screenshots * Fix e2e test case where were should scroll to the top when the active room is no longer in the list * Move from gitpkg to package-patch * Update to latest react virtuoso release/api. Also pass spaceId to the room list and scroll the activeIndex into view when spaceId or primaryFilter change. * Use listbox/option roles to improve ScreenReader experience * Change onKeyDown e.stopPropogation to cover context menu * lint * Remove unneeded exposure of the listView ref Also move scrollIntoViewOnChange to useCallback * Update unit test and snapshot * Fix e2e tests and update screenshots * Fix unit test and snapshot * Update more unit tests * Fix keyboard shortcuts and e2e test * Fix another e2e and unit test * lint * Improve the naming for RoomResult and the documentation on it's fields meaning. Also update the login in RoomList to check for any change in filters, this is a bit more future proof for when we introduce multi select than using activePrimaryFilter. * Put back and fix landmark tests * Fix test import * Add comment regarding context object getting rendered. * onKeyDown should be optional * Use SpaceKey type on RoomResult * lint
This commit is contained in:
@@ -18,6 +18,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { useStickyRoomList } from "./useStickyRoomList";
|
||||
import { useRoomListNavigation } from "./useRoomListNavigation";
|
||||
import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3";
|
||||
|
||||
export interface RoomListViewState {
|
||||
/**
|
||||
@@ -26,9 +27,9 @@ export interface RoomListViewState {
|
||||
isLoadingRooms: boolean;
|
||||
|
||||
/**
|
||||
* A list of rooms to be displayed in the left panel.
|
||||
* The room results to be displayed (along with the spaceId and filter keys at the time of query)
|
||||
*/
|
||||
rooms: Room[];
|
||||
roomsResult: RoomsResult;
|
||||
|
||||
/**
|
||||
* Create a chat room
|
||||
@@ -71,10 +72,10 @@ export interface RoomListViewState {
|
||||
*/
|
||||
export function useRoomListViewModel(): RoomListViewState {
|
||||
const matrixClient = useMatrixClientContext();
|
||||
const { isLoadingRooms, primaryFilters, activePrimaryFilter, rooms: filteredRooms } = useFilteredRooms();
|
||||
const { activeIndex, rooms } = useStickyRoomList(filteredRooms);
|
||||
const { isLoadingRooms, primaryFilters, activePrimaryFilter, roomsResult: filteredRooms } = useFilteredRooms();
|
||||
const { activeIndex, roomsResult } = useStickyRoomList(filteredRooms);
|
||||
|
||||
useRoomListNavigation(rooms);
|
||||
useRoomListNavigation(roomsResult.rooms);
|
||||
|
||||
const currentSpace = useEventEmitterState<Room | null>(
|
||||
SpaceStore.instance,
|
||||
@@ -88,7 +89,7 @@ export function useRoomListViewModel(): RoomListViewState {
|
||||
|
||||
return {
|
||||
isLoadingRooms,
|
||||
rooms,
|
||||
roomsResult,
|
||||
canCreateRoom,
|
||||
createRoom,
|
||||
createChatRoom,
|
||||
|
||||
@@ -5,12 +5,15 @@ 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, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters";
|
||||
import { _t, _td, type TranslationKey } from "../../../languageHandler";
|
||||
import RoomListStoreV3, { LISTS_LOADED_EVENT, LISTS_UPDATE_EVENT } from "../../../stores/room-list-v3/RoomListStoreV3";
|
||||
import RoomListStoreV3, {
|
||||
LISTS_LOADED_EVENT,
|
||||
LISTS_UPDATE_EVENT,
|
||||
type RoomsResult,
|
||||
} from "../../../stores/room-list-v3/RoomListStoreV3";
|
||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { UPDATE_SELECTED_SPACE } from "../../../stores/spaces";
|
||||
@@ -35,7 +38,7 @@ export interface PrimaryFilter {
|
||||
interface FilteredRooms {
|
||||
primaryFilters: PrimaryFilter[];
|
||||
isLoadingRooms: boolean;
|
||||
rooms: Room[];
|
||||
roomsResult: RoomsResult;
|
||||
/**
|
||||
* The currently active primary filter.
|
||||
* If no primary filter is active, this will be undefined.
|
||||
@@ -63,12 +66,12 @@ export function useFilteredRooms(): FilteredRooms {
|
||||
*/
|
||||
const [primaryFilter, setPrimaryFilter] = useState<FilterKey | undefined>();
|
||||
|
||||
const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
|
||||
const [roomsResult, setRoomsResult] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
|
||||
const [isLoadingRooms, setIsLoadingRooms] = useState(() => RoomListStoreV3.instance.isLoadingRooms);
|
||||
|
||||
const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => {
|
||||
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
|
||||
setRooms(newRooms);
|
||||
setRoomsResult(newRooms);
|
||||
}, []);
|
||||
|
||||
// Reset filters when active space changes
|
||||
@@ -77,9 +80,15 @@ export function useFilteredRooms(): FilteredRooms {
|
||||
const filterUndefined = (array: (FilterKey | undefined)[]): FilterKey[] =>
|
||||
array.filter((f) => f !== undefined) as FilterKey[];
|
||||
|
||||
const getAppliedFilters = (): FilterKey[] => {
|
||||
const getAppliedFilters = useCallback((): FilterKey[] => {
|
||||
return filterUndefined([primaryFilter]);
|
||||
};
|
||||
}, [primaryFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
// Update the rooms state when the primary filter changes
|
||||
const filters = getAppliedFilters();
|
||||
updateRoomsFromStore(filters);
|
||||
}, [getAppliedFilters, updateRoomsFromStore]);
|
||||
|
||||
useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
|
||||
const filters = getAppliedFilters();
|
||||
@@ -122,6 +131,6 @@ export function useFilteredRooms(): FilteredRooms {
|
||||
isLoadingRooms,
|
||||
primaryFilters,
|
||||
activePrimaryFilter,
|
||||
rooms,
|
||||
roomsResult,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import type { Optional } from "matrix-events-sdk";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3";
|
||||
|
||||
function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
|
||||
const index = rooms.findIndex((room) => room.roomId === roomId);
|
||||
@@ -67,11 +68,11 @@ function getRoomsWithStickyRoom(
|
||||
return { newIndex: oldIndex, newRooms };
|
||||
}
|
||||
|
||||
interface StickyRoomListResult {
|
||||
export interface StickyRoomListResult {
|
||||
/**
|
||||
* List of rooms with sticky active room.
|
||||
* The rooms result with the active sticky room applied
|
||||
*/
|
||||
rooms: Room[];
|
||||
roomsResult: RoomsResult;
|
||||
/**
|
||||
* Index of the active room in the room list.
|
||||
*/
|
||||
@@ -85,10 +86,10 @@ interface StickyRoomListResult {
|
||||
* @param rooms list of rooms
|
||||
* @see {@link StickyRoomListResult} details what this hook returns..
|
||||
*/
|
||||
export function useStickyRoomList(rooms: Room[]): StickyRoomListResult {
|
||||
const [listState, setListState] = useState<{ index: number | undefined; roomsWithStickyRoom: Room[] }>({
|
||||
index: undefined,
|
||||
roomsWithStickyRoom: rooms,
|
||||
export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResult {
|
||||
const [listState, setListState] = useState<StickyRoomListResult>({
|
||||
activeIndex: getIndexByRoomId(roomsResult.rooms, SdkContextClass.instance.roomViewStore.getRoomId()),
|
||||
roomsResult: roomsResult,
|
||||
});
|
||||
|
||||
const currentSpaceRef = useRef(SpaceStore.instance.activeSpace);
|
||||
@@ -97,13 +98,18 @@ export function useStickyRoomList(rooms: Room[]): StickyRoomListResult {
|
||||
(newRoomId: string | null, isRoomChange: boolean = false) => {
|
||||
setListState((current) => {
|
||||
const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
const newActiveIndex = getIndexByRoomId(rooms, activeRoomId);
|
||||
const oldIndex = current.index;
|
||||
const { newIndex, newRooms } = getRoomsWithStickyRoom(rooms, oldIndex, newActiveIndex, isRoomChange);
|
||||
return { index: newIndex, roomsWithStickyRoom: newRooms };
|
||||
const newActiveIndex = getIndexByRoomId(roomsResult.rooms, activeRoomId);
|
||||
const oldIndex = current.activeIndex;
|
||||
const { newIndex, newRooms } = getRoomsWithStickyRoom(
|
||||
roomsResult.rooms,
|
||||
oldIndex,
|
||||
newActiveIndex,
|
||||
isRoomChange,
|
||||
);
|
||||
return { activeIndex: newIndex, roomsResult: { ...roomsResult, rooms: newRooms } };
|
||||
});
|
||||
},
|
||||
[rooms],
|
||||
[roomsResult],
|
||||
);
|
||||
|
||||
// Re-calculate the index when the active room has changed.
|
||||
@@ -115,20 +121,19 @@ export function useStickyRoomList(rooms: Room[]): StickyRoomListResult {
|
||||
useEffect(() => {
|
||||
let newRoomId: string | null = null;
|
||||
let isRoomChange = false;
|
||||
const newSpace = SpaceStore.instance.activeSpace;
|
||||
if (currentSpaceRef.current !== newSpace) {
|
||||
if (currentSpaceRef.current !== roomsResult.spaceId) {
|
||||
/*
|
||||
If the space has changed, we check if we can immediately set the active
|
||||
index to the last opened room in that space. Otherwise, we might see a
|
||||
flicker because of the delay between the space change event and
|
||||
active room change dispatch.
|
||||
*/
|
||||
newRoomId = SpaceStore.instance.getLastSelectedRoomIdForSpace(newSpace);
|
||||
newRoomId = SpaceStore.instance.getLastSelectedRoomIdForSpace(roomsResult.spaceId);
|
||||
isRoomChange = true;
|
||||
currentSpaceRef.current = newSpace;
|
||||
currentSpaceRef.current = roomsResult.spaceId;
|
||||
}
|
||||
updateRoomsAndIndex(newRoomId, isRoomChange);
|
||||
}, [rooms, updateRoomsAndIndex]);
|
||||
}, [roomsResult, updateRoomsAndIndex]);
|
||||
|
||||
return { activeIndex: listState.index, rooms: listState.roomsWithStickyRoom };
|
||||
return listState;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user