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); document.getElementById('s-scrobble-mode').value = S.cfg.scrobbleMode || '15s'; _renderThemePicker(); document.getElementById('settings-modal').classList.add('open'); checkBackend(); _loadApkInfo(); } // Show the "download the Android APK" button only if the backend actually has an APK staged. async function _loadApkInfo() { const section = document.getElementById('s-apk-section'); const btn = document.getElementById('s-apk-btn'); const meta = document.getElementById('s-apk-meta'); if (!section || !btn) return; section.style.display = 'none'; const base = S.cfg.backendUrl; if (!base) return; try { const info = await fetch(`${base}/api/apk/info`).then(r => r.json()); if (!info?.available) return; btn.href = `${base}/api/apk`; const mb = info.size ? (info.size / 1048576).toFixed(1) + ' MB' : ''; const date = info.updatedAt ? new Date(info.updatedAt).toLocaleDateString('de-DE') : ''; if (meta) meta.textContent = [info.version && `v${info.version}`, mb, date && `Stand ${date}`].filter(Boolean).join(' · '); section.style.display = ''; } catch {} } function _renderThemePicker() { const el = document.getElementById('theme-picker'); if (!el) return; const current = getCurrentThemeId(); el.innerHTML = THEMES.map(t => `
${esc(t.name)}
`).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 = '
Keine Korrekturen vorhanden
'; return; } list.innerHTML = entries.map(([k, v]) => `
${esc(k)} → ${esc(v)}
` ).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'); S.cfg.scrobbleMode = document.getElementById('s-scrobble-mode').value || '15s'; 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; } }