watchlist redesign

This commit is contained in:
Broque Thomas 2026-03-01 20:35:49 -08:00
parent 046233817d
commit 3dad2eae38
3 changed files with 235 additions and 142 deletions

View file

@ -1405,36 +1405,37 @@
text-align: center; text-align: center;
} }
/* Inline remove buttons - compact sizing */ /* Watchlist fullscreen modal on mobile */
.watchlist-remove-btn { .watchlist-fullscreen {
width: auto; max-width: 100vw;
min-height: auto; width: 100vw;
padding: 6px 12px; max-height: 100vh;
font-size: 12px; border-radius: 0;
min-width: auto;
flex-shrink: 0;
} }
/* Watchlist artist items */ /* Watchlist card grid on mobile */
.watchlist-artist-item { .watchlist-artists-grid {
padding: 12px 14px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px; gap: 16px;
padding: 16px;
} }
.watchlist-artist-image, .watchlist-card-info {
.watchlist-artist-image-placeholder { padding: 10px 12px;
width: 44px;
height: 44px;
margin-right: 10px;
} }
.watchlist-artist-name { .watchlist-card-name {
font-size: 14px; font-size: 13px;
} }
.watchlist-artist-date, .watchlist-card-meta {
.watchlist-artist-scan { font-size: 10px;
font-size: 11px; }
/* Show checkbox and remove always on mobile (no hover) */
.watchlist-card-checkbox,
.watchlist-artist-card .watchlist-card-remove {
opacity: 1;
} }
.watchlist-search-container { .watchlist-search-container {
@ -1510,7 +1511,7 @@
/* --- Phase 9: Touch & Hover Adaptations --- */ /* --- Phase 9: Touch & Hover Adaptations --- */
/* Global touch targets - only standalone/action buttons, not inline */ /* Global touch targets - only standalone/action buttons, not inline */
button:not(.watchlist-remove-btn):not(.wishlist-delete-btn):not(.wishlist-delete-album-btn):not(.wishlist-delete-btn-small):not(.wishlist-back-btn):not(.alphabet-btn):not(.filter-btn):not(.playlist-modal-close) { button:not(.watchlist-card-remove):not(.wishlist-delete-btn):not(.wishlist-delete-album-btn):not(.wishlist-delete-btn-small):not(.wishlist-back-btn):not(.alphabet-btn):not(.filter-btn):not(.playlist-modal-close) {
min-height: 38px; min-height: 38px;
} }
@ -1790,25 +1791,19 @@
font-size: 13px; font-size: 13px;
} }
/* Watchlist items even tighter */ /* Watchlist cards — tighter grid on small screens */
.watchlist-artist-item { .watchlist-artists-grid {
padding: 10px 12px; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 12px;
padding: 12px;
} }
.watchlist-artist-image, .watchlist-card-name {
.watchlist-artist-image-placeholder { font-size: 12px;
width: 38px;
height: 38px;
margin-right: 8px;
} }
.watchlist-artist-name { .watchlist-card-info {
font-size: 13px; padding: 8px 10px;
}
.watchlist-remove-btn {
padding: 5px 10px;
font-size: 11px;
} }
/* Download modal - even more compact */ /* Download modal - even more compact */

View file

