This commit is contained in:
ajvpot
2025-07-28 00:00:00 +00:00
parent cac624bbda
commit 6271088644
3 changed files with 79 additions and 33 deletions
+5 -3
View File
@@ -8,10 +8,12 @@ export default function MessagesPage() {
const { config } = useConfig();
return (
<div className="max-w-none mx-auto py-8 px-4">
<div className="w-full h-full flex flex-col">
{/* ChatBox component with all messages tab enabled and expanded behavior */}
<div className="flex justify-center">
<ChatBox showAllMessagesTab={true} startExpanded={true} className="w-[100vw] md:w-[80vw] min-h-[600px]" />
<div className="flex-1 flex justify-center items-start p-4">
<div className="w-full max-w-6xl h-full">
<ChatBox showAllMessagesTab={true} startExpanded={true} className="w-full h-full" />
</div>
</div>
</div>
);
+32 -14
View File
@@ -5,6 +5,7 @@ import { useConfig } from "./ConfigContext";
import { decryptMeshcoreGroupMessage } from "../lib/meshcore";
import { getChannelIdFromKey } from "../lib/meshcore";
import ChatMessageItem, { ChatMessage } from "./ChatMessageItem";
import RefreshButton from "./RefreshButton";
const PAGE_SIZE = 20;
@@ -95,6 +96,13 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
}
};
const handleRefresh = () => {
setMessages([]);
setHasMore(true);
setLastBefore(undefined);
fetchMessages(undefined, true);
};
return (
<div
className={`bg-white dark:bg-neutral-900 rounded-lg shadow-lg flex flex-col ${
@@ -107,22 +115,33 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
>
<div className={`flex items-center justify-between ${startExpanded ? "px-4 py-2 border-b border-gray-200 dark:border-neutral-800" : ""}`} style={startExpanded ? {} : { minHeight: minimized ? '2rem' : '2rem' }}>
<span className="font-semibold text-gray-800 dark:text-gray-100">MeshCore Chat</span>
{!startExpanded && (
<button
className="p-1 rounded hover:bg-neutral-200 dark:hover:bg-neutral-800"
onClick={() => setMinimized((m) => !m)}
aria-label={minimized ? "Maximize MeshCore Chat" : "Minimize MeshCore Chat"}
>
{minimized ? (
<PlusIcon className="h-5 w-5" />
) : (
<MinusIcon className="h-5 w-5" />
<div className="flex items-center gap-2">
{(!minimized) && (
<RefreshButton
onClick={handleRefresh}
loading={loading}
small={true}
title="Refresh chat messages"
ariaLabel="Refresh chat messages"
/>
)}
</button>
)}
{!startExpanded && (
<button
className="p-1 rounded hover:bg-neutral-200 dark:hover:bg-neutral-800"
onClick={() => setMinimized((m) => !m)}
aria-label={minimized ? "Maximize MeshCore Chat" : "Minimize MeshCore Chat"}
>
{minimized ? (
<PlusIcon className="h-5 w-5" />
) : (
<MinusIcon className="h-5 w-5" />
)}
</button>
)}
</div>
</div>
{(!minimized || startExpanded) && (
{(!minimized) && (
<>
{showTabs && (
<div className={`flex gap-1 border-b border-gray-200 dark:border-neutral-800 ${startExpanded ? "px-4 py-2" : "mb-2"}`}>
@@ -152,7 +171,6 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
key={msg.ingest_timestamp + (msg.origins?.join(',') ?? '') + i}
msg={msg}
showErrorRow={selectedKey.isAllMessages}
showChannelId={selectedKey.isAllMessages}
/>
))}
{hasMore && (
+42 -16
View File
@@ -24,7 +24,7 @@ function formatLocalTime(utcString: string): string {
return utcDate.toLocaleString();
}
export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: { msg: ChatMessage, showErrorRow?: boolean, showChannelId?: boolean }) {
export default function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow?: boolean }) {
const { config } = useConfig();
const knownKeys = [
...(config?.meshcoreKeys?.map((k: any) => k.privateKey) || []),
@@ -32,6 +32,7 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
];
const [parsed, setParsed] = useState<any | null>(null);
const [error, setError] = useState<string | null>(null);
const [originsExpanded, setOriginsExpanded] = useState(false);
useEffect(() => {
let cancelled = false;
@@ -63,22 +64,51 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
return () => { cancelled = true; };
}, [msg.encrypted_message, msg.mac, msg.channel_hash, knownKeys.join(",")]);
const origins = msg.origins && msg.origins.length > 0 ? msg.origins : [];
const originsCount = origins.length;
const OriginsBox = () => (
<div className="text-xs text-gray-300">
<button
onClick={() => setOriginsExpanded(!originsExpanded)}
className="flex items-center gap-1 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
>
<span>Heard {originsCount > 0 ? `${originsCount} repeat${originsCount !== 1 ? 's' : ''}` : '0 repeats'}</span>
<svg
className={`w-3 h-3 transition-transform ${originsExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{originsExpanded && originsCount > 0 && (
<div className="mt-1 p-2 bg-gray-100 dark:bg-neutral-700 rounded text-xs break-all">
{origins.map((origin, index) => (
<div key={index} className="font-mono">
{origin}
</div>
))}
</div>
)}
</div>
);
if (parsed) {
return (
<div className="border-b border-gray-200 dark:border-neutral-800 pb-2 mb-2">
<div className="text-xs text-gray-400 flex items-center gap-2">
{formatLocalTime(new Date(parsed.timestamp * 1000).toISOString())}
<span className="text-xs text-gray-500">type: {parsed.msgType}</span>
{showChannelId && (
<span className="text-xs text-purple-600 dark:text-purple-300 ml-2">channel: {msg.channel_hash}</span>
)}
<span className="text-xs text-gray-500 ml-2">channel: {msg.channel_hash}</span>
</div>
<div className="break-all whitespace-pre-wrap">
<span className="font-bold text-blue-800 dark:text-blue-300">{parsed.sender}</span>
{parsed.sender && ": "}
<span>{parsed.text}</span>
</div>
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
<OriginsBox />
</div>
);
}
@@ -89,11 +119,9 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
<div className="border-b border-red-200 dark:border-red-800 pb-2 mb-2 bg-red-50 dark:bg-red-900/30">
<div className="text-xs text-red-600 dark:text-red-300 flex items-center gap-2">
Error: {error}
{showChannelId && (
<span className="text-xs text-purple-600 dark:text-purple-300 ml-2">channel: {msg.channel_hash}</span>
)}
<span className="text-xs text-gray-500 ml-2">channel: {msg.channel_hash}</span>
</div>
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
<OriginsBox />
</div>
);
}else{
@@ -103,14 +131,12 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
return (
<div className="border-b border-gray-200 dark:border-neutral-800 pb-2 mb-2">
<div className="text-xs text-gray-400 flex items-center gap-2">
{formatLocalTime(msg.ingest_timestamp)}
{showChannelId && (
<span className="text-xs text-purple-600 dark:text-purple-300 ml-2">channel: {msg.channel_hash}</span>
)}
</div>
<div className="text-xs text-gray-400 flex items-center gap-2">
{formatLocalTime(msg.ingest_timestamp)}
<span className="text-xs text-gray-500 ml-2">channel: {msg.channel_hash}</span>
</div>
<div className="w-full h-5 bg-gray-200 dark:bg-neutral-800 rounded animate-pulse my-2" />
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
<OriginsBox />
</div>
);
}