From e20f828bf8991dda8ee862952d6839d443166ae1 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:01:44 +0200 Subject: [PATCH] chore: Unignore generated bindings.ts, needed for action builds --- .gitignore | 1 - .vscode/settings.json | 3 +- apps/desktop/src/api/commands/mod.rs | 7 + apps/desktop/src/lib.rs | 3 +- apps/frontend/src/RootLayout.tsx | 4 +- apps/frontend/src/index.tsx | 38 +- apps/frontend/src/ui/hooks/useSettings.tsx | 5 +- .../src/ui/pages/settings/SettingsRoot.tsx | 9 +- packages/client/src/bindings.ts | 1182 +++++++++++++++++ packages/client/src/hooks/usePlausible.ts | 6 +- packages/client/src/types/index.ts | 3 - packages/client/src/utils/index.ts | 1 + packages/client/src/utils/programInfo.ts | 18 + 13 files changed, 1245 insertions(+), 35 deletions(-) create mode 100644 packages/client/src/bindings.ts create mode 100644 packages/client/src/utils/programInfo.ts diff --git a/.gitignore b/.gitignore index 02f7b27e..d341ec83 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ dev.db-journal .cargo/config.toml .deps/* vite.config.ts.timestamp* -packages/client/src/bindings.ts # Created by https://www.toptal.com/developers/gitignore/api/node,rust,yarn,linux,macos,windows,intellij+all,rust-analyzer,visualstudiocode,intellij,intellij+iml,git # Edit at https://www.toptal.com/developers/gitignore?templates=node,rust,yarn,linux,macos,windows,intellij+all,rust-analyzer,visualstudiocode,intellij,intellij+iml,git diff --git a/.vscode/settings.json b/.vscode/settings.json index 30c1bcd5..a582b844 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -188,5 +188,6 @@ "*.vue": "$(capture).*.ts, $(capture).*.js, $(capture).story.vue", "*.w": "$(capture).*.w, I$(capture).w", "*.xaml": "$(capture).xaml.cs" - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index 26557877..a3655106 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -80,10 +80,17 @@ macro_rules! collect_commands { install_update, // Other set_window_style, + get_program_info ] }}; } +#[specta::specta] +#[tauri::command] +pub fn get_program_info() -> Result { + Ok(super::statics::get_program_info()) +} + #[specta::specta] #[tauri::command] pub async fn get_minecraft_versions() -> Result, String> { diff --git a/apps/desktop/src/lib.rs b/apps/desktop/src/lib.rs index a0101f80..12a6475e 100644 --- a/apps/desktop/src/lib.rs +++ b/apps/desktop/src/lib.rs @@ -46,8 +46,7 @@ pub async fn run_app) + Send + 'static>( ) { let prebuild = tauri_specta::Builder::::new() .commands(collect_commands!()) - .events(collect_events!()) - .constant("PROGRAM_INFO", get_program_info()); + .events(collect_events!()); #[cfg(debug_assertions)] prebuild diff --git a/apps/frontend/src/RootLayout.tsx b/apps/frontend/src/RootLayout.tsx index 21f2b2c1..9fbeb693 100644 --- a/apps/frontend/src/RootLayout.tsx +++ b/apps/frontend/src/RootLayout.tsx @@ -1,4 +1,4 @@ -import { PROGRAM_INFO } from '@onelauncher/client/bindings'; +import { getProgramInfo } from '@onelauncher/client'; import AnimatedRoutes from '~ui/components/AnimatedRoutes'; import { onMount, type ParentProps } from 'solid-js'; import { MultiProvider } from './ui/components/MultiProvider'; @@ -12,7 +12,7 @@ import { SettingsProvider } from './ui/hooks/useSettings'; function RootLayout(props: ParentProps) { onMount(() => { - if (PROGRAM_INFO.dev_build !== true) + if (getProgramInfo().dev_build !== true) document.addEventListener('contextmenu', e => e.preventDefault()); }); diff --git a/apps/frontend/src/index.tsx b/apps/frontend/src/index.tsx index 7d46c96b..16dd0feb 100644 --- a/apps/frontend/src/index.tsx +++ b/apps/frontend/src/index.tsx @@ -20,27 +20,29 @@ import ClusterRoot from '~ui/pages/cluster/ClusterRoot'; import Onboarding from '~ui/pages/onboarding/Onboarding'; import SettingsRoot from '~ui/pages/settings/SettingsRoot'; import { render } from 'solid-js/web'; -import { bridge } from './imports'; import RootLayout from './RootLayout'; import HomePage from './ui/pages/Home'; import UpdatesPage from './ui/pages/Updates'; +import { getProgramInfo, initProgramInfo } from '@onelauncher/client'; -document.body.setAttribute('data-platform', bridge.PROGRAM_INFO.platform); +initProgramInfo().finally(() => { + document.body.setAttribute('data-platform', getProgramInfo().platform); -render(() => ( - - - - - } component={ClusterRoot} path="/clusters" /> - } component={SettingsRoot} path="/settings" /> - } component={BrowserRoot} path="/browser" /> - + render(() => ( + + + + + } component={ClusterRoot} path="/clusters" /> + } component={SettingsRoot} path="/settings" /> + } component={BrowserRoot} path="/browser" /> + - - -), document.getElementById('root') as HTMLElement); + + + ), document.getElementById('root') as HTMLElement); +}); diff --git a/apps/frontend/src/ui/hooks/useSettings.tsx b/apps/frontend/src/ui/hooks/useSettings.tsx index 19c440eb..b1abd66c 100644 --- a/apps/frontend/src/ui/hooks/useSettings.tsx +++ b/apps/frontend/src/ui/hooks/useSettings.tsx @@ -1,4 +1,5 @@ -import { PROGRAM_INFO, type Settings } from '@onelauncher/client/bindings'; +import type { Settings } from '@onelauncher/client/bindings'; +import { getProgramInfo } from '@onelauncher/client'; import { useBeforeLeave } from '@solidjs/router'; import { bridge } from '~imports'; import { type Context, createContext, createEffect, type ParentProps, Show, useContext } from 'solid-js'; @@ -44,7 +45,7 @@ export function SettingsProvider(props: ParentProps) { }, }; - if (PROGRAM_INFO.dev_build) + if (getProgramInfo().dev_build) // @ts-expect-error - Expose settings globally for debugging purposes window.onelauncherSettings = controller; diff --git a/apps/frontend/src/ui/pages/settings/SettingsRoot.tsx b/apps/frontend/src/ui/pages/settings/SettingsRoot.tsx index 09e2b054..477d4d01 100644 --- a/apps/frontend/src/ui/pages/settings/SettingsRoot.tsx +++ b/apps/frontend/src/ui/pages/settings/SettingsRoot.tsx @@ -1,4 +1,4 @@ -import { PROGRAM_INFO } from '@onelauncher/client/bindings'; +import { getProgramInfo } from '@onelauncher/client'; import { Route } from '@solidjs/router'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { Brush01Icon, CodeSnippet02Icon, MessageTextSquare01Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon, Users01Icon } from '@untitled-theme/icons-solid'; @@ -89,13 +89,16 @@ function Info() { }); }} > - {` + {(() => { + const PROGRAM_INFO = getProgramInfo(); + return ` Launcher Version: ${PROGRAM_INFO.launcher_version} Tauri Version: ${PROGRAM_INFO.tauri_version} Webview Version: ${PROGRAM_INFO.webview_version} Platform: ${PROGRAM_INFO.platform} ${PROGRAM_INFO.arch} bit Build: ${PROGRAM_INFO.dev_build ? 'dev' : 'release'} -`.trim()} +`.trim(); + })()} diff --git a/packages/client/src/bindings.ts b/packages/client/src/bindings.ts new file mode 100644 index 00000000..b3441062 --- /dev/null +++ b/packages/client/src/bindings.ts @@ -0,0 +1,1182 @@ +// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. + +/** user-defined commands */ + +/** tauri-specta globals */ + +import type { WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow'; +import { + invoke as TAURI_INVOKE, +} from '@tauri-apps/api/core'; +import * as TAURI_API_EVENT from '@tauri-apps/api/event'; + +export const commands = { + async authLogin(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('auth_login') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getUsers(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_users') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getUser(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_user', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getDefaultUser(fallback: boolean | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_default_user', { fallback }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async setDefaultUser(uuid: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('set_default_user', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async removeUser(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('remove_user', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async createCluster(props: CreateCluster): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('create_cluster', { props }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + /** + * Updates the cluster with the given UUID. The cluster only updates game setting fields + */ + async editGameSettings(uuid: string, newCluster: Cluster): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('edit_game_settings', { uuid, newCluster }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async editClusterMeta(uuid: string, name: string | null, iconPath: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('edit_cluster_meta', { uuid, name, iconPath }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async removeCluster(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('remove_cluster', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getCluster(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_cluster', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClusters(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_clusters') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClustersGrouped(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_clusters_grouped') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async runCluster(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('run_cluster', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClusterLogs(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_cluster_logs', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClusterLog(uuid: string, logName: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_cluster_log', { uuid, logName }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async uploadLog(uuid: string, logName: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('upload_log', { uuid, logName }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getScreenshots(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_screenshots', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getWorlds(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_worlds', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getRunningClusters(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_running_clusters') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProcessesByPath(path: ClusterPath): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_processes_by_path', { path }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async killProcess(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('kill_process', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async isClusterRunning(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('is_cluster_running', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getPidByUuid(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_pid_by_uuid', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getUserByProcess(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_user_by_process', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProcessStartedAt(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_process_started_at', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProcessesDetailedByPath(path: ClusterPath): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_processes_detailed_by_path', { path }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProcessDetailedById(uuid: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_process_detailed_by_id', { uuid }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getSettings(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_settings') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async setSettings(settings: Settings): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('set_settings', { settings }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getMinecraftVersions(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_minecraft_versions') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async importLauncherInstances(launcher: ImportType, path: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('import_launcher_instances', { launcher, path }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProviderPackage(provider: Providers, projectId: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_provider_package', { provider, projectId }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProviderPackages(provider: Providers, projectIds: string[]): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_provider_packages', { provider, projectIds }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getAllProviderPackageVersions(provider: Providers, projectId: string, gameVersions: string[] | null, loaders: Loader[] | null, page: number | null, pageSize: number | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_all_provider_package_versions', { provider, projectId, gameVersions, loaders, page, pageSize }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProviderPackageVersions(provider: Providers, versions: string[]): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_provider_package_versions', { provider, versions }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async searchProviderPackages(provider: Providers, query: ProviderSearchQuery): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('search_provider_packages', { provider, query }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProviderAuthors(provider: Providers, author: Author): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_provider_authors', { provider, author }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getPackageBody(provider: Providers, body: PackageBody): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_package_body', { provider, body }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async downloadProviderPackage(provider: Providers, packageId: string, clusterId: string, gameVersion: string | null, loader: Loader | null, packageVersion: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('download_provider_package', { provider, packageId, clusterId, gameVersion, loader, packageVersion }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClusterPackage(clusterPath: ClusterPath, packagePath: PackagePath, packageType: PackageType): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_cluster_package', { clusterPath, packagePath, packageType }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getClusterPackages(clusterPath: ClusterPath, packageType: PackageType): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_cluster_packages', { clusterPath, packageType }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async addClusterPackage(clusterPath: ClusterPath, packagePath: PackagePath, pkg: Package, packageType: PackageType | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('add_cluster_package', { clusterPath, packagePath, pkg, packageType }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async removeClusterPackage(clusterPath: ClusterPath, packagePath: PackagePath, packageType: PackageType): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('remove_cluster_package', { clusterPath, packagePath, packageType }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async syncClusterPackages(clusterPath: ClusterPath): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('sync_cluster_packages', { clusterPath }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async syncClusterPackagesByType(clusterPath: ClusterPath, packageType: PackageType): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('sync_cluster_packages_by_type', { clusterPath, packageType }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async checkForUpdate(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('check_for_update') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async installUpdate(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('install_update') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async setWindowStyle(custom: boolean): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('set_window_style', { custom }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async getProgramInfo(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_program_info') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, +}; + +/** user-defined events */ + +export const events = __makeEvents__<{ + clusterPayload: ClusterPayload; + ingressPayload: IngressPayload; + internetPayload: InternetPayload; + messagePayload: MessagePayload; + offlinePayload: OfflinePayload; + processPayload: ProcessPayload; +}>({ + clusterPayload: 'cluster-payload', + ingressPayload: 'ingress-payload', + internetPayload: 'internet-payload', + messagePayload: 'message-payload', + offlinePayload: 'offline-payload', + processPayload: 'process-payload', +}); + +/** user-defined constants */ + +/** user-defined types */ + +export type Author = { Team: { team: string; organization: string | null } } | { Users: ManagedUser[] }; +/** + * `OneLauncher` browser list type. + */ +export type BrowserListView = 'grid' | 'list'; +/** + * Represents a single Instance and installation of Minecraft + * Contains settings and identifiers on a per-Cluster basis, falling back to default settings for Options<> + */ +export interface Cluster { +/** + * The Uuid of a specific cluster. + */ + uuid: string; + /** + * The download stage that of a cluster. + */ + stage?: ClusterStage; + /** + * The core path of the cluster. + */ + path?: string; + /** + * The associated cluster metadata. + */ + meta: ClusterMeta; + /** + * The per-cluster JVM memory allocation options. + */ + memory?: Memory | null; + /** + * The per-cluster JVM options. + */ + java?: JavaOptions | null; + /** + * The per-cluster Minecraft window default resolution. + */ + resolution?: Resolution | null; + /** + * The per-cluster Minecraft window fullscreen status. + */ + force_fullscreen: boolean | null; + init_hooks?: InitHooks | null; +}; +/** + * Represents core Cluster metadata ([`Cluster#meta`]). + */ +export interface ClusterMeta { +/** + * The user-facing name of the cluster stored as a string. + */ + name: string; + /** + * The user-facing group of the cluster stored as a string. + */ + group?: string | null; + /** + * The associated Minecraft version of the cluster as last updated. + */ + mc_version: string; + /** + * The associated mod [`Loader`] as specified in the cluster. + */ + loader?: Loader; + /** + * The associated mod [`LoaderVersion`] if available. + */ + loader_version?: LoaderVersion | null; + /** + * The time that the cluster was created in [`DateTime`]. + */ + created_at?: string; + /** + * The time the cluster was last modified in [`DateTime`]. + */ + modified_at?: string; + /** + * The last time the cluster was played in [`DateTime`]. + * (Defaults to None if the cluster has never been played) + */ + played_at?: string | null; + /** + * The overall time played in seconds stored as a [`u64`]. + */ + overall_played?: bigint; + /** + * The recent time played stored as a [`u64`]. + */ + recently_played?: bigint; + /** + * The associated [`PackageData`] and modpack data for the cluster. + */ + package_data?: PackageData | null; + /** + * The user-facing cluster icon as a [`PathBuf`]. + */ + icon?: string | null; + /** + * The user-facing cluster icon as a URL string. + */ + icon_url?: string | null; +}; +/** + * Relative Path wrapper to be used as an identifer for a cluster path. + */ +export type ClusterPath = string; +export interface ClusterPayload { uuid: string; cluster_path: ClusterPath; path: string; name: string; event: ClusterPayloadType }; +export type ClusterPayloadType = 'created' | 'inserted' | 'synced' | 'edited' | 'deleted'; +/** + * Core Cluster stages used in package logic. + */ +export type ClusterStage = +/** + * Installed with no downloads left. + */ + 'installed' | +/** + * Downloading any sort of core metadata or mod file. + */ + 'downloading' | +/** + * Downloading a full pack file, which is granted lower priority due to the high network demand. + */ + 'pack_downloading' | +/** + * Not installed at all. + */ + 'not_installed'; +export interface CreateCluster { name: string; mc_version: string; mod_loader: Loader; loader_version: string | null; icon: string | null; icon_url: string | null; package_data: PackageData | null; skip: boolean | null; skip_watch: boolean | null }; +export interface DetailedProcess { uuid: string; user: string | null; started_at: string; pid: number }; +/** + * List of launcher types we support importing from. + */ +export type ImportType = +/** + * `MultiMC` based launchers + */ + 'MultiMC' | +/** + * Prism Launcher has its own category because it has different logic (also is objectively better than mmc) + */ + 'PrismLauncher' | +/** + * `GDLauncher` + */ + 'GDLauncher' | +/** + * Curseforge's launcher + */ + 'Curseforge' | +/** + * `ATLauncher` + */ + 'ATLauncher' | +/** + * Modrinth app. + */ + 'Modrinth' | +/** + * `TLauncher` + */ + 'TLauncher' | +/** + * FTB Launcher + */ + 'FTBLauncher' | +/** + * Technic + */ + 'Technic' | +/** + * Unknown import option (not widely adopted -> probably a custom launcher with a similar file structure to the above) + */ + 'Unknown'; +export interface IngressPayload { event: IngressType; ingress_uuid: string; fraction: number | null; message: string }; +export type IngressType = { type: 'initialize' } | { type: 'download_java'; version: number } | { type: 'download_loader'; cluster_path: string; cluster_name: string } | { type: 'sync_cluster'; cluster_path: string; cluster_name: string } | { type: 'copy_cluster'; import: string; cluster_name: string } | { type: 'sync_config'; new_path: string } | { type: 'archival'; cluster_path: string; cluster_name: string } | { type: 'download_package'; cluster_path: string; package_name: string; icon: string | null; package_id: string | null; package_version: string | null } | { type: 'download_pack'; cluster_path: string; package_name: string; icon: string | null; package_version: string }; +/** + * Global initialization hooks for all Minecraft clusters. + */ +export interface InitHooks { +/** + * Pre-launch hook. + */ + pre?: string | null; + /** + * Wrapper hook for the runtime of the game instance. + */ + wrapper?: string | null; + /** + * Post-launch hook. + */ + post?: string | null; +}; +export type InternetPayload = { event: 'InstallPackage'; id: string } | { event: 'InstallPack'; id: string } | { event: 'InstallPath'; path: string }; +/** + * Custom Java arguments on a per-cluster basis. + */ +export interface JavaOptions { +/** + * A custom java version from the global java store, if specified. + */ + custom_version?: JavaVersion | null; + /** + * Custom runtime arguments when running the cluster. + */ + custom_arguments?: string[] | null; + /** + * Custom environment variables when running the cluster. + */ + custom_env_arguments?: ([string, string])[] | null; +}; +/** + * A struct representing a single version of the Java Runtime Environment. + * Use [`locate_java`] to get an array of all located java instances on the machine. + * + * common java paths from under GPL 3.0 + * + * java check functionality from under MIT + */ +export interface JavaVersion { version: string; arch: string; path: string }; +/** + * A `HashMap` of all located and installed available Java versions + */ +export type JavaVersions = { [key in string]: JavaVersion }; +export interface License { id?: string; name?: string; url?: string | null }; +/** + * Available mod loaders to be used for a cluster. + */ +export type Loader = +/** + * The default vanilla loader, no modding supported. + */ + 'vanilla' | +/** + * The `MinecraftForge` Minecraft mod loader. + */ + 'forge' | +/** + * The `FabircMC` Minecraft mod loader. + */ + 'fabric' | +/** + * The `NeoForge` Minecraft mod loader. + */ + 'neoforge' | +/** + * The Quilt Minecraft mod loader. + */ + 'quilt' | +/** + * The Legacy Fabric port mod loader. + */ + 'legacyfabric'; +/** + * A version of a Minecraft mod loader + */ +export interface LoaderVersion { +/** + * The version ID of the loader + */ + id: string; + /** + * The URL of the version's manifest + */ + url: string; + /** + * Whether the loader is stable or not + */ + stable: boolean; +}; +/** + * Universal interface for managed package dependencies. + */ +export interface ManagedDependency { version_id: string | null; package_id: string | null; file_name: string | null; dependency_type: PackageDependency }; +/** + * Universal metadata for any managed package from a Mod distribution platform. + */ +export interface ManagedPackage { provider: Providers; id: string; package_type: PackageType; title: string; description: string; body: PackageBody; main: string; versions: string[]; game_versions: string[]; loaders: Loader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; downloads: bigint; followers: number; categories: string[]; optional_categories: string[] | null; license: License | null; author: Author; is_archived: boolean }; +/** + * Universal metadata for any managed user from a Mod distribution platform. + */ +export interface ManagedUser { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null }; +/** + * Universal managed package version of a package. + */ +export interface ManagedVersion { id: string; package_id: string; author: string; name: string; featured: boolean; version_display: string; changelog: string; changelog_url: string | null; published: string; downloads: number; version_type: ManagedVersionReleaseType; files: ManagedVersionFile[]; is_available: boolean; deps: ManagedDependency[]; game_versions: string[]; loaders: Loader[] }; +/** + * Universal interface for managed package files. + */ +export interface ManagedVersionFile { url: string; file_name: string; primary: boolean; size: bigint; file_type: PackageFile | null; hashes: { [key in string]: string } }; +/** + * Universal version release type + */ +export type ManagedVersionReleaseType = 'Release' | 'Alpha' | 'Beta' | 'Snapshot'; +/** + * Global memory settings across all clusters. + */ +export interface Memory { +/** + * Maximum amount of Java memory available globally. + */ + maximum: number; + /** + * Minimum amount of Java memory available globally. + */ + minimum: number; +}; +export interface MessagePayload { message: string }; +/** + * A structure of all needed Minecraft credentials for logging in and account management. + */ +export interface 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 interface OfflinePayload { offline: boolean }; +/** + * A struct that represents a Package. + */ +export interface Package { sha512: string; meta: PackageMetadata; file_name: string; disabled: boolean }; +export type PackageBody = { Url: string } | { Markdown: string }; +/** + * Optional data used to link a specific cluster to a package project. + */ +export interface PackageData { +/** + * The package ID as a String. + */ + package_id: string | null; + /** + * The version of the package as a String. + */ + version_id: string | null; + /** + * Whether or not the current package is locked (for legacy modpack support). + */ + locked?: boolean | null; +}; +/** + * The type of a [`ManagedDependency`]. + */ +export type PackageDependency = 'Required' | 'Optional' | 'Incompatible' | 'Embedded'; +/** + * The file type of a [`Package`]. + */ +export type PackageFile = 'required_pack' | 'optional_pack' | 'unknown'; +/** + * Metadata that represents a [`Package`]. + */ +export type PackageMetadata = { type: 'managed'; package_id: string; provider: Providers; package_type: PackageType; title: string; version_id: string; version_formatted: string } | { type: 'mapped'; title: string | null; description: string | null; authors: string[]; version: string | null; icon: string | null; package_type: PackageType | null } | { type: 'unknown' }; +/** + * Relative [`PathBuf`] for a specific [`Package`] of a [`Cluster`]. + */ +export type PackagePath = string; +/** + * The Client/Server side type of a [`Package`]. + */ +export type PackageSide = 'required' | 'optional' | 'unsupported' | 'unknown'; +/** + * Represents types of packages handled by the launcher. + */ +export type PackageType = +/** + * Represents a mod jarfile + */ + 'mod' | +/** + * Represents a datapack file + */ + 'datapack' | +/** + * Represents a resourcepack file + */ + 'resourcepack' | +/** + * Represents a shaderpack file + */ + 'shaderpack' | +/** + * Represents a modpack file + */ + 'modpack'; +export interface Pagination { index: number; pageSize: number; resultCount: number; totalCount: number }; +export interface ProcessPayload { uuid: string; pid: number; event: ProcessPayloadType; message: string }; +export type ProcessPayloadType = 'started' | 'modified' | 'finished' | 'logging'; +export interface ProgramInfo { launcher_version: string; webview_version: string; tauri_version: string; dev_build: boolean; platform: string; arch: string }; +export interface ProviderSearchQuery { query: string | null; limit: number | null; offset: number | null; game_versions: string[] | null; categories: string[] | null; loaders: Loader[] | null; package_types: PackageType[] | null; open_source: boolean | null }; +export interface ProviderSearchResults { provider: Providers; results: SearchResult[]; total: number }; +/** + * Providers for content packages + */ +export type Providers = 'Modrinth' | 'Curseforge'; +/** + * Global Minecraft resolution. + */ +export type Resolution = [number, number]; +export interface SearchResult { slug: string; title: string; description: string; categories?: string[]; client_side: PackageSide; server_side: PackageSide; project_type: PackageType; downloads: bigint; icon_url?: string; project_id: string; author: string; display_categories?: string[]; versions: string[]; follows: number; date_created: string; date_modified: string }; +/** + * A global settings state for the launcher. + */ +export interface Settings { +/** + * A `OneLauncher` [`Theme`] managed by the core GUI. + */ + theme: Theme; + /** + * A global browser list view for the `OneLauncher` GUI. + */ + browser_list_view?: BrowserListView; + /** + * Does not ask for confirmation when closing the `OneLauncher` GUI + */ + hide_close_prompt?: boolean; + /** + * Disables animations in the `OneLauncher` GUI + */ + disable_animations?: boolean; + /** + * A global fullscreen Minecraft state. + */ + force_fullscreen?: boolean; + /** + * Whether to allow launching the same cluster under the same account. + */ + allow_parallel_running_clusters?: boolean; + /** + * Whether to launch Feral Gamemode on Linux systems. + */ + enable_gamemode?: boolean; + /** + * A global default [`Resolution`] for Minecraft. + */ + resolution: Resolution; + /** + * A global [`JavaVersions`] list and default version. + */ + java_versions: JavaVersions; + /** + * A global [`Memory`] settings store for Java memory settings. + */ + memory: Memory; + /** + * Global and default initialization hooks . + */ + init_hooks: InitHooks; + /** + * Global and default custom Java arguments. + */ + custom_java_args: string[]; + /** + * Global and default custom environment variables. + */ + custom_env_args: ([string, string])[]; + /** + * Global and default maximum [`Semaphore`] I/O operations. + */ + max_async_io_operations: bigint; + /** + * Global and default maximum [`Semaphore`] HTTP operations. + */ + max_async_fetches: bigint; + /** + * The [`CURRENT_FORMAT_VERSION`] of this settings file. + */ + version: number; + /** + * Whether or not to disable Discord IPC. + */ + disable_discord?: boolean; + /** + * Whether or not to enable a debug mode in the launcher. + */ + debug_mode?: boolean; + /** + * Whether or not to disable Plausible and Sentry analytics. + */ + disable_analytics?: boolean; + /** + * The core global config directory stored as a [`PathBuf`]. + */ + config_dir?: string | null; + /** + * Whether or not to minimize the launcher upon a game launch. + */ + hide_on_launch?: boolean; + /** + * Enable/disable custom window decorations. + */ + custom_frame?: boolean; + /** + * Completed onboarding. + */ + onboarding_completed?: boolean; +}; +/** + * A `OneLauncher` theme managed by the GUI. + */ +export type Theme = +/** + * A default Dark theme. + */ + 'dark' | +/** + * A default Light theme. + */ + 'light' | +/** + * OLED Dark Theme. + */ + 'oled' | +/** + * Cute and colorful theme. + */ + 'cat'; +export interface Update { version: string }; +/** + * A game version of Minecraft + */ +export interface 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'; + +interface __EventObj__ { + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; +} + +export type Result = + | { status: 'ok'; data: T } + | { status: 'error'; error: E }; + +function __makeEvents__>( + mappings: Record, +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case 'listen': + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case 'once': + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case 'emit': + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); +} diff --git a/packages/client/src/hooks/usePlausible.ts b/packages/client/src/hooks/usePlausible.ts index 5e1d5876..c1831cbb 100644 --- a/packages/client/src/hooks/usePlausible.ts +++ b/packages/client/src/hooks/usePlausible.ts @@ -1,11 +1,11 @@ import type { PlausibleOptions as PlausibleTrackerOptions } from 'plausible-tracker'; -import type { ProgramInfo } from '../types'; +import type { ProgramInfo } from '../bindings'; import Plausible from 'plausible-tracker'; import { createEffect, createSignal } from 'solid-js'; import { createMutable } from 'solid-js/store'; -import { PROGRAM_INFO } from '../bindings'; import { createPersistedMutable, useSolidStore } from '../library'; +import { getProgramInfo } from '../utils'; import { useDebugState } from './useDebug'; /** @@ -30,7 +30,7 @@ export const plausibleState: PlausibleState = createPersistedMutable( createMutable({ shareFullTelemetry: false, // false by default platform: 'unknown', - programInfo: PROGRAM_INFO, + programInfo: getProgramInfo(), }), ); diff --git a/packages/client/src/types/index.ts b/packages/client/src/types/index.ts index 4a83d4a2..488df36a 100644 --- a/packages/client/src/types/index.ts +++ b/packages/client/src/types/index.ts @@ -1,4 +1 @@ -import type { PROGRAM_INFO } from '../bindings'; - -export type ProgramInfo = typeof PROGRAM_INFO; export type MakeOptional = Omit & Partial>; diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts index 98d410b6..0118f5fc 100644 --- a/packages/client/src/utils/index.ts +++ b/packages/client/src/utils/index.ts @@ -1,5 +1,6 @@ export * from './highlight'; export * from './markdown'; export * from './modrinth'; +export * from './programInfo'; export * from './sorting'; export * from './timer'; diff --git a/packages/client/src/utils/programInfo.ts b/packages/client/src/utils/programInfo.ts new file mode 100644 index 00000000..dd5682df --- /dev/null +++ b/packages/client/src/utils/programInfo.ts @@ -0,0 +1,18 @@ +import { commands, type ProgramInfo } from '../bindings'; + +let _programInfo: ProgramInfo = { + arch: 'unknown', + platform: 'unknown', + dev_build: true, + launcher_version: 'unknown', + tauri_version: 'unknown', + webview_version: 'unknown', +}; + +export async function initProgramInfo() { + const result = await commands.getProgramInfo(); + if (result.status === 'ok') + _programInfo = result.data; +} + +export const getProgramInfo = () => _programInfo;