refactor: split views.js and ui.js into focused modules
Extract 3 new files from oversized source files: - src/components.js (~86 lines): makeCard, makeAlbumCard, makeSongItem, makeGlobalTrackItem, mkSection — reusable DOM builders - src/spotify.js (~187 lines): loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll, and private render/artwork helpers - src/nowplaying.js (~255 lines): openNowPlaying, closeNowPlaying, syncNowPlaying, switchNpTab, full lyrics engine, renderNpQueue, npGoArtist/Album, openNpMenu/closeNpMenu views.js: 1121 → 864 lines ui.js: 706 → 462 lines Update imports in main.js, player.js, views.js accordingly. Build verified: 15 modules, no errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
4bdf3c7cbc
commit
16b7faee2b
7 files changed
+540
-508
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { S } from './state.js';
|
||||
import { esc, formatDur } from './utils.js';
|
||||
import { coverUrl } from './api.js';
|
||||
// Circular imports — safe (only used inside event handlers after module init)
|
||||
import { playQueue, dlSongLocal, toggleStar, playPreview } from './player.js';
|
||||
import { showCtxMenu, openDlModal } from './ui.js';
|
||||
import { navigate, quickPlayAlbum } from './views.js';
|
||||
|
||||
// ─── GENERIC CARD ───
|
||||
export function makeCard(title, sub, imgUrl, fallback='💿') {
|
||||
const c = document.createElement('div');
|
||||
c.className = 'card';
|
||||
c.innerHTML = `<div class="card-art">
|
||||
${imgUrl ? `<img src="${imgUrl}" loading="lazy" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>${fallback}</div>'">` : `<div class="no-art">${fallback}</div>`}
|
||||
<div class="play-overlay"><button class="play-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg></button></div>
|
||||
</div>
|
||||
<div class="card-info"><div class="card-title">${esc(title)}</div><div class="card-sub">${esc(sub||'')}</div></div>`;
|
||||
return c;
|
||||
}
|
||||
|
||||
export function makeAlbumCard(a) {
|
||||
const cv = coverUrl(a.coverArt, 200);
|
||||
const c = makeCard(a.name, a.artist||'', cv, '💿');
|
||||
c.querySelector('.play-btn').onclick = (e) => { e.stopPropagation(); quickPlayAlbum(a); };
|
||||
c.onclick = () => navigate('album-detail', a);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ─── SONG LIST ITEM (library) ───
|
||||
export function makeSongItem(s, idx, list) {
|
||||
const el = document.createElement('div');
|
||||
if(s.starred && !s._starred) s._starred = true;
|
||||
el.className = 'song-item' + (S.queue[S.qIdx]?.id === s.id ? ' playing' : '');
|
||||
const cv = coverUrl(s.coverArt, 60);
|
||||
el.innerHTML = `<div class="song-num">${idx+1}</div>
|
||||
<div class="song-thumb">${cv ? `<img src="${cv}" loading="lazy" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>♪</div>'">` : '<div class="no-art">♪</div>'}</div>
|
||||
<div class="song-info">
|
||||
<div class="song-title">${esc(s.title)}</div>
|
||||
<div class="song-artist">
|
||||
${s.artistId||s.artist ? `<span class="song-link" onclick="event.stopPropagation();s_goArtist(event,'${esc(s.artist||'')}','${s.artistId||''}')">${esc(s.artist||'')}</span>` : ''}
|
||||
${s.albumId||s.album ? `<span style="color:var(--text3)"> · </span><span class="song-link" onclick="event.stopPropagation();s_goAlbum(event,'${s.albumId||''}','${esc(s.album||'')}','${esc(s.artist||'')}','${s.artistId||''}')">${esc(s.album||'')}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="song-meta">
|
||||
<span class="song-dur">${formatDur(s.duration)}</span>
|
||||
<div class="song-actions">
|
||||
<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" title="Download"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></button>
|
||||
</div>
|
||||
</div>`;
|
||||
const btns = el.querySelectorAll('.icon-btn');
|
||||
btns[0].onclick = (e) => { e.stopPropagation(); toggleStar(s, btns[0]); };
|
||||
btns[1].onclick = (e) => { e.stopPropagation(); dlSongLocal(s); };
|
||||
el.onclick = () => playQueue(list, idx);
|
||||
el.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, list, idx); });
|
||||
return el;
|
||||
}
|
||||
|
||||
// ─── GLOBAL TRACK ITEM (iTunes / Spotify results) ───
|
||||
export function makeGlobalTrackItem(t, idx, list) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'gtrack-item';
|
||||
const art = t.artworkUrl60 || t.artworkUrl100 || '';
|
||||
el.innerHTML = `
|
||||
<div class="gtrack-art">${art ? `<img src="${art}" loading="lazy">` : '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:22px">🎵</div>'}</div>
|
||||
<div class="gtrack-info">
|
||||
<div class="gtrack-title">${esc(t.trackName||t.collectionName||'—')}${t.trackExplicitness==='explicit'?'<span class="explicit-badge">E</span>':''}</div>
|
||||
<div class="gtrack-sub">${esc(t.artistName||'—')} · ${esc(t.collectionName||'')} ${t.releaseDate?'· '+new Date(t.releaseDate).getFullYear():''}</div>
|
||||
</div>
|
||||
<div class="gtrack-actions">
|
||||
${t.previewUrl ? `<button class="icon-btn blue" title="30s Preview" onclick="event.stopPropagation();playPreview(${JSON.stringify(t).replace(/"/g,'"')})"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg></button>` : ''}
|
||||
<button class="icon-btn green" title="Auf Server laden" onclick="event.stopPropagation();openDlModal(${JSON.stringify(t).replace(/"/g,'"')})">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
</button>
|
||||
</div>`;
|
||||
el.onclick = () => { if(t.previewUrl) playPreview(t); };
|
||||
return el;
|
||||
}
|
||||
|
||||
// ─── SEARCH SECTION HEADER ───
|
||||
export function mkSection(title) {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'search-section';
|
||||
d.innerHTML = `<div class="search-section-title">${title}</div>`;
|
||||
return d;
|
||||
}
|
||||
Reference in new issue
Block a user