From 02520ca62ac5e508e8748b2445171be64f459b6c Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Sun, 18 Jan 2026 13:34:52 +0800 Subject: fix(ci): improve pre-commit fmt hook configuration - Add pass_filenames: false to fmt hook - Add -- separator for cargo fmt args - Manually format code with cargo fmt --- ui/src/types/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'ui/src/types/index.ts') diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index a5b336e..858ee43 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -68,6 +68,19 @@ export interface LauncherConfig { log_upload_service: "paste.rs" | "pastebin.com"; pastebin_api_key?: string; assistant: AssistantConfig; + // Storage management + use_shared_caches: boolean; + keep_legacy_per_instance_storage: boolean; + // Feature-gated argument flags + feature_flags: FeatureFlags; +} + +export interface FeatureFlags { + demo_user: boolean; + quick_play_enabled: boolean; + quick_play_path?: string; + quick_play_singleplayer: boolean; + quick_play_multiplayer_server?: string; } export interface JavaInstallation { -- cgit v1.2.3-70-g09d2 From 6fdb730c323bcb1b052a2f9b13034603cbaf1e4d Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Sun, 18 Jan 2026 14:27:45 +0800 Subject: feat(backend): enhance instance management for editor support - Sync instance.version_id after start_game, install_fabric, install_forge - Add jvm_args_override and memory_override to Instance struct - Add file management commands: list_instance_directory, delete_instance_file, open_file_explorer - Support per-instance settings overrides (Java args, memory) --- src-tauri/src/core/instance.rs | 14 +++++ src-tauri/src/main.rs | 127 +++++++++++++++++++++++++++++++++++++++-- ui/src/types/index.ts | 7 +++ 3 files changed, 142 insertions(+), 6 deletions(-) (limited to 'ui/src/types/index.ts') diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 183e1cc..573273e 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -25,6 +25,16 @@ pub struct Instance { pub notes: Option, // 备注(可选) pub mod_loader: Option, // 模组加载器类型:"fabric", "forge", "vanilla" pub mod_loader_version: Option, // 模组加载器版本 + pub jvm_args_override: Option, // JVM参数覆盖(可选) + #[serde(default)] + pub memory_override: Option, // 内存设置覆盖(可选) +} + +/// Memory settings override for an instance +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MemoryOverride { + pub min: u32, // MB + pub max: u32, // MB } /// Configuration for all instances @@ -99,6 +109,8 @@ impl InstanceState { notes: None, mod_loader: Some("vanilla".to_string()), mod_loader_version: None, + jvm_args_override: None, + memory_override: None, }; let mut config = self.instances.lock().unwrap(); @@ -253,6 +265,8 @@ impl InstanceState { .unwrap() .as_secs() as i64, last_played: None, + jvm_args_override: source_instance.jvm_args_override.clone(), + memory_override: source_instance.memory_override.clone(), }; self.update_instance(new_instance.clone())?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a506713..35e2ef5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,7 +1,7 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::process::Stdio; use std::sync::Mutex; use tauri::{Emitter, Manager, State, Window}; // Added Emitter @@ -854,6 +854,12 @@ async fn start_game( } }); + // Update instance's version_id to track last launched version + if let Some(mut instance) = instance_state.get_instance(&instance_id) { + instance.version_id = Some(version_id.clone()); + let _ = instance_state.update_instance(instance); + } + Ok(format!("Launched Minecraft {} successfully!", version_id)) } @@ -1693,10 +1699,11 @@ async fn install_fabric( format!("Fabric installed successfully: {}", result.id) ); - // Update Instance's mod_loader metadata + // Update Instance's mod_loader metadata and version_id if let Some(mut instance) = instance_state.get_instance(&instance_id) { instance.mod_loader = Some("fabric".to_string()); - instance.mod_loader_version = Some(loader_version); + instance.mod_loader_version = Some(loader_version.clone()); + instance.version_id = Some(result.id.clone()); instance_state.update_instance(instance)?; } @@ -2107,10 +2114,11 @@ async fn install_forge( format!("Forge installed successfully: {}", result.id) ); - // Update Instance's mod_loader metadata + // Update Instance's mod_loader metadata and version_id if let Some(mut instance) = instance_state.get_instance(&instance_id) { instance.mod_loader = Some("forge".to_string()); - instance.mod_loader_version = Some(forge_version); + instance.mod_loader_version = Some(forge_version.clone()); + instance.version_id = Some(result.id.clone()); instance_state.update_instance(instance)?; } @@ -2422,6 +2430,110 @@ async fn migrate_shared_caches( }) } +/// File information for instance file browser +#[derive(Debug, Clone, Serialize, Deserialize)] +struct FileInfo { + name: String, + path: String, + is_directory: bool, + size: u64, + modified: i64, +} + +/// List files in an instance subdirectory (mods, resourcepacks, shaderpacks, saves, screenshots) +#[tauri::command] +async fn list_instance_directory( + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, + folder: String, // "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots" +) -> Result, String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; + + let target_dir = game_dir.join(&folder); + if !target_dir.exists() { + tokio::fs::create_dir_all(&target_dir) + .await + .map_err(|e| e.to_string())?; + } + + let mut files = Vec::new(); + let mut entries = tokio::fs::read_dir(&target_dir) + .await + .map_err(|e| e.to_string())?; + + while let Some(entry) = entries.next_entry().await.map_err(|e| e.to_string())? { + let metadata = entry.metadata().await.map_err(|e| e.to_string())?; + let modified = metadata + .modified() + .ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs() as i64) + .unwrap_or(0); + + files.push(FileInfo { + name: entry.file_name().to_string_lossy().to_string(), + path: entry.path().to_string_lossy().to_string(), + is_directory: metadata.is_dir(), + size: metadata.len(), + modified, + }); + } + + // Sort: directories first, then by name + files.sort_by(|a, b| { + b.is_directory + .cmp(&a.is_directory) + .then(a.name.to_lowercase().cmp(&b.name.to_lowercase())) + }); + + Ok(files) +} + +/// Delete a file in an instance directory +#[tauri::command] +async fn delete_instance_file(path: String) -> Result<(), String> { + let path_buf = std::path::PathBuf::from(&path); + if path_buf.is_dir() { + tokio::fs::remove_dir_all(&path_buf) + .await + .map_err(|e| e.to_string())?; + } else { + tokio::fs::remove_file(&path_buf) + .await + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +/// Open instance directory in system file explorer +#[tauri::command] +async fn open_file_explorer(path: String) -> Result<(), String> { + #[cfg(target_os = "windows")] + { + std::process::Command::new("explorer") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "linux")] + { + std::process::Command::new("xdg-open") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + Ok(()) +} + fn main() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) @@ -2529,7 +2641,10 @@ fn main() { set_active_instance, get_active_instance, duplicate_instance, - migrate_shared_caches + migrate_shared_caches, + list_instance_directory, + delete_instance_file, + open_file_explorer ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 858ee43..6632d58 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -214,4 +214,11 @@ export interface Instance { notes?: string; mod_loader?: string; mod_loader_version?: string; + jvm_args_override?: string; + memory_override?: MemoryOverride; +} + +export interface MemoryOverride { + min: number; // MB + max: number; // MB } -- cgit v1.2.3-70-g09d2 From d4ea239d4477e9427b52994ea25d54941dfdba3f Mon Sep 17 00:00:00 2001 From: HsiangNianian 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/InstanceEditorModal.svelte | 439 +++++++++++++++++++++++++++ ui/src/components/InstancesView.svelte | 67 ++-- ui/src/types/index.ts | 8 + 3 files changed, 464 insertions(+), 50 deletions(-) create mode 100644 ui/src/components/InstanceEditorModal.svelte (limited to 'ui/src/types/index.ts') diff --git a/ui/src/components/InstanceEditorModal.svelte b/ui/src/components/InstanceEditorModal.svelte new file mode 100644 index 0000000..0856d93 --- /dev/null +++ b/ui/src/components/InstanceEditorModal.svelte @@ -0,0 +1,439 @@ + + +{#if isOpen && instance} + +{/if} 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 @@
{#each instancesState.instances as instance (instance.id)}
instancesState.setActiveInstance(instance.id)} + onkeydown={(e) => e.key === "Enter" && instancesState.setActiveInstance(instance.id)} > {#if instancesState.activeInstanceId === instance.id}
@@ -121,6 +113,7 @@
- -
-
-
-{/if} + + { + editingInstance = null; + }} +/> {#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 />