From 5981bd772fb655cc9b780124ee4d5c92876b77ea Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Sun, 28 Sep 2025 18:16:28 -0700 Subject: [PATCH] beatport progress --- webui/index.html | 192 ++++++++++++++++----------------- webui/static/script.js | 236 +++++++++++++++++++---------------------- webui/static/style.css | 58 +++++++++- 3 files changed, 260 insertions(+), 226 deletions(-) diff --git a/webui/index.html b/webui/index.html index 00e6737e..52615159 100644 --- a/webui/index.html +++ b/webui/index.html @@ -409,61 +409,104 @@
- +
-
-
-
-

🔥 Top Charts

-

Beatport Top 100, Top 100 New Releases, Hype Top 100

- 3 Charts -
- -
-
-

🎵 Genre Explorer

-

House, Techno, Trance, and 36 more genres

- 39 Genres -
- -
-
-

📊 Staff Picks

-

Curated selections and secret weapons

- 5 Collections -
-
-
- - -
-
- - Browse Charts > Top Charts -
-
-
-
🏆
-
-

Beatport Top 100

-

The hottest electronic tracks right now

- 100 tracks + +
+

🎵 Genre Explorer

+
+
+
🎵
+
+

Browse All Genres

+

House, Techno, Trance, and 36 more genres

+ 39 Genres +
-
-
📀
-
-

Top 100 New Releases

-

Newest and most popular releases on Beatport

- 100 tracks +
+ + +
+

📊 Main Charts

+
+
+
🔥
+
+

Beatport Top 10

+

Current hottest tracks

+ 10 tracks +
+
+
+
💯
+
+

Beatport Top 100

+

Complete chart rankings

+ 100 tracks +
-
-
🚀
-
-

Hype Top 100

-

Rising stars and breakthrough tracks

- 100 tracks +
+ + +
+

🎵 Releases

+
+
+
🆕
+
+

Top 10 Releases

+

Newest releases trending

+ 10 releases +
+
+
+
📊
+
+

Top 100 Releases

+

All trending releases

+ 100 releases +
+
+
+
🕒
+
+

Latest Releases

+

Recently published

+ 50 releases +
+
+
+
+ + +
+

🔥 Hype

+
+
+
🚀
+
+

Hype Top 10

+

Hottest trending tracks

+ 10 tracks +
+
+
+
🔥
+
+

Hype Top 100

+

Complete hype chart rankings

+ 100 tracks +
+
+
+
+
+

Hype Picks

+

Editor selected hype tracks

+ 50 tracks +
@@ -692,55 +735,6 @@
- -
-
- - Browse Charts > Staff Picks -
-
-
-
-
-

Best New Tracks

-

Editor's picks of the week

- 20 tracks -
-
-
-
🔫
-
-

Secret Weapons

-

Underground gems for DJs

- 15 tracks -
-
-
-
🌅
-
-

Closing Essentials

-

Perfect tracks to end the night

- 12 tracks -
-
-
-
🔥
-
-

Peak Time Driving

-

High-energy dancefloor destroyers

- 25 tracks -
-
-
-
💎
-
-

Underground Gems

-

Hidden treasures from emerging artists