@ -25766,7 +25766,7 @@ async function showWatchlistModal() {
// Build modal content // Build modal content
modal.innerHTML = ` modal.innerHTML = `
<div class="modal-container playlist-modal"> <div class="modal-container playlist-modal watchlist-fullscreen">
<div class="playlist-modal-header"> <div class="playlist-modal-header">
<div class="playlist-header-content" style="width: 100%;"> <div class="playlist-header-content" style="width: 100%;">
<h2>👁 Watchlist</h2> <h2>👁 Watchlist</h2>
@ -25816,7 +25816,7 @@ async function showWatchlistModal() {
</div> </div>
<div class="playlist-modal-body"> <div class="playlist-modal-body">
<div class="watchlist-actions" style="margin-bottom: 20px; display: flex; gap: 12px; align-items: center;"> <div class="watchlist-actions" style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; padding: 0 32px; flex-wrap: wrap;">
<button class="playlist-modal-btn playlist-modal-btn-primary" <button class="playlist-modal-btn playlist-modal-btn-primary"
id="scan-watchlist-btn" id="scan-watchlist-btn"
onclick="startWatchlistScan()" onclick="startWatchlistScan()"
@ -25838,14 +25838,14 @@ async function showWatchlistModal() {
</div> </div>
${globalOverrideActive ? ` ${globalOverrideActive ? `
<div class="watchlist-global-override-banner"> <div class="watchlist-global-override-banner" style="margin: 0 32px 12px;">
<span></span> <span></span>
<span>Global override is active per-artist settings are being ignored during scans.</span> <span>Global override is active per-artist settings are being ignored during scans.</span>
</div> </div>
` : ''} ` : ''}
<!-- Search Bar --> <!-- Search Bar -->
<div class="watchlist-search-container" style="margin-bottom: 16px;"> <div class="watchlist-search-container" style="margin-bottom: 12px;">
<input type="text" <input type="text"
id="watchlist-search-input" id="watchlist-search-input"
class="watchlist-search-input" class="watchlist-search-input"
@ -25863,58 +25863,50 @@ async function showWatchlistModal() {
</button> </button>
</div> </div>
<div class="watchlist-artists-list" id="watchlist-artists-list"> <div class="watchlist-artists-grid" id="watchlist-artists-list">
${artistsData.artists.map(artist => ` ${artistsData.artists.map(artist => `
<div class="watchlist-artist-item" <div class="watchlist-artist-card"
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}" data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}" data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}">
style="cursor: pointer;">
<label class="watchlist-checkbox-wrapper" onclick="event.stopPropagation();"> <label class="watchlist-card-checkbox" onclick="event.stopPropagation();">
<input type="checkbox" class="watchlist-select-cb" <input type="checkbox" class="watchlist-select-cb"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}" data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}" data-artist-name="${escapeHtml(artist.artist_name)}"
onchange="updateWatchlistBatchBar()"> onchange="updateWatchlistBatchBar()">
<span class="watchlist-checkbox-custom"></span> <span class="watchlist-checkbox-custom"></span>
</label> </label>
${artist.image_url ? `
<img src="${artist.image_url}" <button class="watchlist-card-remove"
alt="${escapeHtml(artist.artist_name)}"
class="watchlist-artist-image"
loading="lazy"
onerror="this.style.display='none'">
` : `
<div class="watchlist-artist-image-placeholder">
🎤
</div>
`}
<div class="watchlist-artist-info">
<span class="watchlist-artist-name">${escapeHtml(artist.artist_name)}</span>
<span class="watchlist-artist-date">Added ${new Date(artist.date_added).toLocaleDateString()}</span>
${artist.last_scan_timestamp ? `
<span class="watchlist-artist-scan">Last scanned ${new Date(artist.last_scan_timestamp).toLocaleDateString()}</span>
` : ''}
</div>
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-remove-btn"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}" data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}" data-artist-name="${escapeHtml(artist.artist_name)}"
onclick="event.stopPropagation();"> onclick="event.stopPropagation();"
Remove title="Remove from watchlist">&times;</button>
</button>
<div class="watchlist-card-image">
${artist.image_url ? `
<img src="${artist.image_url}"
alt="${escapeHtml(artist.artist_name)}"
loading="lazy"
onerror="this.parentElement.innerHTML='<div class=\\'watchlist-card-image-fallback\\'>🎤</div>';">
` : `
<div class="watchlist-card-image-fallback">🎤</div>
`}
</div>
<div class="watchlist-card-info">
<span class="watchlist-card-name">${escapeHtml(artist.artist_name)}</span>
<span class="watchlist-card-meta">Added ${new Date(artist.date_added).toLocaleDateString()}</span>
</div>
</div> </div>
`).join('')} `).join('')}
</div> </div>
</div> </div>
<div class="playlist-modal-footer">
<button class="playlist-modal-btn playlist-modal-btn-secondary" onclick="closeWatchlistModal()">
Close
</button>
</div>
</div> </div>
`; `;
// Add event listeners for remove buttons // Add event listeners for remove buttons
modal.querySelectorAll('.watchlist-remove-btn').forEach(button => { modal.querySelectorAll('.watchlist-card-remove').forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
const artistId = button.getAttribute('data-artist-id'); const artistId = button.getAttribute('data-artist-id');
const artistName = button.getAttribute('data-artist-name'); const artistName = button.getAttribute('data-artist-name');
@ -25922,16 +25914,16 @@ async function showWatchlistModal() {
}); });
}); });
// Add click handlers to artist items (except for remove button or checkbox) // Add click handlers to artist cards (except for remove button or checkbox)
modal.querySelectorAll('.watchlist-artist-item').forEach(item => { modal.querySelectorAll('.watchlist-artist-card').forEach(item => {
item.addEventListener('click', (e) => { item.addEventListener('click', (e) => {
// Don't trigger if clicking the remove button or checkbox // Don't trigger if clicking the remove button or checkbox
if (e.target.closest('.watchlist-remove-btn') || e.target.closest('.watchlist-checkbox-wrapper')) { if (e.target.closest('.watchlist-card-remove') || e.target.closest('.watchlist-card-checkbox')) {
return; return;
} }
const artistId = item.getAttribute('data-artist-id'); const artistId = item.getAttribute('data-artist-id');
const artistName = item.querySelector('.watchlist-artist-name').textContent; const artistName = item.querySelector('.watchlist-card-name').textContent;
console.log(`🎵 Artist card clicked: ${artistName} (${artistId})`); console.log(`🎵 Artist card clicked: ${artistName} (${artistId})`);
openWatchlistArtistConfigModal(artistId, artistName); openWatchlistArtistConfigModal(artistId, artistName);
@ -26402,13 +26394,13 @@ function filterWatchlistArtists() {
if (!searchInput || !artistsList) return; if (!searchInput || !artistsList) return;
const searchTerm = searchInput.value.toLowerCase().trim(); const searchTerm = searchInput.value.toLowerCase().trim();
const artistItems = artistsList.querySelectorAll('.watchlist-artist-item'); const artistItems = artistsList.querySelectorAll('.watchlist-artist-card');
artistItems.forEach(item => { artistItems.forEach(item => {
const artistName = item.getAttribute('data-artist-name'); const artistName = item.getAttribute('data-artist-name');
if (!searchTerm || artistName.includes(searchTerm)) { if (!searchTerm || artistName.includes(searchTerm)) {
item.style.display = 'flex'; item.style.display = '';
} else { } else {
item.style.display = 'none'; item.style.display = 'none';
} }
@ -26736,7 +26728,7 @@ async function removeFromWatchlistModal(artistId, artistName) {
*/ */
function getVisibleCheckedWatchlist() { function getVisibleCheckedWatchlist() {
return Array.from(document.querySelectorAll('.watchlist-select-cb:checked')).filter(cb => { return Array.from(document.querySelectorAll('.watchlist-select-cb:checked')).filter(cb => {
const item = cb.closest('.watchlist-artist-item'); const item = cb.closest('.watchlist-artist-card');
return item && item.style.display !== 'none'; return item && item.style.display !== 'none';
}); });
} }

View file

@ -8708,34 +8708,178 @@ body {
} }
/* Watchlist Modal Styles */ /* Watchlist Modal Styles */
.watchlist-artists-list {
max-height: 400px; /* Fullscreen modifier for watchlist modal */
overflow-y: auto; .watchlist-fullscreen {
margin-top: 20px; max-width: 95vw;
padding: 30px; width: 95vw;
max-height: 95vh;
} }
.watchlist-artist-item { /* Override playlist-modal-body within watchlist to allow grid scrolling */
.watchlist-fullscreen .playlist-modal-body {
overflow-y: auto;
padding: 0;
min-height: 0;
}
/* Card grid */
.watchlist-artists-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 24px;
padding: 24px 32px 32px;
}
/* Individual artist card */
.watchlist-artist-card {
position: relative;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
background: rgba(18, 18, 18, 1);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.watchlist-artist-card:hover {
transform: translateY(-4px);
border-color: rgba(var(--accent-rgb), 0.25);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5),
0 0 20px rgba(var(--accent-rgb), 0.1);
}
.watchlist-artist-card:active {
transform: translateY(-1px);
}
/* Image container — normal flow, sets the card height */
.watchlist-card-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: linear-gradient(135deg,
rgba(30, 30, 30, 1) 0%,
rgba(20, 20, 20, 1) 100%);
}
.watchlist-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Image fallback */
.watchlist-card-image-fallback {
width: 100%;
height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
padding: 16px 20px 16px 24px; font-size: 56px;
margin-bottom: 12px; color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
transition: all 0.2s ease;
} }
.watchlist-artist-item:hover { /* Info below the image — normal flow */
background: rgba(255, 255, 255, 0.06); .watchlist-card-info {
border-color: rgba(var(--accent-rgb), 0.2); padding: 14px 16px;
transform: translateY(-1px); display: flex;
flex-direction: column;
gap: 3px;
background: rgba(18, 18, 18, 1);
}
.watchlist-card-name {
font-size: 15px;
font-weight: 700;
color: #ffffff;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.watchlist-card-meta {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
font-weight: 500;
}
/* Checkbox — top-left, hidden until hover or checked */
.watchlist-card-checkbox {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
opacity: 0;
transition: opacity 0.15s ease;
cursor: pointer;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
.watchlist-card-checkbox input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.watchlist-card-checkbox input:checked+.watchlist-checkbox-custom {
background: rgba(var(--accent-rgb), 0.3);
border-color: rgb(var(--accent-rgb));
}
.watchlist-card-checkbox input:checked+.watchlist-checkbox-custom::after {
content: '✓';
color: rgb(var(--accent-rgb));
font-size: 13px;
font-weight: 700;
}
.watchlist-artist-card:hover .watchlist-card-checkbox,
.watchlist-card-checkbox:has(input:checked) {
opacity: 1;
}
/* Remove × button — top-right, visible on hover */
.watchlist-card-remove {
position: absolute;
top: 10px;
right: 10px;
z-index: 3;
opacity: 0;
transition: opacity 0.15s ease, background 0.15s ease;
width: 30px;
height: 30px;
border-radius: 50%;
border: none;
background: rgba(0, 0, 0, 0.55);
color: rgba(255, 255, 255, 0.7);
font-size: 18px;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.watchlist-artist-card:hover .watchlist-card-remove {
opacity: 1;
}
.watchlist-card-remove:hover {
background: rgba(255, 59, 48, 0.7);
color: #ffffff;
} }
/* Watchlist Search */ /* Watchlist Search */
.watchlist-search-container { .watchlist-search-container {
padding: 0 30px; padding: 0 32px;
} }
.watchlist-search-input { .watchlist-search-input {
@ -8747,7 +8891,7 @@ body {
color: #ffffff; color: #ffffff;
font-size: 14px; font-size: 14px;
font-family: inherit; font-family: inherit;
transition: all 0.2s ease; transition: border-color 0.2s ease, box-shadow 0.2s ease;
} }
.watchlist-search-input:focus { .watchlist-search-input:focus {
@ -8761,44 +8905,6 @@ body {
color: rgba(255, 255, 255, 0.4); color: rgba(255, 255, 255, 0.4);
} }
/* Artist Image */
.watchlist-artist-image {
width: 56px;
height: 56px;
border-radius: 50%;
object-fit: cover;
margin-right: 16px;
flex-shrink: 0;
border: 2px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
.watchlist-artist-item:hover .watchlist-artist-image {
border-color: rgba(var(--accent-rgb), 0.4);
transform: scale(1.05);
}
.watchlist-artist-image-placeholder {
width: 56px;
height: 56px;
border-radius: 50%;
margin-right: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.2), rgba(var(--accent-rgb), 0.05));
border: 2px solid rgba(var(--accent-rgb), 0.3);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
transition: all 0.2s ease;
}
.watchlist-artist-item:hover .watchlist-artist-image-placeholder {
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.3), rgba(var(--accent-rgb), 0.1));
border-color: rgba(var(--accent-rgb), 0.5);
transform: scale(1.05);
}
/* Watchlist & Wishlist Batch Action Bar */ /* Watchlist & Wishlist Batch Action Bar */
.watchlist-batch-bar, .watchlist-batch-bar,
.wishlist-batch-bar { .wishlist-batch-bar {
@ -8806,7 +8912,7 @@ body {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 10px 20px; padding: 10px 20px;
margin: 0 30px 12px; margin: 0 32px 12px;
background: rgba(255, 59, 48, 0.1); background: rgba(255, 59, 48, 0.1);
border: 1px solid rgba(255, 59, 48, 0.25); border: 1px solid rgba(255, 59, 48, 0.25);
border-radius: 10px; border-radius: 10px;