// BEATPORT REBUILD SLIDER FUNCTIONALITY // ================================= let beatportRebuildSliderState = { currentSlide: 0, totalSlides: 4, autoPlayInterval: null, autoPlayDelay: 5000 }; /** * Initialize the beatport rebuild slider functionality */ function initializeBeatportRebuildSlider() { console.log('πŸ”„ Initializing beatport rebuild slider...'); const slider = document.getElementById('beatport-rebuild-slider'); if (!slider) { console.warn('Beatport rebuild slider not found'); return; } // Check if already initialized to prevent duplicate event listeners if (slider.dataset.initialized === 'true') { console.log('Beatport rebuild slider already initialized, skipping...'); startBeatportRebuildSliderAutoPlay(); // Just restart autoplay return; } // Mark as initialized slider.dataset.initialized = 'true'; // Load real Beatport data first loadBeatportHeroTracks(); console.log('βœ… Beatport rebuild slider initialized successfully'); } /** * Load real Beatport hero tracks and populate the slider */ async function loadBeatportHeroTracks() { console.log('🎯 Loading real Beatport hero tracks...'); try { const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/hero-tracks', signal ? { signal } : undefined); const data = await response.json(); if (data.success && data.tracks && data.tracks.length > 0) { console.log(`βœ… Loaded ${data.tracks.length} Beatport tracks`); populateBeatportSlider(data.tracks); } else { console.warn('❌ No tracks received from Beatport API, using placeholder data'); setupBeatportSliderWithPlaceholders(); } } catch (error) { if (error && error.name === 'AbortError') return; console.error('❌ Error loading Beatport tracks:', error); setupBeatportSliderWithPlaceholders(); } } /** * Populate the slider with real Beatport track data */ function populateBeatportSlider(tracks) { const sliderTrack = document.getElementById('beatport-rebuild-slider-track'); const indicatorsContainer = document.querySelector('.beatport-rebuild-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.warn('Slider elements not found'); return; } // Clear existing content sliderTrack.innerHTML = ''; indicatorsContainer.innerHTML = ''; // Update state beatportRebuildSliderState.totalSlides = tracks.length; beatportRebuildSliderState.currentSlide = 0; // Generate slides HTML tracks.forEach((track, index) => { const slideHtml = `

${track.title}

${track.artist}

New on Beatport

`; sliderTrack.insertAdjacentHTML('beforeend', slideHtml); // Add indicator const indicatorHtml = ``; indicatorsContainer.insertAdjacentHTML('beforeend', indicatorHtml); }); // Now set up all the functionality setupBeatportSliderFunctionality(); // Add individual click handlers for each slide (like top 10 releases pattern) setupHeroSliderIndividualClickHandlers(tracks); console.log(`βœ… Populated slider with ${tracks.length} real Beatport tracks`); } /** * Set up individual click handlers for hero slider slides (like top 10 releases) */ function setupHeroSliderIndividualClickHandlers(tracks) { const slides = document.querySelectorAll('.beatport-rebuild-slide[data-url]'); slides.forEach((slide, index) => { const releaseUrl = slide.getAttribute('data-url'); if (releaseUrl && releaseUrl !== '#' && releaseUrl !== '') { // Create release data object from the track data (similar to top 10 releases) const track = tracks[index]; if (track) { const releaseData = { url: releaseUrl, title: track.title || 'Unknown Title', artist: track.artist || 'Unknown Artist', label: track.label || 'Unknown Label', image_url: track.image_url || '' }; // Add click handler that mimics the top 10 releases behavior slide.addEventListener('click', (event) => { // Prevent navigation button clicks from triggering this if (event.target.closest('.beatport-rebuild-nav-btn') || event.target.closest('.beatport-rebuild-indicator')) { return; } console.log(`🎯 Hero slider slide clicked: ${releaseData.title} by ${releaseData.artist}`); handleBeatportReleaseCardClick(slide, releaseData); }); slide.style.cursor = 'pointer'; } } }); console.log(`βœ… Set up individual click handlers for ${slides.length} hero slider slides`); } /** * Set up placeholder data if API fails */ function setupBeatportSliderWithPlaceholders() { console.log('πŸ”„ Setting up slider with placeholder data...'); // The HTML already has placeholder slides, just set up functionality setupBeatportSliderFunctionality(); } /** * Set up all slider functionality after content is loaded */ function setupBeatportSliderFunctionality() { // Set up navigation buttons setupBeatportRebuildSliderNavigation(); // Set up indicators setupBeatportRebuildSliderIndicators(); // Start auto-play startBeatportRebuildSliderAutoPlay(); // Set up pause on hover setupBeatportRebuildSliderHoverPause(); } /** * Set up navigation button functionality */ function setupBeatportRebuildSliderNavigation() { const prevBtn = document.getElementById('beatport-rebuild-prev-btn'); const nextBtn = document.getElementById('beatport-rebuild-next-btn'); if (prevBtn) { prevBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Previous button clicked, current slide:', beatportRebuildSliderState.currentSlide); goToBeatportRebuildSlide(beatportRebuildSliderState.currentSlide - 1); resetBeatportRebuildSliderAutoPlay(); }); } if (nextBtn) { nextBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Next button clicked, current slide:', beatportRebuildSliderState.currentSlide); goToBeatportRebuildSlide(beatportRebuildSliderState.currentSlide + 1); resetBeatportRebuildSliderAutoPlay(); }); } } /** * Set up indicator functionality */ function setupBeatportRebuildSliderIndicators() { const indicators = document.querySelectorAll('.beatport-rebuild-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); goToBeatportRebuildSlide(index); resetBeatportRebuildSliderAutoPlay(); }); }); } /** * Navigate to a specific slide */ function goToBeatportRebuildSlide(slideIndex) { console.log('goToBeatportRebuildSlide called with:', slideIndex, 'current:', beatportRebuildSliderState.currentSlide); // Wrap around if out of bounds if (slideIndex < 0) { slideIndex = beatportRebuildSliderState.totalSlides - 1; } else if (slideIndex >= beatportRebuildSliderState.totalSlides) { slideIndex = 0; } console.log('After wrapping, slideIndex:', slideIndex); // Update current slide beatportRebuildSliderState.currentSlide = slideIndex; // Update slide visibility const slides = document.querySelectorAll('.beatport-rebuild-slide'); slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('.beatport-rebuild-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log('Slide updated to:', beatportRebuildSliderState.currentSlide); } /** * Start auto-play functionality */ function startBeatportRebuildSliderAutoPlay() { if (beatportRebuildSliderState.autoPlayInterval) { clearInterval(beatportRebuildSliderState.autoPlayInterval); } beatportRebuildSliderState.autoPlayInterval = setInterval(() => { goToBeatportRebuildSlide(beatportRebuildSliderState.currentSlide + 1); }, beatportRebuildSliderState.autoPlayDelay); } /** * Reset auto-play timer */ function resetBeatportRebuildSliderAutoPlay() { startBeatportRebuildSliderAutoPlay(); } /** * Set up hover pause functionality */ function setupBeatportRebuildSliderHoverPause() { const sliderContainer = document.querySelector('.beatport-rebuild-slider-container'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (beatportRebuildSliderState.autoPlayInterval) { clearInterval(beatportRebuildSliderState.autoPlayInterval); } }); sliderContainer.addEventListener('mouseleave', () => { startBeatportRebuildSliderAutoPlay(); }); } } /** * Clean up beatport rebuild slider when switching away */ function cleanupBeatportRebuildSlider() { if (beatportRebuildSliderState.autoPlayInterval) { clearInterval(beatportRebuildSliderState.autoPlayInterval); beatportRebuildSliderState.autoPlayInterval = null; } } // =================================== // BEATPORT NEW RELEASES SLIDER // =================================== // State management for new releases slider (copied from hero slider) let beatportReleasesSliderState = { currentSlide: 0, totalSlides: 0, autoPlayInterval: null, autoPlayDelay: 8000, isInitialized: false }; /** * Initialize the beatport new releases slider functionality (based on hero slider) */ function initializeBeatportReleasesSlider() { console.log('πŸ†• Initializing beatport new releases slider...'); const slider = document.getElementById('beatport-releases-slider'); if (!slider) { console.warn('Beatport releases slider not found'); return; } // Prevent double initialization if (slider.dataset.initialized === 'true') { console.log('Releases slider already initialized'); return; } const sliderTrack = document.getElementById('beatport-releases-slider-track'); const indicatorsContainer = document.getElementById('beatport-releases-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.warn('Releases slider elements not found'); return; } // Load data and initialize loadBeatportNewReleases().then(success => { if (success) { setupBeatportReleasesSliderNavigation(); setupBeatportReleasesSliderIndicators(); setupBeatportReleasesSliderHoverPause(); startBeatportReleasesSliderAutoPlay(); slider.dataset.initialized = 'true'; beatportReleasesSliderState.isInitialized = true; console.log('βœ… New releases slider initialized successfully'); } }); } /** * Load new releases data from API */ async function loadBeatportNewReleases() { try { console.log('πŸ“‘ Fetching new releases data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/new-releases', signal ? { signal } : undefined); const data = await response.json(); if (data.success && data.releases && data.releases.length > 0) { console.log(`πŸ“€ Loaded ${data.releases.length} releases`); populateBeatportReleasesSlider(data.releases); return true; } else { console.error('Failed to load releases:', data.error || 'No releases found'); showBeatportReleasesError(data.error || 'No releases available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('Error loading new releases:', error); showBeatportReleasesError('Failed to load releases'); return false; } } /** * Populate the releases slider with data (based on hero slider) */ function populateBeatportReleasesSlider(releases) { const sliderTrack = document.getElementById('beatport-releases-slider-track'); const indicatorsContainer = document.getElementById('beatport-releases-slider-indicators'); if (!sliderTrack || !indicatorsContainer) return; // Calculate slides needed (10 cards per slide) const cardsPerSlide = 10; const totalSlides = Math.ceil(releases.length / cardsPerSlide); // Clear existing content sliderTrack.innerHTML = ''; indicatorsContainer.innerHTML = ''; // Update state beatportReleasesSliderState.totalSlides = totalSlides; beatportReleasesSliderState.currentSlide = 0; console.log(`🎯 Creating ${totalSlides} slides with ${cardsPerSlide} cards each`); // Generate slides HTML (similar to hero slider) for (let slideIndex = 0; slideIndex < totalSlides; slideIndex++) { const startIndex = slideIndex * cardsPerSlide; const endIndex = Math.min(startIndex + cardsPerSlide, releases.length); const slideReleases = releases.slice(startIndex, endIndex); // Create grid HTML for this slide let gridHtml = ''; for (let i = 0; i < cardsPerSlide; i++) { if (i < slideReleases.length) { const release = slideReleases[i]; gridHtml += `
${release.image_url ? `${release.title}` : ''}
${release.title}
${release.artist}
${release.label}
`; } else { // Placeholder card gridHtml += `
πŸ“€
More Releases
Coming Soon
Beatport
`; } } const slideHtml = `
${gridHtml}
`; sliderTrack.innerHTML += slideHtml; // Create indicator const indicatorHtml = ``; indicatorsContainer.innerHTML += indicatorHtml; } console.log(`βœ… Created ${totalSlides} slides for releases slider`); // Add click handlers for individual release discovery (matching Top 10 Releases pattern) const releaseCards = sliderTrack.querySelectorAll('.beatport-release-card[data-url]:not(.beatport-release-placeholder)'); releaseCards.forEach((card) => { const releaseUrl = card.getAttribute('data-url'); if (releaseUrl && releaseUrl !== '#') { // Find the corresponding release data const releaseData = releases.find(release => release.url === releaseUrl); if (releaseData) { card.addEventListener('click', () => handleBeatportReleaseCardClick(card, releaseData)); card.style.cursor = 'pointer'; } } }); } /** * Set up navigation functionality (copied from hero slider) */ function setupBeatportReleasesSliderNavigation() { const prevBtn = document.getElementById('beatport-releases-prev-btn'); const nextBtn = document.getElementById('beatport-releases-next-btn'); if (prevBtn) { // Clone button to remove all existing event listeners const newPrevBtn = prevBtn.cloneNode(true); prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn); newPrevBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Previous releases button clicked, current slide:', beatportReleasesSliderState.currentSlide); goToBeatportReleasesSlide(beatportReleasesSliderState.currentSlide - 1); resetBeatportReleasesSliderAutoPlay(); }); } if (nextBtn) { // Clone button to remove all existing event listeners const newNextBtn = nextBtn.cloneNode(true); nextBtn.parentNode.replaceChild(newNextBtn, nextBtn); newNextBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Next releases button clicked, current slide:', beatportReleasesSliderState.currentSlide); goToBeatportReleasesSlide(beatportReleasesSliderState.currentSlide + 1); resetBeatportReleasesSliderAutoPlay(); }); } } /** * Set up indicator functionality (copied from hero slider) */ function setupBeatportReleasesSliderIndicators() { const indicators = document.querySelectorAll('.beatport-releases-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { goToBeatportReleasesSlide(index); resetBeatportReleasesSliderAutoPlay(); }); }); } /** * Navigate to a specific slide (copied from hero slider) */ function goToBeatportReleasesSlide(slideIndex) { console.log('goToBeatportReleasesSlide called with:', slideIndex, 'current:', beatportReleasesSliderState.currentSlide); // Wrap around if out of bounds if (slideIndex < 0) { slideIndex = beatportReleasesSliderState.totalSlides - 1; } else if (slideIndex >= beatportReleasesSliderState.totalSlides) { slideIndex = 0; } console.log('After wrapping, slideIndex:', slideIndex); // Update current slide beatportReleasesSliderState.currentSlide = slideIndex; // Update slide visibility const slides = document.querySelectorAll('.beatport-releases-slide'); slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('.beatport-releases-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log('Releases slide updated to:', beatportReleasesSliderState.currentSlide); } /** * Start auto-play functionality (copied from hero slider) */ function startBeatportReleasesSliderAutoPlay() { if (beatportReleasesSliderState.autoPlayInterval) { clearInterval(beatportReleasesSliderState.autoPlayInterval); } beatportReleasesSliderState.autoPlayInterval = setInterval(() => { goToBeatportReleasesSlide(beatportReleasesSliderState.currentSlide + 1); }, beatportReleasesSliderState.autoPlayDelay); } /** * Reset auto-play timer (copied from hero slider) */ function resetBeatportReleasesSliderAutoPlay() { startBeatportReleasesSliderAutoPlay(); } /** * Set up hover pause functionality (copied from hero slider) */ function setupBeatportReleasesSliderHoverPause() { const sliderContainer = document.querySelector('.beatport-releases-slider-container'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (beatportReleasesSliderState.autoPlayInterval) { clearInterval(beatportReleasesSliderState.autoPlayInterval); beatportReleasesSliderState.autoPlayInterval = null; } }); sliderContainer.addEventListener('mouseleave', () => { startBeatportReleasesSliderAutoPlay(); }); } } /** * Show error state */ function showBeatportReleasesError(errorMessage) { const sliderTrack = document.getElementById('beatport-releases-slider-track'); if (!sliderTrack) return; sliderTrack.innerHTML = `

❌ Error Loading Releases

${errorMessage}

`; } /** * Clean up releases slider when switching away (copied from hero slider) */ function cleanupBeatportReleasesSlider() { if (beatportReleasesSliderState.autoPlayInterval) { clearInterval(beatportReleasesSliderState.autoPlayInterval); beatportReleasesSliderState.autoPlayInterval = null; } } // =================================== // BEATPORT HYPE PICKS SLIDER // =================================== // Hype Picks Slider State let beatportHypePicksSliderState = { currentSlide: 0, totalSlides: 0, autoPlayInterval: null, autoPlayDelay: 4000, isInitialized: false }; /** * Initialize the beatport hype picks slider functionality (based on releases slider) */ function initializeBeatportHypePicksSlider() { console.log('πŸ”₯ Initializing beatport hype picks slider...'); const slider = document.getElementById('beatport-hype-picks-slider'); if (!slider) { console.warn('Beatport hype picks slider not found'); return; } // Check if already initialized if (beatportHypePicksSliderState.isInitialized) { console.log('Beatport hype picks slider already initialized, skipping...'); startBeatportHypePicksSliderAutoPlay(); // Just restart autoplay return; } // Mark as initialized beatportHypePicksSliderState.isInitialized = true; // Reset state beatportHypePicksSliderState.currentSlide = 0; beatportHypePicksSliderState.totalSlides = 0; // Load data and initialize loadBeatportHypePicks().then(success => { if (success) { setupBeatportHypePicksSliderNavigation(); setupBeatportHypePicksSliderIndicators(); setupBeatportHypePicksSliderHoverPause(); startBeatportHypePicksSliderAutoPlay(); } }); console.log('βœ… Beatport hype picks slider initialized successfully'); } /** * Load hype picks data from API */ async function loadBeatportHypePicks() { try { console.log('πŸ”₯ Fetching hype picks data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/hype-picks', signal ? { signal } : undefined); const data = await response.json(); if (data.success && data.releases && data.releases.length > 0) { console.log(`πŸ”₯ Loaded ${data.releases.length} hype picks releases`); populateBeatportHypePicksSlider(data.releases); return true; } else { console.error('Failed to load hype picks:', data.error || 'No hype picks found'); showBeatportHypePicksError(data.error || 'No hype picks available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('Error loading hype picks:', error); showBeatportHypePicksError('Failed to load hype picks'); return false; } } /** * Populate the hype picks slider with data (based on releases slider) */ function populateBeatportHypePicksSlider(releases) { const sliderTrack = document.getElementById('beatport-hype-picks-slider-track'); const indicatorsContainer = document.getElementById('beatport-hype-picks-slider-indicators'); if (!sliderTrack || !indicatorsContainer) return; // Clear existing content sliderTrack.innerHTML = ''; indicatorsContainer.innerHTML = ''; // Group releases into slides (10 releases per slide in 5x2 grid) const releasesPerSlide = 10; const slides = []; for (let i = 0; i < releases.length; i += releasesPerSlide) { slides.push(releases.slice(i, i + releasesPerSlide)); } console.log(`πŸ”₯ Hype Picks: Got ${releases.length} releases, creating ${slides.length} slides`); beatportHypePicksSliderState.totalSlides = slides.length; beatportHypePicksSliderState.currentSlide = 0; // Create slides slides.forEach((slideReleases, slideIndex) => { const slideHtml = `
${slideReleases.map(release => createBeatportHypePickCard(release)).join('')} ${slideReleases.length < releasesPerSlide ? Array(releasesPerSlide - slideReleases.length).fill(0).map(() => `
πŸ”₯
` ).join('') : '' }
`; sliderTrack.insertAdjacentHTML('beforeend', slideHtml); console.log(`πŸ”₯ Created slide ${slideIndex + 1}/${slides.length} with ${slideReleases.length} releases`); // Create indicator const indicatorHtml = ``; indicatorsContainer.insertAdjacentHTML('beforeend', indicatorHtml); }); // Add click handlers to track cards setupBeatportHypePickCardHandlers(); } /** * Create a hype pick card HTML (for release cards, same as new releases) */ function createBeatportHypePickCard(release) { const artworkUrl = release.image_url || ''; const bgStyle = artworkUrl ? `style="--card-bg-image: url('${artworkUrl}')"` : ''; return `
${artworkUrl ? `${release.title || 'Release'}` : ''}
${release.title || 'Unknown Title'}
${release.artist || 'Unknown Artist'}
${release.label || 'Hype Pick'}
`; } /** * Setup navigation for hype picks slider (same pattern as releases) */ function setupBeatportHypePicksSliderNavigation() { const prevBtn = document.getElementById('beatport-hype-picks-prev-btn'); const nextBtn = document.getElementById('beatport-hype-picks-next-btn'); if (prevBtn) { // Clone button to remove all existing event listeners const newPrevBtn = prevBtn.cloneNode(true); prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn); newPrevBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Previous hype picks button clicked, current slide:', beatportHypePicksSliderState.currentSlide); goToBeatportHypePicksSlide(beatportHypePicksSliderState.currentSlide - 1); resetBeatportHypePicksSliderAutoPlay(); }); } if (nextBtn) { // Clone button to remove all existing event listeners const newNextBtn = nextBtn.cloneNode(true); nextBtn.parentNode.replaceChild(newNextBtn, nextBtn); newNextBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Next hype picks button clicked, current slide:', beatportHypePicksSliderState.currentSlide); goToBeatportHypePicksSlide(beatportHypePicksSliderState.currentSlide + 1); resetBeatportHypePicksSliderAutoPlay(); }); } } /** * Setup indicators for hype picks slider */ function setupBeatportHypePicksSliderIndicators() { const indicators = document.querySelectorAll('.beatport-hype-picks-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { goToBeatportHypePicksSlide(index); resetBeatportHypePicksSliderAutoPlay(); }); }); } /** * Navigate to specific slide */ function goToBeatportHypePicksSlide(slideIndex) { console.log('goToBeatportHypePicksSlide called with:', slideIndex, 'current:', beatportHypePicksSliderState.currentSlide); // Handle wrap around if (slideIndex < 0) { slideIndex = beatportHypePicksSliderState.totalSlides - 1; } else if (slideIndex >= beatportHypePicksSliderState.totalSlides) { slideIndex = 0; } // Update current slide beatportHypePicksSliderState.currentSlide = slideIndex; // Update slides const slides = document.querySelectorAll('.beatport-hype-picks-slide'); slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('.beatport-hype-picks-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log('Slide updated to:', beatportHypePicksSliderState.currentSlide); } /** * Start auto-play for hype picks slider */ function startBeatportHypePicksSliderAutoPlay() { if (beatportHypePicksSliderState.autoPlayInterval) { clearInterval(beatportHypePicksSliderState.autoPlayInterval); } beatportHypePicksSliderState.autoPlayInterval = setInterval(() => { goToBeatportHypePicksSlide(beatportHypePicksSliderState.currentSlide + 1); }, beatportHypePicksSliderState.autoPlayDelay); console.log('πŸ”₯ Hype picks slider autoplay started'); } /** * Reset auto-play for hype picks slider */ function resetBeatportHypePicksSliderAutoPlay() { startBeatportHypePicksSliderAutoPlay(); } /** * Setup hover pause for hype picks slider */ function setupBeatportHypePicksSliderHoverPause() { const sliderContainer = document.querySelector('.beatport-hype-picks-slider-container'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (beatportHypePicksSliderState.autoPlayInterval) { clearInterval(beatportHypePicksSliderState.autoPlayInterval); } }); sliderContainer.addEventListener('mouseleave', () => { startBeatportHypePicksSliderAutoPlay(); }); } } /** * Setup click handlers for hype pick cards */ function setupBeatportHypePickCardHandlers() { const cards = document.querySelectorAll('.beatport-hype-pick-card:not(.beatport-hype-pick-placeholder)'); cards.forEach(card => { const releaseUrl = card.getAttribute('data-url'); if (releaseUrl && releaseUrl !== '#' && releaseUrl !== '') { // Extract release data from the card elements const titleElement = card.querySelector('.beatport-hype-pick-title'); const artistElement = card.querySelector('.beatport-hype-pick-artist'); const labelElement = card.querySelector('.beatport-hype-pick-label'); const imageElement = card.querySelector('.beatport-hype-pick-artwork img'); const releaseData = { url: releaseUrl, title: titleElement ? titleElement.textContent.trim() : 'Unknown Title', artist: artistElement ? artistElement.textContent.trim() : 'Unknown Artist', label: labelElement ? labelElement.textContent.trim() : 'Unknown Label', image_url: imageElement ? imageElement.src : '' }; card.addEventListener('click', () => handleBeatportReleaseCardClick(card, releaseData)); card.style.cursor = 'pointer'; } }); } /** * Show error state for hype picks slider */ function showBeatportHypePicksError(errorMessage) { const sliderTrack = document.getElementById('beatport-hype-picks-slider-track'); if (sliderTrack) { sliderTrack.innerHTML = `

❌ Error Loading Hype Picks

${errorMessage}

`; } } /** * Clean up hype picks slider when switching away */ function cleanupBeatportHypePicksSlider() { if (beatportHypePicksSliderState.autoPlayInterval) { clearInterval(beatportHypePicksSliderState.autoPlayInterval); beatportHypePicksSliderState.autoPlayInterval = null; } } // =================================== // BEATPORT FEATURED CHARTS SLIDER // =================================== // State management for featured charts slider (copied from releases slider) let beatportChartsSliderState = { currentSlide: 0, totalSlides: 0, autoPlayInterval: null, autoPlayDelay: 10000, // Slightly longer auto-play for charts isInitialized: false }; /** * Initialize the beatport featured charts slider functionality (based on releases slider) */ function initializeBeatportChartsSlider() { console.log('πŸ”₯ Initializing beatport featured charts slider...'); const slider = document.getElementById('beatport-charts-slider'); if (!slider) { console.warn('Beatport charts slider not found'); return; } // Prevent double initialization if (slider.dataset.initialized === 'true') { console.log('Charts slider already initialized'); return; } const sliderTrack = document.getElementById('beatport-charts-slider-track'); const indicatorsContainer = document.getElementById('beatport-charts-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.warn('Charts slider elements not found'); return; } // Load data and initialize loadBeatportFeaturedCharts().then(success => { if (success) { setupBeatportChartsSliderNavigation(); setupBeatportChartsSliderIndicators(); setupBeatportChartsSliderHoverPause(); startBeatportChartsSliderAutoPlay(); slider.dataset.initialized = 'true'; beatportChartsSliderState.isInitialized = true; console.log('βœ… Featured charts slider initialized successfully'); } }); } /** * Load featured charts data from API */ async function loadBeatportFeaturedCharts() { try { console.log('πŸ“Š Loading featured charts data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/featured-charts', signal ? { signal } : undefined); const data = await response.json(); if (data.success && data.charts && data.charts.length > 0) { console.log(`πŸ“ˆ Loaded ${data.charts.length} featured charts`); createBeatportChartsSlides(data.charts); return true; } else { console.warn('No featured charts data available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('❌ Error loading featured charts:', error); return false; } } /** * Create chart slides with grid layout (copied from releases slider) */ function createBeatportChartsSlides(charts) { const sliderTrack = document.getElementById('beatport-charts-slider-track'); const indicatorsContainer = document.getElementById('beatport-charts-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.error('Charts slider elements not found'); return; } const cardsPerSlide = 10; // 5x2 grid const totalSlides = Math.ceil(charts.length / cardsPerSlide); // Clear existing content sliderTrack.innerHTML = ''; indicatorsContainer.innerHTML = ''; // Update state beatportChartsSliderState.totalSlides = totalSlides; beatportChartsSliderState.currentSlide = 0; console.log(`🎯 Creating ${totalSlides} chart slides with ${cardsPerSlide} cards each`); // Generate slides HTML for (let slideIndex = 0; slideIndex < totalSlides; slideIndex++) { const startIndex = slideIndex * cardsPerSlide; const endIndex = Math.min(startIndex + cardsPerSlide, charts.length); const slideCharts = charts.slice(startIndex, endIndex); // Create grid HTML for this slide const gridHtml = slideCharts.map(chart => { const bgImageStyle = chart.image ? `--chart-bg-image: url('${chart.image}')` : ''; return `
${chart.name || 'Unknown Chart'}
${chart.creator || 'Unknown Creator'}
`; }).join(''); // Create slide HTML const slideHtml = `
${gridHtml}
`; sliderTrack.innerHTML += slideHtml; // Create indicator const indicatorHtml = ``; indicatorsContainer.innerHTML += indicatorHtml; } console.log(`βœ… Created ${totalSlides} chart slides`); // Add click handlers for individual chart discovery (matching chart pattern) const chartCards = sliderTrack.querySelectorAll('.beatport-chart-card[data-url]'); chartCards.forEach((card) => { const chartUrl = card.getAttribute('data-url'); if (chartUrl && chartUrl !== '') { // Find the corresponding chart data const chartData = charts.find(chart => chart.url === chartUrl); if (chartData) { card.addEventListener('click', () => handleBeatportChartCardClick(card, chartData)); card.style.cursor = 'pointer'; } } }); } /** * Set up navigation functionality (copied from releases slider with button cloning) */ function setupBeatportChartsSliderNavigation() { const prevBtn = document.getElementById('beatport-charts-prev-btn'); const nextBtn = document.getElementById('beatport-charts-next-btn'); if (prevBtn) { // Clone button to remove all existing event listeners const newPrevBtn = prevBtn.cloneNode(true); prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn); newPrevBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Previous charts button clicked, current slide:', beatportChartsSliderState.currentSlide); goToBeatportChartsSlide(beatportChartsSliderState.currentSlide - 1); resetBeatportChartsSliderAutoPlay(); }); } if (nextBtn) { // Clone button to remove all existing event listeners const newNextBtn = nextBtn.cloneNode(true); nextBtn.parentNode.replaceChild(newNextBtn, nextBtn); newNextBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Next charts button clicked, current slide:', beatportChartsSliderState.currentSlide); goToBeatportChartsSlide(beatportChartsSliderState.currentSlide + 1); resetBeatportChartsSliderAutoPlay(); }); } } /** * Set up indicator functionality (copied from releases slider) */ function setupBeatportChartsSliderIndicators() { const indicators = document.querySelectorAll('.beatport-charts-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { goToBeatportChartsSlide(index); resetBeatportChartsSliderAutoPlay(); }); }); } /** * Navigate to a specific slide (copied from releases slider) */ function goToBeatportChartsSlide(slideIndex) { console.log('goToBeatportChartsSlide called with:', slideIndex, 'current:', beatportChartsSliderState.currentSlide); // Wrap around if out of bounds if (slideIndex < 0) { slideIndex = beatportChartsSliderState.totalSlides - 1; } else if (slideIndex >= beatportChartsSliderState.totalSlides) { slideIndex = 0; } console.log('After wrapping, slideIndex:', slideIndex); // Update current slide beatportChartsSliderState.currentSlide = slideIndex; // Update slide visibility const slides = document.querySelectorAll('.beatport-charts-slide'); slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('.beatport-charts-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log('Charts slide updated to:', beatportChartsSliderState.currentSlide); } /** * Start auto-play functionality (copied from releases slider) */ function startBeatportChartsSliderAutoPlay() { if (beatportChartsSliderState.autoPlayInterval) { clearInterval(beatportChartsSliderState.autoPlayInterval); } beatportChartsSliderState.autoPlayInterval = setInterval(() => { goToBeatportChartsSlide(beatportChartsSliderState.currentSlide + 1); }, beatportChartsSliderState.autoPlayDelay); } /** * Reset auto-play timer (copied from releases slider) */ function resetBeatportChartsSliderAutoPlay() { startBeatportChartsSliderAutoPlay(); } /** * Set up hover pause functionality (copied from releases slider) */ function setupBeatportChartsSliderHoverPause() { const sliderContainer = document.querySelector('.beatport-charts-slider-container'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (beatportChartsSliderState.autoPlayInterval) { clearInterval(beatportChartsSliderState.autoPlayInterval); beatportChartsSliderState.autoPlayInterval = null; } }); sliderContainer.addEventListener('mouseleave', () => { startBeatportChartsSliderAutoPlay(); }); } } /** * Clean up charts slider when switching away (copied from releases slider) */ function cleanupBeatportChartsSlider() { if (beatportChartsSliderState.autoPlayInterval) { clearInterval(beatportChartsSliderState.autoPlayInterval); beatportChartsSliderState.autoPlayInterval = null; } } // =================================== // BEATPORT DJ CHARTS SLIDER // =================================== // State management for DJ charts slider (3 cards per slide) let beatportDJSliderState = { currentSlide: 0, totalSlides: 0, autoPlayInterval: null, autoPlayDelay: 12000, // Longer auto-play for DJ charts isInitialized: false }; /** * Initialize the beatport DJ charts slider functionality (based on charts slider) */ function initializeBeatportDJSlider() { console.log('🎧 Initializing beatport DJ charts slider...'); const slider = document.getElementById('beatport-dj-slider'); if (!slider) { console.warn('Beatport DJ slider not found'); return; } // Prevent double initialization if (slider.dataset.initialized === 'true') { console.log('DJ slider already initialized'); return; } const sliderTrack = document.getElementById('beatport-dj-slider-track'); const indicatorsContainer = document.getElementById('beatport-dj-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.warn('DJ slider elements not found'); return; } // Load data and initialize loadBeatportDJCharts().then(success => { if (success) { setupBeatportDJSliderNavigation(); setupBeatportDJSliderIndicators(); setupBeatportDJSliderHoverPause(); startBeatportDJSliderAutoPlay(); slider.dataset.initialized = 'true'; beatportDJSliderState.isInitialized = true; console.log('βœ… DJ charts slider initialized successfully'); } }); } /** * Load DJ charts data from API */ async function loadBeatportDJCharts() { try { console.log('🎧 Loading DJ charts data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/dj-charts', signal ? { signal } : undefined); const data = await response.json(); if (data.success && data.charts && data.charts.length > 0) { console.log(`πŸ“ˆ Loaded ${data.charts.length} DJ charts`); createBeatportDJSlides(data.charts); return true; } else { console.warn('No DJ charts data available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('❌ Error loading DJ charts:', error); return false; } } /** * Create DJ chart slides with 3 cards per slide layout */ function createBeatportDJSlides(charts) { const sliderTrack = document.getElementById('beatport-dj-slider-track'); const indicatorsContainer = document.getElementById('beatport-dj-slider-indicators'); if (!sliderTrack || !indicatorsContainer) { console.error('DJ slider elements not found'); return; } const cardsPerSlide = 3; // 3 cards per slide for DJ charts const totalSlides = Math.ceil(charts.length / cardsPerSlide); // Clear existing content sliderTrack.innerHTML = ''; indicatorsContainer.innerHTML = ''; // Update state beatportDJSliderState.totalSlides = totalSlides; beatportDJSliderState.currentSlide = 0; console.log(`🎯 Creating ${totalSlides} DJ chart slides with ${cardsPerSlide} cards each`); // Generate slides HTML for (let slideIndex = 0; slideIndex < totalSlides; slideIndex++) { const startIndex = slideIndex * cardsPerSlide; const endIndex = Math.min(startIndex + cardsPerSlide, charts.length); const slideCharts = charts.slice(startIndex, endIndex); // Create grid HTML for this slide const gridHtml = slideCharts.map(chart => { const bgImageStyle = chart.image ? `--dj-bg-image: url('${chart.image}')` : ''; return `
${chart.name || 'Unknown Chart'}
${chart.creator || 'Unknown Creator'}
`; }).join(''); // Create slide HTML const slideHtml = `
${gridHtml}
`; sliderTrack.innerHTML += slideHtml; // Create indicator const indicatorHtml = ``; indicatorsContainer.innerHTML += indicatorHtml; } console.log(`βœ… Created ${totalSlides} DJ chart slides`); // Add click handlers for individual DJ chart discovery (matching chart pattern) const djChartCards = sliderTrack.querySelectorAll('.beatport-dj-card[data-url]'); djChartCards.forEach((card) => { const chartUrl = card.getAttribute('data-url'); if (chartUrl && chartUrl !== '') { // Find the corresponding chart data const chartData = charts.find(chart => chart.url === chartUrl); if (chartData) { card.addEventListener('click', () => handleBeatportDJChartCardClick(card, chartData)); card.style.cursor = 'pointer'; } } }); } /** * Set up navigation functionality (copied from charts slider with button cloning) */ function setupBeatportDJSliderNavigation() { const prevBtn = document.getElementById('beatport-dj-prev-btn'); const nextBtn = document.getElementById('beatport-dj-next-btn'); if (prevBtn) { // Clone button to remove all existing event listeners const newPrevBtn = prevBtn.cloneNode(true); prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn); newPrevBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Previous DJ button clicked, current slide:', beatportDJSliderState.currentSlide); goToBeatportDJSlide(beatportDJSliderState.currentSlide - 1); resetBeatportDJSliderAutoPlay(); }); } if (nextBtn) { // Clone button to remove all existing event listeners const newNextBtn = nextBtn.cloneNode(true); nextBtn.parentNode.replaceChild(newNextBtn, nextBtn); newNextBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Next DJ button clicked, current slide:', beatportDJSliderState.currentSlide); goToBeatportDJSlide(beatportDJSliderState.currentSlide + 1); resetBeatportDJSliderAutoPlay(); }); } } /** * Set up indicator functionality (copied from charts slider) */ function setupBeatportDJSliderIndicators() { const indicators = document.querySelectorAll('.beatport-dj-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { goToBeatportDJSlide(index); resetBeatportDJSliderAutoPlay(); }); }); } /** * Navigate to a specific slide (copied from charts slider) */ function goToBeatportDJSlide(slideIndex) { console.log('goToBeatportDJSlide called with:', slideIndex, 'current:', beatportDJSliderState.currentSlide); // Wrap around if out of bounds if (slideIndex < 0) { slideIndex = beatportDJSliderState.totalSlides - 1; } else if (slideIndex >= beatportDJSliderState.totalSlides) { slideIndex = 0; } console.log('After wrapping, slideIndex:', slideIndex); // Update current slide beatportDJSliderState.currentSlide = slideIndex; // Update slide visibility const slides = document.querySelectorAll('.beatport-dj-slide'); slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('.beatport-dj-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log('DJ slide updated to:', beatportDJSliderState.currentSlide); } /** * Start auto-play functionality (copied from charts slider) */ function startBeatportDJSliderAutoPlay() { if (beatportDJSliderState.autoPlayInterval) { clearInterval(beatportDJSliderState.autoPlayInterval); } beatportDJSliderState.autoPlayInterval = setInterval(() => { goToBeatportDJSlide(beatportDJSliderState.currentSlide + 1); }, beatportDJSliderState.autoPlayDelay); } /** * Reset auto-play timer (copied from charts slider) */ function resetBeatportDJSliderAutoPlay() { startBeatportDJSliderAutoPlay(); } /** * Set up hover pause functionality (copied from charts slider) */ function setupBeatportDJSliderHoverPause() { const sliderContainer = document.querySelector('.beatport-dj-slider-container'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (beatportDJSliderState.autoPlayInterval) { clearInterval(beatportDJSliderState.autoPlayInterval); beatportDJSliderState.autoPlayInterval = null; } }); sliderContainer.addEventListener('mouseleave', () => { startBeatportDJSliderAutoPlay(); }); } } /** * Clean up DJ slider when switching away (copied from charts slider) */ function cleanupBeatportDJSlider() { if (beatportDJSliderState.autoPlayInterval) { clearInterval(beatportDJSliderState.autoPlayInterval); beatportDJSliderState.autoPlayInterval = null; } } /** * Load top 10 lists data from API and populate both lists */ async function loadBeatportTop10Lists() { try { console.log('πŸ† Loading top 10 lists data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/homepage/top-10-lists', signal ? { signal } : undefined); const data = await response.json(); if (data.success) { console.log(`🎡 Loaded ${data.beatport_count} Beatport Top 10 + ${data.hype_count} Hype Top 10 tracks`); // Populate both lists populateBeatportTop10List(data.beatport_top10); populateHypeTop10List(data.hype_top10); return true; } else { console.error('Failed to load top 10 lists:', data.error); showTop10ListsError(data.error || 'No data available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('Error loading top 10 lists:', error); showTop10ListsError('Failed to load top 10 lists'); return false; } } /** * Clean track/artist text for proper spacing */ function cleanTrackText(text) { if (!text) return text; // Fix common spacing issues text = text.replace(/([a-z$!@#%&*])([A-Z])/g, '$1 $2'); // Add space between lowercase/symbols and uppercase text = text.replace(/([a-zA-Z]),([a-zA-Z])/g, '$1, $2'); // Add space after comma text = text.replace(/([a-zA-Z])(Mix|Remix|Extended|Version)\b/g, '$1 $2'); // Fix mix types text = text.replace(/\s+/g, ' '); // Collapse multiple spaces text = text.trim(); return text; } /** * Populate Beatport Top 10 list with data */ function populateBeatportTop10List(tracks) { const container = document.getElementById('beatport-top10-list'); if (!container || !tracks || tracks.length === 0) return; // Generate HTML for the tracks let tracksHtml = `

