Text Channel UI

This commit is contained in:
2026-08-20 18:12:47 +02:00
parent 8cf69d5b02
commit 83c7926728
3 changed files with 80 additions and 8 deletions
+2 -2
View File
@@ -168,11 +168,11 @@ export default class Enclave<P = Page> {
console.log("msg", msg.messages); console.log("msg", msg.messages);
Object.entries(msg.messages).forEach(([channelId, messages]) => { Object.entries(msg.messages).forEach(([channelId, messages]) => {
if (!server.messages[channelId]) { if (!server.messages[channelId]) {
server.messages[channelId] = []; server.messages[channelId] = new Set();
} }
messages.forEach((message) => { messages.forEach((message) => {
server.messages[channelId].push(message); server.messages[channelId].add(message);
}); });
}); });
+1 -1
View File
@@ -29,7 +29,7 @@ export default class EnclaveServer {
public isSecure: boolean; public isSecure: boolean;
public websocket?: EnclaveWebSocket; public websocket?: EnclaveWebSocket;
public meta?: ServerMeta; public meta?: ServerMeta;
public messages: Record<string, StoredMessage[]>; public messages: Record<string, Set<StoredMessage>>;
public constructor(hostname: string, isSecure: boolean) { public constructor(hostname: string, isSecure: boolean) {
this.hostname = hostname; this.hostname = hostname;
+77 -5
View File
@@ -1,17 +1,21 @@
import Enclave from "@/app/app"; import Enclave from "@/app/app";
import { ChannelPageProps } from "@/components/page/PageView"; import { ChannelPageProps } from "@/components/page/PageView";
import { useEffect } from "react"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { StoredMessage } from "@/lib/types";
import { useEffect, useState } from "react";
export default function TextChannel({ export default function TextChannel({
appRef, appRef,
}: { }: {
appRef: React.RefObject<Enclave<ChannelPageProps> | null>; appRef: React.RefObject<Enclave<ChannelPageProps> | null>;
}) { }) {
const [currentMessage, setCurrentMessage] = useState("");
const channel = appRef.current?.page?.channel; const channel = appRef.current?.page?.channel;
if (!channel) return null;
useEffect(() => { useEffect(() => {
if (!channel) return;
appRef.current?.server?.websocket?.send({ appRef.current?.server?.websocket?.send({
method: "GetMessages", method: "GetMessages",
channel_id: channel.id, channel_id: channel.id,
@@ -19,9 +23,77 @@ export default function TextChannel({
}); });
appRef.current?.forceRender(); appRef.current?.forceRender();
}, []); }, [channel]);
if (!channel) return null;
const sendMessage = () => {
appRef.current?.sendMessage(currentMessage, channel.id);
setCurrentMessage("");
};
return ( return (
<div>{JSON.stringify(appRef.current?.server?.messages[channel.id])}</div> <div className="flex flex-col h-full">
<header className="px-3 pt-3 pb-3 text-sm text-muted-foreground border-b border-b-border">
<h2>{channel.name}</h2>
</header>
<div className="h-full flex flex-col gap-2.5 px-3 pt-4 overflow-y-scroll">
{appRef.current?.server?.messages[channel.id] &&
Array.from(appRef.current?.server?.messages[channel.id]).map(
(message) => <TextMessage appRef={appRef} message={message} />,
)}
</div>
<div className="pb-6 px-4 flex flex-row gap-3">
<Input
className="h-10"
placeholder={`Message ${channel.name}`}
value={currentMessage}
onChange={(e) => setCurrentMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}}
/>
{currentMessage.trim() && <Button onClick={sendMessage}>Send</Button>}
</div>
</div>
);
}
export function TextMessage({
appRef,
message,
}: {
appRef: React.RefObject<Enclave<ChannelPageProps> | null>;
message: StoredMessage;
}) {
const time = new Date(message.timestamp).toLocaleTimeString([], {
hour: "numeric",
minute: "2-digit",
});
return (
<div className="rounded-lg flex gap-3 px-3 py-3 hover:bg-muted/40">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground">
{message.author.slice(0, 2).toUpperCase()}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-2">
<span className="text-sm font-medium">
{message.author.slice(0, 8)}
</span>
<span className="text-xs text-muted-foreground">{time}</span>
</div>
<p className="whitespace-pre-wrap break-words text-sm leading-snug">
{message.content}
</p>
</div>
</div>
); );
} }