feat(sim-builder): фаза 0 — рантайм SimEngine + безопасный движок выражений + адаптер LabRegistry
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
registerSpecSim — адаптер: JSON-спека -> манифест LabRegistry (Фаза 0).
|
||||
|
||||
Строит манифест и регистрирует его в window.LabRegistry, чтобы спек-симуляция
|
||||
открывалась тем же путём, что и ~40 рукописных симуляций (через openSim ->
|
||||
реестр). Никаких правок чужих манифестов — только register().
|
||||
|
||||
Контракт LabRegistry-манифеста (см. _registry.js): { id, cat, title, desc,
|
||||
preview, theory?, open(ctx), stop(), destroy() }.
|
||||
|
||||
Особенности интеграции с /lab:
|
||||
- openSim() прячет все ИЗВЕСТНЫЕ ему тела (ALL_SIM_BODIES) и зовёт _pauseAllSims()
|
||||
-> LabRegistry.stopActive() (наш stop спрячет наш хост). Спек-хосты openSim не
|
||||
знает, поэтому при switch именно stop() прошлой активной спек-симуляции прячет её.
|
||||
- Каждая спек-симуляция получает собственный хост-div внутри #lab-sim, создаётся
|
||||
лениво при первом open и переиспользуется.
|
||||
- Заголовок топбара ставим как делают рукописные _openX (sim-topbar-title).
|
||||
════════════════════════════════════════════════════════════════════════ */
|
||||
(function (global) {
|
||||
|
||||
var HOST_PREFIX = 'sim-spec-host-';
|
||||
|
||||
// Найти контейнер для тел симуляций (#lab-sim) — туда вставляем хост.
|
||||
function simContainer() {
|
||||
return document.getElementById('lab-sim') || document.body;
|
||||
}
|
||||
|
||||
// Лениво создать/получить хост-элемент для данного id.
|
||||
function ensureHost(id) {
|
||||
var hid = HOST_PREFIX + id;
|
||||
var el = document.getElementById(hid);
|
||||
if (el) return el;
|
||||
el = document.createElement('div');
|
||||
el.id = hid;
|
||||
el.className = 'sim-spec-host';
|
||||
// занимает то же место, что .sim-body-wrap у рукописных симуляций
|
||||
el.style.cssText = 'flex:1;min-height:0;display:none';
|
||||
var cont = simContainer();
|
||||
// вставить перед панелью теории, если она есть, иначе в конец
|
||||
var theory = document.getElementById('theory-panel');
|
||||
if (theory && theory.parentNode === cont) cont.insertBefore(el, theory);
|
||||
else cont.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
// Спрятать все спек-хосты (на случай переключения с одной спеки на другую,
|
||||
// когда openSim не знает наших хостов).
|
||||
function hideAllSpecHosts() {
|
||||
var nodes = document.querySelectorAll('.sim-spec-host');
|
||||
for (var i = 0; i < nodes.length; i++) nodes[i].style.display = 'none';
|
||||
}
|
||||
|
||||
function setTitle(spec) {
|
||||
var t = document.getElementById('sim-topbar-title');
|
||||
if (t) t.textContent = (spec.meta && spec.meta.title) || spec.title || 'Симуляция';
|
||||
}
|
||||
|
||||
/* preview из спеки: если задана строка/функция — использовать; иначе
|
||||
сгенерировать простой SVG-плейсхолдер с названием. */
|
||||
function buildPreview(spec) {
|
||||
if (spec.preview) return spec.preview;
|
||||
var title = (spec.meta && spec.meta.title) || spec.title || 'Симуляция';
|
||||
var bg = (spec.viewport && spec.viewport.bg) || '#0D0D1A';
|
||||
return function () {
|
||||
return '<svg class="sim-preview" viewBox="0 0 300 140" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">' +
|
||||
'<rect width="300" height="140" fill="' + _esc(bg) + '"/>' +
|
||||
'<line x1="20" y1="120" x2="280" y2="120" stroke="rgba(255,255,255,0.25)" stroke-width="1.5"/>' +
|
||||
'<line x1="30" y1="20" x2="30" y2="130" stroke="rgba(255,255,255,0.25)" stroke-width="1.5"/>' +
|
||||
'<path d="M30 120 Q120 30 270 110" fill="none" stroke="#06D6E0" stroke-width="2.5"/>' +
|
||||
'<circle cx="150" cy="64" r="5" fill="#9B5DE5"/>' +
|
||||
'<text x="150" y="135" text-anchor="middle" fill="rgba(255,255,255,0.5)" font-size="10" font-family="Manrope,sans-serif">' +
|
||||
_esc(title) + '</text></svg>';
|
||||
};
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/* ── Главная функция ── */
|
||||
function registerSpecSim(spec) {
|
||||
if (!global.LabRegistry) {
|
||||
if (global.console) console.warn('[registerSpecSim] LabRegistry недоступен');
|
||||
return null;
|
||||
}
|
||||
if (!spec || !spec.id) {
|
||||
if (global.console) console.warn('[registerSpecSim] спека без id');
|
||||
return null;
|
||||
}
|
||||
var id = spec.id;
|
||||
var _inst = null; // активный SimEngine-инстанс этой симуляции
|
||||
|
||||
var manifest = {
|
||||
id: id,
|
||||
cat: spec.cat || 'phys',
|
||||
title: (spec.meta && spec.meta.title) || spec.title || id,
|
||||
desc: (spec.meta && spec.meta.desc) || spec.desc || '',
|
||||
preview: buildPreview(spec),
|
||||
theory: spec.theory || null,
|
||||
subject: spec.subject,
|
||||
grade: spec.grade,
|
||||
topics: spec.topics,
|
||||
_spec: spec, // храним исходную спеку (билдеру/доске пригодится)
|
||||
|
||||
open: function (ctx) {
|
||||
hideAllSpecHosts();
|
||||
var host = ensureHost(id);
|
||||
host.style.display = 'flex';
|
||||
setTitle(spec);
|
||||
// пере-смонтировать заново на каждый open (чистое состояние)
|
||||
if (_inst) { try { _inst.destroy(); } catch (e) {} _inst = null; }
|
||||
host.innerHTML = '';
|
||||
if (global.SimEngine) {
|
||||
_inst = global.SimEngine.mount(host, spec);
|
||||
manifest._instance = _inst;
|
||||
} else if (global.console) {
|
||||
console.warn('[registerSpecSim] SimEngine недоступен для', id);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
if (_inst) { try { _inst.pause(); } catch (e) {} }
|
||||
var host = document.getElementById(HOST_PREFIX + id);
|
||||
if (host) host.style.display = 'none';
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
if (_inst) { try { _inst.destroy(); } catch (e) {} _inst = null; }
|
||||
manifest._instance = null;
|
||||
var host = document.getElementById(HOST_PREFIX + id);
|
||||
if (host) host.style.display = 'none';
|
||||
},
|
||||
|
||||
// доступ к живому инстансу (доска онлайн-урока, билдер — Фазы 4/7)
|
||||
instance: function () { return _inst; }
|
||||
};
|
||||
|
||||
return global.LabRegistry.register(manifest);
|
||||
}
|
||||
|
||||
global.registerSpecSim = registerSpecSim;
|
||||
global.SimAdapter = {
|
||||
register: registerSpecSim,
|
||||
ensureHost: ensureHost,
|
||||
hideAllSpecHosts: hideAllSpecHosts
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
Reference in New Issue
Block a user