Add loading state to the new room list view (#29725)

* add loading state to view model and spinner to room list vieqw

* Update snapshots and add loading test

* avoid nested ternary operator

* Add room list skeleton loading state

* Fix loading logic

- Create RoomListStoreV3Event as to not conflict with loading event definition in Create RoomListStoreEvent.
- Add a loaded event
- Use it to determine loaded state in useFilteredRooms rather than the update event which gets fired in other cases.

* Fix isLoadingRooms logic

* update snapshots and fix test

* Forcing an empty commit to fix PR

* Fix _components.pcss order

* Fix test that wasn't doing anything

* fix tests
This commit is contained in:
David Langley
2025-05-02 14:12:00 +01:00
committed by GitHub
parent 72429c1350
commit a430501271
17 changed files with 110 additions and 115 deletions

View File

@@ -22,6 +22,11 @@ import { useStickyRoomList } from "./useStickyRoomList";
import { useRoomListNavigation } from "./useRoomListNavigation";
export interface RoomListViewState {
/**
* Whether the list of rooms is being loaded.
*/
isLoadingRooms: boolean;
/**
* A list of rooms to be displayed in the left panel.
*/
@@ -99,6 +104,7 @@ export interface RoomListViewState {
export function useRoomListViewModel(): RoomListViewState {
const matrixClient = useMatrixClientContext();
const {
isLoadingRooms,
primaryFilters,
activePrimaryFilter,
rooms: filteredRooms,
@@ -123,6 +129,7 @@ export function useRoomListViewModel(): RoomListViewState {
const createRoom = useCallback(() => createRoomFunc(currentSpace), [currentSpace]);
return {
isLoadingRooms,
rooms,
canCreateRoom,
createRoom,

View File

@@ -10,8 +10,7 @@ import { useCallback, 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 from "../../../stores/room-list-v3/RoomListStoreV3";
import { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import RoomListStoreV3, { LISTS_LOADED_EVENT, LISTS_UPDATE_EVENT } 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,6 +34,7 @@ export interface PrimaryFilter {
interface FilteredRooms {
primaryFilters: PrimaryFilter[];
isLoadingRooms: boolean;
rooms: Room[];
activateSecondaryFilter: (filter: SecondaryFilters) => void;
activeSecondaryFilter: SecondaryFilters;
@@ -115,6 +115,7 @@ export function useFilteredRooms(): FilteredRooms {
);
const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
const [isLoadingRooms, setIsLoadingRooms] = useState(() => RoomListStoreV3.instance.isLoadingRooms);
const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => {
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
@@ -139,6 +140,10 @@ export function useFilteredRooms(): FilteredRooms {
updateRoomsFromStore(filters);
});
useEventEmitter(RoomListStoreV3.instance, LISTS_LOADED_EVENT, () => {
setIsLoadingRooms(false);
});
/**
* Secondary filters are activated using this function.
* This is different to how primary filters work because the secondary
@@ -194,5 +199,12 @@ export function useFilteredRooms(): FilteredRooms {
const activePrimaryFilter = useMemo(() => primaryFilters.find((filter) => filter.active), [primaryFilters]);
return { primaryFilters, activePrimaryFilter, rooms, activateSecondaryFilter, activeSecondaryFilter };
return {
isLoadingRooms,
primaryFilters,
activePrimaryFilter,
rooms,
activateSecondaryFilter,
activeSecondaryFilter,
};
}

View File

@@ -19,12 +19,19 @@ import { RoomListSecondaryFilters } from "./RoomListSecondaryFilters";
export function RoomListView(): JSX.Element {
const vm = useRoomListViewModel();
const isRoomListEmpty = vm.rooms.length === 0;
let listBody;
if (vm.isLoadingRooms) {
listBody = <div className="mx_RoomListSkeleton" />;
} else if (isRoomListEmpty) {
listBody = <EmptyRoomList vm={vm} />;
} else {
listBody = <RoomList vm={vm} />;
}
return (
<>
<RoomListPrimaryFilters vm={vm} />
<RoomListSecondaryFilters vm={vm} />
{isRoomListEmpty ? <EmptyRoomList vm={vm} /> : <RoomList vm={vm} />}
{listBody}
</>
);
}