From 4cb6abddd3701ff722a7490ea18b4a0507e96656 Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:15:54 +0000 Subject: [PATCH 1/9] fix: contributing.md urls (#368) --- CONTRIBUTING.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2d4c777..e7b1e723 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,15 +35,15 @@ This project uses [`Cargo`] and [`pnpm`]. Make ensure you have them installed be To make changes locally, follow these steps: -1. Clone the repository: `git clone https://github.com/polyfrost/onelauncher` -2. Navigate to the project directory: `cd onelauncher` +1. Clone the repository: `git clone https://github.com/Polyfrost/OneLauncher` +2. Navigate to the project directory: `cd OneLauncher` 3. Configure your system environment for OneLauncher development: 1. For Linux users, run: `./packages/scripts/setup.sh` > The [Unix script] will check if Rust and pnpm are installed then proceed to install Clang, NASM, LLVM, libvips, [Tauri essentials] and any other required dependencies for OneLauncher to build. 2. For macOS users, run: `./packages/scripts/setup.sh` > The [Unix script] will check if Rust, pnpm and Xcode are installed and proceed to use Homebrew to install NASM, [Tauri essentials] and install any other required dependencies for OneLauncher to build. - 3. For Windows users, run in PowerShell: `.\packages\scripts\setup.ps1` - > The [Unix script] will install pnpm, LLVM, C++ build tools, NASM, Rust + Cargo, Rust tools, Edge Webview 2, [Tauri essentials] and any other required dependencies for OneLauncher to build. + 3. For Windows users, run in PowerShell: `powershell -ExecutionPolicy Bypass -File .\packages\scripts\setup.ps1` + > The [Windows script] will install pnpm, LLVM, C++ build tools, NASM, Rust + Cargo, Rust tools, Edge Webview 2, [Tauri essentials] and any other required dependencies for OneLauncher to build. 4. Install dependencies: `pnpm i` 5. Prepare your cargo installation: `pnpm prep` @@ -98,11 +98,11 @@ Check out the [i18n README](apps/desktop/locales/README.md) for more information This CONTRIBUTING.md file was inspired by the [`github/docs` CONTRIBUTING.md] file, and we extend our gratitude to the original authors. [Tauri essentials]: https://v2.tauri.app/start/prerequisites/ -[Unix script]: https://github.com/polyfrost/onelauncher/blob/main/packages/scripts/setup.sh -[Windows script]: https://github.com/polyfrost/onelauncher/blob/main/packages/scripts/setup.ps1 +[Unix script]: https://github.com/Polyfrost/OneLauncher/blob/oneclient/main/packages/scripts/setup.sh +[Windows script]: https://github.com/Polyfrost/OneLauncher/blob/oneclient/main/packages/scripts/setup.ps1 [`cargo`]: https://doc.rust-lang.org/cargo/getting-started/installation.html [`pnpm`]: https://pnpm.io/installation -[Labels]: https://github.com/polyfrost/onelauncher/labels +[Labels]: https://github.com/Polyfrost/OneLauncher/labels [the `pnpm` CLI]: https://pnpm.io/pnpm-cli [Collaborating with pull requests]: https://docs.github.com/en/github/collaborating-with-pull-requests [GitHub flow]: https://docs.github.com/en/get-started/quickstart/github-flow @@ -111,8 +111,8 @@ This CONTRIBUTING.md file was inspired by the [`github/docs` CONTRIBUTING.md] fi [Code of Conduct]: ./CODE_OF_CONDUCT.md [README]: ./README.md [search if a related issue has already been reported]: https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments -[issue form]: https://github.com/polyfrost/onelauncher/issues/new/choose -[existing issues]: https://github.com/polyfrost/onelauncher/issues +[issue form]: https://github.com/Polyfrost/OneLauncher/issues/new/choose +[existing issues]: https://github.com/Polyfrost/OneLauncher/issues [`github/docs` CONTRIBUTING.md]: https://github.com/github/docs/blob/main/CONTRIBUTING.md [link your PR to the issue]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue [allow maintainer edits]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork From 549bb8e8a330e9e19df1eba3c218f27413d54e62 Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Fri, 26 Sep 2025 05:41:37 +0000 Subject: [PATCH 2/9] feat: oneclient remove account (#369) --- apps/oneclient/frontend/src/bindings.gen.ts | 18 +++---- .../components/overlay/RemoveAccountModal.tsx | 49 +++++++++++++++++++ .../frontend/src/components/overlay/index.ts | 1 + .../frontend/src/routes/app/accounts.tsx | 49 ++++++++++++++----- 4 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 apps/oneclient/frontend/src/components/overlay/RemoveAccountModal.tsx diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index d9c742d8..9e4e7439 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -181,7 +181,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = { enable_gamemode: boolean | null } +export type SettingsOsExtra = Record export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -244,8 +244,11 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"open":["input"],"setDefaultUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"getClusterById":["id"],"getDefaultUser":["fallback"],"removeCluster":["id"],"getProfileOrDefault":["name"],"getRunningProcessesByClusterId":["cluster_id"],"getWorlds":["id"],"fetchMinecraftProfile":["uuid"],"getUser":["uuid"],"getLogByName":["id","name"],"writeSettings":["setting"],"installModpack":["modpack","cluster_id"],"killProcess":["pid"],"updateClusterProfile":["name","profile"],"getUsersFromAuthor":["provider","author"],"getUsers":[],"readSettings":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getPackage":["provider","slug"],"updateClusterById":["id","request"],"getLogs":["id"],"getGameVersions":[],"getGlobalProfile":[],"searchPackages":["provider","query"],"getRunningProcesses":[],"isClusterRunning":["cluster_id"],"getMultiplePackages":["provider","slugs"],"getLoadersForVersion":["mc_version"],"openMsaLogin":[],"removeUser":["uuid"],"createCluster":["options"],"getPackageBody":["provider","body"],"launchCluster":["id","uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}' } -export type Router = { 'core': { getClusters: () => Promise, +const ARGS_MAP = { 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"],"openDevTools":[]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } +export type Router = { 'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise }, +'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -282,14 +285,11 @@ getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise Promise, fetchMinecraftProfile: (uuid: string) => Promise, open: (input: string) => Promise }, -'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise } }; +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/overlay/RemoveAccountModal.tsx b/apps/oneclient/frontend/src/components/overlay/RemoveAccountModal.tsx new file mode 100644 index 00000000..a9439b1b --- /dev/null +++ b/apps/oneclient/frontend/src/components/overlay/RemoveAccountModal.tsx @@ -0,0 +1,49 @@ +import type { MinecraftCredentials } from '@/bindings.gen'; +import { AccountAvatar } from '@/components/AccountAvatar'; +import { Button } from '@onelauncher/common/components'; +import { Overlay } from './Overlay'; + +export function RemoveAccountModal({ + profile, + onPress, +}: { + profile: MinecraftCredentials; + onPress: () => void; +}) { + return ( + + Are you sure? + +

Do you want to remove {profile.username} from you're accounts?

+

This cannot be undone

+ + + +
+ ); +} + +function AccountRow({ + profile, +}: { + profile: MinecraftCredentials; +}) { + return ( +
+ + +
+

{profile.username}

+

{profile.id}

+
+
+ ); +} diff --git a/apps/oneclient/frontend/src/components/overlay/index.ts b/apps/oneclient/frontend/src/components/overlay/index.ts index c0ccba68..913737fa 100644 --- a/apps/oneclient/frontend/src/components/overlay/index.ts +++ b/apps/oneclient/frontend/src/components/overlay/index.ts @@ -2,4 +2,5 @@ export * from './AccountPopup'; export * from './AddAccountModal'; export * from './Overlay'; export * from './Popup'; +export * from './RemoveAccountModal'; export * from './Toasts'; diff --git a/apps/oneclient/frontend/src/routes/app/accounts.tsx b/apps/oneclient/frontend/src/routes/app/accounts.tsx index 9bcad5de..07e3114d 100644 --- a/apps/oneclient/frontend/src/routes/app/accounts.tsx +++ b/apps/oneclient/frontend/src/routes/app/accounts.tsx @@ -3,12 +3,14 @@ import type { ButtonProps } from '@onelauncher/common/components'; import { AccountAvatar, SheetPage, SkinViewer } from '@/components'; import { AddAccountModal } from '@/components/overlay'; import { Overlay } from '@/components/overlay/Overlay'; +import { RemoveAccountModal } from '@/components/overlay/RemoveAccountModal'; import { usePlayerProfile } from '@/hooks/usePlayerProfile'; import { bindings } from '@/main'; import { useCommandMut, useCommandSuspense } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; +import { Trash01Icon } from '@untitled-theme/icons-react'; import { Button as AriaButton, DialogTrigger } from 'react-aria-components'; import { twMerge } from 'tailwind-merge'; @@ -85,12 +87,23 @@ function AccountList() { }); }, }); + const { mutate: removeUser } = useCommandMut(bindings.core.removeUser, { + onSuccess() { + queryClient.invalidateQueries({ + queryKey: ['getDefaultUser'], + }); + queryClient.invalidateQueries({ + queryKey: ['getUsers'], + }); + }, + }); return (
{accounts.map(account => ( removeUser(account.id)} onPress={() => setDefaultUser(account.id)} profile={account} selected={currentAccount?.id === account.id} @@ -108,10 +121,12 @@ function AccountRow({ selected = false, profile, onPress, + onDelete, }: { selected?: boolean; profile: MinecraftCredentials; onPress: () => void; + onDelete: () => void; }) { const { isError } = usePlayerProfile(profile.id); @@ -125,19 +140,31 @@ function AccountRow({ )} onPress={onPress} > - +
+
+ -
-

- {profile.username} - {isError && (Error fetching online profile)} -

-

{profile.id}

+
+

+ {profile.username} + {isError && (Error fetching online profile)} +

+

{profile.id}

+
+
+ +
+ + + + + + + +
- - {/* */} ); } From 589f7048f49fe6b4365844df87461cb0da6001ba Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:51:54 +0000 Subject: [PATCH 3/9] feat: oneclient settings developer (#371) Open up the onboarding option coz it was annoying me --- apps/oneclient/frontend/src/bindings.gen.ts | 14 ++++----- .../src/routes/app/settings/developer.tsx | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 apps/oneclient/frontend/src/routes/app/settings/developer.tsx diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 9e4e7439..4cafb6f3 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -244,11 +244,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"],"openDevTools":[]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } -export type Router = { 'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise }, -'core': { getClusters: () => Promise, +const ARGS_MAP = { 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } +export type Router = { 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -285,11 +282,14 @@ getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise Promise, fetchMinecraftProfile: (uuid: string) => Promise, open: (input: string) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise } }; +'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/routes/app/settings/developer.tsx b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx new file mode 100644 index 00000000..66cb6ef1 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/settings/developer.tsx @@ -0,0 +1,30 @@ +import SettingsRow from '@/components/SettingsRow'; +import { Button } from '@onelauncher/common/components'; +import { createFileRoute, Link } from '@tanstack/react-router'; +import { Truck01Icon } from '@untitled-theme/icons-react'; +import Sidebar from './route'; + +export const Route = createFileRoute('/app/settings/developer')({ + component: RouteComponent, +}) + +function RouteComponent() { + return ( + +
+

General

+ + Folders and Files + } + title="Onboarding" + > + + + + +
+
+ ); +} From 403ea1fab15beb3a44f16b039b4d9f5f8ab8b920 Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:06:28 +0000 Subject: [PATCH 4/9] feat: oneclient onboarding prompt for account (#372) --- apps/oneclient/frontend/src/bindings.gen.ts | 18 ++-- .../src/routes/onboarding/account.tsx | 94 ++++++++++++++++++- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 4cafb6f3..607296c2 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -244,8 +244,14 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } -export type Router = { 'core': { getClusters: () => Promise, +const ARGS_MAP = { 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}' } +export type Router = { 'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -283,13 +289,7 @@ installModpack: (modpack: ModpackArchive, clusterId: number) => Promise, fetchMinecraftProfile: (uuid: string) => Promise, open: (input: string) => Promise }, 'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise } }; +openCluster: (folderName: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/routes/onboarding/account.tsx b/apps/oneclient/frontend/src/routes/onboarding/account.tsx index 60a283e7..3159f724 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/account.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/account.tsx @@ -1,9 +1,93 @@ -import { createFileRoute } from '@tanstack/react-router' +import type { MinecraftCredentials } from '@/bindings.gen'; +import { AccountAvatar, SkinViewer } from '@/components'; +import { usePlayerProfile } from '@/hooks/usePlayerProfile'; +import { bindings } from '@/main'; +import { useCommandMut } from '@onelauncher/common'; +import { Button } from '@onelauncher/common/components'; +import { useQueryClient } from '@tanstack/react-query'; +import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/onboarding/account')({ - component: RouteComponent, -}) + component: RouteComponent, +}); -function RouteComponent() { - return
Hello "/onboarding/account"!
+function Viewer({ + credentials, +}: { + credentials: MinecraftCredentials; +}) { + const { data: profile } = usePlayerProfile(credentials.id); + return ( + + ); +} + +function AccountPreview({ + profile, +}: { + profile: MinecraftCredentials; +}) { + return ( +
+
+ +
+
+ +
+

{profile.username}

+

{profile.id}

+
+
+
+ ); +} + +// TODO: Block the next button until the user logs in (beg lynith to do it coz I spent 20 minutes on it) +function RouteComponent() { + const queryClient = useQueryClient(); + + const { data: profile, isPending, mutate: login } = useCommandMut(bindings.core.openMsaLogin, { + onSuccess() { + queryClient.invalidateQueries({ + queryKey: ['getUsers'], + }); + queryClient.invalidateQueries({ + queryKey: ['getDefaultUser'], + }); + }, + }); + + const onClick = () => { + login(); + }; + + return ( +
+

Account

+

Before you continue, we require you to own a copy of Minecraft: Java Edition.

+ {profile + ? ( + <> + + + ) + : ( + + )} +
+ ); } From 96c0ca4d5af8da0ca6cdf54b5e77a83500d1c230 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sat, 27 Sep 2025 10:19:35 +0200 Subject: [PATCH 5/9] fix: account popup not refetching the default user --- apps/oneclient/frontend/src/bindings.gen.ts | 18 +++++++++--------- .../src/components/overlay/AccountPopup.tsx | 6 ++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 607296c2..0faf9f6d 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -181,7 +181,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = Record +export type SettingsOsExtra = { enable_gamemode: boolean | null } export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -244,13 +244,15 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"getLogs":["id"],"getMultiplePackages":["provider","slugs"],"installModpack":["modpack","cluster_id"],"removeUser":["uuid"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"getDefaultUser":["fallback"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getWorlds":["id"],"open":["input"],"getLoadersForVersion":["mc_version"],"readSettings":[],"writeSettings":["setting"],"fetchMinecraftProfile":["uuid"],"getGameVersions":[],"updateClusterById":["id","request"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"killProcess":["pid"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getClusterById":["id"]}' } -export type Router = { 'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, +const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'core':'{"openMsaLogin":[],"getRunningProcesses":[],"launchCluster":["id","uuid"],"getWorlds":["id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"searchPackages":["provider","query"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getLoadersForVersion":["mc_version"],"removeCluster":["id"],"getUser":["uuid"],"setDefaultUser":["uuid"],"getUsersFromAuthor":["provider","author"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"getProfileOrDefault":["name"],"fetchMinecraftProfile":["uuid"],"updateClusterProfile":["name","profile"],"getUsers":[],"killProcess":["pid"],"createCluster":["options"],"removeUser":["uuid"],"getLogByName":["id","name"],"getGlobalProfile":[],"getGameVersions":[],"getClusterById":["id"],"getScreenshots":["id"],"writeSettings":["setting"],"getClusters":[],"installModpack":["modpack","cluster_id"],"getDefaultUser":["fallback"],"open":["input"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"updateClusterById":["id","request"],"readSettings":[],"getPackageBody":["provider","body"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise }, 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, @@ -287,9 +289,7 @@ downloadPackage: (provider: Provider, packageId: string, versionId: string, clus getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, installModpack: (modpack: ModpackArchive, clusterId: number) => Promise, fetchMinecraftProfile: (uuid: string) => Promise, -open: (input: string) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise } }; +open: (input: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx b/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx index d3cf6579..cc9dfe8b 100644 --- a/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx +++ b/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx @@ -15,8 +15,10 @@ export function AccountPopup() { const users = useCommand(['getUsers'], bindings.core.getUsers); const defaultUser = useCommand(['getDefaultUser'], () => bindings.core.getDefaultUser(false)); - const setDefaultUser = (user: MinecraftCredentials) => { - bindings.core.setDefaultUser(user.id); + const setDefaultUser = async (user: MinecraftCredentials) => { + await bindings.core.setDefaultUser(user.id); + users.refetch(); + defaultUser.refetch(); }; return ( From 3b8afb32890a0987241081c816792de096e7327f Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sat, 27 Sep 2025 11:11:45 +0200 Subject: [PATCH 6/9] feat: mojang account configuration (skins / capes) --- packages/core/src/api/tauri/commands.rs | 54 +++++++++ packages/core/src/utils/http.rs | 34 +++--- packages/core/src/utils/minecraft.rs | 139 +++++++++++++++++++++++- 3 files changed, 208 insertions(+), 19 deletions(-) diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 560057f5..fca54f69 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -193,11 +193,33 @@ pub trait TauriLauncherApi { // endregion: modpack // MARK: API: minecraft + // region: minecraft #[taurpc(alias = "fetchMinecraftProfile")] async fn fetch_player_profile( uuid: String, ) -> LauncherResult; + #[taurpc(alias = "fetchLoggedInProfile")] + async fn fetch_logged_in_profile( + access_token: String, + ) -> LauncherResult; + + #[taurpc(alias = "uploadSkinBytes")] + async fn upload_skin_bytes( + access_token: String, + skin_data: Vec, + image_format: String, + skin_variant: crate::utils::minecraft::SkinVariant, + ) -> LauncherResult; + + #[taurpc(alias = "changeSkin")] + async fn change_skin( + access_token: String, + skin_url: String, + skin_variant: crate::utils::minecraft::SkinVariant, + ) -> LauncherResult; + // endregion: minecraft + // MARK: API: Other async fn open(input: String) -> LauncherResult<()>; } @@ -674,6 +696,38 @@ impl TauriLauncherApi for TauriLauncherApiImpl { ) -> LauncherResult { crate::utils::minecraft::fetch_player_profile(&uuid).await } + + async fn fetch_logged_in_profile( + self, + access_token: String, + ) -> LauncherResult { + crate::utils::minecraft::fetch_logged_in_profile(&access_token).await + } + + async fn upload_skin_bytes( + self, + access_token: String, + skin_data: Vec, + image_format: String, + skin_variant: crate::utils::minecraft::SkinVariant, + ) -> LauncherResult { + crate::utils::minecraft::upload_skin_bytes( + &access_token, + skin_data, + &image_format, + skin_variant, + ) + .await + } + + async fn change_skin( + self, + access_token: String, + skin_url: String, + skin_variant: crate::utils::minecraft::SkinVariant, + ) -> LauncherResult { + crate::utils::minecraft::change_skin(&access_token, &skin_url, skin_variant).await + } // endregion: minecraft // MARK: Impl: Other diff --git a/packages/core/src/utils/http.rs b/packages/core/src/utils/http.rs index aaf7d58d..d37612a6 100644 --- a/packages/core/src/utils/http.rs +++ b/packages/core/src/utils/http.rs @@ -5,7 +5,7 @@ use std::path::Path; use std::sync::{Arc, LazyLock}; use bytes::Bytes; -use reqwest::Method; +use reqwest::{Method, RequestBuilder}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use tokio_stream::StreamExt; @@ -50,34 +50,32 @@ pub(crate) static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { }); pub async fn request(method: Method, url: &str) -> LauncherResult { - request_builder( - method, - url, - None:: reqwest::Result<_>>, - ) - .await + let req = build_request(method, url).build()?; + send_request(req).await } -pub async fn request_builder( - method: Method, - url: &str, - builder: Option reqwest::Result>, -) -> LauncherResult { +pub fn build_request(method: Method, url: &str) -> RequestBuilder { + let client = &REQWEST_CLIENT; + + client.request(method.clone(), url) +} + +pub async fn send_request(request: reqwest::Request) -> LauncherResult { let semaphore = SemaphoreStore::fetch().await; let permit = semaphore.acquire().await; let client = &REQWEST_CLIENT; let mut attempt = 0; + let res = loop { attempt += 1; - let req = client.request(method.clone(), url); - let req = match builder { - Some(ref builder) => builder(req), - None => req.build(), - } - .map_err(LauncherError::from)?; + let req = request.try_clone().ok_or_else(|| { + LauncherError::from(anyhow::anyhow!( + "failed to clone request for retry attempt {attempt}" + )) + })?; match client.execute(req).await { Err(err) => { diff --git a/packages/core/src/utils/minecraft.rs b/packages/core/src/utils/minecraft.rs index a275141a..d106fc60 100644 --- a/packages/core/src/utils/minecraft.rs +++ b/packages/core/src/utils/minecraft.rs @@ -1,5 +1,8 @@ +use std::collections::HashMap; + use base64::Engine; -use reqwest::Method; +use reqwest::{Method, multipart}; +use serde_json::json; use crate::error::LauncherResult; use crate::utils::http; @@ -14,6 +17,50 @@ pub struct MojangPlayerProfile { pub cape_url: Option, } +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +pub enum SkinVariant { + Classic, + Slim, +} + +impl std::fmt::Display for SkinVariant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SkinVariant::Classic => write!(f, "classic"), + SkinVariant::Slim => write!(f, "slim"), + } + } +} + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct MojangFullPlayerProfile { + pub uuid: String, + pub username: String, + pub skins: Vec, + pub capes: Vec, +} + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct MojangSkin { + pub id: String, + pub state: String, + pub url: String, + pub variant: SkinVariant, +} + +#[onelauncher_macro::specta] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct MojangCape { + pub id: String, + pub state: String, + pub url: String, + pub alias: String, +} + pub async fn fetch_player_profile(uuid: &str) -> LauncherResult { #[derive(serde::Deserialize)] struct Properties { @@ -98,3 +145,93 @@ pub async fn fetch_player_profile(uuid: &str) -> LauncherResult LauncherResult { + let mut headers: HashMap<&str, &str> = HashMap::with_capacity(1); + + let bearer = &format!("Bearer {access_token}"); + headers.insert("Authorization", bearer); + + http::fetch_json_advanced::( + Method::GET, + "https://api.minecraftservices.com/minecraft/profile", + None, + Some(headers), + None, + None, + ) + .await +} + +pub async fn upload_skin_bytes( + access_token: &str, + skin_data: Vec, + image_format: &str, + skin_variant: SkinVariant, +) -> LauncherResult { + let bytes_part = + multipart::Part::bytes(skin_data).mime_str(&format!("image/{}", image_format))?; + + let form = multipart::Form::new() + .text("variant", skin_variant.to_string()) + .part("data", bytes_part); + + let req = http::build_request( + Method::POST, + "https://api.minecraftservices.com/minecraft/profile/skins", + ) + .header("Authorization", &format!("Bearer {access_token}")) + .header("Content-Type", "multipart/form-data") + .multipart(form) + .build()?; + + let res = http::send_request(req).await?; + + if !res.status().is_success() { + let status = res.status(); + let text = res.text().await.unwrap_or_default(); + return Err( + anyhow::anyhow!("failed to upload skin: HTTP {}: {}", status.as_u16(), text).into(), + ); + } + + let full_profile = res.json::().await?; + dbg!(&full_profile); + full_profile + .skins + .first() + .cloned() + .ok_or_else(|| anyhow::anyhow!("no skins found in response").into()) +} + +pub async fn change_skin( + access_token: &str, + skin_url: &str, + skin_variant: SkinVariant, +) -> LauncherResult { + let mut headers: HashMap<&str, &str> = HashMap::with_capacity(1); + + let bearer = &format!("Bearer {access_token}"); + headers.insert("Authorization", bearer); + + let profile = http::fetch_json_advanced::( + Method::POST, + "https://api.minecraftservices.com/minecraft/profile/skins", + Some(json!({ + "url": skin_url, + "variant": skin_variant.to_string(), + })), + Some(headers), + None, + None, + ) + .await?; + + profile + .skins + .first() + .cloned() + .ok_or_else(|| anyhow::anyhow!("no skins found in response").into()) +} From a26c84e9b48f58df74c58958fe648ed04e2a3c7d Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Sun, 28 Sep 2025 05:09:20 +0000 Subject: [PATCH 7/9] fix: oneclient onboarding not forcing an account (#374) --- apps/oneclient/frontend/src/bindings.gen.ts | 29 +++++++--- .../src/routes/onboarding/account.tsx | 58 ++++++++++--------- .../frontend/src/routes/onboarding/route.tsx | 12 ++-- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 0faf9f6d..538fcea5 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -106,8 +106,14 @@ export type ModpackFormat = "CurseForge" | "MrPack" | "PolyMrPack" export type ModpackManifest = { name: string; version: string; loader: GameLoader; loader_version: string; mc_version: string; files: ModpackFile[] } +export type MojangCape = { id: string; state: string; url: string; alias: string } + +export type MojangFullPlayerProfile = { uuid: string; username: string; skins: MojangSkin[]; capes: MojangCape[] } + export type MojangPlayerProfile = { uuid: string; username: string; is_slim: boolean; skin_url: string | null; cape_url: string | null } +export type MojangSkin = { id: string; state: string; url: string; variant: SkinVariant } + export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] } export type PackageCategories = { Mod: PackageModCategory[] } | { ResourcePack: PackageResourcePackCategory[] } | { Shader: PackageShaderCategory[] } | { DataPack: PackageModCategory[] } | { ModPack: PackageModPackCategory[] } @@ -181,7 +187,9 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = { enable_gamemode: boolean | null } +export type SettingsOsExtra = Record + +export type SkinVariant = "classic" | "slim" export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -244,13 +252,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"openDevTools":[],"getClustersGroupedByMajor":[]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'core':'{"openMsaLogin":[],"getRunningProcesses":[],"launchCluster":["id","uuid"],"getWorlds":["id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"searchPackages":["provider","query"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getLoadersForVersion":["mc_version"],"removeCluster":["id"],"getUser":["uuid"],"setDefaultUser":["uuid"],"getUsersFromAuthor":["provider","author"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"getProfileOrDefault":["name"],"fetchMinecraftProfile":["uuid"],"updateClusterProfile":["name","profile"],"getUsers":[],"killProcess":["pid"],"createCluster":["options"],"removeUser":["uuid"],"getLogByName":["id","name"],"getGlobalProfile":[],"getGameVersions":[],"getClusterById":["id"],"getScreenshots":["id"],"writeSettings":["setting"],"getClusters":[],"installModpack":["modpack","cluster_id"],"getDefaultUser":["fallback"],"open":["input"],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"updateClusterById":["id","request"],"readSettings":[],"getPackageBody":["provider","body"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'oneclient': { openDevTools: () => Promise, +const ARGS_MAP = { 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"getClusters":[],"getMultiplePackages":["provider","slugs"],"getProfileOrDefault":["name"],"getRunningProcessesByClusterId":["cluster_id"],"removeCluster":["id"],"writeSettings":["setting"],"getPackage":["provider","slug"],"fetchLoggedInProfile":["access_token"],"getLogByName":["id","name"],"searchPackages":["provider","query"],"getWorlds":["id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"updateClusterProfile":["name","profile"],"getScreenshots":["id"],"fetchMinecraftProfile":["uuid"],"getUsersFromAuthor":["provider","author"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getGameVersions":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"killProcess":["pid"],"openMsaLogin":[],"open":["input"],"getClusterById":["id"],"getGlobalProfile":[],"removeUser":["uuid"],"getRunningProcesses":[],"getDefaultUser":["fallback"],"readSettings":[],"getUsers":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"changeSkin":["access_token","skin_url","skin_variant"],"getLoadersForVersion":["mc_version"],"setDefaultUser":["uuid"],"getUser":["uuid"],"getPackageBody":["provider","body"],"getLogs":["id"],"installModpack":["modpack","cluster_id"],"createCluster":["options"],"updateClusterById":["id","request"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}' } +export type Router = { 'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise }, 'core': { getClusters: () => Promise, @@ -289,7 +292,15 @@ downloadPackage: (provider: Provider, packageId: string, versionId: string, clus getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, installModpack: (modpack: ModpackArchive, clusterId: number) => Promise, fetchMinecraftProfile: (uuid: string) => Promise, -open: (input: string) => Promise } }; +fetchLoggedInProfile: (accessToken: string) => Promise, +uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise, +changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise, +open: (input: string) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/routes/onboarding/account.tsx b/apps/oneclient/frontend/src/routes/onboarding/account.tsx index 3159f724..e6f8d433 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/account.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/account.tsx @@ -2,7 +2,7 @@ import type { MinecraftCredentials } from '@/bindings.gen'; import { AccountAvatar, SkinViewer } from '@/components'; import { usePlayerProfile } from '@/hooks/usePlayerProfile'; import { bindings } from '@/main'; -import { useCommandMut } from '@onelauncher/common'; +import { useCommandMut, useCommandSuspense } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; @@ -49,15 +49,11 @@ function AccountPreview({ ); } -// TODO: Block the next button until the user logs in (beg lynith to do it coz I spent 20 minutes on it) function RouteComponent() { const queryClient = useQueryClient(); - + const { data: currentAccount } = useCommandSuspense(['getDefaultUser'], () => bindings.core.getDefaultUser(true)); const { data: profile, isPending, mutate: login } = useCommandMut(bindings.core.openMsaLogin, { onSuccess() { - queryClient.invalidateQueries({ - queryKey: ['getUsers'], - }); queryClient.invalidateQueries({ queryKey: ['getDefaultUser'], }); @@ -69,25 +65,35 @@ function RouteComponent() { }; return ( -
-

Account

-

Before you continue, we require you to own a copy of Minecraft: Java Edition.

- {profile - ? ( - <> - - - ) - : ( - - )} -
+
+
+

Account

+

Before you continue, we require you to own a copy of Minecraft: Java Edition.

+
+ {currentAccount ? + <> + + + : + <> + {profile + ? ( + <> + + + ) + : ( + + )} + + } +
); } diff --git a/apps/oneclient/frontend/src/routes/onboarding/route.tsx b/apps/oneclient/frontend/src/routes/onboarding/route.tsx index 8a362461..bd6cd917 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/route.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/route.tsx @@ -4,7 +4,9 @@ import LauncherLogo from '@/assets/logos/oneclient.svg?react'; import { LoaderSuspense, NavbarButton } from '@/components'; import { GameBackground } from '@/components/GameBackground'; import { Stepper } from '@/components/Stepper'; +import { bindings } from '@/main'; import useAppShellStore from '@/stores/appShellStore'; +import { useCommandSuspense } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { createFileRoute, Link, Outlet } from '@tanstack/react-router'; import { Window } from '@tauri-apps/api/window'; @@ -175,21 +177,23 @@ function BackgroundGradient() { } export function OnboardingNavigation() { - const { isFirstStep, isLastStep, previousPath, nextPath } = Route.useLoaderData(); + const { isFirstStep, isLastStep, previousPath, nextPath, currentStepIndex } = Route.useLoaderData(); + const { data: currentAccount } = useCommandSuspense(['getDefaultUser'], () => bindings.core.getDefaultUser(true)); + const disabled = currentStepIndex === 2 && currentAccount === null; return (
{!isFirstStep && previousPath && ( - + )}
{nextPath && ( - - + + )}
From c90c41ce45ff9d8b2ab4bdeb9f5baad327de3b8e Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Sun, 28 Sep 2025 06:07:03 +0000 Subject: [PATCH 8/9] feat: oneclient onboarding finished page (#375) --- apps/oneclient/frontend/src/bindings.gen.ts | 8 +++---- .../src/routes/onboarding/finished.tsx | 21 +++++++++++++++---- .../frontend/src/routes/onboarding/route.tsx | 12 +++++------ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 538fcea5..50739f78 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -252,7 +252,7 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"getClusters":[],"getMultiplePackages":["provider","slugs"],"getProfileOrDefault":["name"],"getRunningProcessesByClusterId":["cluster_id"],"removeCluster":["id"],"writeSettings":["setting"],"getPackage":["provider","slug"],"fetchLoggedInProfile":["access_token"],"getLogByName":["id","name"],"searchPackages":["provider","query"],"getWorlds":["id"],"isClusterRunning":["cluster_id"],"launchCluster":["id","uuid"],"updateClusterProfile":["name","profile"],"getScreenshots":["id"],"fetchMinecraftProfile":["uuid"],"getUsersFromAuthor":["provider","author"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getGameVersions":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"killProcess":["pid"],"openMsaLogin":[],"open":["input"],"getClusterById":["id"],"getGlobalProfile":[],"removeUser":["uuid"],"getRunningProcesses":[],"getDefaultUser":["fallback"],"readSettings":[],"getUsers":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"changeSkin":["access_token","skin_url","skin_variant"],"getLoadersForVersion":["mc_version"],"setDefaultUser":["uuid"],"getUser":["uuid"],"getPackageBody":["provider","body"],"getLogs":["id"],"installModpack":["modpack","cluster_id"],"createCluster":["options"],"updateClusterById":["id","request"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}' } +const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[],"getBundlesFor":["cluster_id"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'core':'{"getGameVersions":[],"getUsers":[],"getWorlds":["id"],"getGlobalProfile":[],"removeCluster":["id"],"updateClusterById":["id","request"],"getScreenshots":["id"],"getLoadersForVersion":["mc_version"],"getPackageBody":["provider","body"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"getProfileOrDefault":["name"],"changeSkin":["access_token","skin_url","skin_variant"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getRunningProcessesByClusterId":["cluster_id"],"searchPackages":["provider","query"],"openMsaLogin":[],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"createCluster":["options"],"removeUser":["uuid"],"getRunningProcesses":[],"getClusters":[],"getLogByName":["id","name"],"getUser":["uuid"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"killProcess":["pid"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"launchCluster":["id","uuid"],"getDefaultUser":["fallback"],"readSettings":[],"getPackage":["provider","slug"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"open":["input"],"setDefaultUser":["uuid"],"isClusterRunning":["cluster_id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } export type Router = { 'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise }, @@ -296,11 +296,11 @@ fetchLoggedInProfile: (accessToken: string) => Promise, uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise, changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise, open: (input: string) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise } }; +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/routes/onboarding/finished.tsx b/apps/oneclient/frontend/src/routes/onboarding/finished.tsx index 63221c15..d9a7b77b 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/finished.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/finished.tsx @@ -1,9 +1,22 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, Link } from '@tanstack/react-router'; export const Route = createFileRoute('/onboarding/finished')({ - component: RouteComponent, -}) + component: RouteComponent, +}); function RouteComponent() { - return
Hello "/onboarding/finished"!
+ return ( + +
+
+

Finished!

+

Thank you for using OneClient

+
+
+ +
+

Click anywhere to exit

+
+ + ); } diff --git a/apps/oneclient/frontend/src/routes/onboarding/route.tsx b/apps/oneclient/frontend/src/routes/onboarding/route.tsx index bd6cd917..5155cbb4 100644 --- a/apps/oneclient/frontend/src/routes/onboarding/route.tsx +++ b/apps/oneclient/frontend/src/routes/onboarding/route.tsx @@ -177,9 +177,9 @@ function BackgroundGradient() { } export function OnboardingNavigation() { - const { isFirstStep, isLastStep, previousPath, nextPath, currentStepIndex } = Route.useLoaderData(); + const { isFirstStep, previousPath, nextPath, currentStepIndex } = Route.useLoaderData(); const { data: currentAccount } = useCommandSuspense(['getDefaultUser'], () => bindings.core.getDefaultUser(true)); - const disabled = currentStepIndex === 2 && currentAccount === null; + const forceLoginDisable = currentStepIndex === 2 && currentAccount === null; return (
@@ -191,11 +191,9 @@ export function OnboardingNavigation() { )}
- {nextPath && ( - - - - )} + + +
); From c2ad51017be591eca0d3e63d222df0b5a6b7e0af Mon Sep 17 00:00:00 2001 From: Jacob <55346310+Kathund@users.noreply.github.com> Date: Mon, 29 Sep 2025 04:32:53 +0000 Subject: [PATCH 9/9] feat: oneclient mini accounts delete (#376) --- apps/oneclient/frontend/src/bindings.gen.ts | 16 ++++---- .../src/components/overlay/AccountPopup.tsx | 40 ++++++++++++++++--- .../frontend/src/routes/app/accounts.tsx | 3 +- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 50739f78..539d115c 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -252,8 +252,13 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[],"getBundlesFor":["cluster_id"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'core':'{"getGameVersions":[],"getUsers":[],"getWorlds":["id"],"getGlobalProfile":[],"removeCluster":["id"],"updateClusterById":["id","request"],"getScreenshots":["id"],"getLoadersForVersion":["mc_version"],"getPackageBody":["provider","body"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"getProfileOrDefault":["name"],"changeSkin":["access_token","skin_url","skin_variant"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getRunningProcessesByClusterId":["cluster_id"],"searchPackages":["provider","query"],"openMsaLogin":[],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"createCluster":["options"],"removeUser":["uuid"],"getRunningProcesses":[],"getClusters":[],"getLogByName":["id","name"],"getUser":["uuid"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"killProcess":["pid"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"launchCluster":["id","uuid"],"getDefaultUser":["fallback"],"readSettings":[],"getPackage":["provider","slug"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"open":["input"],"setDefaultUser":["uuid"],"isClusterRunning":["cluster_id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'oneclient': { openDevTools: () => Promise, +const ARGS_MAP = { 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[],"getBundlesFor":["cluster_id"]}', 'core':'{"getGameVersions":[],"getUsers":[],"getWorlds":["id"],"getGlobalProfile":[],"removeCluster":["id"],"updateClusterById":["id","request"],"getScreenshots":["id"],"getLoadersForVersion":["mc_version"],"getPackageBody":["provider","body"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"getProfileOrDefault":["name"],"changeSkin":["access_token","skin_url","skin_variant"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getRunningProcessesByClusterId":["cluster_id"],"searchPackages":["provider","query"],"openMsaLogin":[],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"createCluster":["options"],"removeUser":["uuid"],"getRunningProcesses":[],"getClusters":[],"getLogByName":["id","name"],"getUser":["uuid"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"killProcess":["pid"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"launchCluster":["id","uuid"],"getDefaultUser":["fallback"],"readSettings":[],"getPackage":["provider","slug"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"open":["input"],"setDefaultUser":["uuid"],"isClusterRunning":["cluster_id"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise }, 'core': { getClusters: () => Promise, @@ -295,12 +300,7 @@ fetchMinecraftProfile: (uuid: string) => Promise, fetchLoggedInProfile: (accessToken: string) => Promise, uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise, changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise, -open: (input: string) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise } }; +open: (input: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx b/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx index cc9dfe8b..c7a9cd9e 100644 --- a/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx +++ b/apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx @@ -3,13 +3,14 @@ import { bindings } from '@/main'; import { useCommand, useCommandMut } from '@onelauncher/common'; import { Button } from '@onelauncher/common/components'; import { Link } from '@tanstack/react-router'; -import { PlusIcon, Settings01Icon } from '@untitled-theme/icons-react'; +import { PlusIcon, Settings01Icon, Trash01Icon } from '@untitled-theme/icons-react'; import { DialogTrigger } from 'react-aria-components'; import { twMerge } from 'tailwind-merge'; import { AccountAvatar } from '../AccountAvatar'; import { AddAccountModal } from './AddAccountModal'; import { Overlay } from './Overlay'; import { Popup } from './Popup'; +import { RemoveAccountModal } from './RemoveAccountModal'; export function AccountPopup() { const users = useCommand(['getUsers'], bindings.core.getUsers); @@ -21,6 +22,17 @@ export function AccountPopup() { defaultUser.refetch(); }; + const deleteUser = async (user: MinecraftCredentials) => { + await bindings.core.removeUser(user.id); + users.refetch(); + defaultUser.refetch(); + + if (defaultUser.data && defaultUser.data.id === user.id && users.data && users.data.length > 1) { + const filtered = users.data.filter((userData) => userData.id !== user.id) + if (filtered.length > 0) setDefaultUser(filtered[0]); + } + }; + return ( @@ -30,6 +42,7 @@ export function AccountPopup() { { }} + onDelete={() => deleteUser(defaultUser.data)} user={defaultUser.data} />
@@ -39,6 +52,7 @@ export function AccountPopup() {
setDefaultUser(user)} + onDelete={() => deleteUser(user)} user={user} />
@@ -75,10 +89,12 @@ export default AccountPopup; function AccountEntry({ onClick, + onDelete, user, loggedIn = false, }: { onClick: () => void; + onDelete: () => void; user: MinecraftCredentials; loggedIn?: boolean; }) { @@ -88,13 +104,25 @@ function AccountEntry({ onClick={onClick} >
- -
-
-

{user.username}

- {loggedIn &&

Logged in

} +
+ +
+
+

{user.username}

+ {loggedIn &&

Logged in

} +
+ + + + + + + +
); diff --git a/apps/oneclient/frontend/src/routes/app/accounts.tsx b/apps/oneclient/frontend/src/routes/app/accounts.tsx index 07e3114d..b61503ab 100644 --- a/apps/oneclient/frontend/src/routes/app/accounts.tsx +++ b/apps/oneclient/frontend/src/routes/app/accounts.tsx @@ -1,9 +1,8 @@ import type { MinecraftCredentials } from '@/bindings.gen'; import type { ButtonProps } from '@onelauncher/common/components'; import { AccountAvatar, SheetPage, SkinViewer } from '@/components'; -import { AddAccountModal } from '@/components/overlay'; +import { AddAccountModal, RemoveAccountModal } from '@/components/overlay'; import { Overlay } from '@/components/overlay/Overlay'; -import { RemoveAccountModal } from '@/components/overlay/RemoveAccountModal'; import { usePlayerProfile } from '@/hooks/usePlayerProfile'; import { bindings } from '@/main'; import { useCommandMut, useCommandSuspense } from '@onelauncher/common';