Refactor rooms management

This commit is contained in:
2025-11-28 18:41:43 +01:00
parent a656c077bc
commit 1f22d5c41b
5 changed files with 111 additions and 86 deletions

View File

@@ -50,7 +50,10 @@ export class RoomEventsManager {
}
processWsMessage(m: WsMessage) {
if (m.room_id !== this.room.id) return false;
if (m.room_id !== this.room.id) {
console.debug("Not an event for current room.");
return false;
}
let data: MatrixEventData;
if (m.type === "RoomReactionEvent") {

View File

@@ -1,5 +1,6 @@
import { Divider } from "@mui/material";
import React from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import {
MatrixApiProfile,
type UsersMap,
@@ -7,6 +8,7 @@ import {
import { MatrixApiRoom, type Room } from "../../api/matrix/MatrixApiRoom";
import { MatrixSyncApi } from "../../api/MatrixSyncApi";
import type { WsMessage } from "../../api/WsApi";
import { RoomEventsManager } from "../../utils/RoomEventsManager";
import { AsyncWidget } from "../AsyncWidget";
import { useUserInfo } from "../dashboard/BaseAuthenticatedPage";
import { MatrixWS } from "./MatrixWS";
@@ -17,9 +19,8 @@ import { SpaceSelector } from "./SpaceSelector";
export function MainMessageWidget(): React.ReactElement {
const [rooms, setRooms] = React.useState<Room[] | undefined>();
const [users, setUsers] = React.useState<UsersMap | undefined>();
const user = useUserInfo();
const load = async () => {
const loadRoomsList = async () => {
await MatrixSyncApi.Start();
const rooms = await MatrixApiRoom.ListJoined();
@@ -34,37 +35,17 @@ export function MainMessageWidget(): React.ReactElement {
setUsers(await MatrixApiProfile.GetMultiple([...users]));
};
const handleEvent = (m: WsMessage) => {
// Add a new unread message
if (
m.type === "RoomMessageEvent" &&
!m.data["m.new_content"] &&
m.sender !== user.info.matrix_user_id
) {
setRooms((r) => {
const n = r ? [...r] : undefined;
const idx = n?.findIndex((el) => el.id === m.room_id);
if (idx)
n![idx] = {
...n![idx],
number_unread_messages: n![idx].number_unread_messages + 1,
};
return n;
});
}
};
return (
<AsyncWidget
loadKey={1}
load={load}
load={loadRoomsList}
ready={!!rooms && !!users}
errMsg="Failed to initialize messaging component!"
build={() => (
<_MainMessageWidget
rooms={rooms!}
users={users!}
onEvent={handleEvent}
onRoomsListUpdate={(cb) => setRooms((r) => cb(r!))}
/>
)}
/>
@@ -74,10 +55,12 @@ export function MainMessageWidget(): React.ReactElement {
function _MainMessageWidget(p: {
rooms: Room[];
users: UsersMap;
onEvent: (m: WsMessage) => void;
onRoomsListUpdate: (cb: (a: Room[]) => Room[]) => void;
}): React.ReactElement {
const user = useUserInfo();
const [space, setSpace] = React.useState<string | undefined>();
const [room, setRoom] = React.useState<Room | undefined>();
const [currentRoom, setCurrentRoom] = React.useState<Room | undefined>();
const spaceRooms = React.useMemo(() => {
return p.rooms
@@ -87,33 +70,96 @@ function _MainMessageWidget(p: {
);
}, [space, p.rooms]);
const [_refreshCount, setRefreshCount] = React.useState(0);
const [roomMgr, setRoomMgr] = React.useState<undefined | RoomEventsManager>();
const loadRoom = async () => {
setRoomMgr(undefined);
if (!currentRoom) {
console.warn("Cannot load manager for no room!");
return;
}
const messages = await MatrixApiEvent.GetRoomEvents(currentRoom);
const mgr = new RoomEventsManager(currentRoom!, messages);
setRoomMgr(mgr);
};
const handleWsEvent = (m: WsMessage) => {
// Process messages for current room
if (roomMgr?.processWsMessage(m)) {
console.info("Current room updated!");
setRefreshCount((c) => c + 1);
}
// Add a new unread message on left sidebar
if (
m.type === "RoomMessageEvent" &&
!m.data["m.new_content"] &&
m.sender !== user.info.matrix_user_id
) {
p.onRoomsListUpdate((r) => {
const n = [...r];
const idx = r.findIndex((el) => el.id === m.room_id);
if (idx)
n[idx] = {
...n[idx],
number_unread_messages: n[idx].number_unread_messages + 1,
};
return n;
});
}
};
return (
<div style={{ display: "flex", height: "100%" }}>
{/* Websocket */}
<div style={{ position: "absolute", right: "0px", padding: "10px" }}>
<MatrixWS onMessage={handleWsEvent} />
</div>
{/* Space selector */}
<SpaceSelector {...p} selectedSpace={space} onChange={setSpace} />
{/* Separator */}
<Divider orientation="vertical" />
{/* Room selector */}
<RoomSelector
{...p}
rooms={spaceRooms}
currRoom={room}
onChange={setRoom}
currRoom={currentRoom}
onChange={setCurrentRoom}
/>
{/* Separator */}
<Divider orientation="vertical" />
{room === undefined && (
<>
<MatrixWS onMessage={p.onEvent} />
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flex: 1,
}}
>
No room selected.
</div>
</>
{/* If no room is selected */}
{currentRoom === undefined && (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flex: 1,
}}
>
No room selected.
</div>
)}
{/* In case of room */}
{currentRoom && (
<AsyncWidget
loadKey={currentRoom.id}
ready={!!roomMgr}
load={loadRoom}
errMsg="Failed to load room!"
build={() => (
<RoomWidget {...p} manager={roomMgr!} room={currentRoom} />
)}
/>
)}
{room && <RoomWidget {...p} room={room} />}
</div>
);
}

View File

@@ -15,6 +15,12 @@ export function MatrixWS(p: {
}): React.ReactElement {
const snackbar = useSnackbar();
// Keep only the latest version of onMessage
const cbRef = React.useRef(p.onMessage);
React.useEffect(() => {
cbRef.current = p.onMessage;
}, [p.onMessage]);
const [state, setState] = React.useState<string>(State.Closed);
const wsId = React.useRef<number | undefined>(undefined);
const [connCount, setConnCount] = React.useState(0);
@@ -46,7 +52,7 @@ export function MatrixWS(p: {
const dec = JSON.parse(msg.data);
console.info("WS message", dec);
p.onMessage(dec);
cbRef.current(dec);
};
return () => ws.close();

View File

@@ -38,7 +38,7 @@ import { AccountIcon } from "./AccountIcon";
export function RoomMessagesList(p: {
room: Room;
users: UsersMap;
mgr: RoomEventsManager;
manager: RoomEventsManager;
}): React.ReactElement {
const messagesEndRef = React.createRef<HTMLDivElement>();
@@ -46,7 +46,7 @@ export function RoomMessagesList(p: {
React.useEffect(() => {
if (messagesEndRef)
messagesEndRef.current?.scrollIntoView({ behavior: "instant" });
}, [p.mgr.messages.length]);
}, [p.manager.messages.length]);
return (
<div
@@ -58,20 +58,20 @@ export function RoomMessagesList(p: {
paddingLeft: "20px",
}}
>
{p.mgr.messages.map((m, idx) => (
{p.manager.messages.map((m, idx) => (
<RoomMessage
key={m.event_id}
{...p}
message={m}
previousFromSamePerson={
idx > 0 &&
p.mgr.messages[idx - 1].account === m.account &&
m.time_sent - p.mgr.messages[idx - 1].time_sent < 60 * 3 * 1000
p.manager.messages[idx - 1].account === m.account &&
m.time_sent - p.manager.messages[idx - 1].time_sent < 60 * 3 * 1000
}
firstMessageOfDay={
idx === 0 ||
m.time_sent_dayjs.startOf("day").unix() !=
p.mgr.messages[idx - 1].time_sent_dayjs.startOf("day").unix()
p.manager.messages[idx - 1].time_sent_dayjs.startOf("day").unix()
}
/>
))}

View File

@@ -1,49 +1,19 @@
import React from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
import type { Room } from "../../api/matrix/MatrixApiRoom";
import type { WsMessage } from "../../api/WsApi";
import { RoomEventsManager } from "../../utils/RoomEventsManager";
import { AsyncWidget } from "../AsyncWidget";
import { MatrixWS } from "./MatrixWS";
import { RoomMessagesList } from "./RoomMessagesList";
import { SendMessageForm } from "./SendMessageForm";
export function RoomWidget(p: {
room: Room;
users: UsersMap;
onEvent: (m: WsMessage) => void;
manager: RoomEventsManager;
}): React.ReactElement {
const [_count, setCount] = React.useState(0);
const [roomMgr, setRoomMgr] = React.useState<undefined | RoomEventsManager>();
const load = async () => {
setRoomMgr(undefined);
const messages = await MatrixApiEvent.GetRoomEvents(p.room);
const mgr = new RoomEventsManager(p.room, messages);
setRoomMgr(mgr);
};
const handleNewMessage = (m: WsMessage) => {
if (roomMgr?.processWsMessage(m)) setCount((c) => c + 1);
p.onEvent(m);
};
return (
<AsyncWidget
loadKey={p.room.id}
ready={!!roomMgr}
load={load}
errMsg="Failed to load room!"
build={() => (
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
<div style={{ position: "absolute", right: "0px", padding: "10px" }}>
<MatrixWS onMessage={handleNewMessage} />
</div>
<RoomMessagesList mgr={roomMgr!} {...p} />
<SendMessageForm {...p} />
</div>
)}
/>
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
<RoomMessagesList {...p} />
<SendMessageForm {...p} />
</div>
);
}