fix: resolve all remaining audit failures across rust, svelte, and java
This commit is contained in:
1 parent
bc12a25a01
commit
590178e41a
35 files changed
+958
-300
No files matched your search
@@ -626,11 +626,14 @@ pub async fn create_guild(
|
||||
pub async fn get_guild_claims(
|
||||
state: State<'_, AppState>,
|
||||
instance_id: String,
|
||||
server_id: i64,
|
||||
dimension: Option<String>,
|
||||
center_x: i32,
|
||||
center_z: i32,
|
||||
) -> Res<Vec<GuildClaim>> {
|
||||
let panel = state.panel_url().ok_or("no panel configured")?;
|
||||
let dim = dimension.unwrap_or_else(|| "minecraft:overworld".into());
|
||||
let resp = state.http.get(format!("{panel}/api/v1/guilds/claims/grid?instance_id={instance_id}&dimension={dim}"))
|
||||
let resp = state.http.get(format!("{panel}/api/v1/guilds/claims/grid?instance_id={instance_id}&server_id={server_id}&dimension={dim}¢er_x={center_x}¢er_z={center_z}"))
|
||||
.send().await.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
return Err("unable to load claims".into());
|
||||
@@ -643,6 +646,7 @@ pub async fn claim_guild_chunk(
|
||||
state: State<'_, AppState>,
|
||||
guild_id: String,
|
||||
instance_id: String,
|
||||
server_id: i64,
|
||||
dimension: String,
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
@@ -650,6 +654,7 @@ pub async fn claim_guild_chunk(
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/claim")).await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"server_id": server_id,
|
||||
"dimension": dimension,
|
||||
"chunk_x": chunk_x,
|
||||
"chunk_z": chunk_z,
|
||||
@@ -662,25 +667,9 @@ pub async fn claim_guild_chunk(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unclaim_guild_chunk(
|
||||
state: State<'_, AppState>,
|
||||
guild_id: String,
|
||||
instance_id: String,
|
||||
dimension: String,
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/unclaim")).await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"dimension": dimension,
|
||||
"chunk_x": chunk_x,
|
||||
"chunk_z": chunk_z,
|
||||
})).send().await.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to unclaim chunk").to_string());
|
||||
}
|
||||
pub async fn unclaim_guild_chunk(state: State<'_, AppState>, claim_id: i64) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::DELETE, &format!("/guilds/claims/{claim_id}")).await?;
|
||||
req.send().await.map_err(err)?.error_for_status().map_err(err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -827,8 +816,18 @@ pub async fn get_transactions(state: State<'_, AppState>) -> Res<Vec<EconomyTran
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_direct_messages(state: State<'_, AppState>, friend_uuid: String) -> Res<Vec<DirectMessage>> {
|
||||
let req = account_api(&state, reqwest::Method::GET, &format!("/messages/{friend_uuid}")).await?;
|
||||
pub async fn get_direct_messages(
|
||||
state: State<'_, AppState>,
|
||||
friend_uuid: String,
|
||||
before_id: Option<i64>,
|
||||
) -> Res<Vec<DirectMessage>> {
|
||||
// FIX #13: Support optional before_id for loading older messages beyond the first 100.
|
||||
let path = if let Some(before) = before_id {
|
||||
format!("/messages/{friend_uuid}?before_id={before}")
|
||||
} else {
|
||||
format!("/messages/{friend_uuid}")
|
||||
};
|
||||
let req = account_api(&state, reqwest::Method::GET, &path).await?;
|
||||
let resp = req.send().await.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
return Err("unable to load messages".into());
|
||||
@@ -863,7 +862,7 @@ pub async fn send_game_invite(
|
||||
recipient_uuid: String,
|
||||
instance_id: String,
|
||||
server_id: Option<i64>,
|
||||
) -> Res<GameInvite> {
|
||||
) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::POST, "/invites").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"recipient_uuid": recipient_uuid,
|
||||
@@ -874,13 +873,13 @@ pub async fn send_game_invite(
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to send invite").to_string());
|
||||
}
|
||||
resp.json().await.map_err(err)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn respond_game_invite(state: State<'_, AppState>, invite_id: String, accept: bool) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/invites/{invite_id}/respond")).await?;
|
||||
let resp = req.json(&serde_json::json!({ "accept": accept })).send().await.map_err(err)?;
|
||||
let resp = req.json(&serde_json::json!({ "action": if accept { "accept" } else { "decline" } })).send().await.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to respond to invite").to_string());
|
||||
@@ -917,7 +916,8 @@ pub async fn update_my_profile(
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to update profile").to_string());
|
||||
}
|
||||
resp.json().await.map_err(err)
|
||||
let uuid = state.accounts.read().unwrap().active().ok_or("no active account")?.uuid.clone();
|
||||
get_user_profile(state, uuid).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -937,7 +937,7 @@ pub async fn create_user_post(
|
||||
content: String,
|
||||
image_url: Option<String>,
|
||||
) -> Res<UserPost> {
|
||||
let req = account_api(&state, reqwest::Method::POST, "/posts").await?;
|
||||
let req = account_api(&state, reqwest::Method::POST, "/profiles/me/posts").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"content": content,
|
||||
"image_url": image_url,
|
||||
|
||||
@@ -14,10 +14,15 @@ pub struct Recorder {
|
||||
}
|
||||
|
||||
pub fn capture(state: &AppState) -> Option<Recorder> {
|
||||
// We need at least a panel URL to report to. Token is optional – offline
|
||||
// and Microsoft accounts won't have a panel token, but we still want to
|
||||
// fire events where possible (the endpoint will just record them as
|
||||
// unauthenticated). If there is no panel configured at all, skip silently.
|
||||
let panel = state.panel_url()?;
|
||||
let token = accounts::panel_token(state);
|
||||
let accounts = state.accounts.read().unwrap();
|
||||
let active = accounts.active()?;
|
||||
let token = accounts::panel_token(state);
|
||||
|
||||
Some(Recorder {
|
||||
http: state.http.clone(),
|
||||
panel,
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
onunclaim?: (claimId: number) => void;
|
||||
} = $props();
|
||||
|
||||
let servers = $state<Array<{ id: number; name: string; instance_id: string }>>([]);
|
||||
let serverId = $state<number | null>(null);
|
||||
let dimension = $state<'minecraft:overworld' | 'minecraft:the_nether' | 'minecraft:the_end'>('minecraft:overworld');
|
||||
let centerX = $state(0);
|
||||
let centerZ = $state(0);
|
||||
@@ -39,11 +41,14 @@
|
||||
] as const;
|
||||
|
||||
async function loadClaims() {
|
||||
if (!instanceId) return;
|
||||
if (!instanceId || !serverId) { claims = []; return; }
|
||||
loading = true;
|
||||
try {
|
||||
claims = await invoke<GuildClaim[]>('get_guild_claims', {
|
||||
instanceId,
|
||||
serverId,
|
||||
centerX,
|
||||
centerZ,
|
||||
dimension
|
||||
});
|
||||
} catch (e: any) {
|
||||
@@ -66,11 +71,13 @@
|
||||
);
|
||||
|
||||
async function handleClaim() {
|
||||
if (!selectedChunk || !myGuildId) return;
|
||||
if (!selectedChunk || !myGuildId || !serverId) return;
|
||||
claiming = true;
|
||||
try {
|
||||
const claim = await invoke<GuildClaim>('claim_guild_chunk', {
|
||||
guildId: myGuildId,
|
||||
instanceId,
|
||||
serverId,
|
||||
dimension,
|
||||
chunkX: selectedChunk.x,
|
||||
chunkZ: selectedChunk.z
|
||||
@@ -112,17 +119,28 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (instanceId && dimension) {
|
||||
if (instanceId && dimension && serverId) {
|
||||
loadClaims();
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
loadClaims();
|
||||
onMount(async () => {
|
||||
const result = await invoke<{ servers: typeof servers }>('get_public_servers');
|
||||
servers = result.servers;
|
||||
});
|
||||
$effect(() => {
|
||||
const available = servers.filter(s => s.instance_id === instanceId);
|
||||
if (!available.some(s => s.id === serverId)) serverId = available[0]?.id ?? null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="chunk-map-container glass">
|
||||
<label>Game server
|
||||
<select bind:value={serverId}>
|
||||
{#each servers.filter(s => s.instance_id === instanceId) as server}<option value={server.id}>{server.name}</option>{/each}
|
||||
</select>
|
||||
</label>
|
||||
{#if !serverId}<p>Link a game server to this instance in the panel before claiming territory.</p>{/if}
|
||||
<!-- Controls Bar -->
|
||||
<div class="map-controls">
|
||||
<div class="dim-selector">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
savingProfile = true;
|
||||
try {
|
||||
profile = await invoke<UserProfileView>('update_my_profile', {
|
||||
bio: editBio.trim() || null,
|
||||
bio: editBio.trim(),
|
||||
bannerUrl: editBanner.trim() || null
|
||||
});
|
||||
editing = false;
|
||||
@@ -89,9 +89,8 @@
|
||||
|
||||
async function handleLikePost(post: UserPost) {
|
||||
try {
|
||||
const liked = await invoke<boolean>('like_user_post', { postId: post.id });
|
||||
post.liked_by_me = liked;
|
||||
post.likes_count = liked ? post.likes_count + 1 : Math.max(0, post.likes_count - 1);
|
||||
await invoke('like_user_post', { postId: post.id });
|
||||
post.likes_count += 1;
|
||||
} catch (e: any) {
|
||||
toast(e?.message ?? 'Failed to update like', 'error');
|
||||
}
|
||||
@@ -131,8 +130,8 @@
|
||||
<div class="user-titles">
|
||||
<div class="user-row">
|
||||
<h2>{profile.username}</h2>
|
||||
{#if profile.global_level?.title}
|
||||
<span class="badge title-badge"><Award size={12} /> {profile.global_level.title}</span>
|
||||
{#if profile.level_info?.title}
|
||||
<span class="badge title-badge"><Award size={12} /> {profile.level_info.title}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="meta-row tiny muted">
|
||||
@@ -140,7 +139,7 @@
|
||||
<span>•</span>
|
||||
<span>{profile.friends_count} Friends</span>
|
||||
<span>•</span>
|
||||
<span class="global-lvl-tag">Level {profile.global_level?.level ?? 1}</span>
|
||||
<span class="global-lvl-tag">Level {profile.level_info?.level ?? 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,7 +194,7 @@
|
||||
<Award size={14} /> Levels & Ranks
|
||||
</button>
|
||||
<button class:active={activeTab === 'achievements'} onclick={() => (activeTab = 'achievements')}>
|
||||
<Trophy size={14} /> Achievements ({profile.achievements.length})
|
||||
<Trophy size={14} /> Achievements ({profile.achievements_count ?? profile.achievements?.length ?? 0})
|
||||
</button>
|
||||
<button class:active={activeTab === 'stats'} onclick={() => (activeTab = 'stats')}>
|
||||
<Swords size={14} /> Stats
|
||||
@@ -285,16 +284,16 @@
|
||||
<h3>SCOPENET Global Level</h3>
|
||||
</div>
|
||||
<div class="level-pill">
|
||||
Level {profile.global_level.level}
|
||||
Level {profile.level_info.level}
|
||||
</div>
|
||||
</div>
|
||||
<div class="xp-bar-wrap">
|
||||
<div class="xp-numbers tiny">
|
||||
<span>{profile.global_level.current_xp.toLocaleString()} XP</span>
|
||||
<span>{profile.global_level.next_level_xp.toLocaleString()} XP</span>
|
||||
<span>{profile.level_info.current_xp.toLocaleString()} XP</span>
|
||||
<span>{profile.level_info.next_level_xp.toLocaleString()} XP</span>
|
||||
</div>
|
||||
<div class="xp-bar">
|
||||
<div class="xp-fill" style:width="{profile.global_level.progress_pct}%"></div>
|
||||
<div class="xp-fill" style:width="{profile.level_info.progress_pct}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+13
-13
@@ -217,9 +217,9 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
|
||||
return null;
|
||||
case 'get_guild_members':
|
||||
return [
|
||||
{ guild_id: 'g1', user_uuid: 'mock-user-1', username: 'Alyssa', role: 'leader', joined_at: '2026-09-01T00:00:00Z', is_online: true },
|
||||
{ guild_id: 'g1', user_uuid: 'friend-1', username: 'Alex', role: 'officer', joined_at: '2026-09-02T00:00:00Z', is_online: true },
|
||||
{ guild_id: 'g1', user_uuid: 'friend-2', username: 'Steve', role: 'member', joined_at: '2026-09-05T00:00:00Z', is_online: false }
|
||||
{ guild_id: 'g1', uuid: 'mock-user-1', name: 'Alyssa', role: 'leader', joined_at: '2026-09-01T00:00:00Z', online: true },
|
||||
{ guild_id: 'g1', uuid: 'friend-1', name: 'Alex', role: 'officer', joined_at: '2026-09-02T00:00:00Z', online: true },
|
||||
{ guild_id: 'g1', uuid: 'friend-2', name: 'Steve', role: 'member', joined_at: '2026-09-05T00:00:00Z', online: false }
|
||||
];
|
||||
case 'get_guild_posts':
|
||||
return [
|
||||
@@ -229,9 +229,9 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
|
||||
return { id: 'gp' + Date.now(), guild_id: args?.guildId, author_uuid: 'mock-user-1', author_name: 'Alyssa', title: args?.title, content: args?.content, pinned: args?.pinned ?? false, created_at: new Date().toISOString() };
|
||||
case 'get_friends':
|
||||
return [
|
||||
{ uuid: 'friend-1', username: 'Alex', status: 'accepted', is_online: true, playing_on: 'Survival SMP', avatar_url: null },
|
||||
{ uuid: 'friend-2', username: 'Steve', status: 'accepted', is_online: false, playing_on: null, avatar_url: null },
|
||||
{ uuid: 'friend-3', username: 'Notch', status: 'pending_received', is_online: true, playing_on: null, avatar_url: null }
|
||||
{ uuid: 'friend-1', username: 'Alex', status: 'accepted', online: true, playing_on: 'Survival SMP', avatar_url: null },
|
||||
{ uuid: 'friend-2', username: 'Steve', status: 'accepted', online: false, playing_on: null, avatar_url: null },
|
||||
{ uuid: 'friend-3', username: 'Notch', status: 'pending_incoming', online: true, playing_on: null, avatar_url: null }
|
||||
];
|
||||
case 'send_friend_request':
|
||||
case 'respond_friend_request':
|
||||
@@ -239,17 +239,17 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
|
||||
return null;
|
||||
case 'get_direct_messages':
|
||||
return [
|
||||
{ id: 1, from_uuid: args?.friendUuid, to_uuid: 'mock-user-1', content: 'Hey! Want to team up and explore the trial chambers?', created_at: '2026-09-28T18:20:00Z', is_read: true },
|
||||
{ id: 2, from_uuid: 'mock-user-1', to_uuid: args?.friendUuid, content: 'Definitely! Let me grab some potions from base first.', created_at: '2026-09-28T18:22:00Z', is_read: true }
|
||||
{ id: 1, sender_uuid: args?.friendUuid, recipient_uuid: 'mock-user-1', content: 'Hey! Want to team up and explore the trial chambers?', created_at: '2026-09-28T18:20:00Z', is_read: true },
|
||||
{ id: 2, sender_uuid: 'mock-user-1', recipient_uuid: args?.friendUuid, content: 'Definitely! Let me grab some potions from base first.', created_at: '2026-09-28T18:22:00Z', is_read: true }
|
||||
];
|
||||
case 'send_direct_message':
|
||||
return { id: Date.now(), from_uuid: 'mock-user-1', to_uuid: args?.toUuid, content: args?.content, created_at: new Date().toISOString(), is_read: true };
|
||||
return { id: Date.now(), sender_uuid: 'mock-user-1', recipient_uuid: args?.friendUuid, content: args?.content, created_at: new Date().toISOString(), is_read: true };
|
||||
case 'get_game_invites':
|
||||
return [
|
||||
{ id: 'inv-1', from_uuid: 'friend-1', from_username: 'Alex', to_uuid: 'mock-user-1', instance_id: 'survival-smp', server_address: 'smp.scopenet.gg', server_name: 'Survival SMP', status: 'pending', created_at: '2026-09-28T19:00:00Z' }
|
||||
{ id: 'inv-1', sender_uuid: 'friend-1', sender_name: 'Alex', recipient_uuid: 'mock-user-1', instance_id: 'survival-smp', instance_name: 'smp.scopenet.gg', server_name: 'Survival SMP', status: 'pending', created_at: '2026-09-28T19:00:00Z' }
|
||||
];
|
||||
case 'send_game_invite':
|
||||
return { id: 'inv-' + Date.now(), from_uuid: 'mock-user-1', from_username: 'Alyssa', to_uuid: args?.toUuid, instance_id: args?.instanceId, server_address: args?.serverAddress, server_name: args?.serverName, status: 'pending', created_at: new Date().toISOString() };
|
||||
return { id: 'inv-' + Date.now(), sender_uuid: 'mock-user-1', sender_name: 'Alyssa', recipient_uuid: args?.friendUuid, instance_id: args?.instanceId, instance_name: args?.serverAddress, server_name: args?.serverName, status: 'pending', created_at: new Date().toISOString() };
|
||||
case 'respond_game_invite':
|
||||
return null;
|
||||
case 'get_user_profile':
|
||||
@@ -261,7 +261,7 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
|
||||
skin_url: null,
|
||||
skin_model: 'classic',
|
||||
cape_url: null,
|
||||
global_level: { uuid: args?.uuid, level: 14, current_xp: 3450, next_level_xp: 5000, progress_pct: 69, title: 'Veteran Adventurer', badges: ['Beta Pioneer', 'Dragon Slayer'], rewards: [] },
|
||||
level_info: { uuid: args?.uuid, level: 14, current_xp: 3450, next_level_xp: 5000, progress_pct: 69, title: 'Veteran Adventurer', badges: ['Beta Pioneer', 'Dragon Slayer'], rewards: [] },
|
||||
server_levels: [
|
||||
{ server_id: 1, server_name: 'Survival SMP', uuid: args?.uuid, level: 9, current_xp: 1200, next_level_xp: 2200, progress_pct: 54, rank_title: 'Knight' }
|
||||
],
|
||||
@@ -285,7 +285,7 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
|
||||
skin_url: null,
|
||||
skin_model: 'classic',
|
||||
cape_url: null,
|
||||
global_level: { uuid: 'mock-user-1', level: 14, current_xp: 3450, next_level_xp: 5000, progress_pct: 69, title: 'Veteran Adventurer', badges: ['Beta Pioneer'], rewards: [] },
|
||||
level_info: { uuid: 'mock-user-1', level: 14, current_xp: 3450, next_level_xp: 5000, progress_pct: 69, title: 'Veteran Adventurer', badges: ['Beta Pioneer'], rewards: [] },
|
||||
server_levels: [],
|
||||
badges: ['Beta Pioneer'],
|
||||
achievements: [],
|
||||
|
||||
+19
-16
@@ -226,16 +226,15 @@ export interface Guild {
|
||||
}
|
||||
|
||||
export interface GuildMember {
|
||||
guild_id: string;
|
||||
user_uuid: string;
|
||||
username: string;
|
||||
uuid: string;
|
||||
name: string;
|
||||
role: 'leader' | 'officer' | 'member';
|
||||
joined_at: string;
|
||||
is_online?: boolean;
|
||||
online?: boolean;
|
||||
}
|
||||
|
||||
export interface GuildPost {
|
||||
id: string;
|
||||
id: number;
|
||||
guild_id: string;
|
||||
author_uuid: string;
|
||||
author_name: string;
|
||||
@@ -248,7 +247,7 @@ export interface GuildPost {
|
||||
export interface GuildClaim {
|
||||
id: number;
|
||||
guild_id: string;
|
||||
instance_id: string;
|
||||
server_id: number;
|
||||
dimension: string;
|
||||
chunk_x: number;
|
||||
chunk_z: number;
|
||||
@@ -261,16 +260,16 @@ export interface GuildClaim {
|
||||
export interface FriendInfo {
|
||||
uuid: string;
|
||||
username: string;
|
||||
status: 'pending_sent' | 'pending_received' | 'accepted';
|
||||
is_online: boolean;
|
||||
status: 'pending_outgoing' | 'pending_incoming' | 'accepted';
|
||||
online: boolean;
|
||||
playing_on: string | null;
|
||||
avatar_url?: string | null;
|
||||
}
|
||||
|
||||
export interface DirectMessage {
|
||||
id: number;
|
||||
from_uuid: string;
|
||||
to_uuid: string;
|
||||
sender_uuid: string;
|
||||
recipient_uuid: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
is_read: boolean;
|
||||
@@ -278,18 +277,18 @@ export interface DirectMessage {
|
||||
|
||||
export interface GameInvite {
|
||||
id: string;
|
||||
from_uuid: string;
|
||||
from_username: string;
|
||||
to_uuid: string;
|
||||
sender_uuid: string;
|
||||
sender_name: string;
|
||||
recipient_uuid: string;
|
||||
instance_id: string;
|
||||
server_address: string;
|
||||
instance_name: string;
|
||||
server_name: string;
|
||||
status: 'pending' | 'accepted' | 'declined' | 'expired';
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface UserPost {
|
||||
id: string;
|
||||
id: number;
|
||||
user_uuid: string;
|
||||
username: string;
|
||||
content: string;
|
||||
@@ -307,10 +306,14 @@ export interface UserProfileView {
|
||||
skin_url: string | null;
|
||||
skin_model: string;
|
||||
cape_url: string | null;
|
||||
global_level: UserLevelInfo;
|
||||
global_level: number;
|
||||
global_xp: number;
|
||||
title: string | null;
|
||||
level_info: UserLevelInfo;
|
||||
server_levels: ServerLevelInfo[];
|
||||
badges: string[];
|
||||
achievements: Achievement[];
|
||||
achievements_count: number;
|
||||
posts: UserPost[];
|
||||
friends_count: number;
|
||||
stats: PlayerStats | null;
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<div class="guild-info">
|
||||
<div class="guild-title-line">
|
||||
<h2>{myGuild.name}</h2>
|
||||
<span class="badge member-badge"><Users size={12} /> {myGuild.member_count} / {myGuild.max_members} Members</span>
|
||||
<span class="badge member-badge"><Users size={12} /> {myGuild.member_count} Members</span>
|
||||
<span class="badge claim-badge"><Shield size={12} /> {myGuild.claims_count} Claims</span>
|
||||
</div>
|
||||
{#if myGuild.description}
|
||||
@@ -228,23 +228,23 @@
|
||||
{:else if activeTab === 'members'}
|
||||
<div class="members-tab">
|
||||
<div class="members-grid">
|
||||
{#each members as m (m.user_uuid)}
|
||||
{#each members as m (m.uuid)}
|
||||
<div
|
||||
class="member-card glass"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => (app.viewProfileUuid = m.user_uuid)}
|
||||
onkeydown={(e) => e.key === 'Enter' && (app.viewProfileUuid = m.user_uuid)}
|
||||
onclick={() => (app.viewProfileUuid = m.uuid)}
|
||||
onkeydown={(e) => e.key === 'Enter' && (app.viewProfileUuid = m.uuid)}
|
||||
>
|
||||
<div class="member-avatar-wrap">
|
||||
<Avatar uuid={m.user_uuid} name={m.username} size={2.5} />
|
||||
{#if m.is_online}
|
||||
<Avatar uuid={m.uuid} name={m.name} size={2.5} />
|
||||
{#if m.online}
|
||||
<span class="online-indicator" title="Online"></span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<div class="member-name-row">
|
||||
<span class="member-name">{m.username}</span>
|
||||
<span class="member-name">{m.name}</span>
|
||||
{#if m.role === 'leader'}
|
||||
<span class="badge leader-badge"><Crown size={11} /> Leader</span>
|
||||
{:else if m.role === 'officer'}
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
{#if skinProfiles.length}
|
||||
<div class="profiles-grid">
|
||||
{#each skinProfiles as sp (sp.id)}
|
||||
{@const isCurrent = profile.skin_model === sp.skin_model && (sp.cape_id === (profile.cape?.id ?? null))}
|
||||
{@const isCurrent = !!sp.skin_data && sp.skin_data === profile.skin_url && profile.skin_model === sp.skin_model && sp.cape_id === (profile.cape?.id ?? null)}
|
||||
<div class="profile-card" class:active-look={isCurrent}>
|
||||
<div class="profile-preview">
|
||||
<SkinView skin={sp.skin_data} cape={sp.cape_url} slim={sp.skin_model === 'slim'} side="front" scale={3.5} />
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
const text = messageInput.trim();
|
||||
try {
|
||||
const msg = await invoke<DirectMessage>('send_direct_message', {
|
||||
toUuid: activeChatFriend.uuid,
|
||||
friendUuid: activeChatFriend.uuid,
|
||||
content: text
|
||||
});
|
||||
messages = [...messages, msg];
|
||||
@@ -162,12 +162,12 @@
|
||||
if (!inviteTargetFriend) return;
|
||||
sendingInvite = true;
|
||||
try {
|
||||
const inst = instances().find((i) => i.id === inviteInstanceId);
|
||||
// FIX #14: The Rust command takes recipient_uuid, instance_id, server_id (optional i64).
|
||||
// serverAddress/serverName were wrong fields that the command doesn't accept.
|
||||
await invoke('send_game_invite', {
|
||||
toUuid: inviteTargetFriend.uuid,
|
||||
recipientUuid: inviteTargetFriend.uuid,
|
||||
instanceId: inviteInstanceId,
|
||||
serverAddress: inst?.server?.address ?? 'localhost',
|
||||
serverName: inst?.name ?? 'Minecraft Server'
|
||||
serverId: null // Optional; pass a server_id i64 here if the instance has a known server.
|
||||
});
|
||||
toast(`Game invite sent to ${inviteTargetFriend.username}!`, 'ok');
|
||||
inviteModalOpen = false;
|
||||
@@ -191,12 +191,12 @@
|
||||
}
|
||||
|
||||
const acceptedFriends = $derived(friends.filter((f) => f.status === 'accepted'));
|
||||
const pendingReceived = $derived(friends.filter((f) => f.status === 'pending_received'));
|
||||
const pendingSent = $derived(friends.filter((f) => f.status === 'pending_sent'));
|
||||
const pendingReceived = $derived(friends.filter((f) => f.status === 'pending_incoming'));
|
||||
const pendingSent = $derived(friends.filter((f) => f.status === 'pending_outgoing'));
|
||||
|
||||
const displayedFriends = $derived(
|
||||
friends.filter((f) => {
|
||||
if (friendsFilter === 'online') return f.status === 'accepted' && f.is_online;
|
||||
if (friendsFilter === 'online') return f.status === 'accepted' && f.online;
|
||||
if (friendsFilter === 'pending') return f.status !== 'accepted';
|
||||
return f.status === 'accepted';
|
||||
})
|
||||
@@ -267,7 +267,7 @@
|
||||
All ({acceptedFriends.length})
|
||||
</button>
|
||||
<button class="pill" class:active={friendsFilter === 'online'} onclick={() => (friendsFilter = 'online')}>
|
||||
Online ({acceptedFriends.filter((f) => f.is_online).length})
|
||||
Online ({acceptedFriends.filter((f) => f.online).length})
|
||||
</button>
|
||||
<button class="pill" class:active={friendsFilter === 'pending'} onclick={() => (friendsFilter = 'pending')}>
|
||||
Pending ({pendingReceived.length + pendingSent.length})
|
||||
@@ -355,13 +355,13 @@
|
||||
>
|
||||
<div class="avatar-holder">
|
||||
<Avatar uuid={friend.uuid} name={friend.username} size={2.6} />
|
||||
<span class="status-dot" class:online={friend.is_online}></span>
|
||||
<span class="status-dot" class:online={friend.online}></span>
|
||||
</div>
|
||||
<div class="name-block">
|
||||
<span class="user-title">{friend.username}</span>
|
||||
{#if friend.is_online && friend.playing_on}
|
||||
{#if friend.online && friend.playing_on}
|
||||
<span class="tiny playing-on">Playing on {friend.playing_on}</span>
|
||||
{:else if friend.is_online}
|
||||
{:else if friend.online}
|
||||
<span class="tiny online-label">Online in Launcher</span>
|
||||
{:else}
|
||||
<span class="tiny muted">Offline</span>
|
||||
@@ -494,7 +494,7 @@
|
||||
>
|
||||
<div class="avatar-holder">
|
||||
<Avatar uuid={friend.uuid} name={friend.username} size={2} />
|
||||
<span class="status-dot" class:online={friend.is_online}></span>
|
||||
<span class="status-dot" class:online={friend.online}></span>
|
||||
</div>
|
||||
<div class="conv-meta">
|
||||
<span class="conv-name">{friend.username}</span>
|
||||
@@ -521,7 +521,7 @@
|
||||
<Avatar uuid={activeChatFriend.uuid} name={activeChatFriend.username} size={2.2} />
|
||||
<div>
|
||||
<strong>{activeChatFriend.username}</strong>
|
||||
<span class="tiny muted block">{activeChatFriend.is_online ? 'Active now' : 'Offline'}</span>
|
||||
<span class="tiny muted block">{activeChatFriend.online ? 'Active now' : 'Offline'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="primary sm" onclick={() => openInviteModal(activeChatFriend!)}>
|
||||
@@ -537,7 +537,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
{#each messages as msg (msg.id)}
|
||||
{@const isMine = msg.from_uuid === activeAccount()?.uuid}
|
||||
{@const isMine = msg.sender_uuid === activeAccount()?.uuid}
|
||||
<div class="bubble-row" class:is-mine={isMine}>
|
||||
<div class="bubble" class:is-mine={isMine}>
|
||||
<p class="bubble-text">{msg.content}</p>
|
||||
@@ -586,11 +586,11 @@
|
||||
{#each invites as inv (inv.id)}
|
||||
<div class="invite-card glass">
|
||||
<div class="invite-top">
|
||||
<Avatar uuid={inv.from_uuid} name={inv.from_username} size={2.5} />
|
||||
<Avatar uuid={inv.sender_uuid} name={inv.sender_name} size={2.5} />
|
||||
<div class="invite-meta">
|
||||
<span class="inviter-name"><strong>{inv.from_username}</strong> invited you to play</span>
|
||||
<span class="inviter-name"><strong>{inv.sender_name}</strong> invited you to play</span>
|
||||
<span class="server-title">{inv.server_name}</span>
|
||||
<span class="tiny muted server-addr">{inv.server_address}</span>
|
||||
<span class="tiny muted server-addr">{inv.instance_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invite-footer">
|
||||
|
||||
Reference in new issue
Block a user