2026-02-05 21:05:14 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright 2026 Element Creations Ltd.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
|
|
* Please see LICENSE files in the repository root for full details.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React, { type JSX, type ReactNode } from "react";
|
|
|
|
|
|
2026-03-27 20:58:56 +01:00
|
|
|
import { useViewModel, type ViewModel } from "../../core/viewmodel";
|
2026-02-05 21:05:14 +00:00
|
|
|
import { RoomListPrimaryFilters, type FilterId } from "../RoomListPrimaryFilters";
|
|
|
|
|
import { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton";
|
|
|
|
|
import { RoomListEmptyStateView } from "./RoomListEmptyStateView";
|
|
|
|
|
import { VirtualizedRoomListView, type RoomListViewState } from "../VirtualizedRoomListView";
|
2026-05-11 12:09:05 +02:00
|
|
|
import { type Room, type RoomListItemViewModel } from "../VirtualizedRoomListView/RoomListItemWrapper/RoomListItemView";
|
2026-04-14 10:51:36 +02:00
|
|
|
import { type RoomListSectionHeaderViewModel } from "../VirtualizedRoomListView/RoomListSectionHeaderView";
|
2026-04-16 15:14:10 +02:00
|
|
|
import { type ToastType, RoomListToast } from "./RoomListToast";
|
|
|
|
|
import styles from "./RoomListView.module.css";
|
|
|
|
|
import { Flex } from "../../core/utils/Flex";
|
2026-03-16 17:40:12 +01:00
|
|
|
|
|
|
|
|
export type RoomListSection = {
|
|
|
|
|
/** Unique identifier for the section */
|
|
|
|
|
id: string;
|
|
|
|
|
/** Array of room IDs that belong to this section */
|
|
|
|
|
roomIds: string[];
|
|
|
|
|
};
|
2026-02-05 21:05:14 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Snapshot for the room list view
|
|
|
|
|
*/
|
2026-03-17 15:20:52 +01:00
|
|
|
export type RoomListViewSnapshot = {
|
2026-02-05 21:05:14 +00:00
|
|
|
/** Whether the rooms are currently loading */
|
|
|
|
|
isLoadingRooms: boolean;
|
|
|
|
|
/** Whether the room list is empty */
|
|
|
|
|
isRoomListEmpty: boolean;
|
|
|
|
|
/** Array of filter IDs */
|
|
|
|
|
filterIds: FilterId[];
|
|
|
|
|
/** Currently active filter ID (if any) */
|
|
|
|
|
activeFilterId?: FilterId;
|
|
|
|
|
/** Room list state */
|
|
|
|
|
roomListState: RoomListViewState;
|
2026-03-16 17:40:12 +01:00
|
|
|
/** Array of sections in the room list */
|
|
|
|
|
sections: RoomListSection[];
|
2026-02-05 21:05:14 +00:00
|
|
|
/** Optional description for the empty state */
|
|
|
|
|
emptyStateDescription?: string;
|
|
|
|
|
/** Optional action element for the empty state */
|
|
|
|
|
emptyStateAction?: ReactNode;
|
|
|
|
|
/** Whether the user can create rooms */
|
|
|
|
|
canCreateRoom?: boolean;
|
2026-03-16 17:40:12 +01:00
|
|
|
/** Whether the room list is displayed as a flat list */
|
|
|
|
|
isFlatList: boolean;
|
2026-04-16 15:14:10 +02:00
|
|
|
/** Optional toast to display */
|
|
|
|
|
toast?: ToastType;
|
2026-02-05 21:05:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Actions interface for room list operations
|
|
|
|
|
*/
|
|
|
|
|
export interface RoomListViewActions {
|
|
|
|
|
/** Called when a filter is toggled */
|
|
|
|
|
onToggleFilter: (filterId: FilterId) => void;
|
|
|
|
|
/** Called to create a new chat room */
|
|
|
|
|
createChatRoom: () => void;
|
|
|
|
|
/** Called to create a new room */
|
|
|
|
|
createRoom: () => void;
|
2026-03-31 20:43:32 +02:00
|
|
|
/**
|
|
|
|
|
* Get view model for a specific room (virtualization API)
|
|
|
|
|
* Allow undefined to be returned if we don't have a view model for the room. In this case the room will not be rendered.
|
|
|
|
|
*/
|
|
|
|
|
getRoomItemViewModel: (roomId: string) => RoomListItemViewModel | undefined;
|
2026-02-05 21:05:14 +00:00
|
|
|
/** Called when the visible range changes (virtualization API) */
|
|
|
|
|
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
|
2026-03-16 17:40:12 +01:00
|
|
|
/** Get view model for a specific section header (virtualization API) */
|
|
|
|
|
getSectionHeaderViewModel: (sectionId: string) => RoomListSectionHeaderViewModel;
|
2026-04-16 15:14:10 +02:00
|
|
|
/** Called to close the toast message */
|
|
|
|
|
closeToast: () => void;
|
2026-02-05 21:05:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The view model type for the room list view
|
|
|
|
|
*/
|
2026-03-17 15:20:52 +01:00
|
|
|
export type RoomListViewModel = ViewModel<RoomListViewSnapshot, RoomListViewActions>;
|
2026-02-05 21:05:14 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Props for RoomListView component
|
|
|
|
|
*/
|
|
|
|
|
export interface RoomListViewProps {
|
|
|
|
|
/** The view model containing all data and callbacks */
|
|
|
|
|
vm: RoomListViewModel;
|
|
|
|
|
/** Render function for room avatar */
|
|
|
|
|
renderAvatar: (room: Room) => ReactNode;
|
|
|
|
|
/** Optional callback for keyboard events on the room list */
|
|
|
|
|
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Room list view component that manages filters, loading states, empty states, and the room list.
|
|
|
|
|
*/
|
|
|
|
|
export const RoomListView: React.FC<RoomListViewProps> = ({ vm, renderAvatar, onKeyDown }): JSX.Element => {
|
|
|
|
|
const snapshot = useViewModel(vm);
|
|
|
|
|
let listBody: ReactNode;
|
|
|
|
|
|
|
|
|
|
if (snapshot.isLoadingRooms) {
|
|
|
|
|
listBody = <RoomListLoadingSkeleton />;
|
|
|
|
|
} else if (snapshot.isRoomListEmpty) {
|
|
|
|
|
listBody = <RoomListEmptyStateView vm={vm} />;
|
|
|
|
|
} else {
|
|
|
|
|
listBody = <VirtualizedRoomListView vm={vm} renderAvatar={renderAvatar} onKeyDown={onKeyDown} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div>
|
|
|
|
|
<RoomListPrimaryFilters
|
|
|
|
|
filterIds={snapshot.filterIds}
|
|
|
|
|
activeFilterId={snapshot.activeFilterId}
|
|
|
|
|
onToggleFilter={vm.onToggleFilter}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-04-16 15:14:10 +02:00
|
|
|
<Flex direction="column" className={styles.list}>
|
|
|
|
|
{listBody}
|
|
|
|
|
{snapshot.toast && <RoomListToast type={snapshot.toast} onClose={vm.closeToast} />}
|
|
|
|
|
</Flex>
|
2026-02-05 21:05:14 +00:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|