diff options
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/src/components/VersionsView.svelte | 268 |
1 files changed, 231 insertions, 37 deletions
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte index 98261b8..1ea4878 100644 --- a/ui/src/components/VersionsView.svelte +++ b/ui/src/components/VersionsView.svelte @@ -1,55 +1,249 @@ <script lang="ts"> + import { invoke } from "@tauri-apps/api/core"; import { gameState } from "../stores/game.svelte"; + import ModLoaderSelector from "./ModLoaderSelector.svelte"; let searchQuery = $state(""); let normalizedQuery = $derived( searchQuery.trim().toLowerCase().replace(/。/g, ".") ); - let filteredVersions = $derived( - gameState.versions.filter((v) => - v.id.toLowerCase().includes(normalizedQuery) - ) - ); + // Filter by version type + let typeFilter = $state<"all" | "release" | "snapshot" | "modded">("all"); + + // Installed modded versions + let installedFabricVersions = $state<string[]>([]); + let isLoadingModded = $state(false); + + // Load installed modded versions + async function loadInstalledModdedVersions() { + isLoadingModded = true; + try { + installedFabricVersions = await invoke<string[]>( + "list_installed_fabric_versions" + ); + } catch (e) { + console.error("Failed to load installed fabric versions:", e); + } finally { + isLoadingModded = false; + } + } + + // Load on mount + $effect(() => { + loadInstalledModdedVersions(); + }); + + // Combined versions list (vanilla + modded) + let allVersions = $derived(() => { + const moddedVersions = installedFabricVersions.map((id) => ({ + id, + type: "fabric", + url: "", + time: "", + releaseTime: new Date().toISOString(), + })); + return [...moddedVersions, ...gameState.versions]; + }); + + let filteredVersions = $derived(() => { + let versions = allVersions(); + + // Apply type filter + if (typeFilter === "release") { + versions = versions.filter((v) => v.type === "release"); + } else if (typeFilter === "snapshot") { + versions = versions.filter((v) => v.type === "snapshot"); + } else if (typeFilter === "modded") { + versions = versions.filter( + (v) => v.type === "fabric" || v.type === "forge" + ); + } + + // Apply search filter + if (normalizedQuery.length > 0) { + versions = versions.filter((v) => + v.id.toLowerCase().includes(normalizedQuery) + ); + } + + return versions; + }); + + function getVersionBadge(type: string) { + switch (type) { + case "release": + return { text: "Release", class: "bg-green-600" }; + case "snapshot": + return { text: "Snapshot", class: "bg-yellow-600" }; + case "fabric": + return { text: "Fabric", class: "bg-blue-600" }; + case "forge": + return { text: "Forge", class: "bg-orange-600" }; + default: + return { text: type, class: "bg-zinc-600" }; + } + } + + function handleModLoaderInstall(versionId: string) { + // Refresh the installed versions list + loadInstalledModdedVersions(); + // Select the newly installed version + gameState.selectedVersion = versionId; + } + + // Get the base Minecraft version from selected version (for mod loader selector) + let selectedBaseVersion = $derived(() => { + const selected = gameState.selectedVersion; + if (!selected) return ""; + + // If it's a modded version, extract the base version + if (selected.startsWith("fabric-loader-")) { + // Format: fabric-loader-X.X.X-1.20.4 + const parts = selected.split("-"); + return parts[parts.length - 1]; + } + if (selected.includes("-forge-")) { + // Format: 1.20.4-forge-49.0.38 + return selected.split("-forge-")[0]; + } + + // Check if it's a valid vanilla version + const version = gameState.versions.find((v) => v.id === selected); + return version ? selected : ""; + }); </script> <div class="p-8 h-full overflow-y-auto bg-zinc-900"> <h2 class="text-3xl font-bold mb-6">Versions</h2> - <input - type="text" - placeholder="Search versions..." - class="w-full p-3 mb-4 bg-zinc-800 border border-zinc-700 rounded text-white focus:outline-none focus:border-green-500 transition-colors" - bind:value={searchQuery} - /> - - <div class="grid gap-2"> - {#if gameState.versions.length === 0} - <div class="text-zinc-500">Loading versions...</div> - {:else if filteredVersions.length === 0 && normalizedQuery.length > 0} - <div class="text-zinc-500">No versions found matching "{searchQuery}"</div> - {:else} - {#each filteredVersions as version} + <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> + <!-- Left: Version List --> + <div class="lg:col-span-2 space-y-4"> + <!-- Search and Filters --> + <div class="flex gap-3"> + <input + type="text" + placeholder="Search versions..." + class="flex-1 p-3 bg-zinc-800 border border-zinc-700 rounded text-white focus:outline-none focus:border-green-500 transition-colors" + bind:value={searchQuery} + /> + </div> + + <!-- Type Filter Tabs --> + <div class="flex gap-1 bg-zinc-800 rounded-lg p-1"> <button - class="flex items-center justify-between p-4 bg-zinc-800 rounded hover:bg-zinc-700 transition text-left border border-zinc-700 {gameState.selectedVersion === - version.id - ? 'border-green-500 bg-zinc-800/80 ring-1 ring-green-500' - : ''}" - onclick={() => (gameState.selectedVersion = version.id)} + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter === + 'all' + ? 'bg-zinc-700 text-white' + : 'text-zinc-400 hover:text-white'}" + onclick={() => (typeFilter = "all")} > - <div> - <div class="font-bold font-mono text-lg">{version.id}</div> - <div class="text-xs text-zinc-400 capitalize"> - {version.type} • {new Date( - version.releaseTime - ).toLocaleDateString()} - </div> - </div> - {#if gameState.selectedVersion === version.id} - <div class="text-green-500 font-bold text-sm">SELECTED</div> - {/if} + All </button> - {/each} - {/if} + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter === + 'release' + ? 'bg-green-600 text-white' + : 'text-zinc-400 hover:text-white'}" + onclick={() => (typeFilter = "release")} + > + Releases + </button> + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter === + 'snapshot' + ? 'bg-yellow-600 text-white' + : 'text-zinc-400 hover:text-white'}" + onclick={() => (typeFilter = "snapshot")} + > + Snapshots + </button> + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter === + 'modded' + ? 'bg-purple-600 text-white' + : 'text-zinc-400 hover:text-white'}" + onclick={() => (typeFilter = "modded")} + > + Modded + </button> + </div> + + <!-- Version List --> + <div class="grid gap-2 max-h-[calc(100vh-320px)] overflow-y-auto pr-2"> + {#if gameState.versions.length === 0} + <div class="text-zinc-500">Loading versions...</div> + {:else if filteredVersions().length === 0} + <div class="text-zinc-500"> + {#if normalizedQuery.length > 0} + No versions found matching "{searchQuery}" + {:else} + No versions in this category + {/if} + </div> + {:else} + {#each filteredVersions() as version} + {@const badge = getVersionBadge(version.type)} + <button + class="flex items-center justify-between p-4 bg-zinc-800 rounded hover:bg-zinc-700 transition text-left border border-zinc-700 {gameState.selectedVersion === + version.id + ? 'border-green-500 bg-zinc-800/80 ring-1 ring-green-500' + : ''}" + onclick={() => (gameState.selectedVersion = version.id)} + > + <div class="flex items-center gap-3"> + <span + class="px-2 py-0.5 rounded text-xs font-medium {badge.class}" + > + {badge.text} + </span> + <div> + <div class="font-bold font-mono">{version.id}</div> + {#if version.releaseTime && version.type !== "fabric" && version.type !== "forge"} + <div class="text-xs text-zinc-400"> + {new Date(version.releaseTime).toLocaleDateString()} + </div> + {/if} + </div> + </div> + {#if gameState.selectedVersion === version.id} + <div class="text-green-500 font-bold text-sm">SELECTED</div> + {/if} + </button> + {/each} + {/if} + </div> + </div> + + <!-- Right: Mod Loader Panel --> + <div class="space-y-4"> + <!-- Selected Version Info --> + {#if gameState.selectedVersion} + <div class="bg-zinc-800 rounded-lg p-4 border border-zinc-700"> + <h3 class="text-sm font-semibold text-zinc-400 mb-2">Selected</h3> + <p class="font-mono text-lg text-green-400"> + {gameState.selectedVersion} + </p> + </div> + {/if} + + <!-- Mod Loader Selector --> + <ModLoaderSelector + selectedGameVersion={selectedBaseVersion()} + onInstall={handleModLoaderInstall} + /> + + <!-- Help Text --> + <div class="bg-zinc-800/50 rounded-lg p-4 border border-zinc-700/50"> + <h4 class="text-sm font-semibold text-zinc-400 mb-2">💡 Tip</h4> + <p class="text-xs text-zinc-500"> + Select a vanilla Minecraft version, then use the Mod Loader panel to + install Fabric or Forge. Installed modded versions will appear in the + list with colored badges. + </p> + </div> + </div> </div> </div> + |