From dc82d07c288b7e949081be49cc9a89cd3f23f8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=80=E5=BE=8B=E7=BA=AF?= Date: Tue, 20 Jan 2026 13:46:37 +0800 Subject: [PATCH 01/36] chore: Update CI workflow to include dev branch and platforms --- .github/workflows/semifold-ci.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/semifold-ci.yaml b/.github/workflows/semifold-ci.yaml index 7938a9d..6f496f0 100644 --- a/.github/workflows/semifold-ci.yaml +++ b/.github/workflows/semifold-ci.yaml @@ -1,7 +1,7 @@ name: Semifold CI on: push: - branches: [main] + branches: [main, dev] env: CARGO_TERM_COLOR: always @@ -24,10 +24,10 @@ jobs: name: "Linux x86_64 (GNU)" target: "x86_64-unknown-linux-gnu" args: "--target x86_64-unknown-linux-gnu" - # - platform: "ubuntu-latest" - # name: "Linux x86_64 (Musl)" - # target: "x86_64-unknown-linux-musl" - # args: "--target x86_64-unknown-linux-musl" + - platform: "ubuntu-latest" + name: "Linux x86_64 (Musl)" + target: "x86_64-unknown-linux-musl" + args: "--target x86_64-unknown-linux-musl" - platform: "ubuntu-24.04-arm" name: "Linux arm64" target: "aarch64-unknown-linux-gnu" @@ -46,10 +46,10 @@ jobs: name: "Windows x86_64 (MSVC)" target: "x86_64-pc-windows-msvc" args: "--target x86_64-pc-windows-msvc --bundles nsis" - # - platform: "windows-latest" - # name: "Windows x86_64 (GNU)" - # target: "x86_64-pc-windows-gnu" - # args: "--target x86_64-pc-windows-gnu --bundles nsis" + - platform: "windows-latest" + name: "Windows x86_64 (GNU)" + target: "x86_64-pc-windows-gnu" + args: "--target x86_64-pc-windows-gnu --bundles nsis" - platform: "windows-11-arm" name: "Windows arm64" target: "aarch64-pc-windows-msvc" From cf71adf8b02397ff14445c8f8b659ee519c60c50 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Tue, 20 Jan 2026 14:01:16 +0800 Subject: [PATCH 02/36] fix(ci): Add rustflags for Windows builds and install musl-tools for Linux --- .github/workflows/semifold-ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/semifold-ci.yaml b/.github/workflows/semifold-ci.yaml index 6f496f0..89d9e7e 100644 --- a/.github/workflows/semifold-ci.yaml +++ b/.github/workflows/semifold-ci.yaml @@ -50,6 +50,7 @@ jobs: name: "Windows x86_64 (GNU)" target: "x86_64-pc-windows-gnu" args: "--target x86_64-pc-windows-gnu --bundles nsis" + rustflags: "-C link-arg=-lws2_32" - platform: "windows-11-arm" name: "Windows arm64" target: "aarch64-pc-windows-msvc" @@ -64,6 +65,9 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libfuse2 + if [ "${{ matrix.target }}" = "x86_64-unknown-linux-musl" ]; then + sudo apt-get install -y musl-tools pkg-config + fi - name: Install pnpm uses: pnpm/action-setup@v4 @@ -104,6 +108,8 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUSTFLAGS: ${{ matrix.rustflags || '' }} + PKG_CONFIG_PATH: ${{ (matrix.target == 'x86_64-unknown-linux-musl') && '/usr/lib/x86_64-linux-musl/pkgconfig' || '' }} with: args: ${{ matrix.args }} From d2624887ca74e77ad524098c38114e3a7c91f065 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Tue, 20 Jan 2026 14:09:06 +0800 Subject: [PATCH 03/36] fix(ci): Enable cross compilation for musl builds and streamline dependency installation --- .github/workflows/semifold-ci.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/semifold-ci.yaml b/.github/workflows/semifold-ci.yaml index 89d9e7e..b820a78 100644 --- a/.github/workflows/semifold-ci.yaml +++ b/.github/workflows/semifold-ci.yaml @@ -28,6 +28,7 @@ jobs: name: "Linux x86_64 (Musl)" target: "x86_64-unknown-linux-musl" args: "--target x86_64-unknown-linux-musl" + use-cross: true - platform: "ubuntu-24.04-arm" name: "Linux arm64" target: "aarch64-unknown-linux-gnu" @@ -65,9 +66,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libfuse2 - if [ "${{ matrix.target }}" = "x86_64-unknown-linux-musl" ]; then - sudo apt-get install -y musl-tools pkg-config - fi - name: Install pnpm uses: pnpm/action-setup@v4 @@ -87,6 +85,10 @@ jobs: with: targets: ${{ matrix.target }} + - name: Install cross (for musl builds) + if: matrix.use-cross == true + run: cargo install cross + - name: Rust Cache uses: swatinem/rust-cache@v2 with: @@ -109,9 +111,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUSTFLAGS: ${{ matrix.rustflags || '' }} - PKG_CONFIG_PATH: ${{ (matrix.target == 'x86_64-unknown-linux-musl') && '/usr/lib/x86_64-linux-musl/pkgconfig' || '' }} + CARGO_BUILD_TARGET: ${{ matrix.target }} with: args: ${{ matrix.args }} + command: ${{ matrix.use-cross && 'cross' || 'cargo' }} - name: Fix AppImage for Wayland (Linux) if: startsWith(matrix.platform, 'ubuntu') && !startsWith(matrix.platform, 'macos') && !startsWith(matrix.platform, 'windows') From b27f30bfbdd05a53caaf6fe63fd9354b3689623c Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Tue, 20 Jan 2026 17:30:12 +0800 Subject: [PATCH 04/36] chore(deps): Add ts-rs dependency version 11.1.0 --- src-tauri/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6fde40f..f6b992e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,7 @@ tauri-plugin-fs = "2.4.5" bytes = "1.11.0" chrono = "0.4" regex = "1.12.2" +ts-rs = "11.1.0" [build-dependencies] tauri-build = { version = "2.0", features = [] } From 5cffcfe457d1b15d201647a5775268378c6018c6 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 10:47:56 +0800 Subject: [PATCH 05/36] feat: Add TypeScript support to data structures using ts-rs for type generation --- src-tauri/src/core/assistant.rs | 19 ++++++++--- src-tauri/src/core/auth.rs | 42 ++++++++++++++++++----- src-tauri/src/core/config.rs | 19 +++++++++-- src-tauri/src/core/downloader.rs | 43 +++++++++++++++++++---- src-tauri/src/core/fabric.rs | 55 +++++++++++++++++++++++++----- src-tauri/src/core/forge.rs | 14 ++++++-- src-tauri/src/core/game_version.rs | 48 ++++++++++++++++++++------ src-tauri/src/core/instance.rs | 16 +++++++-- src-tauri/src/core/java.rs | 32 ++++++++++++++--- src-tauri/src/core/manifest.rs | 13 +++++-- src-tauri/src/main.rs | 36 +++++++++++++++---- 11 files changed, 276 insertions(+), 61 deletions(-) diff --git a/src-tauri/src/core/assistant.rs b/src-tauri/src/core/assistant.rs index 9a8f7bf..987dc41 100644 --- a/src-tauri/src/core/assistant.rs +++ b/src-tauri/src/core/assistant.rs @@ -4,8 +4,10 @@ use serde::{Deserialize, Serialize}; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use tauri::{Emitter, Window}; +use ts_rs::TS; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Message.ts")] pub struct Message { pub role: String, pub content: String, @@ -51,7 +53,8 @@ pub struct OllamaTagsResponse { } // Simplified model info for frontend -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/ModelInfo.ts")] pub struct ModelInfo { pub id: String, pub name: String, @@ -102,7 +105,11 @@ pub struct OpenAIModelsResponse { } // Streaming response structures -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/GenerationStats.ts" +)] pub struct GenerationStats { pub total_duration: u64, pub load_duration: u64, @@ -112,7 +119,11 @@ pub struct GenerationStats { pub eval_duration: u64, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/StreamChunk.ts" +)] pub struct StreamChunk { pub content: String, pub done: bool, diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index d5e6c17..62f8c69 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use std::sync::Mutex; +use ts_rs::TS; use uuid::Uuid; // Helper to create a client with a custom User-Agent @@ -11,8 +12,13 @@ fn get_client() -> reqwest::Client { .unwrap_or_else(|_| reqwest::Client::new()) } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(tag = "type")] +#[ts( + export, + tag = "type", + export_to = "../packages/ui/src/types/generated/Account.ts" +)] pub enum Account { Offline(OfflineAccount), Microsoft(MicrosoftAccount), @@ -41,13 +47,21 @@ impl Account { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/OfflineAccount.ts" +)] pub struct OfflineAccount { pub username: String, pub uuid: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/MicrosoftAccount.ts" +)] pub struct MicrosoftAccount { pub username: String, pub uuid: String, @@ -73,11 +87,15 @@ pub fn generate_offline_uuid(username: &str) -> String { Uuid::new_v3(&namespace, username.as_bytes()).to_string() } -// const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; -const CLIENT_ID: &str = "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb"; // ATLauncher's Client ID +const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; +// const CLIENT_ID: &str = "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb"; // ATLauncher's Client ID const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DeviceCodeResponse.ts" +)] pub struct DeviceCodeResponse { pub user_code: String, pub device_code: String, @@ -87,7 +105,11 @@ pub struct DeviceCodeResponse { pub message: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/TokenResponse.ts" +)] pub struct TokenResponse { pub access_token: String, pub refresh_token: Option, @@ -209,7 +231,11 @@ pub struct MinecraftAuthResponse { pub expires_in: u64, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/MinecraftProfile.ts" +)] pub struct MinecraftProfile { pub id: String, pub name: String, diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index e4b9381..b31e0ce 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -3,8 +3,13 @@ use std::fs; use std::path::PathBuf; use std::sync::Mutex; use tauri::{AppHandle, Manager}; +use ts_rs::TS; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/AssistantConfig.ts" +)] #[serde(default)] pub struct AssistantConfig { pub enabled: bool, @@ -43,7 +48,11 @@ impl Default for AssistantConfig { } /// Feature-gated arguments configuration -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FeatureFlags.ts" +)] #[serde(default)] pub struct FeatureFlags { /// Demo user: enables demo-related arguments when rules require it @@ -70,7 +79,11 @@ impl Default for FeatureFlags { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/LauncherConfig.ts" +)] #[serde(default)] pub struct LauncherConfig { pub min_memory: u32, // in MB diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs index 26f6ebd..0a3590e 100644 --- a/src-tauri/src/core/downloader.rs +++ b/src-tauri/src/core/downloader.rs @@ -7,8 +7,13 @@ use std::sync::Arc; use tauri::{AppHandle, Emitter, Manager, Window}; use tokio::io::{AsyncSeekExt, AsyncWriteExt}; use tokio::sync::Semaphore; +use ts_rs::TS; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DownloadTask.ts" +)] pub struct DownloadTask { pub url: String, pub path: PathBuf, @@ -19,7 +24,11 @@ pub struct DownloadTask { } /// Metadata for resumable downloads stored in .part.meta file -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DownloadMetadata.ts" +)] pub struct DownloadMetadata { pub url: String, pub file_name: String, @@ -31,7 +40,11 @@ pub struct DownloadMetadata { } /// A download segment for multi-segment parallel downloading -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DownloadSegment.ts" +)] pub struct DownloadSegment { pub start: u64, pub end: u64, @@ -40,7 +53,11 @@ pub struct DownloadSegment { } /// Progress event for Java download -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaDownloadProgress.ts" +)] pub struct JavaDownloadProgress { pub file_name: String, pub downloaded_bytes: u64, @@ -52,7 +69,11 @@ pub struct JavaDownloadProgress { } /// Pending download task for queue persistence -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/PendingJavaDownload.ts" +)] pub struct PendingJavaDownload { pub major_version: u32, pub image_type: String, @@ -65,7 +86,11 @@ pub struct PendingJavaDownload { } /// Download queue for persistence -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DownloadQueue.ts" +)] pub struct DownloadQueue { pub pending_downloads: Vec, } @@ -419,7 +444,11 @@ fn create_new_metadata( } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/ProgressEvent.ts" +)] pub struct ProgressEvent { pub file: String, pub downloaded: u64, diff --git a/src-tauri/src/core/fabric.rs b/src-tauri/src/core/fabric.rs index 32790c7..feadd6b 100644 --- a/src-tauri/src/core/fabric.rs +++ b/src-tauri/src/core/fabric.rs @@ -8,11 +8,16 @@ use serde::{Deserialize, Serialize}; use std::error::Error; use std::path::PathBuf; +use ts_rs::TS; const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; /// Represents a Fabric loader version from the Meta API. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricLoaderVersion.ts" +)] pub struct FabricLoaderVersion { pub separator: String, pub build: i32, @@ -22,7 +27,11 @@ pub struct FabricLoaderVersion { } /// Represents a Fabric intermediary mapping version. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricIntermediaryVersion.ts" +)] pub struct FabricIntermediaryVersion { pub maven: String, pub version: String, @@ -30,7 +39,11 @@ pub struct FabricIntermediaryVersion { } /// Represents a combined loader + intermediary version entry. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricLoaderEntry.ts" +)] pub struct FabricLoaderEntry { pub loader: FabricLoaderVersion, pub intermediary: FabricIntermediaryVersion, @@ -39,7 +52,11 @@ pub struct FabricLoaderEntry { } /// Launcher metadata from Fabric Meta API. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricLauncherMeta.ts" +)] pub struct FabricLauncherMeta { pub version: i32, pub libraries: FabricLibraries, @@ -48,7 +65,11 @@ pub struct FabricLauncherMeta { } /// Libraries required by Fabric loader. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricLibraries.ts" +)] pub struct FabricLibraries { pub client: Vec, pub common: Vec, @@ -56,7 +77,11 @@ pub struct FabricLibraries { } /// A single Fabric library dependency. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricLibrary.ts" +)] pub struct FabricLibrary { pub name: String, pub url: Option, @@ -64,7 +89,11 @@ pub struct FabricLibrary { /// Main class configuration for Fabric. /// Can be either a struct with client/server fields or a simple string. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricMainClass.ts" +)] #[serde(untagged)] pub enum FabricMainClass { Structured { client: String, server: String }, @@ -89,14 +118,22 @@ impl FabricMainClass { } /// Represents a Minecraft version supported by Fabric. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/FabricGameVersion.ts" +)] pub struct FabricGameVersion { pub version: String, pub stable: bool, } /// Information about an installed Fabric version. -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/InstalledFabricVersion.ts" +)] pub struct InstalledFabricVersion { pub id: String, pub minecraft_version: String, diff --git a/src-tauri/src/core/forge.rs b/src-tauri/src/core/forge.rs index 65bf413..a256add 100644 --- a/src-tauri/src/core/forge.rs +++ b/src-tauri/src/core/forge.rs @@ -12,6 +12,7 @@ use std::error::Error; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; use std::path::PathBuf; +use ts_rs::TS; const FORGE_PROMOTIONS_URL: &str = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"; @@ -19,7 +20,11 @@ const FORGE_MAVEN_URL: &str = "https://maven.minecraftforge.net/"; const FORGE_FILES_URL: &str = "https://files.minecraftforge.net/"; /// Represents a Forge version entry. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/ForgeVersion.ts" +)] pub struct ForgeVersion { pub version: String, pub minecraft_version: String, @@ -36,11 +41,16 @@ struct ForgePromotions { } /// Information about an installed Forge version. -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/InstalledForgeVersion.ts" +)] pub struct InstalledForgeVersion { pub id: String, pub minecraft_version: String, pub forge_version: String, + #[ts(type = "string")] pub path: PathBuf, } diff --git a/src-tauri/src/core/game_version.rs b/src-tauri/src/core/game_version.rs index c62e232..52c7da3 100644 --- a/src-tauri/src/core/game_version.rs +++ b/src-tauri/src/core/game_version.rs @@ -1,8 +1,13 @@ use serde::{Deserialize, Serialize}; +use ts_rs::TS; /// Represents a Minecraft version JSON, supporting both vanilla and modded (Fabric/Forge) formats. /// Modded versions use `inheritsFrom` to reference a parent vanilla version. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/GameVersion.ts" +)] pub struct GameVersion { pub id: String, /// Optional for mod loaders that inherit from vanilla @@ -28,13 +33,18 @@ pub struct GameVersion { pub version_type: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Downloads.ts")] pub struct Downloads { pub client: DownloadArtifact, pub server: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/DownloadArtifact.ts" +)] pub struct DownloadArtifact { pub sha1: Option, pub size: Option, @@ -42,7 +52,8 @@ pub struct DownloadArtifact { pub path: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/AssetIndex.ts")] pub struct AssetIndex { pub id: String, pub sha1: String, @@ -52,43 +63,60 @@ pub struct AssetIndex { pub total_size: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Library.ts")] pub struct Library { pub downloads: Option, pub name: String, pub rules: Option>, + #[ts(type = "Record")] pub natives: Option, /// Maven repository URL for mod loader libraries pub url: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Rule.ts")] pub struct Rule { pub action: String, // "allow" or "disallow" pub os: Option, + #[ts(type = "Record")] pub features: Option, // Feature-based rules (e.g., is_demo_user, has_quick_plays_support) } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/OsRule.ts")] pub struct OsRule { pub name: Option, // "linux", "osx", "windows" pub version: Option, // Regex pub arch: Option, // "x86" } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/LibraryDownloads.ts" +)] pub struct LibraryDownloads { pub artifact: Option, + #[ts(type = "Record")] pub classifiers: Option, // Complex, simplifying for now } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Arguments.ts")] pub struct Arguments { + #[ts(type = "Record")] pub game: Option, + #[ts(type = "Record")] pub jvm: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaVersion.ts" +)] pub struct JavaVersion { pub component: String, #[serde(rename = "majorVersion")] diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 573273e..a281968 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -11,9 +11,11 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Mutex; use tauri::{AppHandle, Manager}; +use ts_rs::TS; /// Represents a game instance/profile -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Instance.ts")] pub struct Instance { pub id: String, // 唯一标识符(UUID) pub name: String, // 显示名称 @@ -31,14 +33,22 @@ pub struct Instance { } /// Memory settings override for an instance -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/MemoryOverride.ts" +)] pub struct MemoryOverride { pub min: u32, // MB pub max: u32, // MB } /// Configuration for all instances -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/InstanceConfig.ts" +)] pub struct InstanceConfig { pub instances: Vec, pub active_instance_id: Option, // 当前活动的实例ID diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs index 2e3c8a7..245c496 100644 --- a/src-tauri/src/core/java.rs +++ b/src-tauri/src/core/java.rs @@ -6,6 +6,7 @@ use std::process::Command; use tauri::AppHandle; use tauri::Emitter; use tauri::Manager; +use ts_rs::TS; use crate::core::downloader::{self, DownloadQueue, JavaDownloadProgress, PendingJavaDownload}; use crate::utils::zip; @@ -25,7 +26,11 @@ fn strip_unc_prefix(path: PathBuf) -> PathBuf { path } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaInstallation.ts" +)] pub struct JavaInstallation { pub path: String, pub version: String, @@ -33,7 +38,12 @@ pub struct JavaInstallation { } /// Java image type: JRE or JDK -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts( + export, + rename_all = "lowercase", + export_to = "../packages/ui/src/types/generated/ImageType.ts" +)] #[serde(rename_all = "lowercase")] pub enum ImageType { Jre, @@ -56,7 +66,11 @@ impl std::fmt::Display for ImageType { } /// Java release information for UI display -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaReleaseInfo.ts" +)] pub struct JavaReleaseInfo { pub major_version: u32, pub image_type: String, @@ -72,7 +86,11 @@ pub struct JavaReleaseInfo { } /// Java catalog containing all available versions -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaCatalog.ts" +)] pub struct JavaCatalog { pub releases: Vec, pub available_major_versions: Vec, @@ -128,7 +146,11 @@ pub struct AvailableReleases { } /// Java download information from Adoptium -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/JavaDownloadInfo.ts" +)] pub struct JavaDownloadInfo { pub version: String, pub release_name: String, diff --git a/src-tauri/src/core/manifest.rs b/src-tauri/src/core/manifest.rs index e792071..5382259 100644 --- a/src-tauri/src/core/manifest.rs +++ b/src-tauri/src/core/manifest.rs @@ -3,20 +3,27 @@ use std::error::Error; use std::path::PathBuf; use crate::core::game_version::GameVersion; +use ts_rs::TS; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/VersionManifest.ts" +)] pub struct VersionManifest { pub latest: Latest, pub versions: Vec, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Latest.ts")] pub struct Latest { pub release: String, pub snapshot: String, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/Version.ts")] pub struct Version { pub id: String, #[serde(rename = "type")] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0f1d7a1..e9a24fb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,7 +6,8 @@ use std::process::Stdio; use std::sync::Mutex; use tauri::{Emitter, Manager, State, Window}; // Added Emitter use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::Command; // Added Serialize +use tokio::process::Command; +use ts_rs::TS; // Added Serialize #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; @@ -1749,7 +1750,11 @@ async fn get_version_java_version( } /// Version metadata for display in the UI -#[derive(serde::Serialize)] +#[derive(serde::Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/VersionMetadata.ts" +)] struct VersionMetadata { id: String, #[serde(rename = "javaVersion")] @@ -1899,7 +1904,11 @@ async fn get_version_metadata( } /// Installed version info -#[derive(serde::Serialize)] +#[derive(serde::Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/InstalledVersion.ts" +)] struct InstalledVersion { id: String, #[serde(rename = "type")] @@ -2128,7 +2137,11 @@ async fn install_forge( Ok(result) } -#[derive(serde::Serialize)] +#[derive(serde::Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/GithubRelease.ts" +)] struct GithubRelease { tag_name: String, name: String, @@ -2174,7 +2187,11 @@ async fn get_github_releases() -> Result, String> { Ok(result) } -#[derive(Serialize)] +#[derive(Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/PastebinResponse.ts" +)] struct PastebinResponse { url: String, } @@ -2382,7 +2399,11 @@ async fn assistant_chat_stream( } /// Migrate instance caches to shared global caches -#[derive(Serialize)] +#[derive(Serialize, TS)] +#[ts( + export, + export_to = "../packages/ui/src/types/generated/MigrationResult.ts" +)] struct MigrationResult { moved_files: usize, hardlinks: usize, @@ -2431,7 +2452,8 @@ async fn migrate_shared_caches( } /// File information for instance file browser -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../packages/ui/src/types/generated/FileInfo.ts")] struct FileInfo { name: String, path: String, From e87d097060e41b89d4c68285c103cf96b2dd9449 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 10:52:34 +0800 Subject: [PATCH 06/36] fix: Update TypeScript export paths to reflect new directory structure --- src-tauri/src/core/assistant.rs | 11 +++++++---- src-tauri/src/core/auth.rs | 12 ++++++------ src-tauri/src/core/config.rs | 6 +++--- src-tauri/src/core/downloader.rs | 14 +++++++------- src-tauri/src/core/fabric.rs | 18 +++++++++--------- src-tauri/src/core/forge.rs | 4 ++-- src-tauri/src/core/game_version.rs | 29 +++++++++++++++++++---------- src-tauri/src/core/instance.rs | 9 ++++++--- src-tauri/src/core/java.rs | 10 +++++----- src-tauri/src/core/manifest.rs | 6 +++--- src-tauri/src/main.rs | 15 +++++++++------ 11 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src-tauri/src/core/assistant.rs b/src-tauri/src/core/assistant.rs index 987dc41..7adf713 100644 --- a/src-tauri/src/core/assistant.rs +++ b/src-tauri/src/core/assistant.rs @@ -7,7 +7,7 @@ use tauri::{Emitter, Window}; use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Message.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/Message.ts")] pub struct Message { pub role: String, pub content: String, @@ -54,7 +54,10 @@ pub struct OllamaTagsResponse { // Simplified model info for frontend #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/ModelInfo.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/ModelInfo.ts" +)] pub struct ModelInfo { pub id: String, pub name: String, @@ -108,7 +111,7 @@ pub struct OpenAIModelsResponse { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/GenerationStats.ts" + export_to = "../../packages/ui/src/types/generated/GenerationStats.ts" )] pub struct GenerationStats { pub total_duration: u64, @@ -122,7 +125,7 @@ pub struct GenerationStats { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/StreamChunk.ts" + export_to = "../../packages/ui/src/types/generated/StreamChunk.ts" )] pub struct StreamChunk { pub content: String, diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index 62f8c69..7fa8cd0 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -17,7 +17,7 @@ fn get_client() -> reqwest::Client { #[ts( export, tag = "type", - export_to = "../packages/ui/src/types/generated/Account.ts" + export_to = "../../packages/ui/src/types/generated/Account.ts" )] pub enum Account { Offline(OfflineAccount), @@ -50,7 +50,7 @@ impl Account { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/OfflineAccount.ts" + export_to = "../../packages/ui/src/types/generated/OfflineAccount.ts" )] pub struct OfflineAccount { pub username: String, @@ -60,7 +60,7 @@ pub struct OfflineAccount { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/MicrosoftAccount.ts" + export_to = "../../packages/ui/src/types/generated/MicrosoftAccount.ts" )] pub struct MicrosoftAccount { pub username: String, @@ -94,7 +94,7 @@ const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; #[derive(Debug, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DeviceCodeResponse.ts" + export_to = "../../packages/ui/src/types/generated/DeviceCodeResponse.ts" )] pub struct DeviceCodeResponse { pub user_code: String, @@ -108,7 +108,7 @@ pub struct DeviceCodeResponse { #[derive(Debug, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/TokenResponse.ts" + export_to = "../../packages/ui/src/types/generated/TokenResponse.ts" )] pub struct TokenResponse { pub access_token: String, @@ -234,7 +234,7 @@ pub struct MinecraftAuthResponse { #[derive(Debug, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/MinecraftProfile.ts" + export_to = "../../packages/ui/src/types/generated/MinecraftProfile.ts" )] pub struct MinecraftProfile { pub id: String, diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index b31e0ce..2c72928 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -8,7 +8,7 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/AssistantConfig.ts" + export_to = "../../packages/ui/src/types/generated/AssistantConfig.ts" )] #[serde(default)] pub struct AssistantConfig { @@ -51,7 +51,7 @@ impl Default for AssistantConfig { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FeatureFlags.ts" + export_to = "../../packages/ui/src/types/generated/FeatureFlags.ts" )] #[serde(default)] pub struct FeatureFlags { @@ -82,7 +82,7 @@ impl Default for FeatureFlags { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/LauncherConfig.ts" + export_to = "../../packages/ui/src/types/generated/LauncherConfig.ts" )] #[serde(default)] pub struct LauncherConfig { diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs index 0a3590e..cc83078 100644 --- a/src-tauri/src/core/downloader.rs +++ b/src-tauri/src/core/downloader.rs @@ -12,7 +12,7 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DownloadTask.ts" + export_to = "../../packages/ui/src/types/generated/DownloadTask.ts" )] pub struct DownloadTask { pub url: String, @@ -27,7 +27,7 @@ pub struct DownloadTask { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DownloadMetadata.ts" + export_to = "../../packages/ui/src/types/generated/DownloadMetadata.ts" )] pub struct DownloadMetadata { pub url: String, @@ -43,7 +43,7 @@ pub struct DownloadMetadata { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DownloadSegment.ts" + export_to = "../../packages/ui/src/types/generated/DownloadSegment.ts" )] pub struct DownloadSegment { pub start: u64, @@ -56,7 +56,7 @@ pub struct DownloadSegment { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaDownloadProgress.ts" + export_to = "../../packages/ui/src/types/generated/JavaDownloadProgress.ts" )] pub struct JavaDownloadProgress { pub file_name: String, @@ -72,7 +72,7 @@ pub struct JavaDownloadProgress { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/PendingJavaDownload.ts" + export_to = "../../packages/ui/src/types/generated/PendingJavaDownload.ts" )] pub struct PendingJavaDownload { pub major_version: u32, @@ -89,7 +89,7 @@ pub struct PendingJavaDownload { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DownloadQueue.ts" + export_to = "../../packages/ui/src/types/generated/DownloadQueue.ts" )] pub struct DownloadQueue { pub pending_downloads: Vec, @@ -447,7 +447,7 @@ fn create_new_metadata( #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/ProgressEvent.ts" + export_to = "../../packages/ui/src/types/generated/ProgressEvent.ts" )] pub struct ProgressEvent { pub file: String, diff --git a/src-tauri/src/core/fabric.rs b/src-tauri/src/core/fabric.rs index feadd6b..375786c 100644 --- a/src-tauri/src/core/fabric.rs +++ b/src-tauri/src/core/fabric.rs @@ -16,7 +16,7 @@ const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricLoaderVersion.ts" + export_to = "../../packages/ui/src/types/generated/FabricLoaderVersion.ts" )] pub struct FabricLoaderVersion { pub separator: String, @@ -30,7 +30,7 @@ pub struct FabricLoaderVersion { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricIntermediaryVersion.ts" + export_to = "../../packages/ui/src/types/generated/FabricIntermediaryVersion.ts" )] pub struct FabricIntermediaryVersion { pub maven: String, @@ -42,7 +42,7 @@ pub struct FabricIntermediaryVersion { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricLoaderEntry.ts" + export_to = "../../packages/ui/src/types/generated/FabricLoaderEntry.ts" )] pub struct FabricLoaderEntry { pub loader: FabricLoaderVersion, @@ -55,7 +55,7 @@ pub struct FabricLoaderEntry { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricLauncherMeta.ts" + export_to = "../../packages/ui/src/types/generated/FabricLauncherMeta.ts" )] pub struct FabricLauncherMeta { pub version: i32, @@ -68,7 +68,7 @@ pub struct FabricLauncherMeta { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricLibraries.ts" + export_to = "../../packages/ui/src/types/generated/FabricLibraries.ts" )] pub struct FabricLibraries { pub client: Vec, @@ -80,7 +80,7 @@ pub struct FabricLibraries { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricLibrary.ts" + export_to = "../../packages/ui/src/types/generated/FabricLibrary.ts" )] pub struct FabricLibrary { pub name: String, @@ -92,7 +92,7 @@ pub struct FabricLibrary { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricMainClass.ts" + export_to = "../../packages/ui/src/types/generated/FabricMainClass.ts" )] #[serde(untagged)] pub enum FabricMainClass { @@ -121,7 +121,7 @@ impl FabricMainClass { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/FabricGameVersion.ts" + export_to = "../../packages/ui/src/types/generated/FabricGameVersion.ts" )] pub struct FabricGameVersion { pub version: String, @@ -132,7 +132,7 @@ pub struct FabricGameVersion { #[derive(Debug, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/InstalledFabricVersion.ts" + export_to = "../../packages/ui/src/types/generated/InstalledFabricVersion.ts" )] pub struct InstalledFabricVersion { pub id: String, diff --git a/src-tauri/src/core/forge.rs b/src-tauri/src/core/forge.rs index a256add..2ef8d75 100644 --- a/src-tauri/src/core/forge.rs +++ b/src-tauri/src/core/forge.rs @@ -23,7 +23,7 @@ const FORGE_FILES_URL: &str = "https://files.minecraftforge.net/"; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/ForgeVersion.ts" + export_to = "../../packages/ui/src/types/generated/ForgeVersion.ts" )] pub struct ForgeVersion { pub version: String, @@ -44,7 +44,7 @@ struct ForgePromotions { #[derive(Debug, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/InstalledForgeVersion.ts" + export_to = "../../packages/ui/src/types/generated/InstalledForgeVersion.ts" )] pub struct InstalledForgeVersion { pub id: String, diff --git a/src-tauri/src/core/game_version.rs b/src-tauri/src/core/game_version.rs index 52c7da3..0f8f939 100644 --- a/src-tauri/src/core/game_version.rs +++ b/src-tauri/src/core/game_version.rs @@ -6,7 +6,7 @@ use ts_rs::TS; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/GameVersion.ts" + export_to = "../../packages/ui/src/types/generated/GameVersion.ts" )] pub struct GameVersion { pub id: String, @@ -34,7 +34,10 @@ pub struct GameVersion { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Downloads.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/Downloads.ts" +)] pub struct Downloads { pub client: DownloadArtifact, pub server: Option, @@ -43,7 +46,7 @@ pub struct Downloads { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/DownloadArtifact.ts" + export_to = "../../packages/ui/src/types/generated/DownloadArtifact.ts" )] pub struct DownloadArtifact { pub sha1: Option, @@ -53,7 +56,10 @@ pub struct DownloadArtifact { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/AssetIndex.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/AssetIndex.ts" +)] pub struct AssetIndex { pub id: String, pub sha1: String, @@ -64,7 +70,7 @@ pub struct AssetIndex { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Library.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/Library.ts")] pub struct Library { pub downloads: Option, pub name: String, @@ -76,7 +82,7 @@ pub struct Library { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Rule.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/Rule.ts")] pub struct Rule { pub action: String, // "allow" or "disallow" pub os: Option, @@ -85,7 +91,7 @@ pub struct Rule { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/OsRule.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/OsRule.ts")] pub struct OsRule { pub name: Option, // "linux", "osx", "windows" pub version: Option, // Regex @@ -95,7 +101,7 @@ pub struct OsRule { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/LibraryDownloads.ts" + export_to = "../../packages/ui/src/types/generated/LibraryDownloads.ts" )] pub struct LibraryDownloads { pub artifact: Option, @@ -104,7 +110,10 @@ pub struct LibraryDownloads { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Arguments.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/Arguments.ts" +)] pub struct Arguments { #[ts(type = "Record")] pub game: Option, @@ -115,7 +124,7 @@ pub struct Arguments { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaVersion.ts" + export_to = "../../packages/ui/src/types/generated/JavaVersion.ts" )] pub struct JavaVersion { pub component: String, diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index a281968..43d4719 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -15,7 +15,10 @@ use ts_rs::TS; /// Represents a game instance/profile #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Instance.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/Instance.ts" +)] pub struct Instance { pub id: String, // 唯一标识符(UUID) pub name: String, // 显示名称 @@ -36,7 +39,7 @@ pub struct Instance { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/MemoryOverride.ts" + export_to = "../../packages/ui/src/types/generated/MemoryOverride.ts" )] pub struct MemoryOverride { pub min: u32, // MB @@ -47,7 +50,7 @@ pub struct MemoryOverride { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/InstanceConfig.ts" + export_to = "../../packages/ui/src/types/generated/InstanceConfig.ts" )] pub struct InstanceConfig { pub instances: Vec, diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs index 245c496..cde5879 100644 --- a/src-tauri/src/core/java.rs +++ b/src-tauri/src/core/java.rs @@ -29,7 +29,7 @@ fn strip_unc_prefix(path: PathBuf) -> PathBuf { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaInstallation.ts" + export_to = "../../packages/ui/src/types/generated/JavaInstallation.ts" )] pub struct JavaInstallation { pub path: String, @@ -42,7 +42,7 @@ pub struct JavaInstallation { #[ts( export, rename_all = "lowercase", - export_to = "../packages/ui/src/types/generated/ImageType.ts" + export_to = "../../packages/ui/src/types/generated/ImageType.ts" )] #[serde(rename_all = "lowercase")] pub enum ImageType { @@ -69,7 +69,7 @@ impl std::fmt::Display for ImageType { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaReleaseInfo.ts" + export_to = "../../packages/ui/src/types/generated/JavaReleaseInfo.ts" )] pub struct JavaReleaseInfo { pub major_version: u32, @@ -89,7 +89,7 @@ pub struct JavaReleaseInfo { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaCatalog.ts" + export_to = "../../packages/ui/src/types/generated/JavaCatalog.ts" )] pub struct JavaCatalog { pub releases: Vec, @@ -149,7 +149,7 @@ pub struct AvailableReleases { #[derive(Debug, Clone, Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/JavaDownloadInfo.ts" + export_to = "../../packages/ui/src/types/generated/JavaDownloadInfo.ts" )] pub struct JavaDownloadInfo { pub version: String, diff --git a/src-tauri/src/core/manifest.rs b/src-tauri/src/core/manifest.rs index 5382259..656b1c8 100644 --- a/src-tauri/src/core/manifest.rs +++ b/src-tauri/src/core/manifest.rs @@ -8,7 +8,7 @@ use ts_rs::TS; #[derive(Debug, Deserialize, Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/VersionManifest.ts" + export_to = "../../packages/ui/src/types/generated/VersionManifest.ts" )] pub struct VersionManifest { pub latest: Latest, @@ -16,14 +16,14 @@ pub struct VersionManifest { } #[derive(Debug, Deserialize, Serialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Latest.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/Latest.ts")] pub struct Latest { pub release: String, pub snapshot: String, } #[derive(Debug, Deserialize, Serialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/Version.ts")] +#[ts(export, export_to = "../../packages/ui/src/types/generated/Version.ts")] pub struct Version { pub id: String, #[serde(rename = "type")] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e9a24fb..dfd2535 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1753,7 +1753,7 @@ async fn get_version_java_version( #[derive(serde::Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/VersionMetadata.ts" + export_to = "../../packages/ui/src/types/generated/VersionMetadata.ts" )] struct VersionMetadata { id: String, @@ -1907,7 +1907,7 @@ async fn get_version_metadata( #[derive(serde::Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/InstalledVersion.ts" + export_to = "../../packages/ui/src/types/generated/InstalledVersion.ts" )] struct InstalledVersion { id: String, @@ -2140,7 +2140,7 @@ async fn install_forge( #[derive(serde::Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/GithubRelease.ts" + export_to = "../../packages/ui/src/types/generated/GithubRelease.ts" )] struct GithubRelease { tag_name: String, @@ -2190,7 +2190,7 @@ async fn get_github_releases() -> Result, String> { #[derive(Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/PastebinResponse.ts" + export_to = "../../packages/ui/src/types/generated/PastebinResponse.ts" )] struct PastebinResponse { url: String, @@ -2402,7 +2402,7 @@ async fn assistant_chat_stream( #[derive(Serialize, TS)] #[ts( export, - export_to = "../packages/ui/src/types/generated/MigrationResult.ts" + export_to = "../../packages/ui/src/types/generated/MigrationResult.ts" )] struct MigrationResult { moved_files: usize, @@ -2453,7 +2453,10 @@ async fn migrate_shared_caches( /// File information for instance file browser #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../packages/ui/src/types/generated/FileInfo.ts")] +#[ts( + export, + export_to = "../../packages/ui/src/types/generated/FileInfo.ts" +)] struct FileInfo { name: String, path: String, From 2e99bc54df12873e8b4b9f49534171e30227e461 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 14:40:30 +0800 Subject: [PATCH 07/36] fix(ci): Restrict CI workflow to main branch and remove musl build configurations --- .github/workflows/semifold-ci.yaml | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/.github/workflows/semifold-ci.yaml b/.github/workflows/semifold-ci.yaml index b820a78..7938a9d 100644 --- a/.github/workflows/semifold-ci.yaml +++ b/.github/workflows/semifold-ci.yaml @@ -1,7 +1,7 @@ name: Semifold CI on: push: - branches: [main, dev] + branches: [main] env: CARGO_TERM_COLOR: always @@ -24,11 +24,10 @@ jobs: name: "Linux x86_64 (GNU)" target: "x86_64-unknown-linux-gnu" args: "--target x86_64-unknown-linux-gnu" - - platform: "ubuntu-latest" - name: "Linux x86_64 (Musl)" - target: "x86_64-unknown-linux-musl" - args: "--target x86_64-unknown-linux-musl" - use-cross: true + # - platform: "ubuntu-latest" + # name: "Linux x86_64 (Musl)" + # target: "x86_64-unknown-linux-musl" + # args: "--target x86_64-unknown-linux-musl" - platform: "ubuntu-24.04-arm" name: "Linux arm64" target: "aarch64-unknown-linux-gnu" @@ -47,11 +46,10 @@ jobs: name: "Windows x86_64 (MSVC)" target: "x86_64-pc-windows-msvc" args: "--target x86_64-pc-windows-msvc --bundles nsis" - - platform: "windows-latest" - name: "Windows x86_64 (GNU)" - target: "x86_64-pc-windows-gnu" - args: "--target x86_64-pc-windows-gnu --bundles nsis" - rustflags: "-C link-arg=-lws2_32" + # - platform: "windows-latest" + # name: "Windows x86_64 (GNU)" + # target: "x86_64-pc-windows-gnu" + # args: "--target x86_64-pc-windows-gnu --bundles nsis" - platform: "windows-11-arm" name: "Windows arm64" target: "aarch64-pc-windows-msvc" @@ -85,10 +83,6 @@ jobs: with: targets: ${{ matrix.target }} - - name: Install cross (for musl builds) - if: matrix.use-cross == true - run: cargo install cross - - name: Rust Cache uses: swatinem/rust-cache@v2 with: @@ -110,11 +104,8 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUSTFLAGS: ${{ matrix.rustflags || '' }} - CARGO_BUILD_TARGET: ${{ matrix.target }} with: args: ${{ matrix.args }} - command: ${{ matrix.use-cross && 'cross' || 'cargo' }} - name: Fix AppImage for Wayland (Linux) if: startsWith(matrix.platform, 'ubuntu') && !startsWith(matrix.platform, 'macos') && !startsWith(matrix.platform, 'windows') From 5d4c4450a6c4165f172f56a8cd504372a51e937a Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 14:47:54 +0800 Subject: [PATCH 08/36] fix(deps): Update ts-rs dependency to include serde-compat feature --- src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f6b992e..08537ab 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,7 +33,7 @@ tauri-plugin-fs = "2.4.5" bytes = "1.11.0" chrono = "0.4" regex = "1.12.2" -ts-rs = "11.1.0" +ts-rs = { version = "11.1.0", features = ["serde-compat"] } [build-dependencies] tauri-build = { version = "2.0", features = [] } From 3688279b7ac5319433aa3dc8e456be13902c9fc5 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 14:49:39 +0800 Subject: [PATCH 09/36] fix(auth): Remove commented-out prism's Client ID from auth.rs --- src-tauri/src/core/auth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index 7fa8cd0..b8158eb 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -88,7 +88,6 @@ pub fn generate_offline_uuid(username: &str) -> String { } const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; -// const CLIENT_ID: &str = "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb"; // ATLauncher's Client ID const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; #[derive(Debug, Serialize, Deserialize, TS)] From cfad1f27e2830cee28de480de58fff91d05b9cf8 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 14:52:05 +0800 Subject: [PATCH 10/36] fix: Update ts_rs import to include Serialize --- src-tauri/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index dfd2535..985b3f2 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,7 @@ use std::sync::Mutex; use tauri::{Emitter, Manager, State, Window}; // Added Emitter use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command; -use ts_rs::TS; // Added Serialize +use ts_rs::TS; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; From 2572c65ef7a3dcfa6f44458914ab0614dfd82e7a Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 21 Jan 2026 14:53:27 +0800 Subject: [PATCH 11/36] fix(fabric): Add untagged attribute to FabricMainClass enum export --- src-tauri/src/core/fabric.rs | 3 ++- src-tauri/src/main.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/core/fabric.rs b/src-tauri/src/core/fabric.rs index 375786c..15ea6f3 100644 --- a/src-tauri/src/core/fabric.rs +++ b/src-tauri/src/core/fabric.rs @@ -92,7 +92,8 @@ pub struct FabricLibrary { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricMainClass.ts" + export_to = "../../packages/ui/src/types/generated/FabricMainClass.ts", + untagged )] #[serde(untagged)] pub enum FabricMainClass { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 985b3f2..cee7d45 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::process::Stdio; use std::sync::Mutex; -use tauri::{Emitter, Manager, State, Window}; // Added Emitter +use tauri::{Emitter, Manager, State, Window}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command; use ts_rs::TS; From cef90795b844a8c5a9cbc4f037a0b11c4d2918fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= Date: Wed, 21 Jan 2026 20:21:32 +0800 Subject: [PATCH 12/36] feat(ts-bindings): fix ts bindings naming convention --- src-tauri/src/core/assistant.rs | 11 +++++++---- src-tauri/src/core/auth.rs | 27 ++++++--------------------- src-tauri/src/core/config.rs | 6 +++--- src-tauri/src/core/downloader.rs | 14 +++++++------- src-tauri/src/core/fabric.rs | 18 +++++++++--------- src-tauri/src/core/forge.rs | 4 ++-- src-tauri/src/core/game_version.rs | 29 +++++++++++++++++++---------- src-tauri/src/core/instance.rs | 6 +++--- src-tauri/src/core/java.rs | 22 +++++----------------- src-tauri/src/core/manifest.rs | 12 +++++++++--- src-tauri/src/main.rs | 30 ++++++------------------------ 11 files changed, 76 insertions(+), 103 deletions(-) diff --git a/src-tauri/src/core/assistant.rs b/src-tauri/src/core/assistant.rs index 7adf713..32d2925 100644 --- a/src-tauri/src/core/assistant.rs +++ b/src-tauri/src/core/assistant.rs @@ -7,7 +7,10 @@ use tauri::{Emitter, Window}; use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/Message.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" +)] pub struct Message { pub role: String, pub content: String, @@ -56,7 +59,7 @@ pub struct OllamaTagsResponse { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/ModelInfo.ts" + export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" )] pub struct ModelInfo { pub id: String, @@ -111,7 +114,7 @@ pub struct OpenAIModelsResponse { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/GenerationStats.ts" + export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" )] pub struct GenerationStats { pub total_duration: u64, @@ -125,7 +128,7 @@ pub struct GenerationStats { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/StreamChunk.ts" + export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" )] pub struct StreamChunk { pub content: String, diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index b8158eb..478d549 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -17,7 +17,7 @@ fn get_client() -> reqwest::Client { #[ts( export, tag = "type", - export_to = "../../packages/ui/src/types/generated/Account.ts" + export_to = "../../packages/ui-new/src/types/bindings/auth.ts" )] pub enum Account { Offline(OfflineAccount), @@ -48,20 +48,14 @@ impl Account { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/OfflineAccount.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct OfflineAccount { pub username: String, pub uuid: String, } #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/MicrosoftAccount.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct MicrosoftAccount { pub username: String, pub uuid: String, @@ -91,10 +85,7 @@ const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; #[derive(Debug, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/DeviceCodeResponse.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct DeviceCodeResponse { pub user_code: String, pub device_code: String, @@ -105,10 +96,7 @@ pub struct DeviceCodeResponse { } #[derive(Debug, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/TokenResponse.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct TokenResponse { pub access_token: String, pub refresh_token: Option, @@ -231,10 +219,7 @@ pub struct MinecraftAuthResponse { } #[derive(Debug, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/MinecraftProfile.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct MinecraftProfile { pub id: String, pub name: String, diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index 2c72928..e22e56a 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -8,7 +8,7 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/AssistantConfig.ts" + export_to = "../../packages/ui-new/src/types/bindings/config.ts" )] #[serde(default)] pub struct AssistantConfig { @@ -51,7 +51,7 @@ impl Default for AssistantConfig { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FeatureFlags.ts" + export_to = "../../packages/ui-new/src/types/bindings/config.ts" )] #[serde(default)] pub struct FeatureFlags { @@ -82,7 +82,7 @@ impl Default for FeatureFlags { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/LauncherConfig.ts" + export_to = "../../packages/ui-new/src/types/bindings/config.ts" )] #[serde(default)] pub struct LauncherConfig { diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs index cc83078..99a641c 100644 --- a/src-tauri/src/core/downloader.rs +++ b/src-tauri/src/core/downloader.rs @@ -12,7 +12,7 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/DownloadTask.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct DownloadTask { pub url: String, @@ -27,7 +27,7 @@ pub struct DownloadTask { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/DownloadMetadata.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct DownloadMetadata { pub url: String, @@ -43,7 +43,7 @@ pub struct DownloadMetadata { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/DownloadSegment.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct DownloadSegment { pub start: u64, @@ -56,7 +56,7 @@ pub struct DownloadSegment { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/JavaDownloadProgress.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct JavaDownloadProgress { pub file_name: String, @@ -72,7 +72,7 @@ pub struct JavaDownloadProgress { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/PendingJavaDownload.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct PendingJavaDownload { pub major_version: u32, @@ -89,7 +89,7 @@ pub struct PendingJavaDownload { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/DownloadQueue.ts" + export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" )] pub struct DownloadQueue { pub pending_downloads: Vec, @@ -447,7 +447,7 @@ fn create_new_metadata( #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/ProgressEvent.ts" + export_to = "../../packages/ui/src/types/generated/downloader.ts" )] pub struct ProgressEvent { pub file: String, diff --git a/src-tauri/src/core/fabric.rs b/src-tauri/src/core/fabric.rs index 15ea6f3..4e63bce 100644 --- a/src-tauri/src/core/fabric.rs +++ b/src-tauri/src/core/fabric.rs @@ -16,7 +16,7 @@ const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricLoaderVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricLoaderVersion { pub separator: String, @@ -30,7 +30,7 @@ pub struct FabricLoaderVersion { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricIntermediaryVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricIntermediaryVersion { pub maven: String, @@ -42,7 +42,7 @@ pub struct FabricIntermediaryVersion { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricLoaderEntry.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricLoaderEntry { pub loader: FabricLoaderVersion, @@ -55,7 +55,7 @@ pub struct FabricLoaderEntry { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricLauncherMeta.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricLauncherMeta { pub version: i32, @@ -68,7 +68,7 @@ pub struct FabricLauncherMeta { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricLibraries.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricLibraries { pub client: Vec, @@ -80,7 +80,7 @@ pub struct FabricLibraries { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricLibrary.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricLibrary { pub name: String, @@ -92,7 +92,7 @@ pub struct FabricLibrary { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricMainClass.ts", + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts", untagged )] #[serde(untagged)] @@ -122,7 +122,7 @@ impl FabricMainClass { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/FabricGameVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct FabricGameVersion { pub version: String, @@ -133,7 +133,7 @@ pub struct FabricGameVersion { #[derive(Debug, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/InstalledFabricVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" )] pub struct InstalledFabricVersion { pub id: String, diff --git a/src-tauri/src/core/forge.rs b/src-tauri/src/core/forge.rs index 2ef8d75..9f0215f 100644 --- a/src-tauri/src/core/forge.rs +++ b/src-tauri/src/core/forge.rs @@ -23,7 +23,7 @@ const FORGE_FILES_URL: &str = "https://files.minecraftforge.net/"; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/ForgeVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/forge.ts" )] pub struct ForgeVersion { pub version: String, @@ -44,7 +44,7 @@ struct ForgePromotions { #[derive(Debug, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/InstalledForgeVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/forge.ts" )] pub struct InstalledForgeVersion { pub id: String, diff --git a/src-tauri/src/core/game_version.rs b/src-tauri/src/core/game_version.rs index 0f8f939..7df631a 100644 --- a/src-tauri/src/core/game_version.rs +++ b/src-tauri/src/core/game_version.rs @@ -6,7 +6,7 @@ use ts_rs::TS; #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/GameVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct GameVersion { pub id: String, @@ -36,7 +36,7 @@ pub struct GameVersion { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/Downloads.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct Downloads { pub client: DownloadArtifact, @@ -46,7 +46,7 @@ pub struct Downloads { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/DownloadArtifact.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct DownloadArtifact { pub sha1: Option, @@ -58,7 +58,7 @@ pub struct DownloadArtifact { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/AssetIndex.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct AssetIndex { pub id: String, @@ -70,7 +70,10 @@ pub struct AssetIndex { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/Library.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" +)] pub struct Library { pub downloads: Option, pub name: String, @@ -82,7 +85,10 @@ pub struct Library { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/Rule.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" +)] pub struct Rule { pub action: String, // "allow" or "disallow" pub os: Option, @@ -91,7 +97,10 @@ pub struct Rule { } #[derive(Debug, Deserialize, Serialize, Clone, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/OsRule.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" +)] pub struct OsRule { pub name: Option, // "linux", "osx", "windows" pub version: Option, // Regex @@ -101,7 +110,7 @@ pub struct OsRule { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/LibraryDownloads.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct LibraryDownloads { pub artifact: Option, @@ -112,7 +121,7 @@ pub struct LibraryDownloads { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/Arguments.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct Arguments { #[ts(type = "Record")] @@ -124,7 +133,7 @@ pub struct Arguments { #[derive(Debug, Deserialize, Serialize, Clone, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/JavaVersion.ts" + export_to = "../../packages/ui-new/src/types/bindings/game_version.ts" )] pub struct JavaVersion { pub component: String, diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 43d4719..131d696 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -17,7 +17,7 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/Instance.ts" + export_to = "../../packages/ui-new/src/types/bindings/instance.ts" )] pub struct Instance { pub id: String, // 唯一标识符(UUID) @@ -39,7 +39,7 @@ pub struct Instance { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/MemoryOverride.ts" + export_to = "../../packages/ui-new/src/types/bindings/instance.ts" )] pub struct MemoryOverride { pub min: u32, // MB @@ -50,7 +50,7 @@ pub struct MemoryOverride { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/InstanceConfig.ts" + export_to = "../../packages/ui-new/src/types/bindings/instance.ts" )] pub struct InstanceConfig { pub instances: Vec, diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs index cde5879..ddcbdbd 100644 --- a/src-tauri/src/core/java.rs +++ b/src-tauri/src/core/java.rs @@ -27,10 +27,7 @@ fn strip_unc_prefix(path: PathBuf) -> PathBuf { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/JavaInstallation.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaInstallation { pub path: String, pub version: String, @@ -42,7 +39,7 @@ pub struct JavaInstallation { #[ts( export, rename_all = "lowercase", - export_to = "../../packages/ui/src/types/generated/ImageType.ts" + export_to = "../../packages/ui-new/src/types/bindings/java.ts" )] #[serde(rename_all = "lowercase")] pub enum ImageType { @@ -67,10 +64,7 @@ impl std::fmt::Display for ImageType { /// Java release information for UI display #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/JavaReleaseInfo.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaReleaseInfo { pub major_version: u32, pub image_type: String, @@ -87,10 +81,7 @@ pub struct JavaReleaseInfo { /// Java catalog containing all available versions #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/JavaCatalog.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaCatalog { pub releases: Vec, pub available_major_versions: Vec, @@ -147,10 +138,7 @@ pub struct AvailableReleases { /// Java download information from Adoptium #[derive(Debug, Clone, Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/JavaDownloadInfo.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaDownloadInfo { pub version: String, pub release_name: String, diff --git a/src-tauri/src/core/manifest.rs b/src-tauri/src/core/manifest.rs index 656b1c8..ffa1c77 100644 --- a/src-tauri/src/core/manifest.rs +++ b/src-tauri/src/core/manifest.rs @@ -8,7 +8,7 @@ use ts_rs::TS; #[derive(Debug, Deserialize, Serialize, TS)] #[ts( export, - export_to = "../../packages/ui/src/types/generated/VersionManifest.ts" + export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" )] pub struct VersionManifest { pub latest: Latest, @@ -16,14 +16,20 @@ pub struct VersionManifest { } #[derive(Debug, Deserialize, Serialize, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/Latest.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" +)] pub struct Latest { pub release: String, pub snapshot: String, } #[derive(Debug, Deserialize, Serialize, TS)] -#[ts(export, export_to = "../../packages/ui/src/types/generated/Version.ts")] +#[ts( + export, + export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" +)] pub struct Version { pub id: String, #[serde(rename = "type")] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cee7d45..b7283ba 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1751,10 +1751,7 @@ async fn get_version_java_version( /// Version metadata for display in the UI #[derive(serde::Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/VersionMetadata.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct VersionMetadata { id: String, #[serde(rename = "javaVersion")] @@ -1905,10 +1902,7 @@ async fn get_version_metadata( /// Installed version info #[derive(serde::Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/InstalledVersion.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct InstalledVersion { id: String, #[serde(rename = "type")] @@ -2138,10 +2132,7 @@ async fn install_forge( } #[derive(serde::Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/GithubRelease.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct GithubRelease { tag_name: String, name: String, @@ -2188,10 +2179,7 @@ async fn get_github_releases() -> Result, String> { } #[derive(Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/PastebinResponse.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct PastebinResponse { url: String, } @@ -2400,10 +2388,7 @@ async fn assistant_chat_stream( /// Migrate instance caches to shared global caches #[derive(Serialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/MigrationResult.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct MigrationResult { moved_files: usize, hardlinks: usize, @@ -2453,10 +2438,7 @@ async fn migrate_shared_caches( /// File information for instance file browser #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts( - export, - export_to = "../../packages/ui/src/types/generated/FileInfo.ts" -)] +#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct FileInfo { name: String, path: String, From ef560813c68c113325d8d84ff13cd419eb6583df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= Date: Fri, 23 Jan 2026 20:48:13 +0800 Subject: [PATCH 13/36] feat(ts-bindings): fix ts bindings naming convention --- .changes/ts-bindings.md | 5 +++++ biome.json | 5 +++++ src-tauri/Cargo.toml | 1 + src-tauri/src/core/assistant.rs | 4 ++++ src-tauri/src/core/auth.rs | 6 ++++++ src-tauri/src/core/config.rs | 3 +++ src-tauri/src/core/downloader.rs | 7 +++++++ src-tauri/src/core/fabric.rs | 9 +++++++++ src-tauri/src/core/forge.rs | 2 ++ src-tauri/src/core/instance.rs | 3 +++ src-tauri/src/core/java.rs | 4 ++++ src-tauri/src/core/manifest.rs | 3 +++ src-tauri/src/main.rs | 6 ++++++ 13 files changed, 58 insertions(+) create mode 100644 .changes/ts-bindings.md diff --git a/.changes/ts-bindings.md b/.changes/ts-bindings.md new file mode 100644 index 0000000..01be1b1 --- /dev/null +++ b/.changes/ts-bindings.md @@ -0,0 +1,5 @@ +--- +dropout: "patch:feat" +--- + +Add `ts-rs` for generating TypeScript bindings. diff --git a/biome.json b/biome.json index eb43210..79b8928 100644 --- a/biome.json +++ b/biome.json @@ -31,5 +31,10 @@ "organizeImports": "on" } } + }, + "css": { + "parser": { + "tailwindDirectives": true + } } } diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 08537ab..cf92627 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/HydroRoll-Team/DropOut" publish = false [dependencies] +dropout-macros = { version = "0.1.0", path = "../crates/macros" } serde = { version = "1.0", features = ["derive"] } toml = "0.5" log = "0.4" diff --git a/src-tauri/src/core/assistant.rs b/src-tauri/src/core/assistant.rs index 32d2925..6e656dc 100644 --- a/src-tauri/src/core/assistant.rs +++ b/src-tauri/src/core/assistant.rs @@ -7,6 +7,7 @@ use tauri::{Emitter, Window}; use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" @@ -57,6 +58,7 @@ pub struct OllamaTagsResponse { // Simplified model info for frontend #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" @@ -112,6 +114,7 @@ pub struct OpenAIModelsResponse { // Streaming response structures #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" @@ -126,6 +129,7 @@ pub struct GenerationStats { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/assistant.ts" diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index 478d549..0e873e3 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -14,6 +14,7 @@ fn get_client() -> reqwest::Client { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(tag = "type")] +#[serde(rename_all = "camelCase")] #[ts( export, tag = "type", @@ -48,6 +49,7 @@ impl Account { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct OfflineAccount { pub username: String, @@ -55,6 +57,7 @@ pub struct OfflineAccount { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct MicrosoftAccount { pub username: String, @@ -85,6 +88,7 @@ const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; #[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct DeviceCodeResponse { pub user_code: String, @@ -96,6 +100,7 @@ pub struct DeviceCodeResponse { } #[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct TokenResponse { pub access_token: String, @@ -219,6 +224,7 @@ pub struct MinecraftAuthResponse { } #[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/auth.ts")] pub struct MinecraftProfile { pub id: String, diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index e22e56a..0d0e8ff 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -6,6 +6,7 @@ use tauri::{AppHandle, Manager}; use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/config.ts" @@ -49,6 +50,7 @@ impl Default for AssistantConfig { /// Feature-gated arguments configuration #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/config.ts" @@ -80,6 +82,7 @@ impl Default for FeatureFlags { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/config.ts" diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs index 99a641c..d4fc782 100644 --- a/src-tauri/src/core/downloader.rs +++ b/src-tauri/src/core/downloader.rs @@ -10,6 +10,7 @@ use tokio::sync::Semaphore; use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -25,6 +26,7 @@ pub struct DownloadTask { /// Metadata for resumable downloads stored in .part.meta file #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -41,6 +43,7 @@ pub struct DownloadMetadata { /// A download segment for multi-segment parallel downloading #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -54,6 +57,7 @@ pub struct DownloadSegment { /// Progress event for Java download #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -70,6 +74,7 @@ pub struct JavaDownloadProgress { /// Pending download task for queue persistence #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -87,6 +92,7 @@ pub struct PendingJavaDownload { /// Download queue for persistence #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/downloader.ts" @@ -445,6 +451,7 @@ fn create_new_metadata( } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui/src/types/generated/downloader.ts" diff --git a/src-tauri/src/core/fabric.rs b/src-tauri/src/core/fabric.rs index 4e63bce..7385850 100644 --- a/src-tauri/src/core/fabric.rs +++ b/src-tauri/src/core/fabric.rs @@ -14,6 +14,7 @@ const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; /// Represents a Fabric loader version from the Meta API. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -28,6 +29,7 @@ pub struct FabricLoaderVersion { /// Represents a Fabric intermediary mapping version. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -40,6 +42,7 @@ pub struct FabricIntermediaryVersion { /// Represents a combined loader + intermediary version entry. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -53,6 +56,7 @@ pub struct FabricLoaderEntry { /// Launcher metadata from Fabric Meta API. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -66,6 +70,7 @@ pub struct FabricLauncherMeta { /// Libraries required by Fabric loader. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -78,6 +83,7 @@ pub struct FabricLibraries { /// A single Fabric library dependency. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -90,6 +96,7 @@ pub struct FabricLibrary { /// Main class configuration for Fabric. /// Can be either a struct with client/server fields or a simple string. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts", @@ -120,6 +127,7 @@ impl FabricMainClass { /// Represents a Minecraft version supported by Fabric. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" @@ -131,6 +139,7 @@ pub struct FabricGameVersion { /// Information about an installed Fabric version. #[derive(Debug, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/fabric.ts" diff --git a/src-tauri/src/core/forge.rs b/src-tauri/src/core/forge.rs index 9f0215f..1d4ae1d 100644 --- a/src-tauri/src/core/forge.rs +++ b/src-tauri/src/core/forge.rs @@ -21,6 +21,7 @@ const FORGE_FILES_URL: &str = "https://files.minecraftforge.net/"; /// Represents a Forge version entry. #[derive(Debug, Deserialize, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/forge.ts" @@ -42,6 +43,7 @@ struct ForgePromotions { /// Information about an installed Forge version. #[derive(Debug, Serialize, Clone, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/forge.ts" diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 131d696..e7746b2 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -15,6 +15,7 @@ use ts_rs::TS; /// Represents a game instance/profile #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/instance.ts" @@ -37,6 +38,7 @@ pub struct Instance { /// Memory settings override for an instance #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/instance.ts" @@ -48,6 +50,7 @@ pub struct MemoryOverride { /// Configuration for all instances #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/instance.ts" diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs index ddcbdbd..12a6d56 100644 --- a/src-tauri/src/core/java.rs +++ b/src-tauri/src/core/java.rs @@ -27,6 +27,7 @@ fn strip_unc_prefix(path: PathBuf) -> PathBuf { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaInstallation { pub path: String, @@ -64,6 +65,7 @@ impl std::fmt::Display for ImageType { /// Java release information for UI display #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaReleaseInfo { pub major_version: u32, @@ -81,6 +83,7 @@ pub struct JavaReleaseInfo { /// Java catalog containing all available versions #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaCatalog { pub releases: Vec, @@ -138,6 +141,7 @@ pub struct AvailableReleases { /// Java download information from Adoptium #[derive(Debug, Clone, Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/java.ts")] pub struct JavaDownloadInfo { pub version: String, diff --git a/src-tauri/src/core/manifest.rs b/src-tauri/src/core/manifest.rs index ffa1c77..9e4cb4e 100644 --- a/src-tauri/src/core/manifest.rs +++ b/src-tauri/src/core/manifest.rs @@ -6,6 +6,7 @@ use crate::core::game_version::GameVersion; use ts_rs::TS; #[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" @@ -16,6 +17,7 @@ pub struct VersionManifest { } #[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" @@ -26,6 +28,7 @@ pub struct Latest { } #[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts( export, export_to = "../../packages/ui-new/src/types/bindings/manifest.ts" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b7283ba..45fa77b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1751,6 +1751,7 @@ async fn get_version_java_version( /// Version metadata for display in the UI #[derive(serde::Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct VersionMetadata { id: String, @@ -1902,6 +1903,7 @@ async fn get_version_metadata( /// Installed version info #[derive(serde::Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct InstalledVersion { id: String, @@ -2132,6 +2134,7 @@ async fn install_forge( } #[derive(serde::Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct GithubRelease { tag_name: String, @@ -2179,6 +2182,7 @@ async fn get_github_releases() -> Result, String> { } #[derive(Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct PastebinResponse { url: String, @@ -2388,6 +2392,7 @@ async fn assistant_chat_stream( /// Migrate instance caches to shared global caches #[derive(Serialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct MigrationResult { moved_files: usize, @@ -2438,6 +2443,7 @@ async fn migrate_shared_caches( /// File information for instance file browser #[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] #[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] struct FileInfo { name: String, From 9430bee86fbf943283eb5a6f63bd750b875ff433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= Date: Fri, 23 Jan 2026 20:51:28 +0800 Subject: [PATCH 14/36] feat(ui): add new ui project --- .pre-commit-config.yaml | 2 +- package.json | 1 + packages/ui-new/.gitignore | 24 + packages/ui-new/components.json | 22 + packages/ui-new/index.html | 13 + packages/ui-new/package.json | 50 + packages/ui-new/public/icon.svg | 50 + packages/ui-new/src/components/bottom-bar.tsx | 269 +++ .../src/components/download-monitor.tsx | 61 + .../ui-new/src/components/game-console.tsx | 290 ++++ .../components/instance-creation-modal.tsx | 566 +++++++ .../src/components/instance-editor-modal.tsx | 548 ++++++ .../ui-new/src/components/login-modal.tsx | 156 ++ .../src/components/particle-background.tsx | 63 + packages/ui-new/src/components/sidebar.tsx | 180 ++ packages/ui-new/src/components/ui/badge.tsx | 46 + packages/ui-new/src/components/ui/button.tsx | 62 + packages/ui-new/src/components/ui/card.tsx | 92 + .../ui-new/src/components/ui/checkbox.tsx | 32 + packages/ui-new/src/components/ui/dialog.tsx | 141 ++ packages/ui-new/src/components/ui/input.tsx | 21 + packages/ui-new/src/components/ui/label.tsx | 24 + .../ui-new/src/components/ui/scroll-area.tsx | 56 + packages/ui-new/src/components/ui/select.tsx | 188 +++ .../ui-new/src/components/ui/separator.tsx | 28 + packages/ui-new/src/components/ui/sonner.tsx | 38 + packages/ui-new/src/components/ui/switch.tsx | 29 + packages/ui-new/src/components/ui/tabs.tsx | 66 + .../ui-new/src/components/ui/textarea.tsx | 18 + packages/ui-new/src/index.css | 300 ++++ .../ui-new/src/lib/effects/SaturnEffect.ts | 299 ++++ packages/ui-new/src/lib/tsrs-utils.ts | 67 + packages/ui-new/src/lib/utils.ts | 6 + packages/ui-new/src/main.tsx | 48 + packages/ui-new/src/pages/assistant-view.tsx | 485 ++++++ packages/ui-new/src/pages/home-view.tsx | 382 +++++ packages/ui-new/src/pages/index.tsx | 189 +++ packages/ui-new/src/pages/instances-view.tsx | 370 ++++ packages/ui-new/src/pages/settings-view.tsx | 1158 +++++++++++++ packages/ui-new/src/pages/versions-view.tsx | 662 ++++++++ packages/ui-new/src/stores/assistant-store.ts | 201 +++ packages/ui-new/src/stores/auth-store.ts | 296 ++++ packages/ui-new/src/stores/game-store.ts | 101 ++ packages/ui-new/src/stores/instances-store.ts | 149 ++ packages/ui-new/src/stores/logs-store.ts | 200 +++ packages/ui-new/src/stores/releases-store.ts | 63 + packages/ui-new/src/stores/settings-store.ts | 568 +++++++ packages/ui-new/src/stores/ui-store.ts | 42 + .../ui-new/src/types/bindings/assistant.ts | 25 + packages/ui-new/src/types/bindings/auth.ts | 32 + packages/ui-new/src/types/bindings/config.ts | 61 + packages/ui-new/src/types/bindings/core.ts | 47 + .../ui-new/src/types/bindings/downloader.ts | 63 + packages/ui-new/src/types/bindings/fabric.ts | 74 + packages/ui-new/src/types/bindings/forge.ts | 21 + .../ui-new/src/types/bindings/game_version.ts | 89 + packages/ui-new/src/types/bindings/index.ts | 11 + .../ui-new/src/types/bindings/instance.ts | 32 + packages/ui-new/src/types/bindings/java.ts | 52 + .../ui-new/src/types/bindings/manifest.ts | 22 + packages/ui-new/tsconfig.app.json | 34 + packages/ui-new/tsconfig.json | 13 + packages/ui-new/tsconfig.node.json | 26 + packages/ui-new/vite.config.ts | 18 + pnpm-lock.yaml | 1497 ++++++++++++++++- src-tauri/tauri.conf.json | 6 +- 66 files changed, 10834 insertions(+), 11 deletions(-) create mode 100644 packages/ui-new/.gitignore create mode 100644 packages/ui-new/components.json create mode 100644 packages/ui-new/index.html create mode 100644 packages/ui-new/package.json create mode 100644 packages/ui-new/public/icon.svg create mode 100644 packages/ui-new/src/components/bottom-bar.tsx create mode 100644 packages/ui-new/src/components/download-monitor.tsx create mode 100644 packages/ui-new/src/components/game-console.tsx create mode 100644 packages/ui-new/src/components/instance-creation-modal.tsx create mode 100644 packages/ui-new/src/components/instance-editor-modal.tsx create mode 100644 packages/ui-new/src/components/login-modal.tsx create mode 100644 packages/ui-new/src/components/particle-background.tsx create mode 100644 packages/ui-new/src/components/sidebar.tsx create mode 100644 packages/ui-new/src/components/ui/badge.tsx create mode 100644 packages/ui-new/src/components/ui/button.tsx create mode 100644 packages/ui-new/src/components/ui/card.tsx create mode 100644 packages/ui-new/src/components/ui/checkbox.tsx create mode 100644 packages/ui-new/src/components/ui/dialog.tsx create mode 100644 packages/ui-new/src/components/ui/input.tsx create mode 100644 packages/ui-new/src/components/ui/label.tsx create mode 100644 packages/ui-new/src/components/ui/scroll-area.tsx create mode 100644 packages/ui-new/src/components/ui/select.tsx create mode 100644 packages/ui-new/src/components/ui/separator.tsx create mode 100644 packages/ui-new/src/components/ui/sonner.tsx create mode 100644 packages/ui-new/src/components/ui/switch.tsx create mode 100644 packages/ui-new/src/components/ui/tabs.tsx create mode 100644 packages/ui-new/src/components/ui/textarea.tsx create mode 100644 packages/ui-new/src/index.css create mode 100644 packages/ui-new/src/lib/effects/SaturnEffect.ts create mode 100644 packages/ui-new/src/lib/tsrs-utils.ts create mode 100644 packages/ui-new/src/lib/utils.ts create mode 100644 packages/ui-new/src/main.tsx create mode 100644 packages/ui-new/src/pages/assistant-view.tsx create mode 100644 packages/ui-new/src/pages/home-view.tsx create mode 100644 packages/ui-new/src/pages/index.tsx create mode 100644 packages/ui-new/src/pages/instances-view.tsx create mode 100644 packages/ui-new/src/pages/settings-view.tsx create mode 100644 packages/ui-new/src/pages/versions-view.tsx create mode 100644 packages/ui-new/src/stores/assistant-store.ts create mode 100644 packages/ui-new/src/stores/auth-store.ts create mode 100644 packages/ui-new/src/stores/game-store.ts create mode 100644 packages/ui-new/src/stores/instances-store.ts create mode 100644 packages/ui-new/src/stores/logs-store.ts create mode 100644 packages/ui-new/src/stores/releases-store.ts create mode 100644 packages/ui-new/src/stores/settings-store.ts create mode 100644 packages/ui-new/src/stores/ui-store.ts create mode 100644 packages/ui-new/src/types/bindings/assistant.ts create mode 100644 packages/ui-new/src/types/bindings/auth.ts create mode 100644 packages/ui-new/src/types/bindings/config.ts create mode 100644 packages/ui-new/src/types/bindings/core.ts create mode 100644 packages/ui-new/src/types/bindings/downloader.ts create mode 100644 packages/ui-new/src/types/bindings/fabric.ts create mode 100644 packages/ui-new/src/types/bindings/forge.ts create mode 100644 packages/ui-new/src/types/bindings/game_version.ts create mode 100644 packages/ui-new/src/types/bindings/index.ts create mode 100644 packages/ui-new/src/types/bindings/instance.ts create mode 100644 packages/ui-new/src/types/bindings/java.ts create mode 100644 packages/ui-new/src/types/bindings/manifest.ts create mode 100644 packages/ui-new/tsconfig.app.json create mode 100644 packages/ui-new/tsconfig.json create mode 100644 packages/ui-new/tsconfig.node.json create mode 100644 packages/ui-new/vite.config.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1713952..8d5e056 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: rev: v6.0.0 hooks: - id: check-json - exclude: ^packages/ui/tsconfig.*\.json$ + exclude: ^packages/ui(-new)?/tsconfig.*\.json$ - id: check-toml - id: check-yaml - id: check-case-conflict diff --git a/package.json b/package.json index 4d1093b..800b455 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "description": "Dropout, the next-generation Minecraft game launcher", "scripts": { + "generate": "cargo test export_bindings && biome check packages/ui-new/src/types/bindings --fix", "bump-tauri": "tsx scripts/bump-tauri.ts", "prepare": "prek install" }, diff --git a/packages/ui-new/.gitignore b/packages/ui-new/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/packages/ui-new/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/ui-new/components.json b/packages/ui-new/components.json new file mode 100644 index 0000000..2b0833f --- /dev/null +++ b/packages/ui-new/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/packages/ui-new/index.html b/packages/ui-new/index.html new file mode 100644 index 0000000..5191e6f --- /dev/null +++ b/packages/ui-new/index.html @@ -0,0 +1,13 @@ + + + + + + + Dropout Launcher + + +
+ + + diff --git a/packages/ui-new/package.json b/packages/ui-new/package.json new file mode 100644 index 0000000..706c12b --- /dev/null +++ b/packages/ui-new/package.json @@ -0,0 +1,50 @@ +{ + "name": "@dropout/ui-new", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "biome check .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/plugin-fs": "^2.4.5", + "@tauri-apps/plugin-shell": "^2.3.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "marked": "^17.0.1", + "next-themes": "^0.4.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router": "^7.12.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "zustand": "^5.0.10" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "globals": "^16.5.0", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "vite": "npm:rolldown-vite@^7" + } +} diff --git a/packages/ui-new/public/icon.svg b/packages/ui-new/public/icon.svg new file mode 100644 index 0000000..0baf00f --- /dev/null +++ b/packages/ui-new/public/icon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui-new/src/components/bottom-bar.tsx b/packages/ui-new/src/components/bottom-bar.tsx new file mode 100644 index 0000000..a0c2c00 --- /dev/null +++ b/packages/ui-new/src/components/bottom-bar.tsx @@ -0,0 +1,269 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; +import { Check, ChevronDown, Play, Terminal, User } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useAuthStore } from "@/stores/auth-store"; +import { useGameStore } from "@/stores/game-store"; +import { useInstancesStore } from "@/stores/instances-store"; +import { useUIStore } from "@/stores/ui-store"; + +interface InstalledVersion { + id: string; + type: string; +} + +export function BottomBar() { + const authStore = useAuthStore(); + const gameStore = useGameStore(); + const instancesStore = useInstancesStore(); + const uiStore = useUIStore(); + + const [isVersionDropdownOpen, setIsVersionDropdownOpen] = useState(false); + const [installedVersions, setInstalledVersions] = useState< + InstalledVersion[] + >([]); + const [isLoadingVersions, setIsLoadingVersions] = useState(true); + + const dropdownRef = useRef(null); + + const loadInstalledVersions = useCallback(async () => { + if (!instancesStore.activeInstanceId) { + setInstalledVersions([]); + setIsLoadingVersions(false); + return; + } + + setIsLoadingVersions(true); + try { + const versions = await invoke( + "list_installed_versions", + { instanceId: instancesStore.activeInstanceId }, + ); + + const installed = versions || []; + setInstalledVersions(installed); + + // If no version is selected but we have installed versions, select the first one + if (!gameStore.selectedVersion && installed.length > 0) { + gameStore.setSelectedVersion(installed[0].id); + } + } catch (error) { + console.error("Failed to load installed versions:", error); + } finally { + setIsLoadingVersions(false); + } + }, [ + instancesStore.activeInstanceId, + gameStore.selectedVersion, + gameStore.setSelectedVersion, + ]); + + useEffect(() => { + loadInstalledVersions(); + + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsVersionDropdownOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + // Listen for backend events that should refresh installed versions. + let unlistenDownload: UnlistenFn | null = null; + let unlistenVersionDeleted: UnlistenFn | null = null; + + (async () => { + try { + unlistenDownload = await listen("download-complete", () => { + loadInstalledVersions(); + }); + } catch (err) { + // best-effort: do not break UI if listening fails + // eslint-disable-next-line no-console + console.warn("Failed to attach download-complete listener:", err); + } + + try { + unlistenVersionDeleted = await listen("version-deleted", () => { + loadInstalledVersions(); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.warn("Failed to attach version-deleted listener:", err); + } + })(); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + try { + if (unlistenDownload) unlistenDownload(); + } catch { + // ignore + } + try { + if (unlistenVersionDeleted) unlistenVersionDeleted(); + } catch { + // ignore + } + }; + }, [loadInstalledVersions]); + + const selectVersion = (id: string) => { + if (id !== "loading" && id !== "empty") { + gameStore.setSelectedVersion(id); + setIsVersionDropdownOpen(false); + } + }; + + const handleStartGame = async () => { + await gameStore.startGame( + authStore.currentAccount, + authStore.openLoginModal, + instancesStore.activeInstanceId, + uiStore.setView, + ); + }; + + const getVersionTypeColor = (type: string) => { + switch (type) { + case "release": + return "bg-emerald-500"; + case "snapshot": + return "bg-amber-500"; + case "old_beta": + return "bg-rose-500"; + case "old_alpha": + return "bg-violet-500"; + default: + return "bg-gray-500"; + } + }; + + const versionOptions = isLoadingVersions + ? [{ id: "loading", type: "loading", label: "Loading..." }] + : installedVersions.length === 0 + ? [{ id: "empty", type: "empty", label: "No versions installed" }] + : installedVersions.map((v) => ({ + ...v, + label: `${v.id}${v.type !== "release" ? ` (${v.type})` : ""}`, + })); + + return ( +
+
+
+ {/* Left: Instance Info */} +
+
+ + Active Instance + + + {instancesStore.activeInstance?.name || "No instance selected"} + +
+ + {/* Version Selector */} +
+ + + {/* Dropdown */} + {isVersionDropdownOpen && ( +
+
+ {versionOptions.map((option) => ( + + ))} +
+
+ )} +
+
+ + {/* Right: Action Buttons */} +
+ {/* Console Toggle */} + + + {/* User Login/Info */} + + + {/* Start Game */} + +
+
+
+
+ ); +} diff --git a/packages/ui-new/src/components/download-monitor.tsx b/packages/ui-new/src/components/download-monitor.tsx new file mode 100644 index 0000000..d67e173 --- /dev/null +++ b/packages/ui-new/src/components/download-monitor.tsx @@ -0,0 +1,61 @@ +import { X } from "lucide-react"; +import { useState } from "react"; + +export function DownloadMonitor() { + const [isVisible, setIsVisible] = useState(true); + + if (!isVisible) return null; + + return ( +
+ {/* Header */} +
+
+
+ Downloads +
+ +
+ + {/* Content */} +
+
+ {/* Download Item */} +
+
+ Minecraft 1.20.4 + 65% +
+
+
+
+
+ 142 MB / 218 MB + 2.1 MB/s • 36s remaining +
+
+ + {/* Download Item */} +
+
+ Java 17 + 100% +
+
+
+
+
Completed
+
+
+
+
+ ); +} diff --git a/packages/ui-new/src/components/game-console.tsx b/packages/ui-new/src/components/game-console.tsx new file mode 100644 index 0000000..6980c8c --- /dev/null +++ b/packages/ui-new/src/components/game-console.tsx @@ -0,0 +1,290 @@ +import { Copy, Download, Filter, Search, Trash2, X } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { useLogsStore } from "@/stores/logs-store"; +import { useUIStore } from "@/stores/ui-store"; + +export function GameConsole() { + const uiStore = useUIStore(); + const logsStore = useLogsStore(); + + const [searchTerm, setSearchTerm] = useState(""); + const [selectedLevels, setSelectedLevels] = useState>( + new Set(["info", "warn", "error", "debug", "fatal"]), + ); + const [autoScroll, setAutoScroll] = useState(true); + const consoleEndRef = useRef(null); + const logsContainerRef = useRef(null); + + const levelColors: Record = { + info: "text-blue-400", + warn: "text-amber-400", + error: "text-red-400", + debug: "text-purple-400", + fatal: "text-rose-400", + }; + + const levelBgColors: Record = { + info: "bg-blue-400/10", + warn: "bg-amber-400/10", + error: "bg-red-400/10", + debug: "bg-purple-400/10", + fatal: "bg-rose-400/10", + }; + + // Filter logs based on search term and selected levels + const filteredLogs = logsStore.logs.filter((log) => { + const matchesSearch = + searchTerm === "" || + log.message.toLowerCase().includes(searchTerm.toLowerCase()) || + log.source.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesLevel = selectedLevels.has(log.level); + + return matchesSearch && matchesLevel; + }); + + // Auto-scroll to bottom when new logs arrive or autoScroll is enabled + useEffect(() => { + if (autoScroll && consoleEndRef.current && filteredLogs.length > 0) { + consoleEndRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [filteredLogs, autoScroll]); + + // Handle keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Ctrl/Cmd + K to focus search + if ((e.ctrlKey || e.metaKey) && e.key === "k") { + e.preventDefault(); + // Focus search input + const searchInput = document.querySelector( + 'input[type="text"]', + ) as HTMLInputElement; + if (searchInput) searchInput.focus(); + } + // Escape to close console + if (e.key === "Escape") { + uiStore.toggleConsole(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [uiStore.toggleConsole]); + + const toggleLevel = (level: string) => { + const newLevels = new Set(selectedLevels); + if (newLevels.has(level)) { + newLevels.delete(level); + } else { + newLevels.add(level); + } + setSelectedLevels(newLevels); + }; + + const handleCopyAll = () => { + const logsText = logsStore.exportLogs(filteredLogs); + navigator.clipboard.writeText(logsText); + }; + + const handleExport = () => { + const logsText = logsStore.exportLogs(filteredLogs); + const blob = new Blob([logsText], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `dropout_logs_${new Date().toISOString().replace(/[:.]/g, "-")}.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const handleClear = () => { + logsStore.clear(); + }; + + return ( + <> + {/* Header */} +
+
+

Game Console

+
+ Logs: + + {filteredLogs.length} + + / + + {logsStore.logs.length} + +
+
+ +
+ + {/* Toolbar */} +
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + placeholder="Search logs..." + className="w-full pl-10 pr-4 py-2 bg-[#3E3E42] border border-zinc-600 rounded text-sm text-white placeholder:text-zinc-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500" + /> + {searchTerm && ( + + )} +
+ + {/* Level Filters */} +
+ {Object.entries(levelColors).map(([level, colorClass]) => ( + + ))} +
+ + {/* Actions */} +
+ + + +
+ + {/* Auto-scroll Toggle */} +
+ +
+
+ + {/* Logs Container */} +
+ {filteredLogs.length === 0 ? ( +
+
+ +

No logs match the current filters

+
+
+ ) : ( +
+ {filteredLogs.map((log) => ( +
+
+
+ {log.level.toUpperCase()} +
+
+ {log.timestamp} +
+
+ [{log.source}] +
+
{log.message}
+
+
+ ))} +
+
+ )} +
+ + {/* Footer */} +
+
+
+ Total: + {logsStore.logs.length} + | Filtered: + {filteredLogs.length} +
+
+ + Ctrl+K + + to search +
+
+
+ Updated: + + {new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + })} + +
+
+ + ); +} diff --git a/packages/ui-new/src/components/instance-creation-modal.tsx b/packages/ui-new/src/components/instance-creation-modal.tsx new file mode 100644 index 0000000..bdc1a6f --- /dev/null +++ b/packages/ui-new/src/components/instance-creation-modal.tsx @@ -0,0 +1,566 @@ +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 { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { useGameStore } from "@/stores/game-store"; +import { useInstancesStore } from "@/stores/instances-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"; + +interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +/** + * InstanceCreationModal + * 3-step wizard: + * 1) Name + * 2) Select base Minecraft version + * 3) Optional: choose mod loader (vanilla/fabric/forge) and loader version + * + * Behavior: + * - On Create: invoke("create_instance", { name }) + * - If a base version selected: invoke("install_version", { instanceId, versionId }) + * - If Fabric selected: invoke("install_fabric", { instanceId, gameVersion, loaderVersion }) + * - If Forge selected: invoke("install_forge", { instanceId, gameVersion, forgeVersion }) + * - Reload instances via instancesStore.loadInstances() + */ +export function InstanceCreationModal({ open, onOpenChange }: Props) { + const gameStore = useGameStore(); + const instancesStore = useInstancesStore(); + + // Steps: 1 = name, 2 = version, 3 = mod loader + const [step, setStep] = useState(1); + + // Step 1 + const [instanceName, setInstanceName] = useState(""); + + // Step 2 + const [versionSearch, setVersionSearch] = useState(""); + const [versionFilter, setVersionFilter] = useState< + "all" | "release" | "snapshot" + >("release"); + const [selectedVersionUI, setSelectedVersionUI] = useState( + null, + ); + + // Step 3 + const [modLoaderType, setModLoaderType] = useState< + "vanilla" | "fabric" | "forge" + >("vanilla"); + const [fabricLoaders, setFabricLoaders] = useState([]); + const [forgeVersions, setForgeVersions] = useState([]); + const [selectedFabricLoader, setSelectedFabricLoader] = useState(""); + const [selectedForgeLoader, setSelectedForgeLoader] = useState(""); + const [loadingLoaders, setLoadingLoaders] = useState(false); + + const loadModLoaders = useCallback(async () => { + if (!selectedVersionUI) return; + setLoadingLoaders(true); + setFabricLoaders([]); + setForgeVersions([]); + try { + if (modLoaderType === "fabric") { + const loaders = await invoke( + "get_fabric_loaders_for_version", + { + gameVersion: selectedVersionUI.id, + }, + ); + setFabricLoaders(loaders || []); + if (loaders && loaders.length > 0) { + setSelectedFabricLoader(loaders[0].loader.version); + } else { + setSelectedFabricLoader(""); + } + } else if (modLoaderType === "forge") { + const versions = await invoke( + "get_forge_versions_for_game", + { + gameVersion: selectedVersionUI.id, + }, + ); + setForgeVersions(versions || []); + if (versions && versions.length > 0) { + // Binding `ForgeVersion` uses `version` (not `id`) — use `.version` here. + setSelectedForgeLoader(versions[0].version); + } else { + setSelectedForgeLoader(""); + } + } + } catch (e) { + console.error("Failed to load mod loaders:", e); + toast.error("Failed to fetch mod loader versions"); + } finally { + setLoadingLoaders(false); + } + }, [modLoaderType, selectedVersionUI]); + + // When entering step 3 and a base version exists, fetch loaders if needed + useEffect(() => { + if (step === 3 && modLoaderType !== "vanilla" && selectedVersionUI) { + loadModLoaders(); + } + }, [step, modLoaderType, selectedVersionUI, loadModLoaders]); + + // Creating state + const [creating, setCreating] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + // Derived filtered versions + const filteredVersions = useMemo(() => { + const all = gameStore.versions || []; + let list = all.slice(); + if (versionFilter !== "all") { + list = list.filter((v) => v.type === versionFilter); + } + if (versionSearch.trim()) { + const q = versionSearch.trim().toLowerCase().replace(/。/g, "."); + list = list.filter((v) => v.id.toLowerCase().includes(q)); + } + return list; + }, [gameStore.versions, versionFilter, versionSearch]); + + // Reset when opened/closed + useEffect(() => { + if (open) { + // ensure versions are loaded + gameStore.loadVersions(); + setStep(1); + setInstanceName(""); + setVersionSearch(""); + setVersionFilter("release"); + setSelectedVersionUI(null); + setModLoaderType("vanilla"); + setFabricLoaders([]); + setForgeVersions([]); + setSelectedFabricLoader(""); + setSelectedForgeLoader(""); + setErrorMessage(""); + setCreating(false); + } + }, [open, gameStore.loadVersions]); + + function validateStep1(): boolean { + if (!instanceName.trim()) { + setErrorMessage("Please enter an instance name"); + return false; + } + setErrorMessage(""); + return true; + } + + function validateStep2(): boolean { + if (!selectedVersionUI) { + setErrorMessage("Please select a Minecraft version"); + return false; + } + setErrorMessage(""); + return true; + } + + async function handleNext() { + setErrorMessage(""); + if (step === 1) { + if (!validateStep1()) return; + setStep(2); + } else if (step === 2) { + if (!validateStep2()) return; + setStep(3); + } + } + + function handleBack() { + setErrorMessage(""); + setStep((s) => Math.max(1, s - 1)); + } + + async function handleCreate() { + if (!validateStep1() || !validateStep2()) return; + setCreating(true); + setErrorMessage(""); + + try { + // Step 1: create instance + const instance = await invoke("create_instance", { + name: instanceName.trim(), + }); + + // If selectedVersion provided, install it + if (selectedVersionUI) { + try { + await invoke("install_version", { + instanceId: instance.id, + versionId: selectedVersionUI.id, + }); + } catch (err) { + console.error("Failed to install base version:", err); + // continue - instance created but version install failed + toast.error( + `Failed to install version ${selectedVersionUI.id}: ${String(err)}`, + ); + } + } + + // If mod loader selected, install it + if (modLoaderType === "fabric" && selectedFabricLoader) { + try { + await invoke("install_fabric", { + instanceId: instance.id, + gameVersion: selectedVersionUI?.id ?? "", + loaderVersion: selectedFabricLoader, + }); + } catch (err) { + console.error("Failed to install Fabric:", err); + toast.error(`Failed to install Fabric: ${String(err)}`); + } + } else if (modLoaderType === "forge" && selectedForgeLoader) { + try { + await invoke("install_forge", { + instanceId: instance.id, + gameVersion: selectedVersionUI?.id ?? "", + installerVersion: selectedForgeLoader, + }); + } catch (err) { + console.error("Failed to install Forge:", err); + toast.error(`Failed to install Forge: ${String(err)}`); + } + } + + // Refresh instances list + await instancesStore.loadInstances(); + + toast.success("Instance created successfully"); + onOpenChange(false); + } catch (e) { + console.error("Failed to create instance:", e); + setErrorMessage(String(e)); + toast.error(`Failed to create instance: ${e}`); + } finally { + setCreating(false); + } + } + + // UI pieces + const StepIndicator = () => ( +
+
= 1 ? "bg-indigo-500" : "bg-zinc-700"}`} + /> +
= 2 ? "bg-indigo-500" : "bg-zinc-700"}`} + /> +
= 3 ? "bg-indigo-500" : "bg-zinc-700"}`} + /> +
+ ); + + return ( + + + + Create New Instance + + Multi-step wizard — create an instance and optionally install a + version or mod loader. + + + +
+
+ +
+ + {/* Step 1 - Name */} + {step === 1 && ( +
+
+ + setInstanceName(e.target.value)} + disabled={creating} + /> +
+

+ Give your instance a memorable name. +

+
+ )} + + {/* Step 2 - Version selection */} + {step === 2 && ( +
+
+
+ + setVersionSearch(e.target.value)} + placeholder="Search versions..." + className="pl-9" + /> +
+ +
+ + + +
+
+ + +
+ {gameStore.versions.length === 0 ? ( +
+ + Loading versions... +
+ ) : filteredVersions.length === 0 ? ( +
+ No matching versions found +
+ ) : ( + filteredVersions.map((v) => { + const isSelected = selectedVersionUI?.id === v.id; + return ( + + ); + }) + )} +
+
+
+ )} + + {/* Step 3 - Mod loader */} + {step === 3 && ( +
+
+
Mod Loader Type
+
+ + + +
+
+ + {modLoaderType === "fabric" && ( +
+ {loadingLoaders ? ( +
+ + Loading Fabric versions... +
+ ) : fabricLoaders.length > 0 ? ( +
+ +
+ ) : ( +

+ No Fabric loaders available for this version +

+ )} +
+ )} + + {modLoaderType === "forge" && ( +
+ {loadingLoaders ? ( +
+ + Loading Forge versions... +
+ ) : forgeVersions.length > 0 ? ( +
+ +
+ ) : ( +

+ No Forge versions available for this version +

+ )} +
+ )} +
+ )} + + {errorMessage && ( +
{errorMessage}
+ )} +
+ + +
+
+ +
+ +
+ {step > 1 && ( + + )} + + {step < 3 ? ( + + ) : ( + + )} +
+
+
+
+
+ ); +} + +export default InstanceCreationModal; diff --git a/packages/ui-new/src/components/instance-editor-modal.tsx b/packages/ui-new/src/components/instance-editor-modal.tsx new file mode 100644 index 0000000..012e62c --- /dev/null +++ b/packages/ui-new/src/components/instance-editor-modal.tsx @@ -0,0 +1,548 @@ +import { invoke } from "@tauri-apps/api/core"; +import { Folder, Loader2, Save, Trash2, X } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; + +import { toNumber } from "@/lib/tsrs-utils"; +import { useInstancesStore } from "@/stores/instances-store"; +import { useSettingsStore } from "@/stores/settings-store"; +import type { FileInfo } from "../types/bindings/core"; +import type { Instance } from "../types/bindings/instance"; + +type Props = { + open: boolean; + instance: Instance | null; + onOpenChange: (open: boolean) => void; +}; + +export function InstanceEditorModal({ open, instance, onOpenChange }: Props) { + const instancesStore = useInstancesStore(); + const { settings } = useSettingsStore(); + + const [activeTab, setActiveTab] = useState< + "info" | "version" | "files" | "settings" + >("info"); + const [saving, setSaving] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + // Info tab fields + const [editName, setEditName] = useState(""); + const [editNotes, setEditNotes] = useState(""); + + // Files tab state + const [selectedFileFolder, setSelectedFileFolder] = useState< + "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots" + >("mods"); + const [fileList, setFileList] = useState([]); + const [loadingFiles, setLoadingFiles] = useState(false); + const [deletingPath, setDeletingPath] = useState(null); + + // Version tab state (placeholder - the Svelte implementation used a ModLoaderSelector component) + // React versions-view/instance-creation handle mod loader installs; here we show basic current info. + + // Settings tab fields + const [editMemoryMin, setEditMemoryMin] = useState(0); + const [editMemoryMax, setEditMemoryMax] = useState(0); + const [editJavaArgs, setEditJavaArgs] = useState(""); + + // initialize when open & instance changes + useEffect(() => { + if (open && instance) { + setActiveTab("info"); + setSaving(false); + setErrorMessage(""); + setEditName(instance.name || ""); + setEditNotes(instance.notes ?? ""); + setEditMemoryMin( + (instance.memoryOverride && toNumber(instance.memoryOverride.min)) ?? + settings.minMemory ?? + 512, + ); + setEditMemoryMax( + (instance.memoryOverride && toNumber(instance.memoryOverride.max)) ?? + settings.maxMemory ?? + 2048, + ); + setEditJavaArgs(instance.jvmArgsOverride ?? ""); + setFileList([]); + setSelectedFileFolder("mods"); + } + }, [open, instance, settings.minMemory, settings.maxMemory]); + + // load files when switching to files tab + const loadFileList = useCallback( + async ( + folder: + | "mods" + | "resourcepacks" + | "shaderpacks" + | "saves" + | "screenshots", + ) => { + if (!instance) return; + setLoadingFiles(true); + try { + const files = await invoke("list_instance_directory", { + instanceId: instance.id, + folder, + }); + setFileList(files || []); + } catch (err) { + console.error("Failed to load files:", err); + toast.error("Failed to load files: " + String(err)); + setFileList([]); + } finally { + setLoadingFiles(false); + } + }, + [instance], + ); + + useEffect(() => { + if (open && instance && activeTab === "files") { + // explicitly pass the selected folder so loadFileList doesn't rely on stale closures + loadFileList(selectedFileFolder); + } + }, [activeTab, open, instance, selectedFileFolder, loadFileList]); + + async function changeFolder( + folder: "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots", + ) { + setSelectedFileFolder(folder); + // reload the list for the newly selected folder + if (open && instance) await loadFileList(folder); + } + + async function deleteFile(filePath: string) { + if ( + !confirm( + `Are you sure you want to delete "${filePath.split("/").pop()}"?`, + ) + ) { + return; + } + setDeletingPath(filePath); + try { + await invoke("delete_instance_file", { path: filePath }); + // refresh the currently selected folder + await loadFileList(selectedFileFolder); + toast.success("Deleted"); + } catch (err) { + console.error("Failed to delete file:", err); + toast.error("Failed to delete file: " + String(err)); + } finally { + setDeletingPath(null); + } + } + + async function openInExplorer(filePath: string) { + try { + await invoke("open_file_explorer", { path: filePath }); + } catch (err) { + console.error("Failed to open in explorer:", err); + toast.error("Failed to open file explorer: " + String(err)); + } + } + + async function saveChanges() { + if (!instance) return; + if (!editName.trim()) { + setErrorMessage("Instance name cannot be empty"); + return; + } + setSaving(true); + setErrorMessage(""); + try { + // Build updated instance shape compatible with backend + const updatedInstance: Instance = { + ...instance, + name: editName.trim(), + // some bindings may use camelCase; set optional string fields to null when empty + notes: editNotes.trim() ? editNotes.trim() : null, + memoryOverride: { + min: editMemoryMin, + max: editMemoryMax, + }, + jvmArgsOverride: editJavaArgs.trim() ? editJavaArgs.trim() : null, + }; + + await instancesStore.updateInstance(updatedInstance as Instance); + toast.success("Instance saved"); + onOpenChange(false); + } catch (err) { + console.error("Failed to save instance:", err); + setErrorMessage(String(err)); + toast.error("Failed to save instance: " + String(err)); + } finally { + setSaving(false); + } + } + + function formatFileSize(bytesBig: FileInfo["size"]): string { + const bytes = Number(bytesBig ?? 0); + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`; + } + + function formatDate( + tsBig?: + | FileInfo["modified"] + | Instance["createdAt"] + | Instance["lastPlayed"], + ) { + if (tsBig === undefined || tsBig === null) return ""; + const n = toNumber(tsBig); + // tsrs bindings often use seconds for createdAt/lastPlayed; if value looks like seconds use *1000 + const maybeMs = n > 1e12 ? n : n * 1000; + return new Date(maybeMs).toLocaleDateString(); + } + + return ( + + + +
+
+ Edit Instance + {instance?.name ?? ""} +
+
+ +
+
+
+ + {/* Tab Navigation */} +
+ {[ + { id: "info", label: "Info" }, + { id: "version", label: "Version" }, + { id: "files", label: "Files" }, + { id: "settings", label: "Settings" }, + ].map((tab) => ( + + ))} +
+ + {/* Content */} +
+ {activeTab === "info" && ( +
+
+ + setEditName(e.target.value)} + disabled={saving} + /> +
+ +
+ +