feat(labs): Фаза2 — сохранить кадр симуляции в «Мои материалы» + скачать PNG
Кнопки в топбаре лаборатории: снимок активного canvas → MaterialSave.image (аплоад + kind:image в /api/materials) и «Скачать PNG». Захват — крупнейший видимый canvas сцены. material-save.js подключён в lab.html. (3D/WebGL-кадр может быть пустым без preserveDrawingBuffer — доработать позже.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,16 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
|
||||
</button>
|
||||
|
||||
<!-- save screenshot to «Мои материалы» -->
|
||||
<button class="zoom-btn" id="lab-save-btn" onclick="labSaveToMaterials(this)" title="Сохранить кадр в «Мои материалы»">
|
||||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
</button>
|
||||
|
||||
<!-- download PNG -->
|
||||
<button class="zoom-btn" id="lab-png-btn" onclick="labDownloadPng()" title="Скачать кадр (PNG)">
|
||||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
</button>
|
||||
|
||||
<!-- sound toggle -->
|
||||
<button class="zoom-btn" id="labfx-sound-btn" onclick="(function(){var e=window.LabFX&&window.LabFX.sound;if(!e)return;e.setEnabled(!e.isEnabled());document.getElementById('labfx-sound-btn').setAttribute('aria-pressed',e.isEnabled());document.getElementById('labfx-sound-icon-on').style.display=e.isEnabled()?'':'none';document.getElementById('labfx-sound-icon-off').style.display=e.isEnabled()?'none':'';})()" title="Звук симуляций" style="position:relative" aria-pressed="true">
|
||||
<!-- speaker on -->
|
||||
@@ -412,6 +422,7 @@
|
||||
</div><!-- /.app-layout -->
|
||||
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/material-save.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<!-- ════════════════════════════════════════════════════════════════════════
|
||||
Контент-движок, Фаза 3 — ЛЕНИВАЯ ЗАГРУЗКА КОДА СИМУЛЯЦИЙ.
|
||||
@@ -432,6 +443,11 @@
|
||||
<script src="/js/labs/_fx_motion.js"></script>
|
||||
<script src="/js/labs/_fx_sound.js"></script>
|
||||
<script src="/js/labs/_graph_panel.js"></script>
|
||||
<!-- Конструктор симуляций (Фаза 0): движок выражений + рантайм + адаптер LabRegistry.
|
||||
Лёгкие модули каркаса (~30 КБ), грузятся eager как _registry/_loader. -->
|
||||
<script src="/js/labs/_sim_expr.js"></script>
|
||||
<script src="/js/labs/_sim_engine.js"></script>
|
||||
<script src="/js/labs/_sim_adapter.js"></script>
|
||||
<script src="/js/labs/_tasks.js"></script>
|
||||
<script src="/js/labs/_phys_visuals.js"></script>
|
||||
<script src="/js/labs/_chem_visuals.js"></script>
|
||||
@@ -443,6 +459,9 @@
|
||||
<script src="/js/lab-previews.js"></script>
|
||||
<script src="/js/labs/lab-glue.js"></script>
|
||||
<script src="/js/labs/_register-all.js"></script>
|
||||
<!-- Конструктор симуляций (Фаза 0): демо-спека за флагом (?simdemo=1). Грузится
|
||||
после _register-all, чтобы LabRegistry/registerSpecSim уже существовали. -->
|
||||
<script src="/js/labs/_sim_demo.js"></script>
|
||||
<script>
|
||||
/* Sync sound toggle button icon with localStorage state on load */
|
||||
(function() {
|
||||
@@ -468,6 +487,46 @@
|
||||
off.style.display = eco ? 'none' : '';
|
||||
btn.setAttribute('aria-pressed', eco ? 'true' : 'false');
|
||||
})();
|
||||
|
||||
/* ── Снимок симуляции: «В мои материалы» / «Скачать PNG» (Фаза 2) ──
|
||||
Берём самый крупный видимый canvas в области симуляции. Для 3D (WebGL)
|
||||
кадр может выйти пустым без preserveDrawingBuffer — допустимо для v1. */
|
||||
function _labSimTitle() {
|
||||
var t = document.getElementById('sim-topbar-title');
|
||||
return t ? (t.textContent || '').trim() : '';
|
||||
}
|
||||
function _labActiveCanvas() {
|
||||
var best = null, bestArea = 0;
|
||||
document.querySelectorAll('canvas').forEach(function (c) {
|
||||
if (c.offsetParent === null) return; // скрытый
|
||||
var r = c.getBoundingClientRect();
|
||||
if (r.width < 60 || r.height < 60) return; // мелкие (иконки/спарклайны)
|
||||
var area = r.width * r.height;
|
||||
if (area > bestArea) { bestArea = area; best = c; }
|
||||
});
|
||||
return best;
|
||||
}
|
||||
function labDownloadPng() {
|
||||
var c = _labActiveCanvas();
|
||||
if (!c) { if (window.LS && LS.toast) LS.toast('Нет изображения', 'warn'); return; }
|
||||
try {
|
||||
var a = document.createElement('a');
|
||||
a.href = c.toDataURL('image/png');
|
||||
a.download = (_labSimTitle() || 'simulation') + '.png';
|
||||
document.body.appendChild(a); a.click(); a.remove();
|
||||
} catch (e) { if (window.LS && LS.toast) LS.toast('Не удалось сохранить кадр', 'error'); }
|
||||
}
|
||||
function labSaveToMaterials(btn) {
|
||||
var c = _labActiveCanvas();
|
||||
if (!c) { if (window.LS && LS.toast) LS.toast('Нет изображения', 'warn'); return; }
|
||||
if (!window.MaterialSave) { if (window.LS && LS.toast) LS.toast('Модуль сохранения не загружен', 'error'); return; }
|
||||
try {
|
||||
c.toBlob(function (blob) {
|
||||
if (!blob) { if (window.LS && LS.toast) LS.toast('Не удалось снять кадр', 'error'); return; }
|
||||
MaterialSave.image({ blob: blob, title: _labSimTitle() || 'Симуляция', name: 'sim.png', sourceTitle: 'Лаборатория' }, btn);
|
||||
}, 'image/png');
|
||||
} catch (e) { if (window.LS && LS.toast) LS.toast('Не удалось снять кадр', 'error'); }
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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
|
||||
- [~] Фаза 2 — сделано: «Сохранить кадр в Мои материалы» + «Скачать PNG» в топбаре лаборатории (захват активного canvas, переиспользует MaterialSave.image). Осталось: сохранение/возобновление параметров симуляции, измерительные инструменты. (3D/WebGL-кадр может быть пустым без preserveDrawingBuffer — доработать.)
|
||||
- [ ] Фаза 3
|
||||
- [ ] Фаза 4
|
||||
- [ ] Фаза 5
|
||||
|
||||
Reference in New Issue
Block a user