From 8f7981d9b1384c24b99f5327bd416c309cb5f39f Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 11:34:34 +0800 Subject: [PATCH 01/12] fix: tauri os plugin not being fully loaded --- apps/oneclient/desktop/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/oneclient/desktop/src/lib.rs b/apps/oneclient/desktop/src/lib.rs index 5881c0cf..e787a48f 100644 --- a/apps/oneclient/desktop/src/lib.rs +++ b/apps/oneclient/desktop/src/lib.rs @@ -75,6 +75,7 @@ async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_deep_link::init()) + .plugin(tauri_plugin_os::init()) .menu(tauri::menu::Menu::default) .invoke_handler(router.into_handler()) .setup(move |app| { From 21bb41d8834a135b2d9de9e206282e9dacf8f246 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 13:23:42 +0800 Subject: [PATCH 02/12] feat: debug keybind Bound to `CTRL+SHIFT+D` (and `ALT+F12`) and copies the debug info to the clipboard --- Cargo.lock | 40 ++ Cargo.toml | 1 + apps/oneclient/desktop/Cargo.toml | 1 + .../desktop/capabilities/default.json | 9 +- apps/oneclient/desktop/src/lib.rs | 5 + apps/oneclient/frontend/package.json | 1 + apps/oneclient/frontend/src/bindings.gen.ts | 401 ------------------ .../src/components/overlay/DebugInfo.tsx | 87 +--- apps/oneclient/frontend/src/routes/__root.tsx | 21 +- packages/core/src/api/tauri/debug.rs | 102 ++++- pnpm-lock.yaml | 61 +-- pnpm-workspace.yaml | 1 + 12 files changed, 201 insertions(+), 529 deletions(-) delete mode 100644 apps/oneclient/frontend/src/bindings.gen.ts diff --git a/Cargo.lock b/Cargo.lock index 5a61121f..dd904a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2173,6 +2173,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "global-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.3", + "objc2-app-kit", + "once_cell", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -3744,6 +3762,7 @@ dependencies = [ "tauri-plugin-deep-link", "tauri-plugin-dialog", "tauri-plugin-fs", + "tauri-plugin-global-shortcut", "tauri-plugin-os", "tauri-plugin-single-instance", "tauri-plugin-updater", @@ -6508,6 +6527,21 @@ dependencies = [ "uuid 1.16.0", ] +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31919f3c07bcb585afef217c0c33cde80da9ebccf5b8e2c90e0e0a535b14ab47" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + [[package]] name = "tauri-plugin-opener" version = "2.3.0" @@ -8448,6 +8482,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 05859d53..e4bebe86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ tauri-plugin-deep-link = { version = "=2.2.1" } tauri-plugin-dialog = { version = "=2.2.1" } tauri-plugin-fs = { version = "=2.2.1", features = ["watch"] } tauri-plugin-os = { version = "=2.2.1" } +tauri-plugin-global-shortcut = { version = "=2.2.1" } tauri-plugin-single-instance = { version = "=2.2.3" } tauri-plugin-updater = { version = "=2.7.1" } tauri-plugin-window-state = { version = "=2.2.2" } diff --git a/apps/oneclient/desktop/Cargo.toml b/apps/oneclient/desktop/Cargo.toml index 079bfb4e..cfcff190 100644 --- a/apps/oneclient/desktop/Cargo.toml +++ b/apps/oneclient/desktop/Cargo.toml @@ -38,6 +38,7 @@ tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-fs = { workspace = true } tauri-plugin-os = { workspace = true } +tauri-plugin-global-shortcut = { workspace = true } tauri-plugin-deep-link = { workspace = true } # code gen diff --git a/apps/oneclient/desktop/capabilities/default.json b/apps/oneclient/desktop/capabilities/default.json index b72d4d5c..a6abfba1 100644 --- a/apps/oneclient/desktop/capabilities/default.json +++ b/apps/oneclient/desktop/capabilities/default.json @@ -37,7 +37,8 @@ "fs:allow-download-write", "fs:allow-data-read-recursive", "fs:allow-data-write-recursive", - "fs:allow-watch", + "fs:allow-watch", + "os:deny-hostname", "os:allow-locale", "os:allow-os-type", @@ -45,6 +46,10 @@ "os:allow-version", "os:allow-arch", "os:deny-exe-extension", - "os:allow-family" + "os:allow-family", + + "global-shortcut:allow-is-registered", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister" ] } diff --git a/apps/oneclient/desktop/src/lib.rs b/apps/oneclient/desktop/src/lib.rs index e787a48f..b4d1333c 100644 --- a/apps/oneclient/desktop/src/lib.rs +++ b/apps/oneclient/desktop/src/lib.rs @@ -81,6 +81,11 @@ async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult .setup(move |app| { app.manage(ext::updater::UpdaterState::default()); setup_window(app.handle()).expect("failed to setup main window"); + + #[cfg(desktop)] + app.handle() + .plugin(tauri_plugin_global_shortcut::Builder::new().build()); + Ok(()) }); diff --git a/apps/oneclient/frontend/package.json b/apps/oneclient/frontend/package.json index 6bd7972f..f1237b57 100644 --- a/apps/oneclient/frontend/package.json +++ b/apps/oneclient/frontend/package.json @@ -29,6 +29,7 @@ "@tauri-apps/plugin-clipboard-manager": "catalog:", "@tauri-apps/plugin-dialog": "catalog:", "@tauri-apps/plugin-fs": "catalog:", + "@tauri-apps/plugin-global-shortcut": "catalog:", "@untitled-theme/icons-react": "catalog:", "fuse.js": "^7.1.0", "motion": "catalog:", diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts deleted file mode 100644 index cafcd331..00000000 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ /dev/null @@ -1,401 +0,0 @@ -// @ts-nocheck -// This file has been generated by Specta. DO NOT EDIT. - -import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc' -type TAURI_CHANNEL = (response: T) => void - - -export type ApplyBundleUpdatesResult = { updates_applied: BundlePackageUpdate[]; removals_applied: BundlePackageRemoval[]; additions_applied: BundlePackageAddition[]; updates_failed: string[]; removals_failed: string[]; additions_failed: string[] } - -export type BundlePackageAddition = { cluster_id: number; bundle_name: string; new_file: ModpackFile } - -export type BundlePackageRemoval = { cluster_id: number; package_hash: string; package_id: string; bundle_name: string; installed_at: string } - -export type BundlePackageUpdate = { cluster_id: number; installed_package_hash: string; installed_version_id: string; bundle_name: string; new_version_id: string; new_file: ModpackFile; installed_at: string } - -export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MismatchedVersion"; data: string } | { type: "MismatchedLoader"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string } - -export type ClusterModel = { id: number; folder_name: string; stage: ClusterStage; created_at: string; group_id: number | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: number | null; icon_url: Icon | null; setting_profile_name: string | null; linked_modpack_hash: string | null } - -export type ClusterStage = "notready" | "downloading" | "repairing" | "ready" - -export type ClusterUpdate = { name: string | null; icon_url: Icon | null; setting_profile_name: string | null } - -export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null } - -export type CryptoError = { type: "InvalidHash"; data: string } | { type: "InvalidAlgorithm"; data: string } - -export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExists"; data: string } | { type: "InvalidValue"; data: string } - -export type DbVec = T[] - -export type DirectoryError = { type: "BaseDir"; data: string } - -export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string } - -export type ExternalPackage = { name: string; url: string; sha1: string; size: number; package_type: PackageType } - -export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: PackageCategories | null; package_type: PackageType | null } - -export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric" - -export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "FileNotFoundInZip"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string } | { type: "TempFileError"; data: string } - -export type Icon = string - -export type IncompatiblePackageType = { type: "McVersion"; data: string } | { type: "Loader"; data: string } - -export type IngressError = { type: "NotFound"; data: string } - -export type IngressPayload = { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number } - -export type IngressType = { Download: { file_name: string } } | "JavaPrepare" | "JavaCheck" | "JavaLocate" | "MinecraftDownload" | { PrepareCluster: { cluster_name: string } } - -export type JavaError = { type: "ParseVersion"; data: string } | { type: "Execute"; data: string } | { type: "MissingJava"; data: string } - -export type LauncherError = { type: "DirError"; data: DirectoryError } | { type: "IOError"; data: IOError } | { type: "IngressError"; data: IngressError } | { type: "JavaError"; data: JavaError } | { type: "CryptoError"; data: CryptoError } | { type: "DiscordError"; data: DiscordError } | { type: "MetadataError"; data: MetadataError } | { type: "ClusterError"; data: ClusterError } | { type: "MinecraftAuthError"; data: MinecraftAuthError } | { type: "ProcessError"; data: ProcessError } | { type: "PackageError"; data: PackageError } | { type: "DaoError"; data: DaoError } | { type: "SerdeError"; data: string } | { type: "AnyhowError"; data: string } | { type: "DbError"; data: string } | { type: "ReqwestError"; data: string } | { type: "InterpulseError"; data: string } | { type: "RegexError"; data: string } | { type: "SemaphoreError"; data: string } | { type: "UrlError"; data: string } | { type: "OpenerError"; data: string } | { type: "TauriError"; data: string } - -export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: ManagedPackageBody; -/** - * Won't have all versions for some providers, like `CurseForge`. - * Try making a request to get the versions if needed - */ -version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: PackageCategories; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: number; gallery: PackageGallery[] } - -export type ManagedPackageBody = { Url: string } | { Raw: string } - -export type ManagedUser = { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null } - -export type ManagedVersion = { version_id: string; project_id: string; display_name: string; display_version: string; changelog: string | null; dependencies: ManagedVersionDependency[]; mc_versions: string[]; release_type: PackageReleaseType; loaders: GameLoader[]; published: string; downloads: number; files: ManagedVersionFile[] } - -export type ManagedVersionDependency = { version_id: string | null; project_id: string | null; file_name: string | null; dependency_type: PackageDependencyType } - -export type ManagedVersionFile = { sha1: string; url: string; file_name: string; primary: boolean; size: number } - -export type MessageLevel = "Info" | "Warn" | "Error" - -export type MessagePayload = { level: MessageLevel; message: string } - -export type MetadataError = { type: "FetchError"; data: string } | { type: "NotModdedManifest"; data: string } | { type: "NotVanillaManifest"; data: string } | { type: "ParseError"; data: string } | { type: "NoMatchingLoader"; data: string } | { type: "NoMatchingVersion"; data: string } - -export type MinecraftAuthError = { type: "PublicKeyReading"; data: string } | { type: "PKCS8Error"; data: string } | { type: "SerializeError"; data: string } | { type: "DeserializeError"; data: string } | { type: "RequestError"; data: string } | { type: "SigningError"; data: string } | { type: "HashError"; data: string } | { type: "SessionIdError"; data: string } | { type: "XboxError"; data: string } - -/** - * A structure of all needed Minecraft credentials for logging in and account management. - */ -export type MinecraftCredentials = { -/** - * The uuid of the credentials. - */ -id: string; -/** - * The username of the Minecraft account. - */ -username: string; -/** - * The access token as a String. - */ -access_token: string; -/** - * The refresh token as a string for [`MinecraftState#refresh`]. - */ -refresh_token: string; -/** - * The time that the access token expires as a [`DateTime`]. - */ -expires: string } - -export type ModpackArchive = { manifest: ModpackManifest; path: string; format: ModpackFormat } - -export type ModpackFile = { enabled: boolean; hidden?: boolean; kind: ModpackFileKind; overrides?: PackageOverrides | null } - -export type ModpackFileKind = { Managed: [ManagedPackage, ManagedVersion] } | { External: ExternalPackage } - -export type ModpackFormat = "CurseForge" | "MrPack" | "PolyMrPack" - -export type ModpackManifest = { name: string; version: string; loader: GameLoader; loader_version: string; mc_version: string; enabled?: boolean; files: ModpackFile[] } - -export type MojangCape = { id: string; state: string; url: string; alias: string } - -export type MojangFullPlayerProfile = { id: string; username: string; skins: MojangSkin[]; capes: MojangCape[] } - -export type MojangPlayerProfile = { uuid: string; username: string; is_slim: boolean; skin_url: string | null; cape_url: string | null } - -export type MojangSkin = { id: string; state: string; url: string; variant: SkinVariant } - -export type MowojangProfile = { id: string; username: string } - -export type OnlineCluster = { major_version: number; name: string; art: string; entries: OnlineClusterEntry[] } - -export type OnlineClusterEntry = { minor_version: number; name?: string | null; art?: string | null; loader: GameLoader; tags: string[] } - -/** - * e.g. - * ```json - * { - * "clusters": [ - * { - * "major_version": 21, - * "name": "Tricky Trials", - * "art": "/versions/art/Tricky_Trials.png", - * "entries": [ - * { - * "minor_version": 5, - * "loader": "fabric", - * "tags": ["PvP", "Survival"] - * }, - * { - * "minor_version": 5, - * "loader": "forge", - * "tags": ["PvP", "Survival"] - * } - * ] - * }, - * { - * "major_version": 20, - * "name": "Trails & Tales", - * "art": "/versions/art/Trails_Tales.png", - * "entries": [ - * { - * "minor_version": 5, - * "loader": "fabric", - * "tags": ["PvP", "Survival"] - * } - * ] - * } - * ] - * } - * ``` - */ -export type OnlineClusterManifest = { clusters: OnlineCluster[] } - -export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] } - -export type PackageCategories = { Mod: PackageModCategory[] } | { ResourcePack: PackageResourcePackCategory[] } | { Shader: PackageShaderCategory[] } | { DataPack: PackageModCategory[] } | { ModPack: PackageModPackCategory[] } - -export type PackageDependencyType = "required" | "optional" | "embedded" | "incompatible" - -/** - * - */ -export type PackageDonationPlatform = "patreon" | "buymeacoffee" | "paypal" | "github" | "kofi" | "other" - -export type PackageDonationUrl = { id: PackageDonationPlatform; url: string } - -export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsNotModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType } | { type: "MissingApiKey"; data: string } | { type: "UnsupportedBodyType"; data: string } | { type: "UnsupportedAuthorType"; data: string } | { type: "UnsupportedModpackFormat"; data: string } - -export type PackageGallery = { url: string; thumbnail_url: string; title: string | null; description: string | null; featured: boolean | null } - -/** - * - */ -export type PackageLicense = { id: string; name: string; url: string | null } - -/** - * - */ -export type PackageLinks = { website: string | null; issues: string | null; source: string | null; wiki: string | null; donation: PackageDonationUrl[] | null; discord: string | null } - -export type PackageModCategory = "Adventure" | "Library" | "Equipment" | "Patches" | "Cosmetic" | "Food" | "Magic" | "Information" | "Misc" | "Performance" | "Redstone" | "ServerUtil" | "Storage" | "Technology" | "Farming" | "Automation" | "Transport" | "Utility" | "QoL" | "WorldGen" | "Mobs" | "Economy" | "Social" - -export type PackageModPackCategory = "Technology" | "Quests" | "Optimization" | "Multiplayer" | "Magic" | "LightWeight" | "Combat" | "Challenging" | "Adventure" - -export type PackageModel = { hash: string; file_name: string; version_id: string; published_at: string; display_name: string; display_version: string; package_type: PackageType; provider: Provider; package_id: string; mc_versions: DbVec; mc_loader: DbVec; icon: Icon | null } - -export type PackageOverrides = { icon: string | null; name: string | null; authors: string[] | null; description: string | null } - -export type PackageReleaseType = "release" | "beta" | "alpha" - -export type PackageResourcePackCategory = "X8" | "X16" | "X32" | "X48" | "X64" | "X128" | "X256" | "X512" | "VanillaLike" | "Utility" | "Tweaks" | "Themed" | "Simplistic" | "Realistic" | "Modded" | "Decoration" | "Cursed" | "Combat" | "Audio" | "Blocks" | "CoreShaders" | "Gui" | "Fonts" | "Equipment" | "Environment" | "Entities" | "Items" | "Locale" | "Models" - -export type PackageShaderCategory = "VanillaLike" | "SemiRealistic" | "Realistic" | "Fantasy" | "Cursed" | "Cartoon" | "Bloom" | "Atmosphere" | "Reflections" | "Shadows" | "PBR" | "PathTracing" | "Foliage" | "ColoredLightning" | "Potato" | "Low" | "Medium" | "High" | "Ultra" - -export type PackageSide = "unknown" | "required" | "optional" | "unsupported" - -export type PackageStatus = "active" | "abandoned" - -export type PackageType = "mod" | "resourcepack" | "shader" | "datapack" | "modpack" - -export type Paginated = { total: number; offset: number; limit: number; items: T[] } - -export type Process = { pid: number; started_at: string; cluster_id: number; post_hook: string | null; account_id: string } - -export type ProcessError = { type: "HookUnsuccessful"; data: string } | { type: "NoPid"; data: string } - -export type ProcessLogTail = { content: string; truncated: boolean } - -export type ProcessPayload = { cluster_id: number; kind: ProcessPayloadKind } - -export type ProcessPayloadKind = { type: "Starting"; command: string } | { type: "Started"; pid: number; started_at: string; post_hook: string | null; account_id: string } | { type: "Stopped"; pid: number; exit_code: number } | { type: "Output"; pid: number; output: string } - -export type ProfileUpdate = { res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null } - -export type Provider = "Modrinth" | "CurseForge" | "SkyClient" | "Local" - -export type Resolution = { width: number; height: number } - -export type SearchQuery = { query: string | null; offset: number | null; limit: number | null; sort: Sort | null; filters: Filters | null } - -export type SearchResult = { project_id: string; package_type: PackageType; slug: string; author: string; title: string; description: string; categories: PackageCategories; loaders: GameLoader[]; mc_versions: string[]; downloads: number; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string | null; client_side: PackageSide; server_side: PackageSide; -/** - * List of URLs to images - */ -gallery: string[] } - -export type SettingProfileModel = { name: string; java_id: number | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } - -export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; show_tanstack_dev_tools: boolean } - -export type SettingsOsExtra = Record - -export type SkinVariant = "classic" | "slim" - -export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" - -export type Update = { version: string } - -/** - * A game version of Minecraft - */ -export type Version = { -/** - * A unique identifier of the version - */ -id: string; -/** - * The release type of the version - */ -type: VersionType; -/** - * A link to additional information about the version - */ -url: string; -/** - * The latest time a file in this version was updated - */ -time: string; -/** - * The time this version was released - */ -releaseTime: string; -/** - * The SHA1 hash of the additional information about the version - */ -sha1: string; -/** - * Whether the version supports the latest player safety features - */ -complianceLevel: number; -/** - * The SHA1 hash of the original unmodified Minecraft version's JSON - * This is only available when using the Pulseflow mirror - */ -originalSha1?: string | null } - -/** - * The version type of Minecraft - */ -export type VersionType = -/** - * A major version, which is stable for all players to use - */ -"release" | -/** - * An experimental version, which is unstable and used for feature previews and beta testing - */ -"snapshot" | -/** - * The oldest versions before the game was released - */ -"old_alpha" | -/** - * Early versions of the game - */ -"old_beta" - -const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"extractBundleOverrides":["bundle_path","cluster_id"],"getVersions":[],"isBundleSyncing":[],"cacheArt":["path"],"checkForUpdate":[],"updateBundlePackages":["cluster_id"],"refreshArt":["path"],"getClustersGroupedByMajor":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"installUpdate":[]}', 'core':'{"getLogs":["id"],"getProfileOrDefault":["name"],"getClusters":[],"installModpack":["modpack","cluster_id"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"refreshAccounts":[],"updateClusterById":["id","request"],"launchCluster":["id","uuid","search_for_java"],"getGlobalProfile":[],"createCluster":["options"],"searchPackages":["provider","query"],"removeUser":["uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getDefaultUser":["fallback"],"removeCape":["access_token"],"removePackage":["cluster_id","package_hash"],"removeCluster":["id"],"getMultiplePackages":["provider","slugs"],"syncCluster":["cluster_id"],"getLinkedPackages":["cluster_id"],"fetchMinecraftProfile":["uuid"],"refreshAccount":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"isClusterRunning":["cluster_id"],"setClusterStage":["id","stage"],"killProcess":["pid"],"getLogByName":["id","name"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"getScreenshots":["id"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"openMsaLogin":[],"convertUsernameUUID":["username_uuid"],"getClusterById":["id"],"getRunningProcesses":[],"getLoadersForVersion":["mc_version"],"getUsers":[],"createSettingsProfile":["name"],"getUser":["uuid"],"readSettings":[],"setDefaultUser":["uuid"],"getPackageBody":["provider","body"],"updateClusterProfile":["name","profile"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getWorlds":["id"],"getProcessLogTail":["id","max_lines"],"changeCape":["access_token","cape_uuid"],"getGameVersions":[],"setDiscordRPCMessage":["message"],"open":["input"],"getPackage":["provider","slug"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'debug':'{"getGitCommitHash":[],"getPackageVersion":[],"getFamily":[],"getOsVersion":[],"openDevTools":[],"getArch":[],"getBuildTimestamp":[],"getType":[],"isInDev":[],"getLocale":[],"getPlatform":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise }, -'core': { getClusters: () => Promise, -getClusterById: (id: number) => Promise, -removeCluster: (id: number) => Promise, -createCluster: (options: CreateCluster) => Promise, -launchCluster: (id: number, uuid: string | null, searchForJava: boolean | null) => Promise, -updateClusterById: (id: number, request: ClusterUpdate) => Promise, -setClusterStage: (id: number, stage: ClusterStage) => Promise, -getScreenshots: (id: number) => Promise, -getWorlds: (id: number) => Promise, -getLogs: (id: number) => Promise, -getLogByName: (id: number, name: string) => Promise, -getProcessLogTail: (id: number, maxLines: number) => Promise, -getRunningProcesses: () => Promise, -getRunningProcessesByClusterId: (clusterId: number) => Promise, -isClusterRunning: (clusterId: number) => Promise, -killProcess: (pid: number) => Promise, -getProfileOrDefault: (name: string | null) => Promise, -getGlobalProfile: () => Promise, -updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise, -createSettingsProfile: (name: string) => Promise, -getGameVersions: () => Promise, -getLoadersForVersion: (mcVersion: string) => Promise, -getUsers: () => Promise, -getUser: (uuid: string) => Promise, -removeUser: (uuid: string) => Promise, -getDefaultUser: (fallback: boolean | null) => Promise, -setDefaultUser: (uuid: string | null) => Promise, -openMsaLogin: () => Promise, -refreshAccounts: () => Promise, -refreshAccount: (uuid: string) => Promise, -readSettings: () => Promise, -writeSettings: (setting: Settings) => Promise, -searchPackages: (provider: Provider, query: SearchQuery) => Promise>, -getPackage: (provider: Provider, slug: string) => Promise, -getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise, -getMultiplePackages: (provider: Provider, slugs: string[]) => Promise, -getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: number, limit: number) => Promise>, -downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise, -downloadExternalPackage: (package: ExternalPackage, clusterId: number, force: boolean | null, skipCompatibility: boolean | null) => Promise, -getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, -syncCluster: (clusterId: number) => Promise, -getLinkedPackages: (clusterId: number) => Promise, -removePackage: (clusterId: number, packageHash: string) => Promise, -togglePackage: (clusterId: number, packageHash: string) => Promise, -installModpack: (modpack: ModpackArchive, clusterId: number) => Promise, -fetchMinecraftProfile: (uuid: string) => Promise, -fetchLoggedInProfile: (accessToken: string) => Promise, -uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise, -changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise, -changeCape: (accessToken: string, capeUuid: string) => Promise, -removeCape: (accessToken: string) => Promise, -convertUsernameUUID: (usernameUuid: string) => Promise, -setDiscordRPCMessage: (message: string) => Promise, -open: (input: string) => Promise }, -'oneclient': { getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise, -getVersions: () => Promise, -extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, -checkForUpdate: () => Promise, -installUpdate: () => Promise, -downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, -updateBundlePackages: (clusterId: number) => Promise, -isBundleSyncing: () => Promise, -cacheArt: (path: string) => Promise, -refreshArt: (path: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise } }; - - -export type { InferCommandOutput } -export const createTauRPCProxy = () => createProxy(ARGS_MAP) diff --git a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx index e28f30cd..70af686a 100644 --- a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx +++ b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx @@ -1,97 +1,31 @@ +import type { DebugInfoParsedLine } from '@/bindings.gen'; import { Overlay } from '@/components'; import { bindings } from '@/main'; +import { toast } from '@/utils/toast'; import { Button } from '@onelauncher/common/components'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { useEffect, useState } from 'react'; -export type DebugInfoArray = Array<{ title: string; value: string }>; -export interface DebugInfoData { - inDev: boolean; - platform: string; - arch: string; - family: string; - locale: string; - type: string; - osVersion: string; - commitHash: string; - buildTimestamp: string; - buildVersion: string; -} - +export type DebugInfoArray = Array; export function useDebugInfo(): DebugInfoArray { - const [devInfo, setDevInfo] = useState({ - inDev: false, - platform: 'UNKNOWN', - arch: 'UNKNOWN', - family: 'UNKNOWN', - locale: 'UNKNOWN', - type: 'UNKNOWN', - osVersion: 'UNKNOWN', - commitHash: 'UNKNOWN', - buildTimestamp: 'UNKNOWN', - buildVersion: 'UNKNOWN', - }); + const [debugInfo, setDebugInfo] = useState([]); useEffect(() => { const fetchDevInfo = async () => { - const [ - inDev, - platform, - arch, - family, - locale, - type, - osVersion, - commitHash, - buildTimestamp, - buildVersion, - ] = await Promise.all([ - bindings.debug.isInDev(), - bindings.debug.getPlatform(), - bindings.debug.getArch(), - bindings.debug.getFamily(), - bindings.debug.getLocale(), - bindings.debug.getType(), - bindings.debug.getOsVersion(), - bindings.debug.getGitCommitHash(), - bindings.debug.getBuildTimestamp(), - bindings.debug.getPackageVersion(), - ]); - - setDevInfo({ - inDev, - platform, - arch, - family, - locale: locale ?? 'UNKNOWN', - type, - osVersion, - commitHash, - buildTimestamp, - buildVersion, - }); + const info = await bindings.debug.getFullDebugInfoParsed(); + setDebugInfo(info); }; void fetchDevInfo(); }, []); - return [ - { title: 'inDev', value: devInfo.inDev ? 'yes' : 'no' }, - { title: 'Platform', value: devInfo.platform }, - { title: 'Arch', value: devInfo.arch }, - { title: 'Family', value: devInfo.family }, - { title: 'Locale', value: devInfo.locale }, - { title: 'Type', value: devInfo.type }, - { title: 'Os Version', value: devInfo.osVersion }, - { title: 'Commit Hash', value: devInfo.commitHash }, - { title: 'Build Timestamp', value: devInfo.buildTimestamp }, - { title: 'Version', value: devInfo.buildVersion }, - ]; + return debugInfo; } export function copyDebugInfo(debugInfo: DebugInfoArray) { const timestamp = Math.floor(new Date().getTime() / 1000); const lines = [ + '## OneClient Debug Information', `**Data exported at:** (\`${timestamp}\`)`, ...debugInfo.map((lineData) => { if (lineData.title === 'Build Timestamp') @@ -100,6 +34,11 @@ export function copyDebugInfo(debugInfo: DebugInfoArray) { }), ]; writeText(lines.join('\n')); + toast({ + type: 'info', + title: 'Debug Info', + message: 'Debug info has been copied to clipboard', + }); } export function DebugInfo() { diff --git a/apps/oneclient/frontend/src/routes/__root.tsx b/apps/oneclient/frontend/src/routes/__root.tsx index 2b01b2ef..b0d1261b 100644 --- a/apps/oneclient/frontend/src/routes/__root.tsx +++ b/apps/oneclient/frontend/src/routes/__root.tsx @@ -1,6 +1,7 @@ import type { QueryClient } from '@tanstack/react-query'; import type { NavigateOptions, ToOptions } from '@tanstack/react-router'; -import { Toasts } from '@/components'; +import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut'; +import { copyDebugInfo, Toasts } from '@/components'; import { useSettings } from '@/hooks/useSettings'; import { bindings } from '@/main'; import { checkForUpdate, installUpdate } from '@/utils/updater'; @@ -9,6 +10,7 @@ import { TanStackDevtools } from '@tanstack/react-devtools'; import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'; import { createRootRouteWithContext, Outlet, useLocation, useRouter } from '@tanstack/react-router'; import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; +import { register } from '@tauri-apps/plugin-global-shortcut'; import { useEffect } from 'react'; import { RouterProvider } from 'react-aria-components'; @@ -115,11 +117,28 @@ function useAutoUpdate() { }, []); } +function useDebugKeybind() { + const handleKeybind = async (event: ShortcutEvent) => { + if (event.state !== 'Pressed') + return; + const info = await bindings.debug.getFullDebugInfoParsed(); + copyDebugInfo(info); + }; + + useEffect(() => { + void (async () => { + await register('CommandOrControl+Shift+D', handleKeybind); + await register('Alt+F12', handleKeybind); + })(); + }, []); +} + function RootRoute() { const router = useRouter(); const { setting } = useSettings(); useDiscordRPC(); useAutoUpdate(); + useDebugKeybind(); return ( router.navigate({ to, ...options })} diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index 2b0ef86f..cf49411f 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -1,6 +1,29 @@ use tauri::Runtime; use tauri_plugin_os::{arch, family, locale, platform, type_, version}; +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DebugInfoData { + pub in_dev: bool, + pub platform: String, + pub arch: String, + pub family: String, + pub locale: String, + pub os_type: String, + pub os_version: String, + pub commit_hash: String, + pub build_timestamp: String, + pub build_version: String, +} + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct DebugInfoParsedLine { + pub title: String, + pub value: String, +} + #[taurpc::procedures(path = "debug")] pub trait TauriLauncherDebugApi { #[taurpc(alias = "openDevTools")] @@ -16,7 +39,7 @@ pub trait TauriLauncherDebugApi { async fn get_family() -> String; #[taurpc(alias = "getLocale")] - async fn get_locale() -> Option; + async fn get_locale() -> String; #[taurpc(alias = "getType")] async fn get_type() -> String; @@ -35,6 +58,12 @@ pub trait TauriLauncherDebugApi { #[taurpc(alias = "getPackageVersion")] async fn get_package_version() -> String; + + #[taurpc(alias = "getFullDebugInfo")] + async fn get_full_debug_info() -> DebugInfoData; + + #[taurpc(alias = "getFullDebugInfoParsed")] + async fn get_full_debug_info_parsed() -> Vec; } #[taurpc::ipc_type] @@ -58,8 +87,9 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { family().to_string() } - async fn get_locale(self) -> Option { - locale() + /// Returns the user's locale (like "en-AU"), or "UNKNOWN" if it couldn't be found. + async fn get_locale(self) -> String { + locale().unwrap_or("UNKNOWN".to_string()) } async fn get_type(self) -> String { @@ -85,4 +115,70 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { async fn get_package_version(self) -> String { crate::build::PKG_VERSION.to_string() } + + async fn get_full_debug_info(self) -> DebugInfoData { + DebugInfoData { + in_dev: self.clone().is_in_dev().await, + platform: self.clone().get_platform().await, + arch: self.clone().get_arch().await, + family: self.clone().get_family().await, + locale: self.clone().get_locale().await, + os_type: self.clone().get_type().await, + os_version: self.clone().get_version().await, + commit_hash: self.clone().get_git_commit_hash().await, + build_timestamp: self.clone().get_build_timestamp().await, + build_version: self.clone().get_package_version().await, + } + } + + async fn get_full_debug_info_parsed(self) -> Vec { + let info: DebugInfoData = self.clone().get_full_debug_info().await; + + vec![ + DebugInfoParsedLine { + title: "inDev".into(), + value: if info.in_dev { + "yes".into() + } else { + "no".into() + }, + }, + DebugInfoParsedLine { + title: "Platform".into(), + value: info.platform, + }, + DebugInfoParsedLine { + title: "Arch".into(), + value: info.arch, + }, + DebugInfoParsedLine { + title: "Family".into(), + value: info.family, + }, + DebugInfoParsedLine { + title: "Locale".into(), + value: info.locale, + }, + DebugInfoParsedLine { + title: "Os Type".into(), + value: info.os_type, + }, + DebugInfoParsedLine { + title: "Os Version".into(), + value: info.os_version, + }, + DebugInfoParsedLine { + title: "Commit Hash".into(), + value: info.commit_hash, + }, + DebugInfoParsedLine { + title: "Build Timestamp".into(), + value: info.build_timestamp, + }, + DebugInfoParsedLine { + title: "Version".into(), + value: info.build_version, + }, + ] + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 645679cd..c02a651b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ catalogs: '@tauri-apps/plugin-fs': specifier: ^2.2.1 version: 2.4.2 + '@tauri-apps/plugin-global-shortcut': + specifier: ^2.2.1 + version: 2.3.1 '@tauri-apps/plugin-opener': specifier: 2.3.0 version: 2.3.0 @@ -283,6 +286,9 @@ importers: '@tauri-apps/plugin-fs': specifier: 'catalog:' version: 2.4.2 + '@tauri-apps/plugin-global-shortcut': + specifier: 'catalog:' + version: 2.3.1 '@untitled-theme/icons-react': specifier: 'catalog:' version: 0.14.1(@types/react@19.2.7)(react@19.2.3) @@ -1606,35 +1612,30 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-glibc@2.4.1': resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.4.1': resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.4.1': resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.4.1': resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.4.1': resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} @@ -2357,133 +2358,111 @@ packages: resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.41.1': resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.1': resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.41.1': resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.1': resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.41.1': resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.1': resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-musl@4.41.1': resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.1': resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loongarch64-gnu@4.41.1': resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.1': resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.41.1': resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.1': resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.41.1': resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.1': resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.41.1': resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.1': resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.41.1': resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.1': resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.41.1': resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.1': resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-linux-x64-musl@4.41.1': resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.1': resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==} @@ -2685,28 +2664,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.10': resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.10': resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.10': resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.10': resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} @@ -2932,35 +2907,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tauri-apps/cli-linux-riscv64-gnu@2.5.0': resolution: {integrity: sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-gnu@2.5.0': resolution: {integrity: sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.5.0': resolution: {integrity: sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.5.0': resolution: {integrity: sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==} @@ -2994,6 +2964,9 @@ packages: '@tauri-apps/plugin-fs@2.4.2': resolution: {integrity: sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==} + '@tauri-apps/plugin-global-shortcut@2.3.1': + resolution: {integrity: sha512-vr40W2N6G63dmBPaha1TsBQLLURXG538RQbH5vAm0G/ovVZyXJrmZR1HF1W+WneNloQvwn4dm8xzwpEXRW560g==} + '@tauri-apps/plugin-opener@2.3.0': resolution: {integrity: sha512-yAbauwp8BCHIhhA48NN8rEf6OtfZBPCgTOCa10gmtoVCpmic5Bq+1Ba7C+NZOjogedkSiV7hAotjYnnbUVmYrw==} @@ -3156,49 +3129,41 @@ packages: resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.7.2': resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.7.2': resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.7.2': resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.7.2': resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} @@ -4641,28 +4606,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -8957,6 +8918,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.8.0 + '@tauri-apps/plugin-global-shortcut@2.3.1': + dependencies: + '@tauri-apps/api': 2.8.0 + '@tauri-apps/plugin-opener@2.3.0': dependencies: '@tauri-apps/api': 2.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1a9e8c7f..00e6b01c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -47,6 +47,7 @@ catalog: '@tauri-apps/plugin-clipboard-manager': ^2.2.2 '@tauri-apps/plugin-dialog': ^2.2.1 '@tauri-apps/plugin-fs': ^2.2.1 + '@tauri-apps/plugin-global-shortcut': ^2.2.1 '@tauri-apps/plugin-shell': ^2.2.1 '@tauri-apps/plugin-opener': 2.3.0 overlayscrollbars: ^2.11.2 From 428e46c236d0a0458975d21595d034e24eb5c5c5 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 13:59:40 +0800 Subject: [PATCH 03/12] refactor: __root.tx Clean up a lot of code and move them into there own files --- .../src/components/overlay/DebugInfo.tsx | 42 +------ .../overlay/SuperSecretDevOptions.tsx | 3 +- apps/oneclient/frontend/src/routes/__root.tsx | 119 +----------------- .../oneclient/frontend/src/utils/debugInfo.ts | 58 +++++++++ .../frontend/src/utils/discordRPC.ts | 68 ++++++++++ apps/oneclient/frontend/src/utils/updater.ts | 28 ++++- 6 files changed, 163 insertions(+), 155 deletions(-) create mode 100644 apps/oneclient/frontend/src/utils/debugInfo.ts create mode 100644 apps/oneclient/frontend/src/utils/discordRPC.ts diff --git a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx index 70af686a..f8e9e5d6 100644 --- a/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx +++ b/apps/oneclient/frontend/src/components/overlay/DebugInfo.tsx @@ -1,45 +1,7 @@ -import type { DebugInfoParsedLine } from '@/bindings.gen'; +import type { DebugInfoArray } from '@/utils/debugInfo'; import { Overlay } from '@/components'; -import { bindings } from '@/main'; -import { toast } from '@/utils/toast'; +import { copyDebugInfo, useDebugInfo } from '@/utils/debugInfo'; import { Button } from '@onelauncher/common/components'; -import { writeText } from '@tauri-apps/plugin-clipboard-manager'; -import { useEffect, useState } from 'react'; - -export type DebugInfoArray = Array; -export function useDebugInfo(): DebugInfoArray { - const [debugInfo, setDebugInfo] = useState([]); - - useEffect(() => { - const fetchDevInfo = async () => { - const info = await bindings.debug.getFullDebugInfoParsed(); - setDebugInfo(info); - }; - - void fetchDevInfo(); - }, []); - - return debugInfo; -} - -export function copyDebugInfo(debugInfo: DebugInfoArray) { - const timestamp = Math.floor(new Date().getTime() / 1000); - const lines = [ - '## OneClient Debug Information', - `**Data exported at:** (\`${timestamp}\`)`, - ...debugInfo.map((lineData) => { - if (lineData.title === 'Build Timestamp') - return `**${lineData.title}:** (\`${lineData.value}\`)`; - return `**${lineData.title}:** \`${lineData.value}\``; - }), - ]; - writeText(lines.join('\n')); - toast({ - type: 'info', - title: 'Debug Info', - message: 'Debug info has been copied to clipboard', - }); -} export function DebugInfo() { const debugInfo = useDebugInfo(); diff --git a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx index b99e2b92..ff5d9718 100644 --- a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx +++ b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx @@ -1,6 +1,7 @@ -import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingsRow, SettingsSwitch, useDebugInfo } from '@/components'; +import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingsRow, SettingsSwitch } from '@/components'; import { useSettings } from '@/hooks/useSettings'; import { bindings } from '@/main'; +import { useDebugInfo } from '@/utils/debugInfo'; import { Button } from '@onelauncher/common/components'; import { Link } from '@tanstack/react-router'; import { dataDir, join } from '@tauri-apps/api/path'; diff --git a/apps/oneclient/frontend/src/routes/__root.tsx b/apps/oneclient/frontend/src/routes/__root.tsx index b0d1261b..d10333a2 100644 --- a/apps/oneclient/frontend/src/routes/__root.tsx +++ b/apps/oneclient/frontend/src/routes/__root.tsx @@ -1,17 +1,14 @@ import type { QueryClient } from '@tanstack/react-query'; import type { NavigateOptions, ToOptions } from '@tanstack/react-router'; -import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut'; -import { copyDebugInfo, Toasts } from '@/components'; +import { Toasts } from '@/components'; import { useSettings } from '@/hooks/useSettings'; -import { bindings } from '@/main'; -import { checkForUpdate, installUpdate } from '@/utils/updater'; -import { useCommand } from '@onelauncher/common'; +import { useDebugKeybind } from '@/utils/debugInfo'; +import { useDiscordRPC } from '@/utils/discordRPC'; +import { useAutoUpdater } from '@/utils/updater'; import { TanStackDevtools } from '@tanstack/react-devtools'; import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'; -import { createRootRouteWithContext, Outlet, useLocation, useRouter } from '@tanstack/react-router'; +import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router'; import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; -import { register } from '@tauri-apps/plugin-global-shortcut'; -import { useEffect } from 'react'; import { RouterProvider } from 'react-aria-components'; interface AppRouterContext { @@ -29,115 +26,11 @@ export const Route = createRootRouteWithContext()({ component: RootRoute, }); -type URLPath = Exclude; -const ResolvedPathNames: Record = { - '.': 'UNKNOWN', - '..': 'UNKNOWN', - '/': 'Viewing Home', - '/app': 'Viewing Homepage', - '/app/account': 'Viewing Account', - '/app/account/skins': 'Viewing Skin Manager', - '/app/cluster': 'Viewing Versions', - '/app/cluster/browser': 'Viewing {clusterName}\'s mods', - '/app/cluster/browser/package': 'Browsing {packageName}', - '/app/cluster/logs': 'Viewing {clusterName}\'s logs', - '/app/cluster/mods': 'Viewing {clusterName}\'s mods', - '/app/cluster/resource-packs': 'Viewing {clusterName}\'s resource packs', - '/app/cluster/shaders': 'Viewing {clusterName}\'s shaders', - '/app/cluster/datapacks': 'Viewing {clusterName}\'s data packs', - '/app/cluster/process': 'Viewing {clusterName}', - '/app/cluster/settings': 'Viewing {clusterName}\'s settings', - '/app/settings': 'Viewing Settings', - '/app/settings/appearance': 'Viewing Settings', - '/app/settings/developer': 'Viewing Settings', - '/app/settings/minecraft': 'Viewing Settings', - '/app/settings/changelog': 'Viewing Settings', - '/app/accounts': 'Viewing Accounts', - '/app/clusters': 'Viewing Versions', - '/onboarding': 'Preparing OneClient', - '/onboarding/account': 'Preparing OneClient', - '/onboarding/finished': 'Preparing OneClient', - '/onboarding/language': 'Preparing OneClient', - '/onboarding/preferences/version': 'Preparing OneClient', - '/onboarding/preferences/versionCategory': 'Preparing OneClient', - '/onboarding/preferences': 'Preparing OneClient', -}; - -// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225 -function ReplaceVariables(template: string, variables: Record) { - return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match); -} - -function useDiscordRPC() { - const location = useLocation(); - const clusterId = location.search.clusterId ?? 0; - const provider = location.search.provider ?? null; - const packageId = location.search.packageId ?? null; - const { data: cluster } = useCommand(['getClusterById', clusterId], () => bindings.core.getClusterById(clusterId)); - - const { data: managedPackage } = useCommand( - ['getPackage', provider, packageId], - () => { - if (provider == null || packageId == null) - return Promise.reject(new Error('Missing parameters')); - return bindings.core.getPackage(provider, packageId); - }, - { enabled: provider != null && packageId != null }, - ); - - useEffect(() => { - const template = ResolvedPathNames[location.pathname as URLPath]; - if (template) - bindings.core.setDiscordRPCMessage(ReplaceVariables(template, { clusterName: cluster?.name ?? 'UNKNOWN', packageName: managedPackage?.name ?? 'UNKNOWN' })); - }, [location.pathname, location.search.clusterId, cluster?.name, managedPackage?.name]); -} - -function useAutoUpdate() { - useEffect(() => { - void (async () => { - try { - const update = await checkForUpdate(); - if (!update) - return; - - // eslint-disable-next-line no-console -- Used for debugging - aka important - console.log('Update found on initial check:', update.version); - - try { - await installUpdate(); - } - catch (e) { - console.error('Failed to install update:', e); - } - } - catch (e) { - console.error('Failed to check for update:', e); - } - })(); - }, []); -} - -function useDebugKeybind() { - const handleKeybind = async (event: ShortcutEvent) => { - if (event.state !== 'Pressed') - return; - const info = await bindings.debug.getFullDebugInfoParsed(); - copyDebugInfo(info); - }; - - useEffect(() => { - void (async () => { - await register('CommandOrControl+Shift+D', handleKeybind); - await register('Alt+F12', handleKeybind); - })(); - }, []); -} - function RootRoute() { const router = useRouter(); const { setting } = useSettings(); useDiscordRPC(); - useAutoUpdate(); + useAutoUpdater(); useDebugKeybind(); return ( ; +export function useDebugInfo(): DebugInfoArray { + const [debugInfo, setDebugInfo] = useState([]); + + useEffect(() => { + const fetchDevInfo = async () => { + const info = await bindings.debug.getFullDebugInfoParsed(); + setDebugInfo(info); + }; + + void fetchDevInfo(); + }, []); + + return debugInfo; +} + +export function copyDebugInfo(debugInfo: DebugInfoArray) { + const timestamp = Math.floor(new Date().getTime() / 1000); + const lines = [ + '## OneClient Debug Information', + `**Data exported at:** (\`${timestamp}\`)`, + ...debugInfo.map((lineData) => { + if (lineData.title === 'Build Timestamp') + return `**${lineData.title}:** (\`${lineData.value}\`)`; + return `**${lineData.title}:** \`${lineData.value}\``; + }), + ]; + writeText(lines.join('\n')); + toast({ + type: 'info', + title: 'Debug Info', + message: 'Debug info has been copied to clipboard', + }); +} + +export function useDebugKeybind() { + const handleKeybind = async (event: ShortcutEvent) => { + if (event.state !== 'Pressed') + return; + const info = await bindings.debug.getFullDebugInfoParsed(); + copyDebugInfo(info); + }; + + useEffect(() => { + void (async () => { + await register('CommandOrControl+Shift+D', handleKeybind); + await register('Alt+F12', handleKeybind); + })(); + }, []); +} diff --git a/apps/oneclient/frontend/src/utils/discordRPC.ts b/apps/oneclient/frontend/src/utils/discordRPC.ts new file mode 100644 index 00000000..007989af --- /dev/null +++ b/apps/oneclient/frontend/src/utils/discordRPC.ts @@ -0,0 +1,68 @@ +import type { ToOptions } from '@tanstack/react-router'; +import { bindings } from '@/main'; +import { useCommand } from '@onelauncher/common'; +import { useLocation } from '@tanstack/react-router'; +import { useEffect } from 'react'; + +type URLPath = Exclude; +export const ResolvedPathNames: Record = { + '.': 'UNKNOWN', + '..': 'UNKNOWN', + '/': 'Viewing Home', + '/app': 'Viewing Homepage', + '/app/account': 'Viewing Account', + '/app/account/skins': 'Viewing Skin Manager', + '/app/cluster': 'Viewing Versions', + '/app/cluster/browser': 'Viewing {clusterName}\'s mods', + '/app/cluster/browser/package': 'Browsing {packageName}', + '/app/cluster/logs': 'Viewing {clusterName}\'s logs', + '/app/cluster/mods': 'Viewing {clusterName}\'s mods', + '/app/cluster/resource-packs': 'Viewing {clusterName}\'s resource packs', + '/app/cluster/shaders': 'Viewing {clusterName}\'s shaders', + '/app/cluster/datapacks': 'Viewing {clusterName}\'s data packs', + '/app/cluster/process': 'Viewing {clusterName}', + '/app/cluster/settings': 'Viewing {clusterName}\'s settings', + '/app/settings': 'Viewing Settings', + '/app/settings/appearance': 'Viewing Settings', + '/app/settings/developer': 'Viewing Settings', + '/app/settings/minecraft': 'Viewing Settings', + '/app/settings/changelog': 'Viewing Settings', + '/app/accounts': 'Viewing Accounts', + '/app/clusters': 'Viewing Versions', + '/onboarding': 'Preparing OneClient', + '/onboarding/account': 'Preparing OneClient', + '/onboarding/finished': 'Preparing OneClient', + '/onboarding/language': 'Preparing OneClient', + '/onboarding/preferences/version': 'Preparing OneClient', + '/onboarding/preferences/versionCategory': 'Preparing OneClient', + '/onboarding/preferences': 'Preparing OneClient', +}; + +// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225 +function ReplaceVariables(template: string, variables: Record) { + return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match); +} + +export function useDiscordRPC() { + const location = useLocation(); + const clusterId = location.search.clusterId ?? 0; + const provider = location.search.provider ?? null; + const packageId = location.search.packageId ?? null; + const { data: cluster } = useCommand(['getClusterById', clusterId], () => bindings.core.getClusterById(clusterId)); + + const { data: managedPackage } = useCommand( + ['getPackage', provider, packageId], + () => { + if (provider == null || packageId == null) + return Promise.reject(new Error('Missing parameters')); + return bindings.core.getPackage(provider, packageId); + }, + { enabled: provider != null && packageId != null }, + ); + + useEffect(() => { + const template = ResolvedPathNames[location.pathname as URLPath]; + if (template) + bindings.core.setDiscordRPCMessage(ReplaceVariables(template, { clusterName: cluster?.name ?? 'UNKNOWN', packageName: managedPackage?.name ?? 'UNKNOWN' })); + }, [location.pathname, location.search.clusterId, cluster?.name, managedPackage?.name]); +} diff --git a/apps/oneclient/frontend/src/utils/updater.ts b/apps/oneclient/frontend/src/utils/updater.ts index 0a0b0f26..0f88aa1f 100644 --- a/apps/oneclient/frontend/src/utils/updater.ts +++ b/apps/oneclient/frontend/src/utils/updater.ts @@ -1,6 +1,7 @@ import type { UnlistenFn } from '@tauri-apps/api/event'; +import { bindings } from '@/main'; import { listen } from '@tauri-apps/api/event'; -import { bindings } from '../main'; +import { useEffect } from 'react'; export interface Update { version: string; @@ -28,3 +29,28 @@ export async function listenForUpdateEvents( callback(event.payload); }); } + +export function useAutoUpdater() { + useEffect(() => { + void (async () => { + try { + const update = await checkForUpdate(); + if (!update) + return; + + // eslint-disable-next-line no-console -- Used for debugging - aka important + console.log('Update found on initial check:', update.version); + + try { + await installUpdate(); + } + catch (e) { + console.error('Failed to install update:', e); + } + } + catch (e) { + console.error('Failed to check for update:', e); + } + })(); + }, []); +} From e8ed5e05c8da1b2a86c5ef0858a242aca1d42fb4 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 14:19:06 +0800 Subject: [PATCH 04/12] feat: os distro in debug --- packages/core/src/api/tauri/debug.rs | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index cf49411f..dff52541 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -1,5 +1,6 @@ use tauri::Runtime; use tauri_plugin_os::{arch, family, locale, platform, type_, version}; +use tokio::fs; #[onelauncher_macro::specta] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] @@ -12,6 +13,7 @@ pub struct DebugInfoData { pub locale: String, pub os_type: String, pub os_version: String, + pub os_distro: String, pub commit_hash: String, pub build_timestamp: String, pub build_version: String, @@ -50,6 +52,9 @@ pub trait TauriLauncherDebugApi { #[taurpc(alias = "getOsVersion")] async fn get_version() -> String; + #[taurpc(alias = "getOsDistro")] + async fn get_distro() -> String; + #[taurpc(alias = "getGitCommitHash")] async fn get_git_commit_hash() -> String; @@ -104,6 +109,30 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { version().to_string() } + async fn get_distro(self) -> String { + let platform = self.clone().get_platform().await; + + if platform == "linux" { + if let Ok(contents) = fs::read_to_string("/etc/os-release").await { + for line in contents.lines() { + if let Some(value) = line.strip_prefix("PRETTY_NAME=") { + return value.trim_matches('"').to_string(); + } + } + + for line in contents.lines() { + if let Some(value) = line.strip_prefix("NAME=") { + return value.trim_matches('"').to_string(); + } + } + } + + "UNKNOWN".to_string() + } else { + platform + } + } + async fn get_git_commit_hash(self) -> String { crate::build::COMMIT_HASH.to_string() } @@ -125,6 +154,7 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { locale: self.clone().get_locale().await, os_type: self.clone().get_type().await, os_version: self.clone().get_version().await, + os_distro: self.clone().get_distro().await, commit_hash: self.clone().get_git_commit_hash().await, build_timestamp: self.clone().get_build_timestamp().await, build_version: self.clone().get_package_version().await, @@ -167,6 +197,10 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { title: "Os Version".into(), value: info.os_version, }, + DebugInfoParsedLine { + title: "Os Distro".into(), + value: info.os_distro, + }, DebugInfoParsedLine { title: "Commit Hash".into(), value: info.commit_hash, From 251644bd775a24a0acd4ab134bfa79ab4b876079 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 14:26:16 +0800 Subject: [PATCH 05/12] refactor: get_full_debug_info_parsed --- packages/core/src/api/tauri/debug.rs | 70 +++++++++------------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index dff52541..dd560bc5 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -26,6 +26,15 @@ pub struct DebugInfoParsedLine { pub value: String, } +impl DebugInfoParsedLine { + pub fn new, U: Into>(title: T, value: U) -> Self { + Self { + title: title.into(), + value: value.into(), + } + } +} + #[taurpc::procedures(path = "debug")] pub trait TauriLauncherDebugApi { #[taurpc(alias = "openDevTools")] @@ -162,57 +171,20 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { } async fn get_full_debug_info_parsed(self) -> Vec { - let info: DebugInfoData = self.clone().get_full_debug_info().await; + let info = self.clone().get_full_debug_info().await; vec![ - DebugInfoParsedLine { - title: "inDev".into(), - value: if info.in_dev { - "yes".into() - } else { - "no".into() - }, - }, - DebugInfoParsedLine { - title: "Platform".into(), - value: info.platform, - }, - DebugInfoParsedLine { - title: "Arch".into(), - value: info.arch, - }, - DebugInfoParsedLine { - title: "Family".into(), - value: info.family, - }, - DebugInfoParsedLine { - title: "Locale".into(), - value: info.locale, - }, - DebugInfoParsedLine { - title: "Os Type".into(), - value: info.os_type, - }, - DebugInfoParsedLine { - title: "Os Version".into(), - value: info.os_version, - }, - DebugInfoParsedLine { - title: "Os Distro".into(), - value: info.os_distro, - }, - DebugInfoParsedLine { - title: "Commit Hash".into(), - value: info.commit_hash, - }, - DebugInfoParsedLine { - title: "Build Timestamp".into(), - value: info.build_timestamp, - }, - DebugInfoParsedLine { - title: "Version".into(), - value: info.build_version, - }, + DebugInfoParsedLine::new("inDev", if info.in_dev { "yes" } else { "no" }), + DebugInfoParsedLine::new("Platform", info.platform), + DebugInfoParsedLine::new("Arch", info.arch), + DebugInfoParsedLine::new("Family", info.family), + DebugInfoParsedLine::new("Locale", info.locale), + DebugInfoParsedLine::new("Os Type", info.os_type), + DebugInfoParsedLine::new("Os Version", info.os_version), + DebugInfoParsedLine::new("Os Distro", info.os_distro), + DebugInfoParsedLine::new("Commit Hash", info.commit_hash), + DebugInfoParsedLine::new("Build Timestamp", info.build_timestamp), + DebugInfoParsedLine::new("Version", info.build_version), ] } } From 241661172ed53e7fa3ac8bb91dae4fed05d4a172 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 16:02:41 +0800 Subject: [PATCH 06/12] feat: made by info in settings Also made it so that you can click on it to copy debug info --- .../frontend/src/components/MadeBy.tsx | 26 +++++++++++++++++++ .../frontend/src/components/index.ts | 1 + .../src/routes/app/settings/route.tsx | 4 +-- .../frontend/src/routes/onboarding/route.tsx | 13 +++------- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 apps/oneclient/frontend/src/components/MadeBy.tsx diff --git a/apps/oneclient/frontend/src/components/MadeBy.tsx b/apps/oneclient/frontend/src/components/MadeBy.tsx new file mode 100644 index 00000000..b4d252da --- /dev/null +++ b/apps/oneclient/frontend/src/components/MadeBy.tsx @@ -0,0 +1,26 @@ +import { bindings } from '@/main'; +import { copyDebugInfo } from '@/utils/debugInfo'; +import { useCommand } from '@onelauncher/common'; +import { Button } from '@onelauncher/common/components'; + +export function MadeBy() { + const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion()); + const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev()); + + const onClick = async () => { + const info = await bindings.debug.getFullDebugInfoParsed(); + copyDebugInfo(info); + }; + + return ( + + ); +} diff --git a/apps/oneclient/frontend/src/components/index.ts b/apps/oneclient/frontend/src/components/index.ts index fa60c6e2..ef71b48f 100644 --- a/apps/oneclient/frontend/src/components/index.ts +++ b/apps/oneclient/frontend/src/components/index.ts @@ -8,6 +8,7 @@ export * from './GameBackground'; export * from './LaunchButton'; export * from './Loader'; export * from './LogViewer'; +export * from './MadeBy'; export * from './ManageSkinButton'; export * from './Markdown'; export * from './Navbar'; diff --git a/apps/oneclient/frontend/src/routes/app/settings/route.tsx b/apps/oneclient/frontend/src/routes/app/settings/route.tsx index 6c914558..82ae326c 100644 --- a/apps/oneclient/frontend/src/routes/app/settings/route.tsx +++ b/apps/oneclient/frontend/src/routes/app/settings/route.tsx @@ -1,5 +1,5 @@ import type { JSX } from 'react'; -import { SheetPage } from '@/components'; +import { MadeBy, SheetPage } from '@/components'; import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'; import { CodeSnippet02Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon } from '@untitled-theme/icons-react'; import { useEffect } from 'react'; @@ -50,7 +50,7 @@ function RouteComponent() { }} /> - {/* */} +
diff --git a/apps/oneclient/frontend/src/routes/onboarding/route.tsx b/apps/oneclient/frontend/src/routes/onboarding/route.tsx index adb1772f..25244aea 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/route.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/route.tsx @@ -1,9 +1,9 @@ import type { DownloadModsRef } from '@/components'; import type { PropsWithChildren } from 'react'; import LauncherLogo from '@/assets/logos/oneclient.svg?react'; -import { GameBackground, LoaderSuspense, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components'; +import { GameBackground, LoaderSuspense, MadeBy, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components'; import { bindings } from '@/main'; -import { useCommand, useCommandSuspense } from '@onelauncher/common'; +import { useCommandSuspense } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { useQueryClient } from '@tanstack/react-query'; import { createFileRoute, Link, Outlet, useLocation, useNavigate } from '@tanstack/react-router'; @@ -154,8 +154,6 @@ function AppShell({ children, }: PropsWithChildren) { const { isFirstStep, currentStepIndex } = Route.useLoaderData(); - const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion()); - const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev()); return (
@@ -180,12 +178,7 @@ function AppShell({ - -
-

OneClient by Polyfrost

-

Version {version}

-

{isInDev ? 'Development' : 'Release'} Build

-
+
From 7721d32582f978a2b4c55b7b5d6f3b80e0c56e31 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 16:05:27 +0800 Subject: [PATCH 07/12] fix: warning in oneclient/desktop/lib.rs --- apps/oneclient/desktop/src/lib.rs | 2 +- apps/oneclient/frontend/src/bindings.gen.ts | 408 ++++++++++++++++++++ 2 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 apps/oneclient/frontend/src/bindings.gen.ts diff --git a/apps/oneclient/desktop/src/lib.rs b/apps/oneclient/desktop/src/lib.rs index b4d1333c..f0e895fd 100644 --- a/apps/oneclient/desktop/src/lib.rs +++ b/apps/oneclient/desktop/src/lib.rs @@ -84,7 +84,7 @@ async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult #[cfg(desktop)] app.handle() - .plugin(tauri_plugin_global_shortcut::Builder::new().build()); + .plugin(tauri_plugin_global_shortcut::Builder::new().build())?; Ok(()) }); diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts new file mode 100644 index 00000000..50e81ada --- /dev/null +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -0,0 +1,408 @@ +// @ts-nocheck +// This file has been generated by Specta. DO NOT EDIT. + +import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc' +type TAURI_CHANNEL = (response: T) => void + + +export type ApplyBundleUpdatesResult = { updates_applied: BundlePackageUpdate[]; removals_applied: BundlePackageRemoval[]; additions_applied: BundlePackageAddition[]; updates_failed: string[]; removals_failed: string[]; additions_failed: string[] } + +export type BundlePackageAddition = { cluster_id: number; bundle_name: string; new_file: ModpackFile } + +export type BundlePackageRemoval = { cluster_id: number; package_hash: string; package_id: string; bundle_name: string; installed_at: string } + +export type BundlePackageUpdate = { cluster_id: number; installed_package_hash: string; installed_version_id: string; bundle_name: string; new_version_id: string; new_file: ModpackFile; installed_at: string } + +export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MismatchedVersion"; data: string } | { type: "MismatchedLoader"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string } + +export type ClusterModel = { id: number; folder_name: string; stage: ClusterStage; created_at: string; group_id: number | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: number | null; icon_url: Icon | null; setting_profile_name: string | null; linked_modpack_hash: string | null } + +export type ClusterStage = "notready" | "downloading" | "repairing" | "ready" + +export type ClusterUpdate = { name: string | null; icon_url: Icon | null; setting_profile_name: string | null } + +export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null } + +export type CryptoError = { type: "InvalidHash"; data: string } | { type: "InvalidAlgorithm"; data: string } + +export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExists"; data: string } | { type: "InvalidValue"; data: string } + +export type DbVec = T[] + +export type DebugInfoData = { inDev: boolean; platform: string; arch: string; family: string; locale: string; osType: string; osVersion: string; osDistro: string; commitHash: string; buildTimestamp: string; buildVersion: string } + +export type DebugInfoParsedLine = { title: string; value: string } + +export type DirectoryError = { type: "BaseDir"; data: string } + +export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string } + +export type ExternalPackage = { name: string; url: string; sha1: string; size: number; package_type: PackageType } + +export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: PackageCategories | null; package_type: PackageType | null } + +export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric" + +export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "FileNotFoundInZip"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string } | { type: "TempFileError"; data: string } + +export type Icon = string + +export type IncompatiblePackageType = { type: "McVersion"; data: string } | { type: "Loader"; data: string } + +export type IngressError = { type: "NotFound"; data: string } + +export type IngressPayload = { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number } + +export type IngressType = { Download: { file_name: string } } | "JavaPrepare" | "JavaCheck" | "JavaLocate" | "MinecraftDownload" | { PrepareCluster: { cluster_name: string } } + +export type JavaError = { type: "ParseVersion"; data: string } | { type: "Execute"; data: string } | { type: "MissingJava"; data: string } + +export type LauncherError = { type: "DirError"; data: DirectoryError } | { type: "IOError"; data: IOError } | { type: "IngressError"; data: IngressError } | { type: "JavaError"; data: JavaError } | { type: "CryptoError"; data: CryptoError } | { type: "DiscordError"; data: DiscordError } | { type: "MetadataError"; data: MetadataError } | { type: "ClusterError"; data: ClusterError } | { type: "MinecraftAuthError"; data: MinecraftAuthError } | { type: "ProcessError"; data: ProcessError } | { type: "PackageError"; data: PackageError } | { type: "DaoError"; data: DaoError } | { type: "SerdeError"; data: string } | { type: "AnyhowError"; data: string } | { type: "DbError"; data: string } | { type: "ReqwestError"; data: string } | { type: "InterpulseError"; data: string } | { type: "RegexError"; data: string } | { type: "SemaphoreError"; data: string } | { type: "UrlError"; data: string } | { type: "OpenerError"; data: string } | { type: "TauriError"; data: string } + +export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: ManagedPackageBody; +/** + * Won't have all versions for some providers, like `CurseForge`. + * Try making a request to get the versions if needed + */ +version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: PackageCategories; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: number; gallery: PackageGallery[] } + +export type ManagedPackageBody = { Url: string } | { Raw: string } + +export type ManagedUser = { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null } + +export type ManagedVersion = { version_id: string; project_id: string; display_name: string; display_version: string; changelog: string | null; dependencies: ManagedVersionDependency[]; mc_versions: string[]; release_type: PackageReleaseType; loaders: GameLoader[]; published: string; downloads: number; files: ManagedVersionFile[] } + +export type ManagedVersionDependency = { version_id: string | null; project_id: string | null; file_name: string | null; dependency_type: PackageDependencyType } + +export type ManagedVersionFile = { sha1: string; url: string; file_name: string; primary: boolean; size: number } + +export type MessageLevel = "Info" | "Warn" | "Error" + +export type MessagePayload = { level: MessageLevel; message: string } + +export type MetadataError = { type: "FetchError"; data: string } | { type: "NotModdedManifest"; data: string } | { type: "NotVanillaManifest"; data: string } | { type: "ParseError"; data: string } | { type: "NoMatchingLoader"; data: string } | { type: "NoMatchingVersion"; data: string } + +export type MinecraftAuthError = { type: "PublicKeyReading"; data: string } | { type: "PKCS8Error"; data: string } | { type: "SerializeError"; data: string } | { type: "DeserializeError"; data: string } | { type: "RequestError"; data: string } | { type: "SigningError"; data: string } | { type: "HashError"; data: string } | { type: "SessionIdError"; data: string } | { type: "XboxError"; data: string } + +/** + * A structure of all needed Minecraft credentials for logging in and account management. + */ +export type MinecraftCredentials = { +/** + * The uuid of the credentials. + */ +id: string; +/** + * The username of the Minecraft account. + */ +username: string; +/** + * The access token as a String. + */ +access_token: string; +/** + * The refresh token as a string for [`MinecraftState#refresh`]. + */ +refresh_token: string; +/** + * The time that the access token expires as a [`DateTime`]. + */ +expires: string } + +export type ModpackArchive = { manifest: ModpackManifest; path: string; format: ModpackFormat } + +export type ModpackFile = { enabled: boolean; hidden?: boolean; kind: ModpackFileKind; overrides?: PackageOverrides | null } + +export type ModpackFileKind = { Managed: [ManagedPackage, ManagedVersion] } | { External: ExternalPackage } + +export type ModpackFormat = "CurseForge" | "MrPack" | "PolyMrPack" + +export type ModpackManifest = { name: string; version: string; loader: GameLoader; loader_version: string; mc_version: string; enabled?: boolean; files: ModpackFile[] } + +export type MojangCape = { id: string; state: string; url: string; alias: string } + +export type MojangFullPlayerProfile = { id: string; username: string; skins: MojangSkin[]; capes: MojangCape[] } + +export type MojangPlayerProfile = { uuid: string; username: string; is_slim: boolean; skin_url: string | null; cape_url: string | null } + +export type MojangSkin = { id: string; state: string; url: string; variant: SkinVariant } + +export type MowojangProfile = { id: string; username: string } + +export type OnlineCluster = { major_version: number; name: string; art: string; entries: OnlineClusterEntry[] } + +export type OnlineClusterEntry = { minor_version: number; name?: string | null; art?: string | null; loader: GameLoader; tags: string[] } + +/** + * e.g. + * ```json + * { + * "clusters": [ + * { + * "major_version": 21, + * "name": "Tricky Trials", + * "art": "/versions/art/Tricky_Trials.png", + * "entries": [ + * { + * "minor_version": 5, + * "loader": "fabric", + * "tags": ["PvP", "Survival"] + * }, + * { + * "minor_version": 5, + * "loader": "forge", + * "tags": ["PvP", "Survival"] + * } + * ] + * }, + * { + * "major_version": 20, + * "name": "Trails & Tales", + * "art": "/versions/art/Trails_Tales.png", + * "entries": [ + * { + * "minor_version": 5, + * "loader": "fabric", + * "tags": ["PvP", "Survival"] + * } + * ] + * } + * ] + * } + * ``` + */ +export type OnlineClusterManifest = { clusters: OnlineCluster[] } + +export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] } + +export type PackageCategories = { Mod: PackageModCategory[] } | { ResourcePack: PackageResourcePackCategory[] } | { Shader: PackageShaderCategory[] } | { DataPack: PackageModCategory[] } | { ModPack: PackageModPackCategory[] } + +export type PackageDependencyType = "required" | "optional" | "embedded" | "incompatible" + +/** + * + */ +export type PackageDonationPlatform = "patreon" | "buymeacoffee" | "paypal" | "github" | "kofi" | "other" + +export type PackageDonationUrl = { id: PackageDonationPlatform; url: string } + +export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsNotModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType } | { type: "MissingApiKey"; data: string } | { type: "UnsupportedBodyType"; data: string } | { type: "UnsupportedAuthorType"; data: string } | { type: "UnsupportedModpackFormat"; data: string } + +export type PackageGallery = { url: string; thumbnail_url: string; title: string | null; description: string | null; featured: boolean | null } + +/** + * + */ +export type PackageLicense = { id: string; name: string; url: string | null } + +/** + * + */ +export type PackageLinks = { website: string | null; issues: string | null; source: string | null; wiki: string | null; donation: PackageDonationUrl[] | null; discord: string | null } + +export type PackageModCategory = "Adventure" | "Library" | "Equipment" | "Patches" | "Cosmetic" | "Food" | "Magic" | "Information" | "Misc" | "Performance" | "Redstone" | "ServerUtil" | "Storage" | "Technology" | "Farming" | "Automation" | "Transport" | "Utility" | "QoL" | "WorldGen" | "Mobs" | "Economy" | "Social" + +export type PackageModPackCategory = "Technology" | "Quests" | "Optimization" | "Multiplayer" | "Magic" | "LightWeight" | "Combat" | "Challenging" | "Adventure" + +export type PackageModel = { hash: string; file_name: string; version_id: string; published_at: string; display_name: string; display_version: string; package_type: PackageType; provider: Provider; package_id: string; mc_versions: DbVec; mc_loader: DbVec; icon: Icon | null } + +export type PackageOverrides = { icon: string | null; name: string | null; authors: string[] | null; description: string | null } + +export type PackageReleaseType = "release" | "beta" | "alpha" + +export type PackageResourcePackCategory = "X8" | "X16" | "X32" | "X48" | "X64" | "X128" | "X256" | "X512" | "VanillaLike" | "Utility" | "Tweaks" | "Themed" | "Simplistic" | "Realistic" | "Modded" | "Decoration" | "Cursed" | "Combat" | "Audio" | "Blocks" | "CoreShaders" | "Gui" | "Fonts" | "Equipment" | "Environment" | "Entities" | "Items" | "Locale" | "Models" + +export type PackageShaderCategory = "VanillaLike" | "SemiRealistic" | "Realistic" | "Fantasy" | "Cursed" | "Cartoon" | "Bloom" | "Atmosphere" | "Reflections" | "Shadows" | "PBR" | "PathTracing" | "Foliage" | "ColoredLightning" | "Potato" | "Low" | "Medium" | "High" | "Ultra" + +export type PackageSide = "unknown" | "required" | "optional" | "unsupported" + +export type PackageStatus = "active" | "abandoned" + +export type PackageType = "mod" | "resourcepack" | "shader" | "datapack" | "modpack" + +export type Paginated = { total: number; offset: number; limit: number; items: T[] } + +export type Process = { pid: number; started_at: string; cluster_id: number; post_hook: string | null; account_id: string } + +export type ProcessError = { type: "HookUnsuccessful"; data: string } | { type: "NoPid"; data: string } + +export type ProcessLogTail = { content: string; truncated: boolean } + +export type ProcessPayload = { cluster_id: number; kind: ProcessPayloadKind } + +export type ProcessPayloadKind = { type: "Starting"; command: string } | { type: "Started"; pid: number; started_at: string; post_hook: string | null; account_id: string } | { type: "Stopped"; pid: number; exit_code: number } | { type: "Output"; pid: number; output: string } + +export type ProfileUpdate = { res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null } + +export type Provider = "Modrinth" | "CurseForge" | "SkyClient" | "Local" + +export type Resolution = { width: number; height: number } + +export type SearchQuery = { query: string | null; offset: number | null; limit: number | null; sort: Sort | null; filters: Filters | null } + +export type SearchResult = { project_id: string; package_type: PackageType; slug: string; author: string; title: string; description: string; categories: PackageCategories; loaders: GameLoader[]; mc_versions: string[]; downloads: number; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string | null; client_side: PackageSide; server_side: PackageSide; +/** + * List of URLs to images + */ +gallery: string[] } + +export type SettingProfileModel = { name: string; java_id: number | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } + +export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; show_tanstack_dev_tools: boolean } + +export type SettingsOsExtra = { enable_gamemode: boolean | null } + +export type SkinVariant = "classic" | "slim" + +export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" + +export type Update = { version: string } + +/** + * A game version of Minecraft + */ +export type Version = { +/** + * A unique identifier of the version + */ +id: string; +/** + * The release type of the version + */ +type: VersionType; +/** + * A link to additional information about the version + */ +url: string; +/** + * The latest time a file in this version was updated + */ +time: string; +/** + * The time this version was released + */ +releaseTime: string; +/** + * The SHA1 hash of the additional information about the version + */ +sha1: string; +/** + * Whether the version supports the latest player safety features + */ +complianceLevel: number; +/** + * The SHA1 hash of the original unmodified Minecraft version's JSON + * This is only available when using the Pulseflow mirror + */ +originalSha1?: string | null } + +/** + * The version type of Minecraft + */ +export type VersionType = +/** + * A major version, which is stable for all players to use + */ +"release" | +/** + * An experimental version, which is unstable and used for feature previews and beta testing + */ +"snapshot" | +/** + * The oldest versions before the game was released + */ +"old_alpha" | +/** + * Early versions of the game + */ +"old_beta" + +const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"refreshArt":["path"],"installUpdate":[],"getBundlesFor":["cluster_id"],"getVersions":[],"isBundleSyncing":[],"cacheArt":["path"],"updateBundlePackages":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"]}', 'core':'{"getLoadersForVersion":["mc_version"],"changeCape":["access_token","cape_uuid"],"convertUsernameUUID":["username_uuid"],"updateClusterProfile":["name","profile"],"open":["input"],"removeCape":["access_token"],"setDiscordRPCMessage":["message"],"createCluster":["options"],"refreshAccounts":[],"searchPackages":["provider","query"],"getPackageBody":["provider","body"],"killProcess":["pid"],"openMsaLogin":[],"syncCluster":["cluster_id"],"getUsers":[],"getMultiplePackages":["provider","slugs"],"fetchLoggedInProfile":["access_token"],"getWorlds":["id"],"getUsersFromAuthor":["provider","author"],"getRunningProcesses":[],"createSettingsProfile":["name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"fetchMinecraftProfile":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"getGameVersions":[],"getLogByName":["id","name"],"getProcessLogTail":["id","max_lines"],"getLogs":["id"],"getProfileOrDefault":["name"],"getUser":["uuid"],"refreshAccount":["uuid"],"getLinkedPackages":["cluster_id"],"getClusters":[],"removePackage":["cluster_id","package_hash"],"changeSkin":["access_token","skin_url","skin_variant"],"isClusterRunning":["cluster_id"],"writeSettings":["setting"],"readSettings":[],"setDefaultUser":["uuid"],"togglePackage":["cluster_id","package_hash"],"installModpack":["modpack","cluster_id"],"getGlobalProfile":[],"getPackage":["provider","slug"],"removeCluster":["id"],"updateClusterById":["id","request"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getClusterById":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"setClusterStage":["id","stage"],"getScreenshots":["id"],"getDefaultUser":["fallback"],"launchCluster":["id","uuid","search_for_java"],"removeUser":["uuid"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'debug':'{"getGitCommitHash":[],"getArch":[],"getOsVersion":[],"isInDev":[],"getLocale":[],"getPlatform":[],"getBuildTimestamp":[],"getPackageVersion":[],"getFullDebugInfo":[],"getType":[],"openDevTools":[],"getOsDistro":[],"getFullDebugInfoParsed":[],"getFamily":[]}' } +export type Router = { 'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'debug': { openDevTools: () => Promise, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getOsDistro: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise, +getFullDebugInfo: () => Promise, +getFullDebugInfoParsed: () => Promise }, +'oneclient': { getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise, +getVersions: () => Promise, +extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, +checkForUpdate: () => Promise, +installUpdate: () => Promise, +downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, +updateBundlePackages: (clusterId: number) => Promise, +isBundleSyncing: () => Promise, +cacheArt: (path: string) => Promise, +refreshArt: (path: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'core': { getClusters: () => Promise, +getClusterById: (id: number) => Promise, +removeCluster: (id: number) => Promise, +createCluster: (options: CreateCluster) => Promise, +launchCluster: (id: number, uuid: string | null, searchForJava: boolean | null) => Promise, +updateClusterById: (id: number, request: ClusterUpdate) => Promise, +setClusterStage: (id: number, stage: ClusterStage) => Promise, +getScreenshots: (id: number) => Promise, +getWorlds: (id: number) => Promise, +getLogs: (id: number) => Promise, +getLogByName: (id: number, name: string) => Promise, +getProcessLogTail: (id: number, maxLines: number) => Promise, +getRunningProcesses: () => Promise, +getRunningProcessesByClusterId: (clusterId: number) => Promise, +isClusterRunning: (clusterId: number) => Promise, +killProcess: (pid: number) => Promise, +getProfileOrDefault: (name: string | null) => Promise, +getGlobalProfile: () => Promise, +updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise, +createSettingsProfile: (name: string) => Promise, +getGameVersions: () => Promise, +getLoadersForVersion: (mcVersion: string) => Promise, +getUsers: () => Promise, +getUser: (uuid: string) => Promise, +removeUser: (uuid: string) => Promise, +getDefaultUser: (fallback: boolean | null) => Promise, +setDefaultUser: (uuid: string | null) => Promise, +openMsaLogin: () => Promise, +refreshAccounts: () => Promise, +refreshAccount: (uuid: string) => Promise, +readSettings: () => Promise, +writeSettings: (setting: Settings) => Promise, +searchPackages: (provider: Provider, query: SearchQuery) => Promise>, +getPackage: (provider: Provider, slug: string) => Promise, +getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise, +getMultiplePackages: (provider: Provider, slugs: string[]) => Promise, +getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: number, limit: number) => Promise>, +downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise, +downloadExternalPackage: (package: ExternalPackage, clusterId: number, force: boolean | null, skipCompatibility: boolean | null) => Promise, +getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, +syncCluster: (clusterId: number) => Promise, +getLinkedPackages: (clusterId: number) => Promise, +removePackage: (clusterId: number, packageHash: string) => Promise, +togglePackage: (clusterId: number, packageHash: string) => Promise, +installModpack: (modpack: ModpackArchive, clusterId: number) => Promise, +fetchMinecraftProfile: (uuid: string) => Promise, +fetchLoggedInProfile: (accessToken: string) => Promise, +uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise, +changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise, +changeCape: (accessToken: string, capeUuid: string) => Promise, +removeCape: (accessToken: string) => Promise, +convertUsernameUUID: (usernameUuid: string) => Promise, +setDiscordRPCMessage: (message: string) => Promise, +open: (input: string) => Promise } }; + + +export type { InferCommandOutput } +export const createTauRPCProxy = () => createProxy(ARGS_MAP) From ded43f7a74f1eb482f08778fab8f151468a7e18b Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 18:56:53 +0800 Subject: [PATCH 08/12] refactor: copyDebugInfo Moved it to the backend to later be used --- apps/oneclient/frontend/src/bindings.gen.ts | 61 ++++++++++--------- .../frontend/src/components/MadeBy.tsx | 7 +-- .../src/components/overlay/DebugInfo.tsx | 3 +- .../oneclient/frontend/src/utils/debugInfo.ts | 20 ++---- packages/core/src/api/tauri/debug.rs | 33 ++++++++++ 5 files changed, 71 insertions(+), 53 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 50e81ada..4b189517 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -317,35 +317,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"refreshArt":["path"],"installUpdate":[],"getBundlesFor":["cluster_id"],"getVersions":[],"isBundleSyncing":[],"cacheArt":["path"],"updateBundlePackages":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"]}', 'core':'{"getLoadersForVersion":["mc_version"],"changeCape":["access_token","cape_uuid"],"convertUsernameUUID":["username_uuid"],"updateClusterProfile":["name","profile"],"open":["input"],"removeCape":["access_token"],"setDiscordRPCMessage":["message"],"createCluster":["options"],"refreshAccounts":[],"searchPackages":["provider","query"],"getPackageBody":["provider","body"],"killProcess":["pid"],"openMsaLogin":[],"syncCluster":["cluster_id"],"getUsers":[],"getMultiplePackages":["provider","slugs"],"fetchLoggedInProfile":["access_token"],"getWorlds":["id"],"getUsersFromAuthor":["provider","author"],"getRunningProcesses":[],"createSettingsProfile":["name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"fetchMinecraftProfile":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"getGameVersions":[],"getLogByName":["id","name"],"getProcessLogTail":["id","max_lines"],"getLogs":["id"],"getProfileOrDefault":["name"],"getUser":["uuid"],"refreshAccount":["uuid"],"getLinkedPackages":["cluster_id"],"getClusters":[],"removePackage":["cluster_id","package_hash"],"changeSkin":["access_token","skin_url","skin_variant"],"isClusterRunning":["cluster_id"],"writeSettings":["setting"],"readSettings":[],"setDefaultUser":["uuid"],"togglePackage":["cluster_id","package_hash"],"installModpack":["modpack","cluster_id"],"getGlobalProfile":[],"getPackage":["provider","slug"],"removeCluster":["id"],"updateClusterById":["id","request"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getClusterById":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"setClusterStage":["id","stage"],"getScreenshots":["id"],"getDefaultUser":["fallback"],"launchCluster":["id","uuid","search_for_java"],"removeUser":["uuid"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'debug':'{"getGitCommitHash":[],"getArch":[],"getOsVersion":[],"isInDev":[],"getLocale":[],"getPlatform":[],"getBuildTimestamp":[],"getPackageVersion":[],"getFullDebugInfo":[],"getType":[],"openDevTools":[],"getOsDistro":[],"getFullDebugInfoParsed":[],"getFamily":[]}' } -export type Router = { 'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getOsDistro: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise, -getFullDebugInfo: () => Promise, -getFullDebugInfoParsed: () => Promise }, -'oneclient': { getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise, -getVersions: () => Promise, -extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, -checkForUpdate: () => Promise, -installUpdate: () => Promise, -downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, -updateBundlePackages: (clusterId: number) => Promise, -isBundleSyncing: () => Promise, -cacheArt: (path: string) => Promise, -refreshArt: (path: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, +const ARGS_MAP = { 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getClustersGroupedByMajor":[],"getVersions":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"updateBundlePackages":["cluster_id"],"installUpdate":[],"cacheArt":["path"],"getBundlesFor":["cluster_id"],"refreshArt":["path"],"isBundleSyncing":[]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'core':'{"syncCluster":["cluster_id"],"getLogs":["id"],"getLogByName":["id","name"],"updateClusterProfile":["name","profile"],"refreshAccount":["uuid"],"openMsaLogin":[],"getUsersFromAuthor":["provider","author"],"fetchMinecraftProfile":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"getClusterById":["id"],"getUser":["uuid"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"createCluster":["options"],"updateClusterById":["id","request"],"getLoadersForVersion":["mc_version"],"setDefaultUser":["uuid"],"getPackageBody":["provider","body"],"getProcessLogTail":["id","max_lines"],"getClusters":[],"killProcess":["pid"],"refreshAccounts":[],"getPackage":["provider","slug"],"removePackage":["cluster_id","package_hash"],"removeCluster":["id"],"getUsers":[],"getDefaultUser":["fallback"],"writeSettings":["setting"],"installModpack":["modpack","cluster_id"],"changeCape":["access_token","cape_uuid"],"togglePackage":["cluster_id","package_hash"],"getScreenshots":["id"],"getWorlds":["id"],"getGameVersions":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"removeCape":["access_token"],"launchCluster":["id","uuid","search_for_java"],"convertUsernameUUID":["username_uuid"],"removeUser":["uuid"],"setDiscordRPCMessage":["message"],"open":["input"],"createSettingsProfile":["name"],"getGlobalProfile":[],"searchPackages":["provider","query"],"getLinkedPackages":["cluster_id"],"readSettings":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"setClusterStage":["id","stage"],"fetchLoggedInProfile":["access_token"],"getProfileOrDefault":["name"],"getMultiplePackages":["provider","slugs"],"getRunningProcesses":[],"getRunningProcessesByClusterId":["cluster_id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"isClusterRunning":["cluster_id"]}', 'debug':'{"getFullDebugInfoParsedString":[],"getOsDistro":[],"getPackageVersion":[],"getOsVersion":[],"isInDev":[],"getGitCommitHash":[],"getType":[],"openDevTools":[],"getLocale":[],"getPlatform":[],"getFullDebugInfoParsed":[],"getArch":[],"getFullDebugInfo":[],"getFamily":[],"getBuildTimestamp":[]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise }, 'core': { getClusters: () => Promise, @@ -401,7 +374,35 @@ changeCape: (accessToken: string, capeUuid: string) => Promise Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, -open: (input: string) => Promise } }; +open: (input: string) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'oneclient': { getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise, +getVersions: () => Promise, +extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, +checkForUpdate: () => Promise, +installUpdate: () => Promise, +downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, +updateBundlePackages: (clusterId: number) => Promise, +isBundleSyncing: () => Promise, +cacheArt: (path: string) => Promise, +refreshArt: (path: string) => Promise }, +'debug': { openDevTools: () => Promise, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getOsDistro: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise, +getFullDebugInfo: () => Promise, +getFullDebugInfoParsed: () => Promise, +getFullDebugInfoParsedString: () => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/MadeBy.tsx b/apps/oneclient/frontend/src/components/MadeBy.tsx index b4d252da..6802ee8a 100644 --- a/apps/oneclient/frontend/src/components/MadeBy.tsx +++ b/apps/oneclient/frontend/src/components/MadeBy.tsx @@ -7,16 +7,11 @@ export function MadeBy() { const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion()); const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev()); - const onClick = async () => { - const info = await bindings.debug.getFullDebugInfoParsed(); - copyDebugInfo(info); - }; - return ( + ); } diff --git a/apps/oneclient/frontend/src/utils/debugInfo.ts b/apps/oneclient/frontend/src/utils/debugInfo.ts index 0a7bc361..b6073053 100644 --- a/apps/oneclient/frontend/src/utils/debugInfo.ts +++ b/apps/oneclient/frontend/src/utils/debugInfo.ts @@ -22,18 +22,9 @@ export function useDebugInfo(): DebugInfoArray { return debugInfo; } -export function copyDebugInfo(debugInfo: DebugInfoArray) { - const timestamp = Math.floor(new Date().getTime() / 1000); - const lines = [ - '## OneClient Debug Information', - `**Data exported at:** (\`${timestamp}\`)`, - ...debugInfo.map((lineData) => { - if (lineData.title === 'Build Timestamp') - return `**${lineData.title}:** (\`${lineData.value}\`)`; - return `**${lineData.title}:** \`${lineData.value}\``; - }), - ]; - writeText(lines.join('\n')); +export async function copyDebugInfo() { + const info = await bindings.debug.getFullDebugInfoParsedString(); + writeText(info); toast({ type: 'info', title: 'Debug Info', @@ -42,11 +33,10 @@ export function copyDebugInfo(debugInfo: DebugInfoArray) { } export function useDebugKeybind() { - const handleKeybind = async (event: ShortcutEvent) => { + const handleKeybind = (event: ShortcutEvent) => { if (event.state !== 'Pressed') return; - const info = await bindings.debug.getFullDebugInfoParsed(); - copyDebugInfo(info); + copyDebugInfo(); }; useEffect(() => { diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index dd560bc5..c0ea402a 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -1,3 +1,4 @@ +use std::time::{SystemTime, UNIX_EPOCH}; use tauri::Runtime; use tauri_plugin_os::{arch, family, locale, platform, type_, version}; use tokio::fs; @@ -78,6 +79,9 @@ pub trait TauriLauncherDebugApi { #[taurpc(alias = "getFullDebugInfoParsed")] async fn get_full_debug_info_parsed() -> Vec; + + #[taurpc(alias = "getFullDebugInfoParsedString")] + async fn get_full_debug_info_parsed_string() -> String; } #[taurpc::ipc_type] @@ -187,4 +191,33 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { DebugInfoParsedLine::new("Version", info.build_version), ] } + + async fn get_full_debug_info_parsed_string(self) -> String { + let debug_info = self.clone().get_full_debug_info_parsed().await; + + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + + let mut lines = Vec::new(); + + lines.push("## OneClient Debug Information".to_string()); + lines.push(format!( + "**Data exported at:** (`{timestamp}`)" + )); + + for line_data in debug_info { + if line_data.title == "Build Timestamp" { + lines.push(format!( + "**{}:** (`{}`)", + line_data.title, line_data.value, line_data.value + )); + } else { + lines.push(format!("**{}:** `{}`", line_data.title, line_data.value)); + } + } + + lines.join("\n") + } } From e42a54eba32972544c51b366e345dc5151501f5b Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 19:10:11 +0800 Subject: [PATCH 09/12] feat: log out debug info when oneclient launches --- apps/oneclient/desktop/src/lib.rs | 8 +++++++- apps/oneclient/frontend/src/bindings.gen.ts | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/oneclient/desktop/src/lib.rs b/apps/oneclient/desktop/src/lib.rs index f0e895fd..fbfd9245 100644 --- a/apps/oneclient/desktop/src/lib.rs +++ b/apps/oneclient/desktop/src/lib.rs @@ -1,5 +1,5 @@ use onelauncher_core::api::proxy::ProxyTauri; -use onelauncher_core::api::tauri::TauRPCLauncherExt; +use onelauncher_core::api::tauri::{TauRPCLauncherExt, TauriLauncherDebugApi}; use onelauncher_core::error::LauncherResult; use onelauncher_core::store::proxy::ProxyState; use onelauncher_core::store::semaphore::SemaphoreStore; @@ -48,6 +48,12 @@ async fn initialize_core() -> LauncherResult<()> { SemaphoreStore::get().await; tracing::info!("initialized core modules"); + let debug_info = onelauncher_core::api::tauri::TauriLauncherDebugApiImpl + .get_full_debug_info_parsed_string() + .await; + + tracing::info!("\n{}", debug_info); + Ok(()) } diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 4b189517..0d373f80 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -317,11 +317,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getClustersGroupedByMajor":[],"getVersions":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"updateBundlePackages":["cluster_id"],"installUpdate":[],"cacheArt":["path"],"getBundlesFor":["cluster_id"],"refreshArt":["path"],"isBundleSyncing":[]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'core':'{"syncCluster":["cluster_id"],"getLogs":["id"],"getLogByName":["id","name"],"updateClusterProfile":["name","profile"],"refreshAccount":["uuid"],"openMsaLogin":[],"getUsersFromAuthor":["provider","author"],"fetchMinecraftProfile":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"getClusterById":["id"],"getUser":["uuid"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"createCluster":["options"],"updateClusterById":["id","request"],"getLoadersForVersion":["mc_version"],"setDefaultUser":["uuid"],"getPackageBody":["provider","body"],"getProcessLogTail":["id","max_lines"],"getClusters":[],"killProcess":["pid"],"refreshAccounts":[],"getPackage":["provider","slug"],"removePackage":["cluster_id","package_hash"],"removeCluster":["id"],"getUsers":[],"getDefaultUser":["fallback"],"writeSettings":["setting"],"installModpack":["modpack","cluster_id"],"changeCape":["access_token","cape_uuid"],"togglePackage":["cluster_id","package_hash"],"getScreenshots":["id"],"getWorlds":["id"],"getGameVersions":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"removeCape":["access_token"],"launchCluster":["id","uuid","search_for_java"],"convertUsernameUUID":["username_uuid"],"removeUser":["uuid"],"setDiscordRPCMessage":["message"],"open":["input"],"createSettingsProfile":["name"],"getGlobalProfile":[],"searchPackages":["provider","query"],"getLinkedPackages":["cluster_id"],"readSettings":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"setClusterStage":["id","stage"],"fetchLoggedInProfile":["access_token"],"getProfileOrDefault":["name"],"getMultiplePackages":["provider","slugs"],"getRunningProcesses":[],"getRunningProcessesByClusterId":["cluster_id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"isClusterRunning":["cluster_id"]}', 'debug':'{"getFullDebugInfoParsedString":[],"getOsDistro":[],"getPackageVersion":[],"getOsVersion":[],"isInDev":[],"getGitCommitHash":[],"getType":[],"openDevTools":[],"getLocale":[],"getPlatform":[],"getFullDebugInfoParsed":[],"getArch":[],"getFullDebugInfo":[],"getFamily":[],"getBuildTimestamp":[]}' } -export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'core': { getClusters: () => Promise, +const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"installUpdate":[],"getBundlesFor":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"isBundleSyncing":[],"cacheArt":["path"],"refreshArt":["path"],"getVersions":[],"updateBundlePackages":["cluster_id"]}', 'core':'{"removeCape":["access_token"],"open":["input"],"getProcessLogTail":["id","max_lines"],"getClusterById":["id"],"createCluster":["options"],"openMsaLogin":[],"getDefaultUser":["fallback"],"getUsers":[],"killProcess":["pid"],"getGameVersions":[],"setDefaultUser":["uuid"],"getScreenshots":["id"],"getRunningProcesses":[],"updateClusterById":["id","request"],"getPackageBody":["provider","body"],"fetchLoggedInProfile":["access_token"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"updateClusterProfile":["name","profile"],"getLogs":["id"],"removeCluster":["id"],"getGlobalProfile":[],"getRunningProcessesByClusterId":["cluster_id"],"getUser":["uuid"],"getLoadersForVersion":["mc_version"],"getMultiplePackages":["provider","slugs"],"isClusterRunning":["cluster_id"],"syncCluster":["cluster_id"],"writeSettings":["setting"],"getLinkedPackages":["cluster_id"],"convertUsernameUUID":["username_uuid"],"setDiscordRPCMessage":["message"],"togglePackage":["cluster_id","package_hash"],"createSettingsProfile":["name"],"getClusters":[],"getPackage":["provider","slug"],"getLogByName":["id","name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUsersFromAuthor":["provider","author"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"changeSkin":["access_token","skin_url","skin_variant"],"launchCluster":["id","uuid","search_for_java"],"removeUser":["uuid"],"refreshAccount":["uuid"],"getWorlds":["id"],"setClusterStage":["id","stage"],"refreshAccounts":[],"searchPackages":["provider","query"],"readSettings":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removePackage":["cluster_id","package_hash"],"changeCape":["access_token","cape_uuid"],"getProfileOrDefault":["name"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'debug':'{"getGitCommitHash":[],"getOsVersion":[],"isInDev":[],"getPlatform":[],"getLocale":[],"getFullDebugInfoParsed":[],"getFullDebugInfo":[],"getBuildTimestamp":[],"getFamily":[],"openDevTools":[],"getOsDistro":[],"getType":[],"getPackageVersion":[],"getFullDebugInfoParsedString":[],"getArch":[]}' } +export type Router = { 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -375,6 +372,9 @@ removeCape: (accessToken: string) => Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, open: (input: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, 'folders': { fromCluster: (folderName: string) => Promise, openCluster: (folderName: string) => Promise }, 'oneclient': { getClustersGroupedByMajor: () => Promise>, From fe08ee8e970e44c2e3846da4aeab83a303e9af99 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 19:13:05 +0800 Subject: [PATCH 10/12] `inDev` -> `In Dev` --- packages/core/src/api/tauri/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index c0ea402a..69c6fea5 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -178,7 +178,7 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { let info = self.clone().get_full_debug_info().await; vec![ - DebugInfoParsedLine::new("inDev", if info.in_dev { "yes" } else { "no" }), + DebugInfoParsedLine::new("In Dev", if info.in_dev { "yes" } else { "no" }), DebugInfoParsedLine::new("Platform", info.platform), DebugInfoParsedLine::new("Arch", info.arch), DebugInfoParsedLine::new("Family", info.family), From d928f7b84a5edec161d5e881bb0cd19c854cb10a Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 1 Mar 2026 20:51:44 +0800 Subject: [PATCH 11/12] feat: toggle debug logging --- apps/oneclient/frontend/src/bindings.gen.ts | 48 +++++++++---------- .../overlay/SuperSecretDevOptions.tsx | 4 ++ .../src/routes/app/settings/developer.tsx | 3 ++ packages/core/src/logger.rs | 20 +++++--- packages/core/src/store/settings.rs | 2 + 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 0d373f80..18b0d442 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -248,7 +248,7 @@ gallery: string[] } export type SettingProfileModel = { name: string; java_id: number | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } -export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; show_tanstack_dev_tools: boolean } +export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; log_debug_info: boolean; show_tanstack_dev_tools: boolean } export type SettingsOsExtra = { enable_gamemode: boolean | null } @@ -317,8 +317,28 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"checkForUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"installUpdate":[],"getBundlesFor":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"isBundleSyncing":[],"cacheArt":["path"],"refreshArt":["path"],"getVersions":[],"updateBundlePackages":["cluster_id"]}', 'core':'{"removeCape":["access_token"],"open":["input"],"getProcessLogTail":["id","max_lines"],"getClusterById":["id"],"createCluster":["options"],"openMsaLogin":[],"getDefaultUser":["fallback"],"getUsers":[],"killProcess":["pid"],"getGameVersions":[],"setDefaultUser":["uuid"],"getScreenshots":["id"],"getRunningProcesses":[],"updateClusterById":["id","request"],"getPackageBody":["provider","body"],"fetchLoggedInProfile":["access_token"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"updateClusterProfile":["name","profile"],"getLogs":["id"],"removeCluster":["id"],"getGlobalProfile":[],"getRunningProcessesByClusterId":["cluster_id"],"getUser":["uuid"],"getLoadersForVersion":["mc_version"],"getMultiplePackages":["provider","slugs"],"isClusterRunning":["cluster_id"],"syncCluster":["cluster_id"],"writeSettings":["setting"],"getLinkedPackages":["cluster_id"],"convertUsernameUUID":["username_uuid"],"setDiscordRPCMessage":["message"],"togglePackage":["cluster_id","package_hash"],"createSettingsProfile":["name"],"getClusters":[],"getPackage":["provider","slug"],"getLogByName":["id","name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUsersFromAuthor":["provider","author"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"changeSkin":["access_token","skin_url","skin_variant"],"launchCluster":["id","uuid","search_for_java"],"removeUser":["uuid"],"refreshAccount":["uuid"],"getWorlds":["id"],"setClusterStage":["id","stage"],"refreshAccounts":[],"searchPackages":["provider","query"],"readSettings":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removePackage":["cluster_id","package_hash"],"changeCape":["access_token","cape_uuid"],"getProfileOrDefault":["name"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'debug':'{"getGitCommitHash":[],"getOsVersion":[],"isInDev":[],"getPlatform":[],"getLocale":[],"getFullDebugInfoParsed":[],"getFullDebugInfo":[],"getBuildTimestamp":[],"getFamily":[],"openDevTools":[],"getOsDistro":[],"getType":[],"getPackageVersion":[],"getFullDebugInfoParsedString":[],"getArch":[]}' } -export type Router = { 'core': { getClusters: () => Promise, +const ARGS_MAP = { 'debug':'{"getFullDebugInfoParsedString":[],"getBuildTimestamp":[],"getPackageVersion":[],"openDevTools":[],"getFamily":[],"getArch":[],"isInDev":[],"getFullDebugInfo":[],"getType":[],"getLocale":[],"getPlatform":[],"getOsDistro":[],"getGitCommitHash":[],"getOsVersion":[],"getFullDebugInfoParsed":[]}', 'oneclient':'{"updateBundlePackages":["cluster_id"],"installUpdate":[],"checkForUpdate":[],"cacheArt":["path"],"extractBundleOverrides":["bundle_path","cluster_id"],"isBundleSyncing":[],"getBundlesFor":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getVersions":[],"getClustersGroupedByMajor":[],"refreshArt":["path"]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"removeCape":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"getLinkedPackages":["cluster_id"],"fetchMinecraftProfile":["uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"updateClusterById":["id","request"],"getLogs":["id"],"openMsaLogin":[],"getGameVersions":[],"setDefaultUser":["uuid"],"syncCluster":["cluster_id"],"refreshAccount":["uuid"],"removeCluster":["id"],"removePackage":["cluster_id","package_hash"],"convertUsernameUUID":["username_uuid"],"getClusterById":["id"],"fetchLoggedInProfile":["access_token"],"removeUser":["uuid"],"getLogByName":["id","name"],"launchCluster":["id","uuid","search_for_java"],"readSettings":[],"createCluster":["options"],"getGlobalProfile":[],"isClusterRunning":["cluster_id"],"searchPackages":["provider","query"],"getRunningProcessesByClusterId":["cluster_id"],"refreshAccounts":[],"getDefaultUser":["fallback"],"getUsers":[],"getPackage":["provider","slug"],"getPackageBody":["provider","body"],"getClusters":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"setDiscordRPCMessage":["message"],"updateClusterProfile":["name","profile"],"setClusterStage":["id","stage"],"getUser":["uuid"],"killProcess":["pid"],"getWorlds":["id"],"installModpack":["modpack","cluster_id"],"open":["input"],"createSettingsProfile":["name"],"getProcessLogTail":["id","max_lines"],"getMultiplePackages":["provider","slugs"],"getRunningProcesses":[],"getLoadersForVersion":["mc_version"],"getProfileOrDefault":["name"],"writeSettings":["setting"],"getScreenshots":["id"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"changeCape":["access_token","cape_uuid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'debug': { openDevTools: () => Promise, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getOsDistro: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise, +getFullDebugInfo: () => Promise, +getFullDebugInfoParsed: () => Promise, +getFullDebugInfoParsedString: () => Promise }, +'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -372,11 +392,6 @@ removeCape: (accessToken: string) => Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, open: (input: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, 'oneclient': { getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise, getVersions: () => Promise, @@ -387,22 +402,7 @@ downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleN updateBundlePackages: (clusterId: number) => Promise, isBundleSyncing: () => Promise, cacheArt: (path: string) => Promise, -refreshArt: (path: string) => Promise }, -'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getOsDistro: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise, -getFullDebugInfo: () => Promise, -getFullDebugInfoParsed: () => Promise, -getFullDebugInfoParsedString: () => Promise } }; +refreshArt: (path: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx index ff5d9718..bebf92c4 100644 --- a/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx +++ b/apps/oneclient/frontend/src/components/overlay/SuperSecretDevOptions.tsx @@ -35,6 +35,10 @@ export function SuperSecretDevOptions() { Super Secret Dev Options
+ } title="Log Debug Info"> + + + } title="Tanstack Dev Tools"> diff --git a/apps/oneclient/frontend/src/routes/app/settings/developer.tsx b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx index 196cb999..6c5ec3de 100644 --- a/apps/oneclient/frontend/src/routes/app/settings/developer.tsx +++ b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx @@ -31,6 +31,9 @@ function RouteComponent() { Dev Tools + } title="Log Debug Info"> + + Date: Wed, 4 Mar 2026 09:40:06 +0800 Subject: [PATCH 12/12] fix rust compiler --- packages/core/src/api/tauri/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/tauri/debug.rs b/packages/core/src/api/tauri/debug.rs index 69c6fea5..4173e356 100644 --- a/packages/core/src/api/tauri/debug.rs +++ b/packages/core/src/api/tauri/debug.rs @@ -107,7 +107,7 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl { /// Returns the user's locale (like "en-AU"), or "UNKNOWN" if it couldn't be found. async fn get_locale(self) -> String { - locale().unwrap_or("UNKNOWN".to_string()) + locale().unwrap_or_else(|| "UNKNOWN".to_string()) } async fn get_type(self) -> String {