Paginate wishlist modal to prevent browser crash on large wishlists (5K+)

Initial load fetches 200 tracks instead of all. "Load More" button at
bottom shows (200 of 5,000) and loads next batch on click. Backend now
returns total count alongside limited results for both album and singles
categories. Rendering logic unchanged — just operates on smaller sets.
This commit is contained in:
Broque Thomas 2026-04-05 13:15:24 -07:00
parent 5cecb46788
commit 8ba1585646
3 changed files with 56 additions and 4 deletions

View file

@ -23189,12 +23189,16 @@ def get_wishlist_tracks():
if limit and len(filtered_tracks) >= limit:
break
print(f"📊 Wishlist filter: {len(filtered_tracks)}/{len(sanitized_tracks)} tracks in '{category}' category (limit: {limit or 'none'})")
return jsonify({"tracks": filtered_tracks, "category": category})
# Count total in category (quick scan — no heavy processing, just classification)
total_in_category = sum(1 for t in sanitized_tracks if _classify_wishlist_track(t) == category)
print(f"📊 Wishlist filter: {len(filtered_tracks)}/{total_in_category} tracks in '{category}' category (limit: {limit or 'none'})")
return jsonify({"tracks": filtered_tracks, "category": category, "total": total_in_category})
# Apply limit to non-filtered results
total_count = len(sanitized_tracks)
result_tracks = sanitized_tracks[:limit] if limit else sanitized_tracks
return jsonify({"tracks": result_tracks})
return jsonify({"tracks": result_tracks, "total": total_count})
except Exception as e:
print(f"Error getting wishlist tracks: {e}")
return jsonify({"error": str(e)}), 500

View file

@ -13201,12 +13201,18 @@ async function selectWishlistCategory(category) {
tracksList.innerHTML = '<div class="loading-indicator">Loading tracks...</div>';
const response = await fetch(`/api/wishlist/tracks?category=${category}`);
const _wlPageSize = window._wlNextLimit || 200;
window._wlNextLimit = null;
const response = await fetch(`/api/wishlist/tracks?category=${category}&limit=${_wlPageSize}`);
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'Failed to fetch tracks');
const tracks = data.tracks || [];
const totalAvailable = data.total || tracks.length;
window._wlCategory = category;
window._wlOffset = tracks.length;
window._wlTotal = totalAvailable;
if (tracks.length === 0) {
tracksList.innerHTML = '<div class="empty-state">No tracks in this category</div>';
@ -13333,6 +13339,10 @@ async function selectWishlistCategory(category) {
albumsHTML += '</div>';
tracksList.innerHTML = albumsHTML;
if (totalAvailable > tracks.length) {
tracksList.insertAdjacentHTML('beforeend',
`<button class="wishlist-load-more-btn" onclick="loadMoreWishlistTracks()">Load More (${tracks.length} of ${totalAvailable})</button>`);
}
_attachWishlistDelegation(tracksList);
} else {
@ -13393,6 +13403,10 @@ async function selectWishlistCategory(category) {
});
tracksList.innerHTML = tracksHTML;
if (totalAvailable > tracks.length) {
tracksList.insertAdjacentHTML('beforeend',
`<button class="wishlist-load-more-btn" onclick="loadMoreWishlistTracks()">Load More (${tracks.length} of ${totalAvailable})</button>`);
}
_attachWishlistDelegation(tracksList);
}
@ -13402,6 +13416,16 @@ async function selectWishlistCategory(category) {
}
}
async function loadMoreWishlistTracks() {
const btn = document.querySelector('.wishlist-load-more-btn');
if (btn) { btn.textContent = 'Loading...'; btn.disabled = true; }
// Increase page size and reload
window._wlOffset = (window._wlOffset || 200) + 200;
// Override the page size for this reload
window._wlNextLimit = window._wlOffset;
selectWishlistCategory(window._wlCategory);
}
function _attachWishlistDelegation(container) {
// Single click handler for all wishlist album/track interactions
container.addEventListener('click', (e) => {

View file

@ -16298,6 +16298,30 @@ body.helper-mode-active #dashboard-activity-feed:hover {
padding: 8px 0;
}
.wishlist-load-more-btn {
display: block;
width: 100%;
padding: 12px;
margin-top: 12px;
background: rgba(138, 43, 226, 0.1);
border: 1px solid rgba(138, 43, 226, 0.3);
color: rgba(255, 255, 255, 0.7);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.wishlist-load-more-btn:hover {
background: rgba(138, 43, 226, 0.2);
color: #fff;
}
.wishlist-load-more-btn:disabled {
opacity: 0.5;
cursor: wait;
}
.wishlist-album-card {
background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);