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:
1 parent
7cdcc352d6
commit
9afd73ae7f
7 files changed
+139
-10
No files matched your search
+58
-1
@@ -3,6 +3,7 @@ import { esc, toast, formatGesamtdauer } from '../utils.js';
|
||||
import { api, apiCached, apiIsCached, coverUrl, romajiLookup, dispArtistById, getArtistOverride } from '../api.js';
|
||||
import { makeCard, makeAlbumCard, makeSongItem, mkSection } from '../ui/components.js';
|
||||
import { playQueue } from '../player/player.js';
|
||||
import { mischeListe } from '../player/queueMath.js';
|
||||
import { openEditPlModal } from '../ui/ui.js';
|
||||
// Circular — safe (called inside event handlers)
|
||||
import { navigate } from './navigation.js';
|
||||
@@ -48,6 +49,62 @@ export async function loadPlaylists() {
|
||||
} catch(e) { g.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||
}
|
||||
|
||||
// ─── DEINE MUSIK (Seitenleiste) ───────────────────────────────────────────────
|
||||
// Die Playlists stehen links als Liste, nicht hinter einem Knopf. Dieselbe gecachte Abfrage
|
||||
// wie die Uebersichtsseite (3 Min.), es entsteht also kein zusaetzlicher Netzverkehr: wer die
|
||||
// Seite oeffnet, hat den Stand schon, und umgekehrt.
|
||||
export async function renderSidebarPlaylists() {
|
||||
const el = document.getElementById('sidebar-playlists');
|
||||
if (!el) return;
|
||||
try {
|
||||
const r = await apiCached('getPlaylists', '', 3 * 60 * 1000);
|
||||
const lists = r.playlists?.playlist || [];
|
||||
el.innerHTML = '';
|
||||
if (!lists.length) {
|
||||
el.innerHTML = '<div class="lib-empty">Noch keine Playlists — mit + anlegen.</div>';
|
||||
return;
|
||||
}
|
||||
for (const pl of lists) {
|
||||
const von = _besitzer(pl);
|
||||
const cv = coverUrl(pl.coverArt, 128);
|
||||
const row = document.createElement('div');
|
||||
row.className = 'lib-item';
|
||||
row.dataset.plId = pl.id;
|
||||
row.title = pl.name + (von ? ' · von ' + von : '');
|
||||
row.innerHTML =
|
||||
`<div class="lib-art">${cv ? `<img src="${cv}" loading="lazy" onerror="_imgRetry(this,'🎶')">` : '🎶'}</div>` +
|
||||
`<div class="lib-text"><div class="lib-name">${esc(pl.name)}</div>` +
|
||||
`<div class="lib-sub">${pl.songCount || 0} Titel${von ? ' · ' + esc(von) : ''}</div></div>`;
|
||||
row.onclick = () => navigate('playlist-detail', pl);
|
||||
el.appendChild(row);
|
||||
}
|
||||
markSidebarPlaylist(S._currentPlaylist?.id);
|
||||
} catch {
|
||||
// Kein Alarm in der Seitenleiste: die Uebersichtsseite meldet Fehler ohnehin.
|
||||
el.innerHTML = '<div class="lib-empty">Playlists nicht geladen.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tauscht das Cover EINER Zeile aus, nachdem es gerade hochgeladen oder entfernt wurde.
|
||||
* Noetig, weil die Cover-Adresse dieselbe bleibt (`pl-<id>`): ein Neuzeichnen holte dasselbe
|
||||
* Bild aus dem Browser-Cache, und die Zeile zeigte weiter das alte Cover.
|
||||
*/
|
||||
export function bustSidebarCover(plId, coverArt) {
|
||||
const row = document.querySelector(`#sidebar-playlists .lib-item[data-pl-id="${plId}"] .lib-art`);
|
||||
if (!row) return;
|
||||
const cv = coverArt ? coverUrl(coverArt, 128) + '&_cb=' + Date.now() : '';
|
||||
row.innerHTML = cv ? `<img src="${cv}" onerror="_imgRetry(this,'🎶')">` : '🎶';
|
||||
}
|
||||
|
||||
/** Hebt die laufend geoeffnete Playlist hervor. Aufgerufen aus navigate(). */
|
||||
export function markSidebarPlaylist(plId) {
|
||||
const id = plId == null ? '' : String(plId);
|
||||
document.querySelectorAll('#sidebar-playlists .lib-item').forEach(el => {
|
||||
el.classList.toggle('active', !!id && el.dataset.plId === id);
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlaylistDetail(pl) {
|
||||
S._currentPlaylist = {...pl};
|
||||
document.getElementById('playlist-detail-name').textContent = pl.name;
|
||||
@@ -73,7 +130,7 @@ export async function loadPlaylistDetail(pl) {
|
||||
if(descEl) { descEl.textContent = full.comment||''; descEl.style.display = full.comment ? '' : 'none'; }
|
||||
document.getElementById('btn-play-playlist').onclick = () => playQueue(songs, 0);
|
||||
document.getElementById('btn-shuffle-playlist').onclick = () => {
|
||||
playQueue([...songs].sort(() => Math.random()-.5), 0);
|
||||
playQueue(mischeListe(songs), 0);
|
||||
};
|
||||
sl.innerHTML = '';
|
||||
const plSearch = document.getElementById('playlist-search');
|
||||
|
||||
Reference in new issue
Block a user