diff options
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/src/lib/GameConsole.svelte | 62 |
1 files changed, 52 insertions, 10 deletions
diff --git a/ui/src/lib/GameConsole.svelte b/ui/src/lib/GameConsole.svelte index d6913a5..281dc85 100644 --- a/ui/src/lib/GameConsole.svelte +++ b/ui/src/lib/GameConsole.svelte @@ -4,30 +4,51 @@ export let visible = false; - let logs: { type: 'stdout' | 'stderr', line: string }[] = []; + let logs: { type: 'stdout' | 'stderr' | 'launcher', line: string, timestamp: string }[] = []; let consoleElement: HTMLDivElement; let unlistenStdout: () => void; let unlistenStderr: () => void; + let unlistenLauncher: () => void; + let unlistenGameExited: () => void; + + function getTimestamp(): string { + const now = new Date(); + return now.toTimeString().split(' ')[0]; // HH:MM:SS + } onMount(async () => { + // Listen for launcher logs (preparation, downloads, launch status) + unlistenLauncher = await listen<string>("launcher-log", (event) => { + addLog('launcher', event.payload); + }); + + // Listen for game stdout unlistenStdout = await listen<string>("game-stdout", (event) => { addLog('stdout', event.payload); }); + // Listen for game stderr unlistenStderr = await listen<string>("game-stderr", (event) => { addLog('stderr', event.payload); }); + + // Listen for game exit event + unlistenGameExited = await listen<number>("game-exited", (event) => { + addLog('launcher', `Game process exited with code: ${event.payload}`); + }); }); onDestroy(() => { + if (unlistenLauncher) unlistenLauncher(); if (unlistenStdout) unlistenStdout(); if (unlistenStderr) unlistenStderr(); + if (unlistenGameExited) unlistenGameExited(); }); - function addLog(type: 'stdout' | 'stderr', line: string) { - logs = [...logs, { type, line }]; - if (logs.length > 1000) { - logs = logs.slice(logs.length - 1000); + function addLog(type: 'stdout' | 'stderr' | 'launcher', line: string) { + logs = [...logs, { type, line, timestamp: getTimestamp() }]; + if (logs.length > 2000) { + logs = logs.slice(logs.length - 2000); } // Auto-scroll setTimeout(() => { @@ -40,25 +61,46 @@ function clearLogs() { logs = []; } + + function exportLogs() { + const logText = logs.map(l => `[${l.timestamp}] [${l.type.toUpperCase()}] ${l.line}`).join('\n'); + const blob = new Blob([logText], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `dropout-logs-${new Date().toISOString().split('T')[0]}.txt`; + a.click(); + URL.revokeObjectURL(url); + } </script> {#if visible} <div class="fixed bottom-0 left-0 right-0 h-64 bg-zinc-950/95 border-t border-zinc-700 backdrop-blur flex flex-col z-50 transition-transform duration-300 transform translate-y-0"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-800 bg-zinc-900/50"> - <span class="text-xs font-bold text-zinc-400 uppercase tracking-widest">Game Console</span> + <div class="flex items-center gap-4"> + <span class="text-xs font-bold text-zinc-400 uppercase tracking-widest">Logs</span> + <div class="flex gap-1 text-[10px]"> + <span class="px-1.5 py-0.5 rounded bg-indigo-900/50 text-indigo-300">LAUNCHER</span> + <span class="px-1.5 py-0.5 rounded bg-zinc-800 text-zinc-300">GAME</span> + <span class="px-1.5 py-0.5 rounded bg-red-900/50 text-red-300">ERROR</span> + </div> + </div> <div class="flex gap-2"> + <button on:click={exportLogs} class="text-xs text-zinc-500 hover:text-white px-2 py-1 rounded transition">Export</button> <button on:click={clearLogs} class="text-xs text-zinc-500 hover:text-white px-2 py-1 rounded transition">Clear</button> <button on:click={() => visible = false} class="text-xs text-zinc-500 hover:text-white px-2 py-1 rounded transition">Close</button> </div> </div> - <div bind:this={consoleElement} class="flex-1 overflow-y-auto p-4 font-mono text-xs space-y-1"> + <div bind:this={consoleElement} class="flex-1 overflow-y-auto p-4 font-mono text-xs space-y-0.5"> {#each logs as log} - <div class="{log.type === 'stderr' ? 'text-red-400' : 'text-zinc-300'} whitespace-pre-wrap break-all border-l-2 pl-2 {log.type === 'stderr' ? 'border-red-900/50' : 'border-transparent'}"> - {log.line} + <div class="flex whitespace-pre-wrap break-all {log.type === 'stderr' ? 'text-red-400' : log.type === 'launcher' ? 'text-indigo-300' : 'text-zinc-300'}"> + <span class="text-zinc-600 mr-2 shrink-0">{log.timestamp}</span> + <span class="shrink-0 mr-2 {log.type === 'stderr' ? 'text-red-500' : log.type === 'launcher' ? 'text-indigo-500' : 'text-zinc-500'}">[{log.type === 'launcher' ? 'LAUNCHER' : log.type === 'stderr' ? 'ERROR' : 'GAME'}]</span> + <span class="break-all">{log.line}</span> </div> {/each} {#if logs.length === 0} - <div class="text-zinc-600 italic">Waiting for game output...</div> + <div class="text-zinc-600 italic">Waiting for output... Click "Show Logs" and start a game to see logs here.</div> {/if} </div> </div> |