🎡 Beatport Top 10

Most popular tracks on Beatport

`; tracks.forEach((track, index) => { // Clean the text data before injection const cleanTitle = cleanTrackText(track.title || 'Unknown Title'); const cleanArtist = cleanTrackText(track.artist || 'Unknown Artist'); const cleanLabel = cleanTrackText(track.label || 'Unknown Label'); tracksHtml += `
${track.rank || index + 1}
${track.artwork_url ? `${cleanTitle}` : '
🎡
' }

${cleanTitle}

${cleanArtist}

${cleanLabel}

`; }); tracksHtml += '
'; container.innerHTML = tracksHtml; } /** * Populate Hype Top 10 list with data */ function populateHypeTop10List(tracks) { const container = document.getElementById('beatport-hype10-list'); if (!container || !tracks || tracks.length === 0) return; // Generate HTML for the tracks let tracksHtml = `

πŸ”₯ Hype Top 10

Editor's trending picks

`; tracks.forEach((track, index) => { // Clean the text data before injection const cleanTitle = cleanTrackText(track.title || 'Unknown Title'); const cleanArtist = cleanTrackText(track.artist || 'Unknown Artist'); const cleanLabel = cleanTrackText(track.label || 'Unknown Label'); tracksHtml += `
${track.rank || index + 1}
${track.artwork_url ? `${cleanTitle}` : '
πŸ”₯
' }

${cleanTitle}

${cleanArtist}

${cleanLabel}

`; }); tracksHtml += '
'; container.innerHTML = tracksHtml; } /** * Show error message for top 10 lists */ function showTop10ListsError(errorMessage) { const beatportContainer = document.getElementById('beatport-top10-list'); const hypeContainer = document.getElementById('beatport-hype10-list'); const errorHtml = `

❌ Error Loading Data

${errorMessage}

`; if (beatportContainer) beatportContainer.innerHTML = errorHtml; if (hypeContainer) hypeContainer.innerHTML = errorHtml; } /** * Load top 10 releases data from API and populate the list */ async function loadBeatportTop10Releases() { try { console.log('πŸ’Ώ Loading top 10 releases data...'); const signal = getBeatportContentSignal(); const response = await fetch('/api/beatport/homepage/top-10-releases-cards', signal ? { signal } : undefined); const data = await response.json(); if (data.success) { console.log(`πŸ’Ώ Loaded ${data.releases_count} Top 10 Releases`); populateBeatportTop10Releases(data.releases); return true; } else { console.error('Failed to load top 10 releases:', data.error); showTop10ReleasesError(data.error || 'No data available'); return false; } } catch (error) { if (error && error.name === 'AbortError') return false; console.error('Error loading top 10 releases:', error); showTop10ReleasesError('Failed to load top 10 releases'); return false; } } /** * Populate Top 10 Releases list with data */ function populateBeatportTop10Releases(releases) { const container = document.getElementById('beatport-releases-top10-list'); if (!container || !releases || releases.length === 0) return; // Generate HTML for the releases let releasesHtml = `
`; releases.forEach((release, index) => { releasesHtml += `
${release.rank || index + 1}
${release.image_url ? `${release.title}` : '
πŸ’Ώ
' }

${release.title || 'Unknown Title'}

${release.artist || 'Unknown Artist'}

${release.label || 'Unknown Label'}

`; }); releasesHtml += '
'; container.innerHTML = releasesHtml; // Set background images for cards const cards = container.querySelectorAll('.beatport-releases-top10-card[data-bg-image]'); cards.forEach(card => { const bgImage = card.getAttribute('data-bg-image'); if (bgImage) { // Transform image URL from 95x95 to 500x500 for higher quality background const highResImage = bgImage.replace('/image_size/95x95/', '/image_size/500x500/'); card.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.8)), url('${highResImage}')`; card.style.backgroundSize = 'cover'; card.style.backgroundPosition = 'center'; } }); // Add click handlers for individual release discovery const releaseCards = container.querySelectorAll('.beatport-releases-top10-card[data-url]'); releaseCards.forEach((card, index) => { card.addEventListener('click', () => handleBeatportReleaseCardClick(card, releases[index])); card.style.cursor = 'pointer'; }); } /** * Show error message for top 10 releases */ function showTop10ReleasesError(errorMessage) { const container = document.getElementById('beatport-releases-top10-list'); const errorHtml = `

❌ Error Loading Releases

${errorMessage}

