92030b462c
Replace ~3500L admin.js monolith with thin orchestrator (~700L) + 14 IIFE-wrapped per-section modules under /js/admin/sections/. Section modules expose AdminSections.<name>.init/reload (lazy init via switchTab/router) and re-expose onclick handlers via window.X for backward compat. Shared helpers (MODES/DIFFS, fmtDate, pctClass, renderMath, qTypeBadge, pagination) live in /js/admin/_shared.js exposed on window.AdminCtx. switchTab now dispatches to AdminSections via ROUTE_TO_SECTION map; non-extracted system tabs (topics/audit/errors/health/classroom/avatars) remain inline in admin.js. user-panel overlay markup untouched — Phase 6 will remove it.
119 lines
6.6 KiB
JavaScript
119 lines
6.6 KiB
JavaScript
'use strict';
|
|
/* admin → sims (simulations) section */
|
|
(function () {
|
|
'use strict';
|
|
let inited = false;
|
|
|
|
// Full list of available (non-null id) sims mirrored from /lab
|
|
const ADMIN_SIMS = [
|
|
{ id: 'graph', cat: 'Математика', title: 'График функции' },
|
|
{ id: 'graphtransform', cat: 'Математика', title: 'Трансформации графиков' },
|
|
{ id: 'geometry', cat: 'Математика', title: 'Планиметрия' },
|
|
{ id: 'triangle', cat: 'Математика', title: 'Геометрия треугольника' },
|
|
{ id: 'quadratic', cat: 'Математика', title: 'Корни квадратного уравнения' },
|
|
{ id: 'stereo', cat: 'Математика', title: 'Стереометрия 3D' },
|
|
{ id: 'probability', cat: 'Математика', title: 'Теория вероятностей' },
|
|
{ id: 'trigcircle', cat: 'Математика', title: 'Тригонометрическая окружность' },
|
|
{ id: 'normaldist', cat: 'Математика', title: 'Нормальное распределение' },
|
|
{ id: 'projectile', cat: 'Физика', title: 'Бросок тела' },
|
|
{ id: 'pendulum', cat: 'Физика', title: 'Маятник' },
|
|
{ id: 'collision', cat: 'Физика', title: 'Столкновение шаров' },
|
|
{ id: 'magnetic', cat: 'Физика', title: 'Магнитное поле токов' },
|
|
{ id: 'circuit', cat: 'Физика', title: 'Электрические цепи' },
|
|
{ id: 'coulomb', cat: 'Физика', title: 'Закон Кулона' },
|
|
{ id: 'hydrostatics', cat: 'Физика', title: 'Гидростатика' },
|
|
{ id: 'dynamics', cat: 'Физика', title: 'Динамика' },
|
|
{ id: 'thinlens', cat: 'Физика', title: 'Тонкая линза' },
|
|
{ id: 'refraction', cat: 'Физика', title: 'Преломление света' },
|
|
{ id: 'mirrors', cat: 'Физика', title: 'Зеркала' },
|
|
{ id: 'isoprocess', cat: 'Физика', title: 'Изопроцессы' },
|
|
{ id: 'waves', cat: 'Физика', title: 'Волны и звук' },
|
|
{ id: 'molphys', cat: 'Химия', title: 'Молекулярная физика' },
|
|
{ id: 'chemistry', cat: 'Химия', title: 'Химические реакции' },
|
|
{ id: 'equilibrium', cat: 'Химия', title: 'Химическое равновесие' },
|
|
{ id: 'electrolysis', cat: 'Химия', title: 'Электролиз' },
|
|
{ id: 'bohratom', cat: 'Химия', title: 'Атом Бора' },
|
|
{ id: 'orbitals', cat: 'Химия', title: 'Молекулярные орбитали' },
|
|
{ id: 'titration', cat: 'Химия', title: 'pH и кривая титрования' },
|
|
{ id: 'chemsandbox', cat: 'Химия', title: 'Химическая песочница' },
|
|
{ id: 'crystal', cat: 'Химия', title: 'Кристаллическая решётка' },
|
|
{ id: 'celldivision', cat: 'Биология', title: 'Деление клетки' },
|
|
{ id: 'photosynthesis', cat: 'Биология', title: 'Фотосинтез и дыхание' },
|
|
{ id: 'angrybirds', cat: 'Игры', title: 'Angry Birds Physics' },
|
|
];
|
|
|
|
let _simsSettings = { module_disabled: false, disabled_ids: [] };
|
|
|
|
async function load() {
|
|
try {
|
|
const data = await LS.api('/api/settings/sims');
|
|
_simsSettings = data;
|
|
_render();
|
|
} catch(e) { LS.toast('Ошибка загрузки настроек: ' + e.message, 'error'); }
|
|
}
|
|
|
|
function _render() {
|
|
// master toggle
|
|
const masterChk = document.getElementById('sims-master-chk');
|
|
if (masterChk) masterChk.checked = !_simsSettings.module_disabled;
|
|
|
|
// per-sim cards
|
|
const grid = document.getElementById('sims-grid');
|
|
const dis = new Set(_simsSettings.disabled_ids || []);
|
|
// group by category
|
|
const byCat = {};
|
|
ADMIN_SIMS.forEach(s => { (byCat[s.cat] = byCat[s.cat] || []).push(s); });
|
|
|
|
let html = '';
|
|
Object.entries(byCat).forEach(([cat, sims]) => {
|
|
html += `<div style="grid-column:1/-1;font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--text-3);margin-top:12px;margin-bottom:2px">${esc(cat)}</div>`;
|
|
sims.forEach(s => {
|
|
const enabled = !dis.has(s.id);
|
|
html += `<div class="perm-card${enabled ? ' enabled' : ''}" id="simcard-${s.id}">
|
|
<div class="perm-info">
|
|
<div class="perm-label">${esc(s.title)}</div>
|
|
<div class="perm-desc" style="font-size:11px;margin-top:2px;opacity:.7">${esc(s.id)}</div>
|
|
</div>
|
|
<label class="perm-toggle" title="${enabled ? 'Отключить' : 'Включить'}">
|
|
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="simToggleOne('${s.id}', this.checked)" />
|
|
<span class="perm-track"></span>
|
|
<span class="perm-thumb"></span>
|
|
</label>
|
|
</div>`;
|
|
});
|
|
});
|
|
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 }) });
|
|
_simsSettings.module_disabled = !checked;
|
|
LS.toast(checked ? 'Модуль симуляций включён' : 'Модуль симуляций отключён', checked ? 'success' : 'warning');
|
|
} catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); }
|
|
}
|
|
|
|
async function simToggleOne(simId, enabled) {
|
|
const dis = new Set(_simsSettings.disabled_ids || []);
|
|
if (enabled) dis.delete(simId); else dis.add(simId);
|
|
const disabled_ids = [...dis];
|
|
try {
|
|
await LS.api('/api/settings/sims', { method: 'PUT', body: JSON.stringify({ disabled_ids }) });
|
|
_simsSettings.disabled_ids = disabled_ids;
|
|
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'); }
|
|
}
|
|
|
|
window.simsMasterToggle = simsMasterToggle;
|
|
window.simToggleOne = simToggleOne;
|
|
|
|
window.AdminSections = window.AdminSections || {};
|
|
window.AdminSections.sims = {
|
|
init: async () => { if (inited) return; inited = true; await load(); },
|
|
reload: load,
|
|
};
|
|
})();
|