feat: artist/page endpoint, multi-disc fix, playlist ownership, UX improvements

- Switch artist view to artist/page API (proper sections: Albums, Singles & EPs,
  Live, Compilations; version in titles like "Deluxe")
- Fix artist sections categorization using releases[].type from artist/page
- Add getUser() backend call; fetch on session restore when userId=0 to fix
  playlist ownership (Remove from playlist / Delete playlist were missing)
- Fix multi-disc double-click / Play Now queue start index (disc headers were
  counted in row index but excluded from currentTracksJson)
- Hide redundant Album column when viewing an album
- Make artist name in context header clickable (navigates to artist page)
- Fix gap between title and artist name in context header (addStretch)
- Fix queue panel track titles to include version field
- Fix album list to show version in title column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 23:09:04 +01:00
parent 69fb818c38
commit 872fdecdce
16 changed files with 272 additions and 58 deletions

View File

@@ -6,6 +6,7 @@
#include <QScrollArea>
#include <QFont>
#include <QJsonValue>
#include <QRegularExpression>
// ---------------------------------------------------------------------------
// ArtistSection
@@ -104,50 +105,74 @@ ArtistView::ArtistView(QWidget *parent)
sectLayout->setContentsMargins(0, 0, 0, 0);
sectLayout->setSpacing(8);
m_secAlbums = new ArtistSection(tr("Albums"), content);
m_secEps = new ArtistSection(tr("EPs & Singles"), content);
m_secOther = new ArtistSection(tr("Other"), content);
m_secAlbums = new ArtistSection(tr("Albums"), content);
m_secEps = new ArtistSection(tr("Singles & EPs"), content);
m_secLive = new ArtistSection(tr("Live"), content);
m_secCompilations = new ArtistSection(tr("Compilations"), content);
m_secOther = new ArtistSection(tr("Other"), content);
sectLayout->addWidget(m_secAlbums);
sectLayout->addWidget(m_secEps);
sectLayout->addWidget(m_secLive);
sectLayout->addWidget(m_secCompilations);
sectLayout->addWidget(m_secOther);
sectLayout->addStretch();
scroll->setWidget(content);
outerLayout->addWidget(scroll, 1);
connect(m_secAlbums, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secEps, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secAlbums, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secEps, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secLive, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secCompilations, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
}
void ArtistView::setArtist(const QJsonObject &artist)
{
m_nameLabel->setText(artist["name"].toString());
// artist/page: name is {"display": "..."}
m_nameLabel->setText(artist["name"].toObject()["display"].toString());
const QString summary = artist["biography"].toObject()["summary"].toString();
m_bioLabel->setText(summary);
m_bioLabel->setVisible(!summary.isEmpty());
// biography.content is HTML — strip tags for the summary label
const QString bioHtml = artist["biography"].toObject()["content"].toString();
if (!bioHtml.isEmpty()) {
// Remove HTML tags for plain-text display
QString plain = bioHtml;
plain.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
plain = plain.trimmed();
m_bioLabel->setText(plain);
m_bioLabel->setVisible(true);
} else {
m_bioLabel->setVisible(false);
}
const QJsonArray allAlbums = artist["albums"].toObject()["items"].toArray();
QJsonArray albums, eps, other;
for (const QJsonValue &v : allAlbums) {
const QJsonObject a = v.toObject();
const QString rt = a["release_type"].toString();
if (rt == QStringLiteral("album"))
albums.append(a);
else if (rt == QStringLiteral("epSingle"))
eps.append(a);
else
other.append(a);
// releases is an array of {type, has_more, items[]}
// types we care about: "album", "epSingle", "live"
const QJsonArray releases = artist["releases"].toArray();
QJsonArray albums, eps, live, compilations;
for (const QJsonValue &rv : releases) {
const QJsonObject rg = rv.toObject();
const QString type = rg["type"].toString();
const QJsonArray items = rg["items"].toArray();
if (type == QStringLiteral("album"))
albums = items;
else if (type == QStringLiteral("epSingle"))
eps = items;
else if (type == QStringLiteral("live"))
live = items;
else if (type == QStringLiteral("compilation"))
compilations = items;
}
m_secAlbums->setAlbums(albums);
m_secEps->setAlbums(eps);
m_secOther->setAlbums(other);
m_secLive->setAlbums(live);
m_secCompilations->setAlbums(compilations);
m_secOther->setAlbums({});
m_secAlbums->setVisible(!m_secAlbums->isEmpty());
m_secEps->setVisible(!m_secEps->isEmpty());
m_secOther->setVisible(!m_secOther->isEmpty());
m_secLive->setVisible(!m_secLive->isEmpty());
m_secCompilations->setVisible(!m_secCompilations->isEmpty());
m_secOther->setVisible(false);
}