'use strict'; /* admin → sims (simulations) section — контент-движок, Фаза 4. * * Каталог берётся из БД (/api/lab/sims), а НЕ из захардкоженного списка. * Управление: вкл/выкл (зеркалится в legacy sim_disabled_ids), «рекомендуемая», * теги. Мастер-тумблер модуля — по-прежнему /api/settings/sims. */ (function () { 'use strict'; let inited = false; const CAT_LABEL = { math: 'Математика', phys: 'Физика', chem: 'Химия', bio: 'Биология', game: 'Игры' }; const CAT_ORDER = ['math', 'phys', 'chem', 'bio', 'game']; let _moduleDisabled = false; let _sims = []; // [{id,cat,title,enabled,featured,tags,subject,grade,sort}] function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } async function load() { try { const data = await LS.api('/api/lab/sims'); _moduleDisabled = !!data.module_disabled; _sims = Array.isArray(data.sims) ? data.sims : []; _render(); } catch (e) { LS.toast('Ошибка загрузки симуляций: ' + e.message, 'error'); } } function _render() { const masterChk = document.getElementById('sims-master-chk'); if (masterChk) masterChk.checked = !_moduleDisabled; const grid = document.getElementById('sims-grid'); if (!grid) return; // group by category, preserving catalogue sort within group const byCat = {}; _sims.forEach(s => { (byCat[s.cat] = byCat[s.cat] || []).push(s); }); const cats = CAT_ORDER.filter(c => byCat[c]).concat( Object.keys(byCat).filter(c => !CAT_ORDER.includes(c))); let html = ''; cats.forEach(cat => { html += `
${esc(CAT_LABEL[cat] || cat)}
`; byCat[cat].forEach(s => { const tags = (s.tags || []).map(t => esc(t)).join(', '); html += `
${esc(s.title)}
${esc(s.id)}${tags ? ' · ' + tags : ''}
`; }); }); grid.innerHTML = html; if (window.lucide) lucide.createIcons(); } async function simsMasterToggle(checked) { try { await LS.api('/api/settings/sims', { method: 'PUT', body: JSON.stringify({ module_disabled: !checked }) }); _moduleDisabled = !checked; LS.toast(checked ? 'Модуль симуляций включён' : 'Модуль симуляций отключён', checked ? 'success' : 'warning'); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); } } async function simToggleOne(simId, enabled) { try { await LS.api('/api/lab/sims/' + encodeURIComponent(simId), { method: 'PATCH', body: JSON.stringify({ enabled }) }); const s = _sims.find(x => x.id === simId); if (s) s.enabled = enabled; const card = document.getElementById('simcard-' + simId); if (card) card.classList.toggle('enabled', enabled); LS.toast(enabled ? `«${simId}» включена` : `«${simId}» отключена`, enabled ? 'success' : 'warning'); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); } } async function simToggleFeatured(simId, featured) { try { await LS.api('/api/lab/sims/' + encodeURIComponent(simId), { method: 'PATCH', body: JSON.stringify({ featured }) }); const s = _sims.find(x => x.id === simId); if (s) s.featured = featured; _render(); LS.toast(featured ? `«${simId}» в рекомендуемых` : `«${simId}» убрана из рекомендуемых`, 'success'); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); } } window.simsMasterToggle = simsMasterToggle; window.simToggleOne = simToggleOne; window.simToggleFeatured = simToggleFeatured; window.AdminSections = window.AdminSections || {}; window.AdminSections.sims = { init: async () => { if (inited) return; inited = true; await load(); }, reload: load, }; })();