grub/client/Room.tsx
2023-08-10 18:37:53 -05:00

180 lines
4.6 KiB
TypeScript

import CBOR from "cbor";
import { useEffect, useState } from "react";
import useWebSocket, { ReadyState } from "react-use-websocket";
import * as Automerge from "@automerge/automerge";
import * as uuid from "uuid";
import {} from "libsodium";
import Upload from "./Upload";
import { wsUrl } from "./constants";
const connectionStatusMap = {
[ReadyState.CONNECTING]: "Connecting",
[ReadyState.OPEN]: "Open",
[ReadyState.CLOSING]: "Closing",
[ReadyState.CLOSED]: "Closed",
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
};
export default function Room({ roomId }) {
const [readyState, setReadyState] = useState(ReadyState.CLOSED);
const [connectedClients, setConnectedClients] = useState([]);
const [clientId, setClientId] = useState<string | null>(null);
const [chats, setChats] = useState([]);
const [doc, setDoc] = useState(Automerge.init());
const [syncState, setSyncState] = useState(Automerge.initSyncState());
const [message, setMessage] = useState("");
const [addItemName, setAddItemName] = useState("");
function updateDoc(newDoc) {
setDoc(newDoc);
}
const {
sendMessage,
lastJsonMessage,
readyState: newReadyState,
} = useWebSocket(wsUrl, {
share: true,
onOpen: ({}) => {
console.log("Shiet, connected.");
},
onMessage: async (event) => {
const data = CBOR.decode(await event.data.arrayBuffer());
console.log("received", data);
if (data.type === "ServerHello") {
setClientId(uuid.stringify(data.client_id));
}
if (data.type === "RoomClientList") {
setConnectedClients(data.clients.map((x) => uuid.stringify(x)));
}
if (data.type === "ChatMessage") {
setChats([...chats, data]);
}
if (data.type === "Automerge") {
const [nextDoc, nextSyncState, patch] = Automerge.receiveSyncMessage(
doc,
syncState,
data.message[1]
);
setDoc(nextDoc);
setSyncState(nextSyncState);
console.log("patch", patch);
}
},
});
function sendWtfMessage(data) {
let cbor = CBOR.encode(data);
console.log(
"cbor-encoded",
[...new Uint8Array(cbor)]
.map((x) => x.toString(16).padStart(2, "0"))
.join(" ")
);
sendMessage(cbor);
}
useEffect(() => {
console.log("hellosu", readyState, newReadyState);
if (
readyState === ReadyState.CONNECTING &&
newReadyState === ReadyState.OPEN
) {
// On Open
sendWtfMessage({ type: "JoinRoom", room_id: roomId });
console.log("Sent connection message");
}
setReadyState(newReadyState);
}, [newReadyState]);
function onSubmit(e) {
e.preventDefault();
sendWtfMessage({
type: "ChatMessage",
timestamp: new Date().toISOString(),
message_id: uuid.v4(),
room_id: roomId,
author: clientId,
content: message,
});
setMessage("");
}
function addItem(e) {
e.preventDefault();
const newDoc = Automerge.change(doc, (doc) => {
if (!doc.items) doc.items = [];
doc.items.push({
id: uuid.v4(),
content: addItemName,
});
});
updateDoc(newDoc);
const [syncMessage, binary] = Automerge.generateSyncMessage(doc, syncState);
if (syncMessage)
sendWtfMessage({
type: "Automerge",
client_id: clientId,
room_id: roomId,
message: binary,
});
setAddItemName("");
}
const items = doc.items || [];
const connectionStatus = connectionStatusMap[readyState];
if (newReadyState !== ReadyState.OPEN) return <>Connecting...</>;
return (
<>
<Upload roomId={roomId} />
<hr />
<p>Room Id: {roomId}</p>
<p>Connection status: {connectionStatus}</p>
Connected:
<ul>
{connectedClients.map((x) => (
<li key={x}>{x}</li>
))}
</ul>
Messages:
<ul>
{chats.map((x) => (
<li key={x.message_id}>
[{x.timestamp}] {uuid.stringify(x.author)}: {x.content}
</li>
))}
</ul>
<form onSubmit={onSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message..."
/>
</form>
Grubs:
<ul>
{items.map((x) => (
<li key={x.id}>{x.content}</li>
))}
</ul>
<form onSubmit={addItem}>
<input
type="text"
value={addItemName}
onChange={(e) => setAddItemName(e.target.value)}
placeholder="Type a message..."
/>
<button type="submit">add item</button>
</form>
</>
);
}