Skip to content

Commit 0e12968

Browse files
Kathundundefined303Zickles
authored
feat: oneclient account skin manager (#373)
* feat: oneclient account skin manager * feat: oneclient account skin manager import * feat: oneclient account skin manager improt from url * feat: oneclient accoutn skin manager animations * feat: oneclient account skin manager export skin * feat: oneclient account skin manager skin history * feat: oneclient mini accounts edit button * feat: oneclient account skin manager saving skins * change order of mini account buttons * feat: oneclient account skin manager elytra support * fix: oneclient account skin manager default rotate See bs-community/skinview3d#198 for where fix came from Co-authored-by: undefined303 <undefined303@qq.com> Co-authored-by: Zickles <zicklesistaken@gmail.com> * feat: oneclient account skin manager change cape * feat: oneclient account skin manager import from username --------- Co-authored-by: undefined303 <undefined303@qq.com> Co-authored-by: Zickles <zicklesistaken@gmail.com>
1 parent 4b5051b commit 0e12968

File tree

18 files changed

+804
-34
lines changed

18 files changed

+804
-34
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ tauri-build = { version = "=2.2.0" }
4848
tauri-plugin-clipboard-manager = { version = "=2.2.2" }
4949
tauri-plugin-deep-link = { version = "=2.2.1" }
5050
tauri-plugin-dialog = { version = "=2.2.1" }
51+
tauri-plugin-fs = { version = "=2.2.1" }
5152
tauri-plugin-single-instance = { version = "=2.2.3" }
5253
tauri-plugin-updater = { version = "=2.7.1" }
5354
tauri-plugin-window-state = { version = "=2.2.2" }

apps/oneclient/desktop/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ tauri-plugin-single-instance = { workspace = true }
3636
tauri-plugin-updater = { workspace = true }
3737
tauri-plugin-clipboard-manager = { workspace = true }
3838
tauri-plugin-dialog = { workspace = true }
39+
tauri-plugin-fs = { workspace = true }
3940
tauri-plugin-deep-link = { workspace = true }
4041

4142
# code gen

apps/oneclient/desktop/capabilities/default.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
"dialog:allow-save",
3232
"dialog:allow-confirm",
3333
"deep-link:default",
34-
"updater:default"
34+
"updater:default",
35+
36+
"fs:allow-download-read",
37+
"fs:allow-download-write",
38+
"fs:allow-data-read-recursive",
39+
"fs:allow-data-write-recursive"
3540
]
3641
}

apps/oneclient/desktop/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
7171
// .plugin(tauri_plugin_updater::Builder::new().build())
7272
.plugin(tauri_plugin_clipboard_manager::init())
7373
.plugin(tauri_plugin_dialog::init())
74+
.plugin(tauri_plugin_fs::init())
7475
.plugin(tauri_plugin_deep_link::init())
7576
.menu(tauri::menu::Menu::new)
7677
.invoke_handler(router.into_handler())

apps/oneclient/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@tauri-apps/api": "catalog:",
2929
"@tauri-apps/plugin-clipboard-manager": "catalog:",
3030
"@tauri-apps/plugin-dialog": "catalog:",
31+
"@tauri-apps/plugin-fs": "catalog:",
3132
"@untitled-theme/icons-react": "catalog:",
3233
"motion": "catalog:",
3334
"overlayscrollbars": "catalog:",

apps/oneclient/frontend/src/bindings.gen.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,14 @@ export type ModpackManifest = { name: string; version: string; loader: GameLoade
108108

109109
export type MojangCape = { id: string; state: string; url: string; alias: string }
110110

111-
export type MojangFullPlayerProfile = { uuid: string; username: string; skins: MojangSkin[]; capes: MojangCape[] }
111+
export type MojangFullPlayerProfile = { id: string; username: string; skins: MojangSkin[]; capes: MojangCape[] }
112112

113113
export type MojangPlayerProfile = { uuid: string; username: string; is_slim: boolean; skin_url: string | null; cape_url: string | null }
114114

115115
export type MojangSkin = { id: string; state: string; url: string; variant: SkinVariant }
116116

117+
export type MowojangProfile = { id: string; username: string }
118+
117119
export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] }
118120

119121
export type PackageCategories = { Mod: PackageModCategory[] } | { ResourcePack: PackageResourcePackCategory[] } | { Shader: PackageShaderCategory[] } | { DataPack: PackageModCategory[] } | { ModPack: PackageModPackCategory[] }
@@ -252,15 +254,15 @@ export type VersionType =
252254
*/
253255
"old_beta"
254256

255-
const ARGS_MAP = { 'core':'{"getScreenshots":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"fetchLoggedInProfile":["access_token"],"getPackageBody":["provider","body"],"getLogs":["id"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"updateClusterById":["id","request"],"getMultiplePackages":["provider","slugs"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getUsersFromAuthor":["provider","author"],"updateClusterProfile":["name","profile"],"getLogByName":["id","name"],"removeUser":["uuid"],"getUser":["uuid"],"launchCluster":["id","uuid"],"readSettings":[],"getProfileOrDefault":["name"],"getGameVersions":[],"removeCluster":["id"],"getRunningProcessesByClusterId":["cluster_id"],"open":["input"],"createSettingsProfile":["name"],"getClusterById":["id"],"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getRunningProcesses":[],"getClusters":[],"getWorlds":["id"],"changeSkin":["access_token","skin_url","skin_variant"],"killProcess":["pid"],"getLoadersForVersion":["mc_version"],"createCluster":["options"],"getUsers":[],"getGlobalProfile":[],"searchPackages":["provider","query"],"writeSettings":["setting"],"isClusterRunning":["cluster_id"],"getPackage":["provider","slug"],"getDefaultUser":["fallback"],"setDefaultUser":["uuid"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}' }
256-
export type Router = { 'folders': { fromCluster: (folderName: string) => Promise<string>,
257-
openCluster: (folderName: string) => Promise<null> },
258-
'oneclient': { openDevTools: () => Promise<void>,
257+
const ARGS_MAP = { 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[],"getBundlesFor":["cluster_id"]}', 'core':'{"updateClusterProfile":["name","profile"],"updateClusterById":["id","request"],"getLogByName":["id","name"],"killProcess":["pid"],"getUsers":[],"getPackage":["provider","slug"],"removeCluster":["id"],"launchCluster":["id","uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getProfileOrDefault":["name"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"changeCape":["access_token","cape_uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"getPackageBody":["provider","body"],"installModpack":["modpack","cluster_id"],"getWorlds":["id"],"removeUser":["uuid"],"convertUsernameUUID":["username_uuid"],"writeSettings":["setting"],"getGameVersions":[],"getClusterById":["id"],"getLoadersForVersion":["mc_version"],"getScreenshots":["id"],"getMultiplePackages":["provider","slugs"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"openMsaLogin":[],"getDefaultUser":["fallback"],"getUser":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"getGlobalProfile":[],"createSettingsProfile":["name"],"setDefaultUser":["uuid"],"getClusters":[],"removeCape":["access_token"],"searchPackages":["provider","query"],"isClusterRunning":["cluster_id"],"getLogs":["id"],"fetchLoggedInProfile":["access_token"],"open":["input"],"fetchMinecraftProfile":["uuid"],"readSettings":[],"getRunningProcesses":[],"createCluster":["options"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' }
258+
export type Router = { 'oneclient': { openDevTools: () => Promise<void>,
259259
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
260260
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]> },
261261
'events': { ingress: (event: IngressPayload) => Promise<void>,
262262
message: (event: MessagePayload) => Promise<void>,
263263
process: (event: ProcessPayload) => Promise<void> },
264+
'folders': { fromCluster: (folderName: string) => Promise<string>,
265+
openCluster: (folderName: string) => Promise<null> },
264266
'core': { getClusters: () => Promise<ClusterModel[]>,
265267
getClusterById: (id: number) => Promise<ClusterModel | null>,
266268
removeCluster: (id: number) => Promise<null>,
@@ -301,6 +303,9 @@ fetchMinecraftProfile: (uuid: string) => Promise<MojangPlayerProfile>,
301303
fetchLoggedInProfile: (accessToken: string) => Promise<MojangFullPlayerProfile>,
302304
uploadSkinBytes: (accessToken: string, skinData: number[], imageFormat: string, skinVariant: SkinVariant) => Promise<MojangSkin>,
303305
changeSkin: (accessToken: string, skinUrl: string, skinVariant: SkinVariant) => Promise<MojangSkin>,
306+
changeCape: (accessToken: string, capeUuid: string) => Promise<MojangFullPlayerProfile>,
307+
removeCape: (accessToken: string) => Promise<MojangFullPlayerProfile>,
308+
convertUsernameUUID: (usernameUuid: string) => Promise<MowojangProfile>,
304309
open: (input: string) => Promise<null> } };
305310

306311

apps/oneclient/frontend/src/components/SkinViewer.tsx

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,48 @@ export interface SkinViewerProps {
99
width?: number;
1010
height?: number;
1111
className?: string | undefined;
12+
autoRotate?: boolean;
13+
autoRotateSpeed?: number;
14+
showText?: boolean;
15+
playerRotatePhi?: number;
16+
playerRotateTheta?: number;
17+
translateRotateX?: number;
18+
translateRotateY?: number;
19+
translateRotateZ?: number;
20+
zoom?: number;
21+
animate?: boolean;
22+
animation?: skinviewer.PlayerAnimation;
23+
enableDamping?: boolean;
24+
enableZoom?: boolean;
25+
enableRotate?: boolean;
26+
enablePan?: boolean;
27+
elytra?: boolean;
1228
}
1329

30+
const defaultIdleAnimation = new skinviewer.IdleAnimation();
31+
1432
export function SkinViewer({
1533
skinUrl,
1634
capeUrl,
1735
width = 260,
1836
height = 300,
1937
className,
38+
autoRotate = true,
39+
autoRotateSpeed = 0.25,
40+
showText = true,
41+
playerRotatePhi = Math.PI / 3,
42+
playerRotateTheta = -Math.PI / 6,
43+
translateRotateX = 0,
44+
translateRotateY = 0,
45+
translateRotateZ = 0,
46+
zoom = 0.9,
47+
animate = false,
48+
animation = defaultIdleAnimation,
49+
enableDamping = true,
50+
enableZoom = true,
51+
enableRotate = true,
52+
enablePan = true,
53+
elytra = false
2054
}: SkinViewerProps) {
2155
const canvasRef = useRef<HTMLCanvasElement>(null);
2256
const viewerRef = useRef<skinviewer.SkinViewer | null>(null);
@@ -29,8 +63,32 @@ export function SkinViewer({
2963
canvas: canvasRef.current,
3064
});
3165

32-
viewer.autoRotate = true;
33-
viewer.autoRotateSpeed = 0.25;
66+
viewer.controls.enableDamping = enableDamping;
67+
viewer.controls.enableZoom = enableZoom;
68+
viewer.controls.enableRotate = enableRotate;
69+
viewer.controls.enablePan = enablePan;
70+
71+
viewer.zoom = zoom;
72+
73+
const setAngle = (phi: number, theta: number) => {
74+
const r = viewer.controls.object.position.distanceTo(viewer.controls.target);
75+
const x = r * Math.cos(phi - Math.PI / 2) * Math.sin(theta) + viewer.controls.target.x;
76+
const y = r * Math.sin(phi + Math.PI / 2) + viewer.controls.target.y;
77+
const z = r * Math.cos(phi - Math.PI / 2) * Math.cos(theta) + viewer.controls.target.z;
78+
viewer.controls.object.position.set(x, y, z);
79+
viewer.controls.object.lookAt(viewer.controls.target);
80+
};
81+
setAngle(playerRotatePhi, playerRotateTheta)
82+
83+
viewer.playerWrapper.translateX(translateRotateX);
84+
viewer.playerWrapper.translateY(translateRotateY);
85+
viewer.playerWrapper.translateZ(translateRotateZ);
86+
87+
88+
viewer.animation = animation;
89+
90+
viewer.autoRotate = autoRotate;
91+
viewer.autoRotateSpeed = autoRotateSpeed;
3492

3593
viewerRef.current = viewer;
3694

@@ -46,19 +104,15 @@ export function SkinViewer({
46104
viewerRef.current.loadSkin(getSkinUrl(skinUrl));
47105
}, [skinUrl]);
48106

49-
useEffect(() => {
50-
51-
}, [capeUrl]);
52-
53107
useEffect(() => {
54108
if (!viewerRef.current)
55109
return;
56110

57111
if (capeUrl)
58-
viewerRef.current.loadCape(capeUrl);
112+
viewerRef.current.loadCape(capeUrl, { backEquipment: elytra ? 'elytra' : 'cape' });
59113
else
60114
viewerRef.current.resetCape();
61-
}, [capeUrl]);
115+
}, [capeUrl, elytra]);
62116

63117
useEffect(() => {
64118
if (!viewerRef.current)
@@ -67,6 +121,20 @@ export function SkinViewer({
67121
viewerRef.current.setSize(width, height);
68122
}, [width, height]);
69123

124+
useEffect(() => {
125+
if (!viewerRef.current)
126+
return;
127+
128+
viewerRef.current.animation = animation;
129+
}, [animation]);
130+
131+
useEffect(() => {
132+
if (!viewerRef.current || !viewerRef.current.animation)
133+
return;
134+
135+
viewerRef.current.animation.paused = !animate;
136+
}, [animate]);
137+
70138
return (
71139
<div className={twMerge('flex flex-col justify-center items-center', className)} style={{ minWidth: `${width}px`, minHeight: `${height}px` }}>
72140
<canvas
@@ -75,7 +143,7 @@ export function SkinViewer({
75143
width={width}
76144
/>
77145

78-
<span className="text-fg-secondary text-xs">Hold to drag. Scroll to zoom in/out.</span>
146+
{showText ? <span className="text-fg-secondary text-xs">Hold to drag. Scroll to zoom in/out.</span> : <></>}
79147
</div>
80148
);
81149
}

apps/oneclient/frontend/src/components/overlay/AccountPopup.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { bindings } from '@/main';
33
import { useCommand, useCommandMut } from '@onelauncher/common';
44
import { Button } from '@onelauncher/common/components';
55
import { Link } from '@tanstack/react-router';
6-
import { PlusIcon, Settings01Icon, Trash01Icon } from '@untitled-theme/icons-react';
6+
import { Pencil01Icon, PlusIcon, Settings01Icon, Trash01Icon } from '@untitled-theme/icons-react';
77
import { DialogTrigger } from 'react-aria-components';
88
import { twMerge } from 'tailwind-merge';
99
import { AccountAvatar } from '../AccountAvatar';
@@ -28,8 +28,9 @@ export function AccountPopup() {
2828
defaultUser.refetch();
2929

3030
if (defaultUser.data && defaultUser.data.id === user.id && users.data && users.data.length > 1) {
31-
const filtered = users.data.filter((userData) => userData.id !== user.id)
32-
if (filtered.length > 0) setDefaultUser(filtered[0]);
31+
const filtered = users.data.filter(userData => userData.id !== user.id);
32+
if (filtered.length > 0)
33+
setDefaultUser(filtered[0]);
3334
}
3435
};
3536

@@ -99,8 +100,9 @@ function AccountEntry({
99100
loggedIn?: boolean;
100101
}) {
101102
return (
102-
<div
103-
className={twMerge('flex flex-row justify-between p-2 rounded-lg', !loggedIn && 'hover:bg-component-bg-hover active:bg-component-bg-pressed hover:text-fg-primary-hover')}
103+
<Button
104+
className={twMerge('w-full flex flex-row justify-between p-2 rounded-lg', !loggedIn && 'hover:bg-component-bg-hover active:bg-component-bg-pressed hover:text-fg-primary-hover')}
105+
color="ghost"
104106
onClick={onClick}
105107
>
106108
<div className="flex flex-1 flex-row justify-start gap-x-3">
@@ -114,16 +116,29 @@ function AccountEntry({
114116
</div>
115117
</div>
116118

117-
<DialogTrigger>
118-
<Button className="group w-8 h-8" color="ghost" size="icon">
119-
<Trash01Icon className="group-hover:stroke-danger" />
120-
</Button>
119+
<div className="flex flex-row items-center">
120+
<Link to="/app/accountSkin">
121+
<Button
122+
className="group w-8 h-8"
123+
color="ghost"
124+
onClick={onClick}
125+
size="icon"
126+
>
127+
<Pencil01Icon className="group-hover:stroke-brand-hover" />
128+
</Button>
129+
</Link>
121130

122-
<Overlay>
123-
<RemoveAccountModal onPress={onDelete} profile={user} />
124-
</Overlay>
125-
</DialogTrigger>
131+
<DialogTrigger>
132+
<Button className="group w-8 h-8" color="ghost" size="icon">
133+
<Trash01Icon className="group-hover:stroke-danger" />
134+
</Button>
135+
136+
<Overlay>
137+
<RemoveAccountModal onPress={onDelete} profile={user} />
138+
</Overlay>
139+
</DialogTrigger>
140+
</div>
126141
</div>
127-
</div>
142+
</Button>
128143
);
129144
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Button, TextField } from '@onelauncher/common/components';
2+
import { useState } from 'react';
3+
import { Overlay } from './Overlay';
4+
5+
export function ImportSkinModal({ importFromURL, importFromUsername }: { importFromURL: (url: string) => void; importFromUsername: (username: string) => void }) {
6+
const [input, setInput] = useState<string>('');
7+
return (
8+
<Overlay.Dialog>
9+
<Overlay.Title>Import</Overlay.Title>
10+
<TextField className="w-full" onChange={e => setInput(e.target.value)} />
11+
12+
<div className="flex flex-row gap-4 h-1/2 w-full">
13+
<Button
14+
className="w-1/2"
15+
color="primary"
16+
onClick={() => importFromUsername(input)}
17+
size="normal"
18+
slot="close"
19+
>
20+
From Username
21+
</Button>
22+
<Button
23+
className="w-1/2"
24+
color="primary"
25+
onClick={() => importFromURL(input)}
26+
size="normal"
27+
slot="close"
28+
>
29+
From URL
30+
</Button>
31+
</div>
32+
</Overlay.Dialog>
33+
);
34+
}

0 commit comments

Comments
 (0)