/** * Settings page - form handlers for user preferences, filters, and weights. * Loaded on all pages via base.html so functions are available when * the settings section is dynamically loaded via HTMX. */ function saveSettings() { const data = { default_lang: document.getElementById('default_lang')?.value, theme: document.getElementById('theme')?.value, download_dir: document.getElementById('download_dir')?.value, }; const token = localStorage.getItem('auth_token'); if (!token) return; fetch('/api/settings', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => { if (r.ok) showToast('Preferences enregistrees', 'success'); }).catch(e => { showToast('Erreur: ' + e.message, 'error'); }); } function saveFilter(field, value) { const token = localStorage.getItem('auth_token'); if (!token) return; fetch('/api/settings', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ [field]: value }) }).then(r => { if (r.ok) showToast('Filtre mis a jour', 'success'); }).catch(e => { showToast('Erreur: ' + e.message, 'error'); }); } async function toggleCategory(field, value) { if (!value) { const otherField = field === 'anime_enabled' ? 'series_enabled' : 'anime_enabled'; const otherCheckbox = document.getElementById(otherField); if (otherCheckbox && !otherCheckbox.checked) { showToast('Au moins une categorie doit rester active', 'error'); document.getElementById(field).checked = true; return; } } const token = localStorage.getItem('auth_token'); if (!token) return; try { const r = await fetch('/api/settings', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ [field]: value }) }); if (!r.ok) { const err = await r.json().catch(() => ({})); showToast(err.detail || 'Erreur', 'error'); document.getElementById(field).checked = !value; } else { showToast('Categorie ' + (value ? 'activee' : 'desactivee'), 'success'); } } catch (e) { showToast('Erreur: ' + e.message, 'error'); document.getElementById(field).checked = !value; } } function onWeightModeChange(mode) { const autoInfo = document.getElementById('weight-auto-info'); const manualControls = document.getElementById('weight-manual-controls'); if (mode === 'auto') { if (autoInfo) autoInfo.style.display = 'block'; if (manualControls) manualControls.style.display = 'none'; loadAutoWeights(); } else { if (autoInfo) autoInfo.style.display = 'none'; if (manualControls) manualControls.style.display = 'block'; updateWeightPreview(); } const token = localStorage.getItem('auth_token'); if (!token) return; fetch('/api/settings', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ content_weight_mode: mode }) }); } async function loadAutoWeights() { const details = document.getElementById('weight-auto-details'); if (!details) return; const token = localStorage.getItem('auth_token'); if (!token) return; try { const r = await fetch('/api/settings/content-weight', { headers: { 'Authorization': 'Bearer ' + token } }); if (!r.ok) return; const data = await r.json(); const aw = data.anime_weight; const sw = data.series_weight; const ac = data.anime_count; const sc = data.series_count; const total = data.total || 0; if (total === 0) { details.innerHTML = 'Aucun telechargement detecte. Ratio par defaut : ' + aw + ' anime / ' + sw + ' serie.'; } else { const pctA = total > 0 ? Math.round(ac / total * 100) : 50; const pctS = total > 0 ? Math.round(sc / total * 100) : 50; details.innerHTML = `
${ac} anime${ac > 1 ? 's' : ''} (${pctA}%) — ${sc} serie${sc > 1 ? 's' : ''} (${pctS}%)
Ratio applique : ${aw} anime / ${sw} serie
`; } } catch (e) { details.innerHTML = 'Erreur de chargement'; } } function updateWeightPreview() { const awEl = document.getElementById('content_weight_anime_range'); const swEl = document.getElementById('content_weight_series_range'); const preview = document.getElementById('weight-preview'); if (!awEl || !swEl || !preview) return; const aw = parseInt(awEl.value) || 0; const sw = parseInt(swEl.value) || 0; const total = aw + sw; if (total === 0) { preview.innerHTML = 'Les deux poids ne peuvent pas etre a 0'; return; } const pctA = Math.round(aw / total * 100); const pctS = 100 - pctA; preview.innerHTML = `
${pctA}% animes  /  ${pctS}% series
`; } async function saveManualWeights() { const awEl = document.getElementById('content_weight_anime_range'); const swEl = document.getElementById('content_weight_series_range'); if (!awEl || !swEl) return; const aw = parseInt(awEl.value) || 0; const sw = parseInt(swEl.value) || 0; if (aw === 0 && sw === 0) { showToast('Les deux poids ne peuvent pas etre a 0', 'error'); return; } const token = localStorage.getItem('auth_token'); if (!token) return; try { const r = await fetch('/api/settings', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ content_weight_mode: 'manual', content_weight_anime: aw, content_weight_series: sw }) }); if (r.ok) showToast('Equilibre mis a jour', 'success'); } catch (e) { showToast('Erreur: ' + e.message, 'error'); } } function showToast(message, type) { const event = new CustomEvent('show-toast', { detail: { message, type } }); document.dispatchEvent(event); } // Initialize weight display when settings tab content is loaded via HTMX document.addEventListener('htmx:afterSettle', function(evt) { if (evt.detail.target) { const mode = evt.detail.target.querySelector('#content_weight_mode'); if (mode && mode.value === 'auto') { loadAutoWeights(); } else if (mode && mode.value === 'manual') { updateWeightPreview(); } } });