From 66668d85d603c5841d755a6023aa1925559fc6d4 Mon Sep 17 00:00:00 2001
From: 苏向夜 <fu050409@163.com>
Date: Wed, 25 Feb 2026 01:32:51 +0800
Subject: chore(workspace): replace legacy codes

---
 packages/ui/src/pages/index.tsx | 76 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 packages/ui/src/pages/index.tsx

(limited to 'packages/ui/src/pages/index.tsx')

diff --git a/packages/ui/src/pages/index.tsx b/packages/ui/src/pages/index.tsx
new file mode 100644
index 0000000..54cfc1e
--- /dev/null
+++ b/packages/ui/src/pages/index.tsx
@@ -0,0 +1,76 @@
+import { useEffect } from "react";
+import { Outlet, useLocation } from "react-router";
+import { ParticleBackground } from "@/components/particle-background";
+import { Sidebar } from "@/components/sidebar";
+import { useAuthStore } from "@/models/auth";
+import { useSettingsStore } from "@/models/settings";
+
+export function IndexPage() {
+  const authStore = useAuthStore();
+  const settingsStore = useSettingsStore();
+
+  const location = useLocation();
+
+  useEffect(() => {
+    authStore.init();
+    settingsStore.refresh();
+  }, [authStore.init, settingsStore.refresh]);
+
+  return (
+    <div className="relative h-screen w-full overflow-hidden bg-background font-sans">
+      <div className="absolute inset-0 z-0 bg-gray-100 dark:bg-[#09090b] overflow-hidden">
+        {settingsStore.config?.customBackgroundPath && (
+          <>
+            <img
+              src={settingsStore.config?.customBackgroundPath}
+              alt="Background"
+              className="absolute inset-0 w-full h-full object-cover transition-transform duration-[20s] ease-linear"
+              onError={(e) =>
+                console.error("Failed to load main background:", e)
+              }
+            />
+            {/* Dimming Overlay for readability */}
+            <div className="absolute inset-0 bg-black/50" />
+          </>
+        )}
+
+        {!settingsStore.config?.customBackgroundPath && (
+          <>
+            {settingsStore.theme === "dark" ? (
+              <div className="absolute inset-0 opacity-60 bg-linear-to-br from-emerald-900 via-zinc-900 to-indigo-950"></div>
+            ) : (
+              <div className="absolute inset-0 opacity-100 bg-linear-to-br from-emerald-100 via-gray-100 to-indigo-100"></div>
+            )}
+
+            {location.pathname === "/" && <ParticleBackground />}
+
+            <div className="absolute inset-0 bg-linear-to-t from-zinc-900 via-transparent to-black/50 dark:from-zinc-900 dark:to-black/50"></div>
+          </>
+        )}
+
+        {/* Subtle Grid Overlay */}
+        <div
+          className="absolute inset-0 z-0 dark:opacity-10 opacity-30 pointer-events-none"
+          style={{
+            backgroundImage: `linear-gradient(${
+              settingsStore.config?.theme === "dark" ? "#ffffff" : "#000000"
+            } 1px, transparent 1px), linear-gradient(90deg, ${
+              settingsStore.config?.theme === "dark" ? "#ffffff" : "#000000"
+            } 1px, transparent 1px)`,
+            backgroundSize: "40px 40px",
+            maskImage:
+              "radial-gradient(circle at 50% 50%, black 30%, transparent 70%)",
+          }}
+        />
+      </div>
+
+      <div className="size-full flex flex-row p-4 space-x-4 z-20 relative">
+        <Sidebar />
+
+        <main className="size-full overflow-hidden">
+          <Outlet />
+        </main>
+      </div>
+    </div>
+  );
+}
-- 
cgit v1.2.3-70-g09d2


From d95ca2801c19a89a2a845f43b6e0133bf4e9be50 Mon Sep 17 00:00:00 2001
From: 苏向夜 <fu050409@163.com>
Date: Thu, 26 Feb 2026 18:30:57 +0800
Subject: refactor: migrate some invokes

