From fcd48793e0a9bf444eb3581160636a77af2c68ac Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Wed, 1 Oct 2025 17:59:38 -0700 Subject: [PATCH] Add Beatport Top 100 button to genre page Introduces a 'Beatport Top 100' button to each genre page, allowing users to fetch and view the top 100 tracks for a selected genre. Includes new event handling logic in script.js and corresponding styles in style.css for the button and its container. --- webui/static/script.js | 105 +++++++++++++++++++++++++++++++++++++++++ webui/static/style.css | 25 +++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/webui/static/script.js b/webui/static/script.js index 165bbe61..fb1d91ff 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -21301,6 +21301,14 @@ function showGenrePageView(genreSlug, genreId, genreName) {

🎠 Loading hero releases...

+
+
+ +
+
`; modal.querySelector('.genre-browser-modal-content').appendChild(genrePageContent); @@ -21310,6 +21318,14 @@ function showGenrePageView(genreSlug, genreId, genreName) { 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 @@ -21663,6 +21679,95 @@ function startGenreHeroSliderAutoPlay() { console.log(`▶️ Started auto-play for genre hero slider (${window.genreHeroSliderState.totalSlides} slides)`); } +/** + * Handle genre-specific Top 100 button click - create discovery process for genre top 100 tracks + */ +async function handleGenreTop100Click(genreSlug, genreId, genreName) { + console.log(`💯 Genre Top 100 button clicked for ${genreName}`); + + try { + // Create unique identifiers for this chart + const chartHash = `${genreSlug}_top100_${Date.now()}`; + const chartName = `${genreName} Top 100`; + + showToast(`Loading ${genreName} Top 100...`, 'info'); + showLoadingOverlay(`Getting ${genreName} Top 100 tracks...`); + + // Check if we already have a card for this genre's Top 100 + const existingState = Object.values(beatportChartStates).find(state => + state.chart && + state.chart.name === chartName && + state.chart.chart_type === 'genre_top100' + ); + + if (existingState) { + console.log(`🔄 Found existing ${genreName} Top 100 card, opening existing modal`); + hideLoadingOverlay(); + handleBeatportCardClick(existingState.chart.hash); + return; + } + + // Construct the genre top 100 URL: genre URL + /top-100 + const genreTop100Url = `https://www.beatport.com/genre/${genreSlug}/${genreId}/top-100`; + console.log(`💯 Fetching tracks from ${genreTop100Url}`); + + // Get track data from genre top 100 page + const response = await fetch('/api/beatport/scrape-releases', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + release_urls: [genreTop100Url], + source_name: chartName + }) + }); + + const data = await response.json(); + + if (!data.success || !data.tracks || data.tracks.length === 0) { + throw new Error(`No tracks found in ${genreName} Top 100`); + } + + console.log(`✅ Successfully fetched ${data.tracks.length} tracks from ${genreName} Top 100`); + + // Transform to standard chart format (following the exact pattern) + const chartData = { + hash: chartHash, + name: chartName, + chart_type: 'genre_top100', + track_count: data.tracks.length, + tracks: data.tracks.map(track => ({ + name: cleanTrackText(track.title || 'Unknown Title'), + artists: [cleanTrackText(track.artist || 'Unknown Artist')], + album: chartName, + duration_ms: 0, + external_urls: { beatport: track.url || '' }, + source: 'beatport', + // Include genre metadata + genre_slug: genreSlug, + genre_id: genreId, + genre_name: genreName, + position: track.position || track.rank + })) + }; + + // Create Beatport playlist card (following the exact pattern) + addBeatportCardToContainer(chartData); + + // Automatically open discovery modal (following the exact pattern) + hideLoadingOverlay(); + handleBeatportCardClick(chartHash); + + console.log(`✅ Created ${genreName} Top 100 card and opened discovery modal`); + + } catch (error) { + console.error(`❌ Error handling ${genreName} Top 100 click:`, error); + hideLoadingOverlay(); + showToast(`Error loading ${genreName} Top 100: ${error.message}`, 'error'); + } +} + // Initialize the Genre Browser Modal when the page loads document.addEventListener('DOMContentLoaded', () => { initializeGenreBrowserModal(); diff --git a/webui/static/style.css b/webui/static/style.css index 063995e5..138b0d5d 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -14988,7 +14988,7 @@ body { .genre-hero-slider-container { width: 100%; - height: calc(100% - 80px); + height: fit-content; overflow-y: auto; } @@ -15058,6 +15058,24 @@ body { border-color: rgba(255, 255, 255, 0.25); } +/* === GENRE NAV BUTTONS SECTION === */ + +.genre-nav-buttons-section { + margin-top: 30px; + padding: 20px 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.genre-nav-buttons-container { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; +} + +/* The button styles are already defined in the main .beatport-nav-button class */ +/* This ensures the genre page button looks identical to the main page buttons */ + /* Responsive adjustments for genre page */ @media (max-width: 768px) { .genre-page-header { @@ -15069,4 +15087,9 @@ body { .genre-page-title { font-size: 20px; } + + .genre-nav-buttons-section { + margin-top: 20px; + padding: 15px 0; + } }