// "Who has this?" — the ask, shared by every surface that can raise it: a // gap card on Library, a release on an artist page, an open album, a Space's // Holdings tab. The answer is local (the rooms' holdings are cached on this // device), so asking is cheap enough to do for a whole shelf of gaps at once. // // One sheet at a time, portalled to body by WhoHasSheet.svelte, opened from // wherever the card is and anchored to it. App.svelte mounts the sheet once. import { askHoldings } from "./community.svelte.js"; export const whoHas = $state({ open: false, artist: "", album: "", mbid: null, cover: null, /** Where the card was (viewport px), so the sheet can sit beside it. */ anchor: null, }); export function openWhoHas({ artist, album, mbid = null, cover = null }, anchorEl = null) { const r = anchorEl?.getBoundingClientRect?.(); whoHas.artist = artist; whoHas.album = album; whoHas.mbid = mbid; whoHas.cover = cover; whoHas.anchor = r ? { x: r.left, y: r.top, w: r.width, h: r.height } : null; whoHas.open = true; } export function closeWhoHas() { whoHas.open = false; } /** The map key a page files holders under. */ export const pairKey = (artist, album) => `${(artist ?? "").trim().toLowerCase()}${(album ?? "").trim().toLowerCase()}`; /** Holders for many albums in one ask: pairKey → holders[]. Empty lists are * filed too, so a page can tell "asked, nobody" from "not asked yet". */ export async function holdersFor(items) { const out = new Map(); if (!items.length) return out; try { const answers = await askHoldings(items.map((i) => ({ artist: i.artist, album: i.album, mbid: i.mbid ?? null }))); for (const a of answers) out.set(pairKey(a.artist, a.album), a.holders); } catch { // no rooms, no node, no answer — the shelf simply carries no marker } return out; } /** Distinct people, so a holder in two rooms counts once on a card. */ export function holderCount(holders) { const seen = new Set(); for (const h of holders ?? []) seen.add((h.username ?? h.member).toLowerCase()); return seen.size; }