aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/src/lib/GameConsole.svelte62
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>