diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..e69de29b diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e69de29b diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0e56429a..fdea19de 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,11 +2,11 @@ "recommendations": [ "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", - "bradlc.vscode-tailwindcss", - // "oscarbeaumont.rspc-vscode", + "vadimcn.vscode-lldb", + "antfu.unocss", + "oscarbeaumont.rspc-vscode", "EditorConfig.EditorConfig", "prisma.prisma", "dbaeumer.vscode-eslint" - // "astro-build.astro-vscode" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..72f6a690 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Tauri Development", + "cargo": { + "args": ["build", "--package onelauncher_gui"] + }, + "preLaunchTask": "desktop:frontend:dev" + }, + { + "type": "lldb", + "request": "launch", + "name": "Tauri Production", + "cargo": { + "args": ["build", "--package onelauncher_gui", "--release"] + }, + "preLaunchTask": "desktop:frontend:build" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..fb8a7de1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "desktop:frontend:dev", + "type": "shell", + "isBackground": true, + "command": "pnpm", + "args": ["frontend", "dev"], + // todo: problemMatcher + "group": "build" + }, + { + "label": "desktop:frontend:build", + "type": "shell", + "command": "pnpm", + "args": ["frontend", "build"], + "group": "build" + } + ] +} diff --git a/Cargo.toml b/Cargo.toml index abb54614..e580ee32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "apps/testing", "apps/desktop", "packages/core", - "packages/macros" + "packages/macros", ] [workspace.package] @@ -22,6 +22,8 @@ version = "1.0.0-alpha.1" [workspace.dependencies] onelauncher = { path = "./packages/core" } +onelauncher_gui = { path = "./apps/desktop" } +onelauncher_test = { path = "./apps/testing" } onelauncher_macros = { path = "./packages/macros" } # tauri uses latest crates.io beta channel (temporarily can use v2 and main branches if necessary) @@ -120,6 +122,7 @@ tempfile = { version = "3.10" } dunce = { version = "1.0.4" } serde_json = { version = "1.0" } serde_ini = { version = "0.2" } +strum = { version = "0.23", features = [ "derive" ] } flate2 = { version = "1.0" } tar = { version = "0.4" } zip = { version = "0.6" } @@ -145,6 +148,7 @@ sys-info = { version = "0.9" } whoami = { version = "1.5" } cocoa = { version = "0.25" } objc = { version = "0.2" } +once_cell = { version = "1.19" } webbrowser = { version = "1.0" } url = { version = "2.5.2" } diff --git a/apps/desktop/Cargo.toml b/apps/desktop/Cargo.toml index 90b8970e..f90f59dd 100644 --- a/apps/desktop/Cargo.toml +++ b/apps/desktop/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "onelauncher_gui" description = "Next-generation open source Minecraft launcher" +default-run = "onelauncher_gui" version = "1.0.0-alpha.1" # todo: make it so tauri works with the ws license = { workspace = true } edition = { workspace = true } @@ -32,7 +33,8 @@ tauri = { workspace = true, features = [ "macos-private-api", "image-ico", "image-png", - "protocol-asset" + "protocol-asset", + "specta" ] } tauri-utils = { workspace = true } tauri-build = { workspace = true } @@ -59,13 +61,19 @@ thiserror = { workspace = true } anyhow = { workspace = true } chrono = { workspace = true } uuid = { workspace = true } +strum = { workspace = true } +once_cell = { workspace = true } tracing = { workspace = true } tracing-error = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] -cocoa.workspace = true -objc.workspace = true +cocoa = { workspace = true } +objc = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +# https://github.com/tauri-apps/tauri/blob/tauri-v2.0.0-beta.17/core/tauri/Cargo.toml#L86 +webkit2gtk = { version = "=2.0.1", features = [ "v2_38" ] } [features] default = [ "custom-protocol" ] diff --git a/apps/desktop/build.rs b/apps/desktop/build.rs index 99152bce..80c7d804 100644 --- a/apps/desktop/build.rs +++ b/apps/desktop/build.rs @@ -33,6 +33,11 @@ fn main() { "download_mod", // Other "get_program_info", + "reload_webview", + "set_menu_bar_item_state", + // Updater + "check_for_update", + "install_update" ]), ), ) diff --git a/apps/desktop/permissions/onelauncher/default.toml b/apps/desktop/permissions/onelauncher/default.toml index a8df8266..7507d43f 100644 --- a/apps/desktop/permissions/onelauncher/default.toml +++ b/apps/desktop/permissions/onelauncher/default.toml @@ -35,4 +35,10 @@ permissions = [ # Other "allow-get-program-info", + "allow-reload-webview", + "allow-set-menu-bar-item-state", + + # Updater + "allow-check-for-update", + "allow-install-update", ] diff --git a/apps/desktop/src/api/commands.rs b/apps/desktop/src/api/commands.rs index 1ea6fc4c..5520229c 100644 --- a/apps/desktop/src/api/commands.rs +++ b/apps/desktop/src/api/commands.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::str::FromStr; use interpulse::api::minecraft::Version; use onelauncher::constants::{NATIVE_ARCH, TARGET_OS, VERSION}; @@ -11,15 +12,14 @@ use specta::Type; use tauri::{AppHandle, Manager}; use uuid::Uuid; +// todo: use rspc for commands that don't actually require `tauri` as a dependency #[macro_export] macro_rules! collect_commands { () => {{ use $crate::api::commands::*; + use $crate::ext::updater::*; tauri_specta::ts::builder() - .config( - specta::ts::ExportConfig::default() - .bigint(specta::ts::BigIntExportBehavior::BigInt), - ) + .config(specta::ts::ExportConfig::default().bigint(specta::ts::BigIntExportBehavior::BigInt)) .commands(tauri_specta::collect_commands![ // User auth_login, @@ -49,6 +49,11 @@ macro_rules! collect_commands { download_mod, // Other get_program_info, + reload_webview, + set_menu_bar_item_state, + // Updater + check_for_update, + install_update, ]) }}; } @@ -66,9 +71,51 @@ pub struct CreateCluster { skip_watch: Option, } +#[tauri::command(async)] #[specta::specta] -#[tauri::command] -pub async fn create_cluster(props: CreateCluster) -> Result { +pub async fn set_menu_bar_item_state(window: tauri::Window, event: crate::ext::menu::MenuEvent, enabled: bool) { + let menu = window.menu().expect("failed to get menu for current window"); + crate::ext::menu::set_enabled(&menu, event, enabled); +} + +#[tauri::command(async)] +#[specta::specta] +pub async fn reload_webview(handle: tauri::AppHandle) { + handle + .get_webview_window("main") + .expect("failed to get window handle") + .with_webview(reload_webview_inner) + .expect("failed to reload webview"); +} + +pub fn reload_webview_inner(webview: tauri::webview::PlatformWebview) { + #[cfg(target_os = "macos")] + { + unsafe { + onelauncher_macos::reload_webview(&webview.inner().cast()); + } + } + + #[cfg(target_os = "linux")] + { + use webkit2gtk::WebViewExt; + webview.inner().reload(); + } + + #[cfg(target_os = "windows")] + unsafe { + webview + .controller() + .CoreWebView2() + .expect("failed to get inner webview handle") + .Reload() + .expect("failed to reload webview"); + } +} + +#[specta::specta] +#[tauri::command(async)] +pub async fn create_cluster(props: CreateCluster) -> crate::api::Result> { let path = cluster::create::create_cluster( props.name, props.mc_version, @@ -83,22 +130,22 @@ pub async fn create_cluster(props: CreateCluster) -> Result { .await?; if let Some(cluster) = cluster::get(&path, None).await? { - Ok(cluster.uuid) + Ok(Some(cluster.uuid)) } else { - Err("Cluster does not exist".to_string()) + Ok(None) } } #[specta::specta] -#[tauri::command] -pub async fn remove_cluster(uuid: Uuid) -> Result<(), String> { +#[tauri::command(async)] +pub async fn remove_cluster(uuid: Uuid) -> crate::api::Result<()> { let path = ClusterPath::find_by_uuid(uuid).await?; Ok(cluster::remove(&path).await?) } #[specta::specta] -#[tauri::command] -pub async fn run_cluster(uuid: Uuid) -> Result<(Uuid, u32), String> { +#[tauri::command(async)] +pub async fn run_cluster(uuid: Uuid) -> crate::api::Result<(Uuid, u32)> { let path = ClusterPath::find_by_uuid(uuid).await?; let c_lock = cluster::run(&path).await?; @@ -116,20 +163,20 @@ pub async fn run_cluster(uuid: Uuid) -> Result<(Uuid, u32), String> { } #[specta::specta] -#[tauri::command] -pub async fn get_running_clusters() -> Result, String> { +#[tauri::command(async)] +pub async fn get_running_clusters() -> crate::api::Result> { Ok(processor::get_running_clusters().await?) } #[specta::specta] -#[tauri::command] -pub async fn get_processes_by_path(path: ClusterPath) -> Result, String> { +#[tauri::command(async)] +pub async fn get_processes_by_path(path: ClusterPath) -> crate::api::Result> { Ok(processor::get_uuids_by_cluster_path(path).await?) } #[specta::specta] -#[tauri::command] -pub async fn kill_process(uuid: Uuid) -> Result<(), String> { +#[tauri::command(async)] +pub async fn kill_process(uuid: Uuid) -> crate::api::Result<()> { processor::kill_by_uuid(uuid).await?; Ok(()) } @@ -141,37 +188,34 @@ pub async fn kill_process(uuid: Uuid) -> Result<(), String> { // } #[specta::specta] -#[tauri::command] -pub async fn get_cluster(uuid: Uuid) -> Result { - match cluster::get_by_uuid(uuid, None).await? { - Some(cluster) => Ok(cluster), - None => Err("Cluster does not exist".into()), - } +#[tauri::command(async)] +pub async fn get_cluster(uuid: Uuid) -> crate::api::Result> { + Ok(cluster::get_by_uuid(uuid, None).await?) } #[specta::specta] -#[tauri::command] -pub async fn get_clusters() -> Result, String> { +#[tauri::command(async)] +pub async fn get_clusters() -> crate::api::Result> { Ok(cluster::list(None).await?) } #[specta::specta] -#[tauri::command] -pub async fn get_minecraft_versions() -> Result, String> { +#[tauri::command(async)] +pub async fn get_minecraft_versions() -> crate::api::Result> { Ok(onelauncher::api::metadata::get_minecraft_versions() .await? .versions) } #[specta::specta] -#[tauri::command] -pub async fn get_settings() -> Result { +#[tauri::command(async)] +pub async fn get_settings() -> crate::api::Result { Ok(settings::get().await?) } #[specta::specta] -#[tauri::command] -pub async fn set_settings(settings: Settings) -> Result<(), String> { +#[tauri::command(async)] +pub async fn set_settings(settings: Settings) -> crate::api::Result<()> { Ok(settings::set(settings).await?) } @@ -186,7 +230,7 @@ pub struct ProgramInfo { } #[specta::specta] -#[tauri::command] +#[tauri::command(async)] pub fn get_program_info() -> ProgramInfo { let webview_version = tauri::webview_version().unwrap_or("UNKNOWN".into()); let tauri_version = tauri::VERSION; @@ -203,20 +247,20 @@ pub fn get_program_info() -> ProgramInfo { } #[specta::specta] -#[tauri::command] -pub async fn get_users() -> Result, String> { +#[tauri::command(async)] +pub async fn get_users() -> crate::api::Result> { Ok(minecraft::users().await?) } #[specta::specta] -#[tauri::command] -pub async fn get_user(uuid: Uuid) -> Result { +#[tauri::command(async)] +pub async fn get_user(uuid: Uuid) -> crate::api::Result { Ok(minecraft::get_user(uuid).await?) } #[specta::specta] -#[tauri::command] -pub async fn get_default_user() -> Result, String> { +#[tauri::command(async)] +pub async fn get_default_user() -> crate::api::Result> { let uuid = minecraft::get_default_user().await?; match uuid { @@ -226,59 +270,44 @@ pub async fn get_default_user() -> Result, String> } #[specta::specta] -#[tauri::command] -pub async fn set_default_user(uuid: Uuid) -> Result<(), String> { +#[tauri::command(async)] +pub async fn set_default_user(uuid: Uuid) -> crate::api::Result<()> { minecraft::set_default_user(uuid).await?; Ok(()) } #[specta::specta] -#[tauri::command] -pub async fn auth_login(handle: AppHandle) -> Result, String> { +#[tauri::command(async)] +pub async fn auth_login(handle: AppHandle) -> crate::api::Result> { let flow = minecraft::begin().await?; - let now = chrono::Utc::now(); + let timestamp = chrono::Utc::now(); - if let Some(win) = handle.get_webview_window("login") { - win.close().map_err(|err| err.to_string())?; + if let Some(window) = handle.get_webview_window("signin") { + window.close()?; } - let win = tauri::WebviewWindowBuilder::new( - &handle, - "login", - tauri::WebviewUrl::External( - flow.redirect_uri - .parse() - .map_err(|_| anyhow::anyhow!("failed to parse auth redirect url")) - .map_err(|err| err.to_string())?, - ), - ) - .title("Log into OneLauncher") - .always_on_top(true) - .center() - .build() - .map_err(|err| err.to_string())?; - - win.request_user_attention(Some(tauri::UserAttentionType::Critical)) - .map_err(|err| err.to_string())?; - - while (chrono::Utc::now() - now) < chrono::Duration::minutes(10) { - if win.title().is_err() { + tracing::info!("init webview mod {}", flow.redirect_uri); + let url = tauri::Url::from_str(&flow.redirect_uri).unwrap(); + let url = tauri::WebviewUrl::External(url); + let window = tauri::WebviewWindowBuilder::new(&handle, "signin", url) + .title("Log into OneLauncher") + .always_on_top(true) + .center() + .build() + .unwrap(); + + tracing::info!("requests user attention"); + window.request_user_attention(Some(tauri::UserAttentionType::Critical))?; + + tracing::info!("beginning to check for updates"); + while (chrono::Utc::now() - timestamp) < chrono::Duration::minutes(10) { + if window.title().is_err() { return Ok(None); } - if win - .url() - .map_err(|err| err.to_string())? - .as_str() - .starts_with("https://login.live.com/oauth20_desktop.srf") - { - if let Some((_, code)) = win - .url() - .map_err(|err| err.to_string())? - .query_pairs() - .find(|x| x.0 == "code") - { - win.close().map_err(|err| err.to_string())?; + if window.url()?.as_str().starts_with("https://login.live.com/oauth20_desktop.srf") { + if let Some((_, code)) = window.url()?.query_pairs().find(|x| x.0 == "code") { + window.close()?; let value = minecraft::finish(&code.clone(), flow).await?; return Ok(Some(value)); @@ -288,37 +317,37 @@ pub async fn auth_login(handle: AppHandle) -> Result Result<(), String> { +#[tauri::command(async)] +pub async fn remove_user(uuid: Uuid) -> crate::api::Result<()> { Ok(minecraft::remove_user(uuid).await?) } #[specta::specta] -#[tauri::command] -pub async fn random_mods() -> Result, String> { +#[tauri::command(async)] +pub async fn random_mods() -> crate::api::Result> { let provider = content::Providers::Modrinth; Ok(provider.list().await?) } #[specta::specta] -#[tauri::command] -pub async fn get_mod(project_id: String) -> Result { +#[tauri::command(async)] +pub async fn get_mod(project_id: String) -> crate::api::Result { let provider = content::Providers::Modrinth; Ok(provider.get(&project_id).await?) } #[specta::specta] -#[tauri::command] -pub async fn download_mod(cluster_id: Uuid, version_id: String) -> Result<(), String> { +#[tauri::command(async)] +pub async fn download_mod(cluster_id: Uuid, version_id: String) -> crate::api::Result<()> { let cluster = cluster::get_by_uuid(cluster_id, None) .await? - .ok_or("cluster not found")?; + .ok_or(anyhow::anyhow!("cluster not found").into())?; let provider = content::Providers::Modrinth; let game_version = cluster.meta.mc_version.clone(); @@ -327,7 +356,7 @@ pub async fn download_mod(cluster_id: Uuid, version_id: String) -> Result<(), St .await? .files .first() - .ok_or("no files found")? + .ok_or(anyhow::anyhow!("no files found").into())? .download_to_cluster(&cluster) .await?; diff --git a/apps/desktop/src/api/mod.rs b/apps/desktop/src/api/mod.rs index 878322b6..6a451f29 100644 --- a/apps/desktop/src/api/mod.rs +++ b/apps/desktop/src/api/mod.rs @@ -36,6 +36,7 @@ impl From for OneLauncherSerializableError { } } +// TODO: i really need to fix this lmao #[derive(thiserror::Error, Debug)] pub enum OneLauncherError { #[error("{0}")] @@ -47,6 +48,9 @@ pub enum OneLauncherError { #[error("failed to handle tauri management: {0}")] Tauri(#[from] tauri::Error), + #[error("anyhow error: {0}")] + AnyhowError(#[from] anyhow::Error), + #[cfg(target_os = "macos")] #[error("failed to handle callback: {0}")] Callback(String), @@ -61,7 +65,7 @@ macro_rules! impl_serialize_err { where S: Serializer, { - use serde::ser::SerializeStruct; + use serde::ser::SerializeStruct; match self { OneLauncherError::OneLauncher(onelauncher_error) => { $crate::error::display_tracing_error(onelauncher_error); diff --git a/apps/desktop/src/ext/macos.rs b/apps/desktop/src/ext/macos.rs new file mode 100644 index 00000000..e69de29b diff --git a/apps/desktop/src/ext/menu.rs b/apps/desktop/src/ext/menu.rs new file mode 100644 index 00000000..e6cc8b35 --- /dev/null +++ b/apps/desktop/src/ext/menu.rs @@ -0,0 +1,166 @@ +use tauri::menu::{Menu, MenuItemKind}; +use tauri::{Manager, Wry}; +use serde::Deserialize; +use std::str::FromStr; + +#[derive(Debug, Clone, Copy, specta::Type, Deserialize, strum::EnumString, strum::AsRefStr, strum::Display)] +pub enum MenuEvent { + NewCluster, + AddMod, + OpenMods, + OpenScreenshots, + OpenSettings, + ReloadWebview, + ToggleDeveloperTools, +} + +const CLUSTER_LOCKED_MENU_IDS: &[MenuEvent] = &[ + MenuEvent::AddMod, + MenuEvent::OpenMods, + MenuEvent::OpenScreenshots, +]; + +pub fn setup_menu(handle: &tauri::AppHandle) -> tauri::Result> { + handle.on_menu_event(move |handle, event| { + if let Ok(event) = MenuEvent::from_str(&event.id().0) { + handle_menu_event(event, handle); + } else { + println!("unknown menuevent: {}", event.id().0); + } + }); + + #[cfg(not(target_os = "macos"))] + { + Menu::new(handle) + } + + #[cfg(target_os = "macos")] + { + use tauri::menu::{AboutMetadataBuilder, MenuBuilder, MenuItemBuilder, SubmenuBuilder}; + + let app_menu = SubmenuBuilder::new(handle, "OneLauncher") + .about(Some( + AboutMetadataBuilder::new() + .authors(Some(vec!["Polyfrost".to_string()])) + .license(Some(onelauncher::constants::VERSION)) + .version(Some(onelauncher::constants::VERSION)) + .website(Some("https://polyfrost.org/")) + .website_label(Some("Polyfrost.org")) + .build(), + )) + .separator() + .item( + &MenuItemBuilder::with_id(MenuEvent::NewCluster, "New Cluster") + .accelerator("Cmd+Shift+C") + .build(handle)?, + ) + .separator() + .hide() + .hide_others() + .show_all() + .separator() + .quit() + .build()?; + + let view_menu = SubmenuBuilder::new(handle, "View") + .item( + &MenuItemBuilder::with_id(MenuEvent::OpenMods, "Mods") + .accelerator("CmdOrCtrl+M") + .build(handle)?, + ) + .item( + &MenuItemBuilder::with_id(MenuEvent::OpenScreenshots, "Screenshots") + .accelerator("CmdOrCtrl+S") + .build(handle)?, + ) + .item( + &MenuItemBuilder::with_id(MenuEvent::OpenSettings, "Settings") + .accelerator("CmdOrCtrl+Comma") + .build(handle)?, + ); + + #[cfg(debug_assertions)] + let view_menu = view_menu.separator().item( + &MenuItemBuilder::with_id(MenuEvent::ToggleDeveloperTools, "Toggle Developer Tools") + .accelerator("CmdOrCtrl+Shift+Alt+I") + .build(handle)?, + ); + + let view_menu = view_menu.build()?; + + let window_menu = SubmenuBuilder::new(handle, "Window") + .minimize() + .close_window() + .fullscreen() + .item( + &MenuItemBuilder::with_id(MenuEvent::ReloadWebview, "Reload Webview") + .accelerator("CmdOrCtrl+Shift+R") + .build(handle)?, + ) + .build()?; + + let menu = MenuBuilder::new(handle) + .item(&app_menu) + .item(&view_menu) + .item(&window_menu) + .build()?; + + for event in CLUSTER_LOCKED_MENU_IDS { + set_enabled(&menu, *event, false); + } + + Ok(menu) + } +} + +pub fn handle_menu_event(event: MenuEvent, handle: &tauri::AppHandle) { + let webview = handle.get_webview_window("main").expect("failed to find window"); + + // todo: use tauri specta instead of this + match event { + MenuEvent::NewCluster => webview.emit("keybind", "new_cluster").unwrap(), + MenuEvent::AddMod => webview.emit("keybind", "add_mod").unwrap(), + MenuEvent::OpenMods => webview.emit("keybind", "open_mods").unwrap(), + MenuEvent::OpenScreenshots => webview.emit("keybind", "open_screenshots").unwrap(), + MenuEvent::OpenSettings => webview.emit("keybind", "open_settings").unwrap(), + MenuEvent::ReloadWebview => webview.with_webview(crate::api::commands::reload_webview_inner).expect("failed to reload webview"), + MenuEvent::ToggleDeveloperTools => { + #[cfg(feature = "devtools")] + if webview.is_devtool_open() { + webview.close_devtools(); + } else { + webview.open_devtools(); + } + }, + } +} + +pub fn refresh_menu_bar(handle: &tauri::AppHandle, enabled: bool) { + let menu = handle + .get_webview_window("main") + .expect("unable to find window") + .menu() + .expect("unable to get menu"); + + for event in CLUSTER_LOCKED_MENU_IDS { + set_enabled(&menu, *event, enabled); + } +} + +pub fn set_enabled(menu: &Menu, event: MenuEvent, enabled: bool) { + let result = match menu.get(event.as_ref()) { + Some(MenuItemKind::MenuItem(i)) => i.set_enabled(enabled), + Some(MenuItemKind::Submenu(i)) => i.set_enabled(enabled), + Some(MenuItemKind::Predefined(_)) => return, + Some(MenuItemKind::Check(i)) => i.set_enabled(enabled), + Some(MenuItemKind::Icon(i)) => i.set_enabled(enabled), + None => { + tracing::error!("failed to get menu item: {event:?}"); + return; + } + }; + + if let Err(e) = result { + tracing::error!("failed to set menu item state: {e:#?}"); + } +} diff --git a/apps/desktop/src/ext/mod.rs b/apps/desktop/src/ext/mod.rs index a880d9de..7871e227 100644 --- a/apps/desktop/src/ext/mod.rs +++ b/apps/desktop/src/ext/mod.rs @@ -1 +1,2 @@ pub mod updater; +pub mod menu; diff --git a/apps/desktop/src/ext/updater.rs b/apps/desktop/src/ext/updater.rs index 9acd2dac..5e2cb845 100644 --- a/apps/desktop/src/ext/updater.rs +++ b/apps/desktop/src/ext/updater.rs @@ -3,7 +3,7 @@ use tauri::{Manager, Runtime}; use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt}; use tokio::sync::Mutex; -#[derive(Debug, Clone, serde::Serialize)] +#[derive(Debug, Clone, specta::Type, serde::Serialize)] pub struct Update { pub version: String, } @@ -17,7 +17,7 @@ impl Update { } #[derive(Default)] -pub struct State { +pub struct UpdaterState { install_lock: Mutex<()>, } @@ -43,6 +43,7 @@ pub enum UpdateEvent { } #[tauri::command] +#[specta::specta] pub async fn check_for_update(app: tauri::AppHandle) -> Result, String> { app.emit("updater", UpdateEvent::Loading).ok(); @@ -54,7 +55,7 @@ pub async fn check_for_update(app: tauri::AppHandle) -> Result, S } }; - let update = update.map(|update| Update::new(&update)); + let update = update.map(|u| Update::new(&u)); app.emit( "updater", @@ -70,9 +71,10 @@ pub async fn check_for_update(app: tauri::AppHandle) -> Result, S } #[tauri::command] +#[specta::specta] pub async fn install_update( app: tauri::AppHandle, - state: tauri::State<'_, State>, + state: tauri::State<'_, UpdaterState>, ) -> Result<(), String> { let lock = match state.install_lock.try_lock() { Ok(lock) => lock, diff --git a/apps/desktop/src/lib.rs b/apps/desktop/src/lib.rs index 5aae301b..d622fc34 100644 --- a/apps/desktop/src/lib.rs +++ b/apps/desktop/src/lib.rs @@ -37,6 +37,7 @@ pub async fn run() { pub async fn run_app(setup: F) { let builder = tauri::Builder::default() + .menu(crate::ext::menu::setup_menu) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { println!("{}, {argv:?}, {cwd}", app.package_info().name); @@ -46,9 +47,9 @@ pub async fn run_app(setup: F) { .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(ext::updater::plugin()) - .manage(ext::updater::State::default()) + .manage(ext::updater::UpdaterState::default()) + // todo: bring back tauri plugin window state (its buggy right now) // .plugin(tauri_plugin_window_state::Builder::default().build()) - .menu(tauri::menu::Menu::new) .setup(move |app| { setup(app); Ok(()) @@ -71,7 +72,7 @@ pub async fn run_app(setup: F) { tracing::error!("{err}"); }; - app.run(|_app_handle, _event| {}) + app.run(|_, _| {}); } fn setup(app: &mut tauri::App) -> Result<(), Box> { diff --git a/apps/desktop/tauri.conf.json b/apps/desktop/tauri.conf.json index ecbe241f..5c9e5c87 100644 --- a/apps/desktop/tauri.conf.json +++ b/apps/desktop/tauri.conf.json @@ -24,11 +24,10 @@ "enable": true, "scope": { "allow": [ - "$APPDATA/caches/icons/*", - "$APPCONFIG/caches/icons/*", - "$CONFIG/caches/icons/*", - "$APPDATA/db/**", - "$RESOURCE/**" + "$APPDATA/metadata/caches/icons/*", + "$APPCONFIG/metadata/caches/icons/*", + "$CONFIG/metadata/caches/icons/*", + "$APPDATA/db/**" ], "deny": ["$APPDATA/db/*.stronghold"] } @@ -72,17 +71,18 @@ "../../packages/distribution/icons/icon.ico" ], "copyright": "Polyfrost", - "longDescription": "", + "publisher": "Polyfrost", + "longDescription": "Next-generation open source Minecraft launcher", "resources": [], "externalBin": [], - "publisher": "Polyfrost", "linux": { "deb": { "depends": [], "desktopTemplate": "../../packages/distribution/linux/org.polyfrost.launcher.desktop.template" } }, - "shortDescription": "Next-generation open source Minecraft launcher", + "createUpdaterArtifacts": false, + "shortDescription": "OneLauncher", "macOS": { "entitlements": "App.entitlements", "exceptionDomain": null, @@ -106,7 +106,10 @@ "active": true, "dialog": false, "pubkey": "", - "endpoints": ["https://polyfrost.org/releases/update/{{version}}/{{target}}/{{arch}}"] + "endpoints": ["https://polyfrost.org/releases/tauri/{{version}}/{{target}}/{{arch}}"] + }, + "deep-link": { + "desktop": ["onelauncher"] } } } diff --git a/apps/frontend/src/ui/components/content/ModCard.tsx b/apps/frontend/src/ui/components/content/ModCard.tsx index 06cc1e2a..a165d347 100644 --- a/apps/frontend/src/ui/components/content/ModCard.tsx +++ b/apps/frontend/src/ui/components/content/ModCard.tsx @@ -8,6 +8,8 @@ import type { ManagedPackage } from '~bindings'; export enum Provider { Curseforge, Modrinth, + FTBLegacy, + // Technic Polyfrost, Skyclient, } @@ -34,7 +36,7 @@ function ModCard(props: ManagedPackage) {

By {' '} - Author TODO + Author {' '} on {' '} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 3eb6d94c..c1539f68 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -29,6 +29,7 @@ onelauncher_macros = { workspace = true } tauri = { workspace = true, optional = true } tauri-specta = { workspace = true, optional = true } specta = { workspace = true, optional = true } +rspc = { workspace = true, features = [ "tracing" ] } # CLI-only deps indicatif = { workspace = true, optional = true } diff --git a/packages/core/src/store/settings.rs b/packages/core/src/store/settings.rs index 1af9859a..0c72c6da 100644 --- a/packages/core/src/store/settings.rs +++ b/packages/core/src/store/settings.rs @@ -92,7 +92,7 @@ impl Settings { } else { let settings = Self { theme: Theme::Dark, - hide_close_prompt: false, + hide_close_prompt: true, disable_animations: false, force_fullscreen: false, resolution: Resolution::default(), diff --git a/packages/distribution/rpm/.gitkeep b/packages/distribution/rpm/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/distribution/snapcraft/.gitkeep b/packages/distribution/snapcraft/.gitkeep new file mode 100644 index 00000000..e69de29b