From 4bd3094351a52e4d18257775ca90a6f7a745bec2 Mon Sep 17 00:00:00 2001 From: legop3 Date: Tue, 28 Apr 2026 04:15:49 -0400 Subject: [PATCH] chart --- server/src/services/replayEngineV2.js | 125 +++++++++++++++++++------- 1 file changed, 91 insertions(+), 34 deletions(-) diff --git a/server/src/services/replayEngineV2.js b/server/src/services/replayEngineV2.js index a0fb3a45..9fc441cd 100644 --- a/server/src/services/replayEngineV2.js +++ b/server/src/services/replayEngineV2.js @@ -405,7 +405,12 @@ function buildChatEventsForWindow(startMs, endMs, limit = 18) { const text = String(msg?.text || '').replace(/\s+/g, ' ').trim(); return { ts: Number(msg.ts), - text: `${nickname}: ${text}`.slice(0, 120), + nickname: nickname.slice(0, 32), + text: text.slice(0, 120), + role: String(msg?.role || ''), + fromDiscord: Boolean(msg?.fromDiscord), + roverId: msg?.roverId ? String(msg.roverId) : '', + roverColor: msg?.roverColor ? String(msg.roverColor) : '', }; }); } @@ -436,6 +441,31 @@ function wrapTextLines(text, maxChars = 28) { return lines.slice(0, 4); } +function hexToRgb(hex) { + const value = String(hex || '').trim(); + const match = /^#([0-9A-Fa-f]{6})$/.exec(value); + if (!match) return null; + const raw = match[1]; + return { + r: parseInt(raw.slice(0, 2), 16), + g: parseInt(raw.slice(2, 4), 16), + b: parseInt(raw.slice(4, 6), 16), + }; +} + +function roleColor(role = '') { + switch (String(role)) { + case 'admin': + case 'lockdown': + case 'lockdown-admin': + return '#FCD34D'; // amber-300 + case 'spectator': + return '#94A3B8'; // slate-400 + default: + return '#7DD3FC'; // sky-300 + } +} + function renderSidebarSvg({ width, height, @@ -445,63 +475,87 @@ function renderSidebarSvg({ }) { const titleParts = wrapTextLines(title, 22).slice(0, 4).map((line) => escapeXml(line)); const statLines = driverBatteryLines.slice(0, 8).map((line) => escapeXml(line)); - const normalizedChats = chatLines - .slice(-12) - .map((line) => wrapTextLines(line, 24).slice(0, 3).map((wrapped) => escapeXml(wrapped))); + const normalizedChats = chatLines.slice(-12).map((entry) => { + const wrapped = wrapTextLines(entry.text || '', 24).slice(0, 3).map((line) => escapeXml(line)); + const nick = escapeXml(entry.nickname || 'user'); + const roverId = escapeXml(entry.roverId || ''); + const roverRgb = hexToRgb(entry.roverColor || ''); + const roverBadgeBg = roverRgb ? `rgba(${roverRgb.r},${roverRgb.g},${roverRgb.b},0.18)` : 'rgba(30,41,59,0.70)'; + const roverBadgeBorder = roverRgb ? `rgba(${roverRgb.r},${roverRgb.g},${roverRgb.b},0.60)` : 'rgba(71,85,105,0.75)'; + return { + nick, + wrapped, + nameColor: roleColor(entry.role), + fromDiscord: Boolean(entry.fromDiscord), + roverId, + roverBadgeBg, + roverBadgeBorder, + bubbleTone: entry.fromDiscord ? 'discordBubble' : 'chatBubble', + }; + }); const shapes = []; const textRows = []; - const pad = 12; + const pad = 8; const cardX = pad; const cardW = width - pad * 2; - let y = 12; + let y = 8; // Title card - const titleCardH = Math.max(64, 22 + titleParts.length * 24); - shapes.push(``); + const titleCardH = Math.max(56, 18 + titleParts.length * 22); + shapes.push(``); let ty = y + 28; for (const part of titleParts) { textRows.push(`${part}`); ty += 22; } - y += titleCardH + 10; + y += titleCardH + 6; // Drivers card const driverLines = statLines.length ? statLines : ['No active drivers']; - const driversCardH = 28 + driverLines.length * 20 + 10; - shapes.push(``); + const driversCardH = 24 + driverLines.length * 18 + 8; + shapes.push(``); textRows.push(`Drivers`); let dy = y + 44; for (const line of driverLines) { textRows.push(`${line}`); dy += 20; } - y += driversCardH + 10; + y += driversCardH + 6; // Chat card - const chatCardH = Math.max(80, height - y - 12); - shapes.push(``); + const chatCardH = Math.max(80, height - y - 8); + shapes.push(``); textRows.push(`Chat`); let cy = y + 34; - const bubbleX = cardX + 10; - const bubbleW = cardW - 20; + const bubbleX = cardX + 8; + const bubbleW = cardW - 16; if (!normalizedChats.length) { textRows.push(`No chat in replay window`); } else { for (let i = 0; i < normalizedChats.length; i += 1) { const block = normalizedChats[i]; - const bubbleH = 10 + block.length * 18; + const bubbleH = 26 + block.wrapped.length * 16; if (cy + bubbleH + 6 > y + chatCardH - 8) break; shapes.push( - ``, + ``, ); - let by = cy + 18; - for (const line of block) { - textRows.push(`${line}`); - by += 18; + textRows.push(`${block.nick}`); + if (block.fromDiscord) { + textRows.push(``); + } + if (block.roverId) { + const badgeTextX = bubbleX + bubbleW - 70; + shapes.push( + ``, + ); + textRows.push(`${block.roverId}`); + } + let by = cy + 32; + for (const line of block.wrapped) { + textRows.push(`${line}`); + by += 16; } cy += bubbleH + 6; } @@ -515,17 +569,20 @@ function renderSidebarSvg({ - - + + ${shapes.join('\n ')} ${textRows.join('\n ')}