Event-Stream-View: Echtzeit-Sidebar mit Filtern

Sidebar-View mit Live-Event-Liste, Suche nach Quelle,
Kategorie-Filter-Checkboxen, Pause/Leeren-Buttons,
max 500 sichtbare Einträge, Payload-Zusammenfassung
je Event-Typ.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-12 10:55:08 +01:00
parent afc8c8281d
commit 50af0eb802

150
src/ui/event-stream-view.ts Normal file
View file

@ -0,0 +1,150 @@
import { ItemView, WorkspaceLeaf } from 'obsidian';
import { LogfireEvent, EventCategory } from '../types';
import { EventBus } from '../core/event-bus';
export const EVENT_STREAM_VIEW_TYPE = 'logfire-event-stream';
const MAX_VISIBLE_ENTRIES = 500;
export class EventStreamView extends ItemView {
private entries: LogfireEvent[] = [];
private unsubscribe: (() => void) | null = null;
private listEl!: HTMLElement;
private viewPaused = false;
private filterText = '';
private enabledCategories = new Set<EventCategory>([
'file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system',
]);
constructor(leaf: WorkspaceLeaf, private eventBus: EventBus) {
super(leaf);
}
getViewType(): string {
return EVENT_STREAM_VIEW_TYPE;
}
getDisplayText(): string {
return 'Logfire Events';
}
getIcon(): string {
return 'activity';
}
async onOpen(): Promise<void> {
const container = this.containerEl.children[1] as HTMLElement;
container.empty();
container.addClass('logfire-event-stream');
// Toolbar
const toolbar = container.createDiv({ cls: 'logfire-stream-toolbar' });
const searchInput = toolbar.createEl('input', {
type: 'text',
placeholder: 'Nach Quelle filtern...',
cls: 'logfire-stream-search',
});
searchInput.addEventListener('input', () => {
this.filterText = searchInput.value.toLowerCase();
this.renderList();
});
const btnGroup = toolbar.createDiv({ cls: 'logfire-stream-btn-group' });
const pauseBtn = btnGroup.createEl('button', { text: 'Pause' });
pauseBtn.addEventListener('click', () => {
this.viewPaused = !this.viewPaused;
pauseBtn.textContent = this.viewPaused ? 'Weiter' : 'Pause';
pauseBtn.toggleClass('is-active', this.viewPaused);
});
const clearBtn = btnGroup.createEl('button', { text: 'Leeren' });
clearBtn.addEventListener('click', () => {
this.entries = [];
this.renderList();
});
// Category toggles
const categoryBar = container.createDiv({ cls: 'logfire-stream-categories' });
const categories: EventCategory[] = ['file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system'];
for (const cat of categories) {
const label = categoryBar.createEl('label', { cls: 'logfire-cat-toggle' });
const cb = label.createEl('input', { type: 'checkbox' }) as HTMLInputElement;
cb.checked = true;
label.createSpan({ text: cat });
cb.addEventListener('change', () => {
if (cb.checked) {
this.enabledCategories.add(cat);
} else {
this.enabledCategories.delete(cat);
}
this.renderList();
});
}
// Event list
this.listEl = container.createDiv({ cls: 'logfire-stream-list' });
// Subscribe to events
this.unsubscribe = this.eventBus.onEvent('*', (event) => {
if (this.viewPaused) return;
this.entries.unshift(event);
if (this.entries.length > MAX_VISIBLE_ENTRIES) {
this.entries.length = MAX_VISIBLE_ENTRIES;
}
this.renderList();
});
}
async onClose(): Promise<void> {
this.unsubscribe?.();
this.unsubscribe = null;
}
private renderList(): void {
this.listEl.empty();
const filtered = this.entries.filter(e => {
if (!this.enabledCategories.has(e.category)) return false;
if (this.filterText && !e.source.toLowerCase().includes(this.filterText)) return false;
return true;
});
for (const event of filtered.slice(0, MAX_VISIBLE_ENTRIES)) {
const row = this.listEl.createDiv({ cls: 'logfire-stream-row' });
const time = new Date(event.timestamp);
const timeStr = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}:${String(time.getSeconds()).padStart(2, '0')}`;
const payloadSummary = this.summarizePayload(event);
row.createSpan({ text: timeStr, cls: 'logfire-stream-time' });
row.createSpan({ text: event.type, cls: 'logfire-stream-type' });
row.createSpan({ text: event.source || '', cls: 'logfire-stream-source' });
if (payloadSummary) {
row.createSpan({ text: payloadSummary, cls: 'logfire-stream-payload' });
}
}
}
private summarizePayload(event: LogfireEvent): string {
const p = event.payload;
switch (event.type) {
case 'content:words-changed':
return `+${p.wordsAdded ?? 0}/-${p.wordsRemoved ?? 0} Wörter`;
case 'editor:change':
return `+${p.insertedChars ?? 0}/-${p.deletedChars ?? 0} Zeichen`;
case 'nav:file-close':
return typeof p.duration === 'number' ? `${Math.round(p.duration / 1000)}s` : '';
case 'file:rename':
return `${p.newName ?? ''}`;
case 'file:move':
return `${p.newFolder ?? ''}`;
case 'plugin:command-executed':
return String(p.commandName ?? '');
default:
return '';
}
}
}