A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz

Support multiple artists on Song page

Add artists field to feed and library API responses and song atom.
Index artists by name in the Song component and render song.artist as a
comma-separated list, turning names into links when the artist has a
uri.

+43 -17
+1
apps/web/src/api/feed.ts
··· 28 28 spotifyLink: response.data?.spotifyLink, 29 29 composer: response.data?.composer, 30 30 uri: response.data?.uri, 31 + artists: response.data?.artists, 31 32 }; 32 33 }; 33 34
+1
apps/web/src/api/library.ts
··· 27 27 spotifyLink: response.data?.spotifyLink, 28 28 composer: response.data?.composer, 29 29 uri: response.data?.uri, 30 + artists: response.data?.artists, 30 31 }; 31 32 }; 32 33
+7
apps/web/src/atoms/song.ts
··· 2 2 3 3 export const songAtom = atom<{ 4 4 title: string; 5 + artist: string; 6 + artists: { 7 + id: string; 8 + name: string; 9 + picture?: string; 10 + uri?: string; 11 + }[]; 5 12 albumArtist: string; 6 13 cover: string; 7 14 listeners: number;
+34 -17
apps/web/src/pages/song/Song.tsx
··· 29 29 import Credits from "./Credits"; 30 30 import PopularAlbums from "./PopularAlbums"; 31 31 import PopularTracks from "./PopularTracks"; 32 + import * as R from "ramda"; 32 33 33 34 const Group = styled.div` 34 35 display: flex; ··· 198 199 artistTracksResult.isLoading || 199 200 artistAlbumResult.isLoading; 200 201 202 + const artists = R.indexBy(R.prop("name"), song?.artists || []); 203 + 201 204 return ( 202 205 <Main> 203 206 <div className="pb-[100px] pt-[50px]"> ··· 250 253 <HeadingMedium margin={0} className="!text-[var(--color-text)]"> 251 254 {song?.title} 252 255 </HeadingMedium> 253 - {song?.artistUri && ( 254 - <Link 255 - to={`/${song.artistUri.split("at://")[1].replace("app.rocksky.", "")}`} 256 - > 257 - <LabelLarge 258 - margin={0} 259 - className="!text-[var(--color-text)]" 260 - > 261 - {song?.albumArtist} 262 - </LabelLarge> 263 - </Link> 264 - )} 265 - {!song?.artistUri && ( 266 - <LabelLarge margin={0} className="!text-[var(--color-text)]"> 267 - {song?.albumArtist} 268 - </LabelLarge> 269 - )} 256 + {song?.artist 257 + .split(",") 258 + .map((name) => name.trim()) 259 + .filter((name) => name !== "") 260 + .map((name, index, array) => ( 261 + <span key={index}> 262 + <div className="inline-block"> 263 + {artists[name]?.uri ? ( 264 + <Link 265 + to={`/${artists[name]?.uri?.split("at://")[1].replace("app.rocksky.", "")}`} 266 + > 267 + <LabelLarge 268 + margin={0} 269 + className="!text-[var(--color-text)]" 270 + > 271 + {name} 272 + </LabelLarge> 273 + </Link> 274 + ) : ( 275 + <LabelLarge 276 + margin={0} 277 + className="!text-[var(--color-text)]" 278 + > 279 + {name} 280 + </LabelLarge> 281 + )} 282 + </div> 283 + {index < array.length - 1 && ", "} 284 + </span> 285 + ))} 286 + 270 287 <div className="mt-[20px] flex flex-row"> 271 288 <div 272 289 style={{