web based infinite canvas
1import type { Action } from "../actions";
2import type { EditorState, ToolId } from "../reactivity";
3
4/**
5 * Tool interface - defines behavior for each editor tool
6 *
7 * Tools are explicit state machines that handle user input actions.
8 * Each tool decides how to respond to actions and can update editor state.
9 */
10export interface Tool {
11 /** Unique identifier for this tool */
12 readonly id: ToolId;
13
14 /**
15 * Called when the tool becomes active
16 *
17 * @param state - Current editor state
18 * @returns Updated editor state
19 */
20 onEnter(state: EditorState): EditorState;
21
22 /**
23 * Called when an action occurs while this tool is active
24 *
25 * @param state - Current editor state
26 * @param action - The action to handle
27 * @returns Updated editor state
28 */
29 onAction(state: EditorState, action: Action): EditorState;
30
31 /**
32 * Called when the tool becomes inactive
33 *
34 * @param state - Current editor state
35 * @returns Updated editor state
36 */
37 onExit(state: EditorState): EditorState;
38}
39
40/**
41 * Route an action to the currently active tool
42 *
43 * @param state - Current editor state
44 * @param action - Action to route
45 * @param tools - Map of tool ID to tool instance
46 * @returns Updated editor state after tool handles the action
47 */
48export function routeAction(state: EditorState, action: Action, tools: Map<ToolId, Tool>): EditorState {
49 const currentTool = tools.get(state.ui.toolId);
50 if (!currentTool) return state;
51 return currentTool.onAction(state, action);
52}
53
54/**
55 * Switch from current tool to a new tool
56 *
57 * Calls onExit on the current tool (if it exists), then onEnter on the new tool.
58 *
59 * @param state - Current editor state
60 * @param newToolId - ID of tool to switch to
61 * @param tools - Map of tool ID to tool instance
62 * @returns Updated editor state with new tool active
63 */
64export function switchTool(state: EditorState, newToolId: ToolId, tools: Map<ToolId, Tool>): EditorState {
65 if (state.ui.toolId === newToolId) {
66 return state;
67 }
68
69 const currentTool = tools.get(state.ui.toolId);
70 let nextState = state;
71 if (currentTool) {
72 nextState = currentTool.onExit(nextState);
73 }
74
75 nextState = { ...nextState, ui: { ...nextState.ui, toolId: newToolId } };
76
77 const newTool = tools.get(newToolId);
78 if (newTool) {
79 nextState = newTool.onEnter(nextState);
80 }
81
82 return nextState;
83}
84
85/**
86 * Create a map of tools from an array
87 *
88 * @param toolList - Array of tool instances
89 * @returns Map of tool ID to tool instance
90 */
91export function createToolMap(toolList: Tool[]): Map<ToolId, Tool> {
92 const map = new Map<ToolId, Tool>();
93 for (const tool of toolList) {
94 map.set(tool.id, tool);
95 }
96 return map;
97}