Add watchlist filter to library page

main
Broque Thomas 5 days ago
parent 93d65b1ad3
commit 139b8530f4

@ -4018,7 +4018,7 @@ class MusicDatabase:
'server_source': server_source
}
def get_library_artists(self, search_query: str = "", letter: str = "", page: int = 1, limit: int = 50) -> Dict[str, Any]:
def get_library_artists(self, search_query: str = "", letter: str = "", page: int = 1, limit: int = 50, watchlist_filter: str = "all") -> Dict[str, Any]:
"""
Get artists for the library page with search, filtering, and pagination
@ -4027,6 +4027,7 @@ class MusicDatabase:
letter: Filter by first letter (a-z, #, or "" for all)
page: Page number (1-based)
limit: Number of results per page
watchlist_filter: Filter by watchlist status ("all", "watched", "unwatched")
Returns:
Dict containing artists list, pagination info, and total count
@ -4062,7 +4063,39 @@ class MusicDatabase:
where_clause = " AND ".join(where_conditions) if where_conditions else "1=1"
# Get total count (matching dashboard method)
# Pre-fetch watchlist data (small table, single fast query)
cursor.execute("SELECT spotify_artist_id, itunes_artist_id, LOWER(artist_name) as name_lower FROM watchlist_artists")
watchlist_rows = cursor.fetchall()
wl_spotify = {r['spotify_artist_id'] for r in watchlist_rows if r['spotify_artist_id']}
wl_itunes = {r['itunes_artist_id'] for r in watchlist_rows if r['itunes_artist_id']}
wl_names = {r['name_lower'] for r in watchlist_rows if r['name_lower']}
# Apply watchlist filter as WHERE conditions using IN clauses
if watchlist_filter in ("watched", "unwatched"):
match_parts = []
match_params = []
if wl_spotify:
match_parts.append(f"(a.spotify_artist_id IS NOT NULL AND a.spotify_artist_id IN ({','.join('?' * len(wl_spotify))}))")
match_params.extend(wl_spotify)
if wl_itunes:
match_parts.append(f"(a.itunes_artist_id IS NOT NULL AND a.itunes_artist_id IN ({','.join('?' * len(wl_itunes))}))")
match_params.extend(wl_itunes)
if wl_names:
match_parts.append(f"LOWER(a.name) IN ({','.join('?' * len(wl_names))})")
match_params.extend(wl_names)
if match_parts:
combined = ' OR '.join(match_parts)
if watchlist_filter == "watched":
where_clause += f" AND ({combined})"
else:
where_clause += f" AND NOT ({combined})"
params.extend(match_params)
elif watchlist_filter == "watched":
# Empty watchlist, no artists can match
where_clause += " AND 0"
# Get total count
count_query = f"""
SELECT COUNT(*) as total_count
FROM artists a
@ -4081,17 +4114,13 @@ class MusicDatabase:
a.thumb_url,
a.genres,
a.musicbrainz_id,
a.spotify_artist_id,
a.itunes_artist_id,
COUNT(DISTINCT al.id) as album_count,
COUNT(DISTINCT t.id) as track_count,
MAX(CASE WHEN wa.id IS NOT NULL THEN 1 ELSE 0 END) as is_watched
COUNT(DISTINCT t.id) as track_count
FROM artists a
LEFT JOIN albums al ON a.id = al.artist_id
LEFT JOIN tracks t ON al.id = t.album_id
LEFT JOIN watchlist_artists wa ON (
(a.spotify_artist_id IS NOT NULL AND a.spotify_artist_id = wa.spotify_artist_id)
OR (a.itunes_artist_id IS NOT NULL AND a.itunes_artist_id = wa.itunes_artist_id)
OR LOWER(a.name) = LOWER(wa.artist_name)
)
WHERE {where_clause}
GROUP BY a.id, a.name, a.thumb_url, a.genres, a.musicbrainz_id
ORDER BY a.name COLLATE NOCASE
@ -4124,6 +4153,13 @@ class MusicDatabase:
genres=genres
)
# Determine watchlist status via set lookups
is_watched = (
(row['spotify_artist_id'] and row['spotify_artist_id'] in wl_spotify)
or (row['itunes_artist_id'] and row['itunes_artist_id'] in wl_itunes)
or (row['name'] and row['name'].lower() in wl_names)
)
# Add stats
artist_data = {
'id': artist.id,
@ -4133,7 +4169,7 @@ class MusicDatabase:
'musicbrainz_id': row['musicbrainz_id'],
'album_count': row['album_count'] or 0,
'track_count': row['track_count'] or 0,
'is_watched': bool(row['is_watched'])
'is_watched': bool(is_watched)
}
artists.append(artist_data)

