diff --git a/frontend/js/labs/lab-glue.js b/frontend/js/labs/lab-glue.js index d472611..391dcd1 100644 --- a/frontend/js/labs/lab-glue.js +++ b/frontend/js/labs/lab-glue.js @@ -330,8 +330,39 @@ const SIMS = [ // Map simId → { getState, applyState } registered by openSim handlers const _simStateRegistry = {}; + /* ── Локальный персист параметров симуляции (Фаза 2) ────────────────── + Поверх того же getState/applyState: в обычном (не embed) режиме сохраняем + состояние активной симуляции в localStorage и восстанавливаем при открытии. + В embed/онлайн-уроке состоянием управляет учитель — персист отключён. */ + const _LAB_STATE_KEY = 'lab-sim-state-v1'; + function _loadSavedStates() { try { return JSON.parse(localStorage.getItem(_LAB_STATE_KEY) || '{}') || {}; } catch (e) { return {}; } } + function _saveSavedStates(m) { try { localStorage.setItem(_LAB_STATE_KEY, JSON.stringify(m)); } catch (e) {} } + let _persistSimId = null, _persistInterval = null, _lastPersisted = null; + function _stopPersist() { if (_persistInterval) { clearInterval(_persistInterval); _persistInterval = null; } _persistSimId = null; _lastPersisted = null; } + function _persistNow() { + if (!_persistSimId) return; + const reg = _simStateRegistry[_persistSimId]; + if (!reg || !reg.getState) return; + try { + const s = reg.getState(); + if (s == null) return; + const json = JSON.stringify(s); + if (json === _lastPersisted || json.length > 8000) return; + _lastPersisted = json; + const m = _loadSavedStates(); m[_persistSimId] = s; _saveSavedStates(m); + } catch (e) {} + } + function _startPersist(simId) { _stopPersist(); _persistSimId = simId; _persistInterval = setInterval(_persistNow, 2000); } + window.addEventListener('pagehide', _persistNow); + function _registerSimState(simId, getState, applyState) { _simStateRegistry[simId] = { getState, applyState }; + if (_embedMode) return; // в embed состоянием управляет учитель + // восстановить сохранённые параметры (после инициализации тела) + запустить персист + setTimeout(function () { + try { const saved = _loadSavedStates()[simId]; if (saved != null && applyState) applyState(saved); } catch (e) {} + _startPersist(simId); + }, 0); } let _lastEmittedState = null; diff --git a/plans/simulations-improvement/README.md b/plans/simulations-improvement/README.md index bce4fa0..c35e1ad 100644 --- a/plans/simulations-improvement/README.md +++ b/plans/simulations-improvement/README.md @@ -70,7 +70,7 @@ ## Прогресс - [x] Фаза 0 (фундамент заложен) — эконом-режим/reduced-motion (LabFX, тумблер), выбор симуляции из списка в редакторе урока, удалён мёртвый `SimUtil`, добавлены `LabPalette` (_palette.js) и `SimBase` (_simbase.js) как опциональные основания. **Адаптация симуляций к SimBase/LabPalette и удаление «дробовика» `_pauseAllSims/closeSim` — постепенно, по мере правок каждой симуляции (требует поштучной проверки, нет фронт-тестов).** - [~] Фаза 1 — сделано: фреймворк `LabTasks` (_tasks.js) + интеграция в теорию; задания на 17 симуляций. Осталось: XP за задания, deep-link на §, наполнение остальных. -- [~] Фаза 2 — сделано: «Сохранить кадр в Мои материалы» + «Скачать PNG» в топбаре лаборатории (захват активного canvas, переиспользует MaterialSave.image). Осталось: сохранение/возобновление параметров симуляции, измерительные инструменты. (3D/WebGL-кадр может быть пустым без preserveDrawingBuffer — доработать.) +- [~] Фаза 2 — сделано: «Сохранить кадр в Мои материалы» + «Скачать PNG»; сохранение/возобновление параметров симуляции (localStorage поверх getState/applyState, не в embed). Осталось: измерительные инструменты (линейка/транспортир). (3D/WebGL-кадр пустой без preserveDrawingBuffer — доработать.) - [ ] Фаза 3 - [ ] Фаза 4 - [ ] Фаза 5