beatport progress

pull/49/head
Broque Thomas 7 months ago
parent ad7c765912
commit fd92deb5c8

@ -21006,6 +21006,9 @@ async function loadGenreBrowserGenres() {
genresGrid.innerHTML = genreCardsHTML;
// Add click event listeners to genre cards
addGenreBrowserCardClickListeners();
// Cache the filtered genres data
genreBrowserCache.genres = filteredGenres;
genreBrowserCache.lastLoaded = new Date();
@ -21065,6 +21068,9 @@ function displayCachedGenres() {
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
@ -21221,6 +21227,442 @@ function filterGenreBrowserCards(searchTerm) {
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
await loadGenreHeroSlider(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 = `
<div class="genre-page-header">
<button class="genre-back-button" id="genre-back-button">
<span class="back-icon"></span> Back to Genres
</button>
<h2 class="genre-page-title"></h2>
</div>
<div class="genre-hero-slider-container" id="genre-hero-slider-container">
<div class="genre-loading-container">
<div class="genre-loading-spinner"></div>
<p class="genre-loading-text">🎠 Loading hero releases...</p>
</div>
</div>
`;
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);
}
}
// 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 = `
<div class="genre-loading-container">
<div class="genre-loading-spinner"></div>
<p class="genre-loading-text">🎠 Loading ${genreName} hero releases...</p>
</div>
`;
// 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 = `
<div class="genre-error-container">
<p class="genre-error-text"> Failed to load ${genreName} releases</p>
<p class="genre-error-details">${error.message}</p>
<button class="genre-retry-button" onclick="loadGenreHeroSlider('${genreSlug}', '${genreId}', '${genreName}')">
🔄 Retry
</button>
</div>
`;
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 `
<div class="beatport-rebuild-slide ${index === 0 ? 'active' : ''}"
data-slide="${index}"
data-url="${absoluteUrl}"
data-image="${release.image_url}"
style="--slide-bg-image: url('${release.image_url}')">
<div class="beatport-rebuild-slide-background">
<div class="beatport-rebuild-slide-gradient"></div>
</div>
<div class="beatport-rebuild-slide-content">
<div class="beatport-rebuild-track-info">
<h2 class="beatport-rebuild-track-title">${release.title}</h2>
<p class="beatport-rebuild-artist-name">${release.artists_string}</p>
<p class="beatport-rebuild-album-name">${release.label || genreName + ' Hero Release'}</p>
</div>
</div>
</div>`;
}).join('');
const indicatorsHTML = releases.map((_, index) => `
<button class="beatport-rebuild-indicator ${index === 0 ? 'active' : ''}" data-slide="${index}"></button>
`).join('');
return `
<div class="beatport-rebuild-slider-container">
<div class="beatport-rebuild-slider" id="genre-hero-slider">
<div class="beatport-rebuild-slider-track" id="genre-hero-slider-track">
${slidesHTML}
</div>
<!-- Slider Navigation -->
<div class="beatport-rebuild-slider-nav">
<button class="beatport-rebuild-nav-btn beatport-rebuild-prev-btn" id="genre-hero-prev-btn"></button>
<button class="beatport-rebuild-nav-btn beatport-rebuild-next-btn" id="genre-hero-next-btn"></button>
</div>
<!-- Slider Indicators -->
<div class="beatport-rebuild-slider-indicators">
${indicatorsHTML}
</div>
</div>
</div>
`;
}
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)`);
}
// Initialize the Genre Browser Modal when the page loads
document.addEventListener('DOMContentLoaded', () => {
initializeGenreBrowserModal();

@ -14632,7 +14632,7 @@ body {
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
z-index: 8888;
animation: genreBrowserFadeIn 0.3s ease-out;
}
@ -14927,3 +14927,146 @@ body {
transform: rotate(360deg);
}
}
/* === GENRE PAGE VIEW STYLES === */
.genre-page-content {
display: none;
width: 100%;
height: 100%;
animation: genreBrowserSlideIn 0.3s ease;
}
.genre-page-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.genre-back-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: linear-gradient(135deg,
rgba(30, 30, 30, 0.8) 0%,
rgba(20, 20, 20, 0.9) 100%);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 10px;
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.genre-back-button:hover {
background: linear-gradient(135deg,
rgba(40, 40, 40, 0.9) 0%,
rgba(30, 30, 30, 0.95) 100%);
border-color: rgba(255, 255, 255, 0.25);
transform: translateX(-2px);
}
.back-icon {
font-size: 16px;
transition: transform 0.2s ease;
}
.genre-back-button:hover .back-icon {
transform: translateX(-2px);
}
.genre-page-title {
font-size: 24px;
font-weight: 600;
color: #ffffff;
margin: 0;
}
.genre-hero-slider-container {
width: 100%;
height: calc(100% - 80px);
overflow-y: auto;
}
.genre-loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: rgba(255, 255, 255, 0.7);
}
.genre-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-top: 3px solid #1db954;
border-radius: 50%;
animation: genreBrowserSpin 1s linear infinite;
margin-bottom: 15px;
}
.genre-loading-text {
font-size: 16px;
margin: 0;
text-align: center;
}
.genre-error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.genre-error-text {
font-size: 18px;
color: #ff6b6b;
margin: 0 0 10px 0;
}
.genre-error-details {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
margin: 0 0 20px 0;
}
.genre-retry-button {
padding: 10px 20px;
background: linear-gradient(135deg,
rgba(30, 30, 30, 0.8) 0%,
rgba(20, 20, 20, 0.9) 100%);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 10px;
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.genre-retry-button:hover {
background: linear-gradient(135deg,
rgba(40, 40, 40, 0.9) 0%,
rgba(30, 30, 30, 0.95) 100%);
border-color: rgba(255, 255, 255, 0.25);
}
/* Responsive adjustments for genre page */
@media (max-width: 768px) {
.genre-page-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.genre-page-title {
font-size: 20px;
}
}

Loading…
Cancel
Save