Files

10 KiB
Raw Permalink Blame History

Phase 4: Билдер (редактор)

Status: Implemented (в рабочем дереве, не закоммичено — коммит за оркестратором) Parent plan: PLAN.md Domain: frontend

Objective

Учительский редактор: страница с живым превью и панелями для сборки спеки без кода. После фазы учитель собирает рабочую симуляцию с нуля в UI и сохраняет в БД (Ф3).

Tasks

  • Страница frontend/sim-builder.html (доступ teacher/admin; сайдбар как у других страниц).
  • Левая панель-аккордеоны + центр-превью (встроенный SimEngine инстанс, перемонтаж при правках, debounce 280мс).
  • Панель Параметры: добавить/удалить параметр (имя, min, max, step, начальное, единица) → слайдер в превью.
  • Панель Объекты: добавить объект (тип из whitelist), редактор свойств; числовые поля принимают число ИЛИ выражение; палитра-помощник функций/параметров/объектов; подпись с LaTeX-превью.
  • Панель Графики/Физика: добавить plot (expr/var/range/trace); тумблер физики + её поля (Ф2) + стены/пружины.
  • Размещение объектов мышью на превью (кнопка-«прицел» у объекта → клик-поставить, drag-двигать) с синхроном в свойства.
  • Мета: заголовок, описание, предмет, класс, категория + viewport (xmin/xmax/ymin/ymax, сетка/оси, автозапуск/цикл).
  • Save/Load через LS.customSims* (Ф3): новый / редактировать существующий (?id=); кнопки «Сохранить»/«Опубликовать»/«Тест»/«Сброс».
  • Валидация на клиенте (понятные ошибки до сохранения, модалка-список) + 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

  • Все задачи выполнены
  • Полный цикл build→save→reload→edit работает (headless-смоук 23/23)
  • Доступ только teacher/admin (LS.initPage gate + редирект /dashboard)
  • Нет эмодзи; дизайн-система соблюдена (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.jswindow.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_brange:[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>customSimGetloadFromSim раскладывает спеку обратно по панелям (объекты 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 плана).