display all listenbrainz playlsts in unique categories.
This commit is contained in:
parent
681d4b3cd2
commit
6750c20dc4
2 changed files with 155 additions and 19 deletions
|
|
@ -285,21 +285,21 @@ class ListenBrainzManager:
|
||||||
logger.info(f"✅ Fetched {covers_found}/{len(track_data_list)} cover art URLs")
|
logger.info(f"✅ Fetched {covers_found}/{len(track_data_list)} cover art URLs")
|
||||||
|
|
||||||
def _cleanup_old_playlists(self):
|
def _cleanup_old_playlists(self):
|
||||||
"""Remove old playlists, keeping only the 4 most recent per type"""
|
"""Remove old playlists, keeping only the 25 most recent per type"""
|
||||||
conn = self._get_db_connection()
|
conn = self._get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# For each playlist type, keep only the 4 most recent
|
# For each playlist type, keep only the 25 most recent
|
||||||
playlist_types = ['created_for', 'user', 'collaborative']
|
playlist_types = ['created_for', 'user', 'collaborative']
|
||||||
|
|
||||||
for playlist_type in playlist_types:
|
for playlist_type in playlist_types:
|
||||||
try:
|
try:
|
||||||
# Get IDs of playlists to delete (all except 4 most recent)
|
# Get IDs of playlists to delete (all except 25 most recent)
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT id FROM listenbrainz_playlists
|
SELECT id FROM listenbrainz_playlists
|
||||||
WHERE playlist_type = ?
|
WHERE playlist_type = ?
|
||||||
ORDER BY last_updated DESC
|
ORDER BY last_updated DESC
|
||||||
LIMIT -1 OFFSET 4
|
LIMIT -1 OFFSET 25
|
||||||
""", (playlist_type,))
|
""", (playlist_type,))
|
||||||
|
|
||||||
old_playlist_ids = [row[0] for row in cursor.fetchall()]
|
old_playlist_ids = [row[0] for row in cursor.fetchall()]
|
||||||
|
|
@ -330,7 +330,7 @@ class ListenBrainzManager:
|
||||||
return count > 0
|
return count > 0
|
||||||
|
|
||||||
def get_cached_playlists(self, playlist_type: str) -> List[Dict]:
|
def get_cached_playlists(self, playlist_type: str) -> List[Dict]:
|
||||||
"""Get cached playlists of a specific type from database (limited to 4 most recent)"""
|
"""Get cached playlists of a specific type from database (up to 25 most recent)"""
|
||||||
conn = self._get_db_connection()
|
conn = self._get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
|
@ -339,7 +339,7 @@ class ListenBrainzManager:
|
||||||
FROM listenbrainz_playlists
|
FROM listenbrainz_playlists
|
||||||
WHERE playlist_type = ?
|
WHERE playlist_type = ?
|
||||||
ORDER BY last_updated DESC
|
ORDER BY last_updated DESC
|
||||||
LIMIT 4
|
LIMIT 25
|
||||||
""", (playlist_type,))
|
""", (playlist_type,))
|
||||||
|
|
||||||
playlists = []
|
playlists = []
|
||||||
|
|
|
||||||
|
|
@ -31502,6 +31502,7 @@ async function openDownloadModalForGenre(genreName) {
|
||||||
let listenbrainzPlaylistsCache = {}; // Store playlists by type
|
let listenbrainzPlaylistsCache = {}; // Store playlists by type
|
||||||
let listenbrainzTracksCache = {}; // Store tracks for each playlist
|
let listenbrainzTracksCache = {}; // Store tracks for each playlist
|
||||||
let activeListenBrainzTab = 'recommendations'; // Track active tab
|
let activeListenBrainzTab = 'recommendations'; // Track active tab
|
||||||
|
let activeListenBrainzSubTab = null; // Track active sub-tab within recommendations
|
||||||
|
|
||||||
async function initializeListenBrainzTabs() {
|
async function initializeListenBrainzTabs() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -31622,17 +31623,47 @@ function switchListenBrainzTab(tabId) {
|
||||||
loadListenBrainzTabContent(tabId);
|
loadListenBrainzTabContent(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadListenBrainzTabContent(tabId) {
|
function groupListenBrainzPlaylists(playlists) {
|
||||||
const container = document.getElementById('listenbrainz-tab-content');
|
const groups = {};
|
||||||
if (!container) return;
|
const groupOrder = [];
|
||||||
|
|
||||||
const playlists = listenbrainzPlaylistsCache[tabId] || [];
|
playlists.forEach(playlist => {
|
||||||
if (playlists.length === 0) {
|
const playlistData = playlist.playlist || playlist;
|
||||||
container.innerHTML = '<div class="discover-empty"><p>No playlists in this category</p></div>';
|
const title = (playlistData.title || '').toLowerCase();
|
||||||
return;
|
|
||||||
|
let groupName;
|
||||||
|
if (title.includes('weekly jams')) {
|
||||||
|
groupName = 'Weekly Jams';
|
||||||
|
} else if (title.includes('weekly exploration')) {
|
||||||
|
groupName = 'Weekly Exploration';
|
||||||
|
} else if (title.includes('top discoveries')) {
|
||||||
|
groupName = 'Top Discoveries';
|
||||||
|
} else if (title.includes('top missed recordings')) {
|
||||||
|
groupName = 'Top Missed Recordings';
|
||||||
|
} else if (title.includes('daily jams')) {
|
||||||
|
groupName = 'Daily Jams';
|
||||||
|
} else {
|
||||||
|
groupName = 'Other';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groups[groupName]) {
|
||||||
|
groups[groupName] = [];
|
||||||
|
groupOrder.push(groupName);
|
||||||
|
}
|
||||||
|
groups[groupName].push(playlist);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Move "Other" to the end if it exists
|
||||||
|
const otherIdx = groupOrder.indexOf('Other');
|
||||||
|
if (otherIdx !== -1 && otherIdx !== groupOrder.length - 1) {
|
||||||
|
groupOrder.splice(otherIdx, 1);
|
||||||
|
groupOrder.push('Other');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build HTML for all playlists in this tab
|
return { groups, groupOrder };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildListenBrainzPlaylistsHtml(playlists, tabId) {
|
||||||
let html = '';
|
let html = '';
|
||||||
playlists.forEach((playlist, index) => {
|
playlists.forEach((playlist, index) => {
|
||||||
const playlistData = playlist.playlist || playlist;
|
const playlistData = playlist.playlist || playlist;
|
||||||
|
|
@ -31702,18 +31733,123 @@ async function loadListenBrainzTabContent(tabId) {
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
container.innerHTML = html;
|
function loadTracksForPlaylists(playlists) {
|
||||||
|
playlists.forEach((playlist) => {
|
||||||
// Load tracks for all playlists in this tab
|
|
||||||
playlists.forEach((playlist, index) => {
|
|
||||||
const playlistData = playlist.playlist || playlist;
|
const playlistData = playlist.playlist || playlist;
|
||||||
const identifier = playlistData.identifier?.split('/').pop() || '';
|
const identifier = playlistData.identifier?.split('/').pop() || '';
|
||||||
const playlistId = `discover-lb-playlist-${identifier}`; // Use consistent MBID-based ID
|
const playlistId = `discover-lb-playlist-${identifier}`;
|
||||||
loadListenBrainzPlaylistTracks(identifier, playlistId);
|
loadListenBrainzPlaylistTracks(identifier, playlistId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function switchListenBrainzSubTab(groupId) {
|
||||||
|
activeListenBrainzSubTab = groupId;
|
||||||
|
|
||||||
|
// Update sub-tab buttons
|
||||||
|
const subTabs = document.querySelectorAll('#lb-subtabs-bar .lb-subtab');
|
||||||
|
subTabs.forEach(tab => {
|
||||||
|
if (tab.dataset.group === groupId) {
|
||||||
|
tab.classList.add('active');
|
||||||
|
} else {
|
||||||
|
tab.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show/hide sub-tab content panels
|
||||||
|
const panels = document.querySelectorAll('.lb-subtab-panel');
|
||||||
|
panels.forEach(panel => {
|
||||||
|
if (panel.dataset.group === groupId) {
|
||||||
|
panel.style.display = 'block';
|
||||||
|
// Load tracks for playlists in this panel if not already loaded
|
||||||
|
const unloaded = panel.querySelectorAll('.discover-loading');
|
||||||
|
if (unloaded.length > 0) {
|
||||||
|
const groupPlaylists = panel._playlists;
|
||||||
|
if (groupPlaylists) {
|
||||||
|
loadTracksForPlaylists(groupPlaylists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panel.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadListenBrainzTabContent(tabId) {
|
||||||
|
const container = document.getElementById('listenbrainz-tab-content');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const playlists = listenbrainzPlaylistsCache[tabId] || [];
|
||||||
|
if (playlists.length === 0) {
|
||||||
|
container.innerHTML = '<div class="discover-empty"><p>No playlists in this category</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For recommendations tab with multiple playlists, group into sub-tabs
|
||||||
|
if (tabId === 'recommendations' && playlists.length > 1) {
|
||||||
|
const { groups, groupOrder } = groupListenBrainzPlaylists(playlists);
|
||||||
|
|
||||||
|
// If only one group, no need for sub-tabs
|
||||||
|
if (groupOrder.length <= 1) {
|
||||||
|
const html = buildListenBrainzPlaylistsHtml(playlists, tabId);
|
||||||
|
container.innerHTML = html;
|
||||||
|
loadTracksForPlaylists(playlists);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build sub-tabs bar
|
||||||
|
const firstGroup = activeListenBrainzSubTab && groupOrder.includes(activeListenBrainzSubTab)
|
||||||
|
? activeListenBrainzSubTab
|
||||||
|
: groupOrder[0];
|
||||||
|
activeListenBrainzSubTab = firstGroup;
|
||||||
|
|
||||||
|
let subTabsHtml = '<div class="decade-tabs-inner" id="lb-subtabs-bar" style="margin-bottom: 16px;">';
|
||||||
|
groupOrder.forEach(groupName => {
|
||||||
|
const isActive = groupName === firstGroup;
|
||||||
|
const count = groups[groupName].length;
|
||||||
|
subTabsHtml += `
|
||||||
|
<button class="decade-tab lb-subtab${isActive ? ' active' : ''}"
|
||||||
|
onclick="switchListenBrainzSubTab('${groupName}')"
|
||||||
|
data-group="${groupName}"
|
||||||
|
style="font-size: 13px; padding: 8px 16px;">
|
||||||
|
${groupName} (${count})
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
subTabsHtml += '</div>';
|
||||||
|
|
||||||
|
// Build content panels for each group
|
||||||
|
let panelsHtml = '';
|
||||||
|
groupOrder.forEach(groupName => {
|
||||||
|
const isActive = groupName === firstGroup;
|
||||||
|
panelsHtml += `<div class="lb-subtab-panel" data-group="${groupName}" style="display: ${isActive ? 'block' : 'none'};">`;
|
||||||
|
panelsHtml += buildListenBrainzPlaylistsHtml(groups[groupName], tabId);
|
||||||
|
panelsHtml += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
container.innerHTML = subTabsHtml + panelsHtml;
|
||||||
|
|
||||||
|
// Store playlist references on panels for lazy loading
|
||||||
|
groupOrder.forEach(groupName => {
|
||||||
|
const panel = container.querySelector(`.lb-subtab-panel[data-group="${groupName}"]`);
|
||||||
|
if (panel) {
|
||||||
|
panel._playlists = groups[groupName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load tracks only for the active sub-tab
|
||||||
|
loadTracksForPlaylists(groups[firstGroup]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: flat list for user/collaborative tabs (or single-group recommendations)
|
||||||
|
const html = buildListenBrainzPlaylistsHtml(playlists, tabId);
|
||||||
|
container.innerHTML = html;
|
||||||
|
loadTracksForPlaylists(playlists);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadListenBrainzPlaylistTracks(identifier, playlistId) {
|
async function loadListenBrainzPlaylistTracks(identifier, playlistId) {
|
||||||
try {
|
try {
|
||||||
const playlistContainer = document.getElementById(`${playlistId}-playlist`);
|
const playlistContainer = document.getElementById(`${playlistId}-playlist`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue