* 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
118 lines
4.1 KiB
TypeScript
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 };
|
|
}
|