minecraft skins!
This commit is contained in:
+7
-4
@@ -35,8 +35,8 @@ async deleteRemote(id: string) : Promise<boolean> {
|
||||
async getProfile() : Promise<User> {
|
||||
return await TAURI_INVOKE("get_profile");
|
||||
},
|
||||
async updateProfile(displayName: string) : Promise<User> {
|
||||
return await TAURI_INVOKE("update_profile", { displayName });
|
||||
async updateProfile(displayName: string, skinData: number[] | null) : Promise<User> {
|
||||
return await TAURI_INVOKE("update_profile", { displayName, skinData });
|
||||
},
|
||||
async getPublicKey() : Promise<string> {
|
||||
return await TAURI_INVOKE("get_public_key");
|
||||
@@ -50,6 +50,9 @@ async listFriendStatuses() : Promise<string[]> {
|
||||
async resolveFriendDisplayName(userId: string) : Promise<string | null> {
|
||||
return await TAURI_INVOKE("resolve_friend_display_name", { userId });
|
||||
},
|
||||
async resolveSkin(userId: string, skinHash: string) : Promise<string | null> {
|
||||
return await TAURI_INVOKE("resolve_skin", { userId, skinHash });
|
||||
},
|
||||
async pickAndSendImage(recipientId: string) : Promise<boolean> {
|
||||
return await TAURI_INVOKE("pick_and_send_image", { recipientId });
|
||||
},
|
||||
@@ -125,7 +128,7 @@ export type ForegroundAppChanged = { meta: AppMeta }
|
||||
* A configured public-key relationship with optional cached remote metadata.
|
||||
* The display name remains absent until learned from a signed remote profile.
|
||||
*/
|
||||
export type Friend = { id: string; displayName: string | null }
|
||||
export type Friend = { id: string; displayName: string | null; skinHash: string | null }
|
||||
export type FriendForegroundAppChanged = { friendId: string; meta: AppMeta }
|
||||
export type FriendInteractionReceived = { interactionId: string; friendId: string; content: InteractionContent }
|
||||
export type FriendStatusesChanged = { friendIds: string[] }
|
||||
@@ -143,7 +146,7 @@ export type SceneHitbox = { x: number; y: number; width: number; height: number
|
||||
* Public user identity exchanged with peers and remote servers. `id` is the
|
||||
* user's Ed25519 public key and is not persisted as local profile metadata.
|
||||
*/
|
||||
export type User = { id: string; displayName: string }
|
||||
export type User = { id: string; displayName: string; skinHash: string | null }
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
let dirty = $state(false);
|
||||
let busy = $state(false);
|
||||
let error = $state("");
|
||||
let skinFile = $state<File | null>(null);
|
||||
let skinInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (!dirty) displayName = $profile?.displayName ?? "";
|
||||
@@ -21,12 +23,15 @@
|
||||
onMount(() => register("general", { apply, reset }, { dirty, busy }));
|
||||
|
||||
function updateDirty() {
|
||||
dirty = displayName.trim() !== ($profile?.displayName ?? "");
|
||||
dirty =
|
||||
displayName.trim() !== ($profile?.displayName ?? "") || skinFile !== null;
|
||||
error = "";
|
||||
}
|
||||
|
||||
function reset() {
|
||||
displayName = $profile?.displayName ?? "";
|
||||
skinFile = null;
|
||||
if (skinInput) skinInput.value = "";
|
||||
dirty = false;
|
||||
error = "";
|
||||
}
|
||||
@@ -42,8 +47,13 @@
|
||||
busy = true;
|
||||
error = "";
|
||||
try {
|
||||
await commands.updateProfile(nextName);
|
||||
const skinData = skinFile
|
||||
? Array.from(new Uint8Array(await skinFile.arrayBuffer()))
|
||||
: null;
|
||||
await commands.updateProfile(nextName, skinData);
|
||||
displayName = nextName;
|
||||
skinFile = null;
|
||||
if (skinInput) skinInput.value = "";
|
||||
dirty = false;
|
||||
return true;
|
||||
} catch (cause) {
|
||||
@@ -78,6 +88,34 @@
|
||||
This name can be changed at any time.
|
||||
</p>
|
||||
|
||||
<label class="fieldset-label mt-2" for="skin-file">Character skin</label>
|
||||
<div class="join w-full">
|
||||
<input
|
||||
class="input join-item min-w-0 flex-1"
|
||||
value={skinFile?.name ?? ($profile?.skinHash ? "Custom skin" : "Default skin")}
|
||||
readonly
|
||||
aria-label="Selected character skin"
|
||||
/>
|
||||
<button
|
||||
class="btn join-item"
|
||||
type="button"
|
||||
disabled={!$profile || busy}
|
||||
onclick={() => skinInput?.click()}>Choose PNG</button
|
||||
>
|
||||
</div>
|
||||
<input
|
||||
id="skin-file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="image/png"
|
||||
bind:this={skinInput}
|
||||
onchange={(event) => {
|
||||
skinFile = event.currentTarget.files?.[0] ?? null;
|
||||
updateDirty();
|
||||
}}
|
||||
/>
|
||||
<p class="fieldset-label">64×64 Minecraft-format PNG, base layer only.</p>
|
||||
|
||||
<label class="fieldset-label mt-2" for="public-key"
|
||||
>Identification Key
|
||||
<div
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
error = "";
|
||||
|
||||
try {
|
||||
await commands.updateProfile(displayName);
|
||||
await commands.updateProfile(displayName, null);
|
||||
} catch (err) {
|
||||
error = String(err);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { commands } from "$lib/bindings";
|
||||
|
||||
export const DEFAULT_SKIN_URL = "/default-skin.png";
|
||||
|
||||
export async function resolveSkinSource(
|
||||
userId: string,
|
||||
skinHash: string | null,
|
||||
) {
|
||||
if (!skinHash) return DEFAULT_SKIN_URL;
|
||||
|
||||
try {
|
||||
const data = await commands.resolveSkin(userId, skinHash);
|
||||
return data ? `data:image/png;base64,${data}` : DEFAULT_SKIN_URL;
|
||||
} catch (error) {
|
||||
console.error(`Failed to resolve custom skin for ${userId}`, error);
|
||||
return DEFAULT_SKIN_URL;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user