feat(slint): MB relationships section in the network sidebar

Wires the third MB-driven section: Members & Former, Member Of, and
Collaborators, mirroring Tauri's RELATIONSHIPS block.

Controller (`crates/qbz-slint/src/artist.rs`):
  - load_mb_relationships(runtime, mbid) — calls QbzCore's
    musicbrainz_get_artist_relationships, then maps each section
    independently with group_relations.
  - group_relations dedupes by mbid combining roles, preserves first-
    seen order, and computes the per-row role + tooltip:
      * role = first attribute, or the section default ("Band Member"
        / "Band" / "Collaborator") when MB has no attributes.
      * tooltip = roles joined with ", " plus the period in parens
        ("Vocalist, Guitarist (1965 - 1969)"); falls back to just the
        period or to the name.
  - format_period mirrors Tauri's `{begin || '?'} - {end || 'present'}`
    when at least one of the two is set, otherwise empty.

main.rs:
  - The MB enrichment task now chains: resolve + metadata first, then
    relationships with the resolved mbid. Both loading flags flip
    together so the sidebar shows a single "Loading…" line per section
    while the calls run.

ArtistPageView.slint:
  - RELATIONSHIPS section, gated by mb-available + (loading || has-
    data). Three sub-groups, each with a secondary label
    ("Members & Former" / "Member Of" / "Collaborators"), shown only
    when their list is non-empty.
  - Members and Collaborators use the User icon, Member Of uses the
    Music icon (matches Tauri's Lucide imports).
  - Each row reuses SidebarLink, emits NetworkSidebarActions.musician-
    clicked(name, role) — logged-only until MusicianPage lands.

cc
This commit is contained in:
vicrodh committed 2026-05-20 10:29:26 -06:00
1 parent 6d25b25d1d
commit 72083adb7f
3 files changed
+286 -4

No files matched your search

@@ -887,6 +887,100 @@ export component ArtistPageView inherits Rectangle {
}
}
}
// --- Relationships (MB) --------------------------
// Section is gated by mb-available so a
// disabled MB or unmatched artist hides it.
if NetworkSidebarState.mb-available
&& (NetworkSidebarState.relationships-loading
|| NetworkSidebarState.relationships.has-data):
VerticalLayout {
padding-left: 14px;
padding-right: 14px;
padding-top: 12px;
padding-bottom: 6px;
spacing: 6px;
Text {
text: @tr("RELATIONSHIPS");
color: Theme.text-muted;
font-size: 11px;
font-weight: Typography.semibold;
letter-spacing: 0.5px;
}
if NetworkSidebarState.relationships-loading: Text {
text: @tr("Loading…");
color: Theme.text-muted;
font-size: 12px;
}
if !NetworkSidebarState.relationships-loading
&& NetworkSidebarState.relationships.members.length > 0:
VerticalLayout {
spacing: 2px;
padding-top: 2px;
Text {
text: @tr("Members & Former");
color: Theme.text-muted;
font-size: 11px;
}
for member in NetworkSidebarState.relationships.members:
SidebarLink {
label: member.name;
icon: @image-url("../assets/icons/user.svg");
tooltip: member.tooltip;
clicked => {
NetworkSidebarActions.musician-clicked(
member.name, member.role);
}
}
}
if !NetworkSidebarState.relationships-loading
&& NetworkSidebarState.relationships.groups.length > 0:
VerticalLayout {
spacing: 2px;
padding-top: 2px;
Text {
text: @tr("Member Of");
color: Theme.text-muted;
font-size: 11px;
}
for group in NetworkSidebarState.relationships.groups:
SidebarLink {
label: group.name;
icon: @image-url("../assets/icons/music.svg");
tooltip: group.tooltip;
clicked => {
NetworkSidebarActions.musician-clicked(
group.name, group.role);
}
}
}
if !NetworkSidebarState.relationships-loading
&& NetworkSidebarState.relationships.collaborators.length > 0:
VerticalLayout {
spacing: 2px;
padding-top: 2px;
Text {
text: @tr("Collaborators");
color: Theme.text-muted;
font-size: 11px;
}
for collab in NetworkSidebarState.relationships.collaborators:
SidebarLink {
label: collab.name;
icon: @image-url("../assets/icons/user.svg");
tooltip: collab.tooltip;
clicked => {
NetworkSidebarActions.musician-clicked(
collab.name, collab.role);
}
}
}
}
}
}
}