style youtube cards
This commit is contained in:
parent
61e822bf6f
commit
58f7fdb005
2 changed files with 1500 additions and 1473 deletions
|
|
@ -513,7 +513,7 @@ class YouTubeClient:
|
||||||
video_id = entry.get('id', '')
|
video_id = entry.get('id', '')
|
||||||
filename = f"{video_id}||{title}" # Store video_id and title for later download
|
filename = f"{video_id}||{title}" # Store video_id and title for later download
|
||||||
|
|
||||||
return TrackResult(
|
track_result = TrackResult(
|
||||||
username="youtube", # YouTube doesn't have users - use constant
|
username="youtube", # YouTube doesn't have users - use constant
|
||||||
filename=filename,
|
filename=filename,
|
||||||
size=file_size,
|
size=file_size,
|
||||||
|
|
@ -529,6 +529,11 @@ class YouTubeClient:
|
||||||
track_number=None
|
track_number=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add thumbnail for frontend (surgical addition)
|
||||||
|
track_result.thumbnail = entry.get('thumbnail')
|
||||||
|
|
||||||
|
return track_result
|
||||||
|
|
||||||
async def search(self, query: str, timeout: int = None, progress_callback=None) -> tuple[List[TrackResult], List[AlbumResult]]:
|
async def search(self, query: str, timeout: int = None, progress_callback=None) -> tuple[List[TrackResult], List[AlbumResult]]:
|
||||||
"""
|
"""
|
||||||
Search YouTube for tracks matching the query (async, Soulseek-compatible interface).
|
Search YouTube for tracks matching the query (async, Soulseek-compatible interface).
|
||||||
|
|
@ -564,8 +569,8 @@ class YouTubeClient:
|
||||||
}
|
}
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
# Search YouTube (max 10 results)
|
# Search YouTube (max 50 results)
|
||||||
search_results = ydl.extract_info(f"ytsearch10:{query}", download=False)
|
search_results = ydl.extract_info(f"ytsearch50:{query}", download=False)
|
||||||
|
|
||||||
if not search_results or 'entries' not in search_results:
|
if not search_results or 'entries' not in search_results:
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
|
|
@ -93,39 +93,39 @@ let similarArtistsController = null; // Track ongoing similar artists stream to
|
||||||
// --- Wishlist Modal Persistence State Management ---
|
// --- Wishlist Modal Persistence State Management ---
|
||||||
const WishlistModalState = {
|
const WishlistModalState = {
|
||||||
// Track if wishlist modal was visible before page refresh
|
// Track if wishlist modal was visible before page refresh
|
||||||
setVisible: function() {
|
setVisible: function () {
|
||||||
localStorage.setItem('wishlist_modal_visible', 'true');
|
localStorage.setItem('wishlist_modal_visible', 'true');
|
||||||
console.log('📱 [Modal State] Wishlist modal marked as visible in localStorage');
|
console.log('📱 [Modal State] Wishlist modal marked as visible in localStorage');
|
||||||
},
|
},
|
||||||
|
|
||||||
setHidden: function() {
|
setHidden: function () {
|
||||||
localStorage.setItem('wishlist_modal_visible', 'false');
|
localStorage.setItem('wishlist_modal_visible', 'false');
|
||||||
console.log('📱 [Modal State] Wishlist modal marked as hidden in localStorage');
|
console.log('📱 [Modal State] Wishlist modal marked as hidden in localStorage');
|
||||||
},
|
},
|
||||||
|
|
||||||
wasVisible: function() {
|
wasVisible: function () {
|
||||||
const visible = localStorage.getItem('wishlist_modal_visible') === 'true';
|
const visible = localStorage.getItem('wishlist_modal_visible') === 'true';
|
||||||
console.log(`📱 [Modal State] Checking if wishlist modal was visible: ${visible}`);
|
console.log(`📱 [Modal State] Checking if wishlist modal was visible: ${visible}`);
|
||||||
return visible;
|
return visible;
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: function() {
|
clear: function () {
|
||||||
localStorage.removeItem('wishlist_modal_visible');
|
localStorage.removeItem('wishlist_modal_visible');
|
||||||
console.log('📱 [Modal State] Cleared wishlist modal visibility state');
|
console.log('📱 [Modal State] Cleared wishlist modal visibility state');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Track if user manually closed the modal during auto-processing
|
// Track if user manually closed the modal during auto-processing
|
||||||
setUserClosed: function() {
|
setUserClosed: function () {
|
||||||
localStorage.setItem('wishlist_modal_user_closed', 'true');
|
localStorage.setItem('wishlist_modal_user_closed', 'true');
|
||||||
console.log('📱 [Modal State] User manually closed wishlist modal during auto-processing');
|
console.log('📱 [Modal State] User manually closed wishlist modal during auto-processing');
|
||||||
},
|
},
|
||||||
|
|
||||||
clearUserClosed: function() {
|
clearUserClosed: function () {
|
||||||
localStorage.removeItem('wishlist_modal_user_closed');
|
localStorage.removeItem('wishlist_modal_user_closed');
|
||||||
console.log('📱 [Modal State] Cleared user closed state');
|
console.log('📱 [Modal State] Cleared user closed state');
|
||||||
},
|
},
|
||||||
|
|
||||||
wasUserClosed: function() {
|
wasUserClosed: function () {
|
||||||
const closed = localStorage.getItem('wishlist_modal_user_closed') === 'true';
|
const closed = localStorage.getItem('wishlist_modal_user_closed') === 'true';
|
||||||
console.log(`📱 [Modal State] Checking if user closed modal: ${closed}`);
|
console.log(`📱 [Modal State] Checking if user closed modal: ${closed}`);
|
||||||
return closed;
|
return closed;
|
||||||
|
|
@ -294,7 +294,7 @@ const API = {
|
||||||
// INITIALIZATION
|
// INITIALIZATION
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
console.log('SoulSync WebUI initializing...');
|
console.log('SoulSync WebUI initializing...');
|
||||||
|
|
||||||
// Initialize components
|
// Initialize components
|
||||||
|
|
@ -330,7 +330,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
loadInitialData();
|
loadInitialData();
|
||||||
|
|
||||||
// Handle window resize to re-check track title scrolling
|
// Handle window resize to re-check track title scrolling
|
||||||
window.addEventListener('resize', function() {
|
window.addEventListener('resize', function () {
|
||||||
if (currentTrack) {
|
if (currentTrack) {
|
||||||
const trackTitleElement = document.getElementById('track-title');
|
const trackTitleElement = document.getElementById('track-title');
|
||||||
const trackTitle = currentTrack.title || 'Unknown Track';
|
const trackTitle = currentTrack.title || 'Unknown Track';
|
||||||
|
|
@ -1631,11 +1631,11 @@ async function loadSettingsData() {
|
||||||
document.getElementById('tidal-callback-display').textContent = settings.tidal?.redirect_uri || 'http://127.0.0.1:8889/tidal/callback';
|
document.getElementById('tidal-callback-display').textContent = settings.tidal?.redirect_uri || 'http://127.0.0.1:8889/tidal/callback';
|
||||||
|
|
||||||
// Add event listeners to update display URLs when input changes
|
// Add event listeners to update display URLs when input changes
|
||||||
document.getElementById('spotify-redirect-uri').addEventListener('input', function() {
|
document.getElementById('spotify-redirect-uri').addEventListener('input', function () {
|
||||||
document.getElementById('spotify-callback-display').textContent = this.value || 'http://127.0.0.1:8888/callback';
|
document.getElementById('spotify-callback-display').textContent = this.value || 'http://127.0.0.1:8888/callback';
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('tidal-redirect-uri').addEventListener('input', function() {
|
document.getElementById('tidal-redirect-uri').addEventListener('input', function () {
|
||||||
document.getElementById('tidal-callback-display').textContent = this.value || 'http://127.0.0.1:8889/tidal/callback';
|
document.getElementById('tidal-callback-display').textContent = this.value || 'http://127.0.0.1:8889/tidal/callback';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -2511,8 +2511,8 @@ function initializeSearchModeToggle() {
|
||||||
if (dropdown.classList.contains('hidden')) {
|
if (dropdown.classList.contains('hidden')) {
|
||||||
// Check if there are results to show by looking for actual content
|
// Check if there are results to show by looking for actual content
|
||||||
const hasResults = results &&
|
const hasResults = results &&
|
||||||
!results.classList.contains('hidden') &&
|
!results.classList.contains('hidden') &&
|
||||||
results.children.length > 0;
|
results.children.length > 0;
|
||||||
|
|
||||||
if (hasResults) {
|
if (hasResults) {
|
||||||
showDropdown();
|
showDropdown();
|
||||||
|
|
@ -2574,9 +2574,9 @@ function initializeSearchModeToggle() {
|
||||||
|
|
||||||
// Calculate total
|
// Calculate total
|
||||||
const total = (data.db_artists?.length || 0) +
|
const total = (data.db_artists?.length || 0) +
|
||||||
(data.spotify_artists?.length || 0) +
|
(data.spotify_artists?.length || 0) +
|
||||||
(data.spotify_albums?.length || 0) +
|
(data.spotify_albums?.length || 0) +
|
||||||
(data.spotify_tracks?.length || 0);
|
(data.spotify_tracks?.length || 0);
|
||||||
|
|
||||||
// Hide loading
|
// Hide loading
|
||||||
loadingState.classList.add('hidden');
|
loadingState.classList.add('hidden');
|
||||||
|
|
@ -3141,7 +3141,7 @@ function initializeSearchModeToggle() {
|
||||||
|
|
||||||
// Attach download handlers
|
// Attach download handlers
|
||||||
mainResultsArea.querySelectorAll('.download-result-btn').forEach(btn => {
|
mainResultsArea.querySelectorAll('.download-result-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async function() {
|
btn.addEventListener('click', async function () {
|
||||||
const result = JSON.parse(this.dataset.result);
|
const result = JSON.parse(this.dataset.result);
|
||||||
const type = this.dataset.type;
|
const type = this.dataset.type;
|
||||||
|
|
||||||
|
|
@ -3880,7 +3880,7 @@ async function rehydrateModal(processInfo, userRequested = false) {
|
||||||
// Check if modal already exists and is visible
|
// Check if modal already exists and is visible
|
||||||
const existingProcess = activeDownloadProcesses[playlist_id];
|
const existingProcess = activeDownloadProcesses[playlist_id];
|
||||||
const modalAlreadyOpen = existingProcess && existingProcess.modalElement &&
|
const modalAlreadyOpen = existingProcess && existingProcess.modalElement &&
|
||||||
existingProcess.modalElement.style.display === 'flex';
|
existingProcess.modalElement.style.display === 'flex';
|
||||||
|
|
||||||
if (modalAlreadyOpen) {
|
if (modalAlreadyOpen) {
|
||||||
console.log(`💧 [Rehydrate] Wishlist modal already open - updating existing modal with auto-process state`);
|
console.log(`💧 [Rehydrate] Wishlist modal already open - updating existing modal with auto-process state`);
|
||||||
|
|
@ -5175,8 +5175,8 @@ function showPlaylistDetailsModal(playlist) {
|
||||||
<button class="playlist-modal-btn playlist-modal-btn-secondary" onclick="closePlaylistDetailsModal()">Close</button>
|
<button class="playlist-modal-btn playlist-modal-btn-secondary" onclick="closePlaylistDetailsModal()">Close</button>
|
||||||
<button class="playlist-modal-btn playlist-modal-btn-tertiary" onclick="openDownloadMissingModal('${playlist.id}')">
|
<button class="playlist-modal-btn playlist-modal-btn-tertiary" onclick="openDownloadMissingModal('${playlist.id}')">
|
||||||
${hasCompletedProcess
|
${hasCompletedProcess
|
||||||
? '📊 View Download Results'
|
? '📊 View Download Results'
|
||||||
: '📥 Download Missing Tracks'}
|
: '📥 Download Missing Tracks'}
|
||||||
</button>
|
</button>
|
||||||
<button id="sync-btn-${playlist.id}" class="playlist-modal-btn playlist-modal-btn-primary" onclick="startPlaylistSync('${playlist.id}')" ${isSyncing ? 'disabled' : ''}>${isSyncing ? '⏳ Syncing...' : 'Sync Playlist'}</button>
|
<button id="sync-btn-${playlist.id}" class="playlist-modal-btn playlist-modal-btn-primary" onclick="startPlaylistSync('${playlist.id}')" ${isSyncing ? 'disabled' : ''}>${isSyncing ? '⏳ Syncing...' : 'Sync Playlist'}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -5715,14 +5715,14 @@ async function openDownloadMissingModalForYouTube(virtualPlaylistId, playlistNam
|
||||||
|
|
||||||
// Generate hero section with dynamic source detection
|
// Generate hero section with dynamic source detection
|
||||||
const source = virtualPlaylistId.startsWith('beatport_') ? 'Beatport' :
|
const source = virtualPlaylistId.startsWith('beatport_') ? 'Beatport' :
|
||||||
virtualPlaylistId.startsWith('tidal_') ? 'Tidal' :
|
virtualPlaylistId.startsWith('tidal_') ? 'Tidal' :
|
||||||
virtualPlaylistId.startsWith('listenbrainz_') ? 'ListenBrainz' :
|
virtualPlaylistId.startsWith('listenbrainz_') ? 'ListenBrainz' :
|
||||||
virtualPlaylistId.startsWith('discover_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('discover_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('seasonal_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('seasonal_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('build_playlist_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('build_playlist_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('decade_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('decade_') ? 'SoulSync' :
|
||||||
virtualPlaylistId === 'build_playlist_custom' ? 'SoulSync' :
|
virtualPlaylistId === 'build_playlist_custom' ? 'SoulSync' :
|
||||||
'YouTube';
|
'YouTube';
|
||||||
|
|
||||||
// Store metadata for discover download sidebar (will be added when Begin Analysis is clicked)
|
// Store metadata for discover download sidebar (will be added when Begin Analysis is clicked)
|
||||||
if (source === 'SoulSync' || virtualPlaylistId.startsWith('discover_lb_') || virtualPlaylistId.startsWith('listenbrainz_')) {
|
if (source === 'SoulSync' || virtualPlaylistId.startsWith('discover_lb_') || virtualPlaylistId.startsWith('listenbrainz_')) {
|
||||||
|
|
@ -7672,15 +7672,15 @@ function processModalStatusUpdate(playlistId, data) {
|
||||||
console.debug(`📊 [Status Update] Processing ${data.phase} phase for playlistId: ${playlistId}, tasks: ${(data.tasks || []).length}`);
|
console.debug(`📊 [Status Update] Processing ${data.phase} phase for playlistId: ${playlistId}, tasks: ${(data.tasks || []).length}`);
|
||||||
|
|
||||||
if (document.getElementById(`analysis-progress-fill-${playlistId}`).style.width !== '100%') {
|
if (document.getElementById(`analysis-progress-fill-${playlistId}`).style.width !== '100%') {
|
||||||
document.getElementById(`analysis-progress-fill-${playlistId}`).style.width = '100%';
|
document.getElementById(`analysis-progress-fill-${playlistId}`).style.width = '100%';
|
||||||
document.getElementById(`analysis-progress-text-${playlistId}`).textContent = 'Analysis complete!';
|
document.getElementById(`analysis-progress-text-${playlistId}`).textContent = 'Analysis complete!';
|
||||||
if(data.analysis_results) {
|
if (data.analysis_results) {
|
||||||
updateTrackAnalysisResults(playlistId, data.analysis_results);
|
updateTrackAnalysisResults(playlistId, data.analysis_results);
|
||||||
const foundCount = data.analysis_results.filter(r => r.found).length;
|
const foundCount = data.analysis_results.filter(r => r.found).length;
|
||||||
const missingCount = data.analysis_results.filter(r => !r.found).length;
|
const missingCount = data.analysis_results.filter(r => !r.found).length;
|
||||||
document.getElementById(`stat-found-${playlistId}`).textContent = foundCount;
|
document.getElementById(`stat-found-${playlistId}`).textContent = foundCount;
|
||||||
document.getElementById(`stat-missing-${playlistId}`).textContent = missingCount;
|
document.getElementById(`stat-missing-${playlistId}`).textContent = missingCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const missingTracks = (data.analysis_results || []).filter(r => !r.found);
|
const missingTracks = (data.analysis_results || []).filter(r => !r.found);
|
||||||
const missingCount = missingTracks.length;
|
const missingCount = missingTracks.length;
|
||||||
|
|
@ -7751,7 +7751,7 @@ function processModalStatusUpdate(playlistId, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(statusEl) {
|
if (statusEl) {
|
||||||
statusEl.textContent = statusText;
|
statusEl.textContent = statusText;
|
||||||
console.debug(`✅ [Status Update] Updated track ${task.track_index} to: ${statusText}${isV2Task ? ' (V2)' : ''}`);
|
console.debug(`✅ [Status Update] Updated track ${task.track_index} to: ${statusText}${isV2Task ? ' (V2)' : ''}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -8059,7 +8059,7 @@ async function updateModalWithLiveDownloadProgress() {
|
||||||
if (downloadData.error) return;
|
if (downloadData.error) return;
|
||||||
|
|
||||||
// Get all active and finished downloads
|
// Get all active and finished downloads
|
||||||
const allDownloads = {...(downloadData.active || {}), ...(downloadData.finished || {})};
|
const allDownloads = { ...(downloadData.active || {}), ...(downloadData.finished || {}) };
|
||||||
|
|
||||||
// Update modal tracks that have active downloads
|
// Update modal tracks that have active downloads
|
||||||
const modalRows = document.querySelectorAll('.download-missing-modal tr[data-track-index]');
|
const modalRows = document.querySelectorAll('.download-missing-modal tr[data-track-index]');
|
||||||
|
|
@ -9073,8 +9073,30 @@ function displayDownloadsResults(results) {
|
||||||
let html = '';
|
let html = '';
|
||||||
results.forEach((result, index) => {
|
results.forEach((result, index) => {
|
||||||
const isAlbum = result.result_type === 'album';
|
const isAlbum = result.result_type === 'album';
|
||||||
|
if (result.username === 'youtube') {
|
||||||
|
const thumbnail = result.thumbnail || '';
|
||||||
|
const durationText = result.duration ? formatDuration(result.duration) : '';
|
||||||
|
|
||||||
if (isAlbum) {
|
html += `
|
||||||
|
<div class="youtube-result-card" style="display: flex; gap: 1rem; padding: 1rem; background: rgba(255, 0, 0, 0.05); border: 1px solid rgba(255, 0, 0, 0.1); border-radius: 8px; margin-bottom: 0.5rem; align-items: center;">
|
||||||
|
<div class="yt-thumb" style="width: 120px; height: 68px; flex-shrink: 0; border-radius: 4px; overflow: hidden; background: #000;">
|
||||||
|
<img src="${thumbnail}" style="width: 100%; height: 100%; object-fit: cover;" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iNjgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiMzMzMiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZmlsbD0iIzY2NiIgZm9udC1zaXplPSIxMiIgZGVtaW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Tm8gSW1hZ2U8L3RleHQ+PC9zdmc+'">
|
||||||
|
</div>
|
||||||
|
<div class="yt-info" style="flex: 1;">
|
||||||
|
<div class="yt-title" style="font-weight: 600; margin-bottom: 4px;">${escapeHtml(result.title)}</div>
|
||||||
|
<div class="yt-channel" style="font-size: 0.9em; opacity: 0.8;">${escapeHtml(result.artist || result.username)} • ${durationText}</div>
|
||||||
|
</div>
|
||||||
|
<div class="yt-actions">
|
||||||
|
<button onclick="streamAlbumTrack(${index}, -1)" class="track-stream-btn">Stream ▶</button>
|
||||||
|
<button onclick="downloadAlbumTrack(${index}, -1)" class="track-download-btn">Download ⬇</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return; // Skip standard rendering for YouTube result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isAlbum && result.username !== 'youtube') {
|
||||||
const trackCount = result.tracks ? result.tracks.length : 0;
|
const trackCount = result.tracks ? result.tracks.length : 0;
|
||||||
const totalSize = result.total_size ? `${(result.total_size / 1024 / 1024).toFixed(1)} MB` : 'Unknown size';
|
const totalSize = result.total_size ? `${(result.total_size / 1024 / 1024).toFixed(1)} MB` : 'Unknown size';
|
||||||
|
|
||||||
|
|
@ -9403,9 +9425,9 @@ async function loadArtistsData() {
|
||||||
<div class="artist-card">
|
<div class="artist-card">
|
||||||
<div class="artist-image">
|
<div class="artist-image">
|
||||||
${artist.image ?
|
${artist.image ?
|
||||||
`<img src="${artist.image}" alt="${escapeHtml(artist.name)}" />` :
|
`<img src="${artist.image}" alt="${escapeHtml(artist.name)}" />` :
|
||||||
'<div class="artist-placeholder">🎵</div>'
|
'<div class="artist-placeholder">🎵</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="artist-info">
|
<div class="artist-info">
|
||||||
<div class="artist-name">${escapeHtml(artist.name)}</div>
|
<div class="artist-name">${escapeHtml(artist.name)}</div>
|
||||||
|
|
@ -9892,7 +9914,7 @@ function openDiscoveryFixModal(platform, identifier, trackIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new enter key handler
|
// Add new enter key handler
|
||||||
discoveryFixEnterHandler = function(e) {
|
discoveryFixEnterHandler = function (e) {
|
||||||
if (e.key === 'Enter') searchDiscoveryFix();
|
if (e.key === 'Enter') searchDiscoveryFix();
|
||||||
};
|
};
|
||||||
trackInput.addEventListener('keypress', discoveryFixEnterHandler);
|
trackInput.addEventListener('keypress', discoveryFixEnterHandler);
|
||||||
|
|
@ -13075,7 +13097,7 @@ function updateDbProgressUI(state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.status === 'finished' || state.status === 'error') {
|
if (state.status === 'finished' || state.status === 'error') {
|
||||||
// Final stats refresh after completion/error
|
// Final stats refresh after completion/error
|
||||||
setTimeout(fetchAndUpdateDbStats, 500);
|
setTimeout(fetchAndUpdateDbStats, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13223,7 +13245,7 @@ async function handleTidalCardClick(playlistId) {
|
||||||
state.discovery_results = fullState.discovery_results;
|
state.discovery_results = fullState.discovery_results;
|
||||||
state.spotify_matches = fullState.spotify_matches || state.spotify_matches;
|
state.spotify_matches = fullState.spotify_matches || state.spotify_matches;
|
||||||
state.discovery_progress = fullState.discovery_progress || state.discovery_progress;
|
state.discovery_progress = fullState.discovery_progress || state.discovery_progress;
|
||||||
tidalPlaylistStates[playlistId] = {...tidalPlaylistStates[playlistId], ...state};
|
tidalPlaylistStates[playlistId] = { ...tidalPlaylistStates[playlistId], ...state };
|
||||||
console.log(`✅ [Card Click] Restored ${fullState.discovery_results.length} discovery results from backend`);
|
console.log(`✅ [Card Click] Restored ${fullState.discovery_results.length} discovery results from backend`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13515,10 +13537,10 @@ async function openTidalDiscoveryModal(playlistId, playlistData) {
|
||||||
transformedResults = tidalCardState.discovery_results.map((result, index) => {
|
transformedResults = tidalCardState.discovery_results.map((result, index) => {
|
||||||
// Check multiple status formats
|
// Check multiple status formats
|
||||||
const isFound = result.status === 'found' ||
|
const isFound = result.status === 'found' ||
|
||||||
result.status === '✅ Found' ||
|
result.status === '✅ Found' ||
|
||||||
result.status_class === 'found' ||
|
result.status_class === 'found' ||
|
||||||
result.spotify_data ||
|
result.spotify_data ||
|
||||||
result.spotify_track;
|
result.spotify_track;
|
||||||
if (isFound) actualMatches++;
|
if (isFound) actualMatches++;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -13635,10 +13657,10 @@ function startTidalDiscoveryPolling(fakeUrlHash, playlistId) {
|
||||||
spotify_total: status.spotify_total,
|
spotify_total: status.spotify_total,
|
||||||
results: status.results.map((result, index) => {
|
results: status.results.map((result, index) => {
|
||||||
const isFound = result.status === 'found' ||
|
const isFound = result.status === 'found' ||
|
||||||
result.status === '✅ Found' ||
|
result.status === '✅ Found' ||
|
||||||
result.status_class === 'found' ||
|
result.status_class === 'found' ||
|
||||||
result.spotify_data ||
|
result.spotify_data ||
|
||||||
result.spotify_track;
|
result.spotify_track;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: index,
|
index: index,
|
||||||
|
|
@ -14272,14 +14294,14 @@ async function openDownloadMissingModalForTidal(virtualPlaylistId, playlistName,
|
||||||
|
|
||||||
// Generate hero section with dynamic source detection (same as YouTube/Beatport)
|
// Generate hero section with dynamic source detection (same as YouTube/Beatport)
|
||||||
const source = virtualPlaylistId.startsWith('beatport_') ? 'Beatport' :
|
const source = virtualPlaylistId.startsWith('beatport_') ? 'Beatport' :
|
||||||
virtualPlaylistId.startsWith('tidal_') ? 'Tidal' :
|
virtualPlaylistId.startsWith('tidal_') ? 'Tidal' :
|
||||||
virtualPlaylistId.startsWith('listenbrainz_') ? 'ListenBrainz' :
|
virtualPlaylistId.startsWith('listenbrainz_') ? 'ListenBrainz' :
|
||||||
virtualPlaylistId.startsWith('discover_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('discover_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('seasonal_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('seasonal_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('build_playlist_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('build_playlist_') ? 'SoulSync' :
|
||||||
virtualPlaylistId.startsWith('decade_') ? 'SoulSync' :
|
virtualPlaylistId.startsWith('decade_') ? 'SoulSync' :
|
||||||
virtualPlaylistId === 'build_playlist_custom' ? 'SoulSync' :
|
virtualPlaylistId === 'build_playlist_custom' ? 'SoulSync' :
|
||||||
'YouTube';
|
'YouTube';
|
||||||
|
|
||||||
const heroContext = {
|
const heroContext = {
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
|
|
@ -14991,7 +15013,7 @@ function handleBeatportCategoryClick(category) {
|
||||||
console.log(`🎵 Beatport category clicked: ${category}`);
|
console.log(`🎵 Beatport category clicked: ${category}`);
|
||||||
|
|
||||||
// Only handle genres category now - homepage has direct chart buttons
|
// Only handle genres category now - homepage has direct chart buttons
|
||||||
switch(category) {
|
switch (category) {
|
||||||
case 'genres':
|
case 'genres':
|
||||||
showBeatportSubView('genres');
|
showBeatportSubView('genres');
|
||||||
loadBeatportGenres(); // Load genres dynamically
|
loadBeatportGenres(); // Load genres dynamically
|
||||||
|
|
@ -15645,7 +15667,7 @@ async function getRebuildPageTrackData(trackDataKey) {
|
||||||
// Hook into the loadBeatportTop10Lists function to cache track data
|
// Hook into the loadBeatportTop10Lists function to cache track data
|
||||||
const originalLoadBeatportTop10Lists = window.loadBeatportTop10Lists;
|
const originalLoadBeatportTop10Lists = window.loadBeatportTop10Lists;
|
||||||
if (originalLoadBeatportTop10Lists) {
|
if (originalLoadBeatportTop10Lists) {
|
||||||
window.loadBeatportTop10Lists = async function() {
|
window.loadBeatportTop10Lists = async function () {
|
||||||
const result = await originalLoadBeatportTop10Lists.apply(this, arguments);
|
const result = await originalLoadBeatportTop10Lists.apply(this, arguments);
|
||||||
|
|
||||||
// If the load was successful, we can potentially cache the track data
|
// If the load was successful, we can potentially cache the track data
|
||||||
|
|
@ -17932,13 +17954,13 @@ function openYouTubeDiscoveryModal(urlHash) {
|
||||||
const isBeatport = state.is_beatport_playlist;
|
const isBeatport = state.is_beatport_playlist;
|
||||||
const isListenBrainz = state.is_listenbrainz_playlist;
|
const isListenBrainz = state.is_listenbrainz_playlist;
|
||||||
const modalTitle = isTidal ? '🎵 Tidal Playlist Discovery' :
|
const modalTitle = isTidal ? '🎵 Tidal Playlist Discovery' :
|
||||||
isBeatport ? '🎵 Beatport Chart Discovery' :
|
isBeatport ? '🎵 Beatport Chart Discovery' :
|
||||||
isListenBrainz ? '🎵 ListenBrainz Playlist Discovery' :
|
isListenBrainz ? '🎵 ListenBrainz Playlist Discovery' :
|
||||||
'🎵 YouTube Playlist Discovery';
|
'🎵 YouTube Playlist Discovery';
|
||||||
const sourceLabel = isTidal ? 'Tidal' :
|
const sourceLabel = isTidal ? 'Tidal' :
|
||||||
isBeatport ? 'Beatport' :
|
isBeatport ? 'Beatport' :
|
||||||
isListenBrainz ? 'LB' :
|
isListenBrainz ? 'LB' :
|
||||||
'YT';
|
'YT';
|
||||||
|
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
<div class="modal-overlay" id="youtube-discovery-modal-${urlHash}">
|
<div class="modal-overlay" id="youtube-discovery-modal-${urlHash}">
|
||||||
|
|
@ -18360,17 +18382,17 @@ function formatDuration(durationMs) {
|
||||||
function generateDiscoveryActionButton(result, identifier, platform) {
|
function generateDiscoveryActionButton(result, identifier, platform) {
|
||||||
// Show fix button for not_found, error, or any non-found status
|
// Show fix button for not_found, error, or any non-found status
|
||||||
const isNotFound = result.status === 'not_found' ||
|
const isNotFound = result.status === 'not_found' ||
|
||||||
result.status_class === 'not-found' ||
|
result.status_class === 'not-found' ||
|
||||||
result.status === '❌ Not Found' ||
|
result.status === '❌ Not Found' ||
|
||||||
result.status === 'Not Found';
|
result.status === 'Not Found';
|
||||||
|
|
||||||
const isError = result.status === 'error' ||
|
const isError = result.status === 'error' ||
|
||||||
result.status_class === 'error' ||
|
result.status_class === 'error' ||
|
||||||
result.status === '❌ Error';
|
result.status === '❌ Error';
|
||||||
|
|
||||||
const isFound = result.status === 'found' ||
|
const isFound = result.status === 'found' ||
|
||||||
result.status_class === 'found' ||
|
result.status_class === 'found' ||
|
||||||
result.status === '✅ Found';
|
result.status === '✅ Found';
|
||||||
|
|
||||||
if (isNotFound || isError) {
|
if (isNotFound || isError) {
|
||||||
return `<button class="fix-match-btn"
|
return `<button class="fix-match-btn"
|
||||||
|
|
@ -19172,7 +19194,7 @@ async function startListenBrainzDiscovery(playlistMbid) {
|
||||||
// Call backend to start discovery worker
|
// Call backend to start discovery worker
|
||||||
const response = await fetch(`/api/listenbrainz/discovery/start/${playlistMbid}`, {
|
const response = await fetch(`/api/listenbrainz/discovery/start/${playlistMbid}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
playlist: state.playlist
|
playlist: state.playlist
|
||||||
})
|
})
|
||||||
|
|
@ -20485,7 +20507,7 @@ function createAlbumCardHTML(album) {
|
||||||
const imageUrl = album.image_url || '';
|
const imageUrl = album.image_url || '';
|
||||||
const year = album.release_date ? new Date(album.release_date).getFullYear() : '';
|
const year = album.release_date ? new Date(album.release_date).getFullYear() : '';
|
||||||
const type = album.album_type === 'album' ? 'Album' :
|
const type = album.album_type === 'album' ? 'Album' :
|
||||||
album.album_type === 'single' ? 'Single' : 'EP';
|
album.album_type === 'single' ? 'Single' : 'EP';
|
||||||
|
|
||||||
// Create a fallback gradient if no image is available
|
// Create a fallback gradient if no image is available
|
||||||
const backgroundStyle = imageUrl ?
|
const backgroundStyle = imageUrl ?
|
||||||
|
|
@ -21813,9 +21835,9 @@ async function openSearchDownloadModal(artistName) {
|
||||||
<div class="artist-download-modal-hero-content">
|
<div class="artist-download-modal-hero-content">
|
||||||
<div class="artist-download-modal-hero-avatar">
|
<div class="artist-download-modal-hero-avatar">
|
||||||
${artistBubbleData.artist.image_url
|
${artistBubbleData.artist.image_url
|
||||||
? `<img src="${escapeHtml(artistBubbleData.artist.image_url)}" alt="${escapeHtml(artistBubbleData.artist.name)}" class="artist-download-modal-hero-image" loading="lazy">`
|
? `<img src="${escapeHtml(artistBubbleData.artist.image_url)}" alt="${escapeHtml(artistBubbleData.artist.name)}" class="artist-download-modal-hero-image" loading="lazy">`
|
||||||
: '<div class="artist-download-modal-hero-fallback">🎵</div>'
|
: '<div class="artist-download-modal-hero-fallback">🎵</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="artist-download-modal-hero-info">
|
<div class="artist-download-modal-hero-info">
|
||||||
<h2 class="artist-download-modal-hero-title">${escapeHtml(artistBubbleData.artist.name)}</h2>
|
<h2 class="artist-download-modal-hero-title">${escapeHtml(artistBubbleData.artist.name)}</h2>
|
||||||
|
|
@ -21855,11 +21877,11 @@ function createSearchDownloadItem(download, index) {
|
||||||
<div class="artist-download-item" data-playlist-id="${virtualPlaylistId}">
|
<div class="artist-download-item" data-playlist-id="${virtualPlaylistId}">
|
||||||
<div class="download-item-artwork">
|
<div class="download-item-artwork">
|
||||||
${item.image_url
|
${item.image_url
|
||||||
? `<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="download-item-image" loading="lazy">`
|
? `<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="download-item-image" loading="lazy">`
|
||||||
: `<div class="download-item-fallback">
|
: `<div class="download-item-fallback">
|
||||||
${type === 'album' ? '💿' : '🎵'}
|
${type === 'album' ? '💿' : '🎵'}
|
||||||
</div>`
|
</div>`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="download-item-info">
|
<div class="download-item-info">
|
||||||
<div class="download-item-name">${escapeHtml(item.name)}</div>
|
<div class="download-item-name">${escapeHtml(item.name)}</div>
|
||||||
|
|
@ -22127,7 +22149,7 @@ function bulkCompleteSearchDownloads(artistName) {
|
||||||
// Find all completed downloads
|
// Find all completed downloads
|
||||||
const completedDownloads = artistBubbleData.downloads.filter(d => d.status === 'view_results');
|
const completedDownloads = artistBubbleData.downloads.filter(d => d.status === 'view_results');
|
||||||
console.log(`📋 Found ${completedDownloads.length} completed downloads to close:`,
|
console.log(`📋 Found ${completedDownloads.length} completed downloads to close:`,
|
||||||
completedDownloads.map(d => d.item.name));
|
completedDownloads.map(d => d.item.name));
|
||||||
|
|
||||||
if (completedDownloads.length === 0) {
|
if (completedDownloads.length === 0) {
|
||||||
console.warn(`⚠️ No completed downloads found for bulk close`);
|
console.warn(`⚠️ No completed downloads found for bulk close`);
|
||||||
|
|
@ -22382,9 +22404,9 @@ function openArtistDownloadModal(artistId) {
|
||||||
<div class="artist-download-modal-hero-content">
|
<div class="artist-download-modal-hero-content">
|
||||||
<div class="artist-download-modal-hero-avatar">
|
<div class="artist-download-modal-hero-avatar">
|
||||||
${artistBubbleData.artist.image_url
|
${artistBubbleData.artist.image_url
|
||||||
? `<img src="${escapeHtml(artistBubbleData.artist.image_url)}" alt="${escapeHtml(artistBubbleData.artist.name)}" class="artist-download-modal-hero-image" loading="lazy">`
|
? `<img src="${escapeHtml(artistBubbleData.artist.image_url)}" alt="${escapeHtml(artistBubbleData.artist.name)}" class="artist-download-modal-hero-image" loading="lazy">`
|
||||||
: '<div class="artist-download-modal-hero-fallback"><i class="fas fa-user-music"></i></div>'
|
: '<div class="artist-download-modal-hero-fallback"><i class="fas fa-user-music"></i></div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="artist-download-modal-hero-info">
|
<div class="artist-download-modal-hero-info">
|
||||||
<h2 class="artist-download-modal-hero-title">${escapeHtml(artistBubbleData.artist.name)}</h2>
|
<h2 class="artist-download-modal-hero-title">${escapeHtml(artistBubbleData.artist.name)}</h2>
|
||||||
|
|
@ -22426,11 +22448,11 @@ function createArtistDownloadItem(download, index) {
|
||||||
<div class="artist-download-item" data-playlist-id="${virtualPlaylistId}">
|
<div class="artist-download-item" data-playlist-id="${virtualPlaylistId}">
|
||||||
<div class="download-item-artwork">
|
<div class="download-item-artwork">
|
||||||
${album.image_url
|
${album.image_url
|
||||||
? `<img src="${escapeHtml(album.image_url)}" alt="${escapeHtml(album.name)}" class="download-item-image" loading="lazy">`
|
? `<img src="${escapeHtml(album.image_url)}" alt="${escapeHtml(album.name)}" class="download-item-image" loading="lazy">`
|
||||||
: `<div class="download-item-fallback">
|
: `<div class="download-item-fallback">
|
||||||
<i class="fas fa-${albumType === 'album' ? 'compact-disc' : albumType === 'single' ? 'music' : 'record-vinyl'}"></i>
|
<i class="fas fa-${albumType === 'album' ? 'compact-disc' : albumType === 'single' ? 'music' : 'record-vinyl'}"></i>
|
||||||
</div>`
|
</div>`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="download-item-info">
|
<div class="download-item-info">
|
||||||
<div class="download-item-name">${escapeHtml(album.name)}</div>
|
<div class="download-item-name">${escapeHtml(album.name)}</div>
|
||||||
|
|
@ -22558,7 +22580,7 @@ function bulkCompleteArtistDownloads(artistId) {
|
||||||
// Find all downloads in 'view_results' state
|
// Find all downloads in 'view_results' state
|
||||||
const completedDownloads = artistBubbleData.downloads.filter(d => d.status === 'view_results');
|
const completedDownloads = artistBubbleData.downloads.filter(d => d.status === 'view_results');
|
||||||
console.log(`📋 Found ${completedDownloads.length} completed downloads to close:`,
|
console.log(`📋 Found ${completedDownloads.length} completed downloads to close:`,
|
||||||
completedDownloads.map(d => d.album.name));
|
completedDownloads.map(d => d.album.name));
|
||||||
|
|
||||||
if (completedDownloads.length === 0) {
|
if (completedDownloads.length === 0) {
|
||||||
console.warn(`⚠️ No completed downloads found for bulk close`);
|
console.warn(`⚠️ No completed downloads found for bulk close`);
|
||||||
|
|
@ -22678,7 +22700,7 @@ async function extractImageColors(imageUrl, callback) {
|
||||||
|
|
||||||
img.crossOrigin = 'anonymous';
|
img.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = function () {
|
||||||
// Resize to small dimensions for faster processing
|
// Resize to small dimensions for faster processing
|
||||||
const size = 50;
|
const size = 50;
|
||||||
canvas.width = size;
|
canvas.width = size;
|
||||||
|
|
@ -22730,7 +22752,7 @@ async function extractImageColors(imageUrl, callback) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = function() {
|
img.onerror = function () {
|
||||||
callback(['#1db954', '#1ed760']); // Fallback on error
|
callback(['#1db954', '#1ed760']); // Fallback on error
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -23488,8 +23510,8 @@ async function openWatchlistArtistConfigModal(artistId, artistName) {
|
||||||
${artist.genres && artist.genres.length > 0 ? `
|
${artist.genres && artist.genres.length > 0 ? `
|
||||||
<div class="watchlist-artist-config-hero-genres">
|
<div class="watchlist-artist-config-hero-genres">
|
||||||
${artist.genres.slice(0, 3).map(genre =>
|
${artist.genres.slice(0, 3).map(genre =>
|
||||||
`<span class="watchlist-artist-config-genre-tag">${escapeHtml(genre)}</span>`
|
`<span class="watchlist-artist-config-genre-tag">${escapeHtml(genre)}</span>`
|
||||||
).join('')}
|
).join('')}
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -26331,12 +26353,12 @@ function populateBeatportHypePicksSlider(releases) {
|
||||||
<div class="beatport-hype-picks-grid">
|
<div class="beatport-hype-picks-grid">
|
||||||
${slideReleases.map(release => createBeatportHypePickCard(release)).join('')}
|
${slideReleases.map(release => createBeatportHypePickCard(release)).join('')}
|
||||||
${slideReleases.length < releasesPerSlide ?
|
${slideReleases.length < releasesPerSlide ?
|
||||||
Array(releasesPerSlide - slideReleases.length).fill(0).map(() =>
|
Array(releasesPerSlide - slideReleases.length).fill(0).map(() =>
|
||||||
`<div class="beatport-hype-pick-card beatport-hype-pick-placeholder">
|
`<div class="beatport-hype-pick-card beatport-hype-pick-placeholder">
|
||||||
<div class="placeholder-icon">🔥</div>
|
<div class="placeholder-icon">🔥</div>
|
||||||
</div>`
|
</div>`
|
||||||
).join('') : ''
|
).join('') : ''
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -27218,9 +27240,9 @@ function populateBeatportTop10List(tracks) {
|
||||||
<div class="beatport-top10-card-rank">${track.rank || index + 1}</div>
|
<div class="beatport-top10-card-rank">${track.rank || index + 1}</div>
|
||||||
<div class="beatport-top10-card-artwork">
|
<div class="beatport-top10-card-artwork">
|
||||||
${track.artwork_url ?
|
${track.artwork_url ?
|
||||||
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
||||||
'<div class="beatport-top10-card-placeholder">🎵</div>'
|
'<div class="beatport-top10-card-placeholder">🎵</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-top10-card-info">
|
<div class="beatport-top10-card-info">
|
||||||
<h4 class="beatport-top10-card-title">${cleanTitle}</h4>
|
<h4 class="beatport-top10-card-title">${cleanTitle}</h4>
|
||||||
|
|
@ -27262,9 +27284,9 @@ function populateHypeTop10List(tracks) {
|
||||||
<div class="beatport-hype10-card-rank">${track.rank || index + 1}</div>
|
<div class="beatport-hype10-card-rank">${track.rank || index + 1}</div>
|
||||||
<div class="beatport-hype10-card-artwork">
|
<div class="beatport-hype10-card-artwork">
|
||||||
${track.artwork_url ?
|
${track.artwork_url ?
|
||||||
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
||||||
'<div class="beatport-hype10-card-placeholder">🔥</div>'
|
'<div class="beatport-hype10-card-placeholder">🔥</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-hype10-card-info">
|
<div class="beatport-hype10-card-info">
|
||||||
<h4 class="beatport-hype10-card-title">${cleanTitle}</h4>
|
<h4 class="beatport-hype10-card-title">${cleanTitle}</h4>
|
||||||
|
|
@ -27340,9 +27362,9 @@ function populateBeatportTop10Releases(releases) {
|
||||||
<div class="beatport-releases-top10-card-rank">${release.rank || index + 1}</div>
|
<div class="beatport-releases-top10-card-rank">${release.rank || index + 1}</div>
|
||||||
<div class="beatport-releases-top10-card-artwork">
|
<div class="beatport-releases-top10-card-artwork">
|
||||||
${release.image_url ?
|
${release.image_url ?
|
||||||
`<img src="${release.image_url}" alt="${release.title}" loading="lazy">` :
|
`<img src="${release.image_url}" alt="${release.title}" loading="lazy">` :
|
||||||
'<div class="beatport-releases-top10-card-placeholder">💿</div>'
|
'<div class="beatport-releases-top10-card-placeholder">💿</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-releases-top10-card-info">
|
<div class="beatport-releases-top10-card-info">
|
||||||
<h4 class="beatport-releases-top10-card-title">${release.title || 'Unknown Title'}</h4>
|
<h4 class="beatport-releases-top10-card-title">${release.title || 'Unknown Title'}</h4>
|
||||||
|
|
@ -28778,9 +28800,9 @@ function createGenreTop10ListsHTML(data, genreName) {
|
||||||
<div class="beatport-top10-card-rank">${track.rank || index + 1}</div>
|
<div class="beatport-top10-card-rank">${track.rank || index + 1}</div>
|
||||||
<div class="beatport-top10-card-artwork">
|
<div class="beatport-top10-card-artwork">
|
||||||
${track.artwork_url ?
|
${track.artwork_url ?
|
||||||
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
||||||
'<div class="beatport-top10-card-placeholder">🎵</div>'
|
'<div class="beatport-top10-card-placeholder">🎵</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-top10-card-info">
|
<div class="beatport-top10-card-info">
|
||||||
<h4 class="beatport-top10-card-title">${cleanTitle}</h4>
|
<h4 class="beatport-top10-card-title">${cleanTitle}</h4>
|
||||||
|
|
@ -28819,9 +28841,9 @@ function createGenreTop10ListsHTML(data, genreName) {
|
||||||
<div class="beatport-hype10-card-rank">${track.rank || index + 1}</div>
|
<div class="beatport-hype10-card-rank">${track.rank || index + 1}</div>
|
||||||
<div class="beatport-hype10-card-artwork">
|
<div class="beatport-hype10-card-artwork">
|
||||||
${track.artwork_url ?
|
${track.artwork_url ?
|
||||||
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
`<img src="${track.artwork_url}" alt="${cleanTitle}" loading="lazy">` :
|
||||||
'<div class="beatport-hype10-card-placeholder">🔥</div>'
|
'<div class="beatport-hype10-card-placeholder">🔥</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-hype10-card-info">
|
<div class="beatport-hype10-card-info">
|
||||||
<h4 class="beatport-hype10-card-title">${cleanTitle}</h4>
|
<h4 class="beatport-hype10-card-title">${cleanTitle}</h4>
|
||||||
|
|
@ -29166,9 +29188,9 @@ function createGenreTop10ReleasesCardsHTML(releases) {
|
||||||
<div class="beatport-releases-top10-card-rank">${release.rank || index + 1}</div>
|
<div class="beatport-releases-top10-card-rank">${release.rank || index + 1}</div>
|
||||||
<div class="beatport-releases-top10-card-artwork">
|
<div class="beatport-releases-top10-card-artwork">
|
||||||
${release.image_url ?
|
${release.image_url ?
|
||||||
`<img src="${release.image_url}" alt="${release.title}" loading="lazy">` :
|
`<img src="${release.image_url}" alt="${release.title}" loading="lazy">` :
|
||||||
'<div class="beatport-releases-top10-card-placeholder">💿</div>'
|
'<div class="beatport-releases-top10-card-placeholder">💿</div>'
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="beatport-releases-top10-card-info">
|
<div class="beatport-releases-top10-card-info">
|
||||||
<h4 class="beatport-releases-top10-card-title">${release.title || 'Unknown Title'}</h4>
|
<h4 class="beatport-releases-top10-card-title">${release.title || 'Unknown Title'}</h4>
|
||||||
|
|
@ -29684,7 +29706,7 @@ function displayDiscoverHeroArtist(artist) {
|
||||||
// Add popularity indicator
|
// Add popularity indicator
|
||||||
if (artist.popularity !== undefined && artist.popularity > 0) {
|
if (artist.popularity !== undefined && artist.popularity > 0) {
|
||||||
const popularityClass = artist.popularity >= 80 ? 'high' :
|
const popularityClass = artist.popularity >= 80 ? 'high' :
|
||||||
artist.popularity >= 50 ? 'medium' : 'low';
|
artist.popularity >= 50 ? 'medium' : 'low';
|
||||||
metaHTML += `
|
metaHTML += `
|
||||||
<div class="hero-meta-item hero-popularity ${popularityClass}">
|
<div class="hero-meta-item hero-popularity ${popularityClass}">
|
||||||
<span class="meta-icon">⭐</span>
|
<span class="meta-icon">⭐</span>
|
||||||
|
|
@ -31615,10 +31637,10 @@ async function openListenBrainzPlaylist(playlistMbid, playlistName) {
|
||||||
const spotifyTracks = tracks.map(track => ({
|
const spotifyTracks = tracks.map(track => ({
|
||||||
id: track.recording_mbid || `listenbrainz_${track.title}_${track.creator}`.replace(/[^a-z0-9]/gi, '_'), // Generate ID if missing
|
id: track.recording_mbid || `listenbrainz_${track.title}_${track.creator}`.replace(/[^a-z0-9]/gi, '_'), // Generate ID if missing
|
||||||
name: track.title || 'Unknown',
|
name: track.title || 'Unknown',
|
||||||
artists: [{name: cleanArtistName(track.creator || 'Unknown')}], // Proper Spotify format
|
artists: [{ name: cleanArtistName(track.creator || 'Unknown') }], // Proper Spotify format
|
||||||
album: {
|
album: {
|
||||||
name: track.album || 'Unknown Album',
|
name: track.album || 'Unknown Album',
|
||||||
images: track.album_cover_url ? [{url: track.album_cover_url}] : []
|
images: track.album_cover_url ? [{ url: track.album_cover_url }] : []
|
||||||
},
|
},
|
||||||
duration_ms: track.duration_ms || 0,
|
duration_ms: track.duration_ms || 0,
|
||||||
listenbrainz_metadata: track.additional_metadata
|
listenbrainz_metadata: track.additional_metadata
|
||||||
|
|
@ -32547,7 +32569,7 @@ function checkForActiveDiscoverDownloads() {
|
||||||
*/
|
*/
|
||||||
// Check if discover page is loaded by looking for a discover-specific element
|
// Check if discover page is loaded by looking for a discover-specific element
|
||||||
const discoverPage = document.getElementById('release-radar-download-btn') ||
|
const discoverPage = document.getElementById('release-radar-download-btn') ||
|
||||||
document.getElementById('discovery-weekly-download-btn');
|
document.getElementById('discovery-weekly-download-btn');
|
||||||
|
|
||||||
if (!discoverPage) return;
|
if (!discoverPage) return;
|
||||||
|
|
||||||
|
|
@ -33224,7 +33246,7 @@ async function rehydrateDiscoverDownloadModal(playlistId) {
|
||||||
const spotifyTracks = tracks.map(track => ({
|
const spotifyTracks = tracks.map(track => ({
|
||||||
id: track.mbid || `listenbrainz_${track.track_name}_${track.artist_name}`.replace(/[^a-z0-9]/gi, '_'), // Generate ID if missing
|
id: track.mbid || `listenbrainz_${track.track_name}_${track.artist_name}`.replace(/[^a-z0-9]/gi, '_'), // Generate ID if missing
|
||||||
name: track.track_name,
|
name: track.track_name,
|
||||||
artists: [{name: cleanArtistName(track.artist_name)}], // Proper Spotify format
|
artists: [{ name: cleanArtistName(track.artist_name) }], // Proper Spotify format
|
||||||
album: {
|
album: {
|
||||||
name: track.album_name,
|
name: track.album_name,
|
||||||
images: track.album_cover_url ? [{ url: track.album_cover_url }] : []
|
images: track.album_cover_url ? [{ url: track.album_cover_url }] : []
|
||||||
|
|
@ -33279,23 +33301,23 @@ async function rehydrateDiscoverDownloadModal(playlistId) {
|
||||||
if (track.artists && Array.isArray(track.artists)) {
|
if (track.artists && Array.isArray(track.artists)) {
|
||||||
artistsArray = track.artists.map(artist => {
|
artistsArray = track.artists.map(artist => {
|
||||||
if (typeof artist === 'string') {
|
if (typeof artist === 'string') {
|
||||||
return {name: artist};
|
return { name: artist };
|
||||||
} else if (artist && artist.name) {
|
} else if (artist && artist.name) {
|
||||||
return {name: artist.name};
|
return { name: artist.name };
|
||||||
} else {
|
} else {
|
||||||
return {name: String(artist || 'Unknown Artist')};
|
return { name: String(artist || 'Unknown Artist') };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (track.artists && typeof track.artists === 'string') {
|
} else if (track.artists && typeof track.artists === 'string') {
|
||||||
artistsArray = [{name: track.artists}];
|
artistsArray = [{ name: track.artists }];
|
||||||
} else {
|
} else {
|
||||||
artistsArray = [{name: 'Unknown Artist'}];
|
artistsArray = [{ name: 'Unknown Artist' }];
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: track.id,
|
id: track.id,
|
||||||
name: track.name,
|
name: track.name,
|
||||||
artists: artistsArray,
|
artists: artistsArray,
|
||||||
album: track.album || {name: 'Unknown Album', images: []},
|
album: track.album || { name: 'Unknown Album', images: [] },
|
||||||
duration_ms: track.duration_ms || 0,
|
duration_ms: track.duration_ms || 0,
|
||||||
external_urls: track.external_urls || {}
|
external_urls: track.external_urls || {}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue