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

Configure Feed

Select the types of activity you want to include in your feed.

Add Top Albums embed UI and date filter

Introduce Header to TopAlbums and TopTracks embeds. Render a table of
albums with artwork, artist links, and numeric ranks; use uuid v4 for
row keys. Send startDate/endDate from getLastDays(7) to the
getActorAlbums endpoint. Add small CSS utilities: .rounded-[5px] and
.opacity-60.

+60
+6
apps/embed/public/styles.css
··· 1909 1909 .\!rounded-full { 1910 1910 border-radius: calc(infinity * 1px) !important; 1911 1911 } 1912 + .rounded-\[5px\] { 1913 + border-radius: 5px; 1914 + } 1912 1915 .rounded-box { 1913 1916 border-radius: var(--radius-box); 1914 1917 } ··· 2443 2446 } 2444 2447 .opacity-0 { 2445 2448 opacity: 0%; 2449 + } 2450 + .opacity-60 { 2451 + opacity: 60%; 2446 2452 } 2447 2453 .opacity-100 { 2448 2454 opacity: 100%;
+48
apps/embed/src/embeds/TopAlbumsEmbedPage.tsx
··· 1 + import { v4 } from "uuid"; 2 + import Header from "../components/Header"; 1 3 import type { Album } from "../types/album"; 2 4 import type { Profile } from "../types/profile"; 3 5 ··· 9 11 export function TopAlbumsEmbedPage(props: TopAlbumEmbedPageProps) { 10 12 return ( 11 13 <div className="p-[15px]"> 14 + <Header profile={props.profile} /> 12 15 <h2 className="m-[0px]">Top Albums</h2> 16 + 17 + <div className="w-full overflow-x-auto"> 18 + <table className="table-borderless table"> 19 + <tbody> 20 + {props.albums.map((album, index) => ( 21 + <tr key={v4()}> 22 + <td> 23 + <div className="flex flex-row items-center"> 24 + <div className="mr-[20px] min-w-[30px]">{index + 1}</div> 25 + <a 26 + href={`https://rocksky.app/${album.uri?.split("at://")[1]?.replace("app.rocksky.", "")}`} 27 + target="_blank" 28 + className="flex flex-row items-center no-underline text-inherit" 29 + > 30 + {album.albumArt && ( 31 + <img 32 + className="max-w-[60px] max-h-[60px] mr-[20px] rounded-[5px]" 33 + src={album.albumArt!} 34 + /> 35 + )} 36 + {!album.albumArt && ( 37 + <div className="w-[60px] h-[60px] bg-[var(--color-avatar-background)] flex items-center justify-center mr-[20px]"> 38 + <div className="h-[30px] w-[30px]"></div> 39 + </div> 40 + )} 41 + <div> 42 + <div>{album.title}</div> 43 + <a 44 + href={`https://rocksky.app/${album.artistUri.split("at://")[1]?.replace("app.rocksky.", "")}`} 45 + target="_blank" 46 + className="no-underline text-inherit" 47 + > 48 + <div className="font-rockford-light opacity-60"> 49 + {album.artist} 50 + </div> 51 + </a> 52 + </div> 53 + </a> 54 + </div> 55 + </td> 56 + </tr> 57 + ))} 58 + </tbody> 59 + </table> 60 + </div> 13 61 </div> 14 62 ); 15 63 }
+2
apps/embed/src/embeds/TopTracksEmbedPage.tsx
··· 1 + import Header from "../components/Header"; 1 2 import type { Profile } from "../types/profile"; 2 3 import type { Track } from "../types/track"; 3 4 ··· 9 10 export function TopTracksEmbedPage(props: TopTracksEmbedPageProps) { 10 11 return ( 11 12 <div className="p-[15px]"> 13 + <Header profile={props.profile} /> 12 14 <h2 className="m-[0px]">Top Tracks</h2> 13 15 </div> 14 16 );
+4
apps/embed/src/xrpc/getTopAlbums.ts
··· 1 1 import { ROCKSKY_API_URL } from "../consts"; 2 + import getLastDays from "../lib/getLastDays"; 2 3 import type { Album } from "../types/album"; 3 4 4 5 export default async function getTopAlbums(handle: string) { 6 + const [start, end] = getLastDays(7); 5 7 const url = new URL( 6 8 `${ROCKSKY_API_URL}/xrpc/app.rocksky.actor.getActorAlbums`, 7 9 ); 8 10 url.searchParams.append("did", handle); 11 + url.searchParams.append("startDate", start.toISOString()); 12 + url.searchParams.append("endDate", end.toISOString()); 9 13 url.searchParams.append("limit", "20"); 10 14 const res = await fetch(url); 11 15