feat(web): Playlists und Favoriten als Liste in der Seitenleiste

Statt zweier Knoepfe, die erst eine Uebersichtsseite oeffnen, steht unter den
Navigationsrubriken der Abschnitt "Deine Musik": angepinnt Favoriten und "Neu von
deinen Kuenstlern", darunter die Playlists mit Cover und Titelzahl.

Links und nicht rechts, weil rechts belegt ist: dort faehrt die Warteschlange als
290-px-Panel ein und am Rand sitzt der Rollbalken aus 3.1.

Der Listenteil rollt eigenstaendig (nav ist jetzt eine Flex-Spalte), auf niedrigen
Fenstern rollt als Rueckfall die ganze Leiste. Dieselbe gecachte Abfrage wie die
Uebersichtsseite; nach Anlegen/Umbenennen/Loeschen wird der Cache verworfen und neu
gezeichnet, ein Coverwechsel tauscht nur die betroffene Zeile (gleiche Adresse pl-<id>,
sonst kaeme dasselbe Bild aus dem Browser-Cache).

Die Kachel-Uebersicht bleibt per "Alle" erreichbar, die untere Leiste auf dem Handy
bleibt unveraendert.

Ausserdem: in 3.2 uebersehene Mischstelle nachgezogen — der Shuffle-Knopf der
Playlist-Seite benutzte weiter sort(() => Math.random()-.5). Mein Suchmuster hatte nur
die Schreibweise mit 0.5 erfasst, nicht die mit .5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-10-13 19:40:56 +02:00
1 parent 7cdcc352d6
commit 9afd73ae7f
7 files changed
+139 -10

No files matched your search

+8 -1
View File
@@ -3,7 +3,7 @@ import { esc, toast } from '../utils.js';
import { api, coverUrl, ndAuth, invalidateApiCache } from '../api.js';
// Circular — safe (only used inside function bodies)
import { playQueue, dlSongLocal, nativeInsertAt, renderQueue } from '../player/player.js';
import { navigate, loadPlaylists, loadPlaylistDetail } from '../views/views.js';
import { navigate, loadPlaylists, loadPlaylistDetail, renderSidebarPlaylists, bustSidebarCover } from '../views/views.js';
// ─── CONTEXT MENU ───
export function showCtxMenu(e, song, list, idx) {
@@ -120,6 +120,8 @@ export async function createAndAddPlaylist() {
const ids = S.plModalSongs.map(s => `&songId=${s.id}`).join('');
await api('createPlaylist', `&name=${encodeURIComponent(name)}${ids}`);
toast(`✅ Playlist "${name}" erstellt`);
invalidateApiCache('getPlaylists'); // sonst zeigt die Seitenleiste die neue Liste 3 Min. nicht
renderSidebarPlaylists();
if(document.getElementById('page-playlists').classList.contains('active')) loadPlaylists();
} catch(e) { toast('Fehler: ' + e.message); }
}
@@ -178,6 +180,8 @@ export async function saveEditPlaylist() {
if(descEl) { descEl.textContent=comment; descEl.style.display=comment?'':'none'; }
closeEditPlModal();
toast('Playlist aktualisiert');
invalidateApiCache('getPlaylists'); // Name/Sichtbarkeit stehen auch in der Seitenleiste
renderSidebarPlaylists();
} catch(e) { toast('Fehler: '+e.message); }
finally { btn.disabled=false; btn.textContent='Speichern'; }
}
@@ -202,6 +206,7 @@ export async function editPlCoverSelected(input) {
const detailImg = document.getElementById('playlist-cover');
if(detailImg) detailImg.innerHTML = `<img src="${bust}" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>🎶</div>'">`;
document.getElementById('edit-pl-cover').innerHTML = `<img src="${bust}" style="width:100%;height:100%;object-fit:cover">`;
bustSidebarCover(S._currentPlaylist.id, S._currentPlaylist.coverArt);
}, 800);
} catch(e) {
S._ndToken = null;
@@ -224,6 +229,7 @@ export async function editPlRemoveCover() {
S._currentPlaylist.coverArt = null;
document.getElementById('edit-pl-cover').innerHTML = '<div class="no-art" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px">🎶</div>';
document.getElementById('playlist-cover').innerHTML = '<div class="no-art">🎶</div>';
bustSidebarCover(S._currentPlaylist?.id, null);
} catch(e) { S._ndToken = null; toast('Fehler: '+e.message); }
}
@@ -237,6 +243,7 @@ export async function deletePlaylist() {
closeEditPlModal();
toast(`🗑 "${pl.name}" gelöscht`);
invalidateApiCache('getPlaylists'); // drop the 3-min cache so the list reloads without the deleted playlist
renderSidebarPlaylists();
navigate('playlists');
} catch(e) { toast('Fehler beim Löschen: ' + e.message); }
}