feat: add LogPanel UI component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-10 02:25:21 +00:00
parent 39cb094110
commit a897dbfee4
+116
View File
@@ -0,0 +1,116 @@
import type { LogEntry } from '@dflike/shared';
const MAX_ENTRIES = 200;
const SEVERITY_COLORS: Record<string, string> = {
error: '#ff6666',
warning: '#f0d060',
info: '#6699cc',
};
const SEVERITY_ICONS: Record<string, string> = {
error: '\u25cf',
warning: '\u25b2',
info: '\u25cb',
};
export class LogPanel {
private container: HTMLDivElement;
private logEl: HTMLDivElement;
private emptyEl: HTMLDivElement;
constructor() {
this.container = document.createElement('div');
this.container.style.cssText = `
display: flex;
flex-direction: column;
height: 100%;
gap: 4px;
`;
this.emptyEl = document.createElement('div');
this.emptyEl.style.cssText = `
color: #8878a8;
font-size: 10px;
text-align: center;
padding: 20px 0;
`;
this.emptyEl.textContent = 'No log entries yet...';
this.container.appendChild(this.emptyEl);
this.logEl = document.createElement('div');
this.logEl.style.cssText = `
display: flex;
flex-direction: column;
gap: 1px;
overflow-y: auto;
flex: 1;
`;
this.container.appendChild(this.logEl);
}
getElement(): HTMLDivElement {
return this.container;
}
addEntry(entry: LogEntry): void {
this.emptyEl.style.display = 'none';
const el = this.createLogLine(entry);
this.logEl.insertBefore(el, this.logEl.firstChild);
while (this.logEl.children.length > MAX_ENTRIES) {
this.logEl.removeChild(this.logEl.lastChild!);
}
}
loadHistory(entries: LogEntry[]): void {
this.logEl.innerHTML = '';
if (entries.length === 0) {
this.emptyEl.style.display = '';
return;
}
this.emptyEl.style.display = 'none';
for (let i = entries.length - 1; i >= 0; i--) {
this.logEl.appendChild(this.createLogLine(entries[i]));
}
}
private createLogLine(entry: LogEntry): HTMLDivElement {
const el = document.createElement('div');
el.style.cssText = `
font-size: 9px;
padding: 2px 0;
border-bottom: 1px solid #1c1450;
line-height: 1.5;
display: flex;
gap: 4px;
align-items: baseline;
`;
const timeStr = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
const color = SEVERITY_COLORS[entry.severity] ?? '#a0a0c0';
const icon = SEVERITY_ICONS[entry.severity] ?? '';
const timeEl = document.createElement('span');
timeEl.style.cssText = 'color: #666; flex-shrink: 0;';
timeEl.textContent = timeStr;
const iconEl = document.createElement('span');
iconEl.style.cssText = `color: ${color}; flex-shrink: 0;`;
iconEl.textContent = icon;
const catEl = document.createElement('span');
catEl.style.cssText = 'color: #8878a8; flex-shrink: 0;';
catEl.textContent = `[${entry.category}]`;
const msgEl = document.createElement('span');
msgEl.style.cssText = `color: ${color};`;
msgEl.textContent = entry.message;
el.appendChild(timeEl);
el.appendChild(iconEl);
el.appendChild(catEl);
el.appendChild(msgEl);
return el;
}
}