Files
element-web/src/components/viewmodels/roomlist/useStickyRoomList.tsx
R Midhun Suresh b54122884c RoomListViewModel: Make the active room sticky in the list (#29551)
* Add new hook for sticky room

This hook takes the filtered, sorted rooms and returns a new list of
rooms such that the active room is kept in the same index even when the
list has changes.

* Use new hook in view model

* Add tests

* Use single * in comments
2025-03-21 12:11:59 +00:00

118 lines
4.1 KiB
TypeScript

/*
* Copyright 2025 New Vector 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 { useCallback, useEffect, useState } from "react";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useDispatcher } from "../../../hooks/useDispatcher";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Optional } from "matrix-events-sdk";
function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
const index = rooms.findIndex((room) => room.roomId === roomId);
return index === -1 ? undefined : index;
}
function getRoomsWithStickyRoom(
rooms: Room[],
oldIndex: number | undefined,
newIndex: number | undefined,
isRoomChange: boolean,
): { newRooms: Room[]; newIndex: number | undefined } {
const updated = { newIndex, newRooms: rooms };
if (isRoomChange) {
/*
* When opening another room, the index should obviously change.
*/
return updated;
}
if (newIndex === undefined || oldIndex === undefined) {
/*
* If oldIndex is undefined, then there was no active room before.
* So nothing to do in regards to sticky room.
* Similarly, if newIndex is undefined, there's no active room anymore.
*/
return updated;
}
if (newIndex === oldIndex) {
/*
* If the index hasn't changed, we have nothing to do.
*/
return updated;
}
if (oldIndex > rooms.length - 1) {
/*
* If the old index falls out of the bounds of the rooms array
* (usually because rooms were removed), we can no longer place
* the active room in the same old index.
*/
return updated;
}
/*
* Making the active room sticky is as simple as removing it from
* its new index and placing it in the old index.
*/
const newRooms = [...rooms];
const [newRoom] = newRooms.splice(newIndex, 1);
newRooms.splice(oldIndex, 0, newRoom);
return { newIndex: oldIndex, newRooms };
}
interface StickyRoomListResult {
/**
* List of rooms with sticky active room.
*/
rooms: Room[];
/**
* Index of the active room in the room list.
*/
activeIndex: number | undefined;
}
/**
* - Provides a list of rooms such that the active room is sticky i.e the active room is kept
* in the same index even when the order of rooms in the list changes.
* - Provides the index of the active room.
* @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,
});
const updateRoomsAndIndex = useCallback(
(newRoomId?: string, 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 };
});
},
[rooms],
);
// Re-calculate the index when the active room has changed.
useDispatcher(dispatcher, (payload) => {
if (payload.action === Action.ActiveRoomChanged) updateRoomsAndIndex(payload.newRoomId, true);
});
// Re-calculate the index when the list of rooms has changed.
useEffect(() => {
updateRoomsAndIndex();
}, [rooms, updateRoomsAndIndex]);
return { activeIndex: listState.index, rooms: listState.roomsWithStickyRoom };
}