`; if (container) container.innerHTML = errorHtml; } /** * Handle click on individual Top 10 Release card - create discovery process for single release */ async function handleBeatportReleaseCardClick(cardElement, release) { if (_beatportModalOpening) return; _beatportModalOpening = true; console.log(`πŸ’Ώ Individual release card clicked: ${release.title} by ${release.artist}`); if (!release.url || release.url === '#') { _beatportModalOpening = false; showToast('No release URL available', 'error'); return; } try { showToast(`Loading ${release.title}...`, 'info'); showLoadingOverlay(`Getting tracks from ${release.title}...`); // Fetch structured release metadata for direct download modal console.log(`🎡 Fetching release metadata: ${release.url}`); const response = await fetch('/api/beatport/release-metadata', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ release_url: release.url }) }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error(data.error || 'No tracks found in this release'); } console.log(`βœ… Got ${data.tracks.length} tracks from ${data.album.name}`); // Format artists as array of strings for compatibility with download modal const formattedTracks = data.tracks.map(track => ({ ...track, artists: track.artists.map(a => typeof a === 'object' ? a.name : a) })); const virtualPlaylistId = `beatport_release_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const playlistName = data.album.name; // Open download modal directly - same as clicking an album on the Artists page await openDownloadMissingModalForArtistAlbum( virtualPlaylistId, playlistName, formattedTracks, data.album, data.artist, false ); // Register Beatport download bubble for releases (albums, EPs, singles) const releaseImage = (data.album.images && data.album.images.length > 0) ? data.album.images[0].url : (release.image_url || ''); registerBeatportDownload(playlistName, releaseImage, virtualPlaylistId); hideLoadingOverlay(); _beatportModalOpening = false; console.log(`βœ… Opened download modal for ${playlistName}`); } catch (error) { console.error(`❌ Error handling release click for ${release.title}:`, error); hideLoadingOverlay(); _beatportModalOpening = false; showToast(`Error loading ${release.title}: ${error.message}`, 'error'); } } /** * Convert scraped Beatport tracks into download-modal-compatible format and open the modal. * Used by all chart/playlist handlers (Top 100, Hype 100, Featured Charts, DJ Charts, genre charts). * Charts open as compilations β€” each track is searched independently on Soulseek. */ // Guard against multiple rapid clicks opening duplicate modals let _beatportModalOpening = false; /** * Enrich tracks via a single batch request to the backend. * Progress is reported via WebSocket (beatport:enrich_progress) and updates the loading overlay. * Returns the enriched tracks array. */ async function _enrichTracksWithProgress(tracks, chartName) { const enrichmentId = `enrich_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`; try { const resp = await fetch('/api/beatport/enrich-tracks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tracks, enrichment_id: enrichmentId }) }); const data = await resp.json(); // Synchronous path β€” all tracks were cached, results returned inline if (data.success && data.tracks) { return data.tracks; } // Async path β€” poll for progress until done if (data.success && data.async) { while (true) { await new Promise(r => setTimeout(r, 800)); try { const progressResp = await fetch(`/api/beatport/enrich-progress/${enrichmentId}?_=${Date.now()}`); const progress = await progressResp.json(); if (!progress.success) break; // Update loading overlay with live progress const overlayText = document.querySelector('#loading-overlay .loading-message'); if (overlayText) { overlayText.textContent = `Fetching track metadata... (${progress.completed}/${progress.total}) ${progress.current_track || ''}`; } if (progress.done) { if (progress.tracks) { return progress.tracks; } console.warn('⚠️ Async enrichment failed:', progress.error); return tracks; } } catch (pollErr) { console.warn('⚠️ Progress poll error:', pollErr); } } } console.warn('⚠️ Enrichment failed, returning original tracks'); return tracks; } catch (e) { console.warn('⚠️ Failed to enrich tracks:', e); return tracks; } } function parseBeatportDuration(raw) { if (!raw) return 0; if (typeof raw === 'string' && raw.includes(':')) { const parts = raw.split(':'); return (parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10)) * 1000 || 0; } return (parseInt(raw, 10) || 0) * 1000; } function openBeatportChartAsDownloadModal(tracks, chartName, chartImage) { // Note: callers already guard against double-clicks via _beatportModalOpening. // Reset the flag here so the modal can open even after fast (cached) enrichment. _beatportModalOpening = false; const albumObj = { id: `beatport_chart_${Date.now()}`, name: chartName, album_type: 'compilation', images: chartImage ? [{ url: chartImage }] : [], total_tracks: tracks.length }; const formattedTracks = tracks.map((track, index) => { // Use per-track release metadata if available (from JSON extraction) const hasRelease = track.release_name && track.release_name.length > 0; const trackAlbum = hasRelease ? { id: `beatport_release_${track.release_id || index}`, name: cleanTrackText(track.release_name), album_type: 'single', images: track.release_image ? [{ url: track.release_image }] : [], release_date: track.release_date || '', total_tracks: 1 } : albumObj; // Combine title + mix_name let trackName = cleanTrackText(track.title || 'Unknown Title'); if (track.mix_name && track.mix_name.toLowerCase() !== 'original mix') { trackName = `${trackName} (${cleanTrackText(track.mix_name)})`; } // Split combined artist string into individual names for proper folder structure const rawArtist = cleanTrackText(track.artist || 'Unknown Artist'); const artistList = rawArtist.includes(',') ? rawArtist.split(',').map(a => a.trim()).filter(a => a) : [rawArtist]; return { id: `beatport_chart_${index}`, name: trackName, artists: artistList, duration_ms: parseBeatportDuration(track.duration), track_number: index + 1, disc_number: 1, album: trackAlbum }; }); const virtualPlaylistId = `beatport_chart_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Compilation artist const artistObj = { id: 'beatport_various', name: 'Various Artists' }; openDownloadMissingModalForArtistAlbum( virtualPlaylistId, chartName, formattedTracks, albumObj, artistObj, false, 'playlist' ); // Register Beatport download bubble registerBeatportDownload(chartName, chartImage, virtualPlaylistId); } /** * Handle click on individual chart card - open download modal directly */ async function handleBeatportChartCardClick(cardElement, chart) { console.log(`πŸ“Š Individual chart card clicked: ${chart.name} by ${chart.creator}`); if (!chart.url || chart.url === '') { showToast('No chart URL available', 'error'); return; } try { const chartName = `${chart.name} - ${chart.creator}`; showToast(`Loading ${chart.name}...`, 'info'); showLoadingOverlay(`Scraping ${chart.name}...`); const response = await fetch('/api/beatport/chart/extract', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chart_url: chart.url, chart_name: `Featured Chart: ${chart.name}`, limit: 100, enrich: false }) }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error('No tracks found in this chart'); } console.log(`βœ… Fetched ${data.tracks.length} raw tracks from ${chart.name}, enriching...`); const enrichedTracks = await _enrichTracksWithProgress(data.tracks, chartName); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, chartName, chart.image); } catch (error) { console.error(`❌ Error handling chart click for ${chart.name}:`, error); hideLoadingOverlay(); showToast(`Error loading ${chart.name}: ${error.message}`, 'error'); } } /** * Handle click on individual DJ chart card - open download modal directly */ async function handleBeatportDJChartCardClick(cardElement, chart) { console.log(`🎧 Individual DJ chart card clicked: ${chart.name} by ${chart.creator}`); if (!chart.url || chart.url === '') { showToast('No DJ chart URL available', 'error'); return; } try { const chartName = `${chart.name} - ${chart.creator}`; showToast(`Loading ${chart.name}...`, 'info'); showLoadingOverlay(`Scraping ${chart.name}...`); const response = await fetch('/api/beatport/chart/extract', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chart_url: chart.url, chart_name: `DJ Chart: ${chart.name}`, limit: 100, enrich: false }) }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error('No tracks found in this DJ chart'); } console.log(`βœ… Fetched ${data.tracks.length} raw tracks from ${chart.name}, enriching...`); const enrichedTracks = await _enrichTracksWithProgress(data.tracks, chartName); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, chartName, chart.image); } catch (error) { console.error(`❌ Error handling DJ chart click for ${chart.name}:`, error); hideLoadingOverlay(); showToast(`Error loading ${chart.name}: ${error.message}`, 'error'); } } /** * Handle click on Beatport Top 100 button - open download modal directly */ async function handleBeatportTop100Click() { if (_beatportModalOpening) return; _beatportModalOpening = true; setTimeout(() => { _beatportModalOpening = false; }, 2000); console.log('πŸ’― Beatport Top 100 button clicked'); try { showLoadingOverlay('Scraping Beatport Top 100...'); // Fetch track list without enrichment (fast) const response = await fetch('/api/beatport/top-100?enrich=false', { method: 'GET' }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error('No tracks found in Beatport Top 100'); } console.log(`βœ… Fetched ${data.tracks.length} tracks, enriching one-by-one...`); // Enrich one-by-one with live progress const enrichedTracks = await _enrichTracksWithProgress(data.tracks, 'Beatport Top 100'); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, 'Beatport Top 100', null); } catch (error) { console.error('❌ Error handling Beatport Top 100 click:', error); hideLoadingOverlay(); showToast(`Error loading Beatport Top 100: ${error.message}`, 'error'); } } /** * Handle click on Hype Top 100 button - open download modal directly */ async function handleHypeTop100Click() { if (_beatportModalOpening) return; _beatportModalOpening = true; setTimeout(() => { _beatportModalOpening = false; }, 2000); console.log('πŸ”₯ Hype Top 100 button clicked'); try { showLoadingOverlay('Scraping Hype Top 100...'); // Fetch track list without enrichment (fast) const response = await fetch('/api/beatport/hype-top-100?enrich=false', { method: 'GET' }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error('No tracks found in Hype Top 100'); } console.log(`βœ… Fetched ${data.tracks.length} tracks, enriching one-by-one...`); // Enrich one-by-one with live progress const enrichedTracks = await _enrichTracksWithProgress(data.tracks, 'Hype Top 100'); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, 'Hype Top 100', null); } catch (error) { console.error('❌ Error handling Hype Top 100 click:', error); hideLoadingOverlay(); showToast(`Error loading Hype Top 100: ${error.message}`, 'error'); } } // ================================= // // GENRE BROWSER MODAL FUNCTIONS // // ================================= // // Cache for genre browser data to avoid re-loading let genreBrowserCache = { genres: null, imagesLoaded: false, lastLoaded: null, imageLoadingActive: false, imageWorkers: null }; function initializeGenreBrowserModal() { console.log('🎡 Initializing Genre Browser Modal...'); // Browse by Genre button click handler const browseByGenreBtn = document.getElementById('browse-by-genre-btn'); if (browseByGenreBtn) { browseByGenreBtn.addEventListener('click', () => { console.log('🎡 Browse by Genre button clicked'); openGenreBrowserModal(); }); } // Modal close button handler const modalCloseBtn = document.getElementById('genre-browser-modal-close'); if (modalCloseBtn) { modalCloseBtn.addEventListener('click', closeGenreBrowserModal); } // Click outside modal to close const modalOverlay = document.getElementById('genre-browser-modal'); if (modalOverlay) { modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { closeGenreBrowserModal(); } }); } // Search functionality const searchInput = document.getElementById('genre-browser-search'); if (searchInput) { searchInput.addEventListener('input', (e) => { filterGenreBrowserCards(e.target.value); }); } // ESC key to close modal document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isGenreBrowserModalOpen()) { closeGenreBrowserModal(); } }); console.log('βœ… Genre Browser Modal initialized'); } function openGenreBrowserModal() { console.log('🎡 Opening Genre Browser Modal...'); const modal = document.getElementById('genre-browser-modal'); if (modal) { modal.classList.add('active'); document.body.style.overflow = 'hidden'; // Prevent background scrolling // Check cache before loading genres if (genreBrowserCache.genres && genreBrowserCache.genres.length > 0) { console.log('πŸ’Ύ Using cached genres data'); displayCachedGenres(); } else { console.log('πŸ”„ No cached data, loading genres...'); loadGenreBrowserGenres(); } console.log('βœ… Genre Browser Modal opened'); } } function closeGenreBrowserModal() { console.log('🎡 Closing Genre Browser Modal...'); const modal = document.getElementById('genre-browser-modal'); if (modal) { modal.classList.remove('active'); document.body.style.overflow = ''; // Restore scrolling // Clear search input but keep the genre data cached const searchInput = document.getElementById('genre-browser-search'); if (searchInput) { searchInput.value = ''; // Also reset the display filter to show all genres filterGenreBrowserCards(''); } // Pause image loading workers if they're running if (genreBrowserCache.imageLoadingActive) { console.log('⏸️ Pausing image loading workers...'); genreBrowserCache.imageLoadingActive = false; } console.log('βœ… Genre Browser Modal closed (data preserved in cache)'); } } function isGenreBrowserModalOpen() { const modal = document.getElementById('genre-browser-modal'); return modal && modal.classList.contains('active'); } async function loadGenreBrowserGenres() { console.log('πŸ” Loading genres for Genre Browser Modal...'); const genresGrid = document.getElementById('genre-browser-genres-grid'); if (!genresGrid) { console.error('❌ Genre browser grid not found'); return; } // Show loading state genresGrid.innerHTML = `

πŸ” Discovering current Beatport genres...

`; try { // First, fetch genres quickly without images console.log('πŸš€ Fetching genres without images for fast loading...'); const fastResponse = await fetch('/api/beatport/genres'); if (!fastResponse.ok) { throw new Error(`API returned ${fastResponse.status}: ${fastResponse.statusText}`); } const fastData = await fastResponse.json(); const genres = fastData.genres || []; if (genres.length === 0) { genresGrid.innerHTML = `

⚠️ No genres available

`; return; } // Filter out unwanted genres (section titles, etc.) const filteredGenres = genres.filter(genre => { const name = genre.name.toLowerCase().trim(); const unwantedGenres = [ 'open format', 'electronic', 'genres', 'browse', 'charts', 'new releases', 'trending', 'featured', 'popular' ]; const isUnwanted = unwantedGenres.includes(name); if (isUnwanted) { console.log(`🚫 Filtered out unwanted genre: "${genre.name}"`); } return !isUnwanted; }); console.log(`πŸ“‹ Filtered genres: ${genres.length} β†’ ${filteredGenres.length} (removed ${genres.length - filteredGenres.length} unwanted)`); // Generate genre cards dynamically (without images first) const genreCardsHTML = filteredGenres.map(genre => `
🎡

${genre.name}

Top 10 & Top 100 Charts

`).join(''); genresGrid.innerHTML = genreCardsHTML; // Add click event listeners to genre cards addGenreBrowserCardClickListeners(); // Cache the filtered genres data genreBrowserCache.genres = filteredGenres; genreBrowserCache.lastLoaded = new Date(); genreBrowserCache.imagesLoaded = false; console.log(`βœ… Loaded ${filteredGenres.length} Beatport genres for modal (fast mode)`); console.log(`πŸ’Ύ Cached ${filteredGenres.length} genres for future use`); showToast(`Loaded ${filteredGenres.length} genres for browsing`, 'success'); // Now fetch images progressively in the background if (filteredGenres.length > 5) { console.log('πŸ–ΌοΈ Loading genre images progressively for modal...'); loadGenreBrowserImagesProgressively(filteredGenres); } } catch (error) { console.error('❌ Error loading genres for modal:', error); genresGrid.innerHTML = `

❌ Failed to load genres: ${error.message}

`; showToast(`Error loading genres: ${error.message}`, 'error'); } } function displayCachedGenres() { console.log('πŸ’Ύ Displaying cached genres...'); const genresGrid = document.getElementById('genre-browser-genres-grid'); if (!genresGrid) { console.error('❌ Genre browser grid not found'); return; } const genres = genreBrowserCache.genres; if (!genres || genres.length === 0) { console.error('❌ No cached genres available'); return; } // Generate genre cards from cached data const genreCardsHTML = genres.map(genre => `
🎡

${genre.name}

Top 10 & Top 100 Charts

