diff --git a/web/views/index.erb b/web/views/index.erb index a6d40eb..9a444a4 100644 --- a/web/views/index.erb +++ b/web/views/index.erb @@ -377,8 +377,6 @@ }; let allNodes = []; let shortInfoAnchor = null; - const seenNodeIds = new Set(); - const seenMessageIds = new Set(); let lastChatDate; const NODE_LIMIT = 1000; const CHAT_LIMIT = 1000; @@ -920,16 +918,8 @@ requestAnimationFrame(positionShortInfoOverlay); } - function appendChatEntry(div) { - chatEl.appendChild(div); - while (chatEl.childElementCount > CHAT_LIMIT) { - chatEl.removeChild(chatEl.firstChild); - } - chatEl.scrollTop = chatEl.scrollHeight; - } - - function maybeAddDateDivider(ts) { - if (!ts) return; + function maybeCreateDateDivider(ts) { + if (!ts) return null; const d = new Date(ts * 1000); const key = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; if (lastChatDate !== key) { @@ -939,30 +929,60 @@ const div = document.createElement('div'); div.className = 'chat-entry-date'; div.textContent = `-- ${formatDate(midnight)} --`; - appendChatEntry(div); + return div; } + return null; } - function addNewNodeChatEntry(n) { - maybeAddDateDivider(n.first_heard); + function createNodeChatEntry(n) { const div = document.createElement('div'); const ts = formatTime(new Date(n.first_heard * 1000)); div.className = 'chat-entry-node'; const short = renderShortHtml(n.short_name, n.role, n.long_name, n); const longName = escapeHtml(n.long_name || ''); div.innerHTML = `[${ts}] ${short} New node: ${longName}`; - appendChatEntry(div); + return div; } - function addNewMessageChatEntry(m) { - maybeAddDateDivider(m.rx_time); + function createMessageChatEntry(m) { const div = document.createElement('div'); const ts = formatTime(new Date(m.rx_time * 1000)); const short = renderShortHtml(m.node?.short_name, m.node?.role, m.node?.long_name, m.node); const text = escapeHtml(m.text || ''); div.className = 'chat-entry-msg'; div.innerHTML = `[${ts}] ${short} ${text}`; - appendChatEntry(div); + return div; + } + + function renderChatLog(nodes, messages) { + if (!chatEl) return; + const entries = []; + for (const n of nodes || []) { + entries.push({ type: 'node', ts: n.first_heard ?? 0, item: n }); + } + for (const m of messages || []) { + entries.push({ type: 'msg', ts: m.rx_time ?? 0, item: m }); + } + entries.sort((a, b) => { + if (a.ts !== b.ts) return a.ts - b.ts; + return a.type === 'node' && b.type === 'msg' ? -1 : a.type === 'msg' && b.type === 'node' ? 1 : 0; + }); + const frag = document.createDocumentFragment(); + lastChatDate = null; + for (const entry of entries) { + const divider = maybeCreateDateDivider(entry.ts); + if (divider) frag.appendChild(divider); + if (entry.type === 'node') { + frag.appendChild(createNodeChatEntry(entry.item)); + } else { + frag.appendChild(createMessageChatEntry(entry.item)); + } + } + chatEl.replaceChildren(frag); + while (chatEl.childElementCount > CHAT_LIMIT) { + chatEl.removeChild(chatEl.firstChild); + } + chatEl.scrollTop = chatEl.scrollHeight; } function pad(n) { return String(n).padStart(2, "0"); } @@ -1142,35 +1162,8 @@ statusEl.textContent = 'refreshing…'; const nodes = await fetchNodes(); computeDistances(nodes); - const newNodes = []; - for (const n of nodes) { - if (n.node_id && !seenNodeIds.has(n.node_id)) { - newNodes.push(n); - } - } const messages = await fetchMessages(); - const newMessages = []; - for (const m of messages) { - if (m.id && !seenMessageIds.has(m.id)) { - newMessages.push(m); - } - } - const entries = []; - for (const n of newNodes) entries.push({ type: 'node', ts: n.first_heard ?? 0, item: n }); - for (const m of newMessages) entries.push({ type: 'msg', ts: m.rx_time ?? 0, item: m }); - entries.sort((a, b) => { - if (a.ts !== b.ts) return a.ts - b.ts; - return a.type === 'node' && b.type === 'msg' ? -1 : a.type === 'msg' && b.type === 'node' ? 1 : 0; - }); - for (const e of entries) { - if (e.type === 'node') { - addNewNodeChatEntry(e.item); - if (e.item.node_id) seenNodeIds.add(e.item.node_id); - } else { - addNewMessageChatEntry(e.item); - if (e.item.id) seenMessageIds.add(e.item.id); - } - } + renderChatLog(nodes, messages); allNodes = nodes; applyFilter(); statusEl.textContent = 'updated ' + new Date().toLocaleTimeString();