- 18 tracks -
-
-
-
diff --git a/webui/static/script.js b/webui/static/script.js index 13f935a9..7c290df4 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -9733,14 +9733,18 @@ function initializeSyncPage() { }); }); - // Logic for Beatport category cards - const beatportCategoryCards = document.querySelectorAll('.beatport-category-card'); - beatportCategoryCards.forEach(card => { - card.addEventListener('click', () => { - const category = card.dataset.category; - handleBeatportCategoryClick(category); + // Logic for Homepage Genre Explorer card + const genreExplorerCard = document.querySelector('[data-action="show-genres"]'); + if (genreExplorerCard) { + genreExplorerCard.addEventListener('click', () => { + console.log('🎵 Genre Explorer card clicked'); + showBeatportSubView('genres'); + loadBeatportGenres(); }); - }); + } + + // Setup homepage chart handlers (following genre page pattern to prevent duplicates) + setupHomepageChartTypeHandlers(); // Logic for Beatport breadcrumb back buttons const beatportBackButtons = document.querySelectorAll('.breadcrumb-back'); @@ -10188,19 +10192,12 @@ async function clearBeatportPlaylists() { function handleBeatportCategoryClick(category) { console.log(`🎵 Beatport category clicked: ${category}`); - // Show the appropriate sub-view based on category + // Only handle genres category now - homepage has direct chart buttons switch(category) { - case 'top-charts': - showBeatportSubView('top-charts'); - loadBeatportTopCharts(); // Load top charts dynamically - break; case 'genres': showBeatportSubView('genres'); loadBeatportGenres(); // Load genres dynamically break; - case 'staff-picks': - showBeatportSubView('staff-picks'); - break; default: showToast(`Unknown category: ${category}`, 'error'); } @@ -10369,159 +10366,148 @@ async function loadGenreImagesProgressively(genres) { console.log(`✅ Progressive image loading complete: ${imagesLoaded}/${genres.length} images loaded`); } -async function loadBeatportTopCharts() { - console.log('🔥 Loading Beatport top charts dynamically...'); +function setupHomepageChartTypeHandlers() { + console.log('🔧 Setting up homepage chart type handlers...'); - const chartList = document.querySelector('#beatport-top-charts-view .beatport-chart-list'); - if (!chartList) { - console.error('❌ Could not find chart list element'); + // Select all homepage chart type cards (following genre page pattern) + const chartTypeCards = document.querySelectorAll('.homepage-main-charts-section .genre-chart-type-card[data-chart-type], .homepage-releases-section .genre-chart-type-card[data-chart-type], .homepage-hype-section .genre-chart-type-card[data-chart-type]'); + + chartTypeCards.forEach(card => { + // Remove existing listeners by cloning (following genre page pattern) + card.replaceWith(card.cloneNode(true)); + }); + + // Re-select after cloning to ensure clean event listeners (following genre page pattern) + const newChartTypeCards = document.querySelectorAll('.homepage-main-charts-section .genre-chart-type-card[data-chart-type], .homepage-releases-section .genre-chart-type-card[data-chart-type], .homepage-hype-section .genre-chart-type-card[data-chart-type]'); + + newChartTypeCards.forEach(card => { + card.addEventListener('click', () => { + const chartType = card.dataset.chartType; + const chartEndpoint = card.dataset.chartEndpoint; + const chartName = card.querySelector('.chart-type-info h3').textContent; + console.log(`🔥 Homepage chart clicked: ${chartName} (${chartType})`); + handleHomepageChartTypeClick(chartType, chartEndpoint, chartName); + }); + }); + + console.log(`✅ Setup ${newChartTypeCards.length} homepage chart handlers`); +} + +async function handleHomepageChartTypeClick(chartType, chartEndpoint, chartName) { + console.log(`🔥 Homepage chart type clicked: ${chartType} (${chartName})`); + + // Map chart types to API endpoints and create descriptive names (following genre page pattern) + const chartTypeMap = { + 'top-10': { + endpoint: `/api/beatport/top-100`, // Use top-100 endpoint and limit to 10 + name: `Beatport Top 10`, + limit: 10 + }, + 'top-100': { + endpoint: `/api/beatport/top-100`, + name: `Beatport Top 100`, + limit: 100 + }, + 'releases-top-10': { + endpoint: `/api/beatport/homepage/releases-top-10`, // Placeholder for future + name: `Top 10 Releases`, + limit: 10 + }, + 'releases-top-100': { + endpoint: `/api/beatport/top-100-releases`, + name: `Top 100 Releases`, + limit: 100 + }, + 'latest-releases': { + endpoint: `/api/beatport/homepage/latest-releases`, // Placeholder for future + name: `Latest Releases`, + limit: 50 + }, + 'hype-top-10': { + endpoint: `/api/beatport/hype-top-100`, // Use hype-100 endpoint and limit to 10 + name: `Hype Top 10`, + limit: 10 + }, + 'hype-top-100': { + endpoint: `/api/beatport/hype-top-100`, + name: `Hype Top 100`, + limit: 100 + }, + 'hype-picks': { + endpoint: `/api/beatport/homepage/hype-picks`, // Placeholder for future + name: `Hype Picks`, + limit: 50 + } + }; + + const chartConfig = chartTypeMap[chartType]; + if (!chartConfig) { + console.error(`❌ Unknown homepage chart type: ${chartType}`); + showToast(`Unknown chart type: ${chartType}`, 'error'); return; } - // Show loading state - chartList.innerHTML = ` -
-
-

