Launcher: BlueMap view with player side panel, map actions, guild invitations
This commit is contained in:
10 files changed
+342
-7
No files matched your search
@@ -69,7 +69,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
type Invite = { id: number; guild_id: string; guild_name: string; guild_tag: string; icon_url: string; inviter: string };
|
||||
let invites = $state<Invite[]>([]);
|
||||
async function loadInvites() {
|
||||
try { invites = await invoke<Invite[]>('my_guild_invites'); } catch { invites = []; }
|
||||
}
|
||||
async function answerInvite(inv: Invite, accept: boolean) {
|
||||
try {
|
||||
await invoke('respond_guild_invite', { inviteId: inv.id, accept });
|
||||
toast(accept ? `Welcome to ${inv.guild_name}!` : 'Invitation declined', 'ok');
|
||||
await Promise.all([loadInvites(), loadInstanceGuildData()]);
|
||||
} catch (e: any) {
|
||||
toast(typeof e === 'string' ? e : e?.message ?? 'Could not answer the invitation', 'error');
|
||||
loadInvites();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
loadInvites();
|
||||
try { walletServers = (await invoke<{ servers: typeof walletServers }>('get_public_servers')).servers; }
|
||||
catch { walletServers = []; }
|
||||
});
|
||||
@@ -169,6 +186,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if invites.length}
|
||||
<section class="invites glass" aria-label="Guild invitations">
|
||||
<h3>Guild invitations</h3>
|
||||
{#each invites as inv (inv.id)}
|
||||
<div class="invite">
|
||||
<span class="dot"><Shield size={15} /></span>
|
||||
<span class="text"><strong>[{inv.guild_tag}] {inv.guild_name}</strong><span class="muted tiny">Invited by {inv.inviter}</span></span>
|
||||
<button class="sm ghost" onclick={() => answerInvite(inv, false)}>Decline</button>
|
||||
<button class="sm primary" onclick={() => answerInvite(inv, true)} disabled={!!myGuild} title={myGuild ? 'Leave your guild first' : ''}>Accept</button>
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if loading && !myGuild && !allGuilds.length}
|
||||
<div class="center-state"><LoaderCircle class="spin" size={32} /></div>
|
||||
{:else if myGuild}
|
||||
@@ -416,6 +447,11 @@
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.invites { display: flex; flex-direction: column; gap: 0.6rem; padding: 0.9rem 1.1rem; border-radius: var(--radius); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line)); }
|
||||
.invites h3 { margin: 0; font-size: 0.95rem; }
|
||||
.invite { display: flex; align-items: center; gap: 0.7rem; }
|
||||
.invite .dot { width: 2rem; height: 2rem; display: grid; place-items: center; border-radius: 0.6rem; background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
|
||||
.invite .text { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||
.guilds-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import NewsFeed from '../components/NewsFeed.svelte';
|
||||
import Console from '../components/Console.svelte';
|
||||
import LiveMapModal from '../components/LiveMapModal.svelte';
|
||||
import BlueMapModal from '../components/BlueMapModal.svelte';
|
||||
import { abs, activeAccount, app, instances, selectedInstance } from '../lib/store.svelte';
|
||||
import { loaderName, shortLoaderVersion } from '../lib/format';
|
||||
import { invoke, openUrl } from '../lib/tauri';
|
||||
@@ -14,7 +15,7 @@
|
||||
// Lucide ships no brand logos, so each platform gets a recognisable generic icon.
|
||||
const linkIcon: Record<string, any> = { discord: MessageCircle, website: Globe, store: ShoppingBag, youtube: CirclePlay, twitch: Tv, github: Code, twitter: AtSign };
|
||||
// A game server linked to this instance with the panel's Live Map switched on.
|
||||
let mapServer = $state<{ id: number; name: string } | null>(null);
|
||||
let mapServer = $state<{ id: number; name: string; map_address?: string } | null>(null);
|
||||
let mapOpen = $state(false);
|
||||
$effect(() => {
|
||||
const id = inst?.id;
|
||||
@@ -22,8 +23,8 @@
|
||||
mapOpen = false;
|
||||
if (!id) return;
|
||||
let alive = true;
|
||||
invoke<{ servers: Array<{ id: number; name: string; instance_id: string; live_map?: boolean }> }>('get_public_servers')
|
||||
.then((r) => { if (alive) mapServer = r.servers.find((s) => s.instance_id === id && s.live_map) ?? null; })
|
||||
invoke<{ servers: Array<{ id: number; name: string; instance_id: string; live_map?: boolean; map_address?: string }> }>('get_public_servers')
|
||||
.then((r) => { if (alive) mapServer = r.servers.find((s) => s.instance_id === id && (s.map_address || s.live_map)) ?? null; })
|
||||
.catch(() => {});
|
||||
return () => { alive = false; };
|
||||
});
|
||||
@@ -82,7 +83,7 @@
|
||||
</aside>
|
||||
|
||||
{#if app.consoleOpen}<Console />{/if}
|
||||
{#if mapOpen && mapServer}<LiveMapModal serverId={mapServer.id} serverName={mapServer.name} onclose={() => (mapOpen = false)} />{/if}
|
||||
{#if mapOpen && mapServer?.map_address}<BlueMapModal serverId={mapServer.id} serverName={mapServer.name} address={mapServer.map_address} instanceId={inst?.id ?? ''} onclose={() => (mapOpen = false)} />{:else if mapOpen && mapServer}<LiveMapModal serverId={mapServer.id} serverName={mapServer.name} onclose={() => (mapOpen = false)} />{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in new issue
Block a user