feat: events viewer rows — class-based + data-type colour pills

renderEventsList was painting every timeline row with ~7 inline
style props per element (padding, border-bottom, font-size on the
row; margin-right, color on each span; padding-top on detail) AND
keeping a JS-side colour map per event type. For a chatty recording
with 100+ events the inline-style noise added up, and adding a new
event type meant editing the renderer to extend the map.

Extracted:
- .event-viewer-row picks up the padding + bottom border + font-size
- .event-viewer-time gets the secondary colour + monospace stack
- .event-viewer-tag becomes an actual pill (uppercase, letter-
  spacing, rounded background tint, bordered) — visually consistent
  with the chat viewer's [type] chip tag
- .event-viewer-detail handles the row-detail line spacing

Per-type colour is now driven by CSS [data-type="..."] attribute
selectors (recording_start = green, recording_end = purple,
recording_resume = blue, title_change = amber, game_change = red).
Each variant overrides background + border + text colour to give
each tag a contained "pill" look. The renderer just stamps
ev.type onto data-type and the CSS handles the rest.

Adding a new event type now means one new selector here, not a JS
map edit. Lint, focus, future polish all stay near the styling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 02:14:33 +02:00
parent 4809da8957
commit 9afff4b8b0
2 changed files with 80 additions and 17 deletions

View File

@ -320,33 +320,24 @@ function renderEventsList(events: EventLogEntry[]): void {
for (const ev of events) { for (const ev of events) {
const row = document.createElement('div'); const row = document.createElement('div');
row.style.padding = '8px 10px'; row.className = 'event-viewer-row';
row.style.borderBottom = '1px solid var(--border-soft)';
row.style.fontSize = '12px';
const time = document.createElement('span'); const time = document.createElement('span');
time.style.color = 'var(--text-secondary)'; time.className = 'event-viewer-time';
time.style.marginRight = '8px';
time.textContent = formatEventTime(ev.t); time.textContent = formatEventTime(ev.t);
row.appendChild(time); row.appendChild(time);
const tag = document.createElement('span'); const tag = document.createElement('span');
tag.style.fontWeight = '600'; tag.className = 'event-viewer-tag';
tag.style.marginRight = '8px'; // Per-type tag colour comes from CSS via a data-type attribute
const tagColors: Record<string, string> = { // selector — keeps the type->colour mapping with the rest of the
recording_start: '#00c853', // visual styling instead of inline in the renderer.
recording_end: '#9146ff', if (ev.type) tag.dataset.type = ev.type;
recording_resume: '#2196f3',
title_change: '#ffab00',
game_change: '#ff4444'
};
tag.style.color = tagColors[ev.type || ''] || 'var(--accent)';
tag.textContent = ev.type || 'event'; tag.textContent = ev.type || 'event';
row.appendChild(tag); row.appendChild(tag);
const detail = document.createElement('div'); const detail = document.createElement('div');
detail.style.marginTop = '4px'; detail.className = 'event-viewer-detail';
detail.style.color = 'var(--text)';
if (ev.type === 'recording_start') { if (ev.type === 'recording_start') {
detail.textContent = `${UI_TEXT.queue.eventStartedAs}: "${ev.title || '-'}" — ${ev.game || '-'}`; detail.textContent = `${UI_TEXT.queue.eventStartedAs}: "${ev.title || '-'}" — ${ev.game || '-'}`;

View File

@ -2904,6 +2904,78 @@ input[type="number"]::-webkit-outer-spin-button {
padding-left: 10px; padding-left: 10px;
} }
/* ============================================
EVENTS VIEWER timeline rows
============================================
Per-event-type colours live here via [data-type] attribute
selectors so the renderer just stamps the type and the CSS
handles the palette. Add a new event type by extending this
block, not the renderer. */
.event-viewer-row {
padding: 8px 10px;
border-bottom: 1px solid var(--border-soft);
font-size: 12px;
}
.event-viewer-row:last-child {
border-bottom: none;
}
.event-viewer-time {
color: var(--text-secondary);
margin-right: 8px;
font-family: 'Consolas', 'Segoe UI Mono', monospace;
}
.event-viewer-tag {
font-weight: 600;
margin-right: 8px;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.3px;
font-size: 11px;
padding: 2px 7px;
border-radius: 3px;
background: rgba(145, 70, 255, 0.10);
border: 1px solid rgba(145, 70, 255, 0.25);
}
.event-viewer-tag[data-type="recording_start"] {
color: #00c853;
background: rgba(0, 200, 83, 0.10);
border-color: rgba(0, 200, 83, 0.30);
}
.event-viewer-tag[data-type="recording_end"] {
color: #9146ff;
background: rgba(145, 70, 255, 0.10);
border-color: rgba(145, 70, 255, 0.30);
}
.event-viewer-tag[data-type="recording_resume"] {
color: #2196f3;
background: rgba(33, 150, 243, 0.10);
border-color: rgba(33, 150, 243, 0.30);
}
.event-viewer-tag[data-type="title_change"] {
color: #ffab00;
background: rgba(255, 171, 0, 0.10);
border-color: rgba(255, 171, 0, 0.30);
}
.event-viewer-tag[data-type="game_change"] {
color: #ff4444;
background: rgba(255, 68, 68, 0.10);
border-color: rgba(255, 68, 68, 0.30);
}
.event-viewer-detail {
margin-top: 4px;
color: var(--text);
line-height: 1.5;
}
/* ============================================ /* ============================================
STREAMER PROFILE HEADER STREAMER PROFILE HEADER
============================================ ============================================