Files

99 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 4: Билдер (редактор)
**Status:** ✅ Implemented (в рабочем дереве, не закоммичено — коммит за оркестратором)
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Учительский редактор: страница с живым превью и панелями для сборки спеки без кода.
После фазы учитель собирает рабочую симуляцию с нуля в UI и сохраняет в БД (Ф3).
## Tasks
- [x] Страница `frontend/sim-builder.html` (доступ teacher/admin; сайдбар как у других страниц).
- [x] Левая панель-аккордеоны + центр-превью (встроенный `SimEngine` инстанс, перемонтаж при правках, debounce 280мс).
- [x] Панель **Параметры**: добавить/удалить параметр (имя, min, max, step, начальное, единица) → слайдер в превью.
- [x] Панель **Объекты**: добавить объект (тип из whitelist), редактор свойств; числовые поля
принимают число ИЛИ выражение; палитра-помощник функций/параметров/объектов; подпись с LaTeX-превью.
- [x] Панель **Графики/Физика**: добавить plot (expr/var/range/trace); тумблер физики + её поля (Ф2) + стены/пружины.
- [x] Размещение объектов мышью на превью (кнопка-«прицел» у объекта → клик-поставить, drag-двигать) с синхроном в свойства.
- [x] Мета: заголовок, описание, предмет, класс, категория + viewport (xmin/xmax/ymin/ymax, сетка/оси, автозапуск/цикл).
- [x] Save/Load через `LS.customSims*` (Ф3): новый / редактировать существующий (?id=); кнопки «Сохранить»/«Опубликовать»/«Тест»/«Сброс».
- [x] Валидация на клиенте (понятные ошибки до сохранения, модалка-список) + inline-ошибки выражений у полей.
## Files to Modify/Create
- `frontend/sim-builder.html` (new) — страница + инлайн-логика редактора
- `frontend/js/labs/_sim_engine.js` — при необходимости hook'и для билдера (live re-mount, highlight) (modify, минимально)
- `js/api.js` — если нужны доп. методы (modify, опц.)
- ссылка в сайдбаре/навигации для учителя (modify соответствующего include)
## Acceptance Criteria
- Учитель с нуля добавляет параметры/объекты/график, видит живое превью, сохраняет, открывает заново и видит то же.
- Ошибка в выражении показывается понятно, не роняет редактор.
- Нет эмодзи, дизайн в системе ls.css.
## Notes
- Прагматично: форма-панели + лёгкий drag на превью. НЕ полноценный node-граф.
- Это frontend-фаза — использовать гайдлайны frontend-design.
- Большой файл — держать логику модульной (можно вынести в `frontend/js/sim-builder.js`).
## Review Checklist
- [x] Все задачи выполнены
- [x] Полный цикл build→save→reload→edit работает (headless-смоук 23/23)
- [x] Доступ только teacher/admin (LS.initPage gate + редирект /dashboard)
- [x] Нет эмодзи; дизайн-система соблюдена (ls.css переменные/классы)
## Handoff to Next Phase
### Что реализовано (Phase 4)
- **Страница `frontend/sim-builder.html`** (URL `/sim-builder`, гейт teacher/admin через
`LS.initPage()` → редирект `/dashboard`). Раскладка: `.app-layout > .sidebar(#app-sidebar) +
.sb-content`; внутри `.sbu-wrap` = тулбар + `.sbu-body` (панели-аккордеоны слева 360px +
превью справа). Подключает движок `/js/labs/_sim_expr.js` + `/js/labs/_sim_engine.js` (тем
же путём, что lab.html — `/js` мапится на корневой `js/`, а `labs/*` проваливается на
`express.static(frontend)`), KaTeX CDN, и логику `/js/sim-builder.js`.
- **Логика `frontend/js/sim-builder.js`** → `window.SimBuilder.create({host, previewHost,
panelHost, toolbarHost}) -> Builder`. Состояние `Builder.st` (meta/subject/grade/cat/
viewport/time/params[]/objects[]/plots[]/physics{}); `_uid` на объектах/стенах/пружинах —
только UI-метка, вырезается при сборке.
- **Генерация спеки** `Builder.buildSpec()` → чистый JSON v1: `{ specVersion:1, meta:{title,
desc}, viewport, time, params[], objects[](+merged plots), physics? }`. `stripObj()` убирает
`_uid`/пустые поля; **plot** материализуется отдельно (`normalizePlotForSpec`: UI-поля
`range_a/range_b` → `range:[a,b]`, границы могут быть числом ИЛИ выражением xmin/xmax). Физика
собирается только при `physics.enabled` (gravity{x,y}, friction, restitution клампится 0..1,
walls[], springs[]; конец пружины «id» или «x,y» парсится `parseEnd`). Числовые поля объектов
хранятся как введено (число/строка-выражение) — движок `SimExpr.compileValue` ест оба.
- **Живое превью**: `scheduleRemount(debounce 280мс)` → `remount()` уничтожает старый инстанс,
монтирует `SimEngine.mount(previewHost, buildSpec())`, сохраняет play-состояние. Ошибка сборки
показывается в превью, не роняет редактор.
- **Drag-on-preview**: кнопка-«прицел» у объекта выбирает его (`_selObjId`); pointerdown/move по
`inst.canvas` конвертит px→мир через `inst._toWorld()` и пишет x/y (или x2/y2 для segment/
vector) в свойства объекта + обновляет поля панели. Работает только на паузе движка.
- **Палитра выражений** (`openPalette`): модалка с чипами — параметры, ссылки `id.x/id.y` на
объекты с id, `t/w/h`, константы (`SimExpr.CONSTANTS`), функции (`SimExpr.FUNCTIONS`); клик
вставляет имя в активное поле (функции — с `()`).
- **Валидация (клиент, до запроса)** `Builder.validate()` зеркалит серверную (Ф3): обязателен
title; params ≤50, имя-идентификатор, **запрет `e`/служебных (pi/t/w/h/E/PI/tau)**, без дублей,
min≤max; objects+plots ≤200; каждое выражение `SimExpr.compile` → ошибка показывается у поля И
в списке-модалке; expr ≤500 симв.; walls ≤20, springs ≤50; restitution 0..1; размер JSON ≤200КБ
(через Blob). Ошибки — дружелюбная модалка-список.
- **Save/Load**: «Сохранить»→`customSimCreate`/`customSimUpdate`(если есть simId); «Опубликовать»
добавляет `status:'published'`. После create — `history.replaceState('/sim-builder?id=<id>')`,
чтобы повторное сохранение делало update. Загрузка `?id=<id>` → `customSimGet` → `loadFromSim`
раскладывает спеку обратно по панелям (объекты vs plots разделяются по `type==='plot'`).
- **Сайдбар**: добавлен пункт `/sim-builder` «Конструктор симуляций» (icon `pencil-ruler`,
teacher-only) в группу «Практика и игры» сразу после «Лаборатория» — минимальная аддитивная
правка `js/sidebar.js` (тот же паттерн `cls:'sb-teacher-only', hidden:!isTch`, активная
подсветка через `isActive`).
### Что осталось / на Ф5 (каталог)
- Превью-картинка симуляции (опц., упомянута в задаче) НЕ делалась — `custom_sims` не имеет поля
превью; визуальная карточка каталога — забота Ф5 (можно рендерить мини-`SimEngine` как превью).
- На Ф5: custom-sims из `LS.customSimsList()` (published + свои) должны попасть в каталог `/lab`
через `window.registerSpecSim(spec)` / `SimAdapter` (Ф0). Кнопка «Открыть в конструкторе» из
каталога → `/sim-builder?id=<id>` уже работает (страница грузит по `?id`). Билдер пишет спеку
ровно в формате, который ест `SimEngine.mount` и серверная `validateSpec` (escaped-текст
приходит обратно при GET — для KaTeX/canvas безопасно).
- Drag-on-preview пишет только x/y (или конец segment/vector). Перетаскивание точек polyline,
origin вектора в форме origin+dx/dy, и хэндлов physics-тел — НЕ сделано (числами/выражениями
редактируется). Это осознанный прагматичный минимум (см. Notes плана).