import { zodResolver } from "@hookform/resolvers/zod"; import { defineStepper } from "@stepperize/react"; import { open } from "@tauri-apps/plugin-shell"; import { ArrowLeftIcon, Link2Icon, XIcon } from "lucide-react"; import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { Controller, FormProvider, useForm, useFormContext, Watch, } from "react-hook-form"; import { useNavigate } from "react-router"; import { toast } from "sonner"; import z from "zod"; import { getFabricLoadersForVersion, getForgeVersionsForGame, getVersions, installFabric, installForge, installVersion, updateInstance, } from "@/client"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Field, FieldContent, FieldDescription, FieldError, FieldLabel, FieldSet, FieldTitle, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Spinner } from "@/components/ui/spinner"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { useInstanceStore } from "@/models/instance"; import type { FabricLoaderEntry, ForgeVersion, Version } from "@/types"; const versionSchema = z.object({ versionId: z.string("Version is required"), }); function VersionComponent() { const { control, formState: { errors }, } = useFormContext>(); const [versionSearch, setVersionSearch] = useState(""); const [versionFilter, setVersionFilter] = useState< "all" | "release" | "snapshot" | "old_alpha" | "old_beta" | null >("release"); const [versions, setVersions] = useState(null); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const loadVersions = useCallback(async () => { setErrorMessage(null); setIsLoading(true); try { const versions = await getVersions(); setVersions(versions); } catch (e) { console.error("Failed to load versions:", e); setErrorMessage(`Failed to load versions: ${String(e)}`); return; } finally { setIsLoading(false); } }, []); useEffect(() => { if (!versions) loadVersions(); }, [versions, loadVersions]); const filteredVersions = useMemo(() => { if (!versions) return null; const all = 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; }, [versions, versionFilter, versionSearch]); return (
Versions setVersionSearch(e.target.value)} />
Type
{errorMessage && (

{errorMessage}

)} {isLoading && !errorMessage ? (

Loading versions...

) : (
( {filteredVersions?.map((version) => ( {version.id} {version.type} {new Date(version.releaseTime).toLocaleString()}
))}
)} >
)} {errors.versionId && }
); } const instanceSchema = z.object({ name: z.string().min(1, "Instance name is required"), notes: z.string().max(100, "Notes must be at most 100 characters").optional(), modLoader: z.enum(["fabric", "forge"]).optional(), modLoaderVersion: z.string().optional(), }); function InstanceComponent() { const { control, register, formState: { errors }, } = useFormContext>(); const versionId = useVersionId(); const [forgeVersions, setForgeVersions] = useState( null, ); const [fabricVersions, setFabricVersions] = useState< FabricLoaderEntry[] | null >(null); const [isLoadingForge, setIsLoadingForge] = useState(false); const [isLoadingFabric, setIsLoadingFabric] = useState(false); const loadForgeVersions = useCallback(async () => { if (forgeVersions) return; if (!versionId) return toast.error("Version ID is not set"); setIsLoadingForge(true); try { const versions = await getForgeVersionsForGame(versionId); setForgeVersions(versions); } catch (e) { console.error("Failed to load Forge versions:", e); toast.error(`Failed to load Forge versions: ${String(e)}`); } finally { setIsLoadingForge(false); } }, [versionId, forgeVersions]); const loadFabricVersions = useCallback(async () => { if (fabricVersions) return; if (!versionId) return toast.error("Version ID is not set"); setIsLoadingFabric(true); try { const versions = await getFabricLoadersForVersion(versionId); setFabricVersions(versions); } catch (e) { console.error("Failed to load Fabric versions:", e); toast.error(`Failed to load Fabric versions: ${String(e)}`); } finally { setIsLoadingFabric(false); } }, [versionId, fabricVersions]); const modLoaderField = register("modLoader"); const modLoaderVersionField = register("modLoaderVersion"); return (
Instance Name {errors.name && } Instance Notes