your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import type { Item } from '$lib/types';
3 import { onMount } from 'svelte';
4 import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context';
5 import { CardDefinitionsByType } from '..';
6 import AlbumArt from './AlbumArt.svelte';
7 import { RelativeTime } from '@foxui/time';
8
9 interface Artist {
10 artistName: string;
11 }
12
13 interface PlayValue {
14 releaseMbId?: string;
15 trackName: string;
16 playedTime?: string;
17 artists?: Artist[];
18 originUrl?: string;
19 }
20
21 interface Play {
22 uri: string;
23 value: PlayValue;
24 }
25
26 let { item }: { item: Item } = $props();
27
28 const data = getAdditionalUserData();
29 // svelte-ignore state_referenced_locally
30 let feed = $state(data[item.cardType] as Play[] | undefined);
31
32 let did = getDidContext();
33 let handle = getHandleContext();
34
35 onMount(async () => {
36 if (feed) return;
37
38 feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], {
39 did,
40 handle
41 })) as Play[] | undefined;
42
43 data[item.cardType] = feed;
44 });
45
46 function isNumeric(str: string) {
47 if (typeof str != 'string') return false;
48 return !isNaN(Number(str)) && !isNaN(parseFloat(str));
49 }
50</script>
51
52{#snippet musicItem(play: Play)}
53 <div class="flex w-full items-center gap-3">
54 <div class="size-10 shrink-0">
55 <AlbumArt releaseMbId={play.value.releaseMbId} alt="" />
56 </div>
57 <div class="min-w-0 flex-1">
58 <div class="inline-flex w-full max-w-full justify-between gap-2">
59 <div
60 class="text-accent-500 accent:text-accent-950 min-w-0 flex-1 shrink truncate font-semibold"
61 >
62 {play.value.trackName}
63 </div>
64
65 {#if play.value.playedTime}
66 <div class="shrink-0 text-xs">
67 <RelativeTime
68 date={new Date(
69 isNumeric(play.value.playedTime)
70 ? parseInt(play.value.playedTime) * 1000
71 : play.value.playedTime
72 )}
73 locale="en-US"
74 /> ago
75 </div>
76 {:else}
77 <div></div>
78 {/if}
79 </div>
80 <div class="my-1 min-w-0 gap-2 truncate text-xs whitespace-nowrap">
81 {(play?.value?.artists ?? []).map((a) => a.artistName).join(', ')}
82 </div>
83 </div>
84 </div>
85{/snippet}
86
87<div class="z-10 flex h-full w-full flex-col gap-4 overflow-y-scroll p-4">
88 {#each feed ?? [] as play (play.uri)}
89 {#if play.value.originUrl}
90 <a href={play.value.originUrl} target="_blank" rel="noopener noreferrer" class="w-full">
91 {@render musicItem(play)}
92 </a>
93 {:else}
94 {@render musicItem(play)}
95 {/if}
96 {/each}
97</div>