`).join(''); genresGrid.innerHTML = genreCardsHTML; // Add click event listeners to genre cards addGenreBrowserCardClickListeners(); console.log(`βœ… Displayed ${genres.length} cached genres instantly`); // Handle image loading based on current state if (genreBrowserCache.imagesLoaded) { console.log('πŸ–ΌοΈ Images already loaded, restoring them...'); restoreCachedImages(genres); } else if (!genreBrowserCache.imageLoadingActive && genres.length > 5) { // Resume or start image loading const cachedCount = genres.filter(g => g.imageUrl).length; if (cachedCount > 0) { console.log(`πŸ”„ Resuming image loading (${cachedCount}/${genres.length} already cached)...`); restoreCachedImages(genres); // Show already cached images } else { console.log('πŸ–ΌοΈ Starting fresh image loading for cached genres...'); } loadGenreBrowserImagesProgressively(genres); } else { console.log('πŸ“· Image loading in progress, showing cached images...'); restoreCachedImages(genres); } } function restoreCachedImages(genres) { // Restore images that were already loaded in previous sessions genres.forEach(genre => { if (genre.imageUrl) { const genreCard = document.querySelector( `.genre-browser-card[data-genre-slug="${genre.slug}"][data-genre-id="${genre.id}"]` ); if (genreCard) { const imageElement = genreCard.querySelector('.genre-browser-card-image'); if (imageElement) { imageElement.innerHTML = `${genre.name}`; genreCard.classList.remove('genre-browser-card-fallback'); } } } }); } async function loadGenreBrowserImagesProgressively(genres) { // Load genre images with 2 concurrent workers for faster loading // Only process genres that don't already have cached images const imageQueue = genres.filter(genre => !genre.imageUrl); let imagesLoaded = 0; const maxWorkers = 2; // Mark loading as active genreBrowserCache.imageLoadingActive = true; console.log(`πŸ–ΌοΈ Starting progressive image loading for modal with ${maxWorkers} workers for ${imageQueue.length} remaining genres (${genres.length - imageQueue.length} already cached)`); // If all images are already cached, mark as complete if (imageQueue.length === 0) { console.log('βœ… All images already cached, marking as complete'); genreBrowserCache.imagesLoaded = true; genreBrowserCache.imageLoadingActive = false; return; } // Function to process a single image async function processImage(genre) { try { // Fetch individual genre image from backend const response = await fetch(`/api/beatport/genre-image/${genre.slug}/${genre.id}`); if (response.ok) { const data = await response.json(); if (data.success && data.image_url) { // Cache the image URL in the genre object genre.imageUrl = data.image_url; // Find the genre card in the modal const genreCard = document.querySelector( `.genre-browser-card[data-genre-slug="${genre.slug}"][data-genre-id="${genre.id}"]` ); if (genreCard) { const imageElement = genreCard.querySelector('.genre-browser-card-image'); if (imageElement) { // Replace the fallback emoji with the actual image imageElement.innerHTML = `${genre.name}`; genreCard.classList.remove('genre-browser-card-fallback'); console.log(`βœ… Loaded and cached image for ${genre.name} in modal`); } } } } imagesLoaded++; console.log(`πŸ“· Progress: ${imagesLoaded}/${genres.length} images loaded for modal`); } catch (error) { console.log(`⚠️ Could not load image for ${genre.name} in modal: ${error.message}`); imagesLoaded++; } } // Worker function to process images from the queue async function worker() { while (imageQueue.length > 0 && genreBrowserCache.imageLoadingActive) { const genre = imageQueue.shift(); if (genre) { await processImage(genre); // Small delay to prevent overwhelming the server await new Promise(resolve => setTimeout(resolve, 100)); } // Check if we should pause if (!genreBrowserCache.imageLoadingActive) { console.log('⏸️ Worker paused - modal closed'); break; } } } // Start the workers const workers = []; for (let i = 0; i < maxWorkers; i++) { workers.push(worker()); } // Wait for all workers to complete await Promise.all(workers); // Check if loading was completed or paused if (genreBrowserCache.imageLoadingActive) { // Completed successfully genreBrowserCache.imagesLoaded = true; genreBrowserCache.imageLoadingActive = false; console.log(`πŸŽ‰ Completed loading all genre images for modal (${imagesLoaded}/${genres.length})`); console.log(`πŸ’Ύ Marked images as loaded in cache`); } else { // Was paused console.log(`⏸️ Image loading paused (${imagesLoaded}/${genres.length} completed)`); console.log(`πŸ’Ύ Partial progress saved in cache`); } } function filterGenreBrowserCards(searchTerm) { const genreCards = document.querySelectorAll('.genre-browser-card'); const searchLower = searchTerm.toLowerCase(); genreCards.forEach(card => { const genreName = card.dataset.genreName?.toLowerCase() || ''; const shouldShow = genreName.includes(searchLower); card.style.display = shouldShow ? 'block' : 'none'; }); console.log(`πŸ” Filtered genre cards with search term: "${searchTerm}"`); } // === GENRE BROWSER CARD CLICK HANDLERS === function addGenreBrowserCardClickListeners() { const genreCards = document.querySelectorAll('.genre-browser-card'); genreCards.forEach(card => { card.addEventListener('click', () => { const genreSlug = card.dataset.genreSlug; const genreId = card.dataset.genreId; const genreName = card.dataset.genreName; console.log(`🎡 Genre card clicked: ${genreName} (${genreSlug})`); handleGenreBrowserCardClick(genreSlug, genreId, genreName); }); }); console.log(`πŸ”— Added click listeners to ${genreCards.length} genre browser cards`); } async function handleGenreBrowserCardClick(genreSlug, genreId, genreName) { console.log(`🎠 Loading hero slider for ${genreName}...`); try { // Show the genre page view showGenrePageView(genreSlug, genreId, genreName); // Load the hero slider data // Load hero slider, Top 10 lists, and Top 10 releases in parallel await Promise.all([ loadGenreHeroSlider(genreSlug, genreId, genreName), loadGenreTop10Lists(genreSlug, genreId, genreName), loadGenreTop10Releases(genreSlug, genreId, genreName) ]); } catch (error) { console.error(`❌ Error loading genre page for ${genreName}:`, error); showToast(`Error loading ${genreName}: ${error.message}`, 'error'); // Return to genre list on error showGenreListView(); } } function showGenrePageView(genreSlug, genreId, genreName) { console.log(`🎯 Showing genre page view for ${genreName}`); // CRITICAL: Stop all other slider auto-play to prevent conflicts if (typeof beatportRebuildSliderState !== 'undefined' && beatportRebuildSliderState.autoPlayInterval) { clearInterval(beatportRebuildSliderState.autoPlayInterval); console.log('πŸ›‘ Stopped main slider auto-play to prevent conflicts'); } const modal = document.getElementById('genre-browser-modal'); if (!modal) return; // Hide genre list elements const searchSection = modal.querySelector('.genre-browser-search-section'); const genresSection = modal.querySelector('.genre-browser-genres-section'); if (searchSection) searchSection.style.display = 'none'; if (genresSection) genresSection.style.display = 'none'; // Create or show genre page content let genrePageContent = modal.querySelector('.genre-page-content'); if (!genrePageContent) { genrePageContent = document.createElement('div'); genrePageContent.className = 'genre-page-content'; genrePageContent.innerHTML = `

🎠 Loading hero releases...

🎡 Loading Top 10 lists...

πŸ’Ώ Loading Top 10 releases...

`; modal.querySelector('.genre-browser-modal-content').appendChild(genrePageContent); // Add back button listener const backButton = genrePageContent.querySelector('#genre-back-button'); if (backButton) { backButton.addEventListener('click', showGenreListView); } // Add genre top 100 button listener const genreTop100Button = genrePageContent.querySelector('#genre-top100-btn'); if (genreTop100Button) { genreTop100Button.addEventListener('click', () => { handleGenreTop100Click(genreSlug, genreId, genreName); }); } } // Update title and show genre page const titleElement = genrePageContent.querySelector('.genre-page-title'); if (titleElement) titleElement.textContent = genreName; genrePageContent.style.display = 'block'; // Store current genre info for potential back navigation genrePageContent.dataset.genreSlug = genreSlug; genrePageContent.dataset.genreId = genreId; genrePageContent.dataset.genreName = genreName; } function showGenreListView() { console.log(`πŸ”™ Returning to genre list view`); // Clean up genre hero slider if (window.genreHeroSliderState && window.genreHeroSliderState.autoPlayInterval) { clearInterval(window.genreHeroSliderState.autoPlayInterval); console.log('🧹 Cleaned up genre hero slider auto-play'); } // CRITICAL: Restart main slider auto-play if (typeof beatportRebuildSliderState !== 'undefined' && !beatportRebuildSliderState.autoPlayInterval) { if (typeof startBeatportRebuildSliderAutoPlay === 'function') { startBeatportRebuildSliderAutoPlay(); console.log('πŸ”„ Restarted main slider auto-play'); } } const modal = document.getElementById('genre-browser-modal'); if (!modal) return; // Show genre list elements const searchSection = modal.querySelector('.genre-browser-search-section'); const genresSection = modal.querySelector('.genre-browser-genres-section'); const genrePageContent = modal.querySelector('.genre-page-content'); if (searchSection) searchSection.style.display = 'block'; if (genresSection) genresSection.style.display = 'block'; if (genrePageContent) genrePageContent.style.display = 'none'; } async function loadGenreHeroSlider(genreSlug, genreId, genreName) { console.log(`🎠 Loading hero slider data for ${genreName}...`); const container = document.getElementById('genre-hero-slider-container'); if (!container) return; try { // Show loading state container.innerHTML = `

🎠 Loading ${genreName} hero releases...

`; // Fetch hero slider data from API const response = await fetch(`/api/beatport/genre/${genreSlug}/${genreId}/hero`); if (!response.ok) { throw new Error(`API returned ${response.status}: ${response.statusText}`); } const data = await response.json(); if (!data.success || !data.releases || data.releases.length === 0) { throw new Error(data.message || 'No hero releases found'); } console.log(`βœ… Loaded ${data.count} hero releases for ${genreName} (cached: ${data.cached})`); // Create hero slider HTML const heroSliderHTML = createGenreHeroSliderHTML(data.releases, genreName); container.innerHTML = heroSliderHTML; // Add click handlers to individual releases (for future download functionality) addGenreHeroReleaseClickHandlers(data.releases); showToast(`Loaded ${data.count} ${genreName} releases`, 'success'); } catch (error) { console.error(`❌ Error loading hero slider for ${genreName}:`, error); container.innerHTML = `

❌ Failed to load ${genreName} releases

${error.message}

`; throw error; } } function createGenreHeroSliderHTML(releases, genreName) { const slidesHTML = releases.map((release, index) => { // Convert relative URL to absolute URL const absoluteUrl = release.url.startsWith('http') ? release.url : `https://www.beatport.com${release.url}`; return `

${release.title}

${release.artists_string}

${release.label || genreName + ' Hero Release'}

