import { invoke } from "@tauri-apps/api/core"; import { Loader2, Search } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useGameStore } from "@/stores/game-store"; import { useInstancesStore } from "@/stores/instances-store"; import type { Version } from "@/types/bindings/manifest"; import type { FabricLoaderEntry } from "../types/bindings/fabric"; import type { ForgeVersion as ForgeVersionEntry } from "../types/bindings/forge"; import type { Instance } from "../types/bindings/instance"; interface Props { open: boolean; onOpenChange: (open: boolean) => void; } /** * InstanceCreationModal * 3-step wizard: * 1) Name * 2) Select base Minecraft version * 3) Optional: choose mod loader (vanilla/fabric/forge) and loader version * * Behavior: * - On Create: invoke("create_instance", { name }) * - If a base version selected: invoke("install_version", { instanceId, versionId }) * - If Fabric selected: invoke("install_fabric", { instanceId, gameVersion, loaderVersion }) * - If Forge selected: invoke("install_forge", { instanceId, gameVersion, forgeVersion }) * - Reload instances via instancesStore.loadInstances() */ export function InstanceCreationModal({ open, onOpenChange }: Props) { const gameStore = useGameStore(); const instancesStore = useInstancesStore(); // Steps: 1 = name, 2 = version, 3 = mod loader const [step, setStep] = useState(1); // Step 1 const [instanceName, setInstanceName] = useState(""); // Step 2 const [versionSearch, setVersionSearch] = useState(""); const [versionFilter, setVersionFilter] = useState< "all" | "release" | "snapshot" >("release"); const [selectedVersionUI, setSelectedVersionUI] = useState( null, ); // Step 3 const [modLoaderType, setModLoaderType] = useState< "vanilla" | "fabric" | "forge" >("vanilla"); const [fabricLoaders, setFabricLoaders] = useState([]); const [forgeVersions, setForgeVersions] = useState([]); const [selectedFabricLoader, setSelectedFabricLoader] = useState(""); const [selectedForgeLoader, setSelectedForgeLoader] = useState(""); const [loadingLoaders, setLoadingLoaders] = useState(false); const loadModLoaders = useCallback(async () => { if (!selectedVersionUI) return; setLoadingLoaders(true); setFabricLoaders([]); setForgeVersions([]); try { if (modLoaderType === "fabric") { const loaders = await invoke( "get_fabric_loaders_for_version", { gameVersion: selectedVersionUI.id, }, ); setFabricLoaders(loaders || []); if (loaders && loaders.length > 0) { setSelectedFabricLoader(loaders[0].loader.version); } else { setSelectedFabricLoader(""); } } else if (modLoaderType === "forge") { const versions = await invoke( "get_forge_versions_for_game", { gameVersion: selectedVersionUI.id, }, ); setForgeVersions(versions || []); if (versions && versions.length > 0) { // Binding `ForgeVersion` uses `version` (not `id`) — use `.version` here. setSelectedForgeLoader(versions[0].version); } else { setSelectedForgeLoader(""); } } } catch (e) { console.error("Failed to load mod loaders:", e); toast.error("Failed to fetch mod loader versions"); } finally { setLoadingLoaders(false); } }, [modLoaderType, selectedVersionUI]); // When entering step 3 and a base version exists, fetch loaders if needed useEffect(() => { if (step === 3 && modLoaderType !== "vanilla" && selectedVersionUI) { loadModLoaders(); } }, [step, modLoaderType, selectedVersionUI, loadModLoaders]); // Creating state const [creating, setCreating] = useState(false); const [errorMessage, setErrorMessage] = useState(""); // Derived filtered versions const filteredVersions = useMemo(() => { const all = gameStore.versions || []; let list = all.slice(); if (versionFilter !== "all") { list = list.filter((v) => v.type === versionFilter); } if (versionSearch.trim()) { const q = versionSearch.trim().toLowerCase().replace(/。/g, "."); list = list.filter((v) => v.id.toLowerCase().includes(q)); } return list; }, [gameStore.versions, versionFilter, versionSearch]); // Reset when opened/closed useEffect(() => { if (open) { // ensure versions are loaded gameStore.loadVersions(); setStep(1); setInstanceName(""); setVersionSearch(""); setVersionFilter("release"); setSelectedVersionUI(null); setModLoaderType("vanilla"); setFabricLoaders([]); setForgeVersions([]); setSelectedFabricLoader(""); setSelectedForgeLoader(""); setErrorMessage(""); setCreating(false); } }, [open, gameStore.loadVersions]); function validateStep1(): boolean { if (!instanceName.trim()) { setErrorMessage("Please enter an instance name"); return false; } setErrorMessage(""); return true; } function validateStep2(): boolean { if (!selectedVersionUI) { setErrorMessage("Please select a Minecraft version"); return false; } setErrorMessage(""); return true; } async function handleNext() { setErrorMessage(""); if (step === 1) { if (!validateStep1()) return; setStep(2); } else if (step === 2) { if (!validateStep2()) return; setStep(3); } } function handleBack() { setErrorMessage(""); setStep((s) => Math.max(1, s - 1)); } async function handleCreate() { if (!validateStep1() || !validateStep2()) return; setCreating(true); setErrorMessage(""); try { // Step 1: create instance const instance = await invoke("create_instance", { name: instanceName.trim(), }); // If selectedVersion provided, install it if (selectedVersionUI) { try { await invoke("install_version", { instanceId: instance.id, versionId: selectedVersionUI.id, }); } catch (err) { console.error("Failed to install base version:", err); // continue - instance created but version install failed toast.error( `Failed to install version ${selectedVersionUI.id}: ${String(err)}`, ); } } // If mod loader selected, install it if (modLoaderType === "fabric" && selectedFabricLoader) { try { await invoke("install_fabric", { instanceId: instance.id, gameVersion: selectedVersionUI?.id ?? "", loaderVersion: selectedFabricLoader, }); } catch (err) { console.error("Failed to install Fabric:", err); toast.error(`Failed to install Fabric: ${String(err)}`); } } else if (modLoaderType === "forge" && selectedForgeLoader) { try { await invoke("install_forge", { instanceId: instance.id, gameVersion: selectedVersionUI?.id ?? "", installerVersion: selectedForgeLoader, }); } catch (err) { console.error("Failed to install Forge:", err); toast.error(`Failed to install Forge: ${String(err)}`); } } // Refresh instances list await instancesStore.loadInstances(); toast.success("Instance created successfully"); onOpenChange(false); } catch (e) { console.error("Failed to create instance:", e); setErrorMessage(String(e)); toast.error(`Failed to create instance: ${e}`); } finally { setCreating(false); } } // UI pieces const StepIndicator = () => (
= 1 ? "bg-indigo-500" : "bg-zinc-700"}`} />
= 2 ? "bg-indigo-500" : "bg-zinc-700"}`} />
= 3 ? "bg-indigo-500" : "bg-zinc-700"}`} />
); return ( Create New Instance Multi-step wizard — create an instance and optionally install a version or mod loader.
{/* Step 1 - Name */} {step === 1 && (
setInstanceName(e.target.value)} disabled={creating} />

Give your instance a memorable name.

)} {/* Step 2 - Version selection */} {step === 2 && (
setVersionSearch(e.target.value)} placeholder="Search versions..." className="pl-9" />
{gameStore.versions.length === 0 ? (
Loading versions...
) : filteredVersions.length === 0 ? (
No matching versions found
) : ( filteredVersions.map((v) => { const isSelected = selectedVersionUI?.id === v.id; return ( ); }) )}
)} {/* Step 3 - Mod loader */} {step === 3 && (
Mod Loader Type
{modLoaderType === "fabric" && (
{loadingLoaders ? (
Loading Fabric versions...
) : fabricLoaders.length > 0 ? (
) : (

No Fabric loaders available for this version

)}
)} {modLoaderType === "forge" && (
{loadingLoaders ? (
Loading Forge versions...
) : forgeVersions.length > 0 ? (
) : (

No Forge versions available for this version

)}
)}
)} {errorMessage && (
{errorMessage}
)}
{step > 1 && ( )} {step < 3 ? ( ) : ( )}
); } export default InstanceCreationModal;