Route all artist-detail callers to the standalone page, bump to 2.45
Phase 4a of the Search/Artists unification. The app had two artist- detail implementations: the standalone page Library navigates to via navigateToArtistDetail (its own route, deep-link support, highlights Library in the sidebar), and an inline state inside the Artists page reached via selectArtistForDetail. They rendered similar content but were separate code paths and kept drifting apart (PR #356 just had to fix source propagation in both). Every external caller of selectArtistForDetail (9 sites across api-monitor.js, discover.js, downloads.js, search.js) now calls navigateToArtistDetail(id, name, source) directly. Removed ~63 lines of the navigate-then-setTimeout-then-select dance. Source context (Spotify/iTunes/Deezer/etc.) carries cleanly through via the new third argument. Artists sidebar entry, its inline search, and selectArtistForDetail all still work — they just have no external callers. Phase 4b will retire the sidebar entry and artists.js.
This commit is contained in:
parent
f203b3e46d
commit
9361c29965
7 changed files with 33 additions and 80 deletions
|
|
@ -37,7 +37,7 @@ _log_dir = Path(_log_path).parent
|
|||
logger = setup_logging(_log_level, _log_path)
|
||||
|
||||
# App version — single source of truth for backup metadata, version-info endpoint, etc.
|
||||
_SOULSYNC_BASE_VERSION = "2.44"
|
||||
_SOULSYNC_BASE_VERSION = "2.45"
|
||||
|
||||
def _build_version_string():
|
||||
"""Append short commit hash to version when available (e.g. 2.35+abc1234)."""
|
||||
|
|
@ -22809,6 +22809,17 @@ def get_version_info():
|
|||
"title": "What's New in SoulSync",
|
||||
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Artist Links Everywhere Go to the Same Page",
|
||||
"description": "Clicking an artist result in Search, Discover, the API Monitor, or anywhere else now lands on the standalone artist detail page Library already uses — instead of swapping into the Artists page's inline detail view",
|
||||
"features": [
|
||||
"• One artist detail page, not two — less navigation surprise and a stable URL (/artist-detail) for deep links",
|
||||
"• Source context (Spotify/iTunes/Deezer/etc.) now carries cleanly into the destination so non-Spotify albums load from the right provider",
|
||||
"• Removed the navigate-then-setTimeout dance at 9 callsites (api-monitor, discover, downloads, search, library recommendations)",
|
||||
"• Artists sidebar entry and its inline search still work for now — next phase retires that page entirely",
|
||||
"• Phase 4a of the Search/Artists unification project",
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "Remove Embedded Download Manager from Search Page",
|
||||
"description": "The Search page used to carry a second copy of the Download Manager (active + finished queues, clear/cancel-all buttons) that was hidden by default and duplicated the dedicated Downloads page. That duplicate is gone",
|
||||
|
|
|
|||
|
|
@ -2382,16 +2382,9 @@ async function openWatchlistArtistDetailView(artistId, artistName) {
|
|||
source = spotify_artist_id ? 'spotify' : discogs_artist_id ? 'discogs' : deezer_artist_id ? 'deezer' : 'itunes';
|
||||
}
|
||||
if (discogId) {
|
||||
// Close detail overlay and navigate to Artists page
|
||||
// Close detail overlay and navigate to the standalone artist detail page
|
||||
closeWatchlistArtistDetailView();
|
||||
// Navigate to Artists page and load discography
|
||||
navigateToPage('artists');
|
||||
setTimeout(() => {
|
||||
selectArtistForDetail(
|
||||
{ id: discogId, name: artistName, image_url: artist.image_url || '' },
|
||||
{ source: source }
|
||||
);
|
||||
}, 200);
|
||||
navigateToArtistDetail(discogId, artistName, source);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -739,16 +739,7 @@ async function checkRecommendedWatchlistStatuses(artists) {
|
|||
|
||||
async function viewRecommendedArtistDiscography(artistId, artistName) {
|
||||
closeRecommendedArtistsModal();
|
||||
|
||||
const artist = {
|
||||
id: artistId,
|
||||
name: artistName
|
||||
};
|
||||
|
||||
// Use same navigation pattern as hero slider
|
||||
navigateToPage('artists');
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await selectArtistForDetail(artist);
|
||||
navigateToArtistDetail(artistId, artistName);
|
||||
}
|
||||
|
||||
async function checkAllHeroWatchlistStatus() {
|
||||
|
|
@ -830,25 +821,8 @@ async function viewDiscoverHeroDiscography() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Create artist object matching the expected format
|
||||
const artist = {
|
||||
id: artistId,
|
||||
name: artistName,
|
||||
image_url: discoverHeroArtists[discoverHeroIndex]?.image_url || '',
|
||||
genres: discoverHeroArtists[discoverHeroIndex]?.genres || [],
|
||||
popularity: discoverHeroArtists[discoverHeroIndex]?.popularity || 0
|
||||
};
|
||||
|
||||
console.log(`🎵 Navigating to artist detail for: ${artistName}`);
|
||||
|
||||
// Navigate to Artists page
|
||||
navigateToPage('artists');
|
||||
|
||||
// Small delay to let the page load
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Load the artist details
|
||||
await selectArtistForDetail(artist);
|
||||
navigateToArtistDetail(artistId, artistName);
|
||||
}
|
||||
|
||||
function showDiscoverHeroEmpty() {
|
||||
|
|
@ -4639,9 +4613,9 @@ function _renderYourArtistCard(artist) {
|
|||
const watchlistClass = artist.on_watchlist ? 'active' : '';
|
||||
const hasId = artist.active_source_id && artist.active_source_id !== '';
|
||||
|
||||
// Navigate to artist page (name click)
|
||||
// Navigate to standalone artist detail page (name click)
|
||||
const navAction = hasId
|
||||
? `event.stopPropagation(); navigateToPage('artists'); setTimeout(() => selectArtistForDetail({id:'${escapeForInlineJs(artist.active_source_id)}', name:'${escapeForInlineJs(artist.artist_name)}', image_url:'${escapeForInlineJs(img)}'}), 200)`
|
||||
? `event.stopPropagation(); navigateToArtistDetail('${escapeForInlineJs(artist.active_source_id)}', '${escapeForInlineJs(artist.artist_name)}')`
|
||||
: '';
|
||||
|
||||
// Open info modal (card body click) — pass pool ID so we can look up all data
|
||||
|
|
@ -4820,7 +4794,7 @@ async function openYourArtistInfoModal(poolId) {
|
|||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
<span>Explore</span>
|
||||
</button>
|
||||
<button class="ya-header-btn ya-viewall-btn" onclick="document.getElementById('ya-info-modal-overlay')?.remove(); document.getElementById('your-artists-modal-overlay')?.remove(); navigateToPage('artists'); setTimeout(() => selectArtistForDetail({id:'${escapeForInlineJs(artistId)}', name:'${escapeForInlineJs(artistName)}', image_url:'${escapeForInlineJs(imageUrl)}'}, {source:'${escapeForInlineJs(pool.active_source || '')}'}), 200)">
|
||||
<button class="ya-header-btn ya-viewall-btn" onclick="document.getElementById('ya-info-modal-overlay')?.remove(); document.getElementById('your-artists-modal-overlay')?.remove(); navigateToArtistDetail('${escapeForInlineJs(artistId)}', '${escapeForInlineJs(artistName)}', '${escapeForInlineJs(pool.active_source || '')}' || null)">
|
||||
<span>View Discography</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
|
|
@ -6720,7 +6694,7 @@ function _artMapSetupInteraction(canvas) {
|
|||
<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); ${hasId ? `openYourArtistInfoModal_direct(${JSON.stringify(node).replace(/"/g, '"')})` : ''}">
|
||||
<span>ⓘ</span> Artist Info
|
||||
</div>
|
||||
<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); navigateToPage('artists'); setTimeout(() => selectArtistForDetail({id:'${escapeForInlineJs(bestId)}',name:'${escapeForInlineJs(node.name)}',image_url:'${escapeForInlineJs(node.image_url || '')}'},{source:'${bestSource}'}), 200)">
|
||||
<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); navigateToArtistDetail('${escapeForInlineJs(bestId)}', '${escapeForInlineJs(node.name)}', '${bestSource}' || null)">
|
||||
<span>💿</span> View Discography
|
||||
</div>
|
||||
<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); toggleYourArtistWatchlist(0,'${escapeForInlineJs(node.name)}','${escapeForInlineJs(bestId)}','${bestSource}',null)">
|
||||
|
|
@ -7408,7 +7382,7 @@ async function openGenreDeepDive(genre) {
|
|||
// Always open on Artists page with discography — pass source for correct routing
|
||||
const imgUrl = _esc(a.image_url || '');
|
||||
const artSource = _esc(a.source || '');
|
||||
const clickAction = `onclick="document.getElementById('genre-deep-dive-modal').remove();navigateToPage('artists');setTimeout(()=>selectArtistForDetail({id:'${_esc(a.entity_id)}',name:'${_esc(a.name)}',image_url:'${imgUrl}'},{source:'${artSource}'}),300)"`;
|
||||
const clickAction = `onclick="document.getElementById('genre-deep-dive-modal').remove();navigateToArtistDetail('${_esc(a.entity_id)}','${_esc(a.name)}','${artSource}' || null)"`;
|
||||
const srcClass = (a.source || '').toLowerCase();
|
||||
return `<div class="genre-dive-artist" ${clickAction}>
|
||||
<div class="genre-dive-artist-img" style="${a.image_url ? `background-image:url('${_esc(a.image_url)}')` : ''}">
|
||||
|
|
|
|||
|
|
@ -634,16 +634,7 @@ function _navigateToArtistFromModal(artistId, artistName, imageUrl, source, play
|
|||
if (!artistName) return;
|
||||
// Close the download modal
|
||||
if (playlistId) closeDownloadMissingModal(playlistId);
|
||||
// Navigate to Artists page and load discography
|
||||
navigateToPage('artists');
|
||||
setTimeout(() => {
|
||||
// If we have an artist ID, use it directly
|
||||
// If not, search by name — selectArtistForDetail handles both
|
||||
selectArtistForDetail(
|
||||
{ id: artistId || artistName, name: artistName, image_url: imageUrl || '' },
|
||||
source ? { source: source } : undefined
|
||||
);
|
||||
}, 200);
|
||||
navigateToArtistDetail(artistId || artistName, artistName, source || null);
|
||||
}
|
||||
|
||||
async function closeDownloadMissingModal(playlistId) {
|
||||
|
|
@ -5434,18 +5425,8 @@ function _gsSwitchSource(src) {
|
|||
|
||||
function _gsClickArtist(id, name, isLibrary) {
|
||||
_gsDeactivate();
|
||||
if (isLibrary) {
|
||||
// Same as enhanced search: navigateToArtistDetail
|
||||
navigateToArtistDetail(id, name);
|
||||
} else {
|
||||
// Same as enhanced search: navigate to Artists page + selectArtistForDetail
|
||||
navigateToPage('artists');
|
||||
setTimeout(() => {
|
||||
selectArtistForDetail({ id, name, image_url: '' }, {
|
||||
source: _gsState.activeSource || '',
|
||||
});
|
||||
}, 150);
|
||||
}
|
||||
const source = isLibrary ? null : (_gsState.activeSource || null);
|
||||
navigateToArtistDetail(id, name, source);
|
||||
}
|
||||
|
||||
async function _gsClickAlbum(albumId, albumName, artistName, imageUrl, source) {
|
||||
|
|
|
|||
|
|
@ -3599,6 +3599,11 @@ function closeHelperSearch() {
|
|||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const WHATS_NEW = {
|
||||
'2.45': [
|
||||
// --- April 23, 2026 (night) ---
|
||||
{ date: 'April 23, 2026 (night)' },
|
||||
{ title: 'Artist Links Everywhere Go to the Same Page', desc: 'Clicking an artist result in Search, Discover, the API Monitor, or anywhere else now lands on the standalone artist detail page that Library already uses — instead of swapping into the Artists page\'s inline detail view. One artist detail page, not two: less navigation surprise, a stable /artist-detail URL for deep links, and source context (Spotify/iTunes/Deezer/etc.) now carries cleanly into the destination so non-Spotify albums load from the right provider. Removed the navigate-then-setTimeout dance at 9 callsites. Artists sidebar entry and its inline search still work for now — next phase retires that page entirely. Phase 4a of the Search/Artists unification project', page: 'library' },
|
||||
],
|
||||
'2.44': [
|
||||
// --- April 23, 2026 (evening) ---
|
||||
{ date: 'April 23, 2026 (evening)' },
|
||||
|
|
|
|||
|
|
@ -655,8 +655,8 @@ let discographyFilterState = {
|
|||
ownership: 'all' // 'all', 'owned', 'missing'
|
||||
};
|
||||
|
||||
function navigateToArtistDetail(artistId, artistName) {
|
||||
console.log(`🎵 Navigating to artist detail: ${artistName} (ID: ${artistId})`);
|
||||
function navigateToArtistDetail(artistId, artistName, sourceOverride = null) {
|
||||
console.log(`🎵 Navigating to artist detail: ${artistName} (ID: ${artistId}${sourceOverride ? `, source: ${sourceOverride}` : ''})`);
|
||||
|
||||
// Abort any in-progress completion stream
|
||||
if (artistDetailPageState.completionController) {
|
||||
|
|
@ -672,7 +672,7 @@ function navigateToArtistDetail(artistId, artistName) {
|
|||
// Store current artist info and reset enhanced view state
|
||||
artistDetailPageState.currentArtistId = artistId;
|
||||
artistDetailPageState.currentArtistName = artistName;
|
||||
artistDetailPageState.currentArtistSource = null;
|
||||
artistDetailPageState.currentArtistSource = sourceOverride || null;
|
||||
artistDetailPageState.enhancedData = null;
|
||||
artistDetailPageState.expandedAlbums = new Set();
|
||||
artistDetailPageState.selectedTracks = new Set();
|
||||
|
|
|
|||
|
|
@ -340,18 +340,7 @@ function initializeSearchModeToggle() {
|
|||
const sourceOverride = _activeSearchSource;
|
||||
console.log(`🎵 Opening artist detail: ${artist.name} (ID: ${artist.id}, source: ${sourceOverride})`);
|
||||
hideDropdown();
|
||||
|
||||
// Navigate to Artists page
|
||||
navigateToPage('artists');
|
||||
|
||||
// Small delay to let the page load
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Load the artist details with source context
|
||||
await selectArtistForDetail(artist, {
|
||||
source: sourceOverride,
|
||||
plugin: artist.external_urls?.hydrabase_plugin,
|
||||
});
|
||||
navigateToArtistDetail(artist.id, artist.name, sourceOverride || null);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue