{
if (!this.targetLibraryId) {
await this.resolveTargetLibraryId();
}
if (!this.targetLibraryId) {
console.error('Cannot search for downloads: no library available');
return;
}
this.pickerOpen = true;
}
private renderPicker() {
if (!this.pickerOpen || !this.targetLibraryId) return nothing;
const tracks = this.currentTracks();
return html`
({
position: t.position || index + 1,
discNumber: t.discNumber ?? 0,
title: t.title,
artist: '',
lengthMillis: t.length ?? 0,
}))}
@picker-close=${() => {
this.pickerOpen = false;
}}
>
`;
}
/**
* Where the tracklist on screen came from. The distinction the
* user cares about is not "did a fetch fail" but "is what I am
* looking at everything, or only my own copy" — so an album with
* no MBID is `library` permanently, while one whose catalog fetch
* has not landed is `loading` and then either resolves or degrades
* to `unavailable`.
*/
private catalogScope(): CatalogScope {
if (!this.releaseGroupMBID) return 'library';
if (this.catalogReleasesLoaded) return 'catalog';
if (this.catalogPending) return 'loading';
// Not pending and no catalog data: the browse errored, came
// back empty, or the fallback timer gave up. Whatever is on
// screen is the library copy, and retrying is worth offering.
return 'unavailable';
}
/** Ask the catalog again after a failed or empty fetch. */
private retryCatalog = () => {
const mbid = this.releaseGroupMBID;
if (!mbid) return;
this.errorReleases = '';
this.loadingReleases = this.releases.length === 0;
this.catalogPending = true;
this.releasesReloaded.delete(mbid);
this.armReleasesFallback(mbid);
void this.fetchReleases(mbid);
};
/** Tracks of the version currently selected in the dropdown. */
private currentTracks(): MBTrack[] {
const entry = this.versionEntries.find(
(e) => e.key === this.selectedVersionKey,
);
return entry?.tracks ?? [];
}
private renderAlbumMeta() {
if (this.loadingInfo) {
return html`Loading\u2026`;
}
if (this.errorInfo) {
return html`
${this.errorInfo}
`;
}
if (!this.releaseGroup) return nothing;
const rg = this.releaseGroup;
const artist = rg.artistCredit || '';
const artistMbid = rg.artistMbid ?? '';
const year = extractYear(rg.firstReleaseDate);
const type = rg.primaryType || '';
const metaParts: string[] = [];
if (type) metaParts.push(type);
if (year) metaParts.push(year);
return html`
${artist
? html`
${artistLink(artist, artistMbid)}
`
: nothing}
${metaParts.length > 0
? html`
${metaParts.map(
(p, i) =>
html`${i > 0
? html`\u00B7`
: nothing}${p}`,
)}
`
: nothing}
`;
}
/* ── Version Selector (R025, R026, R027) ── */
private renderVersionSelector() {
if (this.loadingReleases) {
return html`
`;
}
if (this.errorReleases) {
return html`
`;
}
// No selector to show when there are no version entries at all,
// or when the only entry is a single real cluster (no synthetics
// were added because there's only one tracklist).
if (this.versionEntries.length <= 1) return nothing;
const aggregateEntries = this.versionEntries.filter(
(e) => e.group === 'aggregate',
);
const clusterEntries = this.versionEntries.filter(
(e) => e.group === 'cluster',
);
return html`
${this.renderVersionMeta()}
`;
}
/**
* Render a small status line under the version selector explaining
* what the user is currently viewing. Helps disambiguate the
* synthetic entries from real clusters.
*/
private renderVersionMeta() {
const current = this.currentVersion();
if (!current) return nothing;
switch (current.syntheticKind) {
case 'standard':
return html`
Showing the standard version — the most likely
canonical tracklist for this album, picked by
weighing release date, status, and how many
physical releases share this exact tracklist.
`;
case 'comprehensive':
return html`
Showing every distinct track that appears on any
version of this album combined into a single
list. Not a real release.
`;
case 'library':
return html`
Showing the version that best matches your
library copy.
`;
default:
if (current.cluster) {
const releases = current.cluster.allReleases.length;
if (releases > 1) {
return html`
${releases} physical releases share this
tracklist.
`;
}
}
return nothing;
}
}
/* ── Tracklist ── */
private renderTracklist() {
if (this.loadingReleases) {
return html`
`;
}
if (this.errorReleases) {
// Error already shown in version selector section
return nothing;
}
const current = this.currentVersion();
if (!current) {
return html`
No release data available.
`;
}
const tracks = current.tracks;
if (tracks.length === 0) {
return html`
No tracks available for this release.
`;
}
const multiDisc = this.isMultiDisc(tracks);
const discMap = this.groupByDisc(tracks);
const discNumbers = [...discMap.keys()].sort((a, b) => a - b);
return html`
${discNumbers.map((discNum) => {
const discTracks = discMap.get(discNum) ?? [];
return html`
${multiDisc
? html`
Disc ${discNum}
`
: nothing}
${discTracks.map(
(track) => html`
${track.position}
${track.title}
${formatDuration(
track.length,
)}
`,
)}
`;
})}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'explore-album-details': ExploreAlbumDetails;
}
}