From 820b095be0b6493b97e504e2714c6c6732029e3f Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 24 Nov 2025 16:05:01 +0100 Subject: [PATCH] Display the list of spaces --- .../src/api/matrix/MatrixApiEvent.ts | 5 ++ .../src/api/matrix/MatrixApiMedia.ts | 12 +++++ .../src/api/matrix/MatrixApiProfile.ts | 26 +++++++++ .../src/api/matrix/MatrixApiRoom.ts | 27 ++++++++++ matrixgw_frontend/src/routes/HomeRoute.tsx | 15 +----- .../dashboard/BaseAuthenticatedPage.tsx | 11 ++-- .../src/widgets/dashboard/DashboardHeader.tsx | 2 +- .../widgets/dashboard/DashboardSidebar.tsx | 2 +- .../widgets/messages/MainMessagesWidget.tsx | 54 +++++++++++++++++++ .../src/widgets/messages/RoomIcon.tsx | 32 +++++++++++ .../src/widgets/messages/SpaceSelector.tsx | 53 ++++++++++++++++++ 11 files changed, 218 insertions(+), 21 deletions(-) create mode 100644 matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts create mode 100644 matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts create mode 100644 matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts create mode 100644 matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts create mode 100644 matrixgw_frontend/src/widgets/messages/MainMessagesWidget.tsx create mode 100644 matrixgw_frontend/src/widgets/messages/RoomIcon.tsx create mode 100644 matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx diff --git a/matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts b/matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts new file mode 100644 index 0000000..ee0dfd6 --- /dev/null +++ b/matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts @@ -0,0 +1,5 @@ +export interface MatrixEvent { + id: string; + time: number; + sender: string; +} diff --git a/matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts b/matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts new file mode 100644 index 0000000..7d455da --- /dev/null +++ b/matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts @@ -0,0 +1,12 @@ +import { APIClient } from "../ApiClient"; + +export class MatrixApiMedia { + /** + * Get media URL + */ + static MediaURL(url: string, thumbnail: boolean): string { + return `${APIClient.ActualBackendURL()}/matrix/media/${encodeURIComponent( + url + )}?thumbnail=${thumbnail}`; + } +} diff --git a/matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts b/matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts new file mode 100644 index 0000000..ccb39db --- /dev/null +++ b/matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts @@ -0,0 +1,26 @@ +import { APIClient } from "../ApiClient"; + +export interface UserProfile { + user_id: string; + display_name?: string; + avatar?: string; +} + +export type UsersMap = Map; + +export class MatrixApiProfile { + /** + * Get multiple profiles information + */ + static async GetMultiple(ids: string[]): Promise { + const list: UserProfile[] = ( + await APIClient.exec({ + method: "POST", + uri: "/matrix/profile/get_multiple", + jsonData: ids, + }) + ).data; + + return new Map(list.map((e) => [e.user_id, e])); + } +} diff --git a/matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts b/matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts new file mode 100644 index 0000000..39fa989 --- /dev/null +++ b/matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts @@ -0,0 +1,27 @@ +import { APIClient } from "../ApiClient"; +import type { MatrixEvent } from "./MatrixApiEvent"; + +export interface Room { + id: string; + name?: string; + members: string[]; + avatar?: string; + is_space?: boolean; + parents: string[]; + number_unread_messages: number; + latest_event?: MatrixEvent; +} + +export class MatrixApiRoom { + /** + * Get the list of joined rooms + */ + static async ListJoined(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/matrix/room/joined", + }) + ).data; + } +} diff --git a/matrixgw_frontend/src/routes/HomeRoute.tsx b/matrixgw_frontend/src/routes/HomeRoute.tsx index 829833c..f046f41 100644 --- a/matrixgw_frontend/src/routes/HomeRoute.tsx +++ b/matrixgw_frontend/src/routes/HomeRoute.tsx @@ -1,6 +1,5 @@ -import { MatrixSyncApi } from "../api/MatrixSyncApi"; -import { AsyncWidget } from "../widgets/AsyncWidget"; import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage"; +import { MainMessageWidget } from "../widgets/messages/MainMessagesWidget"; import { NotLinkedAccountMessage } from "../widgets/NotLinkedAccountMessage"; export function HomeRoute(): React.ReactElement { @@ -8,15 +7,5 @@ export function HomeRoute(): React.ReactElement { if (!user.info.matrix_account_connected) return ; - return ( -

- Todo home route{" "} - <>sync started} - /> -

- ); + return ; } diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx index 338fda7..0cbee66 100644 --- a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -1,4 +1,4 @@ -import { Button } from "@mui/material"; +import { AppBar, Button } from "@mui/material"; import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import Toolbar from "@mui/material/Toolbar"; @@ -105,6 +105,10 @@ export default function BaseAuthenticatedPage(): React.ReactElement { signOut, }} > + - - + ( - (); + const [users, setUsers] = React.useState(); + + const load = async () => { + await MatrixSyncApi.Start(); + + const rooms = await MatrixApiRoom.ListJoined(); + setRooms(rooms); + + // Get the list of users in rooms + const users = rooms.reduce((prev, r) => { + r.members.forEach((m) => prev.add(m)); + return prev; + }, new Set()); + + setUsers(await MatrixApiProfile.GetMultiple([...users])); + }; + + return ( + <_MainMessageWidget rooms={rooms!} users={users!} />} + /> + ); +} + +function _MainMessageWidget(p: { + rooms: Room[]; + users: UsersMap; +}): React.ReactElement { + const [space, setSpace] = React.useState(); + return ( +
+ + + todo +
+ ); +} diff --git a/matrixgw_frontend/src/widgets/messages/RoomIcon.tsx b/matrixgw_frontend/src/widgets/messages/RoomIcon.tsx new file mode 100644 index 0000000..6c49338 --- /dev/null +++ b/matrixgw_frontend/src/widgets/messages/RoomIcon.tsx @@ -0,0 +1,32 @@ +import { Icon } from "@mui/material"; +import { MatrixApiMedia } from "../../api/matrix/MatrixApiMedia"; +import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; +import type { Room } from "../../api/matrix/MatrixApiRoom"; +import { useUserInfo } from "../dashboard/BaseAuthenticatedPage"; +import GroupIcon from "@mui/icons-material/Group"; + +export function RoomIcon(p: { + room: Room; + users: UsersMap; +}): React.ReactElement { + const user = useUserInfo(); + + let url = p.room.avatar; + + if (!url && p.room.members.length <= 1) url = p.room.members[0]; + + if (!url && p.room.members.length < 2) + url = + p.room.members[0] == user.info.matrix_user_id + ? p.room.members[1] + : p.room.members[0]; + + if (!url) return ; + else + return ( + + ); +} diff --git a/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx b/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx new file mode 100644 index 0000000..8720325 --- /dev/null +++ b/matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx @@ -0,0 +1,53 @@ +import HomeIcon from "@mui/icons-material/Home"; +import { Button } from "@mui/material"; +import React from "react"; +import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; +import type { Room } from "../../api/matrix/MatrixApiRoom"; +import { RoomIcon } from "./RoomIcon"; + +export function SpaceSelector(p: { + rooms: Room[]; + users: UsersMap; + selectedSpace?: string; + onChange: (space?: string) => void; +}): React.ReactElement { + const spaces = React.useMemo( + () => p.rooms.filter((r) => r.is_space), + [p.rooms] + ); + + return ( +
+ } + onClick={() => p.onChange()} + selected={p.selectedSpace === undefined} + /> + + {spaces.map((s) => ( + } + onClick={() => p.onChange(s.id)} + selected={p.selectedSpace === s.id} + /> + ))} +
+ ); +} + +function SpaceButton(p: { + selected?: boolean; + icon: React.ReactElement; + onClick: () => void; +}): React.ReactElement { + return ( + + ); +}