+36
-37
src/App.tsx
+36
-37
src/App.tsx
···
34
},
35
);
36
const [artistRes] = createResource(settings, artistFetcher);
37
-
const [groupsFetcher] = makeCache(
38
-
(set: Settings) =>
39
-
releaseGroups(set.username, undefined, set.range, set.count),
40
{
41
storage: localStorage,
42
sourceHash(source) {
···
44
},
45
},
46
);
47
-
const [groupsRes] = createResource(settings, groupsFetcher);
48
49
return (
50
<main class="bg-black min-h-screen text-white p-8">
···
94
<h2 class="text-2xl font-semibold my-6 text-center">
95
Top Albums
96
</h2>
97
-
<ReleaseGroups groups={groupsRes()?.release_groups || []} />
98
</div>
99
</div>
100
</Suspense>
···
141
<img
142
src={
143
image() ||
144
-
`https://placehold.co/1000x1000/000000/ffffff?text=${props.artist.artist_name}`
145
}
146
alt={`Thumbnail for ${props.artist.artist_name}`}
147
class="w-32 h-32 mx-auto object-cover rounded-full shadow-lg mb-4 transition-all duration-300 group-hover:scale-105"
···
162
);
163
};
164
165
-
const ReleaseGroups: Component<{ groups: ReleaseGroupsGroup[] }> = (props) => {
166
return (
167
<div class="flex flex-wrap justify-center gap-4">
168
<For each={props.groups}>
169
-
{(group) => <ReleaseGroupItem group={group} />}
170
</For>
171
</div>
172
);
173
};
174
175
-
const ReleaseGroupItem: Component<{ group: ReleaseGroupsGroup }> = (props) => {
176
const [imageFetcher] = makeCache(
177
-
async (group: ReleaseGroupsGroup) => {
178
-
if (!group.caa_release_mbid) {
179
return null;
180
}
181
const result = await getReleaseImageURL(
182
"release",
183
-
group.caa_release_mbid,
184
);
185
return result[0]?.image.replace("http://", "https://");
186
},
···
188
storage: localStorage,
189
},
190
);
191
-
const [image] = createResource(props.group, imageFetcher);
192
193
return (
194
<div class="group relative w-40 p-4 rounded-lg bg-gray-900 transition-all duration-300 hover:bg-gray-800 cursor-pointer">
···
201
<img
202
src={
203
image() ||
204
-
`https://placehold.co/1000x1000/000000/ffffff?text=${props.group.release_group_name}`
205
}
206
-
alt={`Cover for ${props.group.release_group_name}`}
207
class="w-32 h-32 mx-auto object-cover rounded-full shadow-lg mb-4 transition-all duration-300 group-hover:scale-105"
208
/>
209
</Show>
210
<p class="text-white text-center font-bold text-base truncate mb-1">
211
<a
212
target="_blank"
213
-
href={`${MB_API_URL}/release/${props.group.caa_release_mbid}`}
214
>
215
-
{props.group.release_group_name}
216
</a>
217
</p>
218
<p class="text-gray-500 text-base truncate mb-1">
219
-
{props.group.listen_count} listens
220
</p>
221
</div>
222
);
···
243
listen_count: number;
244
};
245
246
-
export type ReleaseGroupsStats = {
247
count: number;
248
from_ts: number;
249
last_updated: number;
250
offset: number;
251
range: string;
252
-
release_groups: ReleaseGroupsGroup[];
253
to_ts: number;
254
-
total_release_group_count: number;
255
user_id: string;
256
};
257
258
-
type ReleaseGroupsGroup = {
259
artist_mbids: string[];
260
artist_name: string;
261
-
artists:
262
-
| {
263
-
artist_credit_name: string;
264
-
artist_mbid: string;
265
-
join_phrase: string;
266
-
}[]
267
-
| null;
268
-
caa_id: number | null;
269
-
caa_release_mbid: string | null;
270
listen_count: number;
271
-
release_group_mbid: string | null;
272
-
release_group_name: string;
273
-
};
274
275
async function artists(
276
user: string,
···
287
return getPayload(url);
288
}
289
290
-
async function releaseGroups(
291
user: string,
292
offset: number = 0,
293
range: Range = "this_week",
294
count: number = 5,
295
-
): Promise<ReleaseGroupsStats> {
296
-
const url = new URL(`${LB_API_URL}/1/stats/user/${user}/release-groups`);
297
298
url.searchParams.set("offset", offset.toString());
299
url.searchParams.set("range", range);
···
34
},
35
);
36
const [artistRes] = createResource(settings, artistFetcher);
37
+
const [releasesFetcher] = makeCache(
38
+
(set: Settings) => releases(set.username, undefined, set.range, set.count),
39
{
40
storage: localStorage,
41
sourceHash(source) {
···
43
},
44
},
45
);
46
+
const [releasesRes] = createResource(settings, releasesFetcher);
47
48
return (
49
<main class="bg-black min-h-screen text-white p-8">
···
93
<h2 class="text-2xl font-semibold my-6 text-center">
94
Top Albums
95
</h2>
96
+
<ReleaseGroups groups={releasesRes()?.releases || []} />
97
</div>
98
</div>
99
</Suspense>
···
140
<img
141
src={
142
image() ||
143
+
`https://placehold.co/100x100/000000/ffffff?text=${props.artist.artist_name}`
144
}
145
alt={`Thumbnail for ${props.artist.artist_name}`}
146
class="w-32 h-32 mx-auto object-cover rounded-full shadow-lg mb-4 transition-all duration-300 group-hover:scale-105"
···
161
);
162
};
163
164
+
const ReleaseGroups: Component<{ groups: Release[] }> = (props) => {
165
return (
166
<div class="flex flex-wrap justify-center gap-4">
167
<For each={props.groups}>
168
+
{(group) => <ReleaseGroupItem release={group} />}
169
</For>
170
</div>
171
);
172
};
173
174
+
const ReleaseGroupItem: Component<{ release: Release }> = (props) => {
175
const [imageFetcher] = makeCache(
176
+
async (release: Release) => {
177
+
if (!release.caa_release_mbid) {
178
return null;
179
}
180
const result = await getReleaseImageURL(
181
"release",
182
+
release.caa_release_mbid,
183
);
184
return result[0]?.image.replace("http://", "https://");
185
},
···
187
storage: localStorage,
188
},
189
);
190
+
const [image] = createResource(props.release, imageFetcher);
191
192
return (
193
<div class="group relative w-40 p-4 rounded-lg bg-gray-900 transition-all duration-300 hover:bg-gray-800 cursor-pointer">
···
200
<img
201
src={
202
image() ||
203
+
`https://placehold.co/100x100/000000/ffffff?text=${props.release.release_name}`
204
}
205
+
alt={`Cover for ${props.release.release_name}`}
206
class="w-32 h-32 mx-auto object-cover rounded-full shadow-lg mb-4 transition-all duration-300 group-hover:scale-105"
207
/>
208
</Show>
209
<p class="text-white text-center font-bold text-base truncate mb-1">
210
<a
211
target="_blank"
212
+
href={`${MB_API_URL}/release/${props.release.caa_release_mbid}`}
213
>
214
+
{props.release.release_name}
215
</a>
216
</p>
217
<p class="text-gray-500 text-base truncate mb-1">
218
+
{props.release.listen_count} listens
219
</p>
220
</div>
221
);
···
242
listen_count: number;
243
};
244
245
+
export type ReleasesStats = {
246
count: number;
247
from_ts: number;
248
last_updated: number;
249
offset: number;
250
range: string;
251
+
releases: Release[];
252
to_ts: number;
253
+
total_release_count: number;
254
user_id: string;
255
};
256
257
+
export interface Release {
258
artist_mbids: string[];
259
artist_name: string;
260
+
artists?: Artist[];
261
+
caa_id?: number;
262
+
caa_release_mbid?: string;
263
listen_count: number;
264
+
release_mbid: string;
265
+
release_name: string;
266
+
}
267
+
268
+
export interface Artist {
269
+
artist_credit_name: string;
270
+
artist_mbid: string;
271
+
join_phrase: string;
272
+
}
273
274
async function artists(
275
user: string,
···
286
return getPayload(url);
287
}
288
289
+
async function releases(
290
user: string,
291
offset: number = 0,
292
range: Range = "this_week",
293
count: number = 5,
294
+
): Promise<ReleasesStats> {
295
+
const url = new URL(`${LB_API_URL}/1/stats/user/${user}/releases`);
296
297
url.searchParams.set("offset", offset.toString());
298
url.searchParams.set("range", range);