diff --git a/subsonic-ui-svelte/src/components/ArtistList.svelte b/subsonic-ui-svelte/src/components/ArtistList.svelte
index db56bf5..317a04c 100644
--- a/subsonic-ui-svelte/src/components/ArtistList.svelte
+++ b/subsonic-ui-svelte/src/components/ArtistList.svelte
@@ -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
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);
+ }
+{#if error}
+
Error: {error}
+{:else if artistGroups.length === 0}
+ Loading artists...
+{:else}
+
+
+ {#if showAlbums}
+ {#if selectedArtistId && albums.length > 0}
+
Albums for {albums[0].artist}
+
+ {#each albums as album}
+
+ {#if album.coverArtUrl}
+

+ {/if}
+
{album.title}
+
+ {/each}
+
+ {/if}
+ {:else}
+ {#each artistGroups as group}
+
+ {group.name}
+
+
+ {#each group.artists as artist}
+
+ {/each}
+
+
+ {/each}
+ {/if}
+
+{/if}
+
-{#if error}
- Error: {error}
-{:else if artistGroups.length === 0}
- Loading artists...
-{:else}
-
- {#each artistGroups as group}
-
- {group.name}
-
- {#each group.artists as artist}
-
-

-
{artist.name}
-
{artist.albumCount} album(s)
-
- {/each}
-
-
- {/each}
-
-{/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 */
+ }
+
diff --git a/subsonic-ui-svelte/src/lib/api.ts b/subsonic-ui-svelte/src/lib/api.ts
index 0b74831..0c3db26 100644
--- a/subsonic-ui-svelte/src/lib/api.ts
+++ b/subsonic-ui-svelte/src/lib/api.ts
@@ -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) {
+// 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) {
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();
}