diff --git a/Cargo.lock b/Cargo.lock index 195ec597..b2ab04fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3400,6 +3400,7 @@ dependencies = [ "chrono", "interpulse", "onelauncher_core", + "reqwest", "serde", "serde_json", "specta", @@ -6060,9 +6061,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88371e340ad2f07409a3b68294abe73f20bc9c1bc1b631a31dc37a3d0161f682" +checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" dependencies = [ "anyhow", "dunce", @@ -6078,7 +6079,6 @@ dependencies = [ "thiserror 2.0.12", "toml", "url", - "uuid 1.16.0", ] [[package]] diff --git a/apps/oneclient/desktop/Cargo.toml b/apps/oneclient/desktop/Cargo.toml index 0f0e8f14..8e2ce0c1 100644 --- a/apps/oneclient/desktop/Cargo.toml +++ b/apps/oneclient/desktop/Cargo.toml @@ -60,6 +60,7 @@ tokio = { workspace = true } # utils thiserror = { workspace = true } anyhow = { workspace = true } +reqwest = { workspace = true } [dependencies.tauri] workspace = true diff --git a/apps/oneclient/frontend/package.json b/apps/oneclient/frontend/package.json index 3d5bfa9d..90354df2 100644 --- a/apps/oneclient/frontend/package.json +++ b/apps/oneclient/frontend/package.json @@ -35,6 +35,7 @@ "react-aria-components": "catalog:", "react-dom": "catalog:", "react-just-parallax": "catalog:", + "skinview3d": "catalog:", "tailwind-merge": "catalog:", "tailwind-variants": "catalog:", "tailwindcss": "catalog:", diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 26d7cfcf..1527efba 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -94,6 +94,8 @@ refresh_token: string; */ expires: string } +export type MojangPlayerProfile = { uuid: string; username: string; is_slim: boolean; skin_url: string | null; cape_url: string | null } + 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[] } @@ -228,8 +230,15 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"openMsaLogin":[],"readSettings":[],"getProfileOrDefault":["name"],"getLogByName":["id","name"],"getWorlds":["id"],"launchCluster":["id","uuid"],"getGameVersions":[],"getUser":["uuid"],"removeUser":["uuid"],"getUsersFromAuthor":["provider","author"],"getPackage":["provider","slug"],"setDefaultUser":["uuid"],"getMultiplePackages":["provider","slugs"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"updateClusterById":["id","request"],"getGlobalProfile":[],"getLoadersForVersion":["mc_version"],"getPackageBody":["provider","body"],"writeSettings":["setting"],"getScreenshots":["id"],"getLogs":["id"],"getDefaultUser":["fallback"],"getClusterById":["id"],"removeCluster":["id"],"updateClusterProfile":["name","profile"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"createCluster":["options"],"getClusters":[],"searchPackages":["provider","query"],"getUsers":[]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'core': { getClusters: () => Promise, +const ARGS_MAP = { 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}', 'core':'{"removeUser":["uuid"],"openMsaLogin":[],"getLogByName":["id","name"],"setDefaultUser":["uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUser":["uuid"],"getClusters":[],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"getMultiplePackages":["provider","slugs"],"getWorlds":["id"],"getUsersFromAuthor":["provider","author"],"fetchMinecraftProfile":["uuid"],"getLogs":["id"],"getUsers":[],"launchCluster":["id","uuid"],"writeSettings":["setting"],"getLoadersForVersion":["mc_version"],"getGlobalProfile":[],"getGameVersions":[],"searchPackages":["provider","query"],"removeCluster":["id"],"getDefaultUser":["fallback"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"updateClusterById":["id","request"],"getPackage":["provider","slug"],"getScreenshots":["id"],"readSettings":[],"getClusterById":["id"],"createCluster":["options"],"getPackageBody":["provider","body"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise> }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -258,14 +267,8 @@ getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise Promise, getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: number, limit: number) => Promise>, downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise, -getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise> }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise } }; +getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, +fetchMinecraftProfile: (uuid: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/AccountAvatar.tsx b/apps/oneclient/frontend/src/components/AccountAvatar.tsx new file mode 100644 index 00000000..aad665db --- /dev/null +++ b/apps/oneclient/frontend/src/components/AccountAvatar.tsx @@ -0,0 +1,35 @@ +import type { ImgHTMLAttributes } from 'react'; +import { usePlayerProfile } from '@/hooks/usePlayerProfile'; +import { getSkinUrl } from '@/utils/minecraft'; +import { renderHeadToDataUrl } from '@onelauncher/common'; +import { useEffect, useState } from 'react'; + +export interface AccountAvatarProps extends ImgHTMLAttributes { + uuid: string | undefined | null; +} + +export function AccountAvatar({ + uuid, + alt, + style, + ...rest +}: AccountAvatarProps) { + const { data: profile } = usePlayerProfile(uuid); + const [headUrl, setHeadUrl] = useState(null); + + useEffect(() => { + renderHeadToDataUrl(getSkinUrl(profile?.skin_url)).then(setHeadUrl); + }, [profile]); + + return ( + {alt + ); +} diff --git a/apps/oneclient/frontend/src/components/Navbar.tsx b/apps/oneclient/frontend/src/components/Navbar.tsx index bdb6e0a0..0472cad8 100644 --- a/apps/oneclient/frontend/src/components/Navbar.tsx +++ b/apps/oneclient/frontend/src/components/Navbar.tsx @@ -1,19 +1,25 @@ import type { ButtonProps } from '@onelauncher/common/components'; import type { LinkProps, RegisteredRouter } from '@tanstack/react-router'; import LauncherLogo from '@/assets/logos/oneclient.svg?react'; -import { Button } from '@onelauncher/common/components'; +import { bindings } from '@/main'; +import { useCommand } from '@onelauncher/common'; +import { Button, Popup } from '@onelauncher/common/components'; import { Link } from '@tanstack/react-router'; import { Window } from '@tauri-apps/api/window'; import { MinusIcon, SquareIcon, XCloseIcon } from '@untitled-theme/icons-react'; import { twMerge } from 'tailwind-merge'; +import { AccountAvatar } from './AccountAvatar'; +import { AccountPopup } from './overlay/AccountPopup'; export function Navbar() { + const { data: currentAccount } = useCommand(['getDefaultUser'], () => bindings.core.getDefaultUser(true)); + const onMinimize = () => Window.getCurrent().minimize(); const onMaximize = () => Window.getCurrent().toggleMaximize(); const onClose = () => Window.getCurrent().close(); return ( -