mirror of
https://github.com/ajvpot/meshexplorer.git
synced 2026-08-07 00:52:45 +02:00
a
This commit is contained in:
@@ -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
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user