who has this: a gap card says who holds it, the sheet and the open album name them, a Space grows a Holdings tab, and Get it is the hub's own grab or a wait for their presence

This commit is contained in:
KeelrLeek committed 2026-09-08 12:10:49 -05:00
1 parent a98c750908
commit b8e6fb7396
11 files changed
+454 -6

No files matched your search

+29
View File
@@ -52,6 +52,8 @@
import { openPreview } from "../lib/preview/previewSession.svelte.js";
import { traceToSvg, svgDataUrl } from "../lib/trace.js";
import Icon from "../components/Icon.svelte";
import WhoHas from "../components/WhoHas.svelte";
import { openWhoHas, holdersFor, pairKey, holderCount } from "../lib/whoHas.svelte.js";
import PlaylistMenu from "../components/PlaylistMenu.svelte";
import { consumeScroll, restoreScroll } from "../lib/navigation.svelte.js";
import HeroGpuCanvas from "../components/HeroGpuCanvas.svelte";
@@ -2536,6 +2538,17 @@
});
const visibleTabs = $derived(tabs.filter((tab) => tab.key === activeTabKey));
// Who in your rooms holds a release you lack: asked for the visible tab's
// families at once, filed by pairKey; the count rides the card, and the
// open album shows the people.
let heldBy = $state({});
const heldFor = (title) => (page ? heldBy[pairKey(page.artist.name, title)] : null);
$effect(() => {
const name = page?.artist?.name;
const items = visibleTabs.flatMap((g) => g.items).filter((al) => !inLibrary(al)).map((al) => ({ artist: name, album: al.title }));
if (!name || !items.length) return;
untrack(() => void holdersFor(items).then((m) => { for (const [k, v] of m) heldBy[k] = v; }));
});
// FLIP for the stage-2 correction. Svelte's `animate:` directive can't be
// used here — it requires the animated element to be the ONLY child of the
@@ -5399,6 +5412,9 @@
onclick={(e) => { e.stopPropagation(); streamRelease(al, al); }}
><Icon name="play" size={13} /></button>
{/if}
{#if heldFor(al.title)?.length}
<button class="have" title="In your rooms — who has it?" onclick={(e) => { e.stopPropagation(); openWhoHas({ artist: page.artist.name, album: al.title, cover: al.cover ?? null }, e.currentTarget.closest(".release")); }}>{holderCount(heldFor(al.title))} {holderCount(heldFor(al.title)) === 1 ? "has it" : "have it"}</button>
{/if}
</div>
<div class="title" title={al.title}>
{al.title}
@@ -5565,6 +5581,12 @@
{/if}
</div>
<div class="xp-tracks">
{#if openAlbum && !inLibrary(openAlbum) && heldFor(openAlbum.title)?.length}
<div class="xp-whohas">
<div class="xp-whohas-head"><span class="t">Who has this</span><span class="p">from your rooms' holdings · names compared, never files</span></div>
{#key openAlbum.id}<WhoHas artist={page.artist.name} album={openAlbum.title} compact />{/key}
</div>
{/if}
{#if openLoading}
<!-- Nothing at all until the load
has outlasted LOADING_GRACE:
@@ -9602,4 +9624,11 @@
:global(:root.glass) .notch {
background: var(--glass-surface);
}
/* who in your rooms holds a release you lack — the count rides the card */
.have { position: absolute; right: 6px; bottom: 6px; z-index: 2; display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px; border-radius: 99px; background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); color: #fff; font-size: 10.5px; font-weight: 900; border: 1px solid rgba(255, 255, 255, 0.14); cursor: pointer; white-space: nowrap; }
.have:hover { background: rgba(0, 0, 0, 0.82); border-color: rgba(255, 255, 255, 0.34); }
.xp-whohas { margin: 0 0 12px; padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.07); }
.xp-whohas-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; flex-wrap: wrap; }
.xp-whohas-head .t { font-size: 12.5px; font-weight: 800; white-space: nowrap; }
.xp-whohas-head .p { font-size: 11px; color: var(--text-muted); }
</style>