community: the library overlap shelf, their share folders against your albums on this device after a browse

This commit is contained in:
KeelrLeek committed 2026-09-02 17:08:25 -05:00
1 parent d7bc92fda7
commit 0846b39d45
6 files changed
+451 -7

No files matched your search

+56 -5
View File
@@ -91,6 +91,9 @@
openBoard,
proposeToBoard,
decideProposal,
overlapFor,
computeOverlap,
forgetOverlap,
} from "../lib/community.svelte.js";
onMount(() => {
@@ -583,6 +586,7 @@
const decideOn = (p, accept) =>
decideProposal(sv.owner, sv.space_id, p.id, accept, boardReason[p.id] || (!accept && p.stale ? "stale base — re-submit against the current revision" : "")).catch(() => {});
const whoProposed = (p) => (p.is_me ? "you" : p.username ? display(p.username) : `SoulStream ID ${fpShort(p.author)}`);
const drawerOverlap = $derived(drawerUser ? overlapFor(drawerUser) : null);
async function importPinned() {
if (!sv?.pinned_uuid) return;
const id = await shareImport({ id: "pin:" + sv.pinned_uuid, owner_key: sv.pinned_owner, playlist_uuid: sv.pinned_uuid, name: pinnedPreview?.name ?? "Pinned playlist" });
@@ -1135,11 +1139,29 @@
<h2 class="ax-title">Library overlap</h2>
<span class="prov"><Icon name="library" size={12} /> computed on this device, after an explicit browse</span>
</div>
<div class="emptyshelf">
<Icon name="folder" size={22} />
<div><b>Nothing computed yet.</b> Browse a person's shares and SoulStream compares them with your library locally — nothing is uploaded to do it. The comparison itself is the next piece of this shelf.</div>
<button class="ax-ghost sm" onclick={() => setSection("people")}>Browse someone</button>
</div>
{#if community.overlap.length}
<div class="track">
{#each community.overlap.slice(0, 24) as o (o.username)}
<button class="pcard" onclick={() => (community.drawer = o.username)}>
<span class="face" style:background="hsl({hueOf(o.username)} 30% 14%)"><Identicon seed={o.username} size={84} presence={presenceOf(o.username)} /></span>
<span class="nm">{display(o.username)}</span>
<span class="why">
{#if o.albums_in_common_count}shares <b>{o.albums_in_common_count}</b> {o.albums_in_common_count === 1 ? "album" : "albums"} you own {#if o.gaps_count}· <b>{o.gaps_count}</b> you lack{/if}
{:else if o.artists_in_common_count}<b>{o.artists_in_common_count}</b> {o.artists_in_common_count === 1 ? "artist" : "artists"} in common · <b>{o.gaps_count}</b> albums you lack
{:else if o.their_albums}nothing in common · {fmtCount(o.their_albums)} albums browsed
{:else}no album folders found{/if}
</span>
</button>
{/each}
</div>
<p class="hint pad">Names compared, not files: a folder counts as yours when your library has that album under that artist. Browse someone again to refresh their line.</p>
{:else}
<div class="emptyshelf">
<Icon name="folder" size={22} />
<div><b>Nothing computed yet.</b> Browse a person's shares and SoulStream compares them with your library locally — nothing is uploaded to do it.</div>
<button class="ax-ghost sm" onclick={() => setSection("people")}>Browse someone</button>
</div>
{/if}
</div>
<div class="hint wide">
There is no global directory here and no "people you may know". Every shelf says where its people came from. Freenet
@@ -1667,6 +1689,28 @@
</div>
<textarea class="note-in" rows="2" placeholder="A private note — only you see it" bind:value={noteDraft} onchange={() => saveMeta()}></textarea>
<div class="dsec">
<h4>Library overlap <span class="src slsk"><Icon name="library" size={10} />this device</span></h4>
{#if drawerOverlap}
<p class="hint">
{#if drawerOverlap.their_albums}<b>{drawerOverlap.albums_in_common_count}</b> of their {fmtCount(drawerOverlap.their_albums)} albums are in your library · <b>{drawerOverlap.artists_in_common_count}</b> {drawerOverlap.artists_in_common_count === 1 ? "artist" : "artists"} in common · <b>{drawerOverlap.gaps_count}</b> you lack{:else}No album folders among {fmtCount(drawerOverlap.audio_files)} audio files.{/if}
· compared {fmtWhen(drawerOverlap.computed_at * 1000)}
</p>
{#if drawerOverlap.albums_in_common.length}
<div class="ovl"><span class="lbl">In common</span>{#each drawerOverlap.albums_in_common.slice(0, 8) as a, i (i)}<span class="ov"><b>{a.album}</b> · {a.artist}</span>{/each}{#if drawerOverlap.albums_in_common_count > 8}<span class="ov more">+{drawerOverlap.albums_in_common_count - 8} more</span>{/if}</div>
{/if}
{#if drawerOverlap.gaps.length}
<div class="ovl"><span class="lbl">You lack</span>{#each drawerOverlap.gaps.slice(0, 8) as a, i (i)}<span class="ov gap"><b>{a.album}</b> · {a.artist}</span>{/each}{#if drawerOverlap.gaps_count > 8}<span class="ov more">+{drawerOverlap.gaps_count - 8} more</span>{/if}</div>
{/if}
<div class="bbtns">
<button class="ax-ghost sm" onclick={() => computeOverlap(drawerUser)} disabled={!online || !!community.busy["overlap:" + drawerUser]}><Icon name="refresh" size={12} /> {community.busy["overlap:" + drawerUser] ? "Browsing…" : "Compare again"}</button>
<button class="lnk" onclick={() => forgetOverlap(drawerUser)} disabled={!!community.busy["overlap:" + drawerUser]}>forget</button>
</div>
{:else}
<p class="hint">Not compared yet. Their share folders against your albums, on this device — nothing is uploaded to do it.</p>
<div class="bbtns"><button class="ax-ghost sm" onclick={() => computeOverlap(drawerUser)} disabled={!online || !!community.busy["overlap:" + drawerUser]}><Icon name="library" size={12} /> {community.busy["overlap:" + drawerUser] ? "Browsing their shares…" : "Compare with my library"}</button></div>
{/if}
</div>
<div class="dsec">
<h4>Native Soulseek profile <span class="src slsk"><Icon name="globe" size={10} />Soulseek</span></h4>
<dl class="kv">
@@ -2045,6 +2089,13 @@
.sphead .nm { font-size: 20px; font-weight: 800; display: flex; align-items: center; gap: 10px; }
.sphead .sub { font-size: 12px; color: var(--text-muted); margin-top: 4px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; max-width: none; }
.src.space { background: color-mix(in srgb, var(--space) 14%, transparent); color: #d9a6ff; }
/* library overlap in the drawer */
.ovl { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; align-items: center; }
.ovl .lbl { font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); margin-right: 4px; }
.ovl .ov { font-size: 11px; padding: 3px 8px; border-radius: 99px; background: var(--surf-hi); color: var(--text-secondary); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovl .ov b { color: var(--text-primary); font-weight: 700; }
.ovl .ov.gap { background: color-mix(in srgb, var(--warn) 10%, transparent); }
.ovl .ov.more { background: none; color: var(--text-muted); }
/* the proposal board */
.bbtns { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.dlist { list-style: none; margin: 10px 0; padding: 0; display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow: auto; }