diff --git a/theseus/src/api/mod.rs b/theseus/src/api/mod.rs index 9b585f945..5260635cd 100644 --- a/theseus/src/api/mod.rs +++ b/theseus/src/api/mod.rs @@ -29,7 +29,7 @@ pub mod prelude { profile::{self, create, Profile}, settings, state::JavaGlobals, - state::{ProfilePathId, ProjectPathId}, + state::{Dependency, ProfilePathId, ProjectPathId}, util::{ io::{canonicalize, IOError}, jre::JavaVersion, diff --git a/theseus/src/api/pack/install_from.rs b/theseus/src/api/pack/install_from.rs index e382bacde..e9723f281 100644 --- a/theseus/src/api/pack/install_from.rs +++ b/theseus/src/api/pack/install_from.rs @@ -155,7 +155,7 @@ pub fn get_profile_from_pack( }, CreatePackLocation::FromFile { path } => { let file_name = path - .file_name() + .file_stem() .unwrap_or_default() .to_string_lossy() .to_string(); diff --git a/theseus/src/api/profile/create.rs b/theseus/src/api/profile/create.rs index 3e259cca2..ab280ee7a 100644 --- a/theseus/src/api/profile/create.rs +++ b/theseus/src/api/profile/create.rs @@ -1,6 +1,7 @@ //! Theseus profile management interface use crate::pack::install_from::CreatePackProfile; use crate::prelude::ProfilePathId; +use crate::profile; use crate::state::LinkedData; use crate::util::io::{self, canonicalize}; use crate::{ @@ -32,11 +33,14 @@ pub async fn profile_create( linked_data: Option, // the linked project ID (mainly for modpacks)- used for updating skip_install_profile: Option, ) -> crate::Result { + name = profile::sanitize_profile_name(&name); + trace!("Creating new profile. {}", name); let state = State::get().await?; let uuid = Uuid::new_v4(); let mut path = state.directories.profiles_dir().await.join(&name); + if path.exists() { let mut new_name; let mut new_path; diff --git a/theseus/src/api/profile/mod.rs b/theseus/src/api/profile/mod.rs index 88e152a0a..4c619d09f 100644 --- a/theseus/src/api/profile/mod.rs +++ b/theseus/src/api/profile/mod.rs @@ -623,24 +623,22 @@ pub async fn export_mrpack( // Get highest level folder pair ('a/b' in 'a/b/c', 'a' in 'a') // We only go one layer deep for the sake of not having a huge list of overrides - let topmost_two = relative_path - .iter() - .take(2) - .map(|os| os.to_string_lossy().to_string()) - .collect::>(); + let topmost_two = relative_path.iter().take(2).collect::>(); // a,b => a/b // a => a let topmost = match topmost_two.len() { - 2 => topmost_two.join("/"), - 1 => topmost_two[0].clone(), + 2 => PathBuf::from(topmost_two[0]).join(topmost_two[1]), + 1 => PathBuf::from(topmost_two[0]), _ => { return Err(crate::ErrorKind::OtherError( "No topmost folder found".to_string(), ) .into()) } - }; + } + .to_string_lossy() + .to_string(); if !included_overrides.contains(&topmost) { continue; @@ -851,13 +849,14 @@ pub async fn run_credentials( }; // Any options.txt settings that we want set, add here - let mc_set_options: Vec<(String, String)> = vec![( - "fullscreen".to_string(), - profile - .fullscreen - .unwrap_or(settings.force_fullscreen) - .to_string(), - )]; + let mut mc_set_options: Vec<(String, String)> = vec![]; + if let Some(fullscreen) = profile.fullscreen { + // Profile fullscreen setting takes priority + mc_set_options.push(("fullscreen".to_string(), fullscreen.to_string())); + } else if settings.force_fullscreen { + // If global settings wants to force a fullscreen, do it + mc_set_options.push(("fullscreen".to_string(), "true".to_string())); + } let mc_process = crate::launcher::launch_minecraft( java_args, @@ -929,15 +928,11 @@ pub async fn create_mrpack_json( .map(|(k, v)| (k, sanitize_loader_version_string(&v).to_string())) .collect::>(); - let profile_base_path = profile.get_profile_full_path().await?; let files: Result, crate::ErrorKind> = profile .projects .iter() .filter_map(|(mod_path, project)| { - let path: String = profile_base_path - .join(mod_path.0.clone()) - .to_string_lossy() - .to_string(); + let path: String = mod_path.0.clone().to_string_lossy().to_string(); // Only Modrinth projects have a modrinth metadata field for the modrinth.json Some(Ok(match project.metadata { @@ -1035,3 +1030,7 @@ pub async fn build_folder( } Ok(()) } + +pub fn sanitize_profile_name(input: &str) -> String { + input.replace(['/', '\\'], "_") +} diff --git a/theseus/src/state/projects.rs b/theseus/src/state/projects.rs index 27d3152d8..e5cf6fe9c 100644 --- a/theseus/src/state/projects.rs +++ b/theseus/src/state/projects.rs @@ -6,6 +6,7 @@ use crate::util::fetch::{ fetch_json, write_cached_icon, FetchSemaphore, IoSemaphore, }; use crate::util::io::IOError; + use async_zip::tokio::read::fs::ZipFileReader; use chrono::{DateTime, Utc}; use futures::StreamExt; @@ -49,6 +50,27 @@ impl ProjectType { } } + pub fn get_from_parent_folder(path: PathBuf) -> Option { + // Get parent folder + let path = path.parent()?.file_name()?; + match path.to_str()? { + "mods" => Some(ProjectType::Mod), + "datapacks" => Some(ProjectType::DataPack), + "resourcepacks" => Some(ProjectType::ResourcePack), + "shaderpacks" => Some(ProjectType::ShaderPack), + _ => None, + } + } + + pub fn get_name(&self) -> &'static str { + match self { + ProjectType::Mod => "mod", + ProjectType::DataPack => "datapack", + ProjectType::ResourcePack => "resourcepack", + ProjectType::ShaderPack => "shaderpack", + } + } + pub fn get_folder(&self) -> &'static str { match self { ProjectType::Mod => "mods", @@ -439,6 +461,8 @@ pub async fn infer_data_from_files( )); continue; }; + + // Forge let zip_index_option = zip_file_reader .file() .entries() @@ -512,6 +536,7 @@ pub async fn infer_data_from_files( } } + // Forge let zip_index_option = zip_file_reader .file() .entries() @@ -572,6 +597,7 @@ pub async fn infer_data_from_files( } } + // Fabric let zip_index_option = zip_file_reader .file() .entries() @@ -641,6 +667,7 @@ pub async fn infer_data_from_files( } } + // Quilt let zip_index_option = zip_file_reader .file() .entries() @@ -717,6 +744,7 @@ pub async fn infer_data_from_files( } } + // Other let zip_index_option = zip_file_reader .file() .entries() @@ -745,6 +773,10 @@ pub async fn infer_data_from_files( io_semaphore, ) .await?; + + // Guess the project type from the filepath + let project_type = + ProjectType::get_from_parent_folder(path.clone()); return_projects.push(( path.clone(), Project { @@ -757,7 +789,8 @@ pub async fn infer_data_from_files( authors: Vec::new(), version: None, icon, - project_type: None, + project_type: project_type + .map(|x| x.get_name().to_string()), }, }, )); @@ -778,7 +811,6 @@ pub async fn infer_data_from_files( } // Project paths should be relative - let _profile_base_path = profile.get_profile_full_path().await?; let mut corrected_hashmap = HashMap::new(); let mut stream = tokio_stream::iter(return_projects); while let Some((h, v)) = stream.next().await { diff --git a/theseus_gui/src-tauri/Info.plist b/theseus_gui/src-tauri/Info.plist index bdb6467e3..f10c79882 100644 --- a/theseus_gui/src-tauri/Info.plist +++ b/theseus_gui/src-tauri/Info.plist @@ -59,5 +59,10 @@ + NSCameraUsageDescription + A Minecraft mod wants to access your camera. + NSMicrophoneUsageDescription + A Minecraft mod wants to access your microphone. + diff --git a/theseus_gui/src-tauri/src/api/settings.rs b/theseus_gui/src-tauri/src/api/settings.rs index 1b8b3c134..28221b845 100644 --- a/theseus_gui/src-tauri/src/api/settings.rs +++ b/theseus_gui/src-tauri/src/api/settings.rs @@ -1,27 +1,8 @@ use std::path::PathBuf; use crate::api::Result; -use serde::{Deserialize, Serialize}; use theseus::prelude::*; -// Identical to theseus::settings::Settings except for the custom_java_args field -// This allows us to split the custom_java_args string into a Vec here and join it back into a string in the backend -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct FrontendSettings { - pub theme: Theme, - pub memory: MemorySettings, - pub game_resolution: WindowSize, - pub custom_java_args: String, - pub custom_env_args: String, - pub java_globals: JavaGlobals, - pub default_user: Option, - pub hooks: Hooks, - pub max_concurrent_downloads: usize, - pub max_concurrent_writes: usize, - pub version: u32, - pub collapsed_navigation: bool, -} - pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("settings") .invoke_handler(tauri::generate_handler![ diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 165168878..e8507064a 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -35,7 +35,6 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> { e ))) })?; - println!("Toggled decorations!"); Ok(()) } @@ -97,8 +96,6 @@ fn main() { } #[cfg(target_os = "macos")] { - win.set_decorations(true).unwrap(); - use macos::window_ext::WindowExt; win.set_transparent_titlebar(true); win.position_traffic_lights(9.0, 16.0); diff --git a/theseus_gui/src-tauri/tauri.macos.conf.json b/theseus_gui/src-tauri/tauri.macos.conf.json new file mode 100644 index 000000000..a138f6ec8 --- /dev/null +++ b/theseus_gui/src-tauri/tauri.macos.conf.json @@ -0,0 +1,19 @@ +{ + "tauri": { + "windows": [ + { + "titleBarStyle": "Overlay", + "hiddenTitle": true, + "fullscreen": false, + "height": 650, + "resizable": true, + "title": "Modrinth App", + "width": 1280, + "minHeight": 630, + "minWidth": 1100, + "visible": false, + "decorations": true + } + ] + } +} diff --git a/theseus_gui/src/App.vue b/theseus_gui/src/App.vue index 5a0d210c8..535db78a2 100644 --- a/theseus_gui/src/App.vue +++ b/theseus_gui/src/App.vue @@ -98,7 +98,13 @@ const confirmClose = async () => { } const handleClose = async () => { - const isSafe = await check_safe_loading_bars_complete() + // State should respond immeiately if it's safe to close + // If not, code is deadlocked or worse, so wait 2 seconds and then ask the user to confirm closing + // (Exception: if the user is changing config directory, which takes control of the state, and it's taking a significant amount of time for some reason) + const isSafe = await Promise.race([ + check_safe_loading_bars_complete(), + new Promise((r) => setTimeout(r, 2000)), + ]) if (!isSafe) { const response = await confirmClose() if (!response) { diff --git a/theseus_gui/src/components/ui/ExportModal.vue b/theseus_gui/src/components/ui/ExportModal.vue index 112c0eb41..f11bc6135 100644 --- a/theseus_gui/src/components/ui/ExportModal.vue +++ b/theseus_gui/src/components/ui/ExportModal.vue @@ -6,6 +6,7 @@ import { export_profile_mrpack, get_potential_override_folders } from '@/helpers import { open } from '@tauri-apps/api/dialog' import { handleError } from '@/store/notifications.js' import { sep } from '@tauri-apps/api/path' +import { useTheming } from '@/store/theme' const props = defineProps({ instance: { @@ -27,6 +28,8 @@ const versionInput = ref('1.0.0') const files = ref([]) const folders = ref([]) +const themeStore = useTheming() + const initFiles = async () => { const newFolders = new Map() files.value = [] @@ -88,7 +91,7 @@ const exportPack = async () => {