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:
HoneyandClaude Sonnet 4.6 committed 2026-06-07 14:00:38 +02:00
1 parent 2a2e5ea495
commit 0663b1c923
6 files changed
+1125 -1137

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
import { S } from '../state.js';
import { esc, toast } from '../utils.js';
import { api, fetchRomajiIndex, invalidateCoverCache, getRomajiOverrides, setRomajiOverride, removeRomajiOverride } from '../api.js';
import { THEMES, applyTheme, getCurrentThemeId } from '../themes.js';
// Circular — safe (only used inside function bodies)
import { refreshDisplayMode, updateNpColorMode } from '../player/player.js';
import { startArtistSortFix } from './downloads.js';
// ─── SIDEBAR ───
export function openSidebar() {
document.getElementById('sidebar').classList.add('mobile-open');
document.getElementById('mobile-overlay').classList.add('active');
}
export function closeSidebar() {
document.getElementById('sidebar').classList.remove('mobile-open');
document.getElementById('mobile-overlay').classList.remove('active');
}
export function switchNavMode(mode) {
const lib = document.getElementById('bnav-library');
const disc = document.getElementById('bnav-discover');
if(!lib || !disc) return;
lib.style.display = mode==='library' ? 'flex' : 'none';
disc.style.display = mode==='discover' ? 'flex' : 'none';
document.getElementById('bnav-tab-library')?.classList.toggle('active', mode==='library');
document.getElementById('bnav-tab-discover')?.classList.toggle('active', mode==='discover');
}
// ─── MOBILE SEARCH (legacy stubs — header search is now always visible) ───
export function toggleMobileSearch() {
document.getElementById('header-search-input')?.focus();
}
export function closeMobileSearch() {
import('../views/views.js').then(m => m.closeHeaderSearch());
}
// ─── SETUP ───
export function switchSetupTab(t) {
document.querySelectorAll('.setup-tab').forEach((el,i) => el.classList.toggle('active', i===(t==='navidrome'?0:1)));
document.querySelectorAll('.setup-pane').forEach((el,i) => el.classList.toggle('active', i===(t==='navidrome'?0:1)));
}
// ─── SETTINGS ───
export function openSettings() {
import('../views/abs.js').then(m => m.absOpenSettings());
document.getElementById('s-url').value = S.cfg.url || '';
document.getElementById('s-user').value = S.cfg.user || '';
document.getElementById('s-pass').value = '';
document.getElementById('s-lastfm').value = S.cfg.lastfmKey || '';
document.getElementById('s-backend').value = S.cfg.backendUrl || '';
document.getElementById('s-show-playcount').classList.toggle('on', !!S.cfg.showPlayCount);
document.getElementById('s-show-romaji').classList.toggle('on', !!S.cfg.showRomaji);
document.getElementById('s-cover-color-bg').classList.toggle('on', !!S.cfg.coverColorBg);
_renderThemePicker();
document.getElementById('settings-modal').classList.add('open');
checkBackend();
}
function _renderThemePicker() {
const el = document.getElementById('theme-picker');
if (!el) return;
const current = getCurrentThemeId();
el.innerHTML = THEMES.map(t => `
<div class="theme-swatch${t.id === current ? ' active' : ''}" data-id="${t.id}" onclick="_pickTheme('${t.id}')" style="--sw-bg:${t.bg};--sw-accent:${t.accent}">
<div class="theme-swatch-dot"></div>
<span>${esc(t.name)}</span>
</div>`).join('');
}
export function _pickTheme(id) {
applyTheme(id);
_renderThemePicker();
}
function _renderRomajiOverrides() {
const list = document.getElementById('romaji-overrides-list');
if (!list) return;
const entries = Object.entries(getRomajiOverrides());
if (!entries.length) {
list.innerHTML = '<div style="color:var(--text2);font-size:13px;padding:4px 0">Keine Korrekturen vorhanden</div>';
return;
}
list.innerHTML = entries.map(([k, v]) =>
`<div style="display:flex;align-items:center;gap:6px;padding:7px 10px;background:var(--bg2);border-radius:8px">
<span style="flex:1;font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(k)}</span>
<span style="color:var(--text2);flex-shrink:0">→</span>
<span style="flex:1.2;font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(v)}</span>
<button onclick="removeRomajiOverrideUI('${esc(k)}')" style="background:none;border:none;color:var(--danger);cursor:pointer;font-size:18px;line-height:1;padding:0 2px;flex-shrink:0">×</button>
</div>`
).join('');
}
export async function addRomajiOverrideUI() {
const k = (document.getElementById('ro-kanji')?.value || '').trim();
const v = (document.getElementById('ro-romaji')?.value || '').trim();
if (!k || !v) { toast('⚠ Kanji und Romaji eingeben'); return; }
await setRomajiOverride(k, v);
document.getElementById('ro-kanji').value = '';
document.getElementById('ro-romaji').value = '';
_renderRomajiOverrides();
try { await startArtistSortFix(k, v); } catch (e) { toast('⚠ Fix-Artist-Sort: ' + e.message); }
}
export async function removeRomajiOverrideUI(kanji) {
await removeRomajiOverride(kanji);
_renderRomajiOverrides();
}
export function openRomajiOverridesModal() {
_renderRomajiOverrides();
document.getElementById('romaji-overrides-modal').classList.add('open');
}
export function closeRomajiOverridesModal() {
document.getElementById('romaji-overrides-modal').classList.remove('open');
}
export function closeSettings() { document.getElementById('settings-modal').classList.remove('open'); }
export async function confirmDisconnect() {
const { showConfirm } = await import('./modals.js');
const ok = await showConfirm('Abmelden', 'Wirklich abmelden? Alle lokalen Einstellungen werden gelöscht.', 'Abmelden');
if (ok) { closeSettings(); window.disconnect(); }
}
export function saveSettings() {
const pass = document.getElementById('s-pass').value;
S.cfg.url = document.getElementById('s-url').value.trim().replace(/\/$/,'');
S.cfg.user = document.getElementById('s-user').value.trim();
const credChanged = S.cfg.url !== document.getElementById('s-url').value.trim().replace(/\/$/,'') || !!pass;
if(pass) S.cfg.pass = pass;
if(credChanged) invalidateCoverCache();
S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim();
S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,'');
S.cfg.showPlayCount = document.getElementById('s-show-playcount').classList.contains('on');
const romajiChanged = S.cfg.showRomaji !== document.getElementById('s-show-romaji').classList.contains('on');
S.cfg.showRomaji = document.getElementById('s-show-romaji').classList.contains('on');
S.cfg.coverColorBg = document.getElementById('s-cover-color-bg').classList.contains('on');
updateNpColorMode();
localStorage.setItem('ns_cfg', JSON.stringify(S.cfg));
closeSettings(); toast('✅ Gespeichert');
if (romajiChanged) {
fetchRomajiIndex().then(() => refreshDisplayMode());
}
}
export async function checkBackend() {
const dot = document.getElementById('backend-status-dot');
const txt = document.getElementById('backend-status-text');
const url = document.getElementById('s-backend').value.trim() || S.cfg.backendUrl;
if(!url) { dot.className='status-dot'; txt.textContent='Keine URL konfiguriert'; return; }
dot.className='status-dot'; txt.textContent='Verbinde…';
try {
const r = await fetch(`${url}/api/health`, {signal: AbortSignal.timeout(4000)});
const d = await r.json();
dot.className='status-dot ok';
txt.textContent = `Verbunden · yt-dlp ${d.ytdlpVersion||'OK'} · ${d.musicPath||''}`;
} catch(e) { dot.className='status-dot err'; txt.textContent='Nicht erreichbar: '+e.message; }
}