Display the list of spaces
This commit is contained in:
5
matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts
Normal file
5
matrixgw_frontend/src/api/matrix/MatrixApiEvent.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface MatrixEvent {
|
||||
id: string;
|
||||
time: number;
|
||||
sender: string;
|
||||
}
|
||||
12
matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts
Normal file
12
matrixgw_frontend/src/api/matrix/MatrixApiMedia.ts
Normal file
@@ -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}`;
|
||||
}
|
||||
}
|
||||
26
matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts
Normal file
26
matrixgw_frontend/src/api/matrix/MatrixApiProfile.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { APIClient } from "../ApiClient";
|
||||
|
||||
export interface UserProfile {
|
||||
user_id: string;
|
||||
display_name?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
export type UsersMap = Map<string, UserProfile>;
|
||||
|
||||
export class MatrixApiProfile {
|
||||
/**
|
||||
* Get multiple profiles information
|
||||
*/
|
||||
static async GetMultiple(ids: string[]): Promise<UsersMap> {
|
||||
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]));
|
||||
}
|
||||
}
|
||||
27
matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts
Normal file
27
matrixgw_frontend/src/api/matrix/MatrixApiRoom.ts
Normal file
@@ -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<Room[]> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
method: "GET",
|
||||
uri: "/matrix/room/joined",
|
||||
})
|
||||
).data;
|
||||
}
|
||||
}
|
||||
@@ -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 <NotLinkedAccountMessage />;
|
||||
|
||||
return (
|
||||
<p>
|
||||
Todo home route{" "}
|
||||
<AsyncWidget
|
||||
loadKey={1}
|
||||
errMsg="Failed to start sync thread!"
|
||||
load={MatrixSyncApi.Start}
|
||||
build={() => <>sync started</>}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
return <MainMessageWidget />;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
<DashboardHeader
|
||||
menuOpen={isNavigationExpanded}
|
||||
onToggleMenu={handleToggleHeaderMenu}
|
||||
/>
|
||||
<Box
|
||||
ref={layoutRef}
|
||||
sx={{
|
||||
@@ -115,10 +119,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<DashboardHeader
|
||||
menuOpen={isNavigationExpanded}
|
||||
onToggleMenu={handleToggleHeaderMenu}
|
||||
/>
|
||||
<DashboardSidebar
|
||||
expanded={isNavigationExpanded}
|
||||
setExpanded={setIsNavigationExpanded}
|
||||
@@ -132,7 +132,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ displayPrint: "none" }} />
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function DashboardHeader({
|
||||
);
|
||||
|
||||
return (
|
||||
<AppBar color="inherit" position="absolute" sx={{ displayPrint: "none" }}>
|
||||
<AppBar color="inherit" position="static" sx={{ displayPrint: "none" }}>
|
||||
<Toolbar sx={{ backgroundColor: "inherit", mx: { xs: -0.75, sm: -1 } }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -74,7 +74,6 @@ export default function DashboardSidebar({
|
||||
const getDrawerContent = React.useCallback(
|
||||
(viewport: "phone" | "tablet" | "desktop") => (
|
||||
<React.Fragment>
|
||||
<Toolbar />
|
||||
<Box
|
||||
component="nav"
|
||||
aria-label={`${viewport.charAt(0).toUpperCase()}${viewport.slice(1)}`}
|
||||
@@ -87,6 +86,7 @@ export default function DashboardSidebar({
|
||||
scrollbarGutter: mini ? "stable" : "auto",
|
||||
overflowX: "hidden",
|
||||
pt: !mini ? 0 : 2,
|
||||
paddingTop: 0,
|
||||
...(hasDrawerTransitions
|
||||
? getDrawerSxTransitionMixin(isFullyExpanded, "padding")
|
||||
: {}),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import {
|
||||
MatrixApiProfile,
|
||||
type UsersMap,
|
||||
} from "../../api/matrix/MatrixApiProfile";
|
||||
import { MatrixApiRoom, type Room } from "../../api/matrix/MatrixApiRoom";
|
||||
import { MatrixSyncApi } from "../../api/MatrixSyncApi";
|
||||
import { AsyncWidget } from "../AsyncWidget";
|
||||
import { SpaceSelector } from "./SpaceSelector";
|
||||
import { Divider } from "@mui/material";
|
||||
|
||||
export function MainMessageWidget(): React.ReactElement {
|
||||
const [rooms, setRooms] = React.useState<Room[] | undefined>();
|
||||
const [users, setUsers] = React.useState<UsersMap | undefined>();
|
||||
|
||||
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<string>());
|
||||
|
||||
setUsers(await MatrixApiProfile.GetMultiple([...users]));
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={1}
|
||||
load={load}
|
||||
ready={!!rooms && !!users}
|
||||
errMsg="Failed to initialize messaging component!"
|
||||
build={() => <_MainMessageWidget rooms={rooms!} users={users!} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function _MainMessageWidget(p: {
|
||||
rooms: Room[];
|
||||
users: UsersMap;
|
||||
}): React.ReactElement {
|
||||
const [space, setSpace] = React.useState<string | undefined>();
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100%" }}>
|
||||
<SpaceSelector {...p} selectedSpace={space} onChange={setSpace} />
|
||||
<Divider orientation="vertical" />
|
||||
<span style={{ flex: 1 }}>todo</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
32
matrixgw_frontend/src/widgets/messages/RoomIcon.tsx
Normal file
32
matrixgw_frontend/src/widgets/messages/RoomIcon.tsx
Normal file
@@ -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 <GroupIcon />;
|
||||
else
|
||||
return (
|
||||
<img
|
||||
src={MatrixApiMedia.MediaURL(url, true)}
|
||||
style={{ maxWidth: "1em", maxHeight: "1em" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
53
matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx
Normal file
53
matrixgw_frontend/src/widgets/messages/SpaceSelector.tsx
Normal file
@@ -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 (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<SpaceButton
|
||||
icon={<HomeIcon />}
|
||||
onClick={() => p.onChange()}
|
||||
selected={p.selectedSpace === undefined}
|
||||
/>
|
||||
|
||||
{spaces.map((s) => (
|
||||
<SpaceButton
|
||||
key={s.id}
|
||||
icon={<RoomIcon room={s} {...p} />}
|
||||
onClick={() => p.onChange(s.id)}
|
||||
selected={p.selectedSpace === s.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SpaceButton(p: {
|
||||
selected?: boolean;
|
||||
icon: React.ReactElement;
|
||||
onClick: () => void;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<Button
|
||||
variant={p.selected ? "contained" : "text"}
|
||||
style={{ margin: "2px 5px", padding: "25px 10px", fontSize: "200%" }}
|
||||
onClick={p.onClick}
|
||||
>
|
||||
{p.icon}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user