From abf9d61d72258535836e7e9cbac568a77b895fc9 Mon Sep 17 00:00:00 2001 From: J0Z1L Date: Sat, 28 Feb 2026 00:07:18 +0100 Subject: [PATCH] Add Spotify search type filter for album/track/artist with album-based Lidarr flow --- public/app.js | 119 +++++++++++++++++++++++++++++++++++----------- public/index.html | 7 ++- public/styles.css | 7 +++ server.js | 74 ++++++++++++++++++++++++---- 4 files changed, 167 insertions(+), 40 deletions(-) diff --git a/public/app.js b/public/app.js index f7f2574..0b8a7a8 100644 --- a/public/app.js +++ b/public/app.js @@ -1,5 +1,6 @@ const queryInput = document.getElementById('query'); const searchBtn = document.getElementById('searchBtn'); +const searchTypeSelect = document.getElementById('searchType'); const results = document.getElementById('results'); const statusEl = document.getElementById('status'); const dialog = document.getElementById('albumDialog'); @@ -10,7 +11,6 @@ const sendBtn = document.getElementById('sendBtn'); const cleanupToggle = document.getElementById('cleanupToggle'); let selectedAlbum = null; -let selectedTracks = []; const CLEANUP_KEY = 'cleanupExtras'; cleanupToggle.checked = localStorage.getItem(CLEANUP_KEY) === 'true'; @@ -40,40 +40,85 @@ async function fetchJson(url, options = {}) { return data; } -function renderAlbums(albums) { +function createAlbumCard(album, preselectedTrackNames = []) { + const card = document.createElement('article'); + card.className = 'album-card'; + + card.innerHTML = ` + ${album.image ? `${album.name}` : '
'} +
+

${album.name}

+

${album.artist}

+

${album.totalTracks} Tracks - ${album.releaseDate || 'unbekannt'}

+ +
+ `; + + card.querySelector('button').addEventListener('click', () => openAlbumDialog(album.id, preselectedTrackNames)); + return card; +} + +function createTrackCard(track) { + const card = document.createElement('article'); + card.className = 'album-card'; + + card.innerHTML = ` + ${track.image ? `${track.albumName}` : '
'} +
+

${track.trackName}

+

${track.artist}

+

Album: ${track.albumName}

+ +
+ `; + + card.querySelector('button').addEventListener('click', () => openAlbumDialog(track.albumId, [track.trackName])); + return card; +} + +function createArtistCard(artist) { + const card = document.createElement('article'); + card.className = 'album-card'; + + card.innerHTML = ` + ${artist.image ? `${artist.name}` : '
'} +
+

${artist.name}

+

${artist.followers || 0} Follower

+

Artist

+ +
+ `; + + card.querySelector('button').addEventListener('click', () => loadArtistAlbums(artist.id, artist.name)); + return card; +} + +function renderItems(type, items) { results.innerHTML = ''; - if (!albums.length) { - results.innerHTML = '

Keine Alben gefunden.

'; + if (!items.length) { + results.innerHTML = '

Keine Treffer gefunden.

'; return; } - for (const album of albums) { - const card = document.createElement('article'); - card.className = 'album-card'; - - card.innerHTML = ` - ${album.image ? `${album.name}` : '
'} -
-

${album.name}

-

${album.artist}

-

${album.totalTracks} Tracks - ${album.releaseDate || 'unbekannt'}

- -
- `; - - card.querySelector('button').addEventListener('click', () => openAlbumDialog(album.id)); - results.appendChild(card); + for (const item of items) { + let card; + if (type === 'track') card = createTrackCard(item); + if (type === 'artist') card = createArtistCard(item); + if (type === 'album') card = createAlbumCard(item); + if (card) results.appendChild(card); } } -async function openAlbumDialog(albumId) { +async function openAlbumDialog(albumId, preselectedTrackNames = []) { setStatus('Lade Albumdetails...'); try { const album = await fetchJson(`/api/spotify/album/${albumId}`); + const preselected = new Set(preselectedTrackNames || []); + selectedAlbum = album; - selectedTracks = album.tracks.map((t) => t.name); dialogTitle.textContent = album.name; dialogArtist.textContent = `Artist: ${album.artist}`; @@ -82,8 +127,9 @@ async function openAlbumDialog(albumId) { for (const track of album.tracks) { const row = document.createElement('label'); row.className = 'track-row'; + const isChecked = preselected.size > 0 ? preselected.has(track.name) : true; row.innerHTML = ` - + ${track.trackNumber}. ${track.name} (${formatDuration(track.durationMs)}) `; @@ -97,8 +143,22 @@ async function openAlbumDialog(albumId) { } } -async function searchAlbums() { +async function loadArtistAlbums(artistId, artistName) { + setStatus(`Lade Alben von ${artistName}...`); + + try { + const data = await fetchJson(`/api/spotify/artist/${artistId}/albums`); + renderItems('album', data.albums || []); + setStatus(`${data.albums.length} Alben von ${artistName} gefunden.`); + } catch (err) { + setStatus(err.message, true); + } +} + +async function searchSpotify() { const q = queryInput.value.trim(); + const type = searchTypeSelect.value; + if (!q) { setStatus('Bitte Suchbegriff eingeben.', true); return; @@ -108,9 +168,10 @@ async function searchAlbums() { results.innerHTML = ''; try { - const data = await fetchJson(`/api/spotify/search?q=${encodeURIComponent(q)}`); - renderAlbums(data.albums || []); - setStatus(`${data.albums.length} Alben gefunden.`); + const data = await fetchJson(`/api/spotify/search?q=${encodeURIComponent(q)}&type=${encodeURIComponent(type)}`); + const items = data.items || []; + renderItems(type, items); + setStatus(`${items.length} Treffer gefunden (${type}).`); } catch (err) { setStatus(err.message, true); } @@ -160,10 +221,10 @@ async function sendToLidarr(event) { } } -searchBtn.addEventListener('click', searchAlbums); +searchBtn.addEventListener('click', searchSpotify); queryInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { - searchAlbums(); + searchSpotify(); } }); sendBtn.addEventListener('click', sendToLidarr); diff --git a/public/index.html b/public/index.html index a2e930b..3ae6469 100644 --- a/public/index.html +++ b/public/index.html @@ -28,8 +28,13 @@