Files

150 lines
7.2 KiB
JavaScript

'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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
/* ── Главная функция ── */
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);