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