From d4ea239d4477e9427b52994ea25d54941dfdba3f Mon Sep 17 00:00:00 2001
From: HsiangNianian <i@jyunko.cn>
Date: Sun, 18 Jan 2026 14:53:44 +0800
Subject: feat(frontend): add instance editor modal with tabbed interface

- Create InstanceEditorModal.svelte with 4 tabs:
  * Info: Instance name, notes, metadata (created date, last played)
  * Version: Mod loader switcher and version display
  * Files: File browser for mods/resourcepacks/shaderpacks/saves/screenshots
  * Settings: Memory override and JVM arguments customization
- Wire InstanceEditorModal to InstancesView with Edit button
- Add FileInfo type definition to types/index.ts
- Fix accessibility issues: proper button roles, keyboard events
- All TypeScript and Svelte compilation errors resolved
- Enable comprehensive per-instance configuration management
---
 ui/src/components/InstancesView.svelte | 67 +++++++++-------------------------
 1 file changed, 17 insertions(+), 50 deletions(-)

(limited to 'ui/src/components/InstancesView.svelte')

diff --git a/ui/src/components/InstancesView.svelte b/ui/src/components/InstancesView.svelte
index e42f813..5334f9e 100644
--- a/ui/src/components/InstancesView.svelte
+++ b/ui/src/components/InstancesView.svelte
@@ -4,12 +4,14 @@
   import { Plus, Trash2, Edit2, Copy, Check, X } from "lucide-svelte";
   import type { Instance } from "../types";
   import InstanceCreationModal from "./InstanceCreationModal.svelte";
+  import InstanceEditorModal from "./InstanceEditorModal.svelte";
 
   let showCreateModal = $state(false);
   let showEditModal = $state(false);
   let showDeleteConfirm = $state(false);
   let showDuplicateModal = $state(false);
   let selectedInstance: Instance | null = $state(null);
+  let editingInstance: Instance | null = $state(null);
   let newInstanceName = $state("");
   let duplicateName = $state("");
 
@@ -22,9 +24,7 @@
   }
 
   function handleEdit(instance: Instance) {
-    selectedInstance = instance;
-    newInstanceName = instance.name;
-    showEditModal = true;
+    editingInstance = instance;
   }
 
   function handleDelete(instance: Instance) {
@@ -38,17 +38,6 @@
     showDuplicateModal = true;
   }
 
-  async function confirmEdit() {
-    if (!selectedInstance || !newInstanceName.trim()) return;
-    await instancesState.updateInstance({
-      ...selectedInstance,
-      name: newInstanceName.trim(),
-    });
-    showEditModal = false;
-    selectedInstance = null;
-    newInstanceName = "";
-  }
-
   async function confirmDelete() {
     if (!selectedInstance) return;
     await instancesState.deleteInstance(selectedInstance.id);
@@ -104,10 +93,13 @@
     <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
       {#each instancesState.instances as instance (instance.id)}
         <div
+          role="button"
+          tabindex="0"
           class="relative p-4 bg-gray-100 dark:bg-gray-800 rounded-lg border-2 transition-all cursor-pointer hover:border-blue-500 {instancesState.activeInstanceId === instance.id
             ? 'border-blue-500'
             : 'border-transparent'}"
           onclick={() => instancesState.setActiveInstance(instance.id)}
+          onkeydown={(e) => e.key === "Enter" && instancesState.setActiveInstance(instance.id)}
         >
           {#if instancesState.activeInstanceId === instance.id}
             <div class="absolute top-2 right-2">
@@ -121,6 +113,7 @@
             </h3>
             <div class="flex gap-1">
               <button
+                type="button"
                 onclick={(e) => {
                   e.stopPropagation();
                   handleEdit(instance);
@@ -131,6 +124,7 @@
                 <Edit2 size={16} class="text-gray-600 dark:text-gray-400" />
               </button>
               <button
+                type="button"
                 onclick={(e) => {
                   e.stopPropagation();
                   handleDuplicate(instance);
@@ -141,6 +135,7 @@
                 <Copy size={16} class="text-gray-600 dark:text-gray-400" />
               </button>
               <button
+                type="button"
                 onclick={(e) => {
                   e.stopPropagation();
                   handleDelete(instance);
@@ -190,41 +185,14 @@
 <!-- Create Modal -->
 <InstanceCreationModal isOpen={showCreateModal} onClose={() => (showCreateModal = false)} />
 
-<!-- Edit Modal -->
-{#if showEditModal && selectedInstance}
-  <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
-    <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96">
-      <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Edit Instance</h2>
-      <input
-        type="text"
-        bind:value={newInstanceName}
-        placeholder="Instance name"
-        class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4"
-        onkeydown={(e) => e.key === "Enter" && confirmEdit()}
-        autofocus
-      />
-      <div class="flex gap-2 justify-end">
-        <button
-          onclick={() => {
-            showEditModal = false;
-            selectedInstance = null;
-            newInstanceName = "";
-          }}
-          class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
-        >
-          Cancel
-        </button>
-        <button
-          onclick={confirmEdit}
-          disabled={!newInstanceName.trim()}
-          class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
-        >
-          Save
-        </button>
-      </div>
-    </div>
-  </div>
-{/if}
+<!-- Instance Editor Modal -->
+<InstanceEditorModal
+  isOpen={editingInstance !== null}
+  instance={editingInstance}
+  onClose={() => {
+    editingInstance = null;
+  }}
+/>
 
 <!-- Delete Confirmation -->
 {#if showDeleteConfirm && selectedInstance}
@@ -266,7 +234,6 @@
         placeholder="New instance name"
         class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4"
         onkeydown={(e) => e.key === "Enter" && confirmDuplicate()}
-        autofocus
       />
       <div class="flex gap-2 justify-end">
         <button
-- 
cgit v1.2.3-70-g09d2

