From 0f68d59798fcb539f3f8ed1222d311077a110fb5 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 4 Dec 2025 15:35:16 +0100 Subject: [PATCH] Fix spaces support in UI --- .../matrix/matrix_space_controller.rs | 6 +-- .../src/api/matrix/MatrixApiSpace.ts | 40 +++++++++++++++++++ .../widgets/messages/MainMessagesWidget.tsx | 18 ++++++++- .../src/widgets/messages/SpaceSelector.tsx | 4 +- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 matrixgw_frontend/src/api/matrix/MatrixApiSpace.ts diff --git a/matrixgw_backend/src/controllers/matrix/matrix_space_controller.rs b/matrixgw_backend/src/controllers/matrix/matrix_space_controller.rs index 313264e..cd7538a 100644 --- a/matrixgw_backend/src/controllers/matrix/matrix_space_controller.rs +++ b/matrixgw_backend/src/controllers/matrix/matrix_space_controller.rs @@ -7,11 +7,11 @@ use std::collections::HashMap; /// Get space hierarchy pub async fn hierarchy(client: MatrixClientExtractor) -> HttpResult { + let spaces = client.client.client.joined_space_rooms(); let space_service = SpaceService::new(client.client.client); - let spaces = space_service.joined_spaces().await; let mut hierarchy = HashMap::new(); for space in spaces { - let rooms = space_service.space_room_list(space.room_id.to_owned()); + let rooms = space_service.space_room_list(space.room_id().to_owned()); while !matches!( rooms.pagination_state(), SpaceRoomListPaginationState::Idle { end_reached: true } @@ -20,7 +20,7 @@ pub async fn hierarchy(client: MatrixClientExtractor) -> HttpResult { } hierarchy.insert( - space.room_id.to_owned(), + space.room_id().to_owned(), rooms .rooms() .into_iter() diff --git a/matrixgw_frontend/src/api/matrix/MatrixApiSpace.ts b/matrixgw_frontend/src/api/matrix/MatrixApiSpace.ts new file mode 100644 index 0000000..ae9ddc8 --- /dev/null +++ b/matrixgw_frontend/src/api/matrix/MatrixApiSpace.ts @@ -0,0 +1,40 @@ +import { APIClient } from "../ApiClient"; + +export type SpaceHierarchy = Map; + +export class MatrixApiSpace { + /** + * Request Matrix space hierarchy + */ + static async Hierarchy(): Promise { + const hierarchy = new Map( + Object.entries( + ( + await APIClient.exec({ + method: "GET", + uri: "/matrix/space/hierarchy", + }) + ).data as { [s: string]: string[] } + ) + ) as SpaceHierarchy; + + // Simplify hierarchy + while (true) { + let changed = false; + for (const [roomid, children] of hierarchy) { + for (const child of children) { + if (!hierarchy.has(child)) continue; + hierarchy.set(roomid, [ + ...hierarchy.get(roomid)!, + ...hierarchy.get(child)!, + ]); + hierarchy.delete(child); + changed = true; + } + } + if (!changed) break; + } + + return hierarchy; + } +} diff --git a/matrixgw_frontend/src/widgets/messages/MainMessagesWidget.tsx b/matrixgw_frontend/src/widgets/messages/MainMessagesWidget.tsx index 78e032d..35311dd 100644 --- a/matrixgw_frontend/src/widgets/messages/MainMessagesWidget.tsx +++ b/matrixgw_frontend/src/widgets/messages/MainMessagesWidget.tsx @@ -6,6 +6,10 @@ import { type UsersMap, } from "../../api/matrix/MatrixApiProfile"; import { MatrixApiRoom, type Room } from "../../api/matrix/MatrixApiRoom"; +import { + MatrixApiSpace, + type SpaceHierarchy, +} from "../../api/matrix/MatrixApiSpace"; import { MatrixSyncApi } from "../../api/MatrixSyncApi"; import type { WsMessage } from "../../api/WsApi"; import { RoomEventsManager } from "../../utils/RoomEventsManager"; @@ -19,13 +23,19 @@ import { SpaceSelector } from "./SpaceSelector"; export function MainMessageWidget(): React.ReactElement { const [rooms, setRooms] = React.useState(); + const [hierarchy, setHierarchy] = React.useState< + SpaceHierarchy | undefined + >(); const [users, setUsers] = React.useState(); const loadRoomsList = async () => { await MatrixSyncApi.Start(); const rooms = await MatrixApiRoom.ListJoined(); + const hierarchy = await MatrixApiSpace.Hierarchy(); + setRooms(rooms); + setHierarchy(hierarchy); // Get the list of users in rooms const users = rooms.reduce((prev, r) => { @@ -40,11 +50,12 @@ export function MainMessageWidget(): React.ReactElement { ( setRooms((r) => cb(r!))} /> @@ -55,6 +66,7 @@ export function MainMessageWidget(): React.ReactElement { function MainMessageWidgetInner(p: { rooms: Room[]; + hierarchy: SpaceHierarchy; users: UsersMap; onRoomsListUpdate: (cb: (a: Room[]) => Room[]) => void; }): React.ReactElement { @@ -65,7 +77,9 @@ function MainMessageWidgetInner(p: { const spaceRooms = React.useMemo(() => { return p.rooms - .filter((r) => !r.is_space && (!space || r.parents.includes(space))) + .filter( + (r) => !r.is_space && (!space || p.hierarchy.get(space)?.includes(r.id)) + ) .sort( (a, b) => (b.latest_event?.time ?? 0) - (a.latest_event?.time ?? 0) ); diff --git a/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx b/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx index 1557b16..2e3cabc 100644 --- a/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx +++ b/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx @@ -3,16 +3,18 @@ import { Button } from "@mui/material"; import React from "react"; import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; import type { Room } from "../../api/matrix/MatrixApiRoom"; +import type { SpaceHierarchy } from "../../api/matrix/MatrixApiSpace"; import { RoomIcon } from "./RoomIcon"; export function SpaceSelector(p: { rooms: Room[]; + hierarchy: SpaceHierarchy; users: UsersMap; selectedSpace?: string; onChange: (space?: string) => void; }): React.ReactElement { const spaces = React.useMemo( - () => p.rooms.filter((r) => r.is_space), + () => p.rooms.filter((r) => r.is_space && p.hierarchy.has(r.id)), [p.rooms] );