aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src
diff options
context:
space:
mode:
authorHsiangNianian <i@jyunko.cn>2026-01-13 16:36:02 +0800
committerHsiangNianian <i@jyunko.cn>2026-01-13 16:36:02 +0800
commit8d9856d4a0e469aa9343188dd6c32528881d578f (patch)
treeb2e876d0aa1c2f1eaf7c90c88c79e06962feac59 /ui/src
parentb5cc8c2e9a9661de3b398508636399507f55b496 (diff)
downloadDropOut-8d9856d4a0e469aa9343188dd6c32528881d578f.tar.gz
DropOut-8d9856d4a0e469aa9343188dd6c32528881d578f.zip
feat: add game console component for logging game output and toggle visibility
Diffstat (limited to 'ui/src')
-rw-r--r--ui/src/App.svelte34
-rw-r--r--ui/src/lib/GameConsole.svelte65
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}