fix(slint): MyQBZ row artist links route by item source
The detail view's artist click forwarded only the artist NAME, and the top-level router's heuristic (numeric = Qobuz, name = local) sent every click - Qobuz items included - to the LocalLibrary artist page. The callback now carries (source, name, artist id): Qobuz items navigate to the Qobuz artist page (same recorded-history path as AlbumView's artist button), local/plex items keep the LocalLibrary destination. The resolved- item model gains artist_id hydrated from the first resolved track (live- pushed when the resolve lands; a click before hydration is ignored with a warn instead of navigating wrong). Both click sites updated (list/expanded subtitle link + expanded inline rows); grid cards render plain text as before. cc
This commit is contained in:
4 files changed
+75
-29
No files matched your search
@@ -2868,19 +2868,34 @@ fn wire_myqbz_detail(
|
||||
});
|
||||
}
|
||||
|
||||
// --- Open an item's artist (qobuz items only) -----------------------
|
||||
// --- Open an item's artist (route by SOURCE) -------------------------
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
window
|
||||
.global::<Act>()
|
||||
.on_open_artist(move |_source, artist_name| {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
// The top-level open-artist callback routes a name to the
|
||||
// Local Library Artists tab and a numeric id to the Qobuz
|
||||
// artist page. Collection items carry an artist NAME.
|
||||
if !artist_name.trim().is_empty() {
|
||||
w.invoke_open_artist(artist_name);
|
||||
.on_open_artist(move |source, artist_name, artist_id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// The top-level open-artist callback routes a numeric id to
|
||||
// the Qobuz artist page (with nav history — the same path
|
||||
// AlbumView's artist button uses) and a name to the
|
||||
// LocalLibrary Artists tab. Stored items only carry the
|
||||
// artist NAME, so Qobuz rows route by the numeric artist id
|
||||
// the resolveItems pass derived from their first track.
|
||||
if source == "qobuz" {
|
||||
if !artist_id.trim().is_empty() {
|
||||
w.invoke_open_artist(artist_id);
|
||||
} else {
|
||||
// Resolve still pending (or failed) — do NOT fall
|
||||
// back to the name: that opens the WRONG page (the
|
||||
// LocalLibrary artist) for a Qobuz item.
|
||||
log::warn!(
|
||||
"[qbz-slint] myqbz_detail open-artist: qobuz item '{artist_name}' \
|
||||
has no resolved artist id yet — ignoring click"
|
||||
);
|
||||
}
|
||||
} else if !artist_name.trim().is_empty() {
|
||||
// local / plex -> the LocalLibrary Artists tab by NAME.
|
||||
w.invoke_open_artist(artist_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,6 +115,12 @@ struct ResolvedItem {
|
||||
/// Backfills rows whose stored `artwork_url` was empty (e.g. disco-builder
|
||||
/// local items saved with NULL art before the builder carried the cover).
|
||||
artwork_url: String,
|
||||
/// First resolved track's numeric Qobuz artist id ("" when the track
|
||||
/// carries none — local/Plex items). The stored `MixtapeCollectionItem`
|
||||
/// only has the artist NAME (subtitle), so this is what lets a Qobuz
|
||||
/// item's artist link open the QOBUZ artist page instead of falling back
|
||||
/// to the LocalLibrary Artists tab.
|
||||
artist_id: String,
|
||||
}
|
||||
|
||||
/// Persist the open collection's current toolbar prefs (spec 12 §18), gated on
|
||||
@@ -280,24 +286,25 @@ fn to_item(item: &MixtapeCollectionItem) -> MixtapeDetailItem {
|
||||
// until the async pass lands.
|
||||
let cache_key = inline_cache_key(source, &item.source_item_id);
|
||||
let resolved = RESOLVE_CACHE.with(|cell| cell.borrow().get(&cache_key).cloned());
|
||||
let (source_kind, quality_tier, quality_detail, type_label, quality_resolving) = match resolved
|
||||
{
|
||||
Some(r) => {
|
||||
// Backfill the row cover from the resolved track when the stored
|
||||
// `artwork_url` was empty (disco-builder local items, older saves).
|
||||
if artwork_url.is_empty() && !r.artwork_url.is_empty() {
|
||||
artwork_url = r.artwork_url.clone();
|
||||
let (source_kind, quality_tier, quality_detail, type_label, artist_id, quality_resolving) =
|
||||
match resolved {
|
||||
Some(r) => {
|
||||
// Backfill the row cover from the resolved track when the stored
|
||||
// `artwork_url` was empty (disco-builder local items, older saves).
|
||||
if artwork_url.is_empty() && !r.artwork_url.is_empty() {
|
||||
artwork_url = r.artwork_url.clone();
|
||||
}
|
||||
(r.source_kind, r.quality_tier, r.quality_detail, r.type_label, r.artist_id, false)
|
||||
}
|
||||
(r.source_kind, r.quality_tier, r.quality_detail, r.type_label, false)
|
||||
}
|
||||
None => (
|
||||
source.to_string(),
|
||||
String::new(),
|
||||
String::new(),
|
||||
type_label(item.item_type).to_string(),
|
||||
true,
|
||||
),
|
||||
};
|
||||
None => (
|
||||
source.to_string(),
|
||||
String::new(),
|
||||
String::new(),
|
||||
type_label(item.item_type).to_string(),
|
||||
String::new(),
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
// Re-hydrate inline tracks from the controller-level cache (keyed
|
||||
// `source|source_item_id`) so a filter/sort/search re-derive does NOT lose
|
||||
@@ -321,6 +328,9 @@ fn to_item(item: &MixtapeCollectionItem) -> MixtapeDetailItem {
|
||||
// Only qobuz items get a clickable artist subtitle (spec 12 §6.3).
|
||||
subtitle_is_link: item.source == AlbumSource::Qobuz
|
||||
&& item.subtitle.as_deref().map(|s| !s.is_empty()).unwrap_or(false),
|
||||
// Resolved Qobuz artist id ("" until resolveItems lands / for
|
||||
// local-Plex items) — routes the artist link to the Qobuz artist page.
|
||||
artist_id: artist_id.into(),
|
||||
// Resolved source kind / quality / type label — from the resolveItems
|
||||
// cache (above) when resolved; else the stored-source defaults.
|
||||
source_kind: source_kind.into(),
|
||||
@@ -847,12 +857,20 @@ fn resolve_from_tracks(
|
||||
.map(|u| u.strip_prefix("file://").map(str::to_string).unwrap_or(u))
|
||||
.unwrap_or_default();
|
||||
|
||||
// First resolved track's Qobuz artist id — empty for local/Plex tracks
|
||||
// (QueueTrack.artist_id is None there). Feeds the row's artist link.
|
||||
let artist_id = first
|
||||
.and_then(|t| t.artist_id)
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
ResolvedItem {
|
||||
source_kind,
|
||||
quality_tier: quality_tier.to_string(),
|
||||
quality_detail,
|
||||
type_label,
|
||||
artwork_url,
|
||||
artist_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,6 +957,9 @@ async fn resolve_offline_cached(item: &MixtapeCollectionItem) -> Option<Resolved
|
||||
// local-item concern (those still resolve through the normal path),
|
||||
// and a Qobuz CDN url could not be fetched offline anyway.
|
||||
artwork_url: String::new(),
|
||||
// The offline index carries no artist id — and the Qobuz artist page
|
||||
// is gate-blocked offline anyway, so the link stays a no-op here.
|
||||
artist_id: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1024,6 +1045,7 @@ pub fn resolve_items(
|
||||
it.quality_tier = resolved.quality_tier.clone().into();
|
||||
it.quality_detail = resolved.quality_detail.clone().into();
|
||||
it.type_label = resolved.type_label.clone().into();
|
||||
it.artist_id = resolved.artist_id.clone().into();
|
||||
it.quality_resolving = false;
|
||||
if it.artwork_url.is_empty() && !resolved.artwork_url.is_empty() {
|
||||
it.artwork_url = resolved.artwork_url.clone().into();
|
||||
|
||||
@@ -316,7 +316,7 @@ component DetailRow inherits Rectangle {
|
||||
mouse-cursor: root.item.subtitle-is-link ? pointer : default;
|
||||
clicked => {
|
||||
if (root.item.subtitle-is-link) {
|
||||
MyQbzDetailActions.open-artist(root.item.source, root.item.subtitle);
|
||||
MyQbzDetailActions.open-artist(root.item.source, root.item.subtitle, root.item.artist-id);
|
||||
}
|
||||
}
|
||||
Text {
|
||||
@@ -1189,7 +1189,9 @@ export component MixtapeDetailView inherits Rectangle {
|
||||
// context menu emits ("track", id, "go-to-artist").
|
||||
// Both -> the parent item's artist navigation.
|
||||
if ((kind == "artist" && action == "open") || action == "go-to-artist") {
|
||||
MyQbzDetailActions.open-artist(item.source, et.artist);
|
||||
// et.artist-id is the track's Qobuz artist id ("" for
|
||||
// local/Plex tracks -> name-based LocalLibrary routing).
|
||||
MyQbzDetailActions.open-artist(item.source, et.artist, et.artist-id);
|
||||
} else {
|
||||
// play / play-next / queue / go-to-album (album-link
|
||||
// "open" too) all route through inline-track-action;
|
||||
|
||||
@@ -1661,6 +1661,11 @@ export struct MixtapeDetailItem {
|
||||
// True for qobuz items whose subtitle is a clickable artist link
|
||||
// (spec 12 §6.3 col-2 — local subtitles are plain text).
|
||||
subtitle-is-link: bool,
|
||||
// Numeric Qobuz artist id resolved by the resolveItems pass ("" until it
|
||||
// lands / for local-Plex items). Routes the artist link to the QOBUZ
|
||||
// artist page; without it the link can only fall back to the
|
||||
// LocalLibrary Artists tab (name-based).
|
||||
artist-id: string,
|
||||
// Resolved source kind for the source indicator + source filter
|
||||
// ("qobuz" | "plex" | "local"). Defaults from `source` until resolveItems
|
||||
// lands (deferred — for now equals the raw source mapping).
|
||||
@@ -1774,8 +1779,10 @@ export global MyQbzDetailActions {
|
||||
// Open an item's detail (album / track-album / playlist). Routed to the
|
||||
// existing open-album / open-playlist paths in main.rs.
|
||||
callback open-item(string /* source */, string /* item-type */, string /* source-item-id */);
|
||||
// Open an item's artist page by name (qobuz items only).
|
||||
callback open-artist(string /* source */, string /* artist-name */);
|
||||
// Open an item's artist. Qobuz items route by the resolved numeric
|
||||
// artist-id to the Qobuz artist page; local/Plex items (empty id) route
|
||||
// by NAME to the LocalLibrary Artists tab.
|
||||
callback open-artist(string /* source */, string /* artist-name */, string /* artist-id */);
|
||||
// --- Hero CTAs --------------------------------------------------------
|
||||
callback play-all();
|
||||
callback shuffle();
|
||||
|
||||
Reference in new issue
Block a user