refactor: distribute lab-init.js into 34 engine files
lab-init.js: 4098 -> 543 lines (infrastructure + THEORY only) Each sim's _open*() + UI helpers moved to its engine file: graph.js, projectile.js, collision.js, magnetic.js, triangle.js, geometry.js, trigcircle.js, gas.js (molphys), coulomb.js, circuit.js, reactions.js (chemistry), newton.js (dynamics), chemsandbox.js, celldivision.js, photosynthesis.js, angrybirds.js, quadratic.js, normaldist.js, graphtransform.js, pendulum.js, equilibrium.js, thinlens.js, mirror.js, isoprocess.js, titration.js, refraction.js, probability.js, bohratom.js, electrolysis.js, waves.js, crystal.js, orbitals.js, stereo.js, hydrostatics.js All 34 engine files syntax-checked OK.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
geometry.js — Интерактивная планиметрия для LearnSpace
|
||||
Phase 1: точки, отрезки, прямые, лучи, окружности, многоугольники
|
||||
Phase 2: инструменты построения (середина, биссектрисы, параллельные,
|
||||
@@ -2581,3 +2581,150 @@ class GeoSim {
|
||||
}, 'image/png');
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── lab UI init ─────────────────────────────────── */
|
||||
function geoSetTool(name, btnEl) {
|
||||
if (!geomSim) return;
|
||||
geomSim.setTool(name);
|
||||
document.querySelectorAll('.geo-tool-btn').forEach(b => b.classList.remove('active'));
|
||||
if (btnEl) btnEl.classList.add('active');
|
||||
_geoShowHint(name);
|
||||
}
|
||||
|
||||
const _GEO_PHASE_HINTS = {
|
||||
parallel_2: 'Теперь кликни на точку — через неё проведём прямую',
|
||||
perpendicular_2: 'Теперь кликни на точку — через неё проведём перпендикуляр',
|
||||
intersect_2: 'Теперь кликни на вторую прямую',
|
||||
foot_2: 'Теперь кликни на точку — найдём основание перпендикуляра',
|
||||
reflect_2: 'Теперь кликни на точку — получишь её симметричное отражение',
|
||||
tangent_2: 'Теперь кликни на внешнюю точку — получишь две касательные',
|
||||
translate_2: 'Теперь кликни конец вектора B',
|
||||
translate_3: 'Теперь кликни точку P — она будет перенесена',
|
||||
midline_2: 'Кликни вершину B (конец первой стороны)',
|
||||
midline_3: 'Кликни вершину C (конец второй стороны) — построим среднюю линию',
|
||||
parallelogram_2: 'Кликни вершину B (смежная с A)',
|
||||
parallelogram_3: 'Кликни вершину C — построим параллелограмм ABCD',
|
||||
scale_2: 'Кликни точку P — построим P\' = O + k·(P − O)',
|
||||
thales_2: 'Кликни точку A (на первом луче)',
|
||||
thales_3: 'Кликни точку B (на втором луче) — построим A\'B\' ∥ AB',
|
||||
};
|
||||
|
||||
function _geoShowHint(name, phase) {
|
||||
const hint = document.getElementById('geo-hint');
|
||||
if (!hint) return;
|
||||
if (phase && phase > 1) {
|
||||
hint.textContent = _GEO_PHASE_HINTS[`${name}_${phase}`] || _GEO_HINTS[name] || '';
|
||||
} else {
|
||||
hint.textContent = _GEO_HINTS[name] || '';
|
||||
}
|
||||
}
|
||||
|
||||
function geoNgonN(delta) {
|
||||
if (!geomSim) return;
|
||||
geomSim.setNgonSides(geomSim._ngonSides + delta);
|
||||
const el = document.getElementById('geo-ngon-n');
|
||||
if (el) el.textContent = geomSim._ngonSides;
|
||||
}
|
||||
|
||||
function geoScaleK(delta) {
|
||||
if (!geomSim) return;
|
||||
const k = Math.round((geomSim._scaleK + delta) * 10) / 10;
|
||||
if (k < 0.1) return;
|
||||
geomSim.setScaleK(k);
|
||||
const el = document.getElementById('geo-scale-k');
|
||||
if (el) el.textContent = k;
|
||||
}
|
||||
|
||||
function geoToggle(prop, rowEl) {
|
||||
if (!geomSim) return;
|
||||
geomSim[prop] = !geomSim[prop];
|
||||
const tog = rowEl.querySelector('.geo-toggle');
|
||||
if (tog) tog.classList.toggle('on', geomSim[prop]);
|
||||
geomSim.render();
|
||||
}
|
||||
|
||||
function _geoUpdateStats() {
|
||||
if (!geomSim) return;
|
||||
const s = geomSim.getStats();
|
||||
document.getElementById('geo-st-pts').textContent = s.pts;
|
||||
document.getElementById('geo-st-segs').textContent = s.segs;
|
||||
document.getElementById('geo-st-circs').textContent = s.circs;
|
||||
document.getElementById('geo-st-polys').textContent = s.polys;
|
||||
const cEl = document.getElementById('geo-st-constr');
|
||||
if (cEl) cEl.textContent = s.constructions || 0;
|
||||
}
|
||||
|
||||
/* Диалог подтверждения удаления объекта с зависимыми */
|
||||
let _geoDelSoftFn = null, _geoDelHardFn = null;
|
||||
function _geoShowDeleteConfirm(obj, deps, softFn, hardFn) {
|
||||
const panel = document.getElementById('geo-del-confirm');
|
||||
const msg = document.getElementById('geo-del-msg');
|
||||
if (!panel || !msg) { hardFn(); return; }
|
||||
const names = { point:'точка', segment:'отрезок', line:'прямая', ray:'луч',
|
||||
circle:'окружность', polygon:'многоугольник', derived_line:'построение' };
|
||||
const n = names[obj.type] || 'объект';
|
||||
msg.textContent = `Удалить ${n}? Зависимых: ${deps.length}.`;
|
||||
_geoDelSoftFn = softFn;
|
||||
_geoDelHardFn = hardFn;
|
||||
panel.classList.add('visible');
|
||||
}
|
||||
function _geoHideDeleteConfirm() {
|
||||
document.getElementById('geo-del-confirm')?.classList.remove('visible');
|
||||
_geoDelSoftFn = _geoDelHardFn = null;
|
||||
}
|
||||
// Кнопки диалога — подключаем после DOM ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('geo-del-soft')?.addEventListener('click', () => {
|
||||
_geoDelSoftFn?.(); _geoHideDeleteConfirm(); _geoUpdateStats();
|
||||
});
|
||||
document.getElementById('geo-del-hard')?.addEventListener('click', () => {
|
||||
_geoDelHardFn?.(); _geoHideDeleteConfirm(); _geoUpdateStats();
|
||||
});
|
||||
document.getElementById('geo-del-cancel')?.addEventListener('click', _geoHideDeleteConfirm);
|
||||
});
|
||||
|
||||
function _openGeometry() {
|
||||
document.getElementById('sim-topbar-title').textContent = 'Планиметрия';
|
||||
_simShow('sim-geometry');
|
||||
_simShow('ctrl-geometry');
|
||||
|
||||
_registerSimState(
|
||||
'geometry',
|
||||
() => geomSim?.exportState(),
|
||||
st => { if (geomSim && st) { geomSim.importState(st); _geoUpdateStats(); } }
|
||||
);
|
||||
if (_embedMode) _startStateEmit('geometry');
|
||||
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
const canvas = document.getElementById('geo-canvas');
|
||||
if (!geomSim) {
|
||||
geomSim = new GeoSim(canvas);
|
||||
geomSim.onUpdate = _geoUpdateStats;
|
||||
geomSim.onHintChange = (tool, phase) => _geoShowHint(tool, phase);
|
||||
geomSim.onDeleteRequest = _geoShowDeleteConfirm;
|
||||
|
||||
// keyboard shortcuts
|
||||
canvas.setAttribute('tabindex', '0');
|
||||
canvas.addEventListener('keydown', e => {
|
||||
if (!geomSim) return;
|
||||
if (e.key === 'Escape') { geoSetTool('select', document.getElementById('geo-btn-select')); }
|
||||
if ((e.ctrlKey||e.metaKey) && e.key === 'z') { e.preventDefault(); geomSim.undo(); _geoUpdateStats(); }
|
||||
if ((e.ctrlKey||e.metaKey) && (e.key === 'y' || (e.shiftKey && e.key==='z'))) { e.preventDefault(); geomSim.redo(); _geoUpdateStats(); }
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') { geomSim.deleteSelected(); _geoUpdateStats(); }
|
||||
if (e.key === 'Enter') { geomSim._finishPolygon?.(); _geoUpdateStats(); }
|
||||
});
|
||||
}
|
||||
geomSim.fit();
|
||||
geomSim.render();
|
||||
_geoUpdateStats();
|
||||
|
||||
// sync toggle UI to current state
|
||||
['showGrid','showAxes','showLabels','showLengths','showAngles'].forEach(p => {
|
||||
const el = document.getElementById('geo-tog-' + p);
|
||||
if (el) el.classList.toggle('on', !!geomSim[p]);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/* ── trig circle ── */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user