Add discography filters to artist detail page
Introduce interactive discography filters on the artist detail page. Adds filter UI markup (category, content, ownership) in index.html and styles in style.css. Implements filter state, initialization, reset and apply logic in script.js; tags release cards with data attributes (live/compilation/featured) using regex heuristics, updates visibility and per-section owned/missing counts, and re-applies filters when releases are updated. Integrates filter setup into page init and resets filters when loading artist data.
This commit is contained in:
parent
fff8e62070
commit
44b5032f17
3 changed files with 230 additions and 0 deletions
|
|
@ -2061,6 +2061,30 @@
|
|||
<!-- Main Content -->
|
||||
<div class="artist-detail-main" id="artist-detail-main">
|
||||
|
||||
<!-- Discography Filters -->
|
||||
<div class="discography-filters" id="discography-filters">
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">Show</span>
|
||||
<button class="discography-filter-btn active" data-filter="category" data-value="albums">Albums</button>
|
||||
<button class="discography-filter-btn active" data-filter="category" data-value="eps">EPs</button>
|
||||
<button class="discography-filter-btn active" data-filter="category" data-value="singles">Singles</button>
|
||||
</div>
|
||||
<div class="filter-divider"></div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">Include</span>
|
||||
<button class="discography-filter-btn active" data-filter="content" data-value="live">Live</button>
|
||||
<button class="discography-filter-btn active" data-filter="content" data-value="compilations">Compilations</button>
|
||||
<button class="discography-filter-btn active" data-filter="content" data-value="featured">Featured</button>
|
||||
</div>
|
||||
<div class="filter-divider"></div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">Status</span>
|
||||
<button class="discography-filter-btn active" data-filter="ownership" data-value="all">All</button>
|
||||
<button class="discography-filter-btn" data-filter="ownership" data-value="owned">Owned</button>
|
||||
<button class="discography-filter-btn" data-filter="ownership" data-value="missing">Missing</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discography Sections -->
|
||||
<div class="discography-sections">
|
||||
<!-- Albums Section -->
|
||||
|
|
|
|||
|
|
@ -27085,6 +27085,13 @@ let artistDetailPageState = {
|
|||
currentArtistName: null
|
||||
};
|
||||
|
||||
// Discography filter state
|
||||
let discographyFilterState = {
|
||||
categories: { albums: true, eps: true, singles: true },
|
||||
content: { live: true, compilations: true, featured: true },
|
||||
ownership: 'all' // 'all', 'owned', 'missing'
|
||||
};
|
||||
|
||||
function navigateToArtistDetail(artistId, artistName) {
|
||||
console.log(`🎵 Navigating to artist detail: ${artistName} (ID: ${artistId})`);
|
||||
|
||||
|
|
@ -27140,6 +27147,9 @@ function initializeArtistDetailPage() {
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize discography filter buttons
|
||||
initializeDiscographyFilters();
|
||||
|
||||
artistDetailPageState.isInitialized = true;
|
||||
console.log("✅ Artist Detail page initialized successfully");
|
||||
}
|
||||
|
|
@ -27147,6 +27157,9 @@ function initializeArtistDetailPage() {
|
|||
async function loadArtistDetailData(artistId, artistName) {
|
||||
console.log(`🔄 Loading artist detail data for: ${artistName} (ID: ${artistId})`);
|
||||
|
||||
// Reset discography filters to defaults
|
||||
resetDiscographyFilters();
|
||||
|
||||
// Show loading state and hide all content
|
||||
showArtistDetailLoading(true);
|
||||
showArtistDetailError(false);
|
||||
|
|
@ -27491,6 +27504,9 @@ function populateDiscographySections(discography) {
|
|||
|
||||
// Populate singles
|
||||
populateReleaseSection('singles', discography.singles);
|
||||
|
||||
// Apply any active filters after populating
|
||||
applyDiscographyFilters();
|
||||
}
|
||||
|
||||
function populateReleaseSection(sectionType, releases) {
|
||||
|
|
@ -27540,6 +27556,18 @@ function createReleaseCard(release) {
|
|||
// Store mutable reference so stream updates propagate to click handler
|
||||
card._releaseData = release;
|
||||
|
||||
// Tag card for content-type filtering
|
||||
const titleLower = (release.title || '').toLowerCase();
|
||||
const livePattern = /\b(live)\b|\(live[^)]*\)|\[live[^]]*\]/i;
|
||||
const compilationPattern = /\b(greatest hits|best of|collection|anthology|essential)\b/i;
|
||||
const featuredPattern = /\(?\bfeat\.?\s|\bft\.?\s|\bfeaturing\b/i;
|
||||
const isLive = livePattern.test(release.title || '') || (release.album_type === 'compilation' && livePattern.test(release.title || ''));
|
||||
const isCompilation = (release.album_type === 'compilation') || compilationPattern.test(release.title || '');
|
||||
const isFeatured = featuredPattern.test(release.title || '');
|
||||
card.setAttribute("data-is-live", isLive ? "true" : "false");
|
||||
card.setAttribute("data-is-compilation", isCompilation ? "true" : "false");
|
||||
card.setAttribute("data-is-featured", isFeatured ? "true" : "false");
|
||||
|
||||
// Add MusicBrainz icon if available
|
||||
let mbIcon = null;
|
||||
if (release.musicbrainz_release_id) {
|
||||
|
|
@ -27969,6 +27997,9 @@ function updateLibraryReleaseCard(data) {
|
|||
formatsDiv.innerHTML = data.formats.map(f => `<span class="release-format-tag">${f}</span>`).join('');
|
||||
card.appendChild(formatsDiv);
|
||||
}
|
||||
|
||||
// Re-apply filters so newly resolved cards respect active filters
|
||||
applyDiscographyFilters();
|
||||
}
|
||||
|
||||
function updateCategoryStatsFromStream(category, ownedCount, missingCount) {
|
||||
|
|
@ -28066,6 +28097,125 @@ function recalculateSummaryStats(artistFormatSet) {
|
|||
}
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// Discography Filter Functions
|
||||
// ===============================================
|
||||
|
||||
function initializeDiscographyFilters() {
|
||||
const container = document.getElementById('discography-filters');
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.discography-filter-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const filterType = btn.dataset.filter;
|
||||
const value = btn.dataset.value;
|
||||
|
||||
if (filterType === 'category') {
|
||||
// Multi-toggle: toggle this category on/off
|
||||
btn.classList.toggle('active');
|
||||
discographyFilterState.categories[value] = btn.classList.contains('active');
|
||||
} else if (filterType === 'content') {
|
||||
// Multi-toggle: toggle this content type on/off
|
||||
btn.classList.toggle('active');
|
||||
discographyFilterState.content[value] = btn.classList.contains('active');
|
||||
} else if (filterType === 'ownership') {
|
||||
// Single-select: deactivate siblings, activate this one
|
||||
container.querySelectorAll('[data-filter="ownership"]').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
discographyFilterState.ownership = value;
|
||||
}
|
||||
|
||||
applyDiscographyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
function resetDiscographyFilters() {
|
||||
discographyFilterState.categories = { albums: true, eps: true, singles: true };
|
||||
discographyFilterState.content = { live: true, compilations: true, featured: true };
|
||||
discographyFilterState.ownership = 'all';
|
||||
|
||||
// Reset button visual states
|
||||
const container = document.getElementById('discography-filters');
|
||||
if (!container) return;
|
||||
container.querySelectorAll('.discography-filter-btn').forEach(btn => {
|
||||
const filterType = btn.dataset.filter;
|
||||
const value = btn.dataset.value;
|
||||
if (filterType === 'ownership') {
|
||||
btn.classList.toggle('active', value === 'all');
|
||||
} else {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyDiscographyFilters() {
|
||||
const categories = ['albums', 'eps', 'singles'];
|
||||
|
||||
for (const cat of categories) {
|
||||
const section = document.getElementById(`${cat}-section`);
|
||||
if (!section) continue;
|
||||
|
||||
// Category toggle — hide entire section
|
||||
if (!discographyFilterState.categories[cat]) {
|
||||
section.style.display = 'none';
|
||||
continue;
|
||||
}
|
||||
section.style.display = '';
|
||||
|
||||
// Filter individual cards within the section
|
||||
const grid = document.getElementById(`${cat}-grid`);
|
||||
if (!grid) continue;
|
||||
|
||||
let visibleOwned = 0;
|
||||
let visibleMissing = 0;
|
||||
let visibleCount = 0;
|
||||
|
||||
grid.querySelectorAll('.release-card').forEach(card => {
|
||||
let hidden = false;
|
||||
|
||||
// Content filters
|
||||
if (!discographyFilterState.content.live && card.getAttribute('data-is-live') === 'true') {
|
||||
hidden = true;
|
||||
}
|
||||
if (!discographyFilterState.content.compilations && card.getAttribute('data-is-compilation') === 'true') {
|
||||
hidden = true;
|
||||
}
|
||||
if (!discographyFilterState.content.featured && card.getAttribute('data-is-featured') === 'true') {
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
// Ownership filter (only apply if card is not still checking)
|
||||
if (!hidden && discographyFilterState.ownership !== 'all' && card._releaseData) {
|
||||
const owned = card._releaseData.owned;
|
||||
if (owned !== null) { // Don't hide cards still being checked
|
||||
if (discographyFilterState.ownership === 'owned' && !owned) hidden = true;
|
||||
if (discographyFilterState.ownership === 'missing' && owned) hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
card.style.display = hidden ? 'none' : '';
|
||||
|
||||
// Count visible cards for stats
|
||||
if (!hidden && card._releaseData) {
|
||||
visibleCount++;
|
||||
if (card._releaseData.owned === true) visibleOwned++;
|
||||
else if (card._releaseData.owned === false) visibleMissing++;
|
||||
}
|
||||
});
|
||||
|
||||
// Update section stats to reflect filtered view
|
||||
const ownedEl = document.getElementById(`${cat}-owned-count`);
|
||||
const missingEl = document.getElementById(`${cat}-missing-count`);
|
||||
if (ownedEl) ownedEl.textContent = `${visibleOwned} owned`;
|
||||
if (missingEl) missingEl.textContent = `${visibleMissing} missing`;
|
||||
|
||||
// Hide section entirely if all cards are hidden
|
||||
section.style.display = visibleCount === 0 ? 'none' : '';
|
||||
}
|
||||
}
|
||||
|
||||
// UI state management functions
|
||||
function showArtistDetailLoading(show) {
|
||||
const loadingElement = document.getElementById("artist-detail-loading");
|
||||
|
|
|
|||
|
|
@ -13566,6 +13566,62 @@ body {
|
|||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Discography Filters */
|
||||
.discography-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.filter-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.discography-filter-btn {
|
||||
padding: 5px 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.discography-filter-btn:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.discography-filter-btn.active {
|
||||
color: #fff;
|
||||
border-color: rgba(29, 185, 84, 0.5);
|
||||
background: rgba(29, 185, 84, 0.15);
|
||||
}
|
||||
|
||||
/* Discography Sections */
|
||||
.discography-sections {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in a new issue