From b520f4b849a7875615da67214e2db2c0c65fd520 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Tue, 14 Apr 2026 11:31:39 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=85=20=D1=81=D0=B8=D0=BC=D1=83=D0=BB=D1=8F?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B2=20=D0=BE=D0=BD=D0=BB=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD-=D1=83=D1=80=D0=BE=D0=BA=D0=B5=20+=20fix=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B8=20(arcma?= =?UTF-8?q?rk,=20triangle=20tools)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Онлайн-урок: - Кнопка «Рисовать» в баре симуляции (только учителю) - При активации: холст доски показывается поверх iframe (z-index), фон прозрачный - Учитель рисует прямо поверх симуляции обычными инструментами - Студенты видят то же самое через SSE (classroom_sim_annotate) - Выход из режима → кнопка «Вернуться к симуляции» Планиметрия (bugfix): - arcmark теперь рисуется всегда (не зависит от showAngles) - altitude/median: 1 клик на вершину треугольника (авто-находит противоположную сторону) - centroid/orthocenter: 1 клик внутри/на треугольник Co-Authored-By: Claude Sonnet 4.6 --- .../src/controllers/classroomController.js | 14 +++ backend/src/routes/classroom.js | 1 + frontend/classroom.html | 111 ++++++++++++++++++ frontend/js/whiteboard.js | 7 ++ 4 files changed, 133 insertions(+) diff --git a/backend/src/controllers/classroomController.js b/backend/src/controllers/classroomController.js index edb5b3f..47445ec 100644 --- a/backend/src/controllers/classroomController.js +++ b/backend/src/controllers/classroomController.js @@ -949,6 +949,19 @@ function simMode(req, res) { res.json({ ok: true }); } +/* POST /api/classroom/:id/sim/annotate — teacher toggles draw-over-sim mode */ +function simAnnotate(req, res) { + const sessionId = Number(req.params.id); + const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=? AND status='active'`).get(sessionId); + if (!session) return res.status(404).json({ error: 'Сессия не активна' }); + if (session.teacher_id !== req.user.id && req.user.role !== 'admin') + return res.status(403).json({ error: 'Нет доступа' }); + + const { active } = req.body; + emitToSession(sessionId, { type: 'classroom_sim_annotate', sessionId, active: !!active }); + res.json({ ok: true }); +} + /* DELETE /api/classroom/:id/sim — teacher closes simulation */ function simClose(req, res) { const sessionId = Number(req.params.id); @@ -1522,6 +1535,7 @@ module.exports = { simClose, simState, simMode, + simAnnotate, clearPage, previewStroke, broadcastCursor, diff --git a/backend/src/routes/classroom.js b/backend/src/routes/classroom.js index b54f187..f678ed1 100644 --- a/backend/src/routes/classroom.js +++ b/backend/src/routes/classroom.js @@ -98,6 +98,7 @@ router.post('/:id/sim', ...teacher, c.simOpen); router.delete('/:id/sim', ...teacher, c.simClose); router.post('/:id/sim/state', ...teacher, c.simState); router.post('/:id/sim/mode', ...teacher, c.simMode); +router.post('/:id/sim/annotate', ...teacher, c.simAnnotate); // Cursor broadcast (all participants) router.post('/:id/cursor', ...auth, c.broadcastCursor); diff --git a/frontend/classroom.html b/frontend/classroom.html index 8be4c58..c6ace3e 100644 --- a/frontend/classroom.html +++ b/frontend/classroom.html @@ -2007,6 +2007,55 @@ background: #0D0D1A; } + /* ── Annotate-over-sim mode ───────────────────────────────────────────── */ + /* Board floats above the sim panel (sim visible behind transparent canvas) */ + .cr-board-area.annotate-active .cr-sim-panel { z-index: 1; } + .cr-board-area.annotate-active .cr-board-wrap { + z-index: 45; + background: transparent !important; + border-color: transparent !important; + box-shadow: none !important; + } + .cr-board-area.annotate-active .cr-board-wrap::before, + .cr-board-area.annotate-active .cr-board-wrap::after { display: none !important; } + /* Floating bar shown while annotating */ + .cr-annotate-bar { + display: none; + position: absolute; top: 0; left: 0; right: 0; z-index: 60; + height: 40px; flex-shrink: 0; + background: rgba(10,6,22,0.92); + border-bottom: 1.5px solid rgba(241,91,181,0.4); + backdrop-filter: blur(6px); + align-items: center; gap: 8px; padding: 0 12px; + } + .cr-board-area.annotate-active .cr-annotate-bar { display: flex; } + .cr-annotate-bar-label { + font-size: 0.75rem; font-weight: 700; color: #F15BB5; + display: flex; align-items: center; gap: 5px; flex: 1; + } + .cr-annotate-bar-label svg { width: 14px; height: 14px; stroke: #F15BB5; } + .cr-annotate-exit { + padding: 4px 12px; border-radius: 7px; border: 1px solid rgba(241,91,181,0.4); + background: rgba(241,91,181,0.1); color: #F15BB5; + font-family: 'Manrope',sans-serif; font-size: 0.72rem; font-weight: 700; + cursor: pointer; transition: all .15s; + } + .cr-annotate-exit:hover { background: rgba(241,91,181,0.22); } + /* "Draw" button in sim bar */ + .cr-sim-annotate-btn { + display: none; + padding: 3px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.12); + background: transparent; color: rgba(255,255,255,0.5); + font-family: 'Manrope',sans-serif; font-size: 0.7rem; font-weight: 700; + cursor: pointer; transition: all .15s; align-items: center; gap: 4px; + margin-right: 4px; + } + .cr-sim-annotate-btn svg { width: 12px; height: 12px; flex-shrink: 0; } + .cr-sim-annotate-btn:hover { color: rgba(255,255,255,0.8); border-color: rgba(241,91,181,0.4); } + .cr-sim-annotate-btn.active { background: rgba(241,91,181,0.18); border-color: rgba(241,91,181,0.5); color: #F15BB5; } + /* Show draw button only for teachers when sim is open */ + .cr-sim-panel.open .cr-sim-annotate-btn.teacher-ctrl { display: flex; } + /* ── Simulation picker modal ────────────────────────────────────────── */ .cr-sim-picker-overlay { position: fixed; inset: 0; z-index: 200; @@ -2202,6 +2251,11 @@ Симуляция + + + +
+ + + Режим аннотации + + +