`; }).join(''); const indicatorsHTML = releases.map((_, index) => ` `).join(''); return `
${slidesHTML}
${indicatorsHTML}
`; } function addGenreHeroReleaseClickHandlers(releases) { // Clear any existing intervals first if (window.genreHeroSliderState && window.genreHeroSliderState.autoPlayInterval) { clearInterval(window.genreHeroSliderState.autoPlayInterval); console.log('🧹 Cleared previous genre hero auto-play interval'); } // CRITICAL: Clear ALL possible conflicting intervals if (typeof beatportRebuildSliderState !== 'undefined' && beatportRebuildSliderState.autoPlayInterval) { clearInterval(beatportRebuildSliderState.autoPlayInterval); console.log('πŸ›‘ Cleared main rebuild slider auto-play interval'); } // Initialize global slider state for genre hero slider window.genreHeroSliderState = { currentSlide: 0, totalSlides: releases.length, autoPlayInterval: null }; console.log(`🎠 Initializing genre hero slider with ${releases.length} slides`); // Set up navigation button handlers const prevBtn = document.getElementById('genre-hero-prev-btn'); const nextBtn = document.getElementById('genre-hero-next-btn'); if (prevBtn) { prevBtn.addEventListener('click', () => { window.genreHeroSliderState.currentSlide = window.genreHeroSliderState.currentSlide > 0 ? window.genreHeroSliderState.currentSlide - 1 : window.genreHeroSliderState.totalSlides - 1; updateGenreHeroSlide(window.genreHeroSliderState.currentSlide); console.log(`⬅️ Previous: Moving to slide ${window.genreHeroSliderState.currentSlide}`); }); } if (nextBtn) { nextBtn.addEventListener('click', () => { window.genreHeroSliderState.currentSlide = (window.genreHeroSliderState.currentSlide + 1) % window.genreHeroSliderState.totalSlides; updateGenreHeroSlide(window.genreHeroSliderState.currentSlide); console.log(`➑️ Next: Moving to slide ${window.genreHeroSliderState.currentSlide}`); }); } // Set up indicator handlers const indicators = document.querySelectorAll('#genre-hero-slider .beatport-rebuild-indicator'); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { window.genreHeroSliderState.currentSlide = index; updateGenreHeroSlide(index); console.log(`🎯 Indicator: Jumping to slide ${index}`); }); }); // Set up individual slide click handlers (like the main hero slider) const slides = document.querySelectorAll('#genre-hero-slider .beatport-rebuild-slide[data-url]'); console.log(`πŸ”— Found ${slides.length} slides to set up click handlers for`); slides.forEach((slide, index) => { const releaseUrl = slide.getAttribute('data-url'); if (releaseUrl && releaseUrl !== '#' && releaseUrl !== '') { const release = releases[index]; if (release) { // Ensure we use the absolute URL and match the expected data structure const releaseData = { url: releaseUrl, // This is already the absolute URL from data-url title: release.title || 'Unknown Title', artist: release.artists_string || 'Unknown Artist', // handleBeatportReleaseCardClick expects 'artist' label: release.label || 'Unknown Label', image_url: release.image_url || '', // Include all original data for completeness artists_string: release.artists_string, type: release.type, source: release.source, badges: release.badges || [] }; slide.addEventListener('click', async (event) => { // Prevent navigation button clicks from triggering this if (event.target.closest('.beatport-rebuild-nav-btn') || event.target.closest('.beatport-rebuild-indicator')) { return; } console.log(`🎡 Genre hero slide clicked: ${releaseData.title} by ${releaseData.artist}`); // Use the exact same functionality as the main hero slider await handleBeatportReleaseCardClick(slide, releaseData); }); slide.style.cursor = 'pointer'; } } }); // Ensure first slide is active BEFORE starting auto-play updateGenreHeroSlide(0); // Delay auto-play start to let DOM settle setTimeout(() => { startGenreHeroSliderAutoPlay(); }, 100); // Pause on hover const sliderContainer = document.querySelector('#genre-hero-slider'); if (sliderContainer) { sliderContainer.addEventListener('mouseenter', () => { if (window.genreHeroSliderState.autoPlayInterval) { clearInterval(window.genreHeroSliderState.autoPlayInterval); console.log('⏸️ Paused auto-play on hover'); } }); sliderContainer.addEventListener('mouseleave', () => { // Delay restart to avoid rapid state changes setTimeout(() => { startGenreHeroSliderAutoPlay(); }, 100); console.log('▢️ Resumed auto-play after hover'); }); } console.log(`βœ… Set up slider functionality for ${releases.length} genre hero releases`); } function updateGenreHeroSlide(slideIndex) { if (!window.genreHeroSliderState) { console.error('❌ Genre hero slider state not initialized'); return; } // First update the state window.genreHeroSliderState.currentSlide = slideIndex; // Update slide visibility - use the exact same logic as main slider const slides = document.querySelectorAll('#genre-hero-slider .beatport-rebuild-slide'); console.log(`πŸ”„ Updating slide to index ${slideIndex}, found ${slides.length} slides`); if (slideIndex >= slides.length || slideIndex < 0) { console.error(`❌ Invalid slide index ${slideIndex}, max is ${slides.length - 1}`); return; } slides.forEach((slide, index) => { slide.classList.remove('active', 'prev', 'next'); if (index === slideIndex) { slide.classList.add('active'); console.log(`βœ… Activated slide ${index}: ${slide.getAttribute('data-slide')} - Title: ${slide.querySelector('.beatport-rebuild-track-title')?.textContent}`); } else if (index < slideIndex) { slide.classList.add('prev'); } else { slide.classList.add('next'); } }); // Update indicators const indicators = document.querySelectorAll('#genre-hero-slider .beatport-rebuild-indicator'); indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === slideIndex); }); console.log(`Genre slide updated to: ${window.genreHeroSliderState.currentSlide}`); } function startGenreHeroSliderAutoPlay() { if (!window.genreHeroSliderState) { console.error('❌ Cannot start auto-play: Genre hero slider state not initialized'); return; } // Clear any existing intervals first if (window.genreHeroSliderState.autoPlayInterval) { clearInterval(window.genreHeroSliderState.autoPlayInterval); console.log('🧹 Cleared existing auto-play interval'); } window.genreHeroSliderState.autoPlayInterval = setInterval(() => { if (!window.genreHeroSliderState) { console.error('❌ Auto-play fired but state is gone, clearing interval'); clearInterval(window.genreHeroSliderState.autoPlayInterval); return; } const currentSlide = window.genreHeroSliderState.currentSlide; const totalSlides = window.genreHeroSliderState.totalSlides; const nextSlide = (currentSlide + 1) % totalSlides; console.log(`⏰ Auto-play: Current=${currentSlide}, Total=${totalSlides}, Next=${nextSlide}`); // Validate the next slide index if (nextSlide >= 0 && nextSlide < totalSlides) { updateGenreHeroSlide(nextSlide); } else { console.error(`❌ Invalid nextSlide calculated: ${nextSlide}, resetting to 0`); updateGenreHeroSlide(0); } }, 5000); // 5 second intervals like the main slider console.log(`▢️ Started auto-play for genre hero slider (${window.genreHeroSliderState.totalSlides} slides)`); } /** * Load Top 10 lists for a specific genre (Beatport + Hype) */ async function loadGenreTop10Lists(genreSlug, genreId, genreName) { console.log(`🎡 Loading Top 10 lists for ${genreName}...`); const container = document.getElementById('genre-top10-lists-container'); if (!container) { console.error('❌ Genre Top 10 lists container not found'); return; } try { const response = await fetch(`/api/beatport/genre/${genreSlug}/${genreId}/top-10-lists`); const data = await response.json(); if (!data.success) { throw new Error(data.error || 'Failed to load Top 10 lists'); } console.log(`βœ… Loaded ${data.beatport_count} Beatport + ${data.hype_count} Hype Top 10 tracks for ${genreName}`); // Generate HTML using exact same structure as main page (but unique IDs) const top10ListsHTML = createGenreTop10ListsHTML(data, genreName); container.innerHTML = top10ListsHTML; // Add container-level click handlers exactly like main page addGenreTop10ClickHandlers(); console.log(`βœ… Successfully populated genre Top 10 lists for ${genreName}`); } catch (error) { console.error(`❌ Error loading Top 10 lists for ${genreName}:`, error); // Show error state container.innerHTML = `

❌ Error Loading Top 10 Lists

Could not load Top 10 tracks for ${genreName}

${error.message}

`; } } /** * Create HTML for genre Top 10 lists (exact structure as main page, unique IDs) */ function createGenreTop10ListsHTML(data, genreName) { const { beatport_top10, hype_top10, has_hype_section } = data; // Use exact same structure as main page but with genre-specific IDs let html = `

πŸ† ${genreName} Top 10 Lists

Current trending ${genreName.toLowerCase()} tracks

🎡 Beatport Top 10

Most popular ${genreName.toLowerCase()} tracks

`; // Add Beatport Top 10 tracks (same classes as main page) beatport_top10.forEach((track, index) => { const cleanTitle = cleanTrackText(track.title || 'Unknown Title'); const cleanArtist = cleanTrackText(track.artist || 'Unknown Artist'); const cleanLabel = cleanTrackText(track.label || 'Unknown Label'); html += `
${track.rank || index + 1}
${track.artwork_url ? `${cleanTitle}` : '
🎡
' }

${cleanTitle}

${cleanArtist}

${cleanLabel}

`; }); html += `
`; // Add Hype Top 10 section (same classes, unique ID) if (has_hype_section && hype_top10.length > 0) { html += `

πŸ”₯ Hype Top 10

Editor's trending ${genreName.toLowerCase()} picks

`; // Add Hype Top 10 tracks (same classes as main page) hype_top10.forEach((track, index) => { const cleanTitle = cleanTrackText(track.title || 'Unknown Title'); const cleanArtist = cleanTrackText(track.artist || 'Unknown Artist'); const cleanLabel = cleanTrackText(track.label || 'Unknown Label'); html += `
${track.rank || index + 1}
${track.artwork_url ? `${cleanTitle}` : '
πŸ”₯
' }

${cleanTitle}

${cleanArtist}

${cleanLabel}

`; }); html += `
`; } // No else block - completely hide hype section when no hype tracks available html += `
`; return html; } /** * Add container-level click handlers for genre Top 10 lists (exact parity with main page) */ function addGenreTop10ClickHandlers() { console.log('πŸ”— Adding container-level click handlers for genre Top 10 lists...'); // Add container-level click handler for Beatport Top 10 (exact match to main page) const beatportContainer = document.getElementById('genre-beatport-top10-list'); if (beatportContainer) { beatportContainer.addEventListener('click', () => { console.log('🎡 Genre Beatport Top 10 container clicked'); handleGenreBeatportTop10Click(); }); console.log('βœ… Added Beatport Top 10 container click handler'); } // Add container-level click handler for Hype Top 10 (exact match to main page) const hypeContainer = document.getElementById('genre-beatport-hype10-list'); if (hypeContainer) { hypeContainer.addEventListener('click', () => { console.log('πŸ”₯ Genre Hype Top 10 container clicked'); handleGenreHypeTop10Click(); }); console.log('βœ… Added Hype Top 10 container click handler'); } console.log(`βœ… Set up container-level click handlers for genre Top 10 lists`); } /** * Handle genre Beatport Top 10 container click (exact parity with main page) */ async function handleGenreBeatportTop10Click() { console.log('🎡 Handling Genre Beatport Top 10 click'); // Get the actual genre name from the page title const genreName = document.querySelector('.genre-page-title')?.textContent?.trim() || 'Genre'; // Use actual genre name in chart title await handleGenreChartClick('genre_beatport_top10', `${genreName} Beatport Top 10`, 'genre_beatport_top10'); } /** * Handle genre Hype Top 10 container click (exact parity with main page) */ async function handleGenreHypeTop10Click() { console.log('πŸ”₯ Handling Genre Hype Top 10 click'); // Get the actual genre name from the page title const genreName = document.querySelector('.genre-page-title')?.textContent?.trim() || 'Genre'; // Use actual genre name in chart title await handleGenreChartClick('genre_hype_top10', `${genreName} Hype Top 10`, 'genre_hype_top10'); } /** * Handle genre chart click (based on main page handleRebuildChartClick) */ async function handleGenreChartClick(trackDataKey, chartName, chartType) { if (_beatportModalOpening) return; _beatportModalOpening = true; setTimeout(() => { _beatportModalOpening = false; }, 2000); try { // Extract track data from DOM cards const trackData = await getGenrePageTrackData(trackDataKey); if (!trackData || trackData.length === 0) { throw new Error(`No track data found for ${chartName}`); } console.log(`βœ… Got ${trackData.length} tracks from ${chartName}, enriching one-by-one...`); showLoadingOverlay(`Fetching track metadata... (0/${trackData.length})`); const enrichedTracks = await _enrichTracksWithProgress(trackData, chartName); console.log(`βœ… Enriched ${enrichedTracks.length} tracks`); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, chartName, null); } catch (error) { hideLoadingOverlay(); console.error(`❌ Error handling ${chartName} click:`, error); showToast(`Error loading ${chartName}: ${error.message}`, 'error'); } } /** * Extract track data from genre page DOM (based on main page getRebuildPageTrackData) */ async function getGenrePageTrackData(trackDataKey) { console.log(`πŸ” Extracting ${trackDataKey} data from genre page DOM`); let containerSelector, cardSelector; if (trackDataKey === 'genre_beatport_top10') { containerSelector = '#genre-beatport-top10-list'; cardSelector = '.beatport-top10-card[data-url]'; } else if (trackDataKey === 'genre_hype_top10') { containerSelector = '#genre-beatport-hype10-list'; cardSelector = '.beatport-hype10-card[data-url]'; } else { throw new Error(`Unknown track data key: ${trackDataKey}`); } const container = document.querySelector(containerSelector); if (!container) { throw new Error(`Container ${containerSelector} not found`); } const trackCards = container.querySelectorAll(cardSelector); if (trackCards.length === 0) { throw new Error(`No track cards found in ${containerSelector}`); } // Extract track data from DOM cards (exact same pattern as main page) const tracks = Array.from(trackCards).map(card => { const title = card.querySelector('.beatport-top10-card-title, .beatport-hype10-card-title')?.textContent?.trim() || 'Unknown Title'; const artist = card.querySelector('.beatport-top10-card-artist, .beatport-hype10-card-artist')?.textContent?.trim() || 'Unknown Artist'; const label = card.querySelector('.beatport-top10-card-label, .beatport-hype10-card-label')?.textContent?.trim() || 'Unknown Label'; const url = card.getAttribute('data-url') || ''; const rank = card.querySelector('.beatport-top10-card-rank, .beatport-hype10-card-rank')?.textContent?.trim() || ''; return { title: title, artist: artist, label: label, url: url, rank: rank }; }); console.log(`πŸ“‹ Extracted ${tracks.length} tracks from ${containerSelector}`); return tracks; } /** * Handle genre-specific Top 100 button click - create discovery process for genre top 100 tracks */ async function handleGenreTop100Click(genreSlug, genreId, genreName) { if (_beatportModalOpening) return; _beatportModalOpening = true; setTimeout(() => { _beatportModalOpening = false; }, 2000); console.log(`πŸ’― Genre Top 100 button clicked for ${genreName}`); const chartName = `${genreName} Top 100`; try { showLoadingOverlay(`Scraping ${chartName}...`); // Use the genre tracks endpoint without enrichment const response = await fetch(`/api/beatport/genre/${genreSlug}/${genreId}/tracks?enrich=false`, { method: 'GET' }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error(`No tracks found in ${chartName}`); } console.log(`βœ… Fetched ${data.tracks.length} tracks, enriching one-by-one...`); // Enrich one-by-one with live progress const enrichedTracks = await _enrichTracksWithProgress(data.tracks, chartName); hideLoadingOverlay(); openBeatportChartAsDownloadModal(enrichedTracks, chartName, null); } catch (error) { console.error(`❌ Error handling ${chartName} click:`, error); hideLoadingOverlay(); showToast(`Error loading ${chartName}: ${error.message}`, 'error'); } } /** * Load Top 10 releases for a specific genre */ async function loadGenreTop10Releases(genreSlug, genreId, genreName) { console.log(`πŸ’Ώ Loading Top 10 releases for ${genreName}...`); const container = document.getElementById('genre-top10-releases-container'); if (!container) { console.error('❌ Genre Top 10 releases container not found'); return; } try { const response = await fetch(`/api/beatport/genre/${genreSlug}/${genreId}/top-10-releases`); const data = await response.json(); if (!data.success) { throw new Error(data.error || 'Failed to load Top 10 releases'); } console.log(`πŸ’Ώ Loaded ${data.releases.length} Top 10 releases for ${genreName}`); createGenreTop10ReleasesHTML(data.releases, genreName); } catch (error) { console.error(`❌ Error loading Top 10 releases for ${genreName}:`, error); showGenreTop10ReleasesError(error.message || 'Failed to load Top 10 releases'); } } /** * Create HTML for genre Top 10 releases section (exact parity with main page) */ function createGenreTop10ReleasesHTML(releases, genreName) { const container = document.getElementById('genre-top10-releases-container'); if (!container || !releases || releases.length === 0) return; // Create section with unique ID but exact same structure as main page const sectionHtml = `

πŸ’Ώ Top 10 ${genreName} Releases

Most popular albums and EPs for ${genreName}

${createGenreTop10ReleasesCardsHTML(releases)}
`; container.innerHTML = sectionHtml; // Add background images and click handlers addGenreTop10ReleasesInteractivity(releases); } /** * Create release cards HTML for genre Top 10 releases */ function createGenreTop10ReleasesCardsHTML(releases) { let cardsHtml = '
'; releases.forEach((release, index) => { cardsHtml += `
${release.rank || index + 1}
${release.image_url ? `${release.title}` : '
πŸ’Ώ
' }

${release.title || 'Unknown Title'}

${release.artist || 'Unknown Artist'}

${release.label || 'Unknown Label'}

`; }); cardsHtml += '
'; return cardsHtml; } /** * Add interactivity to genre Top 10 releases cards */ function addGenreTop10ReleasesInteractivity(releases) { const container = document.getElementById('genre-beatport-releases-top10-list'); if (!container) return; // Set background images for cards const cards = container.querySelectorAll('.beatport-releases-top10-card[data-bg-image]'); cards.forEach(card => { const bgImage = card.getAttribute('data-bg-image'); if (bgImage) { // Transform image URL from 95x95 to 500x500 for higher quality background const highResImage = bgImage.replace('/image_size/95x95/', '/image_size/500x500/'); card.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.8)), url('${highResImage}')`; card.style.backgroundSize = 'cover'; card.style.backgroundPosition = 'center'; } }); // Add click handlers for individual release discovery (exact same pattern as main page) const releaseCards = container.querySelectorAll('.beatport-releases-top10-card[data-url]'); releaseCards.forEach((card, index) => { card.addEventListener('click', () => handleGenreReleaseCardClick(card, releases[index])); card.style.cursor = 'pointer'; }); } /** * Handle click on individual genre Top 10 Release card (exact parity with main page) */ async function handleGenreReleaseCardClick(cardElement, release) { if (_beatportModalOpening) return; _beatportModalOpening = true; console.log(`πŸ’Ώ Individual genre release card clicked: ${release.title} by ${release.artist}`); if (!release.url || release.url === '#') { _beatportModalOpening = false; showToast('No release URL available', 'error'); return; } try { showToast(`Loading ${release.title}...`, 'info'); showLoadingOverlay(`Getting tracks from ${release.title}...`); // Fetch structured release metadata for direct download modal console.log(`🎡 Fetching release metadata: ${release.url}`); const response = await fetch('/api/beatport/release-metadata', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ release_url: release.url }) }); const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { throw new Error(data.error || 'No tracks found in this release'); } console.log(`βœ… Got ${data.tracks.length} tracks from ${data.album.name}`); const formattedTracks = data.tracks.map(track => ({ ...track, artists: track.artists.map(a => typeof a === 'object' ? a.name : a) })); const virtualPlaylistId = `beatport_release_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const playlistName = data.album.name; await openDownloadMissingModalForArtistAlbum( virtualPlaylistId, playlistName, formattedTracks, data.album, data.artist, false ); hideLoadingOverlay(); _beatportModalOpening = false; console.log(`βœ… Opened download modal for ${playlistName}`); } catch (error) { console.error(`❌ Error handling release click for ${release.title}:`, error); hideLoadingOverlay(); _beatportModalOpening = false; showToast(`Error loading ${release.title}: ${error.message}`, 'error'); } } /** * Show error message for genre Top 10 releases */ function showGenreTop10ReleasesError(errorMessage) { const container = document.getElementById('genre-top10-releases-container'); const errorHtml = `

