diff options
| author | 2026-01-14 16:37:56 +0800 | |
|---|---|---|
| committer | 2026-01-14 16:38:36 +0800 | |
| commit | 5a7afced64373df9619ec98f6d1dd99406da1dab (patch) | |
| tree | a9964eecda6eeb40da4184af2d9bd23c5523211f /ui/src/components/ModLoaderSelector.svelte | |
| parent | cba180b0d625b26bcf810dec3c5b6d24ff5b1568 (diff) | |
| download | DropOut-5a7afced64373df9619ec98f6d1dd99406da1dab.tar.gz DropOut-5a7afced64373df9619ec98f6d1dd99406da1dab.zip | |
feat: add ModLoaderSelector component for managing mod loader installations
Diffstat (limited to 'ui/src/components/ModLoaderSelector.svelte')
| -rw-r--r-- | ui/src/components/ModLoaderSelector.svelte | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte new file mode 100644 index 0000000..06eb6ae --- /dev/null +++ b/ui/src/components/ModLoaderSelector.svelte @@ -0,0 +1,245 @@ +<script lang="ts"> + import { invoke } from "@tauri-apps/api/core"; + import type { + FabricGameVersion, + FabricLoaderVersion, + ForgeVersion, + ModLoaderType, + } from "../types"; + + interface Props { + selectedGameVersion: string; + onInstall: (versionId: string) => void; + } + + let { selectedGameVersion, onInstall }: Props = $props(); + + // State + let selectedLoader = $state<ModLoaderType>("vanilla"); + let isLoading = $state(false); + let error = $state<string | null>(null); + + // Fabric state + let fabricLoaders = $state<FabricLoaderVersion[]>([]); + let selectedFabricLoader = $state(""); + + // Forge state + let forgeVersions = $state<ForgeVersion[]>([]); + let selectedForgeVersion = $state(""); + + // Load mod loader versions when game version changes + $effect(() => { + if (selectedGameVersion && selectedLoader !== "vanilla") { + loadModLoaderVersions(); + } + }); + + async function loadModLoaderVersions() { + isLoading = true; + error = null; + + try { + if (selectedLoader === "fabric") { + const loaders = await invoke<any[]>("get_fabric_loaders_for_version", { + gameVersion: selectedGameVersion, + }); + fabricLoaders = loaders.map((l) => l.loader); + if (fabricLoaders.length > 0) { + // Select first stable version or first available + const stable = fabricLoaders.find((l) => l.stable); + selectedFabricLoader = stable?.version || fabricLoaders[0].version; + } + } else if (selectedLoader === "forge") { + forgeVersions = await invoke<ForgeVersion[]>( + "get_forge_versions_for_game", + { + gameVersion: selectedGameVersion, + } + ); + if (forgeVersions.length > 0) { + // Select recommended version first, then latest + const recommended = forgeVersions.find((v) => v.recommended); + const latest = forgeVersions.find((v) => v.latest); + selectedForgeVersion = + recommended?.version || latest?.version || forgeVersions[0].version; + } + } + } catch (e) { + error = `Failed to load ${selectedLoader} versions: ${e}`; + console.error(e); + } finally { + isLoading = false; + } + } + + async function installModLoader() { + if (!selectedGameVersion) { + error = "Please select a Minecraft version first"; + return; + } + + isLoading = true; + error = null; + + try { + if (selectedLoader === "fabric" && selectedFabricLoader) { + const result = await invoke<any>("install_fabric", { + gameVersion: selectedGameVersion, + loaderVersion: selectedFabricLoader, + }); + onInstall(result.id); + } else if (selectedLoader === "forge" && selectedForgeVersion) { + const result = await invoke<any>("install_forge", { + gameVersion: selectedGameVersion, + forgeVersion: selectedForgeVersion, + }); + onInstall(result.id); + } + } catch (e) { + error = `Failed to install ${selectedLoader}: ${e}`; + console.error(e); + } finally { + isLoading = false; + } + } + + function onLoaderChange(loader: ModLoaderType) { + selectedLoader = loader; + error = null; + if (loader !== "vanilla" && selectedGameVersion) { + loadModLoaderVersions(); + } + } +</script> + +<div class="bg-zinc-800 rounded-lg p-4 border border-zinc-700"> + <h3 class="text-sm font-semibold text-zinc-400 mb-3">Mod Loader</h3> + + <!-- Loader Type Tabs --> + <div class="flex gap-1 mb-4 bg-zinc-900 rounded-lg p-1"> + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader === + 'vanilla' + ? 'bg-zinc-700 text-white' + : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}" + onclick={() => onLoaderChange("vanilla")} + > + Vanilla + </button> + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader === + 'fabric' + ? 'bg-blue-600 text-white' + : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}" + onclick={() => onLoaderChange("fabric")} + > + Fabric + </button> + <button + class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader === + 'forge' + ? 'bg-orange-600 text-white' + : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}" + onclick={() => onLoaderChange("forge")} + > + Forge + </button> + </div> + + {#if selectedLoader === "vanilla"} + <p class="text-sm text-zinc-500"> + Launch the selected Minecraft version without any mod loaders. + </p> + {:else if !selectedGameVersion} + <p class="text-sm text-zinc-500"> + Select a Minecraft version first to see available {selectedLoader} versions. + </p> + {:else if isLoading} + <div class="flex items-center gap-2 text-sm text-zinc-400"> + <svg + class="animate-spin h-4 w-4" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + > + <circle + class="opacity-25" + cx="12" + cy="12" + r="10" + stroke="currentColor" + stroke-width="4" + ></circle> + <path + class="opacity-75" + fill="currentColor" + d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" + ></path> + </svg> + Loading {selectedLoader} versions... + </div> + {:else if error} + <p class="text-sm text-red-400">{error}</p> + {:else if selectedLoader === "fabric"} + <div class="space-y-3"> + <div> + <label for="fabric-loader-select" class="block text-xs text-zinc-500 mb-1" + >Loader Version</label + > + <select + id="fabric-loader-select" + class="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-sm focus:outline-none focus:border-blue-500" + bind:value={selectedFabricLoader} + > + {#each fabricLoaders as loader} + <option value={loader.version}> + {loader.version} + {loader.stable ? "(stable)" : ""} + </option> + {/each} + </select> + </div> + <button + class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed" + onclick={installModLoader} + disabled={isLoading || !selectedFabricLoader} + > + Install Fabric {selectedFabricLoader} + </button> + </div> + {:else if selectedLoader === "forge"} + <div class="space-y-3"> + {#if forgeVersions.length === 0} + <p class="text-sm text-zinc-500"> + No Forge versions available for Minecraft {selectedGameVersion} + </p> + {:else} + <div> + <label for="forge-version-select" class="block text-xs text-zinc-500 mb-1" + >Forge Version</label + > + <select + id="forge-version-select" + class="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-sm focus:outline-none focus:border-orange-500" + bind:value={selectedForgeVersion} + > + {#each forgeVersions as version} + <option value={version.version}> + {version.version} + {version.recommended ? "⭐ recommended" : ""} + {version.latest ? "(latest)" : ""} + </option> + {/each} + </select> + </div> + <button + class="w-full bg-orange-600 hover:bg-orange-700 text-white py-2 px-4 rounded font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed" + onclick={installModLoader} + disabled={isLoading || !selectedForgeVersion} + > + Install Forge {selectedForgeVersion} + </button> + {/if} + </div> + {/if} +</div> |