new: mix for artists, use new api
This commit is contained in:
parent
4ffac0ae0a
commit
57f3e42dbe
8 changed files with 271 additions and 117 deletions
186
index.html
186
index.html
|
|
@ -41,34 +41,33 @@
|
|||
<!-- Content injected dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fullscreen-cover-overlay" style="display: none;">
|
||||
<div class="fullscreen-cover-content">
|
||||
<button id="close-fullscreen-cover-btn" title="Close">×</button>
|
||||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Toggle Lyrics">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||||
<line x1="12" y1="19" x2="12" y2="22"/>
|
||||
<line x1="8" y1="22" x2="16" y2="22"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="fullscreen-main-view">
|
||||
<img id="fullscreen-cover-image" src="" alt="Album Cover">
|
||||
<div class="fullscreen-track-info">
|
||||
<h2 id="fullscreen-track-title"></h2>
|
||||
<h3 id="fullscreen-track-artist"></h3>
|
||||
<div id="fullscreen-next-track" style="display: none;">
|
||||
<span class="label">Up Next: </span>
|
||||
<span class="value"></span>
|
||||
|
||||
<div id="fullscreen-cover-overlay" style="display: none;">
|
||||
<div class="fullscreen-cover-content">
|
||||
<button id="close-fullscreen-cover-btn" title="Close">×</button>
|
||||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Toggle Lyrics">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||||
<line x1="12" y1="19" x2="12" y2="22"/>
|
||||
<line x1="8" y1="22" x2="16" y2="22"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="fullscreen-main-view">
|
||||
<img id="fullscreen-cover-image" src="" alt="Album Cover">
|
||||
<div class="fullscreen-track-info">
|
||||
<h2 id="fullscreen-track-title"></h2>
|
||||
<h3 id="fullscreen-track-artist"></h3>
|
||||
<div id="fullscreen-next-track" style="display: none;">
|
||||
<span class="label">Up Next: </span>
|
||||
<span class="value"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullscreen-lyrics-container" id="fullscreen-lyrics-container" style="display: none;">
|
||||
<div class="fullscreen-lyrics-container" id="fullscreen-lyrics-container" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="playlist-modal" class="modal" style="display: none;">
|
||||
<div class="modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;">
|
||||
|
|
@ -89,11 +88,11 @@
|
|||
<aside class="sidebar">
|
||||
<div>
|
||||
<div class="sidebar-logo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="14.75 14.75 70.5 70.5" >
|
||||
<g fill="currentColor" >
|
||||
<path d="M38.25 14.75H85.25V61.75H61.75V38.25H38.25ZM14.75 38.25H38.25V61.75H61.75V85.25H14.75Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="14.75 14.75 70.5 70.5" >
|
||||
<g fill="currentColor" >
|
||||
<path d="M38.25 14.75H85.25V61.75H61.75V38.25H38.25ZM14.75 38.25H38.25V61.75H61.75V85.25H14.75Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span>Monochrome</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
|
@ -199,29 +198,32 @@
|
|||
</div>
|
||||
|
||||
<div id="page-library" class="page">
|
||||
<h2 class="section-title">Your Library</h2>
|
||||
<section class="content-section">
|
||||
<h2 class="section-title">My Playlists <button id="create-playlist-btn" class="btn-secondary">Create Playlist</button></h2>
|
||||
<div class="card-grid" id="my-playlists-container"></div>
|
||||
</section>
|
||||
<div class="search-tabs">
|
||||
<button class="search-tab active" data-tab="tracks">Liked Tracks</button>
|
||||
<button class="search-tab" data-tab="albums">Albums</button>
|
||||
<button class="search-tab" data-tab="artists">Artists</button>
|
||||
<button class="search-tab" data-tab="playlists">Playlists</button>
|
||||
</div>
|
||||
<div class="search-tab-content active" id="library-tab-tracks">
|
||||
<div class="track-list" id="library-tracks-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-albums">
|
||||
<div class="card-grid" id="library-albums-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-artists">
|
||||
<div class="card-grid" id="library-artists-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-playlists">
|
||||
<div class="card-grid" id="library-playlists-container"></div>
|
||||
</div>
|
||||
|
||||
<section class="content-section">
|
||||
<h2 class="section-title">Your Favorites</h2>
|
||||
<div class="search-tabs">
|
||||
<button class="search-tab active" data-tab="tracks">Liked Tracks</button>
|
||||
<button class="search-tab" data-tab="albums">Albums</button>
|
||||
<button class="search-tab" data-tab="artists">Artists</button>
|
||||
<button class="search-tab" data-tab="playlists">Playlists</button>
|
||||
</div>
|
||||
<div class="search-tab-content active" id="library-tab-tracks">
|
||||
<div class="track-list" id="library-tracks-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-albums">
|
||||
<div class="card-grid" id="library-albums-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-artists">
|
||||
<div class="card-grid" id="library-artists-container"></div>
|
||||
</div>
|
||||
<div class="search-tab-content" id="library-tab-playlists">
|
||||
<div class="card-grid" id="library-playlists-container"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="page-recent" class="page">
|
||||
|
|
@ -240,10 +242,16 @@
|
|||
<button id="play-album-btn" class="btn-primary">
|
||||
<span>Play Album</span>
|
||||
</button>
|
||||
<button id="download-album-btn" class="btn-primary">
|
||||
<span>Download Album</span>
|
||||
</button>
|
||||
<button id="like-album-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="album" title="Save to Library">
|
||||
<button id="album-mix-btn" class="btn-primary" style="display: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
|
||||
</svg>
|
||||
<span>Mix</span>
|
||||
</button>
|
||||
<button id="download-album-btn" class="btn-primary">
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button id="like-album-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="album" title="Save to Favorites">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="heart-icon"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -252,29 +260,46 @@
|
|||
<div class="track-list" id="album-detail-tracklist"></div>
|
||||
</div>
|
||||
|
||||
<section id="page-playlist" class="page">
|
||||
<div class="detail-header">
|
||||
<img id="playlist-detail-image" class="detail-cover" alt="Playlist Cover">
|
||||
<div class="detail-header-info">
|
||||
<h1 class="title" id="playlist-detail-title"></h1>
|
||||
<p class="meta" id="playlist-detail-meta"></p>
|
||||
<p id="playlist-detail-description" class="detail-description"></p>
|
||||
<div class="detail-actions">
|
||||
<button id="play-playlist-btn" class="btn-primary">
|
||||
<span>Play</span>
|
||||
</button>
|
||||
<!-- Like button not typically for own playlists, but good for "followed" ones if we support that. For now, maybe skip or add "Edit" if it's user playlist -->
|
||||
<button id="download-playlist-btn" class="btn-primary">
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button id="like-playlist-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="playlist" title="Save to Library">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="heart-icon"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playlist-detail-tracklist" class="track-list"></div>
|
||||
</section>
|
||||
<section id="page-playlist" class="page">
|
||||
<div class="detail-header">
|
||||
<img id="playlist-detail-image" class="detail-cover" alt="Playlist Cover">
|
||||
<div class="detail-header-info">
|
||||
<h1 class="title" id="playlist-detail-title"></h1>
|
||||
<p class="meta" id="playlist-detail-meta"></p>
|
||||
<p id="playlist-detail-description" class="detail-description"></p>
|
||||
<div class="detail-actions">
|
||||
<button id="play-playlist-btn" class="btn-primary">
|
||||
<span>Play</span>
|
||||
</button>
|
||||
<!-- Like button not typically for own playlists, but good for "followed" ones if we support that. For now, maybe skip or add "Edit" if it's user playlist -->
|
||||
<button id="download-playlist-btn" class="btn-primary">
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button id="like-playlist-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="playlist" title="Save to Favorites">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="heart-icon"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playlist-detail-tracklist" class="track-list"></div>
|
||||
</section>
|
||||
|
||||
<section id="page-mix" class="page">
|
||||
<div class="detail-header">
|
||||
<img id="mix-detail-image" class="detail-cover" alt="Mix Cover">
|
||||
<div class="detail-header-info">
|
||||
<h1 class="title" id="mix-detail-title"></h1>
|
||||
<p class="meta" id="mix-detail-meta"></p>
|
||||
<p id="mix-detail-description" class="detail-description"></p>
|
||||
<div class="detail-actions">
|
||||
<button id="play-mix-btn" class="btn-primary">
|
||||
<span>Play</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mix-detail-tracklist" class="track-list"></div>
|
||||
</section>
|
||||
|
||||
<div id="page-artist" class="page">
|
||||
<header class="detail-header">
|
||||
|
|
@ -289,10 +314,16 @@
|
|||
</svg>
|
||||
<span>Artist Radio</span>
|
||||
</button>
|
||||
<button id="artist-mix-btn" class="btn-primary" style="display: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
|
||||
</svg>
|
||||
<span>Mix</span>
|
||||
</button>
|
||||
<button id="download-discography-btn" class="btn-primary">
|
||||
<span>Download Discography</span>
|
||||
</button>
|
||||
<button id="like-artist-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="artist" title="Save to Library">
|
||||
<button id="like-artist-btn" class="btn-secondary like-btn" data-action="toggle-like" data-type="artist" title="Save to Favorites">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="heart-icon"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -652,7 +683,7 @@
|
|||
</div>
|
||||
<div class="volume-controls">
|
||||
<div class="player-actions-row">
|
||||
<button id="now-playing-like-btn" class="like-btn" data-action="toggle-like" title="Save to Library" style="display: none;">
|
||||
<button id="now-playing-like-btn" class="like-btn" data-action="toggle-like" title="Save to Favorites" style="display: none;">
|
||||
</button>
|
||||
<button id="toggle-lyrics-btn" title="Lyrics">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></svg>
|
||||
|
|
@ -696,7 +727,6 @@ if ('serviceWorker' in navigator) {
|
|||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"api": [
|
||||
"https://tidal-api.binimum.org"
|
||||
"https://monochrome-api.samidy.com"
|
||||
],
|
||||
"streaming": [
|
||||
"https://triton.squid.wtf",
|
||||
|
|
|
|||
34
js/api.js
34
js/api.js
|
|
@ -480,6 +480,40 @@ export class LosslessAPI {
|
|||
return result;
|
||||
}
|
||||
|
||||
async getMix(id) {
|
||||
const cached = await this.cache.get('mix', id);
|
||||
if (cached) return cached;
|
||||
|
||||
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api' });
|
||||
const data = await response.json();
|
||||
|
||||
let mix = null;
|
||||
let tracks = [];
|
||||
|
||||
// Mix response structure might vary, handle likely cases
|
||||
const items = data.items || data.tracks || (Array.isArray(data) ? data : []);
|
||||
|
||||
// If data has mix info, use it. Otherwise, fabricate one or look for it.
|
||||
if (data.mix) {
|
||||
mix = data.mix;
|
||||
} else if (!Array.isArray(data) && data.id) {
|
||||
mix = data;
|
||||
}
|
||||
|
||||
if (!mix) {
|
||||
// Basic placeholder if mix metadata isn't explicitly separated
|
||||
mix = { id, title: 'Mix', description: 'Generated Mix' };
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
tracks = items.map(i => this.prepareTrack(i.item || i));
|
||||
}
|
||||
|
||||
const result = { mix, tracks };
|
||||
await this.cache.set('mix', id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getArtist(artistId) {
|
||||
const cached = await this.cache.get('artist', artistId);
|
||||
if (cached) return cached;
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ export async function handleTrackAction(action, item, player, api, lyricsManager
|
|||
}
|
||||
}
|
||||
btn.classList.toggle('active', added);
|
||||
btn.title = added ? 'Remove from Library' : 'Add to Library';
|
||||
btn.title = added ? 'Remove from Favorites' : 'Add to Favorites';
|
||||
});
|
||||
|
||||
// Handle Library Page Update
|
||||
|
|
@ -674,7 +674,7 @@ async function updateContextMenuLikeState(menu, track) {
|
|||
const likeItem = menu.querySelector('[data-action="toggle-like"]');
|
||||
if (likeItem) {
|
||||
const isLiked = await db.isFavorite('track', track.id);
|
||||
likeItem.textContent = isLiked ? 'Remove from Library' : 'Add to Library';
|
||||
likeItem.textContent = isLiked ? 'Remove from Favorites' : 'Add to Favorites';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ export function createRouter(ui) {
|
|||
case 'userplaylist':
|
||||
ui.renderPlaylistPage(param);
|
||||
break;
|
||||
case 'mix':
|
||||
ui.renderMixPage(param);
|
||||
break;
|
||||
case 'library':
|
||||
ui.renderLibraryPage();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export const apiSettings = {
|
|||
} catch (error) {
|
||||
console.error('Failed to load instances from GitHub:', error);
|
||||
this.defaultInstances = {
|
||||
api: ['https://tidal-api.binimum.org'],
|
||||
api: ["https://monochrome-api.samidy.com"],
|
||||
streaming: [
|
||||
"https://triton.squid.wtf",
|
||||
"https://wolf.qqdl.site",
|
||||
|
|
|
|||
100
js/ui.js
100
js/ui.js
|
|
@ -797,6 +797,8 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
if (playBtn) playBtn.innerHTML = `${SVG_PLAY}<span>Play Album</span>`;
|
||||
const dlBtn = document.getElementById('download-album-btn');
|
||||
if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}<span>Download Album</span>`;
|
||||
const mixBtn = document.getElementById('album-mix-btn');
|
||||
if (mixBtn) mixBtn.style.display = 'none';
|
||||
|
||||
imageEl.src = '';
|
||||
imageEl.style.backgroundColor = 'var(--muted)';
|
||||
|
|
@ -902,6 +904,13 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
|
||||
const artistData = await this.api.getArtist(album.artist.id);
|
||||
|
||||
// Add Mix/Radio Button to header
|
||||
const mixBtn = document.getElementById('album-mix-btn');
|
||||
if (mixBtn && artistData.mixes && artistData.mixes.ARTIST_MIX) {
|
||||
mixBtn.style.display = 'flex';
|
||||
mixBtn.onclick = () => window.location.hash = `#mix/${artistData.mixes.ARTIST_MIX}`;
|
||||
}
|
||||
|
||||
// Remove placeholder
|
||||
placeholderSection.remove();
|
||||
|
||||
|
|
@ -1105,6 +1114,86 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
}
|
||||
}
|
||||
|
||||
async renderMixPage(mixId) {
|
||||
this.showPage('mix');
|
||||
const imageEl = document.getElementById('mix-detail-image');
|
||||
const titleEl = document.getElementById('mix-detail-title');
|
||||
const metaEl = document.getElementById('mix-detail-meta');
|
||||
const descEl = document.getElementById('mix-detail-description');
|
||||
const tracklistContainer = document.getElementById('mix-detail-tracklist');
|
||||
const playBtn = document.getElementById('play-mix-btn');
|
||||
if (playBtn) playBtn.innerHTML = `${SVG_PLAY}<span>Play</span>`;
|
||||
|
||||
// Skeleton loading
|
||||
imageEl.src = '';
|
||||
imageEl.style.backgroundColor = 'var(--muted)';
|
||||
titleEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
|
||||
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
|
||||
descEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 100%;"></div>';
|
||||
tracklistContainer.innerHTML = `
|
||||
<div class="track-list-header">
|
||||
<span style="width: 40px; text-align: center;">#</span>
|
||||
<span>Title</span>
|
||||
<span class="duration-header">Duration</span>
|
||||
</div>
|
||||
${this.createSkeletonTracks(10, true)}
|
||||
`;
|
||||
|
||||
try {
|
||||
const { mix, tracks } = await this.api.getMix(mixId);
|
||||
|
||||
// Mixes usually have covers from Tidal resources, similar to playlists
|
||||
const imageId = mix.images?.medium?.source || mix.image || mix.id;
|
||||
// Fallback for cover: if mix.id matches a pattern or we can just try generic mix cover
|
||||
// Often mix ID isn't directly an image ID.
|
||||
// If API returns explicit image URL/ID use it.
|
||||
// For now assume standard playlist-like cover or placeholder.
|
||||
if (imageId && imageId !== mix.id) {
|
||||
imageEl.src = this.api.getCoverUrl(imageId, '1080');
|
||||
} else {
|
||||
// Try to get cover from first track album
|
||||
if (tracks.length > 0 && tracks[0].album?.cover) {
|
||||
imageEl.src = this.api.getCoverUrl(tracks[0].album.cover, '1080');
|
||||
} else {
|
||||
imageEl.src = 'assets/appicon.png';
|
||||
}
|
||||
}
|
||||
|
||||
imageEl.style.backgroundColor = '';
|
||||
|
||||
const firstTrackArtist = tracks.length > 0 ? tracks[0].artist?.name : '';
|
||||
const displayTitle = firstTrackArtist ? `${firstTrackArtist} Mix` : 'Mix';
|
||||
|
||||
titleEl.textContent = displayTitle;
|
||||
this.adjustTitleFontSize(titleEl, displayTitle);
|
||||
|
||||
const totalDuration = calculateTotalDuration(tracks);
|
||||
|
||||
metaEl.textContent = `${tracks.length} tracks • ${formatDuration(totalDuration)}`;
|
||||
descEl.textContent = mix.subTitle || mix.description || '';
|
||||
|
||||
tracklistContainer.innerHTML = `
|
||||
<div class="track-list-header">
|
||||
<span style="width: 40px; text-align: center;">#</span>
|
||||
<span>Title</span>
|
||||
<span class="duration-header">Duration</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.renderListWithTracks(tracklistContainer, tracks, true);
|
||||
|
||||
// Set play button action
|
||||
playBtn.onclick = () => {
|
||||
player.playTracks(tracks, 0);
|
||||
};
|
||||
|
||||
document.title = `${displayTitle} - Monochrome`;
|
||||
} catch (error) {
|
||||
console.error("Failed to load mix:", error);
|
||||
tracklistContainer.innerHTML = createPlaceholder(`Could not load mix details. ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async renderArtistPage(artistId) {
|
||||
this.showPage('artist');
|
||||
|
||||
|
|
@ -1134,6 +1223,17 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
try {
|
||||
const artist = await this.api.getArtist(artistId);
|
||||
|
||||
// Handle Artist Mix Button
|
||||
const mixBtn = document.getElementById('artist-mix-btn');
|
||||
if (mixBtn) {
|
||||
if (artist.mixes && artist.mixes.ARTIST_MIX) {
|
||||
mixBtn.style.display = 'flex';
|
||||
mixBtn.onclick = () => window.location.hash = `#mix/${artist.mixes.ARTIST_MIX}`;
|
||||
} else {
|
||||
mixBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Similar Artists
|
||||
if (similarContainer && similarSection) {
|
||||
this.api.getSimilarArtists(artistId).then(similar => {
|
||||
|
|
|
|||
57
styles.css
57
styles.css
|
|
@ -3061,7 +3061,8 @@ input:checked + .slider::before {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
#page-playlist .detail-header {
|
||||
#page-playlist .detail-header,
|
||||
#page-mix .detail-header {
|
||||
display: flex;
|
||||
align-items: flex-top;
|
||||
gap: var(--spacing-xl);
|
||||
|
|
@ -3069,7 +3070,8 @@ input:checked + .slider::before {
|
|||
padding-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
#page-playlist .detail-cover {
|
||||
#page-playlist .detail-cover,
|
||||
#page-mix .detail-cover {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
flex-shrink: 0;
|
||||
|
|
@ -3080,12 +3082,14 @@ input:checked + .slider::before {
|
|||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#playlist-detail-image.loading {
|
||||
#playlist-detail-image.loading,
|
||||
#mix-detail-image.loading {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
#playlist-detail-title {
|
||||
#playlist-detail-title,
|
||||
#mix-detail-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
|
|
@ -3096,7 +3100,8 @@ input:checked + .slider::before {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#playlist-detail-meta {
|
||||
#playlist-detail-meta,
|
||||
#mix-detail-meta {
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 0.75rem;
|
||||
|
|
@ -3106,7 +3111,8 @@ input:checked + .slider::before {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#playlist-detail-description {
|
||||
#playlist-detail-description,
|
||||
#mix-detail-description {
|
||||
color: var(--foreground);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
|
|
@ -3114,7 +3120,8 @@ input:checked + .slider::before {
|
|||
max-width: 600px;
|
||||
}
|
||||
|
||||
#page-playlist .detail-actions {
|
||||
#page-playlist .detail-actions,
|
||||
#page-mix .detail-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
|
|
@ -3178,7 +3185,8 @@ input:checked + .slider::before {
|
|||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#page-playlist .detail-header {
|
||||
#page-playlist .detail-header,
|
||||
#page-mix .detail-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-lg);
|
||||
|
|
@ -3186,42 +3194,21 @@ input:checked + .slider::before {
|
|||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
#playlist-detail-image {
|
||||
#playlist-detail-image,
|
||||
#mix-detail-image {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#playlist-detail-title {
|
||||
#playlist-detail-title,
|
||||
#mix-detail-title {
|
||||
font-size: 2rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#playlist-detail-meta {
|
||||
#playlist-detail-meta,
|
||||
#mix-detail-meta {
|
||||
font-size: 0.85rem;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
#playlist-detail-description {
|
||||
font-size: 0.85rem;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
#page-playlist .detail-actions {
|
||||
width: auto;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#play-playlist-btn,
|
||||
#download-playlist-btn {
|
||||
width: auto;
|
||||
padding: 0.875rem;
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
#play-playlist-btn span,
|
||||
#download-playlist-btn span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue