'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 += ``;
});
});
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,
};
})();