feat(control): инструмент «Маркер» на области видимости

Добавлена кисть маркера на пульте и презентации с настройками в панели эффектов; штрихи привязаны к жёлтой рамке, ластик и очистка работают с отдельным сессионным слоем.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-02 12:53:06 +08:00
parent 153be98780
commit 8b28a9b11a
18 changed files with 906 additions and 32 deletions
+95
View File
@@ -0,0 +1,95 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { SceneMarkerStore } from './sceneMarkerStore';
void test('SceneMarkerStore: сохраняет штрихи при переключении сцен', () => {
const store = new SceneMarkerStore();
store.switchScene('scene_a');
store.dispatch({
kind: 'stroke.add',
stroke: {
id: 'm1',
seed: 1,
createdAtMs: 100,
points: [{ x: 0.5, y: 0.5, tMs: 100 }],
radiusN: 0.08,
color: '#ff0000',
},
});
store.switchScene('scene_b');
assert.equal(store.getState().strokes.length, 0);
store.switchScene('scene_a');
assert.equal(store.getState().strokes.length, 1);
assert.equal(store.getState().strokes[0]?.id, 'm1');
});
void test('SceneMarkerStore: resetSession очищает кэш', () => {
const store = new SceneMarkerStore();
store.switchScene('scene_a');
store.dispatch({
kind: 'stroke.add',
stroke: {
id: 'm1',
seed: 1,
createdAtMs: 100,
points: [{ x: 0.2, y: 0.2, tMs: 100 }],
radiusN: 0.08,
color: '#00ff00',
},
});
store.resetSession();
store.switchScene('scene_a');
assert.equal(store.getState().strokes.length, 0);
});
void test('SceneMarkerStore: strokes.erase удаляет точки под ластиком', () => {
const store = new SceneMarkerStore();
store.switchScene('scene_a');
store.dispatch({
kind: 'stroke.add',
stroke: {
id: 'm1',
seed: 1,
createdAtMs: 100,
points: [
{ x: 0.1, y: 0.5, tMs: 100 },
{ x: 0.5, y: 0.5, tMs: 101 },
{ x: 0.9, y: 0.5, tMs: 102 },
],
radiusN: 0.04,
color: '#ff0000',
},
});
store.dispatch({
kind: 'strokes.erase',
points: [{ x: 0.5, y: 0.5, tMs: 200 }],
radiusN: 0.06,
});
const strokes = store.getState().strokes;
assert.ok(strokes.length >= 1);
assert.ok(strokes.every((s) => s.points.every((p) => Math.abs(p.x - 0.5) > 0.05)));
});
void test('SceneMarkerStore: strokes.clear удаляет все штрихи', () => {
const store = new SceneMarkerStore();
store.switchScene('scene_a');
store.dispatch({
kind: 'stroke.add',
stroke: {
id: 'm1',
seed: 1,
createdAtMs: 100,
points: [{ x: 0.5, y: 0.5, tMs: 100 }],
radiusN: 0.08,
color: '#0000ff',
},
});
store.dispatch({ kind: 'strokes.clear' });
assert.equal(store.getState().strokes.length, 0);
store.switchScene('scene_b');
store.switchScene('scene_a');
assert.equal(store.getState().strokes.length, 0);
});