diff options
| -rw-r--r-- | ui/src/App.svelte | 34 | ||||
| -rw-r--r-- | ui/src/lib/GameConsole.svelte | 65 |
2 files changed, 87 insertions, 12 deletions
diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 0e14cb0..de5faec 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -2,8 +2,10 @@ import { invoke } from "@tauri-apps/api/core"; import { onMount } from "svelte"; import DownloadMonitor from "./lib/DownloadMonitor.svelte"; + import GameConsole from "./lib/GameConsole.svelte"; let status = "Ready"; + let showConsole = false; interface Version { id: string; @@ -149,20 +151,26 @@ <!-- Bottom Bar --> <div class="h-24 bg-zinc-900 border-t border-zinc-800 flex items-center px-8 justify-between z-20 shadow-2xl"> - <div class="flex items-center gap-4 cursor-pointer hover:opacity-80 transition-opacity" onclick={login} role="button" tabindex="0" onkeydown={(e) => e.key === 'Enter' && login()}> - <div class="w-12 h-12 rounded bg-gradient-to-tr from-indigo-500 to-purple-500 shadow-lg flex items-center justify-center text-white font-bold text-xl overflow-hidden"> - {#if currentAccount} - <img src={`https://minotar.net/avatar/${currentAccount.username}/48`} alt={currentAccount.username} class="w-full h-full"> - {:else} - ? - {/if} - </div> - <div> - <div class="font-bold text-white text-lg">{currentAccount ? currentAccount.username : "Click to Login"}</div> - <div class="text-xs text-zinc-400 flex items-center gap-1"> - <span class="w-1.5 h-1.5 rounded-full {currentAccount ? 'bg-green-500' : 'bg-zinc-500'}"></span> {currentAccount ? 'Ready' : 'Guest'} + <div class="flex items-center gap-4"> + <div class="flex items-center gap-4 cursor-pointer hover:opacity-80 transition-opacity" onclick={login} role="button" tabindex="0" onkeydown={(e) => e.key === 'Enter' && login()}> + <div class="w-12 h-12 rounded bg-gradient-to-tr from-indigo-500 to-purple-500 shadow-lg flex items-center justify-center text-white font-bold text-xl overflow-hidden"> + {#if currentAccount} + <img src={`https://minotar.net/avatar/${currentAccount.username}/48`} alt={currentAccount.username} class="w-full h-full"> + {:else} + ? + {/if} + </div> + <div> + <div class="font-bold text-white text-lg">{currentAccount ? currentAccount.username : "Click to Login"}</div> + <div class="text-xs text-zinc-400 flex items-center gap-1"> + <span class="w-1.5 h-1.5 rounded-full {currentAccount ? 'bg-green-500' : 'bg-zinc-500'}"></span> {currentAccount ? 'Ready' : 'Guest'} + </div> </div> </div> + <!-- Console Toggle --> + <button class="ml-4 text-xs text-zinc-500 hover:text-zinc-300 transition" onclick={() => showConsole = !showConsole}> + {showConsole ? 'Hide Logs' : 'Show Logs'} + </button> </div> <div class="flex items-center gap-4"> @@ -197,4 +205,6 @@ <div class="font-mono text-sm whitespace-pre-wrap">{status}</div> </div> {/if} + + <GameConsole visible={showConsole} /> </div> diff --git a/ui/src/lib/GameConsole.svelte b/ui/src/lib/GameConsole.svelte new file mode 100644 index 0000000..d6913a5 --- /dev/null +++ b/ui/src/lib/GameConsole.svelte @@ -0,0 +1,65 @@ +<script lang="ts"> + import { listen } from "@tauri-apps/api/event"; + import { onMount, onDestroy } from "svelte"; + + export let visible = false; + + let logs: { type: 'stdout' | 'stderr', line: string }[] = []; + let consoleElement: HTMLDivElement; + let unlistenStdout: () => void; + let unlistenStderr: () => void; + + onMount(async () => { + unlistenStdout = await listen<string>("game-stdout", (event) => { + addLog('stdout', event.payload); + }); + + unlistenStderr = await listen<string>("game-stderr", (event) => { + addLog('stderr', event.payload); + }); + }); + + onDestroy(() => { + if (unlistenStdout) unlistenStdout(); + if (unlistenStderr) unlistenStderr(); + }); + + function addLog(type: 'stdout' | 'stderr', line: string) { + logs = [...logs, { type, line }]; + if (logs.length > 1000) { + logs = logs.slice(logs.length - 1000); + } + // Auto-scroll + setTimeout(() => { + if (consoleElement) { + consoleElement.scrollTop = consoleElement.scrollHeight; + } + }, 0); + } + + function clearLogs() { + logs = []; + } +</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 gap-2"> + <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"> + {#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> + {/each} + {#if logs.length === 0} + <div class="text-zinc-600 italic">Waiting for game output...</div> + {/if} + </div> +</div> +{/if} |