feat(slint): clickable artist name on AlbumCard -> artist page

Ports Tauri's behavior where clicking an album card's artist name
navigates to that artist (the card body still opens the album).

  - AlbumCardItem gains artist-id; populated in every album-card
    constructor (home/search/label/foryou/favorites). Artist-page
    release cards leave it empty (their subtitle slot shows the year,
    not the artist, so it stays non-clickable).
  - AlbumCard wraps the artist name in a TouchArea that emits
    media-action("artist", artist-id, "open") and brightens on hover
    only when an id is known.
  - main.rs handles ("artist","open") -> open the artist page;
    FavoritesView (which only forwarded clicked) now forwards the
    artist action.

cc
This commit is contained in:
vicrodh committed 2026-05-22 02:54:52 -06:00
1 parent db8a6ac5b1
commit 75d8571be3
8 files changed
+56 -6

No files matched your search

+3
View File
@@ -341,6 +341,7 @@ fn map_compilation_album(album: PageArtistTrackAlbum) -> CardData {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: String::new(), artist: String::new(),
artist_id: String::new(),
genre: album.genre.map(|g| g.name).unwrap_or_default(), genre: album.genre.map(|g| g.name).unwrap_or_default(),
year: String::new(), year: String::new(),
quality_tier: String::new(), quality_tier: String::new(),
@@ -397,6 +398,7 @@ fn map_release(release: PageArtistRelease) -> CardData {
id: release.id, id: release.id,
title: release.title, title: release.title,
artist, artist,
artist_id: String::new(),
genre: release.genre.map(|g| g.name).unwrap_or_default(), genre: release.genre.map(|g| g.name).unwrap_or_default(),
year, year,
quality_tier, quality_tier,
@@ -439,6 +441,7 @@ fn card_to_item(card: CardData) -> AlbumCardItem {
id: card.id.into(), id: card.id.into(),
title: card.title.into(), title: card.title.into(),
artist: card.year.clone().into(), artist: card.year.clone().into(),
artist_id: "".into(),
genre: card.genre.into(), genre: card.genre.into(),
year: card.year.into(), year: card.year.into(),
quality_tier: card.quality_tier.into(), quality_tier: card.quality_tier.into(),
+3
View File
@@ -103,6 +103,7 @@ pub struct AlbumCard {
pub id: String, pub id: String,
pub title: String, pub title: String,
pub artist: String, pub artist: String,
pub artist_id: String,
pub year: String, pub year: String,
pub quality_tier: String, pub quality_tier: String,
pub quality_label: String, pub quality_label: String,
@@ -252,6 +253,7 @@ fn map_album(album: Album) -> AlbumCard {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: album.artist.name, artist: album.artist.name,
artist_id: album.artist.id.to_string(),
year, year,
quality_tier: tier(album.maximum_bit_depth).to_string(), quality_tier: tier(album.maximum_bit_depth).to_string(),
quality_label, quality_label,
@@ -335,6 +337,7 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
id: a.id.into(), id: a.id.into(),
title: a.title.into(), title: a.title.into(),
artist: a.artist.into(), artist: a.artist.into(),
artist_id: a.artist_id.into(),
genre: "".into(), genre: "".into(),
year: a.year.into(), year: a.year.into(),
quality_tier: a.quality_tier.into(), quality_tier: a.quality_tier.into(),
+9
View File
@@ -64,6 +64,7 @@ pub struct AlbumCard {
pub id: String, pub id: String,
pub title: String, pub title: String,
pub artist: String, pub artist: String,
pub artist_id: String,
pub year: String, pub year: String,
pub quality_tier: String, pub quality_tier: String,
pub quality_label: String, pub quality_label: String,
@@ -106,6 +107,7 @@ fn map_album(album: Album) -> AlbumCard {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: album.artist.name, artist: album.artist.name,
artist_id: album.artist.id.to_string(),
year, year,
quality_tier, quality_tier,
quality_label, quality_label,
@@ -146,6 +148,7 @@ where
id: a.id, id: a.id,
title: a.title, title: a.title,
artist: a.artist, artist: a.artist,
artist_id: String::new(),
year: String::new(), year: String::new(),
quality_tier: a.quality_tier, quality_tier: a.quality_tier,
quality_label: a.quality_label, quality_label: a.quality_label,
@@ -366,6 +369,11 @@ where
.as_ref() .as_ref()
.map(|a| a.name.display.clone()) .map(|a| a.name.display.clone())
.unwrap_or_else(|| page.name.display.clone()), .unwrap_or_else(|| page.name.display.clone()),
artist_id: rel
.artist
.as_ref()
.map(|a| a.id.to_string())
.unwrap_or_default(),
year, year,
quality_tier: match bd { quality_tier: match bd {
Some(d) if d >= 24 => "hires", Some(d) if d >= 24 => "hires",
@@ -406,6 +414,7 @@ fn album_items(cards: &[AlbumCard]) -> Vec<AlbumCardItem> {
id: c.id.clone().into(), id: c.id.clone().into(),
title: c.title.clone().into(), title: c.title.clone().into(),
artist: c.artist.clone().into(), artist: c.artist.clone().into(),
artist_id: c.artist_id.clone().into(),
genre: "".into(), genre: "".into(),
year: c.year.clone().into(), year: c.year.clone().into(),
quality_tier: c.quality_tier.clone().into(), quality_tier: c.quality_tier.clone().into(),
+11
View File
@@ -52,6 +52,9 @@ pub struct CardData {
pub id: String, pub id: String,
pub title: String, pub title: String,
pub artist: String, pub artist: String,
/// Artist id for the clickable artist name; empty = not clickable
/// (e.g. artist-page release cards, whose subtitle slot is the year).
pub artist_id: String,
pub genre: String, pub genre: String,
pub year: String, pub year: String,
/// "hires" | "cd" | "" — drives the icon-only quality badge. /// "hires" | "cd" | "" — drives the icon-only quality badge.
@@ -159,6 +162,7 @@ where
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: album.artist, artist: album.artist,
artist_id: String::new(),
genre: String::new(), genre: String::new(),
year: String::new(), year: String::new(),
quality_tier: album.quality_tier, quality_tier: album.quality_tier,
@@ -220,6 +224,11 @@ fn map_album(album: DiscoverAlbum) -> CardData {
.first() .first()
.map(|a| a.name.clone()) .map(|a| a.name.clone())
.unwrap_or_default(); .unwrap_or_default();
let artist_id = album
.artists
.first()
.map(|a| a.id.to_string())
.unwrap_or_default();
let genre = album.genre.map(|g| g.name).unwrap_or_default(); let genre = album.genre.map(|g| g.name).unwrap_or_default();
let year = album let year = album
.dates .dates
@@ -241,6 +250,7 @@ fn map_album(album: DiscoverAlbum) -> CardData {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist, artist,
artist_id,
genre, genre,
year, year,
quality_tier, quality_tier,
@@ -338,6 +348,7 @@ fn card_to_item(card: CardData) -> AlbumCardItem {
id: card.id.into(), id: card.id.into(),
title: card.title.into(), title: card.title.into(),
artist: card.artist.into(), artist: card.artist.into(),
artist_id: card.artist_id.into(),
genre: card.genre.into(), genre: card.genre.into(),
year: card.year.into(), year: card.year.into(),
quality_tier: card.quality_tier.into(), quality_tier: card.quality_tier.into(),
+3
View File
@@ -33,6 +33,7 @@ pub struct AlbumCard {
pub id: String, pub id: String,
pub title: String, pub title: String,
pub artist: String, pub artist: String,
pub artist_id: String,
pub year: String, pub year: String,
pub quality_tier: String, pub quality_tier: String,
pub quality_label: String, pub quality_label: String,
@@ -112,6 +113,7 @@ fn map_album(album: Album) -> AlbumCard {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: album.artist.name, artist: album.artist.name,
artist_id: album.artist.id.to_string(),
year, year,
quality_tier, quality_tier,
quality_label, quality_label,
@@ -150,6 +152,7 @@ fn to_item(card: AlbumCard) -> AlbumCardItem {
id: card.id.into(), id: card.id.into(),
title: card.title.into(), title: card.title.into(),
artist: card.artist.into(), artist: card.artist.into(),
artist_id: card.artist_id.into(),
genre: "".into(), genre: "".into(),
year: card.year.into(), year: card.year.into(),
quality_tier: card.quality_tier.into(), quality_tier: card.quality_tier.into(),
+3
View File
@@ -45,6 +45,7 @@ pub struct AlbumRow {
pub id: String, pub id: String,
pub title: String, pub title: String,
pub artist: String, pub artist: String,
pub artist_id: String,
pub genre: String, pub genre: String,
pub year: String, pub year: String,
pub quality_tier: String, pub quality_tier: String,
@@ -181,6 +182,7 @@ pub fn map_album(album: Album) -> AlbumRow {
id: album.id, id: album.id,
title: album.title, title: album.title,
artist: album.artist.name, artist: album.artist.name,
artist_id: album.artist.id.to_string(),
genre: album genre: album
.genre .genre
.map(|g| g.name) .map(|g| g.name)
@@ -324,6 +326,7 @@ fn album_item(row: AlbumRow) -> AlbumCardItem {
id: row.id.into(), id: row.id.into(),
title: row.title.into(), title: row.title.into(),
artist: row.artist.into(), artist: row.artist.into(),
artist_id: row.artist_id.into(),
genre: row.genre.into(), genre: row.genre.into(),
year: row.year.into(), year: row.year.into(),
quality_tier: row.quality_tier.into(), quality_tier: row.quality_tier.into(),
+19 -6
View File
@@ -286,13 +286,26 @@ export component AlbumCard inherits Rectangle {
vertical-alignment: center; vertical-alignment: center;
overflow: elide; overflow: elide;
} }
Text { // Artist name — clickable when an artist id is known,
text: root.album.artist; // navigating to the artist page (1:1 with Tauri).
artist-area := TouchArea {
height: 18px; height: 18px;
color: Theme.text-muted; width: artist-text.preferred-width;
font-size: Typography.link; mouse-cursor: root.album.artist-id != "" ? pointer : default;
vertical-alignment: center; clicked => {
overflow: elide; if (root.album.artist-id != "") {
root.media-action("artist", root.album.artist-id, "open");
}
}
artist-text := Text {
text: root.album.artist;
color: root.album.artist-id != "" && artist-area.has-hover
? Theme.text-primary
: Theme.text-muted;
font-size: Typography.link;
vertical-alignment: center;
overflow: elide;
}
} }
} }
@@ -355,6 +355,11 @@ export component FavoritesView inherits Rectangle {
height: root.album-h; height: root.album-h;
album: album; album: album;
clicked(id) => { root.open-album(id); } clicked(id) => { root.open-album(id); }
media-action(kind, id, action) => {
if (kind == "artist") {
root.open-artist(id);
}
}
} }
} }