import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent } from './utils.js'; import { recentActivityManager } from './storage.js'; export class UIRenderer { constructor(api) { this.api = api; } createExplicitBadge() { return 'E'; } createTrackItemHTML(track, index, showCover = false) { const playIconSmall = ''; const trackNumberHTML = `
${showCover ? playIconSmall : index + 1}
`; const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : ''; return `
${trackNumberHTML}
${showCover ? `Track Cover` : ''}
${track.title} ${explicitBadge}
${track.artist?.name ?? 'Unknown Artist'}
${formatTime(track.duration)}
`; } createAlbumCardHTML(album) { const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : ''; return `
${album.title}

${album.title} ${explicitBadge}

Album • ${album.artist?.name ?? ''}

`; } createArtistCardHTML(artist) { return `
${artist.name}

${artist.name}

Artist

`; } createSkeletonTrack(showCover = false) { return `
${showCover ? '
' : ''}
`; } createSkeletonCard(isArtist = false) { return `
`; } createSkeletonTracks(count = 5, showCover = false) { return `
${Array(count).fill(0).map(() => this.createSkeletonTrack(showCover)).join('')}
`; } createSkeletonCards(count = 6, isArtist = false) { return `
${Array(count).fill(0).map(() => this.createSkeletonCard(isArtist)).join('')}
`; } renderListWithTracks(container, tracks, showCover) { const fragment = document.createDocumentFragment(); const tempDiv = document.createElement('div'); tempDiv.innerHTML = tracks.map((track, i) => this.createTrackItemHTML(track, i, showCover) ).join(''); while (tempDiv.firstChild) { fragment.appendChild(tempDiv.firstChild); } container.innerHTML = ''; container.appendChild(fragment); tracks.forEach(track => { const element = container.querySelector(`[data-track-id="${track.id}"]`); if (element) trackDataStore.set(element, track); }); } showPage(pageId) { document.querySelectorAll('.page').forEach(page => { page.classList.toggle('active', page.id === `page-${pageId}`); }); document.querySelectorAll('.sidebar-nav a').forEach(link => { link.classList.toggle('active', link.hash === `#${pageId}`); }); document.querySelector('.main-content').scrollTop = 0; if (pageId === 'settings') { this.renderApiSettings(); } } renderHomePage() { this.showPage('home'); const recents = recentActivityManager.getRecents(); document.getElementById('home-recent-albums').innerHTML = recents.albums.length ? recents.albums.map(album => this.createAlbumCardHTML(album)).join('') : createPlaceholder("You haven't viewed any albums yet."); document.getElementById('home-recent-artists').innerHTML = recents.artists.length ? recents.artists.map(artist => this.createArtistCardHTML(artist)).join('') : createPlaceholder("You haven't viewed any artists yet."); } async renderSearchPage(query) { this.showPage('search'); document.getElementById('search-results-title').textContent = `Search Results for "${query}"`; const tracksContainer = document.getElementById('search-tracks-container'); const artistsContainer = document.getElementById('search-artists-container'); const albumsContainer = document.getElementById('search-albums-container'); tracksContainer.innerHTML = this.createSkeletonTracks(8, false); artistsContainer.innerHTML = this.createSkeletonCards(6, true); albumsContainer.innerHTML = this.createSkeletonCards(6, false); try { const [tracksResult, artistsResult, albumsResult] = await Promise.all([ this.api.searchTracks(query), this.api.searchArtists(query), this.api.searchAlbums(query) ]); let finalTracks = tracksResult.items; let finalArtists = artistsResult.items; let finalAlbums = albumsResult.items; if (finalArtists.length === 0 && finalTracks.length > 0) { const artistMap = new Map(); finalTracks.forEach(track => { if (track.artist && !artistMap.has(track.artist.id)) { artistMap.set(track.artist.id, track.artist); } if (track.artists) { track.artists.forEach(artist => { if (!artistMap.has(artist.id)) { artistMap.set(artist.id, artist); } }); } }); finalArtists = Array.from(artistMap.values()); } if (finalAlbums.length === 0 && finalTracks.length > 0) { const albumMap = new Map(); finalTracks.forEach(track => { if (track.album && !albumMap.has(track.album.id)) { albumMap.set(track.album.id, track.album); } }); finalAlbums = Array.from(albumMap.values()); } if (finalTracks.length) { this.renderListWithTracks(tracksContainer, finalTracks, false); } else { tracksContainer.innerHTML = createPlaceholder('No tracks found.'); } artistsContainer.innerHTML = finalArtists.length ? finalArtists.map(artist => this.createArtistCardHTML(artist)).join('') : createPlaceholder('No artists found.'); albumsContainer.innerHTML = finalAlbums.length ? finalAlbums.map(album => this.createAlbumCardHTML(album)).join('') : createPlaceholder('No albums found.'); } catch (error) { console.error("Search failed:", error); const errorMsg = createPlaceholder(`Error during search. ${error.message}`); tracksContainer.innerHTML = errorMsg; artistsContainer.innerHTML = errorMsg; albumsContainer.innerHTML = errorMsg; } } async renderAlbumPage(albumId) { this.showPage('album'); const imageEl = document.getElementById('album-detail-image'); const titleEl = document.getElementById('album-detail-title'); const metaEl = document.getElementById('album-detail-meta'); const tracklistContainer = document.getElementById('album-detail-tracklist'); imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; titleEl.innerHTML = '
'; metaEl.innerHTML = '
'; tracklistContainer.innerHTML = `
# Title Duration
${this.createSkeletonTracks(10, false)} `; try { const { album, tracks } = await this.api.getAlbum(albumId); imageEl.src = this.api.getCoverUrl(album.cover, '640'); imageEl.style.backgroundColor = ''; const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : ''; titleEl.innerHTML = `${album.title} ${explicitBadge}`; metaEl.innerHTML = `By ${album.artist.name} • ${new Date(album.releaseDate).getFullYear()}`; tracklistContainer.innerHTML = `
# Title Duration
`; tracks.sort((a, b) => a.trackNumber - b.trackNumber); this.renderListWithTracks(tracklistContainer, tracks, false); recentActivityManager.addAlbum(album); } catch (error) { console.error("Failed to load album:", error); tracklistContainer.innerHTML = createPlaceholder(`Could not load album details. ${error.message}`); } } async renderArtistPage(artistId) { this.showPage('artist'); const imageEl = document.getElementById('artist-detail-image'); const nameEl = document.getElementById('artist-detail-name'); const metaEl = document.getElementById('artist-detail-meta'); const tracksContainer = document.getElementById('artist-detail-tracks'); const albumsContainer = document.getElementById('artist-detail-albums'); imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; nameEl.innerHTML = '
'; metaEl.innerHTML = '
'; tracksContainer.innerHTML = this.createSkeletonTracks(5, true); albumsContainer.innerHTML = this.createSkeletonCards(6, false); try { const artist = await this.api.getArtist(artistId); imageEl.src = this.api.getArtistPictureUrl(artist.picture, '640'); imageEl.style.backgroundColor = ''; nameEl.textContent = artist.name; metaEl.textContent = `${artist.popularity} popularity`; this.renderListWithTracks(tracksContainer, artist.tracks, true); albumsContainer.innerHTML = artist.albums.map(album => this.createAlbumCardHTML(album) ).join(''); recentActivityManager.addArtist(artist); } catch (error) { console.error("Failed to load artist:", error); tracksContainer.innerHTML = albumsContainer.innerHTML = createPlaceholder(`Could not load artist details. ${error.message}`); } } renderApiSettings() { const container = document.getElementById('api-instance-list'); const instances = this.api.settings.getInstances(); const defaultInstancesSet = new Set(this.api.settings.defaultInstances); container.innerHTML = instances.map((url, index) => `
  • ${url}
    ${!defaultInstancesSet.has(url) ? ` ` : ''}
  • `).join(''); const stats = this.api.getCacheStats(); const cacheInfo = document.getElementById('cache-info'); if (cacheInfo) { cacheInfo.textContent = `Cache: ${stats.memoryEntries}/${stats.maxSize} entries`; } } }