Albums are loaded correctly
This commit is contained in:
parent
ffd2c8d3fa
commit
326d080e2c
@ -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>
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user