Redesign watchlist modal with enriched artist detail view
This commit is contained in:
parent
d06b7e5a25
commit
84de4ad16b
4 changed files with 811 additions and 37 deletions
|
|
@ -21527,7 +21527,14 @@ def get_watchlist_artists():
|
|||
"created_at": artist.created_at.isoformat() if artist.created_at else None,
|
||||
"updated_at": artist.updated_at.isoformat() if artist.updated_at else None,
|
||||
"image_url": artist.image_url, # Cached during watchlist scans
|
||||
"itunes_artist_id": artist.itunes_artist_id # For iTunes-only artists
|
||||
"itunes_artist_id": artist.itunes_artist_id, # For iTunes-only artists
|
||||
"include_albums": artist.include_albums,
|
||||
"include_eps": artist.include_eps,
|
||||
"include_singles": artist.include_singles,
|
||||
"include_live": artist.include_live,
|
||||
"include_remixes": artist.include_remixes,
|
||||
"include_acoustic": artist.include_acoustic,
|
||||
"include_compilations": artist.include_compilations,
|
||||
})
|
||||
|
||||
return jsonify({"success": True, "artists": artists_data})
|
||||
|
|
@ -22362,7 +22369,8 @@ def watchlist_artist_config(artist_id):
|
|||
cursor.execute("""
|
||||
SELECT include_albums, include_eps, include_singles,
|
||||
include_live, include_remixes, include_acoustic, include_compilations,
|
||||
artist_name, image_url, spotify_artist_id, itunes_artist_id
|
||||
artist_name, image_url, spotify_artist_id, itunes_artist_id,
|
||||
last_scan_timestamp, date_added
|
||||
FROM watchlist_artists
|
||||
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
|
||||
""", (artist_id, artist_id))
|
||||
|
|
@ -22405,6 +22413,53 @@ def watchlist_artist_config(artist_id):
|
|||
'genres': []
|
||||
}
|
||||
|
||||
# Enrich with library artist data (banner, bio, style, mood, label)
|
||||
try:
|
||||
conn2 = sqlite3.connect(str(database.database_path))
|
||||
cur2 = conn2.cursor()
|
||||
cur2.execute("""
|
||||
SELECT banner_url, summary, style, mood, label, genres
|
||||
FROM artists
|
||||
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
|
||||
LIMIT 1
|
||||
""", (artist_id, artist_id))
|
||||
lib_row = cur2.fetchone()
|
||||
if lib_row:
|
||||
artist_info['banner_url'] = lib_row[0]
|
||||
artist_info['summary'] = lib_row[1]
|
||||
artist_info['style'] = lib_row[2]
|
||||
artist_info['mood'] = lib_row[3]
|
||||
artist_info['label'] = lib_row[4]
|
||||
# Backfill genres from library if Spotify didn't provide any
|
||||
if not artist_info.get('genres') and lib_row[5]:
|
||||
try:
|
||||
artist_info['genres'] = json.loads(lib_row[5])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
# Get recent releases for this watchlist artist
|
||||
cur2.execute("""
|
||||
SELECT rr.album_name, rr.release_date, rr.album_cover_url, rr.track_count
|
||||
FROM recent_releases rr
|
||||
JOIN watchlist_artists wa ON rr.watchlist_artist_id = wa.id
|
||||
WHERE wa.spotify_artist_id = ? OR wa.itunes_artist_id = ?
|
||||
ORDER BY rr.release_date DESC
|
||||
LIMIT 6
|
||||
""", (artist_id, artist_id))
|
||||
releases = [
|
||||
{
|
||||
'album_name': r[0],
|
||||
'release_date': r[1],
|
||||
'album_cover_url': r[2],
|
||||
'track_count': r[3],
|
||||
}
|
||||
for r in cur2.fetchall()
|
||||
]
|
||||
conn2.close()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not enrich artist from library: {e}")
|
||||
releases = []
|
||||
|
||||
config = {
|
||||
'include_albums': bool(result[0]), # Convert INTEGER to boolean
|
||||
'include_eps': bool(result[1]),
|
||||
|
|
@ -22412,13 +22467,16 @@ def watchlist_artist_config(artist_id):
|
|||
'include_live': bool(result[3]),
|
||||
'include_remixes': bool(result[4]),
|
||||
'include_acoustic': bool(result[5]),
|
||||
'include_compilations': bool(result[6])
|
||||
'include_compilations': bool(result[6]),
|
||||
'last_scan_timestamp': result[11],
|
||||
'date_added': result[12],
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"config": config,
|
||||
"artist": artist_info
|
||||
"artist": artist_info,
|
||||
"recent_releases": releases
|
||||
})
|
||||
|
||||
else: # POST
|
||||
|
|
|
|||
|
|
@ -1486,12 +1486,34 @@
|
|||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Show checkbox and remove always on mobile (no hover) */
|
||||
/* Show checkbox, remove, and gear always on mobile (no hover) */
|
||||
.watchlist-card-checkbox,
|
||||
.watchlist-artist-card .watchlist-card-remove {
|
||||
.watchlist-artist-card .watchlist-card-remove,
|
||||
.watchlist-artist-card .watchlist-card-gear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-genres {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.watchlist-detail-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.watchlist-detail-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.watchlist-search-container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
|
@ -1904,6 +1926,32 @@
|
|||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.watchlist-pill {
|
||||
font-size: 8px;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.watchlist-card-pills {
|
||||
padding: 0 10px 6px;
|
||||
}
|
||||
|
||||
.watchlist-detail-content {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.watchlist-detail-releases {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Download modal - even more compact */
|
||||
.download-missing-modal-hero {
|
||||
padding: 10px 12px;
|
||||
|
|
|
|||
|
|
@ -26757,25 +26757,25 @@ async function showWatchlistModal() {
|
|||
</div>
|
||||
<div class="playlist-modal-sync-status" id="watchlist-scan-status" style="display: ${scanStatus !== 'idle' ? 'flex' : 'none'}; flex-direction: column; align-items: center;">
|
||||
<!-- Live Visual Activity Display -->
|
||||
<div id="watchlist-live-activity" style="display: ${scanStatus === 'scanning' ? 'flex' : 'none'}; gap: 15px; margin-top: 15px; padding: 15px; background: #2a2a2a; border-radius: 8px; border: 1px solid #444; justify-content: center; align-items: flex-start;">
|
||||
<div id="watchlist-live-activity" class="watchlist-live-activity" style="display: ${scanStatus === 'scanning' ? 'flex' : 'none'};">
|
||||
<!-- Artist Photo -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||
<img id="watchlist-artist-img" src="" alt="Artist" style="width: 80px; height: 80px; border-radius: 50%; border: 2px solid rgb(var(--accent-rgb)); object-fit: cover; background: #1a1a1a;" onerror="this.style.display='none';" />
|
||||
<div id="watchlist-artist-name" style="font-size: 11px; font-weight: bold; color: #fff; text-align: center; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Waiting...</div>
|
||||
<div class="watchlist-live-activity-col">
|
||||
<img id="watchlist-artist-img" class="watchlist-live-activity-artist-img" src="" alt="Artist" onerror="this.style.display='none';" />
|
||||
<div id="watchlist-artist-name" class="watchlist-live-activity-label">Waiting...</div>
|
||||
</div>
|
||||
|
||||
<!-- Album Cover -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||
<img id="watchlist-album-img" src="" alt="Album" style="width: 80px; height: 80px; border-radius: 6px; border: 2px solid #ffc107; object-fit: cover; background: #1a1a1a;" onerror="this.style.display='none';" />
|
||||
<div id="watchlist-album-name" style="font-size: 11px; font-weight: bold; color: #fff; text-align: center; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Waiting...</div>
|
||||
<div class="watchlist-live-activity-col">
|
||||
<img id="watchlist-album-img" class="watchlist-live-activity-album-img" src="" alt="Album" onerror="this.style.display='none';" />
|
||||
<div id="watchlist-album-name" class="watchlist-live-activity-label">Waiting...</div>
|
||||
</div>
|
||||
|
||||
<!-- Track and Wishlist Feed -->
|
||||
<div style="display: flex; flex-direction: column; gap: 6px; min-width: 250px; max-width: 300px;">
|
||||
<div style="font-size: 10px; color: #b3b3b3; text-transform: uppercase;">Current Track:</div>
|
||||
<div id="watchlist-track-name" style="font-size: 11px; font-weight: bold; color: rgb(var(--accent-light-rgb)); margin-bottom: 8px;">Waiting...</div>
|
||||
<div class="watchlist-live-activity-feed">
|
||||
<div class="watchlist-live-activity-feed-label">Current Track:</div>
|
||||
<div id="watchlist-track-name" class="watchlist-live-activity-track">Waiting...</div>
|
||||
|
||||
<div style="font-size: 10px; color: #ff9800; text-transform: uppercase;">✨ Recently Added:</div>
|
||||
<div class="watchlist-live-activity-feed-label-orange">✨ Recently Added:</div>
|
||||
<div id="watchlist-additions-feed" style="max-height: 80px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; font-size: 10px;">
|
||||
<!-- Populated by JavaScript -->
|
||||
</div>
|
||||
|
|
@ -26845,7 +26845,16 @@ async function showWatchlistModal() {
|
|||
</div>
|
||||
|
||||
<div class="watchlist-artists-grid" id="watchlist-artists-list">
|
||||
${artistsData.artists.map(artist => `
|
||||
${artistsData.artists.map(artist => {
|
||||
const pills = [];
|
||||
if (artist.include_albums) pills.push('<span class="watchlist-pill watchlist-pill-active">Albums</span>');
|
||||
if (artist.include_eps) pills.push('<span class="watchlist-pill watchlist-pill-active">EPs</span>');
|
||||
if (artist.include_singles) pills.push('<span class="watchlist-pill watchlist-pill-active">Singles</span>');
|
||||
if (artist.include_live) pills.push('<span class="watchlist-pill watchlist-pill-filter">Live</span>');
|
||||
if (artist.include_remixes) pills.push('<span class="watchlist-pill watchlist-pill-filter">Remixes</span>');
|
||||
if (artist.include_acoustic) pills.push('<span class="watchlist-pill watchlist-pill-filter">Acoustic</span>');
|
||||
if (artist.include_compilations) pills.push('<span class="watchlist-pill watchlist-pill-filter">Compilations</span>');
|
||||
return `
|
||||
<div class="watchlist-artist-card"
|
||||
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '"')}"
|
||||
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}">
|
||||
|
|
@ -26858,11 +26867,13 @@ async function showWatchlistModal() {
|
|||
<span class="watchlist-checkbox-custom"></span>
|
||||
</label>
|
||||
|
||||
<button class="watchlist-card-remove"
|
||||
<button class="watchlist-card-gear"
|
||||
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
|
||||
data-artist-name="${escapeHtml(artist.artist_name)}"
|
||||
onclick="event.stopPropagation();"
|
||||
title="Remove from watchlist">×</button>
|
||||
title="Artist settings">
|
||||
<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.48.48 0 00-.48-.41h-3.84a.48.48 0 00-.48.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87a.48.48 0 00.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.26.41.48.41h3.84c.24 0 .44-.17.48-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.6 3.6 0 0112 15.6z"/></svg>
|
||||
</button>
|
||||
|
||||
<div class="watchlist-card-image">
|
||||
${artist.image_url ? `
|
||||
|
|
@ -26877,29 +26888,29 @@ async function showWatchlistModal() {
|
|||
|
||||
<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>
|
||||
<span class="watchlist-card-meta">${formatRelativeScanTime(artist.last_scan_timestamp)}</span>
|
||||
</div>
|
||||
${pills.length > 0 ? `<div class="watchlist-card-pills">${pills.join('')}</div>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
`}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listeners for remove buttons
|
||||
modal.querySelectorAll('.watchlist-card-remove').forEach(button => {
|
||||
// Add event listeners for gear buttons
|
||||
modal.querySelectorAll('.watchlist-card-gear').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const artistId = button.getAttribute('data-artist-id');
|
||||
const artistName = button.getAttribute('data-artist-name');
|
||||
removeFromWatchlistModal(artistId, artistName);
|
||||
openWatchlistArtistConfigModal(artistId, artistName);
|
||||
});
|
||||
});
|
||||
|
||||
// Add click handlers to artist cards (except for remove button or checkbox)
|
||||
// Add click handlers to artist cards (except for gear button or checkbox)
|
||||
modal.querySelectorAll('.watchlist-artist-card').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// Don't trigger if clicking the remove button or checkbox
|
||||
if (e.target.closest('.watchlist-card-remove') || e.target.closest('.watchlist-card-checkbox')) {
|
||||
if (e.target.closest('.watchlist-card-gear') || e.target.closest('.watchlist-card-checkbox')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -26907,7 +26918,7 @@ async function showWatchlistModal() {
|
|||
const artistName = item.querySelector('.watchlist-card-name').textContent;
|
||||
|
||||
console.log(`🎵 Artist card clicked: ${artistName} (${artistId})`);
|
||||
openWatchlistArtistConfigModal(artistId, artistName);
|
||||
openWatchlistArtistDetailView(artistId, artistName);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -27113,6 +27124,177 @@ function closeWatchlistArtistConfigModal() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open watchlist artist detail view (slides in from right)
|
||||
*/
|
||||
async function openWatchlistArtistDetailView(artistId, artistName) {
|
||||
try {
|
||||
const response = await fetch(`/api/watchlist/artist/${artistId}/config`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
showToast(`Error loading artist info: ${data.error}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const { config, artist, recent_releases } = data;
|
||||
|
||||
// Remove existing overlay if any
|
||||
const existing = document.querySelector('.watchlist-artist-detail-overlay');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'watchlist-artist-detail-overlay';
|
||||
|
||||
// Build pills
|
||||
const pills = [];
|
||||
if (config.include_albums) pills.push('<span class="watchlist-pill watchlist-pill-active">Albums</span>');
|
||||
if (config.include_eps) pills.push('<span class="watchlist-pill watchlist-pill-active">EPs</span>');
|
||||
if (config.include_singles) pills.push('<span class="watchlist-pill watchlist-pill-active">Singles</span>');
|
||||
if (config.include_live) pills.push('<span class="watchlist-pill watchlist-pill-filter">Live</span>');
|
||||
if (config.include_remixes) pills.push('<span class="watchlist-pill watchlist-pill-filter">Remixes</span>');
|
||||
if (config.include_acoustic) pills.push('<span class="watchlist-pill watchlist-pill-filter">Acoustic</span>');
|
||||
if (config.include_compilations) pills.push('<span class="watchlist-pill watchlist-pill-filter">Compilations</span>');
|
||||
|
||||
// Build scan info
|
||||
const scanTimeText = config.last_scan_timestamp ? formatRelativeScanTime(config.last_scan_timestamp) : 'Never scanned';
|
||||
const dateAddedText = config.date_added ? `Added ${new Date(config.date_added).toLocaleDateString()}` : '';
|
||||
|
||||
// Build metadata tags (style, mood, label)
|
||||
const metaTags = [];
|
||||
if (artist.style) metaTags.push(`<span class="watchlist-detail-genre-tag">${escapeHtml(artist.style)}</span>`);
|
||||
if (artist.mood) metaTags.push(`<span class="watchlist-detail-genre-tag">${escapeHtml(artist.mood)}</span>`);
|
||||
if (artist.label) metaTags.push(`<span class="watchlist-detail-genre-tag">${escapeHtml(artist.label)}</span>`);
|
||||
|
||||
overlay.innerHTML = `
|
||||
${artist.banner_url ? `
|
||||
<div class="watchlist-detail-banner">
|
||||
<img src="${artist.banner_url}" alt="" onerror="this.parentElement.remove();">
|
||||
<div class="watchlist-detail-banner-fade"></div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="watchlist-detail-content ${artist.banner_url ? 'has-banner' : ''}">
|
||||
<button class="watchlist-detail-back watchlist-detail-back-btn">
|
||||
← Back to Watchlist
|
||||
</button>
|
||||
|
||||
<div class="watchlist-detail-hero">
|
||||
${artist.image_url ? `<img src="${artist.image_url}" alt="${escapeHtml(artist.name)}" onerror="this.style.display='none';">` : ''}
|
||||
<div class="watchlist-detail-hero-info">
|
||||
<h2 class="watchlist-detail-hero-name">${escapeHtml(artist.name)}</h2>
|
||||
${artist.followers || artist.popularity ? `
|
||||
<div class="watchlist-detail-hero-stats">
|
||||
${artist.followers ? `
|
||||
<div class="watchlist-detail-stat">
|
||||
<span class="watchlist-detail-stat-value">${formatNumber(artist.followers)}</span>
|
||||
<span class="watchlist-detail-stat-label">Followers</span>
|
||||
</div>` : ''}
|
||||
${artist.popularity ? `
|
||||
<div class="watchlist-detail-stat">
|
||||
<span class="watchlist-detail-stat-value">${artist.popularity}/100</span>
|
||||
<span class="watchlist-detail-stat-label">Popularity</span>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
${artist.genres && artist.genres.length > 0 ? `
|
||||
<div class="watchlist-detail-hero-genres">
|
||||
${artist.genres.map(g => `<span class="watchlist-detail-genre-tag">${escapeHtml(g)}</span>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${artist.summary ? `
|
||||
<div class="watchlist-detail-section">
|
||||
<div class="watchlist-detail-section-title">About</div>
|
||||
<p class="watchlist-detail-bio">${escapeHtml(artist.summary)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${metaTags.length > 0 ? `
|
||||
<div class="watchlist-detail-section">
|
||||
<div class="watchlist-detail-section-title">Info</div>
|
||||
<div class="watchlist-detail-hero-genres">${metaTags.join('')}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${recent_releases && recent_releases.length > 0 ? `
|
||||
<div class="watchlist-detail-section">
|
||||
<div class="watchlist-detail-section-title">Recent Releases</div>
|
||||
<div class="watchlist-detail-releases">
|
||||
${recent_releases.map(r => `
|
||||
<div class="watchlist-detail-release">
|
||||
${r.album_cover_url ? `<img src="${r.album_cover_url}" alt="" onerror="this.style.display='none';">` : ''}
|
||||
<div class="watchlist-detail-release-info">
|
||||
<span class="watchlist-detail-release-name">${escapeHtml(r.album_name)}</span>
|
||||
<span class="watchlist-detail-release-meta">${r.release_date}${r.track_count ? ` · ${r.track_count} tracks` : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="watchlist-detail-section">
|
||||
<div class="watchlist-detail-section-title">Watchlist</div>
|
||||
<div class="watchlist-detail-watchlist-info">
|
||||
<span class="watchlist-card-meta">${scanTimeText}</span>
|
||||
${dateAddedText ? `<span class="watchlist-detail-info-sep">·</span><span class="watchlist-card-meta">${dateAddedText}</span>` : ''}
|
||||
</div>
|
||||
<div class="watchlist-card-pills" style="padding: 0; margin-top: 8px;">
|
||||
${pills.length > 0 ? pills.join('') : '<span class="watchlist-card-meta">No release types enabled</span>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="watchlist-detail-actions">
|
||||
<button class="watchlist-detail-settings-btn watchlist-detail-settings-action">Settings</button>
|
||||
<button class="watchlist-detail-remove-btn watchlist-detail-remove-action">Remove from Watchlist</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Wire up event listeners (avoids inline onclick escaping issues)
|
||||
overlay.querySelector('.watchlist-detail-back-btn').addEventListener('click', () => {
|
||||
closeWatchlistArtistDetailView();
|
||||
});
|
||||
|
||||
overlay.querySelector('.watchlist-detail-settings-action').addEventListener('click', () => {
|
||||
// Remove overlay immediately so it doesn't block the config modal
|
||||
const detailOverlay = document.querySelector('.watchlist-artist-detail-overlay');
|
||||
if (detailOverlay) detailOverlay.remove();
|
||||
openWatchlistArtistConfigModal(artistId, artistName);
|
||||
});
|
||||
|
||||
overlay.querySelector('.watchlist-detail-remove-action').addEventListener('click', () => {
|
||||
removeFromWatchlistModal(artistId, artistName);
|
||||
});
|
||||
|
||||
// Append to the modal container
|
||||
const container = document.querySelector('.watchlist-fullscreen');
|
||||
if (container) {
|
||||
container.appendChild(overlay);
|
||||
// Trigger slide-in animation
|
||||
requestAnimationFrame(() => overlay.classList.add('visible'));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error opening artist detail view:', error);
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close watchlist artist detail view (slides out)
|
||||
*/
|
||||
function closeWatchlistArtistDetailView() {
|
||||
const overlay = document.querySelector('.watchlist-artist-detail-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.remove('visible');
|
||||
overlay.addEventListener('transitionend', () => overlay.remove(), { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open global watchlist settings modal
|
||||
*/
|
||||
|
|
@ -27365,6 +27547,23 @@ function formatNumber(num) {
|
|||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format last scan timestamp as relative time
|
||||
*/
|
||||
function formatRelativeScanTime(isoString) {
|
||||
if (!isoString) return 'Never scanned';
|
||||
const diff = Date.now() - new Date(isoString).getTime();
|
||||
const mins = Math.floor(diff / 60000);
|
||||
if (mins < 1) return 'Scanned just now';
|
||||
if (mins < 60) return `Scanned ${mins}m ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `Scanned ${hrs}h ago`;
|
||||
const days = Math.floor(hrs / 24);
|
||||
if (days < 30) return `Scanned ${days}d ago`;
|
||||
const months = Math.floor(days / 30);
|
||||
return `Scanned ${months}mo ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter watchlist artists based on search input
|
||||
*/
|
||||
|
|
@ -27476,16 +27675,16 @@ function handleWatchlistScanData(data) {
|
|||
if (additionsFeed) {
|
||||
if (data.recent_wishlist_additions && data.recent_wishlist_additions.length > 0) {
|
||||
additionsFeed.innerHTML = data.recent_wishlist_additions.map(item => `
|
||||
<div style="display: flex; gap: 6px; align-items: center; padding: 3px; background: #1a1a1a; border-radius: 4px;">
|
||||
<img src="${item.album_image_url || ''}" alt="" style="width: 24px; height: 24px; border-radius: 3px; object-fit: cover;" onerror="this.style.display='none';" />
|
||||
<div style="flex: 1; overflow: hidden;">
|
||||
<div style="font-weight: bold; color: rgb(var(--accent-light-rgb)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${item.track_name}</div>
|
||||
<div style="font-size: 9px; color: #b3b3b3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${item.artist_name}</div>
|
||||
<div class="watchlist-live-addition-item">
|
||||
<img src="${item.album_image_url || ''}" alt="" onerror="this.style.display='none';" />
|
||||
<div class="watchlist-live-addition-item-info">
|
||||
<div class="watchlist-live-addition-item-track">${item.track_name}</div>
|
||||
<div class="watchlist-live-addition-item-artist">${item.artist_name}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
additionsFeed.innerHTML = '<div style="color: #666; font-size: 10px;">No tracks added yet...</div>';
|
||||
additionsFeed.innerHTML = '<div class="watchlist-live-addition-empty">No tracks added yet...</div>';
|
||||
}
|
||||
}
|
||||
} else if (liveActivity && data.status !== 'scanning') {
|
||||
|
|
@ -27523,8 +27722,8 @@ function handleWatchlistScanData(data) {
|
|||
|
||||
// Update the scan status display with completion message and summary
|
||||
statusDiv.innerHTML = `
|
||||
<div style="text-align: center; padding: 15px; background: #2a2a2a; border-radius: 8px; border: 1px solid #444;">
|
||||
<div style="font-size: 14px; color: rgb(var(--accent-light-rgb)); margin-bottom: 10px;">${completionMessage}</div>
|
||||
<div class="watchlist-scan-completion">
|
||||
<div class="watchlist-scan-completion-message">${completionMessage}</div>
|
||||
<div style="font-size: 13px; opacity: 0.8;">
|
||||
<span class="sync-stat">Artists: ${totalArtists}</span>
|
||||
<span class="sync-separator"> • </span>
|
||||
|
|
@ -27694,6 +27893,9 @@ async function removeFromWatchlistModal(artistId, artistName) {
|
|||
|
||||
console.log(`❌ Removed ${artistName} from watchlist`);
|
||||
|
||||
// Close detail view if open
|
||||
closeWatchlistArtistDetailView();
|
||||
|
||||
// Refresh the modal
|
||||
showWatchlistModal();
|
||||
|
||||
|
|
|
|||
|
|
@ -8714,6 +8714,8 @@ body {
|
|||
max-width: 95vw;
|
||||
width: 95vw;
|
||||
max-height: 95vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Override playlist-modal-body within watchlist to allow grid scrolling */
|
||||
|
|
@ -8807,6 +8809,34 @@ body {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Card release-type pills */
|
||||
.watchlist-card-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
padding: 0 16px 10px;
|
||||
}
|
||||
|
||||
.watchlist-pill {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
line-height: 1.3;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.watchlist-pill-active {
|
||||
background: rgba(var(--accent-rgb), 0.15);
|
||||
color: rgb(var(--accent-rgb));
|
||||
}
|
||||
|
||||
.watchlist-pill-filter {
|
||||
background: rgba(255, 152, 0, 0.15);
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
/* Checkbox — top-left, hidden until hover or checked */
|
||||
.watchlist-card-checkbox {
|
||||
position: absolute;
|
||||
|
|
@ -8877,6 +8907,442 @@ body {
|
|||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Gear icon — top-right, visible on hover */
|
||||
.watchlist-card-gear {
|
||||
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);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.watchlist-artist-card:hover .watchlist-card-gear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.watchlist-card-gear:hover {
|
||||
background: rgba(var(--accent-rgb), 0.7);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.watchlist-card-gear svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Watchlist Artist Detail Overlay */
|
||||
.watchlist-artist-detail-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(18, 18, 18, 0.98);
|
||||
z-index: 10;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.watchlist-artist-detail-overlay.visible {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Banner at top of detail view */
|
||||
.watchlist-detail-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.watchlist-detail-banner img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.watchlist-detail-banner-fade {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80px;
|
||||
background: linear-gradient(to bottom, transparent, rgba(18, 18, 18, 0.98));
|
||||
}
|
||||
|
||||
.watchlist-detail-content {
|
||||
padding: 32px;
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.watchlist-detail-content.has-banner {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
/* Bio text */
|
||||
.watchlist-detail-bio {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Watchlist info row */
|
||||
.watchlist-detail-watchlist-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.watchlist-detail-info-sep {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Recent releases grid */
|
||||
.watchlist-detail-releases {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.watchlist-detail-release {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.watchlist-detail-release:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.watchlist-detail-release img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.watchlist-detail-release-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.watchlist-detail-release-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.watchlist-detail-release-meta {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.watchlist-detail-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 8px 0;
|
||||
margin-bottom: 24px;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.watchlist-detail-back:hover {
|
||||
color: rgb(var(--accent-rgb));
|
||||
}
|
||||
|
||||
.watchlist-detail-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid rgba(var(--accent-rgb), 0.3);
|
||||
background: #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-name {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.watchlist-detail-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.watchlist-detail-stat-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: rgb(var(--accent-rgb));
|
||||
}
|
||||
|
||||
.watchlist-detail-stat-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.watchlist-detail-hero-genres {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.watchlist-detail-genre-tag {
|
||||
font-size: 10px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.watchlist-detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.watchlist-detail-section-title {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.watchlist-detail-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.watchlist-detail-actions button {
|
||||
flex: 1;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.watchlist-detail-actions button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.watchlist-detail-settings-btn {
|
||||
background: rgba(var(--accent-rgb), 0.15);
|
||||
color: rgb(var(--accent-rgb));
|
||||
}
|
||||
|
||||
.watchlist-detail-settings-btn:hover {
|
||||
background: rgba(var(--accent-rgb), 0.25);
|
||||
}
|
||||
|
||||
.watchlist-detail-remove-btn {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.watchlist-detail-remove-btn:hover {
|
||||
background: rgba(255, 59, 48, 0.2);
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
/* Live activity classes (replaces inline styles) */
|
||||
.watchlist-live-activity {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: #2a2a2a;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #444;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-artist-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgb(var(--accent-rgb));
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-album-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #ffc107;
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-label {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 250px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-feed-label {
|
||||
font-size: 10px;
|
||||
color: #b3b3b3;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-feed-label-orange {
|
||||
font-size: 10px;
|
||||
color: #ff9800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.watchlist-live-activity-track {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-item {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-item img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 3px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-item-info {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-item-track {
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-item-artist {
|
||||
font-size: 9px;
|
||||
color: #b3b3b3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.watchlist-live-addition-empty {
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.watchlist-scan-completion {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: #2a2a2a;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.watchlist-scan-completion-message {
|
||||
font-size: 14px;
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Watchlist Search */
|
||||
.watchlist-search-container {
|
||||
padding: 0 32px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue