diff --git a/internal/database/library.go b/internal/database/library.go index 67731f7..4485ec5 100644 --- a/internal/database/library.go +++ b/internal/database/library.go @@ -492,6 +492,21 @@ func ListSongArtistNames(db *sql.DB, songID string) ([]string, error) { return out, rows.Err() } +// PrimarySongArtistID returns the ID of a song's first-credited artist +// (position 0), or "" if the song has no linked artist — used to link +// the now-playing bar's artist name to that artist's page. +func PrimarySongArtistID(db *sql.DB, songID string) (string, error) { + var id string + err := db.QueryRow(`SELECT a.id FROM song_artists sa JOIN artists a ON a.id = sa.artist_id WHERE sa.song_id = ? ORDER BY sa.position LIMIT 1`, songID).Scan(&id) + if errors.Is(err, sql.ErrNoRows) { + return "", nil + } + if err != nil { + return "", err + } + return id, nil +} + func nullInt(n sql.NullInt64) any { if !n.Valid { return nil diff --git a/internal/web/views.go b/internal/web/views.go index 03a9187..4d1899e 100644 --- a/internal/web/views.go +++ b/internal/web/views.go @@ -28,6 +28,7 @@ type SongRow struct { ID string Title string ArtistName string + ArtistID string AlbumTitle string AlbumID string CoverArtID string @@ -81,6 +82,9 @@ func toSongRow(db *sql.DB, s *database.Song, album *database.Album, userID strin row.ArtistName += ", " + n } } + if id, err := database.PrimarySongArtistID(db, s.ID); err == nil { + row.ArtistID = id + } if album != nil { row.AlbumTitle = album.Title row.AlbumID = album.ID diff --git a/web/static/css/app.css b/web/static/css/app.css index 85e06de..4a81e96 100644 --- a/web/static/css/app.css +++ b/web/static/css/app.css @@ -226,19 +226,33 @@ body { } .vs-player-title { + display: block; font-size: 0.85rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + color: inherit; + text-decoration: none; } .vs-player-artist { + display: block; font-size: 0.78rem; color: var(--bs-secondary-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-decoration: none; +} + +.vs-player-title:hover, +.vs-player-artist:hover { + text-decoration: underline; +} + +.vs-player-art[role="button"] { + cursor: pointer; } .vs-player-controls { diff --git a/web/static/js/app.js b/web/static/js/app.js index 7f531b8..15bd6bc 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -114,7 +114,9 @@ if (!song) return; this.audio.src = "/stream/" + encodeURIComponent(song.id); this.ui.title.textContent = song.title || ""; + this.ui.title.href = song.albumId ? "/albums/" + encodeURIComponent(song.albumId) : "javascript:void(0)"; this.ui.artist.textContent = song.artist || ""; + this.ui.artist.href = song.artistId ? "/artists/" + encodeURIComponent(song.artistId) : "javascript:void(0)"; this.ui.art.src = song.cover ? "/cover-art/" + encodeURIComponent(song.cover) + "?size=100" : "/static/img/default-art.svg"; this.ui.starBtn.dataset.itemType = "song"; this.ui.starBtn.dataset.itemId = song.id; @@ -307,7 +309,9 @@ id: node.dataset.songId, title: node.dataset.title, artist: node.dataset.artist, + artistId: node.dataset.artistId, album: node.dataset.album, + albumId: node.dataset.albumId, cover: node.dataset.cover, starred: node.dataset.starred === "1", }); @@ -348,6 +352,20 @@ }); } + var artModalEl = document.getElementById("vs-player-art-modal"); + if (ui.art && artModalEl && window.bootstrap) { + var artModalImg = document.getElementById("vs-player-art-modal-img"); + var artModal = new bootstrap.Modal(artModalEl); + ui.art.addEventListener("click", function () { + var song = player.queue[player.index]; + artModalImg.src = song && song.cover + ? "/cover-art/" + encodeURIComponent(song.cover) + "?size=1200" + : "/static/img/default-art.svg"; + artModalImg.alt = song ? (song.title || "") : ""; + artModal.show(); + }); + } + var lyricsBtn = document.getElementById("vs-lyrics-btn"); if (lyricsBtn && ui.lyricsPanel) { lyricsBtn.addEventListener("click", function () { diff --git a/web/templates/partials/cards.html b/web/templates/partials/cards.html index 64a5440..75593f3 100644 --- a/web/templates/partials/cards.html +++ b/web/templates/partials/cards.html @@ -22,7 +22,9 @@ data-song-id="{{$s.ID}}" data-title="{{$s.Title}}" data-artist="{{$s.ArtistName}}" + data-artist-id="{{$s.ArtistID}}" data-album="{{$s.AlbumTitle}}" + data-album-id="{{$s.AlbumID}}" data-cover="{{$s.CoverArtID}}" data-starred="{{if $s.Starred}}1{{else}}0{{end}}">