feat: better debug (#486)

* fix: tauri os plugin not being fully loaded

* feat: debug keybind

Bound to `CTRL+SHIFT+D` (and `ALT+F12`) and copies the debug info to the
clipboard

* refactor: __root.tx

Clean up a lot of code and move them into there own files

* feat: os distro in debug

* refactor: get_full_debug_info_parsed

* feat: made by info in settings

Also made it so that you can click on it to copy debug info

* fix: warning in oneclient/desktop/lib.rs

* refactor: copyDebugInfo

Moved it to the backend to later be used

* feat: log out debug info when oneclient launches

* `inDev` -> `In Dev`

* feat: toggle debug logging

* fix rust compiler
This commit is contained in:
Jacob 2026-03-04 09:46:11 +08:00 • committed by GitHub
parent 45db558013
commit fb5da05ad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 438 additions and 284 deletions

40
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

View file

@ -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

View file

@ -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"
]
}

View file

@ -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(())
}
@ -75,11 +81,17 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> 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| {
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(())
});

View file

@ -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:",

View file

@ -29,6 +29,10 @@ export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExis
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 }
@ -244,9 +248,9 @@ 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 = Record<string, never>
export type SettingsOsExtra = { enable_gamemode: boolean | null }
export type SkinVariant = "classic" | "slim"
@ -313,18 +317,27 @@ export type VersionType =
*/
"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<void>,
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<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> },
'debug': { openDevTools: () => Promise<void>,
isInDev: () => Promise<boolean>,
getArch: () => Promise<string>,
getFamily: () => Promise<string>,
getLocale: () => Promise<string | null>,
getLocale: () => Promise<string>,
getType: () => Promise<string>,
getPlatform: () => Promise<string>,
getOsVersion: () => Promise<string>,
getOsDistro: () => Promise<string>,
getGitCommitHash: () => Promise<string>,
getBuildTimestamp: () => Promise<string>,
getPackageVersion: () => Promise<string> },
getPackageVersion: () => Promise<string>,
getFullDebugInfo: () => Promise<DebugInfoData>,
getFullDebugInfoParsed: () => Promise<DebugInfoParsedLine[]>,
getFullDebugInfoParsedString: () => Promise<string> },
'core': { getClusters: () => Promise<ClusterModel[]>,
getClusterById: (id: number) => Promise<ClusterModel | null>,
removeCluster: (id: number) => Promise<null>,
@ -389,12 +402,7 @@ downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleN
updateBundlePackages: (clusterId: number) => Promise<ApplyBundleUpdatesResult>,
isBundleSyncing: () => Promise<boolean>,
cacheArt: (path: string) => Promise<string>,
refreshArt: (path: string) => Promise<null> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> } };
refreshArt: (path: string) => Promise<null> } };
export type { InferCommandOutput }

View file

@ -0,0 +1,21 @@
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());
return (
<Button
className="flex flex-col items-start p-4 text-xs text-fg-secondary"
color="ghost"
onClick={copyDebugInfo}
>
<p>OneClient by Polyfrost</p>
<p>Version {version}</p>
<p>{isInDev ? 'Development' : 'Release'} Build</p>
</Button>
);
}

View file

@ -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';

View file

@ -1,106 +1,7 @@
import type { DebugInfoArray } from '@/utils/debugInfo';
import { Overlay } from '@/components';
import { bindings } from '@/main';
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<{ 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 function useDebugInfo(): DebugInfoArray {
const [devInfo, setDevInfo] = useState<DebugInfoData>({
inDev: false,
platform: 'UNKNOWN',
arch: 'UNKNOWN',
family: 'UNKNOWN',
locale: 'UNKNOWN',
type: 'UNKNOWN',
osVersion: 'UNKNOWN',
commitHash: 'UNKNOWN',
buildTimestamp: 'UNKNOWN',
buildVersion: 'UNKNOWN',
});
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,
});
};
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 },
];
}
export function copyDebugInfo(debugInfo: DebugInfoArray) {
const timestamp = Math.floor(new Date().getTime() / 1000);
const lines = [
`**Data exported at:** <t:${timestamp}> (\`${timestamp}\`)`,
...debugInfo.map((lineData) => {
if (lineData.title === 'Build Timestamp')
return `**${lineData.title}:** <t:${lineData.value}> (\`${lineData.value}\`)`;
return `**${lineData.title}:** \`${lineData.value}\``;
}),
];
writeText(lines.join('\n'));
}
export function DebugInfo() {
const debugInfo = useDebugInfo();
@ -113,7 +14,6 @@ export function DebugInfo() {
}
export function RawDebugInfo({ debugInfo }: { debugInfo: DebugInfoArray }) {
const copy = () => copyDebugInfo(debugInfo);
return (
<>
<div>
@ -126,7 +26,7 @@ export function RawDebugInfo({ debugInfo }: { debugInfo: DebugInfoArray }) {
return <p key={lineData.title}>{line}</p>;
})}
</div>
<Button onPress={copy}>Copy Data</Button>
<Button onPress={copyDebugInfo}>Copy Data</Button>
</>
);
}

View file

@ -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';
@ -34,6 +35,10 @@ export function SuperSecretDevOptions() {
<Overlay.Title>Super Secret Dev Options</Overlay.Title>
<div>
<SettingsRow description="WARNING! This requires a restart to apply. Logs out debug info" icon={<Code02Icon />} title="Log Debug Info">
<SettingsSwitch setting={createSetting('log_debug_info')} />
</SettingsRow>
<SettingsRow description="Enable The Tanstack Dev Tools" icon={<Code02Icon />} title="Tanstack Dev Tools">
<SettingsSwitch setting={createSetting('show_tanstack_dev_tools')} />
</SettingsRow>

View file

@ -2,14 +2,13 @@ import type { QueryClient } from '@tanstack/react-query';
import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
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 { useEffect } from 'react';
import { RouterProvider } from 'react-aria-components';
interface AppRouterContext {
@ -27,99 +26,12 @@ export const Route = createRootRouteWithContext<AppRouterContext>()({
component: RootRoute,
});
type URLPath = Exclude<ToOptions['to'], undefined>;
const ResolvedPathNames: Record<URLPath, string> = {
'.': '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<string, any>) {
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 RootRoute() {
const router = useRouter();
const { setting } = useSettings();
useDiscordRPC();
useAutoUpdate();
useAutoUpdater();
useDebugKeybind();
return (
<RouterProvider
navigate={(to, options) => router.navigate({ to, ...options })}

View file

@ -31,6 +31,9 @@ function RouteComponent() {
</SettingsRow>
<SettingsRow.Header>Dev Tools</SettingsRow.Header>
<SettingsRow description="WARNING! This requires a restart to apply. Logs out debug info" icon={<Code02Icon />} title="Log Debug Info">
<SettingsSwitch setting={createSetting('log_debug_info')} />
</SettingsRow>
<SettingsRow
description="Enable The Tanstack Dev Tools"
title="Tanstack Dev Tools"

View file

@ -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() {
}}
/>
{/* <Info /> */}
<MadeBy />
</div>
<div className="h-full w-full flex flex-col">

View file

@ -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 (
<div className="flex flex-col h-full w-full">
@ -180,12 +178,7 @@ function AppShell({
<nav className="flex-1 p-4">
<Stepper currentStepIndex={currentStepIndex} steps={ONBOARDING_STEPS} />
</nav>
<div className="p-4 text-xs text-fg-secondary">
<p>OneClient by Polyfrost</p>
<p>Version {version}</p>
<p>{isInDev ? 'Development' : 'Release'} Build</p>
</div>
<MadeBy />
</div>
<div className={`flex-1 flex ${isFirstStep ? '' : 'bg-page'} flex-col relative`}>

View file

@ -0,0 +1,48 @@
import type { DebugInfoParsedLine } from '@/bindings.gen';
import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut';
import { bindings } from '@/main';
import { toast } from '@/utils/toast';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { register } from '@tauri-apps/plugin-global-shortcut';
import { useEffect, useState } from 'react';
export type DebugInfoArray = Array<DebugInfoParsedLine>;
export function useDebugInfo(): DebugInfoArray {
const [debugInfo, setDebugInfo] = useState<DebugInfoArray>([]);
useEffect(() => {
const fetchDevInfo = async () => {
const info = await bindings.debug.getFullDebugInfoParsed();
setDebugInfo(info);
};
void fetchDevInfo();
}, []);
return debugInfo;
}
export async function copyDebugInfo() {
const info = await bindings.debug.getFullDebugInfoParsedString();
writeText(info);
toast({
type: 'info',
title: 'Debug Info',
message: 'Debug info has been copied to clipboard',
});
}
export function useDebugKeybind() {
const handleKeybind = (event: ShortcutEvent) => {
if (event.state !== 'Pressed')
return;
copyDebugInfo();
};
useEffect(() => {
void (async () => {
await register('CommandOrControl+Shift+D', handleKeybind);
await register('Alt+F12', handleKeybind);
})();
}, []);
}

View file

@ -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<ToOptions['to'], undefined>;
export const ResolvedPathNames: Record<URLPath, string> = {
'.': '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<string, any>) {
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]);
}

View file

@ -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);
}
})();
}, []);
}