🔥 Discovering current Beatport top charts...

-
- `; - try { - // Define the top charts we want to show (based on our working endpoints) - const topCharts = [ - { - id: 'beatport-top-100', - name: 'Beatport Top 100', - description: 'The hottest electronic tracks right now', - icon: '🏆', - endpoint: '/api/beatport/top-100', - trackCount: '100 tracks' - }, - { - id: 'top-100-releases', - name: 'Top 100 New Releases', - description: 'Newest and most popular releases on Beatport', - icon: '📀', - endpoint: '/api/beatport/top-100-releases', - trackCount: '100 tracks' - }, - { - id: 'hype-top-100', - name: 'Hype Top 100', - description: 'Trending tracks gaining momentum', - icon: '🚀', - endpoint: '/api/beatport/hype-top-100', - trackCount: '100 tracks' - } - ]; - - // Generate chart cards dynamically - const chartCardsHTML = topCharts.map(chart => ` -
-
${chart.icon}
-
-

${chart.name}

-

${chart.description}

- ${chart.trackCount} -
-
- `).join(''); - - chartList.innerHTML = chartCardsHTML; - - // Add click handlers to dynamically created chart items - const chartItems = chartList.querySelectorAll('.beatport-chart-item'); - chartItems.forEach(item => { - item.addEventListener('click', () => { - const chartType = item.dataset.chartType; - const chartId = item.dataset.chartId; - const chartName = item.dataset.chartName; - const chartEndpoint = item.dataset.chartEndpoint; - handleBeatportTopChartClick(chartType, chartId, chartName, chartEndpoint); - }); - }); - - console.log(`✅ Loaded ${topCharts.length} Beatport top charts dynamically`); - showToast(`Loaded ${topCharts.length} top charts`, 'success'); - - } catch (error) { - console.error('❌ Error loading Beatport top charts:', error); - chartList.innerHTML = ` -
-

❌ Failed to load top charts: ${error.message}

