task(A4.9): migrate event domain tools (chair/row/block/table/stage) to v2

This commit is contained in:
Agent Zero
2026-08-27 01:12:52 +02:00
parent cf67635732
commit 76f305fc6f
3 changed files with 135 additions and 1 deletions
@@ -0,0 +1,78 @@
/**
* event-tools Built-in V2-Plugin (Task A4.9).
*
* Fachwerkzeuge für Veranstaltungsplanung: chair, seating-row,
* seating-block, table, stage — alle Einzelklick-Platzierer über
* den (durch BUG-1-Fix korrigierten) SeatingService.
*
* Jede Platzierung = doc.transact → 1 Undo-Schritt (Multi-Element-Sets
* wie seating-block erscheinen/verschwinden gemeinsam).
*/
import type { PluginV2, ToolExtensionV2, FullToolContext } from '../../types';
import type { CADElement } from '../../../types/cad.types';
import { SeatingService } from '../../../services/seatingService';
const seating = new SeatingService();
/** Erzeugt ein Einzelklick-Platzierer-Werkzeug über einen placer. */
function makePlaceTool(
id: string,
label: string,
icon: string,
placer: (x: number, y: number, layerId: string) => CADElement | CADElement[],
statusNoun: string,
): ToolExtensionV2 {
return {
manifest: {
id,
label,
icon,
ribbonTab: 'insert',
tags: ['basic'],
description: `${statusNoun} per Klick platzieren`,
},
optionsSchema: [],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc) return;
const layerId = (c.options.layerId as string | undefined) ?? 'layer-0';
const placed = placer(e.world.x, e.world.y, layerId);
const els = Array.isArray(placed) ? placed : [placed];
c.doc.transact(() => {
for (const el of els) c.doc!.addElement(el);
});
ctx.setStatus(`${statusNoun} platziert (${els.length} Element${els.length === 1 ? '' : 'e'})`);
},
},
};
}
export const chairTool = makePlaceTool('chair', 'Stuhl', '\u2570',
(x, y, layerId) => seating.createChair(x, y, layerId), 'Stuhl');
export const seatingRowTool = makePlaceTool('seating-row', 'Stuhldreihe', '\u2500\u2570\u2500',
(x, y, layerId) => seating.createSeatingRow(x, y, layerId), 'Reihe');
export const seatingBlockTool = makePlaceTool('seating-block', 'Stuhlblock', '\u2565',
(x, y, layerId) => seating.createSeatingBlock(x, y, layerId), 'Block');
export const tableTool = makePlaceTool('table', 'Tisch', '\u25cb',
(x, y, layerId) => seating.createTable(x, y, layerId), 'Tisch');
export const stageTool = makePlaceTool('stage', 'Bühne', '\u25ad',
(x, y, layerId) => seating.createStage(x, y, layerId), 'Bühne');
/** V2-Plugin: Event-Domain (Task A4.9). */
export const eventToolsV2Plugin: PluginV2 = {
manifest: {
id: 'event-tools',
name: 'Event-Bestuhlung',
version: '2.0.0',
author: 'web-cad team',
description: 'Stühle, Reihen, Blöcke, Tische, Bühne (V2).',
category: 'elements',
enabledByDefault: true,
},
tools: [chairTool, seatingRowTool, seatingBlockTool, tableTool, stageTool],
};
+3
View File
@@ -31,6 +31,7 @@ export { coreDrawingPlugin } from './builtin/core-drawing';
export { coreModifyPlugin } from './builtin/core-modify'; export { coreModifyPlugin } from './builtin/core-modify';
export { coreAnnotatePlugin } from './builtin/core-annotate'; export { coreAnnotatePlugin } from './builtin/core-annotate';
export { coreMeasurePlugin } from './builtin/core-measure'; export { coreMeasurePlugin } from './builtin/core-measure';
export { eventToolsV2Plugin } from './builtin/event-tools';
import { pluginRegistry } from './PluginRegistry'; import { pluginRegistry } from './PluginRegistry';
import { eventToolsPlugin } from './builtin/eventTools'; import { eventToolsPlugin } from './builtin/eventTools';
@@ -38,6 +39,7 @@ import { coreDrawingPlugin } from './builtin/core-drawing';
import { coreModifyPlugin } from './builtin/core-modify'; import { coreModifyPlugin } from './builtin/core-modify';
import { coreAnnotatePlugin } from './builtin/core-annotate'; import { coreAnnotatePlugin } from './builtin/core-annotate';
import { coreMeasurePlugin } from './builtin/core-measure'; import { coreMeasurePlugin } from './builtin/core-measure';
import { eventToolsV2Plugin } from './builtin/event-tools';
/** Register all built-in plugins */ /** Register all built-in plugins */
export function registerBuiltinPlugins() { export function registerBuiltinPlugins() {
@@ -47,4 +49,5 @@ export function registerBuiltinPlugins() {
pluginRegistry.registerV2(coreModifyPlugin); pluginRegistry.registerV2(coreModifyPlugin);
pluginRegistry.registerV2(coreAnnotatePlugin); pluginRegistry.registerV2(coreAnnotatePlugin);
pluginRegistry.registerV2(coreMeasurePlugin); pluginRegistry.registerV2(coreMeasurePlugin);
pluginRegistry.registerV2(eventToolsV2Plugin);
} }
+54 -1
View File
@@ -6,8 +6,9 @@
import { describe, it, expect, vi } from 'vitest'; import { describe, it, expect, vi } from 'vitest';
import { lineTool, coreDrawingPlugin } from '../src/plugins/builtin/core-drawing'; import { lineTool, coreDrawingPlugin } from '../src/plugins/builtin/core-drawing';
import { selectTool, coreModifyPlugin } from '../src/plugins/builtin/core-modify'; import { selectTool, coreModifyPlugin } from '../src/plugins/builtin/core-modify';
import { coreMeasurePlugin } from '../src/plugins/builtin/core-measure';
import { coreAnnotatePlugin } from '../src/plugins/builtin/core-annotate'; import { coreAnnotatePlugin } from '../src/plugins/builtin/core-annotate';
import { coreMeasurePlugin } from '../src/plugins/builtin/core-measure';
import { eventToolsV2Plugin } from '../src/plugins/builtin/event-tools';
import { CADDocument } from '../src/kernel/document/CADDocument'; import { CADDocument } from '../src/kernel/document/CADDocument';
import { createInMemoryDoc } from './helpers/inMemoryDoc'; import { createInMemoryDoc } from './helpers/inMemoryDoc';
import type { ToolPointerEvent, FullToolContext } from '../src/plugins/types'; import type { ToolPointerEvent, FullToolContext } from '../src/plugins/types';
@@ -348,6 +349,58 @@ describe('A4.5 pilot: hatch tool', () => {
}); });
}); });
describe('A4.9 pilot: event tools', () => {
it('chair platziert Einzel-Element per Klick', () => {
const { ydoc } = createInMemoryDoc();
const doc = new CADDocument(ydoc);
const ctx = makeCtx({ doc });
const chair = eventToolsV2Plugin.tools!.find((t) => t.manifest.id === 'chair')!;
chair.handlers.down!(pe(100, 200), ctx);
const all = doc.getAllElements();
expect(all.length).toBe(1);
expect(all[0].type).toBe('chair');
expect(all[0].x).toBe(100);
expect(all[0].y).toBe(200);
});
it('seating-block erzeugt rows×cols Elemente in EINER Transaktion (ein undo = ganzer Block weg)', () => {
const { ydoc } = createInMemoryDoc();
const doc = new CADDocument(ydoc);
const ctx = makeCtx({ doc });
const block = eventToolsV2Plugin.tools!.find((t) => t.manifest.id === 'seating-block')!;
block.handlers.down!(pe(0, 0), ctx); // Legacy-Default: rows=3, cols=8
const all = doc.getAllElements();
expect(all.length).toBeGreaterThan(1); // Multi-Element-Satz
expect(all.every((e) => e.type === 'chair')).toBe(true);
// BUG-1-Regressionsschutz: alle Reihen im Block haben eigene rowIds
const rowIds = new Set(all.map((e) => e.properties.rowId));
expect(rowIds.size).toBeGreaterThanOrEqual(3);
// Kern-Akzeptanz: EIN undo entfernt den GANZEN Block
expect(doc.undo()).toBe(true);
expect(doc.getAllElements().length).toBe(0);
});
it('seating-row platziert mehrere Stühle mit gemeinsamer rowId; undo entfernt Reihe', () => {
const { ydoc } = createInMemoryDoc();
const doc = new CADDocument(ydoc);
const ctx = makeCtx({ doc });
const row = eventToolsV2Plugin.tools!.find((t) => t.manifest.id === 'seating-row')!;
row.handlers.down!(pe(0, 0), ctx);
const all = doc.getAllElements();
expect(all.length).toBeGreaterThanOrEqual(2);
const rowIds = new Set(all.map((e) => e.properties.rowId));
expect(rowIds.size).toBe(1); // eine Reihe → EINE rowId
expect(doc.undo()).toBe(true);
expect(doc.getAllElements().length).toBe(0);
});
});
describe('A4.6 pilot: move/copy tools', () => { describe('A4.6 pilot: move/copy tools', () => {
it('move verschiebt selektierte Elemente in EINER Transaktion (undo entfernt Versatz)', () => { it('move verschiebt selektierte Elemente in EINER Transaktion (undo entfernt Versatz)', () => {
const { ydoc } = createInMemoryDoc(); const { ydoc } = createInMemoryDoc();