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:
1 parent
a98c750908
commit
b8e6fb7396
11 files changed
+454
-6
No files matched your search
@@ -19,7 +19,7 @@
|
||||
// is CatalogArtist's class for class with its animation numbers unchanged.
|
||||
// Styles are copied because Svelte scopes them per component — the same
|
||||
// trade CatalogDiscography made, with provenance comments at each block.
|
||||
import { tick } from "svelte";
|
||||
import { tick, untrack } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import Icon from "../components/Icon.svelte";
|
||||
import PlaylistMenu from "../components/PlaylistMenu.svelte";
|
||||
@@ -41,6 +41,7 @@
|
||||
importResume,
|
||||
goToView,
|
||||
} from "../lib/state.svelte.js";
|
||||
import { openWhoHas, holdersFor, pairKey, holderCount } from "../lib/whoHas.svelte.js";
|
||||
import {
|
||||
fileRelease,
|
||||
CAT_ORDER,
|
||||
@@ -121,6 +122,17 @@
|
||||
} catch { return {}; }
|
||||
})());
|
||||
let gapRows = $state({}); // foldKey(artist) -> catalog rows not owned
|
||||
let gapNames = $state({}); // foldKey(artist) -> the artist's display name
|
||||
// Who in your rooms holds a gap: asked for every open shelf's rows at
|
||||
// once, filed by pairKey. A marked card opens the ask sheet.
|
||||
let heldBy = $state({});
|
||||
$effect(() => {
|
||||
const items = Object.keys(gapOpen)
|
||||
.filter((k) => gapOpen[k] && gapRows[k]?.length && gapNames[k])
|
||||
.flatMap((k) => gapRows[k].map((r) => ({ artist: gapNames[k], album: r.title })));
|
||||
if (!items.length) return;
|
||||
untrack(() => void holdersFor(items).then((m) => { for (const [k, v] of m) heldBy[k] = v; }));
|
||||
});
|
||||
let gapOpen = $state({}); // foldKey(artist) -> line expanded
|
||||
let gapLoading = $state({});
|
||||
|
||||
@@ -182,6 +194,7 @@
|
||||
...gapRows,
|
||||
[key]: released.filter((r) => !ownedTitles.has(normKey(r.title))),
|
||||
};
|
||||
gapNames = { ...gapNames, [key]: artistName };
|
||||
gapTotals = { ...gapTotals, [key]: { total: released.length } };
|
||||
try { localStorage.setItem(GAPS_KEY, JSON.stringify(gapTotals)); } catch { /* ignore */ }
|
||||
} catch { /* the line stays collapsed; nothing is invented */ }
|
||||
@@ -1544,7 +1557,7 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet gapCard(r)}
|
||||
{#snippet gapCard(r, artist)}
|
||||
<!-- A record you do NOT own, at full size and dimmed. It is filed by the
|
||||
same classifier as everything else, off the CatalogAlbum payload's own
|
||||
fields (`record_type`/`track_count`/`release_date`, plus the is_live and
|
||||
@@ -1558,7 +1571,8 @@
|
||||
is_live: r.is_live,
|
||||
is_soundtrack: r.is_soundtrack,
|
||||
})}
|
||||
<div class="release gap" role="listitem" title={`${r.title} — not in your library`}>
|
||||
{@const held = heldBy[pairKey(artist, r.title)]}
|
||||
<div class="release gap" class:held={!!held?.length} role="listitem" title={`${r.title} — not in your library`}>
|
||||
<div class="cover-wrap">
|
||||
{#if r.release_date?.slice(0, 4)}
|
||||
<span class="ed-badge">{r.release_date.slice(0, 4)}</span>
|
||||
@@ -1568,6 +1582,9 @@
|
||||
{:else}
|
||||
<div class="cover placeholder"><Icon name="disc" size={30} /></div>
|
||||
{/if}
|
||||
{#if held?.length}
|
||||
<button class="have" title="{holderCount(held)} in your rooms {holderCount(held) === 1 ? "has" : "have"} this — who?" onclick={(e) => { e.stopPropagation(); openWhoHas({ artist, album: r.title, cover: r.cover ?? null }, e.currentTarget.closest(".release")); }}>{holderCount(held)} {holderCount(held) === 1 ? "has it" : "have it"}</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="title" title={r.title}>{r.title}</div>
|
||||
<div class="sub">{cat === "other" ? "" : CAT_ONE[cat]}</div>
|
||||
@@ -1826,7 +1843,7 @@
|
||||
<div class="lib-gaps" role="list">
|
||||
<div class="grid">
|
||||
{#each missing as r (r.id)}
|
||||
{@render gapCard(r)}
|
||||
{@render gapCard(r, name)}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2942,6 +2959,10 @@
|
||||
.lib-gaps { margin-top: 12px; }
|
||||
.lib-gaps .release.gap { opacity: 0.44; transition: opacity 150ms ease; cursor: default; }
|
||||
.lib-gaps .release.gap:hover { opacity: 1; background: none; }
|
||||
.lib-gaps .release.gap.held { opacity: 0.72; }
|
||||
/* 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); }
|
||||
|
||||
/* ============================================================ the table
|
||||
The power-user surface: every column is something the files carry. The
|
||||
|
||||
Reference in new issue
Block a user