diff --git a/README.md b/README.md index 102e366..6705a2a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,27 @@ -# Tauri + React + Typescript +

+ Saturn Launcher Logo +

-This template should help get you started developing with Tauri, React and Typescript in Vite. +

Saturn Launcher

-## Recommended IDE Setup +

+ A fast, modern, and customizable launcher built for performance and flexibility, built with Rust. +

+ +--- + +## ✨ Features + +- ⚡ **High Performance** – Optimized for speed and responsiveness +- 🎨 **Modern UI** – Clean, minimal, and aesthetic interface +- 🧩 **Modular Architecture** – Easily extend and maintain mods +- 📦 **Efficient File Handling** – Smart installation and management system + +--- + +## 🧱 Tech Stack + +- **Frontend:** React / TypeScript / TailwindCSS +- **Backend:** Rust (Tauri) +- **Build Tooling:** Vite -- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/package-lock.json b/package-lock.json index 2559835..34fd321 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "lucide-react": "^1.7.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-hot-toast": "^2.6.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2" }, @@ -1776,7 +1777,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -1945,6 +1945,15 @@ "node": ">=6.9.0" } }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2384,6 +2393,23 @@ "react": "^19.2.4" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/package.json b/package.json index 0ae37c1..b7fd0d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "saturn-launcher", "private": true, - "version": "0.1.0", + "version": "0.1.1", "type": "module", "scripts": { "dev": "vite", @@ -18,6 +18,7 @@ "lucide-react": "^1.7.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-hot-toast": "^2.6.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2" }, diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b183c09..0f8f23b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -874,15 +874,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "6.0.0" @@ -3896,12 +3887,10 @@ dependencies = [ [[package]] name = "saturn-launcher" -version = "0.1.0" +version = "0.1.1" dependencies = [ - "directories", "futures", "lyceris", - "once_cell", "rayon", "reqwest 0.12.28", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3c502cc..aaa4454 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "saturn-launcher" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] +version = "0.1.1" +description = "A minecraft launcher for Saturn Client" +authors = ["SaturnClient"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,8 +22,6 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -directories = "6.0" -once_cell = "1.21" lyceris = "1.1.3" rayon = "1.10.0" reqwest = "0.12.9" diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 3069f0c..d5d9392 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index cf1c0e2..9c1bc91 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index 956bc35..a41fb19 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png index 61c7cb7..20313d0 100644 Binary files a/src-tauri/icons/64x64.png and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 352aad5..a804a99 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index ff736fc..2c84081 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 5678854..91b884d 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index 637adc4..526250c 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 1509eff..5469595 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index 43c35ee..5185972 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d2f0d13..cdf04bc 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 2c8058b..cfa883c 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index 2a31f6e..d3cd0e8 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 5f8a42b..8df8c94 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..d70fdbb Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2c3773d Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..c746622 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..4bae155 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..952f65d Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..cc1570a Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6b80b34 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e3f5ac6 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..de4e5b6 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..309980c Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b4aaa6f Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a91b2da Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..afcb02c Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3012397 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4590263 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 5e78c72..400f112 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index f8165bf..85ca785 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index af829c6..b68e341 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..f8c3acc Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..77f16bd Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..77f16bd Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..ada5318 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..7d01203 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..8bbb0c0 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..8bbb0c0 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..59d8cbf Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..77f16bd Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..74105c9 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..74105c9 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..5f50e49 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..f1eb508 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..5f50e49 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..6961685 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..6750c24 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..74d1417 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..c43d0a1 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 0f7ed26..b05ffc4 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,10 +1,10 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use serde::Deserialize; -use tauri::State; +use tauri::{AppHandle, Emitter, State}; use tokio::fs; -use crate::{launcher::LAUNCHER_DIR, save_state, LauncherSettings, Mod, SharedState}; +use crate::{launcher::launcher_dir, save_state, LauncherSettings, Mod, SharedState}; #[derive(Deserialize)] struct ModrinthVersion { @@ -25,12 +25,12 @@ struct ModrinthFile { pub async fn get_installed_mods(version_id: String) -> Result, String> { let mut mods = Vec::new(); - let base_dir = LAUNCHER_DIR.data_dir().join(&version_id).join("mods"); + let base_dir = launcher_dir().join(&version_id).join("mods"); let enabled_dir = base_dir.clone(); let disabled_dir = base_dir.join("disabled_mods"); - let cache_dir = LAUNCHER_DIR.data_dir().join("cache"); + let cache_dir = launcher_dir().join("cache"); // process a directory async async fn process_dir(dir: PathBuf, enabled: bool, cache_dir: PathBuf) -> Vec { @@ -105,10 +105,32 @@ fn fallback_mod(file_name: &str, enabled: bool) -> Mod { } } +#[tauri::command] +pub async fn remove_mod(version: String, file_name: String) -> Result<(), String> { + // mods folder + let mods_dir: PathBuf = launcher_dir().join(&version).join("mods"); + + // File to remove + let mut source = mods_dir.join(&file_name); + + if !source.exists() { + source = mods_dir.join("disabled_mods").join(&file_name); + } + + if !source.exists() { + return Err(format!("Mod file not found: {}", file_name)); + } + + // move file + fs::remove_file(&source).await.map_err(|e| e.to_string())?; + + Ok(()) +} + #[tauri::command] pub async fn disable_mod(version: String, file_name: String) -> Result<(), String> { // mods folder - let mods_dir: PathBuf = LAUNCHER_DIR.data_dir().join(&version).join("mods"); + let mods_dir: PathBuf = launcher_dir().join(&version).join("mods"); // disabled_mods folder let disabled_dir = mods_dir.join("disabled_mods"); @@ -135,7 +157,7 @@ pub async fn disable_mod(version: String, file_name: String) -> Result<(), Strin #[tauri::command] pub async fn enable_mod(version: String, file_name: String) -> Result<(), String> { - let mods_dir = LAUNCHER_DIR.data_dir().join(&version).join("mods"); + let mods_dir = launcher_dir().join(&version).join("mods"); let disabled_dir = mods_dir.join("disabled_mods"); @@ -159,7 +181,7 @@ pub async fn install_mod(mut mod_meta: Mod, versions: Vec) -> Result<(), println!("Installing {} for {:?}", mod_meta.name, versions); - let cache_dir = LAUNCHER_DIR.data_dir().join("cache"); + let cache_dir = launcher_dir().join("cache"); fs::create_dir_all(&cache_dir) .await .map_err(|e| e.to_string())?; @@ -206,7 +228,7 @@ pub async fn install_mod(mut mod_meta: Mod, versions: Vec) -> Result<(), .await .map_err(|e| e.to_string())?; - let dir: PathBuf = LAUNCHER_DIR.data_dir().join(&mc_version).join("mods"); + let dir: PathBuf = launcher_dir().join(&mc_version).join("mods"); fs::create_dir_all(&dir).await.map_err(|e| e.to_string())?; @@ -269,3 +291,34 @@ pub fn update_version(state: State, version: String) { save_state(&state); } + +#[tauri::command] +pub async fn install_paths( + app: AppHandle, + state: State<'_, SharedState>, + paths: Vec, +) -> Result<(), String> { + for path in &paths { + let file = Path::new(&path) + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| String::from("File name not found"))?; + + let version_dir = launcher_dir().join(&state.lock().unwrap().version); + + let mc_child = if path.ends_with(".jar") { + "mods" + } else { + return Err(String::from("Invalid file type")); + }; + + fs::copy(path, version_dir.join(mc_child).join(file)) + .await + .map_err(|e| e.to_string())?; + + app.emit("path-installed", path.to_string()) + .map_err(|e| e.to_string())?; + } + + Ok(()) +} diff --git a/src-tauri/src/launcher/auth.rs b/src-tauri/src/launcher/auth.rs index ffb4760..a1ac007 100644 --- a/src-tauri/src/launcher/auth.rs +++ b/src-tauri/src/launcher/auth.rs @@ -5,10 +5,10 @@ use tauri::{command, AppHandle, Emitter, Manager, Url}; use lyceris::auth::microsoft::{authenticate, create_link, refresh, validate, MinecraftAccount}; -use crate::launcher::LAUNCHER_DIR; +use crate::launcher::launcher_dir; fn accounts_path() -> PathBuf { - LAUNCHER_DIR.data_dir().join("accounts.json") + launcher_dir().join("accounts.json") } fn load_accounts() -> Vec { @@ -52,30 +52,31 @@ pub fn auth_create_link(app: AppHandle) -> Result { std::thread::spawn(move || { tauri::WebviewWindowBuilder::new(&app, "auth", tauri::WebviewUrl::External(url)) - .title("Microsoft Login") - .inner_size(500.0, 700.0) - .resizable(false) - .on_navigation(move |nav_url| { - let url_str = nav_url.to_string(); + .title("Microsoft Login") + .inner_size(500.0, 700.0) + .resizable(false) + .on_navigation(move |nav_url| { + let url_str = nav_url.to_string(); - // Check for auth code - if url_str.contains("code=") { - if let Some(code) = extract_code(&url_str) { - println!("Auth code received: {}", code); + // Check for auth code + if url_str.contains("code=") { + if let Some(code) = extract_code(&url_str) { + println!("Auth code received: {}", code); - let _ = app_handle.emit("auth-code", code); + let _ = app_handle.emit("auth-code", code); - if let Some(win) = app_handle.get_webview_window("auth") { - let _ = win.close(); + if let Some(win) = app_handle.get_webview_window("auth") { + let _ = win.close(); + } } } - } - // Always allow navigation - true - }) - .build() - .map_err(|e| e.to_string()).unwrap(); + // Always allow navigation + true + }) + .build() + .map_err(|e| e.to_string()) + .unwrap(); }); Ok(link) diff --git a/src-tauri/src/launcher/mod.rs b/src-tauri/src/launcher/mod.rs index 4b27879..de96d04 100644 --- a/src-tauri/src/launcher/mod.rs +++ b/src-tauri/src/launcher/mod.rs @@ -2,7 +2,8 @@ pub mod auth; pub mod downloader; pub mod install; -use directories::ProjectDirs; +use std::{env, path::PathBuf}; + use lyceris::{ auth::microsoft::MinecraftAccount, minecraft::{ @@ -12,15 +13,35 @@ use lyceris::{ loader::fabric::Fabric, }, }; -use once_cell::sync::Lazy; + use tauri::{Emitter as _, State}; use crate::{GameVersion, SharedState}; -pub static LAUNCHER_DIR: Lazy = Lazy::new(|| { - ProjectDirs::from("org", "SaturnLauncher", "SaturnLauncher") - .expect("Failed to create project directories") -}); +pub fn launcher_dir() -> PathBuf { + if cfg!(target_os = "macos") { + let home = env::var("HOME") + .map(PathBuf::from) + .expect("HOME environment variable not set"); + + home.join("Library/Application Support/SaturnLauncher") + } else if cfg!(target_os = "windows") { + env::var("APPDATA") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from(".")) + .join("SaturnLauncher") + } else { + env::var("XDG_DATA_HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| { + env::var("HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from(".")) + .join(".local/share") + }) + .join("SaturnLauncher") + } +} #[tauri::command] pub async fn launch_game( @@ -70,10 +91,8 @@ pub async fn launch_game( }) .await; - let current_dir = LAUNCHER_DIR.data_dir(); - let config = ConfigBuilder::new( - current_dir.join(&version.id), + launcher_dir().join(&version.id), version.id.clone(), lyceris::auth::AuthMethod::Microsoft { username: account.username, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fc60075..8cb4471 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2,11 +2,15 @@ pub mod commands; pub mod launcher; use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf, sync::Mutex}; +use std::{ + fs, + path::PathBuf, + sync::{Arc, Mutex}, +}; -use crate::launcher::LAUNCHER_DIR; +use crate::launcher::launcher_dir; -type SharedState = Mutex; +pub type SharedState = Arc>; // --- Types --- #[derive(Clone, Serialize, Deserialize)] @@ -51,7 +55,7 @@ impl Default for AppState { // Helper to get config path fn config_path() -> PathBuf { - LAUNCHER_DIR.config_dir().join("launcher.json") + launcher_dir().join("launcher.json") } // --- Load --- @@ -61,7 +65,7 @@ pub fn load_state() -> SharedState { if path.exists() { match fs::read_to_string(&path) { Ok(content) => match serde_json::from_str::(&content) { - Ok(state) => return Mutex::new(state), + Ok(state) => return Arc::new(Mutex::new(state)), Err(err) => { eprintln!("Failed to parse config, using default: {err}"); } @@ -73,7 +77,7 @@ pub fn load_state() -> SharedState { } // If missing or failed → create default - let default = Mutex::new(AppState::default()); + let default = Arc::new(Mutex::new(AppState::default())); save_state(&default); default diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5bbb611..72fa828 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use saturn_launcher_lib::load_state; +use tauri::{Emitter, Manager, WindowEvent}; #[cfg_attr(mobile, tauri::mobile_entry_point)] fn main() { @@ -11,13 +12,15 @@ fn main() { .invoke_handler(tauri::generate_handler![ // Saturn Launcher Commands saturn_launcher_lib::commands::get_installed_mods, + saturn_launcher_lib::commands::install_mod, saturn_launcher_lib::commands::enable_mod, saturn_launcher_lib::commands::disable_mod, - saturn_launcher_lib::commands::install_mod, + saturn_launcher_lib::commands::remove_mod, saturn_launcher_lib::commands::get_settings, saturn_launcher_lib::commands::update_settings, saturn_launcher_lib::commands::get_version, saturn_launcher_lib::commands::update_version, + saturn_launcher_lib::commands::install_paths, // Launcher Commands saturn_launcher_lib::launcher::launch_game, // Auth Commands @@ -29,6 +32,46 @@ fn main() { saturn_launcher_lib::launcher::auth::auth_validate, saturn_launcher_lib::launcher::auth::auth_get_valid ]) + .setup(|app| { + match app.get_webview_window("main") { + Some(window) => { + let handle = app.handle().clone(); + + window.on_window_event(move |event| { + if let WindowEvent::DragDrop(file_event) = event { + if let tauri::DragDropEvent::Drop { paths, .. } = file_event { + println!("Installing files: {:?}", paths); + + if handle + .emit( + "install-paths", + paths + .iter() + .filter_map(|p| { + if p.is_file() { + Some(p.to_str()?.to_string()) + } else { + None + } + }) + .collect::>(), + ) + .is_err() + { + println!("Failed emitting files to frontend"); + } + } + } + }); + } + + None => { + println!("WARN: Drag and drop feature will not work"); + } + } + + Ok(()) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ec87aae..2240a4c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "saturn-launcher", - "version": "0.1.0", + "version": "0.1.1", "identifier": "org.saturnclient.saturn-launcher", "build": { "beforeDevCommand": "npm run dev", @@ -12,11 +12,12 @@ "app": { "windows": [ { - "title": "saturn-launcher", + "title": "Saturn Launcher v0.1.1", "width": 1000, "height": 650, "backgroundColor": "#0a0a0a", - "visible": false + "visible": false, + "dragDropEnabled": true } ], "security": { @@ -34,4 +35,4 @@ "icons/icon.ico" ] } -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 224b5df..daa7827 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,8 +7,10 @@ import SettingsSection from "./components/SettingsSection"; import { versions } from "./lib/launcher"; import { GameVersion } from "./lib/types"; import { MinecraftAccount } from "./lib/auth"; -import { getVersion, updateVersion } from "./lib/saturn"; +import { getVersion, installPaths, updateVersion } from "./lib/saturn"; import { getCurrentWindow } from "@tauri-apps/api/window"; +import { listen } from "@tauri-apps/api/event"; +import toast, { Toaster } from "react-hot-toast"; type Section = "play" | "mods" | "settings"; @@ -68,37 +70,82 @@ export default function App() { window.show(); }, []); + useEffect(() => { + if (!selectedVersion?.id) return; + + const version = selectedVersion?.id; + + const unlistenInstalls = listen("install-paths", (event) => { + event.payload.forEach((path) => { + const fileName = path.split(/[\\/]/).pop(); + + toast.promise(installPaths(version, [path]), { + loading: `${fileName}`, + success: `${fileName}`, + error: (e) => `${fileName} Failed: ${e}` + }).catch(console.error); + }); + }); + + return () => { + unlistenInstalls.then((f) => f()); + }; + }, [selectedVersion]); + return ( -
- + <> + -
- +
+ -
- {active === "play" && ( - - )} - {active === "mods" && selectedVersion && ( - - )} - {active === "settings" && } -
-
-
+
+ + +
+ {active === "play" && ( + + )} + {active === "mods" && selectedVersion && ( + + )} + {active === "settings" && } +
+
+ + ); } diff --git a/src/assets/logo.png b/src/assets/logo.png index cff8085..83a0142 100644 Binary files a/src/assets/logo.png and b/src/assets/logo.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..7b75b95 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/logo_no_bg.png b/src/assets/logo_no_bg.png new file mode 100644 index 0000000..cff8085 Binary files /dev/null and b/src/assets/logo_no_bg.png differ diff --git a/src/components/ModsSection.tsx b/src/components/ModsSection.tsx index f62aeb6..e075db2 100644 --- a/src/components/ModsSection.tsx +++ b/src/components/ModsSection.tsx @@ -14,12 +14,14 @@ import { enableMod, installMod, disableMod, + removeMod, } from "@/lib/saturn"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; import { motion, AnimatePresence } from "framer-motion"; import { GameVersion, Mod } from "@/lib/types"; import { versions } from "@/lib/launcher"; +import toast from "react-hot-toast"; function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -46,6 +48,10 @@ const ModsSection: React.FC = ({ version }) => { if (activeTab === "installed") { const m = await getInstalledMods(version.id); setMods(m); + if (activeTab === "installed") { + // Fix: If discover mods takes too long and the user switches tabs it glitches + setMods(m); + } } else { const m = await discoverMods(searchQuery); if (activeTab === "discover") { @@ -72,6 +78,11 @@ const ModsSection: React.FC = ({ version }) => { setMods(m); }; + const handleRemove = async (modId: string) => { + await removeMod(version.id, modId); + loadMods(); + } + const openInstallDialog = (mod: Mod) => { setSelectedModForInstall(mod); setSelectedVersionsForInstall([version.id]); @@ -80,12 +91,17 @@ const ModsSection: React.FC = ({ version }) => { const handleInstall = async () => { if (selectedModForInstall && selectedVersionsForInstall.length > 0) { - await installMod(selectedModForInstall, selectedVersionsForInstall); setIsInstallDialogOpen(false); - if (activeTab === "installed") { - const m = await getInstalledMods(version.id); - setMods(m); - } + toast.promise(installMod(selectedModForInstall, selectedVersionsForInstall), { + loading: `Installing mod: ${selectedModForInstall.name}`, + success: `Mod sucessfully installed: ${selectedModForInstall.name}`, + error: (e) => `${selectedModForInstall.name} Failed: ${e}` + }).then(async () => { + if (activeTab === "installed") { + const m = await getInstalledMods(version.id); + setMods(m); + } + }).catch(console.error); } }; @@ -170,7 +186,7 @@ const ModsSection: React.FC = ({ version }) => {

{mod.name}

- v{mod.version} + {mod.version} by {mod.author} @@ -200,7 +216,7 @@ const ModsSection: React.FC = ({ version }) => { )} {mod.enabled ? "Enabled" : "Disabled"} - @@ -271,7 +287,7 @@ const ModsSection: React.FC = ({ version }) => { ? "bg-saturn-accent/10 border-saturn-accent/50" : "bg-white/5 border-transparent hover:border-white/10", !isSupported && - "opacity-40 cursor-not-allowed grayscale", + "opacity-40 cursor-not-allowed grayscale", )} >
diff --git a/src/components/SettingsSection.tsx b/src/components/SettingsSection.tsx index a43eb65..ef1a077 100644 --- a/src/components/SettingsSection.tsx +++ b/src/components/SettingsSection.tsx @@ -45,9 +45,9 @@ const SettingsSection: React.FC = () => {
-

Performance Settings

+

Settings

- Configure how Saturn Client interacts with your hardware. + Configure how Saturn Launcher launches your game

diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 202125f..8d0516f 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,5 +1,5 @@ import { Play, Puzzle, Settings } from "lucide-react"; -import Logo from "@/assets/logo.png"; +import Logo from "@/assets/logo_no_bg.png"; import { cn } from "@/lib/utils"; export default function Sidebar({ active, setActive }: any) { diff --git a/src/lib/saturn.ts b/src/lib/saturn.ts index 0dad4ad..debd614 100644 --- a/src/lib/saturn.ts +++ b/src/lib/saturn.ts @@ -17,6 +17,13 @@ export async function disableMod( await invoke("disable_mod", { version, fileName: modId }); } +export async function removeMod( + version: string, + modId: string, +): Promise { + await invoke("remove_mod", { version, fileName: modId }); +} + export async function discoverMods(query: string): Promise { const url = new URL("https://api.modrinth.com/v2/search"); @@ -73,3 +80,7 @@ export async function getVersion(): Promise { export async function updateVersion(version: string): Promise { await invoke("update_version", { version }); } + +export async function installPaths(version: string, paths: string[]): Promise { + await invoke("install_paths", { version, paths }); +}