diff options
Diffstat (limited to 'ui/src/components/BottomBar.svelte')
| -rw-r--r-- | ui/src/components/BottomBar.svelte | 148 |
1 files changed, 113 insertions, 35 deletions
diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte index 9dcb9ac..04d53de 100644 --- a/ui/src/components/BottomBar.svelte +++ b/ui/src/components/BottomBar.svelte @@ -1,23 +1,68 @@ <script lang="ts"> + import { invoke } from "@tauri-apps/api/core"; + import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { authState } from "../stores/auth.svelte"; import { gameState } from "../stores/game.svelte"; import { uiState } from "../stores/ui.svelte"; - import { Terminal, ChevronDown, Play, User, Check } from 'lucide-svelte'; + import { Terminal, ChevronDown, Play, User, Check, RefreshCw } from 'lucide-svelte'; + + interface InstalledVersion { + id: string; + type: string; + } let isVersionDropdownOpen = $state(false); let dropdownRef: HTMLDivElement; + let installedVersions = $state<InstalledVersion[]>([]); + let isLoadingVersions = $state(true); + let downloadCompleteUnlisten: UnlistenFn | null = null; + + // Load installed versions on mount + $effect(() => { + loadInstalledVersions(); + setupDownloadListener(); + return () => { + if (downloadCompleteUnlisten) { + downloadCompleteUnlisten(); + } + }; + }); + + async function setupDownloadListener() { + // Refresh list when a download completes + downloadCompleteUnlisten = await listen("download-complete", () => { + loadInstalledVersions(); + }); + } + + async function loadInstalledVersions() { + isLoadingVersions = true; + try { + installedVersions = await invoke<InstalledVersion[]>("list_installed_versions"); + // If no version is selected but we have installed versions, select the first one + if (!gameState.selectedVersion && installedVersions.length > 0) { + gameState.selectedVersion = installedVersions[0].id; + } + } catch (e) { + console.error("Failed to load installed versions:", e); + } finally { + isLoadingVersions = false; + } + } let versionOptions = $derived( - gameState.versions.length === 0 + isLoadingVersions ? [{ id: "loading", type: "loading", label: "Loading..." }] - : gameState.versions.map(v => ({ - ...v, - label: `${v.id}${v.type !== 'release' ? ` (${v.type})` : ''}` - })) + : installedVersions.length === 0 + ? [{ id: "empty", type: "empty", label: "No versions installed" }] + : installedVersions.map(v => ({ + ...v, + label: `${v.id}${v.type !== 'release' ? ` (${v.type})` : ''}` + })) ); function selectVersion(id: string) { - if (id !== "loading") { + if (id !== "loading" && id !== "empty") { gameState.selectedVersion = id; isVersionDropdownOpen = false; } @@ -35,6 +80,15 @@ return () => document.removeEventListener('click', handleClickOutside); } }); + + function getVersionTypeColor(type: string) { + switch (type) { + case 'fabric': return 'text-indigo-400'; + case 'forge': return 'text-orange-400'; + case 'snapshot': return 'text-amber-400'; + default: return 'text-emerald-400'; + } + } </script> <div @@ -105,47 +159,70 @@ <div class="flex flex-col items-end mr-2"> <!-- Custom Version Dropdown --> <div class="relative" bind:this={dropdownRef}> - <button - type="button" - onclick={() => isVersionDropdownOpen = !isVersionDropdownOpen} - class="flex items-center justify-between gap-2 w-56 px-4 py-2.5 text-left - dark:bg-zinc-900 bg-zinc-50 border dark:border-zinc-700 border-zinc-300 rounded-md - text-sm font-mono dark:text-white text-gray-900 - dark:hover:border-zinc-600 hover:border-zinc-400 - focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 - transition-colors cursor-pointer outline-none" - > - <span class="truncate"> - {#if gameState.versions.length === 0} - Loading... - {:else} - {gameState.selectedVersion || "Select version"} - {/if} - </span> - <ChevronDown - size={14} - class="shrink-0 dark:text-zinc-500 text-zinc-400 transition-transform duration-200 {isVersionDropdownOpen ? 'rotate-180' : ''}" - /> - </button> + <div class="flex items-center gap-2"> + <button + type="button" + onclick={() => loadInstalledVersions()} + class="p-2.5 dark:bg-zinc-900 bg-zinc-50 border dark:border-zinc-700 border-zinc-300 rounded-md + dark:text-zinc-500 text-zinc-400 dark:hover:text-white hover:text-black + dark:hover:border-zinc-600 hover:border-zinc-400 transition-colors" + title="Refresh installed versions" + > + <RefreshCw size={14} class={isLoadingVersions ? 'animate-spin' : ''} /> + </button> + <button + type="button" + onclick={() => isVersionDropdownOpen = !isVersionDropdownOpen} + disabled={installedVersions.length === 0 && !isLoadingVersions} + class="flex items-center justify-between gap-2 w-56 px-4 py-2.5 text-left + dark:bg-zinc-900 bg-zinc-50 border dark:border-zinc-700 border-zinc-300 rounded-md + text-sm font-mono dark:text-white text-gray-900 + dark:hover:border-zinc-600 hover:border-zinc-400 + focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 + transition-colors cursor-pointer outline-none + disabled:opacity-50 disabled:cursor-not-allowed" + > + <span class="truncate"> + {#if isLoadingVersions} + Loading... + {:else if installedVersions.length === 0} + No versions installed + {:else} + {gameState.selectedVersion || "Select version"} + {/if} + </span> + <ChevronDown + size={14} + class="shrink-0 dark:text-zinc-500 text-zinc-400 transition-transform duration-200 {isVersionDropdownOpen ? 'rotate-180' : ''}" + /> + </button> + </div> - {#if isVersionDropdownOpen} + {#if isVersionDropdownOpen && installedVersions.length > 0} <div class="absolute z-50 w-full mt-1 py-1 dark:bg-zinc-900 bg-white border dark:border-zinc-700 border-zinc-300 rounded-md shadow-xl - max-h-72 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150 bottom-full mb-1" + max-h-72 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150 bottom-full mb-1 right-0" > {#each versionOptions as version} <button type="button" onclick={() => selectVersion(version.id)} - disabled={version.id === "loading"} + disabled={version.id === "loading" || version.id === "empty"} class="w-full flex items-center justify-between px-3 py-2 text-sm font-mono text-left transition-colors outline-none {version.id === gameState.selectedVersion ? 'bg-indigo-600 text-white' : 'dark:text-zinc-300 text-gray-700 dark:hover:bg-zinc-800 hover:bg-zinc-100'} - {version.id === 'loading' ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}" + {version.id === 'loading' || version.id === 'empty' ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}" > - <span class="truncate">{version.label}</span> + <span class="truncate flex items-center gap-2"> + {version.id} + {#if version.type && version.type !== 'release' && version.type !== 'loading' && version.type !== 'empty'} + <span class="text-[10px] uppercase {version.id === gameState.selectedVersion ? 'text-white/70' : getVersionTypeColor(version.type)}"> + {version.type} + </span> + {/if} + </span> {#if version.id === gameState.selectedVersion} <Check size={14} class="shrink-0 ml-2" /> {/if} @@ -158,7 +235,8 @@ <button onclick={() => gameState.startGame()} - class="bg-emerald-600 hover:bg-emerald-500 text-white h-14 px-10 rounded-sm transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-emerald-500/20 flex items-center gap-3 font-bold text-lg tracking-widest uppercase" + disabled={installedVersions.length === 0 || !gameState.selectedVersion} + class="bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 disabled:cursor-not-allowed text-white h-14 px-10 rounded-sm transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-emerald-500/20 flex items-center gap-3 font-bold text-lg tracking-widest uppercase" > <Play size={24} fill="currentColor" /> <span>Launch</span> |