535005b3d5
- settings.js: replace broken CSS vars with getThemeColor() helper - base.html: add bg-primary text-primary-content active state to drawer - All templates: btn-small -> btn-sm (DaisyUI standard) - Delete orphan templates/components/header.html - auth-utils.js: fix .show class -> use hidden (Tailwind) - login.html: remove redundant auth-* classes, keep DaisyUI only - auth-ui.js: update form selector for cleanup - watchlist.html: fix nav active class styling - 4 JS files (series-search, tabs, recommendations, anime-details): - Replace all old CSS classes with DaisyUI/Tailwind - Remove hardcoded colors, use theme-aware classes - loading-spinner -> DaisyUI loading component - no-results/search-results -> Tailwind utility layout - All badges -> DaisyUI badge variants
233 lines
9.1 KiB
JavaScript
233 lines
9.1 KiB
JavaScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* Read a DaisyUI theme color from computed CSS custom properties.
|
|
* Falls back to sensible defaults if the theme variable is not found.
|
|
*/
|
|
function getThemeColor(varName, fallback) {
|
|
const style = getComputedStyle(document.documentElement);
|
|
const value = style.getPropertyValue(varName).trim();
|
|
return value || fallback;
|
|
}
|
|
|
|
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;
|
|
|
|
const primary = getThemeColor('--color-primary', '#6366f1');
|
|
const secondary = getThemeColor('--color-secondary', '#a3a3a3');
|
|
const accent = getThemeColor('--color-accent', '#38bdf8');
|
|
const error = getThemeColor('--color-error', '#f43f5e');
|
|
const muted = getThemeColor('--color-base-content', '#999');
|
|
|
|
if (total === 0) {
|
|
details.innerHTML = `<span style="color: ${muted}; opacity: 0.6;">Aucun telechargement detecte. Ratio par defaut : ${aw} anime / ${sw} serie.</span>`;
|
|
} else {
|
|
const pctA = total > 0 ? Math.round(ac / total * 100) : 50;
|
|
const pctS = total > 0 ? Math.round(sc / total * 100) : 50;
|
|
details.innerHTML = `
|
|
<div style="margin-bottom: 8px;">
|
|
<strong>${ac}</strong> anime${ac > 1 ? 's' : ''} (${pctA}%) — <strong>${sc}</strong> serie${sc > 1 ? 's' : ''} (${pctS}%)
|
|
</div>
|
|
<div class="progress w-full" style="height: 8px;">
|
|
<div class="progress-bar" style="width: 100%; display: flex; border-radius: 4px; overflow: hidden;">
|
|
<div style="width: ${pctA}%; background: ${primary};"></div>
|
|
<div style="width: ${pctS}%; background: ${accent};"></div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top: 8px; font-size: 12px;">
|
|
Ratio applique : <strong style="color: ${primary};">${aw}</strong> anime / <strong style="color: ${accent};">${sw}</strong> serie
|
|
</div>
|
|
`;
|
|
}
|
|
} catch (e) {
|
|
const error = getThemeColor('--color-error', '#f43f5e');
|
|
details.innerHTML = `<span style="color: ${error};">Erreur de chargement</span>`;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
const primary = getThemeColor('--color-primary', '#6366f1');
|
|
const secondary = getThemeColor('--color-secondary', '#a3a3a3');
|
|
const accent = getThemeColor('--color-accent', '#38bdf8');
|
|
const error = getThemeColor('--color-error', '#f43f5e');
|
|
|
|
if (total === 0) {
|
|
preview.innerHTML = `<span style="color: ${error};">Les deux poids ne peuvent pas etre a 0</span>`;
|
|
return;
|
|
}
|
|
|
|
const pctA = Math.round(aw / total * 100);
|
|
const pctS = 100 - pctA;
|
|
|
|
preview.innerHTML = `
|
|
<div style="margin-bottom: 6px;">
|
|
<span style="color: ${primary}; font-weight: 700;">${pctA}%</span> animes /
|
|
<span style="color: ${accent}; font-weight: 700;">${pctS}%</span> series
|
|
</div>
|
|
<div class="progress w-full" style="height: 8px;">
|
|
<div class="progress-bar" style="width: 100%; display: flex; border-radius: 4px; overflow: hidden;">
|
|
<div style="width: ${pctA}%; background: ${primary}; transition: width 0.2s;"></div>
|
|
<div style="width: ${pctS}%; background: ${accent}; transition: width 0.2s;"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
});
|