web based infinite canvas
1import type { Action } from "../actions";
2import { createId, ShapeRecord } from "../model";
3import type { EditorState, ToolId } from "../reactivity";
4import { getCurrentPage } from "../reactivity";
5import type { Tool } from "./base";
6
7/**
8 * Text tool - creates text shapes on click
9 *
10 * Features:
11 * - Click to create a text shape at the pointer position
12 * - Text is created with default content "Text"
13 * - Shape is immediately selected after creation
14 */
15export class TextTool implements Tool {
16 readonly id: ToolId = "text";
17
18 onEnter(state: EditorState): EditorState {
19 return state;
20 }
21
22 onExit(state: EditorState): EditorState {
23 return state;
24 }
25
26 onAction(state: EditorState, action: Action): EditorState {
27 switch (action.type) {
28 case "pointer-down": {
29 return this.handlePointerDown(state, action);
30 }
31 default: {
32 return state;
33 }
34 }
35 }
36
37 private handlePointerDown(state: EditorState, action: Action): EditorState {
38 if (action.type !== "pointer-down") return state;
39
40 const currentPage = getCurrentPage(state);
41 if (!currentPage) return state;
42
43 const shapeId = createId("shape");
44
45 const shape = ShapeRecord.createText(currentPage.id, action.world.x, action.world.y, {
46 text: "Text",
47 fontSize: 16,
48 fontFamily: "sans-serif",
49 color: "#1f2933",
50 }, shapeId);
51
52 const newPage = { ...currentPage, shapeIds: [...currentPage.shapeIds, shapeId] };
53
54 return {
55 ...state,
56 doc: {
57 ...state.doc,
58 shapes: { ...state.doc.shapes, [shapeId]: shape },
59 pages: { ...state.doc.pages, [currentPage.id]: newPage },
60 },
61 ui: { ...state.ui, selectionIds: [shapeId] },
62 };
63 }
64}