@ -4850,6 +4850,7 @@ def get_library_artists():
letter = request.args.get('letter', 'all')
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 75))
watchlist_filter = request.args.get('watchlist', 'all')
# Get database instance
database = get_database()
@ -4859,7 +4860,8 @@ def get_library_artists():
search_query=search_query,
letter=letter,
page=page,
limit=limit
limit=limit,
watchlist_filter=watchlist_filter
)
# Fix image URLs for all artists

@ -1712,6 +1712,13 @@
<div class="library-search-icon">🔍</div>
</div>
<!-- Watchlist Filter -->
<div class="watchlist-filter" id="watchlist-filter">
<button class="watchlist-filter-btn active" data-filter="all">All</button>
<button class="watchlist-filter-btn" data-filter="watched">Watched</button>
<button class="watchlist-filter-btn" data-filter="unwatched">Unwatched</button>
</div>
<!-- Alphabet Selector -->
<div class="alphabet-selector" id="alphabet-selector">
<div class="alphabet-selector-inner">

@ -25299,7 +25299,8 @@ const libraryPageState = {
currentLetter: "all",
currentPage: 1,
limit: 75,
debounceTimer: null
debounceTimer: null,
watchlistFilter: "all"
};
function initializeLibraryPage() {
@ -25309,6 +25310,9 @@ function initializeLibraryPage() {
// Initialize search functionality
initializeLibrarySearch();
// Initialize watchlist filter
initializeWatchlistFilter();
// Initialize alphabet selector
initializeAlphabetSelector();
@ -25358,6 +25362,25 @@ function initializeLibrarySearch() {
});
}
function initializeWatchlistFilter() {
const filterButtons = document.querySelectorAll(".watchlist-filter-btn");
filterButtons.forEach(button => {
button.addEventListener("click", () => {
const filter = button.getAttribute("data-filter");
// Update active state
filterButtons.forEach(btn => btn.classList.remove("active"));
button.classList.add("active");
// Update state and reload
libraryPageState.watchlistFilter = filter;
libraryPageState.currentPage = 1;
loadLibraryArtists();
});
});
}
function initializeAlphabetSelector() {
const alphabetButtons = document.querySelectorAll(".alphabet-btn");
@ -25408,7 +25431,8 @@ async function loadLibraryArtists() {
search: libraryPageState.currentSearch,
letter: libraryPageState.currentLetter,
page: libraryPageState.currentPage,
limit: libraryPageState.limit
limit: libraryPageState.limit,
watchlist: libraryPageState.watchlistFilter
});
// Fetch artists from API

@ -12620,6 +12620,39 @@ body {
}
/* Alphabet Selector - Apple Style Design */
/* Watchlist Filter */
.watchlist-filter {
display: flex;
gap: 8px;
justify-content: center;
padding: 8px 0;
}
.watchlist-filter-btn {
padding: 6px 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
font-weight: 600;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;
cursor: pointer;
border-radius: 20px;
transition: all 0.2s ease-out;
}
.watchlist-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);
}
.watchlist-filter-btn.active {
color: #fff;
border-color: rgba(29, 185, 84, 0.5);
background: rgba(29, 185, 84, 0.15);
}
.alphabet-selector {
overflow-x: auto;
-webkit-overflow-scrolling: touch;

Loading…
Cancel
Save