@@ -11,6 +11,7 @@ import {
1111 type FilterId ,
1212 type RoomListViewActions ,
1313 type RoomListViewState ,
14+ _t ,
1415} from "@element-hq/web-shared-components" ;
1516import { type MatrixClient , type Room } from "matrix-js-sdk/src/matrix" ;
1617
@@ -20,37 +21,47 @@ import { type ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDel
2021import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload" ;
2122import SpaceStore from "../../stores/spaces/SpaceStore" ;
2223import RoomListStoreV3 , { RoomListStoreV3Event , type RoomsResult } from "../../stores/room-list-v3/RoomListStoreV3" ;
23- import { FilterKey } from "../../stores/room-list-v3/skip-list/filters" ;
24+ import { FilterEnum , type FilterKey } from "../../stores/room-list-v3/skip-list/filters" ;
2425import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore" ;
2526import { RoomListItemViewModel } from "./RoomListItemViewModel" ;
2627import { SdkContextClass } from "../../contexts/SDKContext" ;
2728import { hasCreateRoomRights } from "./utils" ;
2829import { keepIfSame } from "../../utils/keepIfSame" ;
30+ import { CHATS_TAG } from "../../stores/room-list-v3/SectionStore" ;
31+ import { DefaultTagID } from "../../stores/room-list-v3/skip-list/tag" ;
32+ import { RoomListSectionHeaderViewModel } from "./RoomListSectionHeaderViewModel" ;
2933
3034interface RoomListViewModelProps {
3135 client : MatrixClient ;
3236}
3337
34- const filterKeyToIdMap : Map < FilterKey , FilterId > = new Map ( [
35- [ FilterKey . UnreadFilter , "unread" ] ,
36- [ FilterKey . PeopleFilter , "people" ] ,
37- [ FilterKey . RoomsFilter , "rooms" ] ,
38- [ FilterKey . FavouriteFilter , "favourite" ] ,
39- [ FilterKey . MentionsFilter , "mentions" ] ,
40- [ FilterKey . InvitesFilter , "invites" ] ,
41- [ FilterKey . LowPriorityFilter , "low_priority" ] ,
38+ const filterKeyToIdMap : Map < FilterEnum , FilterId > = new Map ( [
39+ [ FilterEnum . UnreadFilter , "unread" ] ,
40+ [ FilterEnum . PeopleFilter , "people" ] ,
41+ [ FilterEnum . RoomsFilter , "rooms" ] ,
42+ [ FilterEnum . FavouriteFilter , "favourite" ] ,
43+ [ FilterEnum . MentionsFilter , "mentions" ] ,
44+ [ FilterEnum . InvitesFilter , "invites" ] ,
45+ [ FilterEnum . LowPriorityFilter , "low_priority" ] ,
4246] ) ;
4347
48+ const TAG_TO_TITLE_MAP : Record < string , string > = {
49+ [ DefaultTagID . Favourite ] : _t ( "room_list|section|favourites" ) ,
50+ [ CHATS_TAG ] : _t ( "room_list|section|chats" ) ,
51+ [ DefaultTagID . LowPriority ] : _t ( "room_list|section|low_priority" ) ,
52+ } ;
53+
4454export class RoomListViewModel
4555 extends BaseViewModel < RoomListViewSnapshot , RoomListViewModelProps >
4656 implements RoomListViewActions
4757{
4858 // State tracking
49- private activeFilter : FilterKey | undefined = undefined ;
59+ private activeFilter : FilterEnum | undefined = undefined ;
5060 private roomsResult : RoomsResult ;
5161 private lastActiveRoomIndex : number | undefined = undefined ;
5262
5363 // Child view model management
64+ private roomSectionHeaderViewModels = new Map < string , RoomListSectionHeaderViewModel > ( ) ;
5465 private roomItemViewModels = new Map < string , RoomListItemViewModel > ( ) ;
5566 private roomsMap = new Map < string , Room > ( ) ;
5667
@@ -61,22 +72,26 @@ export class RoomListViewModel
6172 const roomsResult = RoomListStoreV3 . instance . getSortedRoomsInActiveSpace ( undefined ) ;
6273 const canCreateRoom = hasCreateRoomRights ( props . client , activeSpace ) ;
6374 const filterIds = [ ...filterKeyToIdMap . values ( ) ] ;
64- const roomIds = roomsResult . rooms . map ( ( room ) => room . roomId ) ;
65- const sections = [ { id : "all" , roomIds } ] ;
75+ const sections = roomsResult . sections . map ( ( { tag, rooms } ) => ( {
76+ id : tag ,
77+ roomIds : rooms . map ( ( room ) => room . roomId ) ,
78+ } ) ) ;
79+
80+ const isRoomListEmpty = roomsResult . sections . every ( ( section ) => section . rooms . length === 0 ) ;
81+ const isFlatList = roomsResult . sections . length === 1 && roomsResult . sections [ 0 ] . tag == CHATS_TAG ;
6682
6783 super ( props , {
6884 // Initial view state - start with empty, will populate in async init
6985 isLoadingRooms : RoomListStoreV3 . instance . isLoadingRooms ,
70- isRoomListEmpty : roomsResult . rooms . length === 0 ,
86+ isRoomListEmpty,
7187 filterIds,
7288 activeFilterId : undefined ,
7389 roomListState : {
7490 activeRoomIndex : undefined ,
7591 spaceId : roomsResult . spaceId ,
7692 filterKeys : undefined ,
7793 } ,
78- // Until we implement sections, this view model only supports the flat list mode
79- isFlatList : true ,
94+ isFlatList,
8095 sections,
8196 canCreateRoom,
8297 } ) ;
@@ -147,7 +162,7 @@ export class RoomListViewModel
147162 */
148163 private updateRoomsMap ( roomsResult : RoomsResult ) : void {
149164 this . roomsMap . clear ( ) ;
150- for ( const room of roomsResult . rooms ) {
165+ for ( const room of roomsResult . sections . flatMap ( ( section ) => section . rooms ) ) {
151166 this . roomsMap . set ( room . roomId , room ) ;
152167 }
153168 }
@@ -167,7 +182,7 @@ export class RoomListViewModel
167182 * Get the ordered list of room IDs.
168183 */
169184 public get roomIds ( ) : string [ ] {
170- return this . roomsResult . rooms . map ( ( room ) => room . roomId ) ;
185+ return this . roomsResult . sections . flatMap ( ( section ) => section . rooms ) . map ( ( room ) => room . roomId ) ;
171186 }
172187
173188 /**
@@ -199,13 +214,22 @@ export class RoomListViewModel
199214 return viewModel ;
200215 }
201216
202- /**
203- * Not implemented - this view model does not support sections.
204- * Flat list mode is forced so this method is never be called.
205- * @throw Error if called
206- */
207- public getSectionHeaderViewModel ( ) : never {
208- throw new Error ( "Sections are not supported in this room list" ) ;
217+ public getSectionHeaderViewModel ( tag : string ) : RoomListSectionHeaderViewModel {
218+ if ( this . roomSectionHeaderViewModels . has ( tag ) ) return this . roomSectionHeaderViewModels . get ( tag ) ! ;
219+
220+ const title = TAG_TO_TITLE_MAP [ tag ] || tag ;
221+ const viewModel = new RoomListSectionHeaderViewModel ( {
222+ tag,
223+ title,
224+ onToggleExpanded : ( ) => {
225+ // TODO implement expand/collapse logic
226+ // This will require changes to the data structure to track which sections are expanded, and to slice roomIds accordingly
227+ // For now we can just log the click to verify the button is working
228+ console . log ( `Toggled section ${ tag } ` ) ;
229+ } ,
230+ } ) ;
231+ this . roomSectionHeaderViewModels . set ( tag , viewModel ) ;
232+ return viewModel ;
209233 }
210234
211235 /**
@@ -250,7 +274,7 @@ export class RoomListViewModel
250274 if ( ! currentRoomId ) return ;
251275
252276 const { delta, unread } = payload ;
253- const rooms = this . roomsResult . rooms ;
277+ const rooms = this . roomsResult . sections . flatMap ( ( section ) => section . rooms ) ;
254278
255279 const filteredRooms = unread
256280 ? // Filter the rooms to only include unread ones and the active room
@@ -338,7 +362,9 @@ export class RoomListViewModel
338362 return undefined ;
339363 }
340364
341- const index = this . roomsResult . rooms . findIndex ( ( room ) => room . roomId === roomId ) ;
365+ const index = this . roomsResult . sections
366+ . flatMap ( ( section ) => section . rooms )
367+ . findIndex ( ( room ) => room . roomId === roomId ) ;
342368 return index >= 0 ? index : undefined ;
343369 }
344370
@@ -350,47 +376,47 @@ export class RoomListViewModel
350376 * @param roomId - The room ID to apply sticky logic for (can be null/undefined)
351377 * @returns The modified rooms array with sticky positioning applied
352378 */
353- private applyStickyRoom ( isRoomChange : boolean , roomId : string | null | undefined ) : Room [ ] {
354- const rooms = this . roomsResult . rooms ;
355-
356- if ( ! roomId ) {
357- return rooms ;
358- }
359-
360- const newIndex = rooms . findIndex ( ( room ) => room . roomId === roomId ) ;
361- const oldIndex = this . lastActiveRoomIndex ;
362-
363- // When opening another room, the index should obviously change
364- if ( isRoomChange ) {
365- return rooms ;
366- }
367-
368- // If oldIndex is undefined, then there was no active room before
369- // Similarly, if newIndex is -1, the active room is not in the current list
370- if ( newIndex === - 1 || oldIndex === undefined ) {
371- return rooms ;
372- }
373-
374- // If the index hasn't changed, we have nothing to do
375- if ( newIndex === oldIndex ) {
376- return rooms ;
377- }
378-
379- // If the old index falls out of the bounds of the rooms array
380- // (usually because rooms were removed), we can no longer place
381- // the active room in the same old index
382- if ( oldIndex > rooms . length - 1 ) {
383- return rooms ;
384- }
385-
386- // Making the active room sticky is as simple as removing it from
387- // its new index and placing it in the old index
388- const newRooms = [ ...rooms ] ;
389- const [ stickyRoom ] = newRooms . splice ( newIndex , 1 ) ;
390- newRooms . splice ( oldIndex , 0 , stickyRoom ) ;
391-
392- return newRooms ;
393- }
379+ // private applyStickyRoom(isRoomChange: boolean, roomId: string | null | undefined): Room[] {
380+ // const rooms = this.roomsResult.rooms;
381+
382+ // if (!roomId) {
383+ // return rooms;
384+ // }
385+
386+ // const newIndex = rooms.findIndex((room) => room.roomId === roomId);
387+ // const oldIndex = this.lastActiveRoomIndex;
388+
389+ // // When opening another room, the index should obviously change
390+ // if (isRoomChange) {
391+ // return rooms;
392+ // }
393+
394+ // // If oldIndex is undefined, then there was no active room before
395+ // // Similarly, if newIndex is -1, the active room is not in the current list
396+ // if (newIndex === -1 || oldIndex === undefined) {
397+ // return rooms;
398+ // }
399+
400+ // // If the index hasn't changed, we have nothing to do
401+ // if (newIndex === oldIndex) {
402+ // return rooms;
403+ // }
404+
405+ // // If the old index falls out of the bounds of the rooms array
406+ // // (usually because rooms were removed), we can no longer place
407+ // // the active room in the same old index
408+ // if (oldIndex > rooms.length - 1) {
409+ // return rooms;
410+ // }
411+
412+ // // Making the active room sticky is as simple as removing it from
413+ // // its new index and placing it in the old index
414+ // const newRooms = [...rooms];
415+ // const [stickyRoom] = newRooms.splice(newIndex, 1);
416+ // newRooms.splice(oldIndex, 0, stickyRoom);
417+
418+ // return newRooms;
419+ // }
394420
395421 private async updateRoomListData (
396422 isRoomChange : boolean = false ,
@@ -400,14 +426,15 @@ export class RoomListViewModel
400426 // Use override if provided (e.g., during space changes), otherwise fall back to RoomViewStore
401427 const roomId = roomIdOverride ?? SdkContextClass . instance . roomViewStore . getRoomId ( ) ;
402428
429+ // TODO to implement for sections
403430 // Apply sticky room logic to keep selected room at same position
404- const stickyRooms = this . applyStickyRoom ( isRoomChange , roomId ) ;
431+ // const stickyRooms = this.applyStickyRoom(isRoomChange, roomId);
405432
406433 // Update roomsResult with sticky rooms
407- this . roomsResult = {
408- ...this . roomsResult ,
409- rooms : stickyRooms ,
410- } ;
434+ // this.roomsResult = {
435+ // ...this.roomsResult,
436+ // rooms: stickyRooms,
437+ // };
411438
412439 // Rebuild roomsMap with the reordered rooms
413440 this . updateRoomsMap ( this . roomsResult ) ;
@@ -420,8 +447,11 @@ export class RoomListViewModel
420447
421448 // Build the complete state atomically to ensure consistency
422449 // roomIds and roomListState must always be in sync
423- const roomIds = this . roomIds ;
424- const sections = [ { id : "all" , roomIds } ] ;
450+ //const roomIds = this.roomIds;
451+ const sections = this . roomsResult . sections . map ( ( { tag, rooms } ) => ( {
452+ id : tag ,
453+ roomIds : rooms . map ( ( room ) => room . roomId ) ,
454+ } ) ) ;
425455
426456 // Update filter keys - only update if they have actually changed to prevent unnecessary re-renders of the room list
427457 const previousFilterKeys = this . snapshot . current . roomListState . filterKeys ;
@@ -433,7 +463,7 @@ export class RoomListViewModel
433463 } ;
434464
435465 const activeFilterId = this . activeFilter !== undefined ? filterKeyToIdMap . get ( this . activeFilter ) : undefined ;
436- const isRoomListEmpty = roomIds . length === 0 ;
466+ const isRoomListEmpty = this . roomsResult . sections . every ( ( section ) => section . rooms . length === 0 ) ;
437467 const isLoadingRooms = RoomListStoreV3 . instance . isLoadingRooms ;
438468
439469 // Single atomic snapshot update
0 commit comments