diff --git a/src/ui/event-stream-view.ts b/src/ui/event-stream-view.ts new file mode 100644 index 0000000..72ef44c --- /dev/null +++ b/src/ui/event-stream-view.ts @@ -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([ + '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 { + 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 { + 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 ''; + } + } +}