From 9430bee86fbf943283eb5a6f63bd750b875ff433 Mon Sep 17 00:00:00 2001 From: 苏向夜 Date: Fri, 23 Jan 2026 20:51:28 +0800 Subject: feat(ui): add new ui project --- packages/ui-new/src/pages/instances-view.tsx | 370 +++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 packages/ui-new/src/pages/instances-view.tsx (limited to 'packages/ui-new/src/pages/instances-view.tsx') diff --git a/packages/ui-new/src/pages/instances-view.tsx b/packages/ui-new/src/pages/instances-view.tsx new file mode 100644 index 0000000..0c511a1 --- /dev/null +++ b/packages/ui-new/src/pages/instances-view.tsx @@ -0,0 +1,370 @@ +import { Copy, Edit2, Plus, Trash2 } from "lucide-react"; +import { useEffect, useState } from "react"; +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 "@/stores/instances-store"; +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 [newInstanceName, setNewInstanceName] = useState(""); + const [duplicateName, setDuplicateName] = useState(""); + + // Load instances on mount (matches Svelte onMount behavior) + useEffect(() => { + instancesStore.loadInstances(); + // instancesStore methods are stable (Zustand); do not add to deps to avoid spurious runs + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [instancesStore.loadInstances]); + + // Handlers to open modals + const openCreate = () => { + setNewInstanceName(""); + 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); + }; + + // Confirm actions + const confirmCreate = async () => { + const name = newInstanceName.trim(); + if (!name) return; + await instancesStore.createInstance(name); + setShowCreateModal(false); + setNewInstanceName(""); + }; + + const confirmEdit = async () => { + if (!editingInstance) return; + await instancesStore.updateInstance(editingInstance); + setEditingInstance(null); + setShowEditModal(false); + }; + + const confirmDelete = async () => { + if (!selectedInstance) return; + await instancesStore.deleteInstance(selectedInstance.id); + setSelectedInstance(null); + setShowDeleteConfirm(false); + }; + + const confirmDuplicate = async () => { + if (!selectedInstance) return; + const name = duplicateName.trim(); + if (!name) return; + await instancesStore.duplicateInstance(selectedInstance.id, name); + setSelectedInstance(null); + setDuplicateName(""); + setShowDuplicateModal(false); + }; + + const setActiveInstance = async (id: string) => { + await instancesStore.setActiveInstance(id); + }; + + 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.activeInstanceId === instance.id; + + return ( +
  • setActiveInstance(instance.id)} + onKeyDown={(e) => + e.key === "Enter" && setActiveInstance(instance.id) + } + className={`relative p-4 text-left rounded-lg 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 */} +
    + + + + + +
    +
  • + ); + })} +
+ )} + + {/* Create Modal */} + + + + Create Instance + + Enter a name for the new instance. + + + +
+ setNewInstanceName(e.target.value)} + placeholder="Instance name" + /> +
+ + + + + +
+
+ + { + 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