From 5b799a125a970e5e56f29a08b3c86450855fb6c4 Mon Sep 17 00:00:00 2001 From: 苏向夜 Date: Sun, 29 Mar 2026 21:32:54 +0800 Subject: refactor(ui): rewrite instance create --- packages/ui/src/pages/instances/create.tsx | 746 +++++++++++++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 packages/ui/src/pages/instances/create.tsx (limited to 'packages/ui/src/pages/instances/create.tsx') diff --git a/packages/ui/src/pages/instances/create.tsx b/packages/ui/src/pages/instances/create.tsx new file mode 100644 index 0000000..57efea2 --- /dev/null +++ b/packages/ui/src/pages/instances/create.tsx @@ -0,0 +1,746 @@ +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 + +