feat(web+android): Folgen-Herz ins Künstler-Banner, Favoriten zeigen Künstler und Alben
Das Herz sitzt jetzt im Banner neben dem Pin-Zahnrad statt in der Aktionsleiste: die Leiste ist "was ich jetzt tue" (abspielen, mischen, Konzerte), Folgen ist ein Zustand und gehört an den Künstler. Auf beiden Plattformen gleich. Beim Album bleibt das Herz in der Aktionsleiste, dort gibt es kein Banner. Dazu die Übersicht, wem man folgt: die Favoritenseite bekommt Abschnitte Künstler / Alben / Songs. Ohne zusätzlichen Aufruf — getStarred2 liefert Künstler und Alben im selben Paket wie die Songs, wir haben sie bisher verworfen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f043beb5d
commit
19f241e493
8 files changed
+190
-54
No files matched your search
+46
-2
@@ -1,12 +1,13 @@
|
||||
import { S } from '../state.js';
|
||||
import { esc, toast } from '../utils.js';
|
||||
import { api, apiCached, apiIsCached, coverUrl, romajiLookup } from '../api.js';
|
||||
import { makeCard, makeSongItem } from '../ui/components.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 { openEditPlModal } from '../ui/ui.js';
|
||||
// Circular — safe (called inside event handlers)
|
||||
import { navigate } from './navigation.js';
|
||||
import { quickPlayPlaylist } from './detail.js';
|
||||
import { getArtistImage } from './home.js';
|
||||
|
||||
// Besitzer einer Playlist, sofern es nicht die eigene ist. Navidrome liefert `owner` und `public`
|
||||
// in getPlaylists/getPlaylist mit; interessant ist die Angabe nur bei geteilten (oeffentlichen)
|
||||
@@ -230,6 +231,10 @@ export async function loadStarred(sort = _starredSort) {
|
||||
if (searchEl) searchEl.value = '';
|
||||
try {
|
||||
const r = await api('getStarred2');
|
||||
// getStarred2 liefert Kuenstler und Alben im SELBEN Paket wie die Songs — hier ist also die
|
||||
// Uebersicht "wem folge ich" zu haben, ohne einen zusaetzlichen Aufruf.
|
||||
_renderStarredArtists(r.starred2?.artist || []);
|
||||
_renderStarredAlbums(r.starred2?.album || []);
|
||||
let songs = (r.starred2?.song || []).map(s => ({...s, _starred: true}));
|
||||
if (sort === 'newest') songs.sort((a, b) => new Date(b.starred||0) - new Date(a.starred||0));
|
||||
else if (sort === 'oldest') songs.sort((a, b) => new Date(a.starred||0) - new Date(b.starred||0));
|
||||
@@ -238,10 +243,49 @@ export async function loadStarred(sort = _starredSort) {
|
||||
else if (sort === 'plays') songs.sort((a, b) => (b.playCount||0) - (a.playCount||0));
|
||||
el.innerHTML = '';
|
||||
if(!songs.length) { el.innerHTML = '<div class="empty-state"><div class="icon">⭐</div><p>Noch keine Favoriten</p></div>'; return; }
|
||||
const titel = document.getElementById('starred-songs-title');
|
||||
if (titel) titel.style.display = songs.length ? '' : 'none';
|
||||
songs.forEach((s,i) => el.appendChild(makeSongItem(s, i, songs, { noArtistLink: true })));
|
||||
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||
}
|
||||
|
||||
// Kuenstler, denen man folgt. Die Sortierchips oben gelten fuer die Songliste; hier waere eine
|
||||
// eigene Sortierung nur Ballast, also alphabetisch.
|
||||
function _renderStarredArtists(artists) {
|
||||
const wrap = document.getElementById('starred-artists-section');
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '';
|
||||
if (!artists.length) return;
|
||||
const s = mkSection(`Künstler, denen du folgst (${artists.length})`);
|
||||
const row = document.createElement('div'); row.className = 'scroll-row';
|
||||
[...artists].sort((a,b) => (a.name||'').localeCompare(b.name||'')).forEach(a => {
|
||||
const c = makeCard(dispArtistById(a.id, a.name), `${a.albumCount || 0} Alben`, null, '🎤');
|
||||
c.classList.add('artist-card');
|
||||
c.onclick = () => navigate('artist-detail', { id: a.id, name: a.name });
|
||||
row.appendChild(c);
|
||||
const pin = getArtistOverride(a.id);
|
||||
const setzen = img => {
|
||||
if (!img) return;
|
||||
const art = c.querySelector('.card-art');
|
||||
if (art) art.innerHTML = `<img src="${img}" loading="lazy" onerror="this.parentNode.innerHTML='<div class=\'no-art\'>🎤</div>'">`;
|
||||
};
|
||||
if (pin?.image) setzen(pin.image); else getArtistImage(a.id).then(setzen);
|
||||
});
|
||||
s.appendChild(row); wrap.appendChild(s);
|
||||
}
|
||||
|
||||
function _renderStarredAlbums(albums) {
|
||||
const wrap = document.getElementById('starred-albums-section');
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '';
|
||||
if (!albums.length) return;
|
||||
const s = mkSection(`Gemerkte Alben (${albums.length})`);
|
||||
const row = document.createElement('div'); row.className = 'scroll-row';
|
||||
[...albums].sort((a,b) => new Date(b.starred||0) - new Date(a.starred||0))
|
||||
.forEach(a => row.appendChild(makeAlbumCard(a)));
|
||||
s.appendChild(row); wrap.appendChild(s);
|
||||
}
|
||||
|
||||
export function filterStarred(val) {
|
||||
const q = val.trim().toLowerCase();
|
||||
document.querySelectorAll('#starred-songs .song-item').forEach(item => {
|
||||
|
||||
Reference in new issue
Block a user