View file

@ -1,5 +1,40 @@
use std::time::{SystemTime, UNIX_EPOCH};
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)]
#[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 os_distro: 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,
}
impl DebugInfoParsedLine {
pub fn new<T: Into<String>, U: Into<String>>(title: T, value: U) -> Self {
Self {
title: title.into(),
value: value.into(),
}
}
}
#[taurpc::procedures(path = "debug")]
pub trait TauriLauncherDebugApi {
@ -16,7 +51,7 @@ pub trait TauriLauncherDebugApi {
async fn get_family() -> String;
#[taurpc(alias = "getLocale")]
async fn get_locale() -> Option<String>;
async fn get_locale() -> String;
#[taurpc(alias = "getType")]
async fn get_type() -> String;
@ -27,6 +62,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;
@ -35,6 +73,15 @@ 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<DebugInfoParsedLine>;
#[taurpc(alias = "getFullDebugInfoParsedString")]
async fn get_full_debug_info_parsed_string() -> String;
}
#[taurpc::ipc_type]
@ -58,8 +105,9 @@ impl TauriLauncherDebugApi for TauriLauncherDebugApiImpl {
family().to_string()
}
async fn get_locale(self) -> Option<String> {
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_else(|| "UNKNOWN".to_string())
}
async fn get_type(self) -> String {
@ -74,6 +122,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()
}
@ -85,4 +157,67 @@ 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,
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,
}
}
async fn get_full_debug_info_parsed(self) -> Vec<DebugInfoParsedLine> {
let info = self.clone().get_full_debug_info().await;
vec![
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),
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),
]
}
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:** <t:{timestamp}> (`{timestamp}`)"
));
for line_data in debug_info {
if line_data.title == "Build Timestamp" {
lines.push(format!(
"**{}:** <t:{}> (`{}`)",
line_data.title, line_data.value, line_data.value
));
} else {
lines.push(format!("**{}:** `{}`", line_data.title, line_data.value));
}
}
lines.join("\n")
}
}

View file

@ -11,15 +11,21 @@ pub async fn start_logger() {
use std::fs::OpenOptions;
use tracing_subscriber::fmt::time::ChronoLocal;
use crate::store::Dirs;
use crate::store::{Dirs, State};
let state = State::get().await.expect("failed to get state");
let enable_debug_logs = state.settings.read().await.log_debug_info;
let level = if enable_debug_logs {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
tracing_subscriber::EnvFilter::new(
Core::get()
.logger_filter
.clone()
.unwrap_or_else(|| format!("{}=debug", env!("CARGO_PKG_NAME"))),
)
tracing_subscriber::EnvFilter::new(format!(
"oneclient_gui={level},onelauncher_core={level}"
))
});
let mut console_fmt_layer = tracing_subscriber::fmt::layer()

View file

@ -21,6 +21,7 @@ pub struct Settings {
pub native_window_frame: bool,
#[cfg(feature = "tauri")]
pub log_debug_info: bool,
pub show_tanstack_dev_tools: bool,
}
@ -38,6 +39,7 @@ impl Default for Settings {
native_window_frame: false,
#[cfg(feature = "tauri")]
log_debug_info: tauri::is_dev(),
show_tanstack_dev_tools: tauri::is_dev(),
}
}

61
pnpm-lock.yaml generated
View file

@ -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

View file

@ -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