---
 .changes/migrate-apis.md                           |   5 +
 .../ui/src/components/instance-creation-modal.tsx  |  70 +++++------
 .../ui/src/components/instance-editor-modal.tsx    |   4 +-
 packages/ui/src/models/instance.ts                 | 131 ++++++++++++++++++++
 packages/ui/src/models/instances.ts                | 135 ---------------------
 packages/ui/src/pages/index.tsx                    |   5 +-
 packages/ui/src/pages/instances-view.tsx           |   4 +-
 7 files changed, 175 insertions(+), 179 deletions(-)
 create mode 100644 .changes/migrate-apis.md
 create mode 100644 packages/ui/src/models/instance.ts
 delete mode 100644 packages/ui/src/models/instances.ts

(limited to 'packages/ui/src/pages/index.tsx')

diff --git a/.changes/migrate-apis.md b/.changes/migrate-apis.md
new file mode 100644
index 0000000..54f1566
--- /dev/null
+++ b/.changes/migrate-apis.md
@@ -0,0 +1,5 @@
+---
+"@dropout/ui": "patch:refactor"
+---
+
+Migrate tauri invokes of instance creation modal to generated client.
diff --git a/packages/ui/src/components/instance-creation-modal.tsx b/packages/ui/src/components/instance-creation-modal.tsx
index 8a2b1b4..7c46d0f 100644
--- a/packages/ui/src/components/instance-creation-modal.tsx
+++ b/packages/ui/src/components/instance-creation-modal.tsx
@@ -1,7 +1,13 @@
-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 {
+  getFabricLoadersForVersion,
+  getForgeVersionsForGame,
+  installFabric,
+  installForge,
+  installVersion,
+} from "@/client";
 import { Button } from "@/components/ui/button";
 import {
   Dialog,
@@ -13,12 +19,13 @@ import {
 } from "@/components/ui/dialog";
 import { Input } from "@/components/ui/input";
 import { ScrollArea } from "@/components/ui/scroll-area";
-import { useInstancesStore } from "@/models/instances";
+import { useInstanceStore } from "@/models/instance";
 import { useGameStore } from "@/stores/game-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";
+import type {
+  FabricLoaderEntry,
+  ForgeVersion as ForgeVersionEntry,
+  Version,
+} from "@/types";
 
 interface Props {
   open: boolean;
@@ -27,7 +34,7 @@ interface Props {
 
 export function InstanceCreationModal({ open, onOpenChange }: Props) {
   const gameStore = useGameStore();
-  const instancesStore = useInstancesStore();
+  const instancesStore = useInstanceStore();
 
   // Steps: 1 = name, 2 = version, 3 = mod loader
   const [step, setStep] = useState<number>(1);
@@ -61,12 +68,7 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) {
     setForgeVersions([]);
     try {
       if (modLoaderType === "fabric") {
-        const loaders = await invoke<FabricLoaderEntry[]>(
-          "get_fabric_loaders_for_version",
-          {
-            gameVersion: selectedVersionUI.id,
-          },
-        );
+        const loaders = await getFabricLoadersForVersion(selectedVersionUI.id);
         setFabricLoaders(loaders || []);
         if (loaders && loaders.length > 0) {
           setSelectedFabricLoader(loaders[0].loader.version);
@@ -74,12 +76,7 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) {
           setSelectedFabricLoader("");
         }
       } else if (modLoaderType === "forge") {
-        const versions = await invoke<ForgeVersionEntry[]>(
-          "get_forge_versions_for_game",
-          {
-            gameVersion: selectedVersionUI.id,
-          },
-        );
+        const versions = await getForgeVersionsForGame(selectedVersionUI.id);
         setForgeVersions(versions || []);
         if (versions && versions.length > 0) {
           // Binding `ForgeVersion` uses `version` (not `id`) — use `.version` here.
@@ -182,17 +179,12 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) {
 
     try {
       // Step 1: create instance
-      const instance = await invoke<Instance>("create_instance", {
-        name: instanceName.trim(),
-      });
+      const instance = await instancesStore.create(instanceName.trim());
 
       // If selectedVersion provided, install it
-      if (selectedVersionUI) {
+      if (selectedVersionUI && instance) {
         try {
-          await invoke("install_version", {
-            instanceId: instance.id,
-            versionId: selectedVersionUI.id,
-          });
+          await installVersion(instance?.id, selectedVersionUI.id);
         } catch (err) {
           console.error("Failed to install base version:", err);
           // continue - instance created but version install failed
@@ -203,24 +195,24 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) {
       }
 
       // If mod loader selected, install it
-      if (modLoaderType === "fabric" && selectedFabricLoader) {
+      if (modLoaderType === "fabric" && selectedFabricLoader && instance) {
         try {
-          await invoke("install_fabric", {
-            instanceId: instance.id,
-            gameVersion: selectedVersionUI?.id ?? "",
-            loaderVersion: selectedFabricLoader,
-          });
+          await installFabric(
+            instance?.id,
+            selectedVersionUI?.id ?? "",
+            selectedFabricLoader,
+          );
         } catch (err) {
           console.error("Failed to install Fabric:", err);
           toast.error(`Failed to install Fabric: ${String(err)}`);
         }
-      } else if (modLoaderType === "forge" && selectedForgeLoader) {
+      } else if (modLoaderType === "forge" && selectedForgeLoader && instance) {
         try {
-          await invoke("install_forge", {
-            instanceId: instance.id,
-            gameVersion: selectedVersionUI?.id ?? "",
-            installerVersion: selectedForgeLoader,
-          });
+          await installForge(
+            instance?.id,
+            selectedVersionUI?.id ?? "",
+            selectedForgeLoader,
+          );
         } catch (err) {
           console.error("Failed to install Forge:", err);
           toast.error(`Failed to install Forge: ${String(err)}`);
diff --git a/packages/ui/src/components/instance-editor-modal.tsx b/packages/ui/src/components/instance-editor-modal.tsx
index f880c20..d964185 100644
--- a/packages/ui/src/components/instance-editor-modal.tsx
+++ b/packages/ui/src/components/instance-editor-modal.tsx
@@ -16,7 +16,7 @@ import { Input } from "@/components/ui/input";
 import { Textarea } from "@/components/ui/textarea";
 
 import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/models/instances";
+import { useInstanceStore } from "@/models/instance";
 import { useSettingsStore } from "@/models/settings";
 import type { FileInfo } from "../types/bindings/core";
 import type { Instance } from "../types/bindings/instance";
@@ -28,7 +28,7 @@ type Props = {
 };
 
 export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
-  const instancesStore = useInstancesStore();
+  const instancesStore = useInstanceStore();
   const { config } = useSettingsStore();
 
   const [activeTab, setActiveTab] = useState<
diff --git a/packages/ui/src/models/instance.ts b/packages/ui/src/models/instance.ts
new file mode 100644
index 0000000..a3fda3d
--- /dev/null
+++ b/packages/ui/src/models/instance.ts
@@ -0,0 +1,131 @@
+import { toast } from "sonner";
+import { create } from "zustand";
+import {
+  createInstance,
+  deleteInstance,
+  duplicateInstance,
+  getActiveInstance,
+  getInstance,
+  listInstances,
+  setActiveInstance,
+  updateInstance,
+} from "@/client";
+import type { Instance } from "@/types";
+
+interface InstanceState {
+  instances: Instance[];
+  activeInstance: Instance | null;
+
+  refresh: () => Promise<void>;
+  create: (name: string) => Promise<Instance | null>;
+  delete: (id: string) => Promise<void>;
+  update: (instance: Instance) => Promise<void>;
+  setActiveInstance: (instance: Instance) => Promise<void>;
+  duplicate: (id: string, newName: string) => Promise<Instance | null>;
+  get: (id: string) => Promise<Instance | null>;
+}
+
+export const useInstanceStore = create<InstanceState>((set, get) => ({
+  instances: [],
+  activeInstance: null,
+
+  refresh: async () => {
+    const { setActiveInstance } = get();
+    try {
+      const instances = await listInstances();
+      const activeInstance = await getActiveInstance();
+
+      if (!activeInstance && instances.length > 0) {
+        // If no active instance but instances exist, set the first one as active
+        await setActiveInstance(instances[0]);
+      }
+
+      set({ instances, activeInstance });
+    } catch (e) {
+      console.error("Failed to load instances:", e);
+      toast.error("Error loading instances");
+    }
+  },
+
+  create: async (name) => {
+    const { refresh } = get();
+    try {
+      const instance = await createInstance(name);
+      await refresh();
+      toast.success(`Instance "${name}" created successfully`);
+      return instance;
+    } catch (e) {
+      console.error("Failed to create instance:", e);
+      toast.error("Error creating instance");
+      return null;
+    }
+  },
+
+  delete: async (id) => {
+    const { refresh, instances, activeInstance, setActiveInstance } = get();
+    try {
+      await deleteInstance(id);
+      await refresh();
+
+      // If deleted instance was active, set another as active
+      if (activeInstance?.id === id) {
+        if (instances.length > 0) {
+          await setActiveInstance(instances[0]);
+        } else {
+          set({ activeInstance: null });
+        }
+      }
+
+      toast.success("Instance deleted successfully");
+    } catch (e) {
+      console.error("Failed to delete instance:", e);
+      toast.error("Error deleting instance");
+    }
+  },
+
+  update: async (instance) => {
+    const { refresh } = get();
+    try {
+      await updateInstance(instance);
+      await refresh();
+      toast.success("Instance updated successfully");
+    } catch (e) {
+      console.error("Failed to update instance:", e);
+      toast.error("Error updating instance");
+    }
+  },
+
+  setActiveInstance: async (instance) => {
+    try {
+      await setActiveInstance(instance.id);
+      set({ activeInstance: instance });
+      toast.success("Active instance changed");
+    } catch (e) {
+      console.error("Failed to set active instance:", e);
+      toast.error("Error setting active instance");
+    }
+  },
+
+  duplicate: async (id, newName) => {
+    const { refresh } = get();
+    try {
+      const instance = await duplicateInstance(id, newName);
+      await refresh();
+      toast.success(`Instance duplicated as "${newName}"`);
+      return instance;
+    } catch (e) {
+      console.error("Failed to duplicate instance:", e);
+      toast.error("Error duplicating instance");
+      return null;
+    }
+  },
+
+  get: async (id) => {
+    try {
+      return await getInstance(id);
+    } catch (e) {
+      console.error("Failed to get instance:", e);
+      return null;
+    }
+  },
+}));
diff --git a/packages/ui/src/models/instances.ts b/packages/ui/src/models/instances.ts
deleted file mode 100644
index f434c7c..0000000
--- a/packages/ui/src/models/instances.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { toast } from "sonner";
-import { create } from "zustand";
-import {
-  createInstance,
-  deleteInstance,
-  duplicateInstance,
-  getActiveInstance,
-  getInstance,
-  listInstances,
-  setActiveInstance,
-  updateInstance,
-} from "@/client";
-import type { Instance } from "@/types";
-
-interface InstancesState {
-  // State
-  instances: Instance[];
-  activeInstance: Instance | null;
-
-  // Actions
-  refresh: () => Promise<void>;
-  create: (name: string) => Promise<Instance | null>;
-  delete: (id: string) => Promise<void>;
-  update: (instance: Instance) => Promise<void>;
-  setActiveInstance: (instance: Instance) => Promise<void>;
-  duplicate: (id: string, newName: string) => Promise<Instance | null>;
-  getInstance: (id: string) => Promise<Instance | null>;
-}
-
-export const useInstancesStore = create<InstancesState>((set, get) => ({
-  // Initial state
-  instances: [],
-  activeInstance: null,
-
-  // Actions
-  refresh: async () => {
-    const { setActiveInstance } = get();
-    try {
-      const instances = await listInstances();
-      const active = await getActiveInstance();
-
-      if (!active && instances.length > 0) {
-        // If no active instance but instances exist, set the first one as active
-        await setActiveInstance(instances[0]);
-      }
-
-      set({ instances });
-    } catch (e) {
-      console.error("Failed to load instances:", e);
-      toast.error("Error loading instances");
-    }
-  },
-
-  create: async (name) => {
-    const { refresh } = get();
-    try {
-      const instance = await createInstance(name);
-      await refresh();
-      toast.success(`Instance "${name}" created successfully`);
-      return instance;
-    } catch (e) {
-      console.error("Failed to create instance:", e);
-      toast.error("Error creating instance");
-      return null;
-    }
-  },
-
-  delete: async (id) => {
-    const { refresh, instances, activeInstance, setActiveInstance } = get();
-    try {
-      await deleteInstance(id);
-      await refresh();
-
-      // If deleted instance was active, set another as active
-      if (activeInstance?.id === id) {
-        if (instances.length > 0) {
-          await setActiveInstance(instances[0]);
-        } else {
-          set({ activeInstance: null });
-        }
-      }
-
-      toast.success("Instance deleted successfully");
-    } catch (e) {
-      console.error("Failed to delete instance:", e);
-      toast.error("Error deleting instance");
-    }
-  },
-
-  update: async (instance) => {
-    const { refresh } = get();
-    try {
-      await updateInstance(instance);
-      await refresh();
-      toast.success("Instance updated successfully");
-    } catch (e) {
-      console.error("Failed to update instance:", e);
-      toast.error("Error updating instance");
-    }
-  },
-
-  setActiveInstance: async (instance) => {
-    try {
-      await setActiveInstance(instance.id);
-      set({ activeInstance: instance });
-      toast.success("Active instance changed");
-    } catch (e) {
-      console.error("Failed to set active instance:", e);
-      toast.error("Error setting active instance");
-    }
-  },
-
-  duplicate: async (id, newName) => {
-    const { refresh } = get();
-    try {
-      const instance = await duplicateInstance(id, newName);
-      await refresh();
-      toast.success(`Instance duplicated as "${newName}"`);
-      return instance;
-    } catch (e) {
-      console.error("Failed to duplicate instance:", e);
-      toast.error("Error duplicating instance");
-      return null;
-    }
-  },
-
-  getInstance: async (id) => {
-    try {
-      return await getInstance(id);
-    } catch (e) {
-      console.error("Failed to get instance:", e);
-      return null;
-    }
-  },
-}));
diff --git a/packages/ui/src/pages/index.tsx b/packages/ui/src/pages/index.tsx
index 54cfc1e..093ccb2 100644
--- a/packages/ui/src/pages/index.tsx
+++ b/packages/ui/src/pages/index.tsx
@@ -3,18 +3,21 @@ import { Outlet, useLocation } from "react-router";
 import { ParticleBackground } from "@/components/particle-background";
 import { Sidebar } from "@/components/sidebar";
 import { useAuthStore } from "@/models/auth";
+import { useInstanceStore } from "@/models/instance";
 import { useSettingsStore } from "@/models/settings";
 
 export function IndexPage() {
   const authStore = useAuthStore();
   const settingsStore = useSettingsStore();
+  const instanceStore = useInstanceStore();
 
   const location = useLocation();
 
   useEffect(() => {
     authStore.init();
     settingsStore.refresh();
-  }, [authStore.init, settingsStore.refresh]);
+    instanceStore.refresh();
+  }, [authStore.init, settingsStore.refresh, instanceStore.refresh]);
 
   return (
     <div className="relative h-screen w-full overflow-hidden bg-background font-sans">
diff --git a/packages/ui/src/pages/instances-view.tsx b/packages/ui/src/pages/instances-view.tsx
index ad6bd38..1634905 100644
--- a/packages/ui/src/pages/instances-view.tsx
+++ b/packages/ui/src/pages/instances-view.tsx
@@ -13,11 +13,11 @@ import {
 } from "@/components/ui/dialog";
 import { Input } from "@/components/ui/input";
 import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/models/instances";
+import { useInstanceStore } from "@/models/instance";
 import type { Instance } from "../types/bindings/instance";
 
 export function InstancesView() {
-  const instancesStore = useInstancesStore();
+  const instancesStore = useInstanceStore();
 
   // Modal / UI state
   const [showCreateModal, setShowCreateModal] = useState(false);
-- 
cgit v1.2.3-70-g09d2