- -
- `; - showToast(`Error loading Beatport top charts: ${error.message}`, 'error'); - } -} - -async function handleBeatportTopChartClick(chartType, chartId, chartName, chartEndpoint) { - console.log(`🔥 Beatport top chart clicked: ${chartName} - CREATING PLAYLIST CARD`); - - try { - // Check if we already have a card for this chart + // Check if we already have a card for this specific chart type (following genre page pattern) const existingState = Object.values(beatportChartStates).find(state => - state.chart && state.chart.name === chartName && state.chart.chart_type === chartType + state.chart && state.chart.name === chartConfig.name && state.chart.chart_type === `homepage_${chartType}` ); if (existingState) { - console.log(`🔄 Found existing Beatport card for ${chartName}, opening existing modal`); + console.log(`🔄 Found existing Beatport card for ${chartConfig.name}, opening existing modal`); handleBeatportCardClick(existingState.chart.hash); return; } - // First, create a chart hash for state management - const chartHash = `${chartType}_${chartId}_${Date.now()}`; + // Create a chart hash for state management (following genre page pattern) + const chartHash = `homepage_${chartType}_${Date.now()}`; - showToast(`Loading ${chartName}...`, 'info'); + showToast(`Loading ${chartConfig.name}...`, 'info'); - // Fetch tracks from the chart endpoint - const response = await fetch(`${chartEndpoint}?limit=100`); + // Fetch tracks from the specific endpoint (following genre page pattern) + const response = await fetch(`${chartConfig.endpoint}?limit=${chartConfig.limit}`); if (!response.ok) { - throw new Error(`Failed to fetch ${chartName}: ${response.status}`); + throw new Error(`Failed to fetch ${chartConfig.name}: ${response.status}`); } const data = await response.json(); if (!data.success || !data.tracks || data.tracks.length === 0) { - throw new Error(`No tracks found in ${chartName}`); + throw new Error(`No tracks found in ${chartConfig.name}`); } - // Create chart data object for playlist card + // Create chart data object for playlist card (following genre page pattern) const chartData = { hash: chartHash, - name: chartName, - chart_type: chartType, + name: chartConfig.name, + chart_type: `homepage_${chartType}`, track_count: data.tracks.length, tracks: data.tracks.map(track => ({ name: track.title || 'Unknown Title', artists: [track.artist || 'Unknown Artist'], - album: chartName, + album: chartConfig.name, duration_ms: 0, external_urls: { beatport: track.url || '' }, source: 'beatport' })) }; - // Add card to container (in background, like YouTube does) - console.log(`🃏 Creating Beatport playlist card for: ${chartName}`); + // Add card to container (in background, like YouTube does - following genre page pattern) + console.log(`🃏 Creating Beatport playlist card for: ${chartConfig.name}`); addBeatportCardToContainer(chartData); - // Automatically open discovery modal (like when you click a YouTube or Tidal card in fresh state) + // Automatically open discovery modal (like when you click a YouTube or Tidal card in fresh state - following genre page pattern) handleBeatportCardClick(chartHash); - console.log(`✅ Created Beatport card and opened discovery modal for ${chartName}`); + console.log(`✅ Created Beatport card and opened discovery modal for ${chartConfig.name}`); } catch (error) { - console.error(`❌ Error loading ${chartName}:`, error); - showToast(`Error loading ${chartName}: ${error.message}`, 'error'); + console.error(`❌ Error loading ${chartConfig.name}:`, error); + showToast(`Error loading ${chartConfig.name}: ${error.message}`, 'error'); } } + + async function openBeatportDiscoveryModal(chartHash, chartData) { console.log(`🎵 Opening Beatport discovery modal (reusing YouTube modal): ${chartData.name}`); diff --git a/webui/static/style.css b/webui/static/style.css index 5d8c9a0f..5d6e95bb 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -4893,7 +4893,11 @@ body { .genre-main-charts-section, .genre-releases-section, .genre-editorial-section, -.genre-new-charts-section { +.genre-new-charts-section, +.homepage-genre-section, +.homepage-main-charts-section, +.homepage-releases-section, +.homepage-hype-section { margin-bottom: 40px; padding: 20px; background: linear-gradient(135deg, @@ -5065,6 +5069,52 @@ body { letter-spacing: 0.5px; } +/* Empty Section Placeholder */ +.empty-section-placeholder { + display: flex; + align-items: center; + gap: 20px; + padding: 40px 20px; + background: linear-gradient(135deg, + rgba(30, 30, 30, 0.5) 0%, + rgba(20, 20, 20, 0.7) 100%); + border-radius: 12px; + border: 2px dashed rgba(255, 255, 255, 0.15); + text-align: center; + opacity: 0.8; + transition: all 0.3s ease; + min-height: 120px; + justify-content: center; +} + +.empty-section-placeholder:hover { + opacity: 1; + border-color: rgba(255, 255, 255, 0.25); + background: linear-gradient(135deg, + rgba(35, 35, 35, 0.6) 0%, + rgba(25, 25, 25, 0.8) 100%); +} + +.empty-section-icon { + font-size: 48px; + opacity: 0.6; + filter: grayscale(1); +} + +.empty-section-text h4 { + font-size: 18px; + font-weight: 600; + color: rgba(255, 255, 255, 0.8); + margin: 0 0 8px 0; +} + +.empty-section-text p { + font-size: 14px; + color: rgba(255, 255, 255, 0.5); + margin: 0; + font-style: italic; +} + .expand-indicator { position: absolute; top: 50%; @@ -5520,7 +5570,11 @@ body { .genre-main-charts-section, .genre-releases-section, .genre-editorial-section, - .genre-new-charts-section { + .genre-new-charts-section, + .homepage-genre-section, + .homepage-main-charts-section, + .homepage-releases-section, + .homepage-hype-section { margin-bottom: 24px; padding: 16px; }