Albums are loaded correctly

This commit is contained in:
Tudorel Oprisan 2024-12-20 15:57:02 +00:00
parent ffd2c8d3fa
commit 326d080e2c
2 changed files with 203 additions and 38 deletions

View File

@ -15,26 +15,142 @@
artists: Artist[]; artists: Artist[];
}; };
type Album = {
id: string;
title: string;
artist: string;
coverArtUrl: string;
};
let artistGroups: GroupedArtists[] = []; let artistGroups: GroupedArtists[] = [];
let albums: Album[] = [];
let selectedArtistId: string | null = null;
let error: string | null = null; let error: string | null = null;
async function fetchAlbumsForArtist(id: string) {
try {
const response = await fetchFromAPI("getArtist", { id });
const albums = response["subsonic-response"]?.artist?.album || [];
// Fetch the cover art for each album
for (const album of albums) {
const coverArtUrl = await fetchCoverArt(album.id);
album.coverArtUrl = coverArtUrl; // Add the full URL to the album object
}
return albums;
} catch (err) {
error = (err as Error).message;
return [];
}
}
async function fetchCoverArt(albumId: string) {
try {
const blob = await fetchFromAPI("getCoverArt", {
id: albumId,
size: "150",
});
// Convert the Blob into a URL that can be used in an <img> tag
const coverArtUrl = URL.createObjectURL(blob);
return coverArtUrl;
} catch (err) {
console.error("Error fetching cover art:", err);
return "";
}
}
let showAlbums = false;
const toggleView = () => {
showAlbums = !showAlbums;
};
onMount(async () => { onMount(async () => {
try { try {
const data = await fetchFromAPI("getArtists", {}); const data = await fetchFromAPI("getArtists", {});
artistGroups = (data["subsonic-response"]?.artists?.index || []).map((group: any) => ({ artistGroups = (
data["subsonic-response"]?.artists?.index || []
).map((group: any) => ({
name: group.name, name: group.name,
artists: group.artist || [] artists: group.artist || [],
})); }));
} catch (err) { } catch (err) {
error = (err as Error).message; error = (err as Error).message;
} }
}); });
async function handleArtistClick(artistId: string) {
selectedArtistId = artistId;
albums = await fetchAlbumsForArtist(artistId);
albums.length > 0 && (showAlbums = true);
}
</script> </script>
{#if error}
<p>Error: {error}</p>
{:else if artistGroups.length === 0}
<p>Loading artists...</p>
{:else}
<div>
<button on:click={toggleView}>Toggle Albums/Artists</button>
{#if showAlbums}
{#if selectedArtistId && albums.length > 0}
<h3>Albums for {albums[0].artist}</h3>
<div class="album-list">
{#each albums as album}
<div class="album-card">
{#if album.coverArtUrl}
<img
src={album.coverArtUrl}
alt={album.title}
/>
{/if}
<h3>{album.title}</h3>
</div>
{/each}
</div>
{/if}
{:else}
{#each artistGroups as group}
<section>
<h2>{group.name}</h2>
<!-- Group name (e.g., A, B, etc.) -->
<div class="artist-container">
{#each group.artists as artist}
<button
type="button"
class="artist-card"
on:click={() => handleArtistClick(artist.id)}
on:keydown={(e) =>
e.key === "Enter" &&
handleArtistClick(artist.id)}
aria-label="View albums by {artist.name}"
>
<img
src={artist.artistImageUrl}
alt={artist.name}
/>
<h3>{artist.name}</h3>
<p>{artist.albumCount} album(s)</p>
</button>
{/each}
</div>
</section>
{/each}
{/if}
</div>
{/if}
<style> <style>
.artist-container { .artist-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Responsive grid */ grid-template-columns: repeat(
auto-fill,
minmax(150px, 1fr)
); /* Responsive grid */
gap: 16px; gap: 16px;
padding: 0; padding: 0;
margin: 0 auto; margin: 0 auto;
@ -50,7 +166,9 @@
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
background-color: #f9f9f9; background-color: #f9f9f9;
transition: transform 0.2s, box-shadow 0.2s; transition:
transform 0.2s,
box-shadow 0.2s;
} }
.artist-card:hover { .artist-card:hover {
@ -82,27 +200,31 @@
font-size: 1.5rem; font-size: 1.5rem;
color: #222; color: #222;
} }
</style>
{#if error} .album-list {
<p>Error: {error}</p> display: grid;
{:else if artistGroups.length === 0} grid-template-columns: repeat(
<p>Loading artists...</p> auto-fill,
{:else} minmax(150px, 1fr)
<div> ); /* Flexible column layout */
{#each artistGroups as group} gap: 1rem; /* Space between items */
<section> justify-content: center;
<h2>{group.name}</h2> <!-- Group name (e.g., A, B, etc.) --> padding: 1rem;
<div class="artist-container"> }
{#each group.artists as artist}
<div class="artist-card"> .album-card {
<img src={artist.artistImageUrl} alt="{artist.name}" /> text-align: center;
<h3>{artist.name}</h3> color: black;
<p>{artist.albumCount} album(s)</p> border: 1px solid #ccc;
</div> border-radius: 8px;
{/each} padding: 1rem;
</div> background-color: #f9f9f9;
</section> }
{/each}
</div> .album-card img {
{/if} width: 100%; /* Makes the image responsive within the card */
height: auto;
border-radius: 4px;
object-fit: cover; /* Ensures the image covers the area without distortion */
}
</style>

View File

@ -3,8 +3,44 @@ const API_VERSION = "1.16.1"; // Replace with the Subsonic API version you're us
const API_PASSWORD = "xWPciqjr5VSVDvVS0sAH9L8gKEQm42"; // Replace with your actual token const API_PASSWORD = "xWPciqjr5VSVDvVS0sAH9L8gKEQm42"; // Replace with your actual token
const API_CLIENT = "SvelteApp"; const API_CLIENT = "SvelteApp";
// export async function fetchFromAPI(endpoint: string, params: Record<string, string>) {
// const url = new URL(`${API_URL}/${endpoint}`);
// url.search = new URLSearchParams({
// ...params,
// v: API_VERSION,
// c: API_CLIENT,
// f: "json", // Always request JSON format
// u: "drome", // Replace with your username
// p: API_PASSWORD
// //t: API_TOKEN, // Your token
// //s: "salt", // Optional if you're using salted tokens
// }).toString();
// const response = await fetch(url.toString());
// if (!response.ok) {
// throw new Error(`API request failed: ${response.statusText}`);
// }
// return response.json();
// }
export async function fetchFromAPI(endpoint: string, params: Record<string, string>) { export async function fetchFromAPI(endpoint: string, params: Record<string, string>) {
const url = new URL(`${API_URL}/${endpoint}`); const url = new URL(`${API_URL}/${endpoint}`);
// If we're requesting an image (cover art), we don't need f: "json"
if (endpoint === "getCoverArt") {
// Add the parameters for the cover art request, but exclude f: "json"
url.search = new URLSearchParams({
...params,
v: API_VERSION,
c: API_CLIENT,
u: "drome", // Replace with your username
p: API_PASSWORD,
// Optionally you can add other params like size if needed
}).toString();
} else {
// For JSON requests, we add f: "json"
url.search = new URLSearchParams({ url.search = new URLSearchParams({
...params, ...params,
v: API_VERSION, v: API_VERSION,
@ -12,13 +48,20 @@ export async function fetchFromAPI(endpoint: string, params: Record<string, stri
f: "json", // Always request JSON format f: "json", // Always request JSON format
u: "drome", // Replace with your username u: "drome", // Replace with your username
p: API_PASSWORD p: API_PASSWORD
//t: API_TOKEN, // Your token
//s: "salt", // Optional if you're using salted tokens
}).toString(); }).toString();
}
// Make the fetch request
const response = await fetch(url.toString()); const response = await fetch(url.toString());
if (!response.ok) { if (!response.ok) {
throw new Error(`API request failed: ${response.statusText}`); throw new Error(`API request failed: ${response.statusText}`);
} }
// If it's a request for JSON, return the parsed JSON
if (endpoint !== "getCoverArt") {
return response.json(); return response.json();
}
// If it's a request for cover art, return the raw image data as a Blob
return response.blob();
} }