refactor: split ui/ui.js into 5 focused modules
settings.js — settings modal, romaji overrides, sidebar, theme, nav mode modals.js — context menu, playlist-add modal, confirm dialog, edit-playlist modal metadata.js — song info/edit modal, MusicBrainz/iTunes autofill, saveMetadata, deleteSong notify.js — notification panel, bell badge, _addNotif, _notifNavigate downloads.js — download modal, Spotify download, jobs panel, startArtistSortFix ui.js — barrel re-export; all external imports unchanged player.js left unsplit: shared private state (_nativePlaying, _playGen, _queueGen, etc.) spans every section — splitting would require exporting internal state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a2e5ea495
commit
0663b1c923
6 files changed
+1125
-1137
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import { esc } from '../utils.js';
|
||||
// Circular — safe (only used inside function bodies)
|
||||
import { navigate } from '../views/views.js';
|
||||
import { api } from '../api.js';
|
||||
|
||||
const _NOTIF_KEY = 'ns_notifications';
|
||||
|
||||
function _loadNotifs() {
|
||||
try { return JSON.parse(localStorage.getItem(_NOTIF_KEY) || '[]'); } catch { return []; }
|
||||
}
|
||||
function _saveNotifs(n) { localStorage.setItem(_NOTIF_KEY, JSON.stringify(n.slice(0, 100))); }
|
||||
|
||||
export function _addNotif(notif) {
|
||||
const list = _loadNotifs();
|
||||
list.unshift(notif);
|
||||
_saveNotifs(list);
|
||||
_renderNotifBell();
|
||||
_renderNotifList();
|
||||
}
|
||||
|
||||
export function deleteNotif(id) {
|
||||
_saveNotifs(_loadNotifs().filter(n => n.id !== id));
|
||||
_renderNotifBell();
|
||||
_renderNotifList();
|
||||
}
|
||||
|
||||
export function clearNotifs() {
|
||||
_saveNotifs([]);
|
||||
_renderNotifBell();
|
||||
_renderNotifList();
|
||||
}
|
||||
|
||||
export function toggleNotifPanel() {
|
||||
const panel = document.getElementById('notif-panel');
|
||||
const overlay = document.getElementById('notif-overlay');
|
||||
if (!panel) return;
|
||||
const isOpen = panel.classList.contains('open');
|
||||
if (isOpen) {
|
||||
panel.classList.remove('open');
|
||||
overlay?.classList.remove('active');
|
||||
} else {
|
||||
_saveNotifs(_loadNotifs().map(n => ({...n, read: true})));
|
||||
_renderNotifList();
|
||||
_renderNotifBell();
|
||||
panel.classList.add('open');
|
||||
overlay?.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
export function initNotifBell() { _renderNotifBell(); }
|
||||
|
||||
function _renderNotifBell() {
|
||||
const unread = _loadNotifs().filter(n => !n.read).length;
|
||||
document.querySelectorAll('.notif-badge').forEach(b => {
|
||||
b.textContent = unread > 9 ? '9+' : unread;
|
||||
b.style.display = unread > 0 ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function _renderNotifList() {
|
||||
const list = document.getElementById('notif-list');
|
||||
if (!list) return;
|
||||
const notifs = _loadNotifs();
|
||||
if (!notifs.length) {
|
||||
list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = notifs.map(n => {
|
||||
const d = new Date(n.timestamp);
|
||||
const time = d.toLocaleDateString('de-DE', {day:'2-digit', month:'2-digit'}) + ' ' + d.toLocaleTimeString('de-DE', {hour:'2-digit', minute:'2-digit'});
|
||||
const icon = n.status === 'done' ? '✅' : '❌';
|
||||
const failedHtml = n.failed?.length
|
||||
? `<details class="notif-failed"><summary>${n.failed.length} Track${n.failed.length > 1 ? 's' : ''} fehlgeschlagen</summary><ul>${n.failed.map(f => `<li>${esc(f)}</li>`).join('')}</ul></details>`
|
||||
: '';
|
||||
const missingHtml = n.missing?.length
|
||||
? `<details class="notif-failed"><summary>${n.missing.length} Track${n.missing.length > 1 ? 's' : ''} nicht in Navidrome gefunden</summary><ul>${n.missing.map(f => `<li>${esc(f)}</li>`).join('')}</ul></details>`
|
||||
: '';
|
||||
const hasNav = n.status === 'done' && ((n.navType === 'playlist' && n.navId) || n.navArtist || n.navAlbum);
|
||||
const navLabel = n.navType === 'artist' ? 'Zum Künstler →'
|
||||
: (n.navType === 'track' || n.navType === 'album') ? 'Zum Album →'
|
||||
: 'Öffnen →';
|
||||
const navBtn = hasNav
|
||||
? `<button class="notif-nav-btn" onclick="_notifNavigate('${esc(n.id)}')">${navLabel}</button>`
|
||||
: '';
|
||||
return `<div class="notif-card${n.read ? '' : ' unread'}">
|
||||
<div class="notif-card-top">
|
||||
<span class="notif-icon">${icon}</span>
|
||||
<div class="notif-info">
|
||||
<div class="notif-title">${esc(n.title)}</div>
|
||||
<div class="notif-sub">${n.artist ? esc(n.artist) + ' · ' : ''}${time}</div>
|
||||
</div>
|
||||
<button class="notif-x" onclick="deleteNotif('${esc(n.id)}')">✕</button>
|
||||
</div>
|
||||
${n.message ? `<div class="notif-msg">${esc(n.message)}</div>` : ''}
|
||||
${failedHtml}
|
||||
${missingHtml}
|
||||
${navBtn}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
export async function _notifNavigate(notifId) {
|
||||
const notifs = _loadNotifs();
|
||||
const n = notifs.find(x => x.id === notifId);
|
||||
if (!n) return;
|
||||
const panel = document.getElementById('notif-panel');
|
||||
const overlay = document.getElementById('notif-overlay');
|
||||
panel?.classList.remove('open');
|
||||
overlay?.classList.remove('active');
|
||||
|
||||
if (n.navType === 'playlist' && n.navId) {
|
||||
navigate('playlist-detail', { id: n.navId, name: n.navName || n.title.replace(/^Playlist:\s*/, '') });
|
||||
return;
|
||||
}
|
||||
|
||||
const norm = s => s?.toLowerCase().trim() || '';
|
||||
|
||||
if (n.navType === 'track' || n.navType === 'album') {
|
||||
const albumQuery = n.navAlbum || n.navTitle;
|
||||
if (albumQuery) {
|
||||
try {
|
||||
const r = await api('search3', `&query=${encodeURIComponent(albumQuery)}&artistCount=0&albumCount=10&songCount=0`);
|
||||
const albums = r.searchResult3?.album || [];
|
||||
const match = albums.find(a => norm(a.name) === norm(albumQuery) && norm(a.artist) === norm(n.navArtist))
|
||||
|| albums.find(a => norm(a.name) === norm(albumQuery))
|
||||
|| albums[0];
|
||||
if (match) {
|
||||
navigate('album-detail', { id: match.id, name: match.name, artist: match.artist || '', artistId: match.artistId || '', coverArt: match.coverArt || '' });
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
import('../utils.js').then(m => m.toast('Album nicht gefunden — Library-Scan nötig?'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (n.navArtist) {
|
||||
try {
|
||||
const r = await api('search3', `&query=${encodeURIComponent(n.navArtist)}&artistCount=5&albumCount=0&songCount=0`);
|
||||
const artists = r.searchResult3?.artist || [];
|
||||
const match = artists.find(a => norm(a.name) === norm(n.navArtist)) || artists[0];
|
||||
if (match) { navigate('artist-detail', { id: match.id, name: match.name }); return; }
|
||||
} catch {}
|
||||
import('../utils.js').then(m => m.toast('Künstler nicht gefunden — Library-Scan nötig?'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user