refactor: dedup buildTrackList, extract topsong row helper, split romaji to lib
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
84f1fd006d
commit
b9a5c6468f
4 files changed
+162
-171
No files matched your search
+7
-17
@@ -46,9 +46,9 @@ function _nsBridge(method, ...args) {
|
||||
try { window.NSBridge[method](...args); } catch(_) {}
|
||||
}
|
||||
|
||||
// Build the NSBridge Track JSON array from S.queue.
|
||||
function buildTrackList() {
|
||||
return S.queue.map(s => ({
|
||||
// Convert a single Navidrome song to the NSBridge Track schema.
|
||||
function _trackToNative(s) {
|
||||
return {
|
||||
id: s.id,
|
||||
url: streamUrl(s.id),
|
||||
title: s.title || '',
|
||||
@@ -56,9 +56,12 @@ function buildTrackList() {
|
||||
album: s.album || null,
|
||||
artworkUrl: s.coverArt ? coverUrl(s.coverArt, 1024) : null,
|
||||
durationMs: Math.floor((s.duration || 0) * 1000),
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
// Build the NSBridge Track JSON array from S.queue.
|
||||
const buildTrackList = () => S.queue.map(_trackToNative);
|
||||
|
||||
// Push the current JS queue to native whenever S.queue changes (radio top-ups, etc.).
|
||||
function _pushQueueIfBridge() {
|
||||
if (!HAS_BRIDGE) return;
|
||||
@@ -73,19 +76,6 @@ export function nativeInsertAt(song, position) {
|
||||
try { window.NSBridge.addToQueue(JSON.stringify(_trackToNative(song)), position); } catch(_) {}
|
||||
}
|
||||
|
||||
// Convert a single Navidrome song to the NSBridge Track schema (used for addToQueue).
|
||||
function _trackToNative(s) {
|
||||
return {
|
||||
id: s.id,
|
||||
url: streamUrl(s.id),
|
||||
title: s.title || '',
|
||||
artist: s.artist || '',
|
||||
album: s.album || null,
|
||||
artworkUrl: s.coverArt ? coverUrl(s.coverArt, 1024) : null,
|
||||
durationMs: Math.floor((s.duration || 0) * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
// ─── NATIVE → JS CALLBACKS ────────────────────────────────────────────────
|
||||
// window.NSPlayer is the surface that MusicService calls back into.
|
||||
// Installed here so it exists before onPageFinished fires.
|
||||
|
||||
+24
-36
@@ -310,6 +310,28 @@ export async function loadAlbums(type = _albumSort) {
|
||||
} catch(e) { g.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||
}
|
||||
|
||||
// ─── SHARED TOPSONG ROW ───
|
||||
// Used by loadArtistDetail (top songs) and loadArtistAllSongs.
|
||||
function _makeTopsongRow(s, num, subtitle, list, idx) {
|
||||
const cv = coverUrl(s.coverArt, 128);
|
||||
if (s.starred && !s._starred) s._starred = true;
|
||||
const row = document.createElement('div'); row.className = 'topsong-item';
|
||||
row.innerHTML = `<div class="topsong-num">${num}</div>
|
||||
<div class="topsong-thumb">${cv ? `<img src="${cv}" loading="lazy">` : '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:18px">🎵</div>'}</div>
|
||||
<div style="min-width:0;flex:1"><div class="topsong-title">${esc(s.title)}</div>${subtitle ? `<div style="font-size:11px;color:var(--text2)">${esc(subtitle)}</div>` : ''}</div>
|
||||
<div class="topsong-plays">${s.playCount ? s.playCount.toLocaleString() : '—'}</div>
|
||||
<div class="song-actions" style="flex-shrink:0">
|
||||
<button class="icon-btn${s._starred ? ' starred' : ''}" title="Favorit"><svg width="12" height="12" viewBox="0 0 24 24" fill="${s._starred ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></button>
|
||||
<button class="icon-btn song-menu-btn" title="Mehr"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg></button>
|
||||
</div>`;
|
||||
row.onclick = () => playQueue(list, idx);
|
||||
row.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, list, idx); });
|
||||
const btns = row.querySelectorAll('.icon-btn');
|
||||
btns[0].onclick = e => { e.stopPropagation(); toggleStar(s, btns[0]); };
|
||||
btns[1].onclick = e => { e.stopPropagation(); showCtxMenu(e, s, list, idx); };
|
||||
return row;
|
||||
}
|
||||
|
||||
// ─── ARTIST DETAIL ───
|
||||
export async function loadArtistDetail(artist) {
|
||||
document.getElementById('artist-detail-name').textContent = artist.name;
|
||||
@@ -395,25 +417,7 @@ export async function loadArtistDetail(artist) {
|
||||
h.className = 'section-title'; h.style.marginBottom = '10px'; h.textContent = 'Top Songs';
|
||||
tsEl.appendChild(h);
|
||||
const list = document.createElement('div'); list.style.marginBottom = '8px';
|
||||
topSongs.forEach((s, i) => {
|
||||
const cv = coverUrl(s.coverArt, 128);
|
||||
const row = document.createElement('div'); row.className = 'topsong-item';
|
||||
if(s.starred && !s._starred) s._starred = true;
|
||||
row.innerHTML = `<div class="topsong-num">${i+1}</div>
|
||||
<div class="topsong-thumb">${cv ? `<img src="${cv}" loading="lazy">` : '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:18px">🎵</div>'}</div>
|
||||
<div style="min-width:0;flex:1"><div class="topsong-title">${esc(s.title)}</div><div style="font-size:11px;color:var(--text2)">${esc(s.album||'')}</div></div>
|
||||
<div class="topsong-plays">${s.playCount ? s.playCount.toLocaleString() : '—'}</div>
|
||||
<div class="song-actions" style="flex-shrink:0">
|
||||
<button class="icon-btn${s._starred?' starred':''}" title="Favorit"><svg width="12" height="12" viewBox="0 0 24 24" fill="${s._starred?'currentColor':'none'}" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></button>
|
||||
<button class="icon-btn song-menu-btn" title="Mehr"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg></button>
|
||||
</div>`;
|
||||
row.onclick = () => playQueue(topSongs, i);
|
||||
row.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, topSongs, i); });
|
||||
const btns = row.querySelectorAll('.icon-btn');
|
||||
btns[0].onclick = e => { e.stopPropagation(); toggleStar(s, btns[0]); };
|
||||
btns[1].onclick = e => { e.stopPropagation(); showCtxMenu(e, s, topSongs, i); };
|
||||
list.appendChild(row);
|
||||
});
|
||||
topSongs.forEach((s, i) => list.appendChild(_makeTopsongRow(s, i + 1, s.album || '', topSongs, i)));
|
||||
tsEl.appendChild(list);
|
||||
if(ndAlbums.length) {
|
||||
const total = ndAlbums.reduce((n, a) => n + (a.songCount || 0), 0);
|
||||
@@ -580,23 +584,7 @@ export async function loadArtistAllSongs({ albums, artist }) {
|
||||
h.textContent = s._alb?.name || '';
|
||||
el.appendChild(h);
|
||||
}
|
||||
const cv = coverUrl(s.coverArt, 60);
|
||||
if(s.starred && !s._starred) s._starred = true;
|
||||
const row = document.createElement('div'); row.className = 'topsong-item';
|
||||
row.innerHTML = `<div class="topsong-num">${s.trackNumber||''}</div>
|
||||
<div class="topsong-thumb">${cv?`<img src="${cv}" loading="lazy">`:'<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:16px">🎵</div>'}</div>
|
||||
<div style="min-width:0;flex:1"><div class="topsong-title">${esc(s.title)}</div></div>
|
||||
<div class="topsong-plays">${s.playCount?s.playCount.toLocaleString():''}</div>
|
||||
<div class="song-actions" style="flex-shrink:0">
|
||||
<button class="icon-btn${s._starred?' starred':''}" title="Favorit"><svg width="12" height="12" viewBox="0 0 24 24" fill="${s._starred?'currentColor':'none'}" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></button>
|
||||
<button class="icon-btn song-menu-btn" title="Mehr"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg></button>
|
||||
</div>`;
|
||||
row.onclick = () => playQueue(allSongs, i);
|
||||
row.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, allSongs, i); });
|
||||
const btns = row.querySelectorAll('.icon-btn');
|
||||
btns[0].onclick = e => { e.stopPropagation(); toggleStar(s, btns[0]); };
|
||||
btns[1].onclick = e => { e.stopPropagation(); showCtxMenu(e, s, allSongs, i); };
|
||||
el.appendChild(row);
|
||||
el.appendChild(_makeTopsongRow(s, s.trackNumber || '', null, allSongs, i));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user