From 8b66682f8d4319062733f0232a07edc89f78b9f9 Mon Sep 17 00:00:00 2001 From: snxraven Date: Sun, 22 Jun 2025 05:05:40 -0400 Subject: [PATCH] Add advanced search --- views/index.ejs | 178 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 26 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index d453f50..01a2be6 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -130,18 +130,15 @@ margin-top: -90px; } - /* Page container styling */ .page-container { display: none; width: 100%; - /* Ensure full width for grid */ } .page-container.active { display: block; } - /* Iframe styling */ iframe { width: 100%; height: 166px; @@ -150,7 +147,6 @@ border-radius: 4px; } - /* Ensure row and columns work correctly */ .page-container .row { display: flex; flex-wrap: wrap; @@ -161,41 +157,87 @@ .pagination-btn { margin: 0 10px; color: white; - /* Ensure text is white for consistency */ font-weight: bold; - /* Match the bold style of other UI elements */ } - /* Hover state (already defined for btn-primary, but reinforcing for clarity) */ .pagination-btn.btn-primary:hover { background-color: #ff3300; border-color: #ff3300; } - /* Disabled state */ .pagination-btn.btn-primary:disabled, .pagination-btn.btn-primary[disabled] { background-color: #444; - /* Darker, muted gray to indicate inactivity */ border-color: #444; color: #888; - /* Lighter gray text for readability */ opacity: 1; - /* Override Bootstrap's default opacity */ cursor: not-allowed; - /* Clear visual cue for disabled state */ } - /* Active state (when button is clicked, if applicable) */ .pagination-btn.btn-primary:active, .pagination-btn.btn-primary:focus { background-color: #cc4400; - /* Slightly darker orange for feedback */ border-color: #cc4400; box-shadow: 0 0 8px rgba(255, 85, 0, 0.5); - /* Subtle glow to match theme */ outline: none; - /* Remove default focus outline */ + } + + /* Search box styling */ + .search-container { + position: relative; + width: 100%; + max-width: 300px; + } + + .search-input { + background-color: #222; + border: 1px solid #444; + color: white; + border-radius: 4px; + padding: 8px 12px; + width: 100%; + transition: border-color 0.3s ease; + } + + .search-input:focus { + border-color: #ff5500; + outline: none; + box-shadow: 0 0 5px rgba(255, 85, 0, 0.3); + } + + .search-results { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: #222; + border: 1px solid #444; + border-radius: 4px; + max-height: 300px; + overflow-y: auto; + z-index: 1000; + display: none; + margin-top: 5px; + } + + .search-results.show { + display: block; + } + + .search-result-item { + padding: 10px; + color: white; + cursor: pointer; + transition: background-color 0.2s ease; + } + + .search-result-item:hover { + background-color: #ff5500; + } + + .search-result-item small { + color: #aaa; + display: block; } @@ -208,7 +250,7 @@ @@ -301,15 +347,12 @@ console.log(`Received page_data for genre: ${genre}, page: ${page}, tracks: ${tracks.length}`); const trackContainer = document.getElementById(`${genre}-tracks`); - // Initialize cache for genre if (!pageCache[genre]) { pageCache[genre] = {}; } - // Update current page currentPages[genre] = page; - // Create new page if not cached if (!pageCache[genre][page]) { console.log(`Creating new page container for genre: ${genre}, page: ${page}`); const pageContainer = document.createElement('div'); @@ -317,11 +360,9 @@ pageContainer.dataset.page = page; pageContainer.dataset.totalPages = totalPages; - // Create row for grid const row = document.createElement('div'); row.className = 'row'; - // Build track elements tracks.forEach(track => { const trackDiv = document.createElement('div'); trackDiv.className = 'col-md-6 mb-4'; @@ -347,13 +388,11 @@ lazyLoadIframes(pageContainer); } - // Switch to requested page const allPages = trackContainer.querySelectorAll('.page-container'); allPages.forEach(p => p.classList.remove('active')); pageCache[genre][page].container.classList.add('active'); console.log(`Switched to page ${page} for genre: ${genre}`); - // Update pagination controls const prevButton = document.getElementById(`${genre}-prev`); const nextButton = document.getElementById(`${genre}-next`); prevButton.disabled = page === 1; @@ -371,7 +410,6 @@ console.error('WebSocket error:', message); }); - // Pagination button handlers document.querySelectorAll('.pagination-btn').forEach(button => { button.addEventListener('click', () => { const genre = button.id.split('-')[0]; @@ -386,7 +424,6 @@ allPages.forEach(p => p.classList.remove('active')); pageCache[genre][page].container.classList.add('active'); - // Update pagination controls const prevButton = document.getElementById(`${genre}-prev`); const nextButton = document.getElementById(`${genre}-next`); const totalPages = parseInt(pageCache[genre][page].totalPages); @@ -411,6 +448,95 @@ } }); }); + + // Search functionality + const searchInput = document.querySelector('.search-input'); + const searchResults = document.getElementById('search-results'); + let allTracks = []; + const genres = ['metal', 'altrock', 'rap', 'lofi', 'edm', 'cuts']; + + // Fetch all tracks from all genres + async function fetchAllTracks() { + try { + const fetchPromises = genres.map(genre => + fetch(`/json/${genre}`) + .then(res => res.json()) + .then(tracks => tracks.map(track => ({ ...track, genre }))) + ); + const tracksArrays = await Promise.all(fetchPromises); + allTracks = tracksArrays.flat(); + console.log(`Fetched ${allTracks.length} tracks across all genres`); + } catch (err) { + console.error('Error fetching tracks:', err); + } + } + + // Initialize tracks on page load + fetchAllTracks(); + + // Debounce function to limit search frequency + function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + // Handle search + const performSearch = debounce(() => { + const query = searchInput.value.trim().toLowerCase(); + searchResults.innerHTML = ''; + if (query.length < 2) { + searchResults.classList.remove('show'); + return; + } + + const filteredTracks = allTracks.filter(track => + track.title.toLowerCase().includes(query) || + (track.description && track.description.toLowerCase().includes(query)) + ); + + if (filteredTracks.length === 0) { + searchResults.innerHTML = '
No results found
'; + } else { + filteredTracks.forEach(track => { + const resultItem = document.createElement('div'); + resultItem.className = 'search-result-item'; + resultItem.innerHTML = ` + ${track.title} + ${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)} + `; + resultItem.addEventListener('click', () => { + window.location.href = `/${track.genre}/track/${track.slug}`; + }); + searchResults.appendChild(resultItem); + }); + } + + searchResults.classList.add('show'); + }, 300); + + // Search input event listener + searchInput.addEventListener('input', performSearch); + + // Hide search results when clicking outside + document.addEventListener('click', (e) => { + if (!searchContainer.contains(e.target)) { + searchResults.classList.remove('show'); + } + }); + + // Show search results when clicking input + searchInput.addEventListener('focus', () => { + if (searchInput.value.trim().length >= 2) { + performSearch(); + } + });