userplaylists in editor picks + refresh button for playlist suggestions
This commit is contained in:
parent
562e8aa6b2
commit
49c054e64a
4 changed files with 85 additions and 6 deletions
27
index.html
27
index.html
|
|
@ -2477,8 +2477,31 @@
|
||||||
id="playlist-section-recommended"
|
id="playlist-section-recommended"
|
||||||
style="display: none; margin-top: 3rem"
|
style="display: none; margin-top: 3rem"
|
||||||
>
|
>
|
||||||
<h2 class="section-title">Recommended Songs</h2>
|
<div class="section-header-row">
|
||||||
<p style="color: grey; margin-bottom: 15px">Suggested Songs From Your Playlist</p>
|
<div>
|
||||||
|
<h2 class="section-title">Recommended Songs</h2>
|
||||||
|
<p style="color: grey; margin-bottom: 15px">Suggested Songs From Your Playlist</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="refresh-recommended-songs-btn"
|
||||||
|
class="btn-secondary"
|
||||||
|
title="Refresh Recommendations"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||||
|
<path d="M21 3v5h-5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="track-list" id="playlist-detail-recommended"></div>
|
<div class="track-list" id="playlist-detail-recommended"></div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
46
js/ui.js
46
js/ui.js
|
|
@ -492,7 +492,7 @@ export class UIRenderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createUserPlaylistCardHTML(playlist) {
|
createUserPlaylistCardHTML(playlist, customSubtitle = null) {
|
||||||
let imageHTML = '';
|
let imageHTML = '';
|
||||||
if (playlist.cover) {
|
if (playlist.cover) {
|
||||||
imageHTML = `<img src="${playlist.cover}" alt="${playlist.name}" class="card-image" loading="lazy">`;
|
imageHTML = `<img src="${playlist.cover}" alt="${playlist.name}" class="card-image" loading="lazy">`;
|
||||||
|
|
@ -529,6 +529,8 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCompact = cardSettings.isCompactAlbum();
|
const isCompact = cardSettings.isCompactAlbum();
|
||||||
|
const subtitle =
|
||||||
|
customSubtitle || `${playlist.tracks ? playlist.tracks.length : playlist.numberOfTracks || 0} tracks`;
|
||||||
|
|
||||||
return this.createBaseCardHTML({
|
return this.createBaseCardHTML({
|
||||||
type: 'user-playlist', // Note: data-type logic in base might need adjustment if it uses this for buttons.
|
type: 'user-playlist', // Note: data-type logic in base might need adjustment if it uses this for buttons.
|
||||||
|
|
@ -536,7 +538,7 @@ export class UIRenderer {
|
||||||
id: playlist.id,
|
id: playlist.id,
|
||||||
href: `/userplaylist/${playlist.id}`,
|
href: `/userplaylist/${playlist.id}`,
|
||||||
title: escapeHtml(playlist.name),
|
title: escapeHtml(playlist.name),
|
||||||
subtitle: `${playlist.tracks ? playlist.tracks.length : playlist.numberOfTracks || 0} tracks`,
|
subtitle,
|
||||||
imageHTML: imageHTML,
|
imageHTML: imageHTML,
|
||||||
actionButtonsHTML: `
|
actionButtonsHTML: `
|
||||||
<button class="edit-playlist-btn" data-action="edit-playlist" title="Edit Playlist">
|
<button class="edit-playlist-btn" data-action="edit-playlist" title="Edit Playlist">
|
||||||
|
|
@ -1782,6 +1784,26 @@ export class UIRenderer {
|
||||||
itemsToStore.push({ el: null, data: track, type: 'track' });
|
itemsToStore.push({ el: null, data: track, type: 'track' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (item.type === 'user-playlist') {
|
||||||
|
if (item.id && item.name) {
|
||||||
|
const playlist = {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
cover: item.cover,
|
||||||
|
tracks: item.tracks || [],
|
||||||
|
numberOfTracks: item.numberOfTracks || (item.tracks ? item.tracks.length : 0),
|
||||||
|
};
|
||||||
|
const subtitle = item.username ? `by ${item.username}` : null;
|
||||||
|
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
||||||
|
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
||||||
|
} else {
|
||||||
|
const playlist = await syncManager.getPublicPlaylist(item.id);
|
||||||
|
if (playlist) {
|
||||||
|
const subtitle = item.username ? `by ${item.username}` : null;
|
||||||
|
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
||||||
|
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`Failed to load ${item.type} ${item.id}:`, e);
|
console.warn(`Failed to load ${item.type} ${item.id}:`, e);
|
||||||
|
|
@ -2404,7 +2426,7 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadRecommendedSongsForPlaylist(tracks) {
|
async loadRecommendedSongsForPlaylist(tracks, forceRefresh = false) {
|
||||||
const recommendedSection = document.getElementById('playlist-section-recommended');
|
const recommendedSection = document.getElementById('playlist-section-recommended');
|
||||||
const recommendedContainer = document.getElementById('playlist-detail-recommended');
|
const recommendedContainer = document.getElementById('playlist-detail-recommended');
|
||||||
|
|
||||||
|
|
@ -2413,8 +2435,12 @@ export class UIRenderer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceRefresh) {
|
||||||
|
recommendedContainer.innerHTML = this.createSkeletonTracks(5, true);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let recommendedTracks = await this.api.getRecommendedTracksForPlaylist(tracks, 20);
|
let recommendedTracks = await this.api.getRecommendedTracksForPlaylist(tracks, 20, forceRefresh);
|
||||||
|
|
||||||
// Filter out blocked tracks
|
// Filter out blocked tracks
|
||||||
const { contentBlockingSettings } = await import('./storage.js');
|
const { contentBlockingSettings } = await import('./storage.js');
|
||||||
|
|
@ -2680,6 +2706,18 @@ export class UIRenderer {
|
||||||
// Load recommended songs thingy
|
// Load recommended songs thingy
|
||||||
if (ownedPlaylist) {
|
if (ownedPlaylist) {
|
||||||
this.loadRecommendedSongsForPlaylist(tracks);
|
this.loadRecommendedSongsForPlaylist(tracks);
|
||||||
|
|
||||||
|
const refreshBtn = document.getElementById('refresh-recommended-songs-btn');
|
||||||
|
if (refreshBtn) {
|
||||||
|
refreshBtn.onclick = async () => {
|
||||||
|
const icon = refreshBtn.querySelector('svg');
|
||||||
|
if (icon) icon.style.animation = 'spin 1s linear infinite';
|
||||||
|
refreshBtn.disabled = true;
|
||||||
|
await this.loadRecommendedSongsForPlaylist(tracks, true);
|
||||||
|
if (icon) icon.style.animation = '';
|
||||||
|
refreshBtn.disabled = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render Actions (Sort, Shuffle, Edit, Delete, Share)
|
// Render Actions (Sort, Shuffle, Edit, Delete, Share)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"type": "user-playlist",
|
||||||
|
"id": "e64ed040-57ab-4583-b047-8fb590b04750",
|
||||||
|
"name": "2edi",
|
||||||
|
"cover": "https://i.imgur.gg/jgYh3K6-favicon_(2).png",
|
||||||
|
"username": "edideaur"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "album",
|
"type": "album",
|
||||||
"id": 89313048,
|
"id": 89313048,
|
||||||
|
|
|
||||||
11
styles.css
11
styles.css
|
|
@ -1627,6 +1627,17 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header-row .section-title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.search-tabs {
|
.search-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-xs);
|
gap: var(--spacing-xs);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue