Display rooms list

This commit is contained in:
2025-11-24 17:50:31 +01:00
parent cce9b3de5d
commit 1f4e374e66
7 changed files with 148 additions and 21 deletions

View File

@@ -1,5 +1,7 @@
import { APIClient } from "../ApiClient";
import type { UserInfo } from "../AuthApi";
import type { MatrixEvent } from "./MatrixApiEvent";
import type { UsersMap } from "./MatrixApiProfile";
export interface Room {
id: string;
@@ -12,6 +14,32 @@ export interface Room {
latest_event?: MatrixEvent;
}
/**
* Find main member of room
*/
export function mainRoomMember(user: UserInfo, r: Room): string | undefined {
if (r.members.length <= 1) return r.members[0];
if (r.members.length < 2)
return r.members[0] == user.matrix_user_id ? r.members[1] : r.members[0];
return undefined;
}
/**
* Find room name
*/
export function roomName(user: UserInfo, r: Room, users: UsersMap): string {
if (r.name) return r.name;
const name = r.members
.filter((m) => m !== user.matrix_user_id)
.map((m) => users.get(m)?.display_name ?? m)
.join(",");
return name === "" ? "Empty room" : name;
}
export class MatrixApiRoom {
/**
* Get the list of joined rooms

View File

@@ -7,3 +7,12 @@ body,
#root {
height: 100%;
}
#root {
display: flex;
flex-direction: column;
}
#root > div {
flex: 1;
}

View File

@@ -114,8 +114,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
position: "relative",
display: "flex",
overflow: "hidden",
height: "100%",
width: "100%",
}}
>
<DashboardSidebar

View File

@@ -81,7 +81,11 @@ export default function DashboardHeader({
);
return (
<AppBar color="inherit" position="static" sx={{ displayPrint: "none" }}>
<AppBar
color="inherit"
position="static"
sx={{ displayPrint: "none", overflow: "hidden" }}
>
<Toolbar sx={{ backgroundColor: "inherit", mx: { xs: -0.75, sm: -1 } }}>
<Stack
direction="row"

View File

@@ -8,6 +8,7 @@ import { MatrixSyncApi } from "../../api/MatrixSyncApi";
import { AsyncWidget } from "../AsyncWidget";
import { SpaceSelector } from "./SpaceSelector";
import { Divider } from "@mui/material";
import { RoomSelector } from "./RoomSelector";
export function MainMessageWidget(): React.ReactElement {
const [rooms, setRooms] = React.useState<Room[] | undefined>();
@@ -44,10 +45,27 @@ function _MainMessageWidget(p: {
users: UsersMap;
}): React.ReactElement {
const [space, setSpace] = React.useState<string | undefined>();
const [room, setRoom] = React.useState<Room | undefined>();
const spaceRooms = React.useMemo(() => {
return p.rooms
.filter((r) => !r.is_space && (!space || r.parents.includes(space)))
.sort(
(a, b) => (b.latest_event?.time ?? 0) - (a.latest_event?.time ?? 0)
);
}, [space, p.rooms]);
return (
<div style={{ display: "flex", height: "100%" }}>
<SpaceSelector {...p} selectedSpace={space} onChange={setSpace} />
<Divider orientation="vertical" />
<RoomSelector
{...p}
rooms={spaceRooms}
currRoom={room}
onChange={setRoom}
/>
<Divider orientation="vertical" />
<span style={{ flex: 1 }}>todo</span>
</div>
);

View File

@@ -1,9 +1,12 @@
import { Icon } from "@mui/material";
import { Avatar } from "@mui/material";
import { MatrixApiMedia } from "../../api/matrix/MatrixApiMedia";
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
import type { Room } from "../../api/matrix/MatrixApiRoom";
import {
mainRoomMember,
roomName,
type Room,
} from "../../api/matrix/MatrixApiRoom";
import { useUserInfo } from "../dashboard/BaseAuthenticatedPage";
import GroupIcon from "@mui/icons-material/Group";
export function RoomIcon(p: {
room: Room;
@@ -13,20 +16,18 @@ export function RoomIcon(p: {
let url = p.room.avatar;
if (!url && p.room.members.length <= 1) url = p.room.members[0];
if (!url) {
const member = mainRoomMember(user.info, p.room);
if (member) url = p.users.get(member)?.avatar;
}
const name = roomName(user.info, p.room, p.users);
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" }}
/>
<Avatar
variant={p.room.is_space ? "square" : undefined}
src={url ? MatrixApiMedia.MediaURL(url, true) : undefined}
>
{name.slice(0, 1)}
</Avatar>
);
}

View File

@@ -0,0 +1,69 @@
import {
Chip,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
} from "@mui/material";
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
import { roomName, type Room } from "../../api/matrix/MatrixApiRoom";
import { useUserInfo } from "../dashboard/BaseAuthenticatedPage";
import { RoomIcon } from "./RoomIcon";
const ROOM_SELECTOR_WIDTH = "300px";
export function RoomSelector(p: {
users: UsersMap;
rooms: Room[];
currRoom?: Room;
onChange: (r: Room) => void;
}): React.ReactElement {
const user = useUserInfo();
if (p.rooms.length === 0)
return (
<div
style={{
width: ROOM_SELECTOR_WIDTH,
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
No room to display.
</div>
);
return (
<List
style={{
width: ROOM_SELECTOR_WIDTH,
}}
>
{p.rooms.map((r) => (
<ListItem
key={r.id}
secondaryAction={
r.number_unread_messages === 0 ? undefined : (
<Chip color="error" label={r.number_unread_messages} />
)
}
disablePadding
>
<ListItemButton
role={undefined}
onClick={() => p.onChange(r)}
dense
selected={p.currRoom?.id === r.id}
>
<ListItemIcon>
<RoomIcon room={r} {...p} />
</ListItemIcon>
<ListItemText primary={roomName(user.info, r, p.users)} />
</ListItemButton>
</ListItem>
))}
</List>
);
}