feat(labs): Фаза0 — эконом-режим FX + выбор симуляции из списка в редакторе
План улучшения симуляций — plans/simulations-improvement/README.md. - LabFX: reduced-motion/эконом-режим (prefers-reduced-motion + тумблер localStorage labfx-economy). Тряска отключается, частицы ×0.25 — доступность и экономия на слабых устройствах сразу для всех ~50 симуляций. Кнопка-тумблер в lab.html рядом со звуком. - lesson-editor: блок «Симуляция» — выпадающий список из /api/lab/sims (сгруппирован по предметам) вместо сырого ввода simId; неизвестный id не теряется, помечается «(не найдена)». Закрывает хрупкую вставку в урок. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2093,14 +2093,48 @@
|
||||
}
|
||||
|
||||
/* ── sim ── */
|
||||
// Каталог симуляций (кэш) — чтобы выбирать из списка, а не вписывать id руками.
|
||||
let _simCatalog = null, _simCatalogLoading = false;
|
||||
const SIM_SUBJ_RU = { phys:'Физика', chem:'Химия', bio:'Биология', math:'Математика', game:'Игры' };
|
||||
function loadSimCatalog(cb) {
|
||||
if (_simCatalog) { cb(_simCatalog); return; }
|
||||
if (_simCatalogLoading) { setTimeout(() => loadSimCatalog(cb), 200); return; }
|
||||
_simCatalogLoading = true;
|
||||
LS.api('/api/lab/sims')
|
||||
.then(r => { _simCatalog = (r && r.sims) || []; _simCatalogLoading = false; cb(_simCatalog); })
|
||||
.catch(() => { _simCatalog = []; _simCatalogLoading = false; cb(_simCatalog); });
|
||||
}
|
||||
function simOptionsHtml(selected) {
|
||||
const list = _simCatalog || [];
|
||||
const bySubj = {};
|
||||
for (const s of list) (bySubj[s.subject] = bySubj[s.subject] || []).push(s);
|
||||
let html = '<option value="">— выберите симуляцию —</option>';
|
||||
const known = new Set(list.map(s => s.id));
|
||||
if (selected && !known.has(selected)) {
|
||||
html += `<option value="${escAttr(selected)}" selected>${escAttr(selected)} (не найдена)</option>`;
|
||||
}
|
||||
for (const subj of Object.keys(bySubj)) {
|
||||
html += `<optgroup label="${escAttr(SIM_SUBJ_RU[subj] || subj)}">`;
|
||||
for (const s of bySubj[subj]) {
|
||||
html += `<option value="${escAttr(s.id)}"${s.id === selected ? ' selected' : ''}>${escAttr(s.title || s.id)}</option>`;
|
||||
}
|
||||
html += '</optgroup>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
function renderSimEditor(b) {
|
||||
const d = b.data;
|
||||
const bid = b._id;
|
||||
// подгрузить каталог и заполнить select уже после вставки в DOM
|
||||
loadSimCatalog(() => { const sel = document.getElementById('sim-select-' + bid); if (sel) sel.innerHTML = simOptionsHtml(d.simId || ''); });
|
||||
const initial = _simCatalog
|
||||
? simOptionsHtml(d.simId || '')
|
||||
: `<option value="${escAttr(d.simId || '')}" selected>${d.simId ? escAttr(d.simId) : 'Загрузка…'}</option>`;
|
||||
return `<div>
|
||||
<div class="block-field">
|
||||
<div class="block-row-label">ID симуляции (напр. coulomb, mag, waves)</div>
|
||||
<input class="block-input" type="text" placeholder="coulomb" value="${escAttr(d.simId||'')}"
|
||||
oninput="updateBlockData('${bid}','simId',this.value);updateSimPreview('${bid}',this.value);markDirty()" />
|
||||
<div class="block-row-label">Симуляция</div>
|
||||
<select class="block-input" id="sim-select-${bid}"
|
||||
onchange="updateBlockData('${bid}','simId',this.value);updateSimPreview('${bid}',this.value);markDirty()">${initial}</select>
|
||||
</div>
|
||||
<div class="block-field">
|
||||
<div class="block-row-label">Подпись</div>
|
||||
@@ -2116,8 +2150,10 @@
|
||||
function updateSimPreview(bid, simId) {
|
||||
const el = document.getElementById('sim-preview-' + bid);
|
||||
if (!el) return;
|
||||
let label = simId;
|
||||
if (_simCatalog) { const s = _simCatalog.find(x => x.id === simId); if (s) label = s.title || simId; }
|
||||
el.innerHTML = simId
|
||||
? `<div class="sim-preview">${LS.icon('atom',14)} Симуляция: <strong>${escAttr(simId)}</strong></div>`
|
||||
? `<div class="sim-preview">${LS.icon('atom',14)} Симуляция: <strong>${escAttr(label)}</strong></div>`
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user