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,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; }
|
||||
}
|
||||
Reference in new issue
Block a user