2026-06-22 21:57:32 +02:00
|
|
|
import { useEffect, useRef, useState } from 'react'
|
|
|
|
|
import { useWaste } from '../store'
|
|
|
|
|
|
2026-06-29 11:45:51 +02:00
|
|
|
const today = new Date()
|
|
|
|
|
today.setHours(0, 0, 0, 0)
|
|
|
|
|
const todayMs = today.getTime()
|
|
|
|
|
const yesterdayMs = todayMs - 86400000
|
|
|
|
|
|
|
|
|
|
function formatTs(ts: number): string {
|
|
|
|
|
const time = new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
|
|
|
|
|
if (ts >= todayMs) return time
|
|
|
|
|
if (ts >= yesterdayMs) return `Yesterday ${time}`
|
|
|
|
|
return new Date(ts).toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + time
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 21:57:32 +02:00
|
|
|
export function MessagePane() {
|
2026-06-28 23:50:48 +02:00
|
|
|
const { messages, historyCutoff, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste()
|
2026-06-22 21:57:32 +02:00
|
|
|
const [draft, setDraft] = useState('')
|
|
|
|
|
const bottomRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
const roomMessages = messages[activeRoom] ?? []
|
2026-06-28 23:50:48 +02:00
|
|
|
const cutoff = historyCutoff[activeRoom] ?? 0
|
2026-06-22 21:57:32 +02:00
|
|
|
|
2026-06-29 11:45:51 +02:00
|
|
|
// Find the index of the first live message (ts > cutoff).
|
|
|
|
|
// The divider appears just before this index, or at the top if all are history.
|
|
|
|
|
const firstLiveIdx = cutoff > 0
|
|
|
|
|
? roomMessages.findIndex(m => m.ts > cutoff)
|
|
|
|
|
: -1
|
|
|
|
|
// If all messages are history (no live yet), put divider at the start.
|
|
|
|
|
const dividerIdx = cutoff > 0
|
|
|
|
|
? (firstLiveIdx === -1 ? 0 : firstLiveIdx)
|
|
|
|
|
: -1
|
|
|
|
|
|
2026-06-22 21:57:32 +02:00
|
|
|
useEffect(() => {
|
|
|
|
|
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
|
|
|
}, [roomMessages.length])
|
|
|
|
|
|
|
|
|
|
function submit(e: React.FormEvent) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
const text = draft.trim()
|
|
|
|
|
if (!text || !activeNetworkId) return
|
|
|
|
|
|
|
|
|
|
if (activeRoom.startsWith('dm:')) {
|
|
|
|
|
const toPeerID = activeRoom.slice(3)
|
|
|
|
|
const peer = connectedPeers.find(p => p.id.startsWith(toPeerID) || p.id === toPeerID)
|
|
|
|
|
if (peer) {
|
|
|
|
|
send({ type: 'send_message', network_id: activeNetworkId, room: activeRoom, body: text, to: peer.id })
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
send({ type: 'send_message', network_id: activeNetworkId, room: activeRoom, body: text })
|
|
|
|
|
}
|
|
|
|
|
setDraft('')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function aliasFor(fromId: string) {
|
|
|
|
|
if (fromId === localPeer?.id) return localPeer.alias
|
|
|
|
|
return connectedPeers.find(p => p.id === fromId)?.alias ?? fromId.slice(0, 8)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 13:28:40 +02:00
|
|
|
const roomLabel = activeRoom.startsWith('dm:')
|
|
|
|
|
? `@ ${activeRoom.slice(3, 11)}…`
|
|
|
|
|
: `# ${activeRoom}`
|
|
|
|
|
|
2026-06-22 21:57:32 +02:00
|
|
|
return (
|
|
|
|
|
<main className="message-pane">
|
2026-06-25 13:28:40 +02:00
|
|
|
<div className="message-pane-header">{roomLabel}</div>
|
2026-06-22 21:57:32 +02:00
|
|
|
|
|
|
|
|
<div className="messages">
|
2026-06-29 11:45:51 +02:00
|
|
|
{dividerIdx === 0 && (
|
|
|
|
|
<div className="history-divider"><span>earlier messages</span></div>
|
|
|
|
|
)}
|
2026-06-22 21:57:32 +02:00
|
|
|
{roomMessages.map((msg, i) => {
|
|
|
|
|
const mine = msg.from === localPeer?.id
|
2026-06-29 11:45:51 +02:00
|
|
|
const alias = aliasFor(String(msg.from))
|
|
|
|
|
const ts = formatTs(msg.ts)
|
2026-06-22 21:57:32 +02:00
|
|
|
return (
|
2026-06-28 23:50:48 +02:00
|
|
|
<div key={msg.mid ?? i}>
|
2026-06-29 11:45:51 +02:00
|
|
|
{i === dividerIdx && dividerIdx > 0 && (
|
|
|
|
|
<div className="history-divider"><span>earlier messages</span></div>
|
2026-06-28 23:50:48 +02:00
|
|
|
)}
|
|
|
|
|
<div className={`message ${mine ? 'mine' : ''}`}>
|
2026-06-29 11:45:51 +02:00
|
|
|
<span className="message-ts">{ts}</span>
|
2026-06-28 23:50:48 +02:00
|
|
|
<span className="message-alias">{alias}</span>
|
|
|
|
|
<span className="message-text">{msg.text}</span>
|
|
|
|
|
</div>
|
2026-06-22 21:57:32 +02:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
<div ref={bottomRef} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form className="compose" onSubmit={submit}>
|
|
|
|
|
<input
|
|
|
|
|
value={draft}
|
|
|
|
|
onChange={e => setDraft(e.target.value)}
|
2026-06-25 13:28:40 +02:00
|
|
|
placeholder={`Message ${roomLabel}`}
|
2026-06-22 21:57:32 +02:00
|
|
|
autoComplete="off"
|
|
|
|
|
/>
|
|
|
|
|
<button type="submit" disabled={!draft.trim()}>Send</button>
|
|
|
|
|
</form>
|
|
|
|
|
</main>
|
|
|
|
|
)
|
|
|
|
|
}
|