Albums are loaded correctly
This commit is contained in:
parent
ffd2c8d3fa
commit
326d080e2c
@ -15,26 +15,142 @@
|
||||
artists: Artist[];
|
||||
};
|
||||
|
||||
type Album = {
|
||||
id: string;
|
||||
title: string;
|
||||
artist: string;
|
||||
coverArtUrl: string;
|
||||
};
|
||||
|
||||
let artistGroups: GroupedArtists[] = [];
|
||||
let albums: Album[] = [];
|
||||
let selectedArtistId: 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 () => {
|
||||
try {
|
||||
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,
|
||||
artists: group.artist || []
|
||||
artists: group.artist || [],
|
||||
}));
|
||||
} catch (err) {
|
||||
error = (err as Error).message;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleArtistClick(artistId: string) {
|
||||
selectedArtistId = artistId;
|
||||
albums = await fetchAlbumsForArtist(artistId);
|
||||
albums.length > 0 && (showAlbums = true);
|
||||
}
|
||||
</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>
|
||||
.artist-container {
|
||||
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;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
@ -50,7 +166,9 @@
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: #f9f9f9;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.artist-card:hover {
|
||||
@ -82,27 +200,31 @@
|
||||
font-size: 1.5rem;
|
||||
color: #222;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if error}
|
||||
<p>Error: {error}</p>
|
||||
{:else if artistGroups.length === 0}
|
||||
<p>Loading artists...</p>
|
||||
{:else}
|
||||
<div>
|
||||
{#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}
|
||||
<div class="artist-card">
|
||||
<img src={artist.artistImageUrl} alt="{artist.name}" />
|
||||
<h3>{artist.name}</h3>
|
||||
<p>{artist.albumCount} album(s)</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
.album-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fill,
|
||||
minmax(150px, 1fr)
|
||||
); /* Flexible column layout */
|
||||
gap: 1rem; /* Space between items */
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.album-card {
|
||||
text-align: center;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.album-card img {
|
||||
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>
|
||||
|
||||
@ -3,22 +3,65 @@ 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_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>) {
|
||||
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();
|
||||
|
||||
// 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({
|
||||
...params,
|
||||
v: API_VERSION,
|
||||
c: API_CLIENT,
|
||||
f: "json", // Always request JSON format
|
||||
u: "drome", // Replace with your username
|
||||
p: API_PASSWORD
|
||||
}).toString();
|
||||
}
|
||||
|
||||
// Make the fetch request
|
||||
const response = await fetch(url.toString());
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
|
||||
// If it's a request for JSON, return the parsed JSON
|
||||
if (endpoint !== "getCoverArt") {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// If it's a request for cover art, return the raw image data as a Blob
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user