your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { getAdditionalUserData } from '$lib/website/context';
4 import type { ContentComponentProps } from '../../../types';
5 import LastFMAlbumArt from '../LastFMAlbumArt.svelte';
6 import { RelativeTime } from '@foxui/time';
7 import { fetchLastFM } from '../api.remote';
8
9 interface Track {
10 name: string;
11 artist: { '#text': string };
12 album: { '#text': string };
13 image: { '#text': string; size: string }[];
14 url: string;
15 date?: { uts: string };
16 '@attr'?: { nowplaying: string };
17 }
18
19 let { item }: ContentComponentProps = $props();
20
21 const data = getAdditionalUserData();
22 const cacheKey = $derived(`lastfmRecentTracks:${item.cardData.lastfmUsername}`);
23
24 // svelte-ignore state_referenced_locally
25 let tracks = $state(data[cacheKey] as Track[] | undefined);
26 let error = $state(false);
27
28 onMount(async () => {
29 if (tracks) return;
30 if (!item.cardData.lastfmUsername) return;
31
32 try {
33 const result = await fetchLastFM({
34 method: 'user.getRecentTracks',
35 user: item.cardData.lastfmUsername
36 });
37 if (result) {
38 tracks = result?.recenttracks?.track ?? [];
39 data[cacheKey] = tracks;
40 } else {
41 error = true;
42 }
43 } catch {
44 error = true;
45 }
46 });
47</script>
48
49<div class="z-10 flex h-full w-full flex-col gap-3 overflow-y-scroll p-4">
50 {#if tracks && tracks.length > 0}
51 {#each tracks as track, i (track.url + i)}
52 <a
53 href={track.url}
54 target="_blank"
55 rel="noopener noreferrer"
56 class="flex w-full items-center gap-3"
57 >
58 <div class="size-10 shrink-0">
59 <LastFMAlbumArt images={track.image} alt={track.album?.['#text']} />
60 </div>
61 <div class="min-w-0 flex-1">
62 <div class="inline-flex w-full max-w-full justify-between gap-2">
63 <div
64 class="text-accent-500 accent:text-accent-950 min-w-0 flex-1 shrink truncate font-semibold"
65 >
66 {track.name}
67 </div>
68 {#if track['@attr']?.nowplaying === 'true'}
69 <div class="flex shrink-0 items-center gap-1 text-xs text-green-500">
70 <span class="inline-block size-2 animate-pulse rounded-full bg-green-500"></span>
71 Now
72 </div>
73 {:else if track.date?.uts}
74 <div class="shrink-0 text-xs">
75 <RelativeTime date={new Date(parseInt(track.date.uts) * 1000)} locale="en-US" /> ago
76 </div>
77 {/if}
78 </div>
79 <div class="my-1 min-w-0 truncate text-xs whitespace-nowrap">
80 {track.artist?.['#text']}
81 </div>
82 </div>
83 </a>
84 {/each}
85 {:else if error}
86 <div
87 class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm"
88 >
89 Failed to load tracks.
90 </div>
91 {:else if tracks}
92 <div
93 class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm"
94 >
95 No recent tracks found.
96 </div>
97 {:else}
98 <div
99 class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm"
100 >
101 Loading tracks...
102 </div>
103 {/if}
104</div>