fix: prevent home page double refresh and improve artist name extraction in album cards

This commit is contained in:
Julien Maille 2026-02-21 14:16:30 +01:00
parent b62f4144ac
commit acf64cbc17
2 changed files with 92 additions and 62 deletions

View file

@ -330,6 +330,17 @@ export class QobuzAPI {
}
}
// Similar/recommendation methods
async getSimilarArtists(artistId) {
// Qobuz doesn't have a direct similar artists endpoint in this simplified API
return [];
}
async getSimilarAlbums(albumId) {
// Qobuz doesn't have a direct similar albums endpoint in this simplified API
return [];
}
// Unified search - search all types at once
async search(query, options = {}) {
const offset = options.offset || 0;

143
js/ui.js
View file

@ -91,6 +91,7 @@ export class UIRenderer {
this.searchAbortController = null;
this.vibrantColorCache = new Map();
this.visualizer = null;
this.renderLock = false;
// Listen for dynamic color reset events
window.addEventListener('reset-dynamic-color', () => {
@ -575,13 +576,19 @@ export class UIRenderer {
else if (album.type === 'SINGLE') typeLabel = ' • Single';
const isCompact = cardSettings.isCompactAlbum();
let artistName = '';
if (album.artist) {
artistName = typeof album.artist === 'string' ? album.artist : album.artist.name;
} else if (album.artists?.length) {
artistName = album.artists.map((a) => a.name).join(', ');
}
return this.createBaseCardHTML({
type: 'album',
id: album.id,
href: `/album/${album.id}`,
title: `${escapeHtml(album.title)} ${explicitBadge} ${qualityBadge}`,
subtitle: `${escapeHtml(album.artist?.name ?? '')}${yearDisplay}${typeLabel}`,
subtitle: `${escapeHtml(artistName)}${yearDisplay}${typeLabel}`,
imageHTML: `<img src="${this.api.getCoverUrl(album.cover)}" alt="${escapeHtml(album.title)}" class="card-image" loading="lazy">`,
actionButtonsHTML: `
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="album" title="Add to Liked">
@ -1464,66 +1471,78 @@ export class UIRenderer {
}
async renderHomePage() {
this.showPage('home');
if (this.renderLock) return;
this.renderLock = true;
const welcomeEl = document.getElementById('home-welcome');
const contentEl = document.getElementById('home-content');
const editorsPicksSectionEmpty = document.getElementById('home-editors-picks-section-empty');
const editorsPicksSection = document.getElementById('home-editors-picks-section');
try {
this.showPage('home');
const history = await db.getHistory();
const favorites = await db.getFavorites('track');
const playlists = await db.getPlaylists(true);
const welcomeEl = document.getElementById('home-welcome');
const contentEl = document.getElementById('home-content');
const editorsPicksSectionEmpty = document.getElementById('home-editors-picks-section-empty');
const editorsPicksSection = document.getElementById('home-editors-picks-section');
const hasActivity = history.length > 0 || favorites.length > 0 || playlists.length > 0;
const history = await db.getHistory();
const favorites = await db.getFavorites('track');
const playlists = await db.getPlaylists(true);
// Handle Editor's Picks visibility based on settings
if (!homePageSettings.shouldShowEditorsPicks()) {
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = 'none';
if (editorsPicksSection) editorsPicksSection.style.display = 'none';
} else {
// Show empty-state section at top when no activity, hide the bottom one
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = hasActivity ? 'none' : '';
// Show bottom section when has activity, render it
if (editorsPicksSection) editorsPicksSection.style.display = hasActivity ? '' : 'none';
const hasActivity = history.length > 0 || favorites.length > 0 || playlists.length > 0;
// Handle Editor's Picks visibility based on settings
if (!homePageSettings.shouldShowEditorsPicks()) {
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = 'none';
if (editorsPicksSection) editorsPicksSection.style.display = 'none';
} else {
// Show empty-state section at top when no activity, hide the bottom one
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = hasActivity ? 'none' : '';
// Show bottom section when has activity, render it
if (editorsPicksSection) editorsPicksSection.style.display = hasActivity ? '' : 'none';
}
// Render editor's picks in the visible container
if (hasActivity) {
this.renderHomeEditorsPicks(false, 'home-editors-picks');
} else {
this.renderHomeEditorsPicks(false, 'home-editors-picks-empty');
}
if (!hasActivity) {
if (welcomeEl) welcomeEl.style.display = 'block';
if (contentEl) contentEl.style.display = 'none';
return;
}
if (welcomeEl) welcomeEl.style.display = 'none';
if (contentEl) contentEl.style.display = 'block';
const refreshSongsBtn = document.getElementById('refresh-songs-btn');
const refreshAlbumsBtn = document.getElementById('refresh-albums-btn');
const refreshArtistsBtn = document.getElementById('refresh-artists-btn');
const clearRecentBtn = document.getElementById('clear-recent-btn');
if (refreshSongsBtn) refreshSongsBtn.onclick = () => this.renderHomeSongs(true);
if (refreshAlbumsBtn) refreshAlbumsBtn.onclick = () => this.renderHomeAlbums(true);
if (refreshArtistsBtn) refreshArtistsBtn.onclick = () => this.renderHomeArtists(true);
if (clearRecentBtn)
clearRecentBtn.onclick = () => {
if (confirm('Clear recent activity?')) {
recentActivityManager.clear();
this.renderHomeRecent();
}
};
this.renderHomeRecent();
// Load dynamic sections in parallel with pre-fetched seeds
const seeds = await this.getSeeds();
await Promise.all([
this.renderHomeSongs(false, seeds),
this.renderHomeAlbums(false, seeds),
this.renderHomeArtists(false, seeds),
]);
} finally {
this.renderLock = false;
}
// Render editor's picks in the visible container
if (hasActivity) {
this.renderHomeEditorsPicks(false, 'home-editors-picks');
} else {
this.renderHomeEditorsPicks(false, 'home-editors-picks-empty');
}
if (!hasActivity) {
if (welcomeEl) welcomeEl.style.display = 'block';
if (contentEl) contentEl.style.display = 'none';
return;
}
if (welcomeEl) welcomeEl.style.display = 'none';
if (contentEl) contentEl.style.display = 'block';
const refreshSongsBtn = document.getElementById('refresh-songs-btn');
const refreshAlbumsBtn = document.getElementById('refresh-albums-btn');
const refreshArtistsBtn = document.getElementById('refresh-artists-btn');
const clearRecentBtn = document.getElementById('clear-recent-btn');
if (refreshSongsBtn) refreshSongsBtn.onclick = () => this.renderHomeSongs(true);
if (refreshAlbumsBtn) refreshAlbumsBtn.onclick = () => this.renderHomeAlbums(true);
if (refreshArtistsBtn) refreshArtistsBtn.onclick = () => this.renderHomeArtists(true);
if (clearRecentBtn)
clearRecentBtn.onclick = () => {
if (confirm('Clear recent activity?')) {
recentActivityManager.clear();
this.renderHomeRecent();
}
};
this.renderHomeSongs();
this.renderHomeAlbums();
this.renderHomeArtists();
this.renderHomeRecent();
}
async getSeeds() {
@ -1545,7 +1564,7 @@ export class UIRenderer {
return shuffle(seeds);
}
async renderHomeSongs(forceRefresh = false) {
async renderHomeSongs(forceRefresh = false, providedSeeds = null) {
const songsContainer = document.getElementById('home-recommended-songs');
const section = songsContainer?.closest('.content-section');
@ -1564,7 +1583,7 @@ export class UIRenderer {
}
try {
const seeds = await this.getSeeds();
const seeds = providedSeeds || (await this.getSeeds());
const trackSeeds = seeds.slice(0, 5);
const recommendedTracks = await this.api.getRecommendedTracksForPlaylist(trackSeeds, 20, {
skipCache: forceRefresh,
@ -1584,7 +1603,7 @@ export class UIRenderer {
}
}
async renderHomeAlbums(forceRefresh = false) {
async renderHomeAlbums(forceRefresh = false, providedSeeds = null) {
const albumsContainer = document.getElementById('home-recommended-albums');
const section = albumsContainer?.closest('.content-section');
@ -1603,7 +1622,7 @@ export class UIRenderer {
}
try {
const seeds = await this.getSeeds();
const seeds = providedSeeds || (await this.getSeeds());
const albumSeed = seeds.find((t) => t.album && t.album.id);
if (albumSeed) {
const similarAlbums = await this.api.getSimilarAlbums(albumSeed.album.id);
@ -1790,7 +1809,7 @@ export class UIRenderer {
}
}
async renderHomeArtists(forceRefresh = false) {
async renderHomeArtists(forceRefresh = false, providedSeeds = null) {
const artistsContainer = document.getElementById('home-recommended-artists');
const section = artistsContainer?.closest('.content-section');
@ -1809,7 +1828,7 @@ export class UIRenderer {
}
try {
const seeds = await this.getSeeds();
const seeds = providedSeeds || (await this.getSeeds());
const artistSeed = seeds.find((t) => (t.artist && t.artist.id) || (t.artists && t.artists.length > 0));
const artistId = artistSeed ? artistSeed.artist?.id || artistSeed.artists?.[0]?.id : null;