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) { 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; let data: MatrixEventData;
if (m.type === "RoomReactionEvent") { if (m.type === "RoomReactionEvent") {

View File

@@ -1,5 +1,6 @@
import { Divider } from "@mui/material"; import { Divider } from "@mui/material";
import React from "react"; import React from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import { import {
MatrixApiProfile, MatrixApiProfile,
type UsersMap, type UsersMap,
@@ -7,6 +8,7 @@ import {
import { MatrixApiRoom, type Room } from "../../api/matrix/MatrixApiRoom"; import { MatrixApiRoom, type Room } from "../../api/matrix/MatrixApiRoom";
import { MatrixSyncApi } from "../../api/MatrixSyncApi"; import { MatrixSyncApi } from "../../api/MatrixSyncApi";
import type { WsMessage } from "../../api/WsApi"; import type { WsMessage } from "../../api/WsApi";
import { RoomEventsManager } from "../../utils/RoomEventsManager";
import { AsyncWidget } from "../AsyncWidget"; import { AsyncWidget } from "../AsyncWidget";
import { useUserInfo } from "../dashboard/BaseAuthenticatedPage"; import { useUserInfo } from "../dashboard/BaseAuthenticatedPage";
import { MatrixWS } from "./MatrixWS"; import { MatrixWS } from "./MatrixWS";
@@ -17,9 +19,8 @@ import { SpaceSelector } from "./SpaceSelector";
export function MainMessageWidget(): React.ReactElement { export function MainMessageWidget(): React.ReactElement {
const [rooms, setRooms] = React.useState<Room[] | undefined>(); const [rooms, setRooms] = React.useState<Room[] | undefined>();
const [users, setUsers] = React.useState<UsersMap | undefined>(); const [users, setUsers] = React.useState<UsersMap | undefined>();
const user = useUserInfo();
const load = async () => { const loadRoomsList = async () => {
await MatrixSyncApi.Start(); await MatrixSyncApi.Start();
const rooms = await MatrixApiRoom.ListJoined(); const rooms = await MatrixApiRoom.ListJoined();
@@ -34,37 +35,17 @@ export function MainMessageWidget(): React.ReactElement {
setUsers(await MatrixApiProfile.GetMultiple([...users])); 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 ( return (
<AsyncWidget <AsyncWidget
loadKey={1} loadKey={1}
load={load} load={loadRoomsList}
ready={!!rooms && !!users} ready={!!rooms && !!users}
errMsg="Failed to initialize messaging component!" errMsg="Failed to initialize messaging component!"
build={() => ( build={() => (
<_MainMessageWidget <_MainMessageWidget
rooms={rooms!} rooms={rooms!}
users={users!} users={users!}
onEvent={handleEvent} onRoomsListUpdate={(cb) => setRooms((r) => cb(r!))}
/> />
)} )}
/> />
@@ -74,10 +55,12 @@ export function MainMessageWidget(): React.ReactElement {
function _MainMessageWidget(p: { function _MainMessageWidget(p: {
rooms: Room[]; rooms: Room[];
users: UsersMap; users: UsersMap;
onEvent: (m: WsMessage) => void; onRoomsListUpdate: (cb: (a: Room[]) => Room[]) => void;
}): React.ReactElement { }): React.ReactElement {
const user = useUserInfo();
const [space, setSpace] = React.useState<string | undefined>(); 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(() => { const spaceRooms = React.useMemo(() => {
return p.rooms return p.rooms
@@ -87,33 +70,96 @@ function _MainMessageWidget(p: {
); );
}, [space, p.rooms]); }, [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 ( return (
<div style={{ display: "flex", height: "100%" }}> <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} /> <SpaceSelector {...p} selectedSpace={space} onChange={setSpace} />
{/* Separator */}
<Divider orientation="vertical" /> <Divider orientation="vertical" />
{/* Room selector */}
<RoomSelector <RoomSelector
{...p} {...p}
rooms={spaceRooms} rooms={spaceRooms}
currRoom={room} currRoom={currentRoom}
onChange={setRoom} onChange={setCurrentRoom}
/> />
{/* Separator */}
<Divider orientation="vertical" /> <Divider orientation="vertical" />
{room === undefined && (
<> {/* If no room is selected */}
<MatrixWS onMessage={p.onEvent} /> {currentRoom === undefined && (
<div <div
style={{ style={{
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
flex: 1, flex: 1,
}} }}
> >
No room selected. No room selected.
</div> </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> </div>
); );
} }

View File

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

View File

@@ -38,7 +38,7 @@ import { AccountIcon } from "./AccountIcon";
export function RoomMessagesList(p: { export function RoomMessagesList(p: {
room: Room; room: Room;
users: UsersMap; users: UsersMap;
mgr: RoomEventsManager; manager: RoomEventsManager;
}): React.ReactElement { }): React.ReactElement {
const messagesEndRef = React.createRef<HTMLDivElement>(); const messagesEndRef = React.createRef<HTMLDivElement>();
@@ -46,7 +46,7 @@ export function RoomMessagesList(p: {
React.useEffect(() => { React.useEffect(() => {
if (messagesEndRef) if (messagesEndRef)
messagesEndRef.current?.scrollIntoView({ behavior: "instant" }); messagesEndRef.current?.scrollIntoView({ behavior: "instant" });
}, [p.mgr.messages.length]); }, [p.manager.messages.length]);
return ( return (
<div <div
@@ -58,20 +58,20 @@ export function RoomMessagesList(p: {
paddingLeft: "20px", paddingLeft: "20px",
}} }}
> >
{p.mgr.messages.map((m, idx) => ( {p.manager.messages.map((m, idx) => (
<RoomMessage <RoomMessage
key={m.event_id} key={m.event_id}
{...p} {...p}
message={m} message={m}
previousFromSamePerson={ previousFromSamePerson={
idx > 0 && idx > 0 &&
p.mgr.messages[idx - 1].account === m.account && p.manager.messages[idx - 1].account === m.account &&
m.time_sent - p.mgr.messages[idx - 1].time_sent < 60 * 3 * 1000 m.time_sent - p.manager.messages[idx - 1].time_sent < 60 * 3 * 1000
} }
firstMessageOfDay={ firstMessageOfDay={
idx === 0 || idx === 0 ||
m.time_sent_dayjs.startOf("day").unix() != 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 React from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
import type { Room } from "../../api/matrix/MatrixApiRoom"; import type { Room } from "../../api/matrix/MatrixApiRoom";
import type { WsMessage } from "../../api/WsApi";
import { RoomEventsManager } from "../../utils/RoomEventsManager"; import { RoomEventsManager } from "../../utils/RoomEventsManager";
import { AsyncWidget } from "../AsyncWidget";
import { MatrixWS } from "./MatrixWS";
import { RoomMessagesList } from "./RoomMessagesList"; import { RoomMessagesList } from "./RoomMessagesList";
import { SendMessageForm } from "./SendMessageForm"; import { SendMessageForm } from "./SendMessageForm";
export function RoomWidget(p: { export function RoomWidget(p: {
room: Room; room: Room;
users: UsersMap; users: UsersMap;
onEvent: (m: WsMessage) => void; manager: RoomEventsManager;
}): React.ReactElement { }): 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 ( return (
<AsyncWidget <div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
loadKey={p.room.id} <RoomMessagesList {...p} />
ready={!!roomMgr} <SendMessageForm {...p} />
load={load} </div>
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>
)}
/>
); );
} }