forked from Keelrleek/SoulStream
community: the drawer asks a peer for its card and the server for interests, and discover gets the similar-users shelf
This commit is contained in:
1 parent
64a7561750
commit
5160d97a5f
10 files changed
+412
-2
No files matched your search
@@ -56,6 +56,8 @@
|
||||
setContactMeta,
|
||||
toggleRoomFav,
|
||||
personaKey,
|
||||
loadUserInfo,
|
||||
loadSimilar,
|
||||
} from "../lib/community.svelte.js";
|
||||
|
||||
onMount(() => {
|
||||
@@ -385,6 +387,21 @@
|
||||
noteDraft = m?.note ?? "";
|
||||
});
|
||||
});
|
||||
// The native card is fetched only for a surfaced profile (the design's
|
||||
// rule), once per session per person, and only while signed in.
|
||||
$effect(() => {
|
||||
const u = drawerUser;
|
||||
if (u && online) untrack(() => loadUserInfo(u));
|
||||
});
|
||||
const drawerInfo = $derived(drawerUser ? community.userInfo[drawerUser] : null);
|
||||
const drawerInterests = $derived(drawerUser ? community.interests[drawerUser] : null);
|
||||
const drawerPhoto = $derived(
|
||||
drawerInfo?.picture_png_base64 ? `data:image/png;base64,${drawerInfo.picture_png_base64}` : null,
|
||||
);
|
||||
// Discover's similar-users shelf asks the server once per session.
|
||||
$effect(() => {
|
||||
if (community.section === "discover" && online) untrack(() => loadSimilar());
|
||||
});
|
||||
function saveMeta(patch = {}) {
|
||||
if (!drawerUser) return;
|
||||
void setContactMeta({
|
||||
@@ -835,6 +852,27 @@
|
||||
<div class="emptyshelf"><Icon name="download" size={22} /><div>Nobody yet. People who send you files, or take files from you, appear here for the session.</div></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="shelf">
|
||||
<div class="ax-head">
|
||||
<h2 class="ax-title">Similar Soulseek interests</h2>
|
||||
<span class="prov"><Icon name="heart" size={12} /> the server's match on declared likes · not follows</span>
|
||||
</div>
|
||||
{#if community.similar?.users?.length}
|
||||
<div class="track">
|
||||
{#each community.similar.users.slice(0, 24) as su (su.username)}
|
||||
<button class="pcard" onclick={() => (community.drawer = su.username)}>
|
||||
<span class="face" style:background="hsl({hueOf(su.username)} 30% 14%)"><Identicon seed={su.username} size={84} presence={presenceOf(su.username)} /></span>
|
||||
<span class="nm">{su.username}</span>
|
||||
<span class="why">shares <b>{su.rating}</b> of your declared likes{#if isContact(su.username)} · a contact{/if}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if community.busy.similar}
|
||||
<div class="emptyshelf"><span class="bl"></span><div>Asking the server…</div></div>
|
||||
{:else}
|
||||
<div class="emptyshelf"><Icon name="heart" size={22} /><div>{community.similar?.error ? "The server did not answer." : online ? "The server matches people on the likes you declare on Soulseek — SoulStream has none set for you yet, so this shelf is empty." : "Sign in to Soulseek to ask the server."}</div>{#if online}<button class="ax-ghost sm" onclick={() => loadSimilar(true)} disabled={!!community.busy.similar}>Ask again</button>{/if}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="shelf">
|
||||
<div class="ax-head">
|
||||
<h2 class="ax-title">Library overlap</h2>
|
||||
@@ -1087,7 +1125,7 @@
|
||||
<div class="scrim" role="presentation" onclick={closeDrawer}></div>
|
||||
<aside class="drawer" aria-label="Profile of {drawerUser}">
|
||||
<div class="dhead">
|
||||
<Identicon seed={drawerUser} size={56} presence={presenceOf(drawerUser)} />
|
||||
<Identicon seed={drawerUser} size={56} photo={drawerPhoto} presence={presenceOf(drawerUser)} />
|
||||
<div class="dtx">
|
||||
<div class="nm">
|
||||
<input class="pet" placeholder={drawerUser} bind:value={petDraft} onchange={() => saveMeta()} title="Your private petname for this person" />
|
||||
@@ -1132,8 +1170,35 @@
|
||||
{:else}
|
||||
<dt>Shares</dt><dd class="muted">not watched — add to contacts and Soulseek reports status and share counts</dd>
|
||||
{/if}
|
||||
{#if drawerInfo && !drawerInfo.pending && !drawerInfo.error}
|
||||
<dt>Uploads</dt><dd>{drawerInfo.has_free_slot ? "a slot is free" : "no free slot"} · queue {fmtCount(drawerInfo.queue_size)} · {fmtCount(drawerInfo.total_uploads)} served</dd>
|
||||
{/if}
|
||||
</dl>
|
||||
<p class="hint">Description, country and interests come with the native user-info fetch — the next Soulseek piece for this drawer.</p>
|
||||
{#if !online}
|
||||
<p class="hint">Sign in to Soulseek to ask this person's client for their card.</p>
|
||||
{:else if !drawerInfo || drawerInfo.pending}
|
||||
<p class="hint"><span class="bl"></span> Asking their client for the card — direct, peer to peer.</p>
|
||||
{:else if drawerInfo.error}
|
||||
<p class="hint">Their client did not answer ({drawerInfo.error.replace(/^.*?: /, "")}) — offline, firewalled, or not sharing a card.</p>
|
||||
{:else}
|
||||
<p class="desc">{drawerInfo.description.trim() || "No description."}</p>
|
||||
{#if drawerInfo.picture_note}<p class="hint warn">{drawerInfo.picture_note}</p>{/if}
|
||||
{/if}
|
||||
<div class="hint" style="margin-top:10px">Soulseek interests <span class="muted">— raw likes and hates, not follows</span></div>
|
||||
{#if drawerInterests && !drawerInterests.pending && !drawerInterests.error}
|
||||
{#if drawerInterests.likes.length || drawerInterests.hates.length}
|
||||
<div class="tagrow">
|
||||
{#each drawerInterests.likes as l (l)}<span class="tag">{l}</span>{/each}
|
||||
{#each drawerInterests.hates as h (h)}<span class="tag hate">{h}</span>{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="hint">None declared.</p>
|
||||
{/if}
|
||||
{:else if drawerInterests?.error}
|
||||
<p class="hint">The server did not answer.</p>
|
||||
{:else if online}
|
||||
<p class="hint"><span class="bl"></span> asking the server</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="dsec">
|
||||
<h4>Signed SoulStream profile <span class="chip off">none presented</span></h4>
|
||||
@@ -1488,6 +1553,10 @@
|
||||
.grp-in:focus { border-color: rgba(255, 255, 255, 0.28); }
|
||||
.note-in { width: 100%; margin-top: 8px; background: var(--surf-hi); border: 1px solid transparent; border-radius: 8px; color: #fff; padding: 8px 12px; font: inherit; font-size: 12.5px; outline: none; resize: none; }
|
||||
.note-in:focus { border-color: rgba(255, 255, 255, 0.28); }
|
||||
.tagrow { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
|
||||
.tag { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 99px; background: var(--surf-hi); color: var(--text-secondary); }
|
||||
.tag.hate { text-decoration: line-through; opacity: 0.7; }
|
||||
.desc { margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--text-secondary); white-space: pre-wrap; overflow-wrap: anywhere; max-height: 180px; overflow-y: auto; }
|
||||
.dsec { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--hair); }
|
||||
.dsec h4 { margin: 0 0 8px; font-size: 10.5px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); display: flex; align-items: center; gap: 8px; }
|
||||
.dsec h4 .src, .dsec h4 .chip { margin-left: auto; }
|
||||
|
||||
Reference in new issue
Block a user