@@ -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,41 @@ 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
4448export class RoomListViewModel
4549 extends BaseViewModel < RoomListViewSnapshot , RoomListViewModelProps >
4650 implements RoomListViewActions
4751{
4852 // State tracking
49- private activeFilter : FilterKey | undefined = undefined ;
53+ private activeFilter : FilterEnum | undefined = undefined ;
5054 private roomsResult : RoomsResult ;
5155 private lastActiveRoomIndex : number | undefined = undefined ;
5256
5357 // Child view model management
58+ private roomSectionHeaderViewModels = new Map < string , RoomListSectionHeaderViewModel > ( ) ;
5459 private roomItemViewModels = new Map < string , RoomListItemViewModel > ( ) ;
5560 private roomsMap = new Map < string , Room > ( ) ;
5661
@@ -61,22 +66,26 @@ export class RoomListViewModel
6166 const roomsResult = RoomListStoreV3 . instance . getSortedRoomsInActiveSpace ( undefined ) ;
6267 const canCreateRoom = hasCreateRoomRights ( props . client , activeSpace ) ;
6368 const filterIds = [ ...filterKeyToIdMap . values ( ) ] ;
64- const roomIds = roomsResult . rooms . map ( ( room ) => room . roomId ) ;
65- const sections = [ { id : "all" , roomIds } ] ;
69+ const sections = roomsResult . sections . map ( ( { tag, rooms } ) => ( {
70+ id : tag ,
71+ roomIds : rooms . map ( ( room ) => room . roomId ) ,
72+ } ) ) ;
73+
74+ const isRoomListEmpty = roomsResult . sections . every ( ( section ) => section . rooms . length === 0 ) ;
75+ const isFlatList = roomsResult . sections . length === 1 && roomsResult . sections [ 0 ] . tag == CHATS_TAG ;
6676
6777 super ( props , {
6878 // Initial view state - start with empty, will populate in async init
6979 isLoadingRooms : RoomListStoreV3 . instance . isLoadingRooms ,
70- isRoomListEmpty : roomsResult . rooms . length === 0 ,
80+ isRoomListEmpty,
7181 filterIds,
7282 activeFilterId : undefined ,
7383 roomListState : {
7484 activeRoomIndex : undefined ,
7585 spaceId : roomsResult . spaceId ,
7686 filterKeys : undefined ,
7787 } ,
78- // Until we implement sections, this view model only supports the flat list mode
79- isFlatList : true ,
88+ isFlatList,
8089 sections,
8190 canCreateRoom,
8291 } ) ;
@@ -147,7 +156,7 @@ export class RoomListViewModel
147156 */
148157 private updateRoomsMap ( roomsResult : RoomsResult ) : void {
149158 this . roomsMap . clear ( ) ;
150- for ( const room of roomsResult . rooms ) {
159+ for ( const room of roomsResult . sections . flatMap ( ( section ) => section . rooms ) ) {
151160 this . roomsMap . set ( room . roomId , room ) ;
152161 }
153162 }
@@ -167,7 +176,7 @@ export class RoomListViewModel
167176 * Get the ordered list of room IDs.
168177 */
169178 public get roomIds ( ) : string [ ] {
170- return this . roomsResult . rooms . map ( ( room ) => room . roomId ) ;
179+ return this . roomsResult . sections . flatMap ( ( section ) => section . rooms ) . map ( ( room ) => room . roomId ) ;
171180 }
172181
173182 /**
@@ -199,13 +208,22 @@ export class RoomListViewModel
199208 return viewModel ;
200209 }
201210
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" ) ;
211+ public getSectionHeaderViewModel ( tag : string ) : RoomListSectionHeaderViewModel {
212+ if ( this . roomSectionHeaderViewModels . has ( tag ) ) return this . roomSectionHeaderViewModels . get ( tag ) ! ;
213+
214+ const title = TAG_TO_TITLE_MAP [ tag ] || tag ;
215+ const viewModel = new RoomListSectionHeaderViewModel ( {
216+ tag,
217+ title,
218+ onToggleExpanded : ( ) => {
219+ // TODO implement expand/collapse logic
220+ // This will require changes to the data structure to track which sections are expanded, and to slice roomIds accordingly
221+ // For now we can just log the click to verify the button is working
222+ console . log ( `Toggled section ${ tag } ` ) ;
223+ } ,
224+ } ) ;
225+ this . roomSectionHeaderViewModels . set ( tag , viewModel ) ;
226+ return viewModel ;
209227 }
210228
211229 /**
@@ -250,7 +268,7 @@ export class RoomListViewModel
250268 if ( ! currentRoomId ) return ;
251269
252270 const { delta, unread } = payload ;
253- const rooms = this . roomsResult . rooms ;
271+ const rooms = this . roomsResult . sections . flatMap ( ( section ) => section . rooms ) ;
254272
255273 const filteredRooms = unread
256274 ? // Filter the rooms to only include unread ones and the active room
@@ -338,7 +356,9 @@ export class RoomListViewModel
338356 return undefined ;
339357 }
340358
341- const index = this . roomsResult . rooms . findIndex ( ( room ) => room . roomId === roomId ) ;
359+ const index = this . roomsResult . sections
360+ . flatMap ( ( section ) => section . rooms )
361+ . findIndex ( ( room ) => room . roomId === roomId ) ;
342362 return index >= 0 ? index : undefined ;
343363 }
344364
@@ -350,47 +370,47 @@ export class RoomListViewModel
350370 * @param roomId - The room ID to apply sticky logic for (can be null/undefined)
351371 * @returns The modified rooms array with sticky positioning applied
352372 */
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- }
373+ // private applyStickyRoom(isRoomChange: boolean, roomId: string | null | undefined): Room[] {
374+ // const rooms = this.roomsResult.rooms;
375+
376+ // if (!roomId) {
377+ // return rooms;
378+ // }
379+
380+ // const newIndex = rooms.findIndex((room) => room.roomId === roomId);
381+ // const oldIndex = this.lastActiveRoomIndex;
382+
383+ // // When opening another room, the index should obviously change
384+ // if (isRoomChange) {
385+ // return rooms;
386+ // }
387+
388+ // // If oldIndex is undefined, then there was no active room before
389+ // // Similarly, if newIndex is -1, the active room is not in the current list
390+ // if (newIndex === -1 || oldIndex === undefined) {
391+ // return rooms;
392+ // }
393+
394+ // // If the index hasn't changed, we have nothing to do
395+ // if (newIndex === oldIndex) {
396+ // return rooms;
397+ // }
398+
399+ // // If the old index falls out of the bounds of the rooms array
400+ // // (usually because rooms were removed), we can no longer place
401+ // // the active room in the same old index
402+ // if (oldIndex > rooms.length - 1) {
403+ // return rooms;
404+ // }
405+
406+ // // Making the active room sticky is as simple as removing it from
407+ // // its new index and placing it in the old index
408+ // const newRooms = [...rooms];
409+ // const [stickyRoom] = newRooms.splice(newIndex, 1);
410+ // newRooms.splice(oldIndex, 0, stickyRoom);
411+
412+ // return newRooms;
413+ // }
394414
395415 private async updateRoomListData (
396416 isRoomChange : boolean = false ,
@@ -400,14 +420,15 @@ export class RoomListViewModel
400420 // Use override if provided (e.g., during space changes), otherwise fall back to RoomViewStore
401421 const roomId = roomIdOverride ?? SdkContextClass . instance . roomViewStore . getRoomId ( ) ;
402422
423+ // TODO to implement for sections
403424 // Apply sticky room logic to keep selected room at same position
404- const stickyRooms = this . applyStickyRoom ( isRoomChange , roomId ) ;
425+ // const stickyRooms = this.applyStickyRoom(isRoomChange, roomId);
405426
406427 // Update roomsResult with sticky rooms
407- this . roomsResult = {
408- ...this . roomsResult ,
409- rooms : stickyRooms ,
410- } ;
428+ // this.roomsResult = {
429+ // ...this.roomsResult,
430+ // rooms: stickyRooms,
431+ // };
411432
412433 // Rebuild roomsMap with the reordered rooms
413434 this . updateRoomsMap ( this . roomsResult ) ;
@@ -420,8 +441,11 @@ export class RoomListViewModel
420441
421442 // Build the complete state atomically to ensure consistency
422443 // roomIds and roomListState must always be in sync
423- const roomIds = this . roomIds ;
424- const sections = [ { id : "all" , roomIds } ] ;
444+ //const roomIds = this.roomIds;
445+ const sections = this . roomsResult . sections . map ( ( { tag, rooms } ) => ( {
446+ id : tag ,
447+ roomIds : rooms . map ( ( room ) => room . roomId ) ,
448+ } ) ) ;
425449
426450 // Update filter keys - only update if they have actually changed to prevent unnecessary re-renders of the room list
427451 const previousFilterKeys = this . snapshot . current . roomListState . filterKeys ;
@@ -433,7 +457,7 @@ export class RoomListViewModel
433457 } ;
434458
435459 const activeFilterId = this . activeFilter !== undefined ? filterKeyToIdMap . get ( this . activeFilter ) : undefined ;
436- const isRoomListEmpty = roomIds . length === 0 ;
460+ const isRoomListEmpty = this . roomsResult . sections . every ( ( section ) => section . rooms . length === 0 ) ;
437461 const isLoadingRooms = RoomListStoreV3 . instance . isLoadingRooms ;
438462
439463 // Single atomic snapshot update
0 commit comments