πŸ’Ώ Top 10 Releases

Error loading releases

❌ Error Loading Releases

${errorMessage}

`; if (container) container.innerHTML = errorHtml; } // Initialize the Genre Browser Modal when the page loads document.addEventListener('DOMContentLoaded', () => { initializeGenreBrowserModal(); }); // ============ Plex Music Library Selection ============ async function loadPlexMusicLibraries() { try { const response = await fetch('/api/plex/music-libraries'); const data = await response.json(); if (data.success && data.libraries && data.libraries.length > 0) { const selector = document.getElementById('plex-music-library'); const container = document.getElementById('plex-library-selector-container'); // Clear existing options selector.innerHTML = ''; // Add options for each library. ``value`` is the canonical // identifier the backend expects (real libraries: title; // synthetic "All Libraries" entry: the sentinel string). // ``title`` stays the human-readable label. data.libraries.forEach(library => { const option = document.createElement('option'); const optionValue = library.value || library.title; option.value = optionValue; option.textContent = library.title; // Pre-select match: compare ``value`` against the saved // DB pref (``data.selected``) AND ``title`` against the // live-active library name (``data.current``). Covers // both the sentinel case and the legacy single-library // case. if (optionValue === data.selected || library.title === data.current || library.title === data.selected) { option.selected = true; } selector.appendChild(option); }); // Show the container container.style.display = 'block'; } else { // Hide if no libraries found or not connected document.getElementById('plex-library-selector-container').style.display = 'none'; } } catch (error) { console.error('Error loading Plex music libraries:', error); document.getElementById('plex-library-selector-container').style.display = 'none'; } } async function selectPlexLibrary() { const selector = document.getElementById('plex-music-library'); const selectedLibrary = selector.value; if (!selectedLibrary) return; try { const response = await fetch('/api/plex/select-music-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ library_name: selectedLibrary }) }); const data = await response.json(); if (data.success) { console.log(`Plex music library switched to: ${selectedLibrary}`); } else { console.error('Failed to switch library:', data.error); alert(`Failed to switch library: ${data.error}`); } } catch (error) { console.error('Error selecting Plex library:', error); alert('Error selecting library. Please try again.'); } } // ============ Jellyfin User Selection ============ async function loadJellyfinUsers() { try { const response = await fetch('/api/jellyfin/users'); const data = await response.json(); if (data.success && data.users && data.users.length > 0) { const selector = document.getElementById('jellyfin-user'); const container = document.getElementById('jellyfin-user-selector-container'); // Clear existing options selector.innerHTML = ''; // Add options for each user data.users.forEach(user => { const option = document.createElement('option'); option.value = user.name; option.textContent = user.name; // Mark the currently selected user if (user.name === data.current || user.name === data.selected) { option.selected = true; } selector.appendChild(option); }); // Show the container container.style.display = 'block'; } else { // Hide if no users found or not connected document.getElementById('jellyfin-user-selector-container').style.display = 'none'; } } catch (error) { console.error('Error loading Jellyfin users:', error); document.getElementById('jellyfin-user-selector-container').style.display = 'none'; } } async function selectJellyfinUser() { const selector = document.getElementById('jellyfin-user'); const selectedUser = selector.value; if (!selectedUser) return; try { const response = await fetch('/api/jellyfin/select-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: selectedUser }) }); const data = await response.json(); if (data.success) { console.log(`Jellyfin user switched to: ${selectedUser}`); // Refresh library dropdown for the new user loadJellyfinMusicLibraries(); } else { console.error('Failed to switch user:', data.error); alert(`Failed to switch user: ${data.error}`); } } catch (error) { console.error('Error selecting Jellyfin user:', error); alert('Error selecting user. Please try again.'); } } // ============ Jellyfin Music Library Selection ============ async function loadJellyfinMusicLibraries() { try { const response = await fetch('/api/jellyfin/music-libraries'); const data = await response.json(); if (data.success && data.libraries && data.libraries.length > 0) { const selector = document.getElementById('jellyfin-music-library'); const container = document.getElementById('jellyfin-library-selector-container'); // Clear existing options selector.innerHTML = ''; // Add options for each library data.libraries.forEach(library => { const option = document.createElement('option'); option.value = library.title; option.textContent = library.title; // Mark the currently selected library if (library.title === data.current || library.title === data.selected) { option.selected = true; } selector.appendChild(option); }); // Show the container container.style.display = 'block'; } else { // Hide if no libraries found or not connected document.getElementById('jellyfin-library-selector-container').style.display = 'none'; } } catch (error) { console.error('Error loading Jellyfin music libraries:', error); document.getElementById('jellyfin-library-selector-container').style.display = 'none'; } } async function selectJellyfinLibrary() { const selector = document.getElementById('jellyfin-music-library'); const selectedLibrary = selector.value; if (!selectedLibrary) return; try { const response = await fetch('/api/jellyfin/select-music-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ library_name: selectedLibrary }) }); const data = await response.json(); if (data.success) { console.log(`Jellyfin music library switched to: ${selectedLibrary}`); } else { console.error('Failed to switch library:', data.error); alert(`Failed to switch library: ${data.error}`); } } catch (error) { console.error('Error selecting Jellyfin library:', error); alert('Error selecting library. Please try again.'); } } // ============ Navidrome Music Folder Selection ============ async function loadNavidromeMusicFolders() { try { const response = await fetch('/api/navidrome/music-folders'); const data = await response.json(); if (data.success && data.folders && data.folders.length > 0) { const selector = document.getElementById('navidrome-music-folder'); const container = document.getElementById('navidrome-folder-selector-container'); selector.innerHTML = ''; data.folders.forEach(folder => { const option = document.createElement('option'); option.value = folder.title; option.textContent = folder.title; if (folder.title === data.current || folder.title === data.selected) { option.selected = true; } selector.appendChild(option); }); container.style.display = 'block'; } else { document.getElementById('navidrome-folder-selector-container').style.display = 'none'; } } catch (error) { console.error('Error loading Navidrome music folders:', error); document.getElementById('navidrome-folder-selector-container').style.display = 'none'; } } async function selectNavidromeMusicFolder() { const selector = document.getElementById('navidrome-music-folder'); const selectedFolder = selector.value; try { const response = await fetch('/api/navidrome/select-music-folder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ folder_name: selectedFolder }) }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); } else { console.error('Failed to set music folder:', data.error); showToast(`Failed to set music folder: ${data.error}`, 'error', 'set-media'); } } catch (error) { console.error('Error selecting Navidrome music folder:', error); showToast('Error selecting music folder. Please try again.', 'error', 'set-media'); } } // ============================================