From 66668d85d603c5841d755a6023aa1925559fc6d4 Mon Sep 17 00:00:00 2001 From: 苏向夜 Date: Wed, 25 Feb 2026 01:32:51 +0800 Subject: chore(workspace): replace legacy codes --- packages/ui/src/pages/instances-view.tsx | 315 +++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 packages/ui/src/pages/instances-view.tsx (limited to 'packages/ui/src/pages/instances-view.tsx') diff --git a/packages/ui/src/pages/instances-view.tsx b/packages/ui/src/pages/instances-view.tsx new file mode 100644 index 0000000..ad6bd38 --- /dev/null +++ b/packages/ui/src/pages/instances-view.tsx @@ -0,0 +1,315 @@ +import { Copy, Edit2, Plus, Trash2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import InstanceCreationModal from "@/components/instance-creation-modal"; +import InstanceEditorModal from "@/components/instance-editor-modal"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { toNumber } from "@/lib/tsrs-utils"; +import { useInstancesStore } from "@/models/instances"; +import type { Instance } from "../types/bindings/instance"; + +export function InstancesView() { + const instancesStore = useInstancesStore(); + + // Modal / UI state + const [showCreateModal, setShowCreateModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [showDuplicateModal, setShowDuplicateModal] = useState(false); + + // Selected / editing instance state + const [selectedInstance, setSelectedInstance] = useState( + null, + ); + const [editingInstance, setEditingInstance] = useState(null); + + // Form fields + const [duplicateName, setDuplicateName] = useState(""); + + useEffect(() => { + instancesStore.refresh(); + }, [instancesStore.refresh]); + + // Handlers to open modals + const openCreate = () => { + setShowCreateModal(true); + }; + + const openEdit = (instance: Instance) => { + setEditingInstance({ ...instance }); + setShowEditModal(true); + }; + + const openDelete = (instance: Instance) => { + setSelectedInstance(instance); + setShowDeleteConfirm(true); + }; + + const openDuplicate = (instance: Instance) => { + setSelectedInstance(instance); + setDuplicateName(`${instance.name} (Copy)`); + setShowDuplicateModal(true); + }; + + const confirmDelete = async () => { + if (!selectedInstance) return; + await instancesStore.delete(selectedInstance.id); + setSelectedInstance(null); + setShowDeleteConfirm(false); + }; + + const confirmDuplicate = async () => { + if (!selectedInstance) return; + const name = duplicateName.trim(); + if (!name) return; + await instancesStore.duplicate(selectedInstance.id, name); + setSelectedInstance(null); + setDuplicateName(""); + setShowDuplicateModal(false); + }; + + const formatDate = (timestamp: number): string => + new Date(timestamp * 1000).toLocaleDateString(); + + const formatLastPlayed = (timestamp: number): string => { + const date = new Date(timestamp * 1000); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (days === 0) return "Today"; + if (days === 1) return "Yesterday"; + if (days < 7) return `${days} days ago`; + return date.toLocaleDateString(); + }; + + return ( +
+
+

+ Instances +

+ +
+ + {instancesStore.instances.length === 0 ? ( +
+
+

No instances yet

+

Create your first instance to get started

+
+
+ ) : ( +
    + {instancesStore.instances.map((instance) => { + const isActive = instancesStore.activeInstance?.id === instance.id; + + return ( +
  • instancesStore.setActiveInstance(instance)} + onKeyDown={(e) => + e.key === "Enter" && + instancesStore.setActiveInstance(instance) + } + className={`relative p-4 text-left border-2 transition-all cursor-pointer hover:border-blue-500 ${ + isActive ? "border-blue-500" : "border-transparent" + } bg-gray-100 dark:bg-gray-800`} + > + {/* Instance Icon */} + {instance.iconPath ? ( +
    + {instance.name} +
    + ) : ( +
    + + {instance.name.charAt(0).toUpperCase()} + +
    + )} + +

    + {instance.name} +

    + +
    + {instance.versionId ? ( +

    Version: {instance.versionId}

    + ) : ( +

    No version selected

    + )} + + {instance.modLoader && ( +

    + Mod Loader:{" "} + {instance.modLoader} +

    + )} + +

    + Created: {formatDate(toNumber(instance.createdAt))} +

    + + {instance.lastPlayed && ( +

    + Last played:{" "} + {formatLastPlayed(toNumber(instance.lastPlayed))} +

    + )} +
    + + {/* Action Buttons */} +
    + + + + + +
    +
  • + ); + })} +
+ )} + + + + { + setShowEditModal(open); + if (!open) setEditingInstance(null); + }} + /> + + {/* Delete Confirmation */} + + + + Delete Instance + + Are you sure you want to delete "{selectedInstance?.name}"? This + action cannot be undone. + + + + + + + + + + + {/* Duplicate Modal */} + + + + Duplicate Instance + + Provide a name for the duplicated instance. + + + +
+ setDuplicateName(e.target.value)} + placeholder="New instance name" + onKeyDown={(e) => e.key === "Enter" && confirmDuplicate()} + /> +
+ + + + + +
+
+
+ ); +} -- cgit v1.2.3-70-g09d2 From d95ca2801c19a89a2a845f43b6e0133bf4e9be50 Mon Sep 17 00:00:00 2001 From: 苏向夜 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/instances-view.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(1); @@ -61,12 +68,7 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) { setForgeVersions([]); try { if (modLoaderType === "fabric") { - const loaders = await invoke( - "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( - "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("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; + create: (name: string) => Promise; + delete: (id: string) => Promise; + update: (instance: Instance) => Promise; + setActiveInstance: (instance: Instance) => Promise; + duplicate: (id: string, newName: string) => Promise; + get: (id: string) => Promise; +} + +export const useInstanceStore = create((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; - create: (name: string) => Promise; - delete: (id: string) => Promise; - update: (instance: Instance) => Promise; - setActiveInstance: (instance: Instance) => Promise; - duplicate: (id: string, newName: string) => Promise; - getInstance: (id: string) => Promise; -} - -export const useInstancesStore = create((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 (
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