Add watchlist filter to library page
This commit is contained in:
parent
93d65b1ad3
commit
139b8530f4
5 changed files with 115 additions and 13 deletions
|
|
@ -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…
Reference in a new issue