@teachinglab/omd 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,394 +1,390 @@
1
- export class ToolManager {
2
- /**
3
- * @param {OMDCanvas} canvas - Canvas instance
4
- */
5
- constructor(canvas) {
6
- this.canvas = canvas;
7
- this.tools = new Map();
8
- this.activeTool = null;
9
- this.previousTool = null;
10
- this.isDestroyed = false;
11
- }
12
-
13
- /**
14
- * Register a tool with the manager
15
- * @param {string} name - Tool name
16
- * @param {Tool} tool - Tool instance
17
- * @returns {boolean} True if tool was registered successfully
18
- */
19
- registerTool(name, tool) {
20
- if (this.isDestroyed) {
21
- console.warn('Cannot register tool on destroyed ToolManager');
22
- return false;
23
- }
24
-
25
- if (!name || typeof name !== 'string') {
26
- console.error('Tool name must be a non-empty string');
27
- return false;
28
- }
29
-
30
- if (!tool || typeof tool.onPointerDown !== 'function') {
31
- console.error('Tool must implement required methods');
32
- return false;
33
- }
34
-
35
- // Check if tool is enabled in config
36
- if (!this.canvas.config.enabledTools.includes(name)) {
37
- console.warn(`Tool '${name}' is not enabled in canvas configuration`);
38
- return false;
39
- }
40
-
41
- // Set tool name and canvas reference
42
- tool.name = name;
43
- tool.canvas = this.canvas;
44
-
45
- // Store tool
46
- this.tools.set(name, tool);
47
-
48
- console.log(`Tool '${name}' registered successfully`);
49
- return true;
50
- }
51
-
52
- /**
53
- * Unregister a tool
54
- * @param {string} name - Tool name
55
- * @returns {boolean} True if tool was unregistered
56
- */
57
- unregisterTool(name) {
58
- const tool = this.tools.get(name);
59
- if (!tool) return false;
60
-
61
- // Deactivate if it's the active tool
62
- if (this.activeTool === tool) {
63
- this.setActiveTool(null);
64
- }
65
-
66
- // Remove from tools
67
- this.tools.delete(name);
68
-
69
- console.log(`Tool '${name}' unregistered`);
70
- return true;
71
- }
72
-
73
- /**
74
- * Set the active tool
75
- * @param {string|null} toolName - Tool name to activate, or null to deactivate all
76
- * @returns {boolean} True if tool was activated successfully
77
- */
78
- setActiveTool(toolName) {
79
- // Deactivate current tool
80
- if (this.activeTool) {
81
- try {
82
- this.activeTool.onDeactivate();
83
- } catch (error) {
84
- console.error('Error deactivating tool:', error);
85
- }
86
- this.previousTool = this.activeTool;
87
- }
88
-
89
- // Clear active tool if null
90
- if (!toolName) {
91
- this.activeTool = null;
92
- this.canvas.emit('toolChanged', { name: null, tool: null, previous: this.previousTool?.name });
93
- return true;
94
- }
95
-
96
- // Get new tool
97
- const newTool = this.tools.get(toolName);
98
- if (!newTool) {
99
- console.error(`Tool '${toolName}' not found`);
100
- return false;
101
- }
102
-
103
- // Activate new tool
104
- this.activeTool = newTool;
105
-
106
- try {
107
- this.activeTool.onActivate();
108
- } catch (error) {
109
- console.error('Error activating tool:', error);
110
- this.activeTool = null;
111
- return false;
112
- }
113
-
114
- // Update cursor if available
115
- if (this.canvas.cursor && this.activeTool.getCursor) {
116
- const cursorType = this.activeTool.getCursor();
117
- this.canvas.cursor.setShape(cursorType);
118
- }
119
-
120
- // Update tool config for cursor
121
- if (this.canvas.cursor && this.activeTool.config) {
122
- this.canvas.cursor.updateFromToolConfig(this.activeTool.config);
123
- }
124
-
125
- // Emit tool change event
126
- this.canvas.emit('toolChanged', {
127
- name: toolName,
128
- tool: newTool,
129
- previous: this.previousTool?.name
130
- });
131
-
132
- console.log(`Tool '${toolName}' activated`);
133
- return true;
134
- }
135
-
136
- /**
137
- * Get the currently active tool
138
- * @returns {Tool|null} Active tool instance
139
- */
140
- getActiveTool() {
141
- return this.activeTool;
142
- }
143
-
144
- /**
145
- * Get tool by name
146
- * @param {string} name - Tool name
147
- * @returns {Tool|undefined} Tool instance
148
- */
149
- getTool(name) {
150
- return this.tools.get(name);
151
- }
152
-
153
- /**
154
- * Get all registered tool names
155
- * @returns {Array<string>} Array of tool names
156
- */
157
- getToolNames() {
158
- return Array.from(this.tools.keys());
159
- }
160
-
161
- /**
162
- * Get all registered tools
163
- * @returns {Map<string, Tool>} Map of tools
164
- */
165
- getAllTools() {
166
- return new Map(this.tools);
167
- }
168
-
169
- /**
170
- * Get metadata for all tools
171
- * @returns {Array<Object>} Array of tool metadata
172
- */
173
- getAllToolMetadata() {
174
- return Array.from(this.tools.entries()).map(([name, tool]) => ({
175
- name,
176
- displayName: tool.displayName || name,
177
- description: tool.description || '',
178
- shortcut: tool.shortcut || '',
179
- category: tool.category || 'general',
180
- icon: tool.icon || 'tool'
181
- }));
182
- }
183
-
184
- /**
185
- * Switch to previous tool
186
- * @returns {boolean} True if switched successfully
187
- */
188
- switchToPreviousTool() {
189
- if (this.previousTool) {
190
- return this.setActiveTool(this.previousTool.name);
191
- }
192
- return false;
193
- }
194
-
195
- /**
196
- * Temporarily switch to a tool and back
197
- * @param {string} toolName - Tool to switch to temporarily
198
- * @param {Function} callback - Function to execute with temporary tool
199
- * @returns {Promise<any>} Result of callback
200
- */
201
- async withTemporaryTool(toolName, callback) {
202
- const currentTool = this.activeTool?.name;
203
-
204
- if (!this.setActiveTool(toolName)) {
205
- throw new Error(`Failed to activate temporary tool: ${toolName}`);
206
- }
207
-
208
- try {
209
- const result = await callback(this.activeTool);
210
- return result;
211
- } finally {
212
- // Restore previous tool
213
- if (currentTool) {
214
- this.setActiveTool(currentTool);
215
- }
216
- }
217
- }
218
-
219
- /**
220
- * Update tool configuration
221
- * @param {string} toolName - Tool name
222
- * @param {Object} config - Configuration updates
223
- * @returns {boolean} True if updated successfully
224
- */
225
- updateToolConfig(toolName, config) {
226
- const tool = this.tools.get(toolName);
227
- if (!tool) {
228
- console.error(`Tool '${toolName}' not found`);
229
- return false;
230
- }
231
-
232
- if (tool.updateConfig) {
233
- tool.updateConfig(config);
234
-
235
- // Update cursor if this is the active tool
236
- if (this.activeTool === tool && this.canvas.cursor) {
237
- this.canvas.cursor.updateFromToolConfig(tool.config);
238
- }
239
-
240
- return true;
241
- }
242
-
243
- console.warn(`Tool '${toolName}' does not support configuration updates`);
244
- return false;
245
- }
246
-
247
- /**
248
- * Get tool configuration
249
- * @param {string} toolName - Tool name
250
- * @returns {Object|null} Tool configuration
251
- */
252
- getToolConfig(toolName) {
253
- const tool = this.tools.get(toolName);
254
- return tool ? tool.config || {} : null;
255
- }
256
-
257
- /**
258
- * Check if a tool is registered
259
- * @param {string} toolName - Tool name
260
- * @returns {boolean} True if tool is registered
261
- */
262
- hasTool(toolName) {
263
- return this.tools.has(toolName);
264
- }
265
-
266
- /**
267
- * Check if a tool is enabled in configuration
268
- * @param {string} toolName - Tool name
269
- * @returns {boolean} True if tool is enabled
270
- */
271
- isToolEnabled(toolName) {
272
- return this.canvas.config.enabledTools.includes(toolName);
273
- }
274
-
275
- /**
276
- * Get tool capabilities
277
- * @param {string} toolName - Tool name
278
- * @returns {Object|null} Tool capabilities
279
- */
280
- getToolCapabilities(toolName) {
281
- const tool = this.tools.get(toolName);
282
- if (!tool) return null;
283
-
284
- return {
285
- name: tool.name,
286
- displayName: tool.displayName,
287
- description: tool.description,
288
- shortcut: tool.shortcut,
289
- category: tool.category,
290
- supportsKeyboardShortcuts: typeof tool.onKeyboardShortcut === 'function',
291
- supportsPressure: tool.supportsPressure || false,
292
- supportsMultiTouch: tool.supportsMultiTouch || false,
293
- configurable: typeof tool.updateConfig === 'function',
294
- hasHelp: typeof tool.getHelpText === 'function'
295
- };
296
- }
297
-
298
- /**
299
- * Handle keyboard shortcuts for tools
300
- * @param {string} key - Key pressed
301
- * @param {KeyboardEvent} event - Keyboard event
302
- * @returns {boolean} True if shortcut was handled
303
- */
304
- handleKeyboardShortcut(key, event) {
305
- // First, check for tool switching shortcuts
306
- for (const [name, tool] of this.tools) {
307
- if (tool.shortcut && tool.shortcut.toLowerCase() === key.toLowerCase()) {
308
- this.setActiveTool(name);
309
- return true;
310
- }
311
- }
312
-
313
- // Then, delegate to active tool
314
- if (this.activeTool && this.activeTool.onKeyboardShortcut) {
315
- return this.activeTool.onKeyboardShortcut(key, event);
316
- }
317
-
318
- return false;
319
- }
320
-
321
- /**
322
- * Get help text for all tools or specific tool
323
- * @param {string} [toolName] - Optional tool name
324
- * @returns {string|Object} Help text
325
- */
326
- getHelpText(toolName = null) {
327
- if (toolName) {
328
- const tool = this.tools.get(toolName);
329
- if (tool && tool.getHelpText) {
330
- return tool.getHelpText();
331
- }
332
- return `No help available for tool: ${toolName}`;
333
- }
334
-
335
- // Return help for all tools
336
- const helpTexts = {};
337
- for (const [name, tool] of this.tools) {
338
- if (tool.getHelpText) {
339
- helpTexts[name] = tool.getHelpText();
340
- }
341
- }
342
- return helpTexts;
343
- }
344
-
345
- /**
346
- * Get current tool manager state
347
- * @returns {Object} Current state
348
- */
349
- getState() {
350
- return {
351
- activeToolName: this.activeTool?.name || null,
352
- previousToolName: this.previousTool?.name || null,
353
- registeredTools: this.getToolNames(),
354
- enabledTools: this.canvas.config.enabledTools,
355
- isDestroyed: this.isDestroyed
356
- };
357
- }
358
-
359
- /**
360
- * Destroy the tool manager
361
- */
362
- destroy() {
363
- if (this.isDestroyed) return;
364
-
365
- // Deactivate current tool
366
- if (this.activeTool) {
367
- try {
368
- this.activeTool.onDeactivate();
369
- } catch (error) {
370
- console.error('Error deactivating tool during destroy:', error);
371
- }
372
- }
373
-
374
- // Destroy all tools if they have a destroy method
375
- for (const [name, tool] of this.tools) {
376
- if (tool.destroy) {
377
- try {
378
- tool.destroy();
379
- } catch (error) {
380
- console.error(`Error destroying tool '${name}':`, error);
381
- }
382
- }
383
- }
384
-
385
- // Clear references
386
- this.tools.clear();
387
- this.activeTool = null;
388
- this.previousTool = null;
389
- this.canvas = null;
390
- this.isDestroyed = true;
391
-
392
- console.log('ToolManager destroyed');
393
- }
1
+ export class ToolManager {
2
+ /**
3
+ * @param {OMDCanvas} canvas - Canvas instance
4
+ */
5
+ constructor(canvas) {
6
+ this.canvas = canvas;
7
+ this.tools = new Map();
8
+ this.activeTool = null;
9
+ this.previousTool = null;
10
+ this.isDestroyed = false;
11
+ }
12
+
13
+ /**
14
+ * Register a tool with the manager
15
+ * @param {string} name - Tool name
16
+ * @param {Tool} tool - Tool instance
17
+ * @returns {boolean} True if tool was registered successfully
18
+ */
19
+ registerTool(name, tool) {
20
+ if (this.isDestroyed) {
21
+ console.warn('Cannot register tool on destroyed ToolManager');
22
+ return false;
23
+ }
24
+
25
+ if (!name || typeof name !== 'string') {
26
+ console.error('Tool name must be a non-empty string');
27
+ return false;
28
+ }
29
+
30
+ if (!tool || typeof tool.onPointerDown !== 'function') {
31
+ console.error('Tool must implement required methods');
32
+ return false;
33
+ }
34
+
35
+ // Check if tool is enabled in config
36
+ if (!this.canvas.config.enabledTools.includes(name)) {
37
+ console.warn(`Tool '${name}' is not enabled in canvas configuration`);
38
+ return false;
39
+ }
40
+
41
+ // Set tool name and canvas reference
42
+ tool.name = name;
43
+ tool.canvas = this.canvas;
44
+
45
+ // Store tool
46
+ this.tools.set(name, tool);
47
+
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Unregister a tool
53
+ * @param {string} name - Tool name
54
+ * @returns {boolean} True if tool was unregistered
55
+ */
56
+ unregisterTool(name) {
57
+ const tool = this.tools.get(name);
58
+ if (!tool) return false;
59
+
60
+ // Deactivate if it's the active tool
61
+ if (this.activeTool === tool) {
62
+ this.setActiveTool(null);
63
+ }
64
+
65
+ // Remove from tools
66
+ this.tools.delete(name);
67
+
68
+ return true;
69
+ }
70
+
71
+ /**
72
+ * Set the active tool
73
+ * @param {string|null} toolName - Tool name to activate, or null to deactivate all
74
+ * @returns {boolean} True if tool was activated successfully
75
+ */
76
+ setActiveTool(toolName) {
77
+ // Deactivate current tool
78
+ if (this.activeTool) {
79
+ try {
80
+ this.activeTool.onDeactivate();
81
+ } catch (error) {
82
+ console.error('Error deactivating tool:', error);
83
+ }
84
+ this.previousTool = this.activeTool;
85
+ }
86
+
87
+ // Clear active tool if null
88
+ if (!toolName) {
89
+ this.activeTool = null;
90
+ this.canvas.emit('toolChanged', { name: null, tool: null, previous: this.previousTool?.name });
91
+ return true;
92
+ }
93
+
94
+ // Get new tool
95
+ const newTool = this.tools.get(toolName);
96
+ if (!newTool) {
97
+ console.error(`Tool '${toolName}' not found`);
98
+ return false;
99
+ }
100
+
101
+ // Activate new tool
102
+ this.activeTool = newTool;
103
+
104
+ try {
105
+ this.activeTool.onActivate();
106
+ } catch (error) {
107
+ console.error('Error activating tool:', error);
108
+ this.activeTool = null;
109
+ return false;
110
+ }
111
+
112
+ // Update cursor if available
113
+ if (this.canvas.cursor && this.activeTool.getCursor) {
114
+ const cursorType = this.activeTool.getCursor();
115
+ this.canvas.cursor.setShape(cursorType);
116
+ }
117
+
118
+ // Update tool config for cursor
119
+ if (this.canvas.cursor && this.activeTool.config) {
120
+ this.canvas.cursor.updateFromToolConfig(this.activeTool.config);
121
+ }
122
+
123
+ // Emit tool change event
124
+ this.canvas.emit('toolChanged', {
125
+ name: toolName,
126
+ tool: newTool,
127
+ previous: this.previousTool?.name
128
+ });
129
+
130
+ return true;
131
+ }
132
+
133
+ /**
134
+ * Get the currently active tool
135
+ * @returns {Tool|null} Active tool instance
136
+ */
137
+ getActiveTool() {
138
+ return this.activeTool;
139
+ }
140
+
141
+ /**
142
+ * Get tool by name
143
+ * @param {string} name - Tool name
144
+ * @returns {Tool|undefined} Tool instance
145
+ */
146
+ getTool(name) {
147
+ return this.tools.get(name);
148
+ }
149
+
150
+ /**
151
+ * Get all registered tool names
152
+ * @returns {Array<string>} Array of tool names
153
+ */
154
+ getToolNames() {
155
+ return Array.from(this.tools.keys());
156
+ }
157
+
158
+ /**
159
+ * Get all registered tools
160
+ * @returns {Map<string, Tool>} Map of tools
161
+ */
162
+ getAllTools() {
163
+ return new Map(this.tools);
164
+ }
165
+
166
+ /**
167
+ * Get metadata for all tools
168
+ * @returns {Array<Object>} Array of tool metadata
169
+ */
170
+ getAllToolMetadata() {
171
+ return Array.from(this.tools.entries()).map(([name, tool]) => ({
172
+ name,
173
+ displayName: tool.displayName || name,
174
+ description: tool.description || '',
175
+ shortcut: tool.shortcut || '',
176
+ category: tool.category || 'general',
177
+ icon: tool.icon || 'tool'
178
+ }));
179
+ }
180
+
181
+ /**
182
+ * Switch to previous tool
183
+ * @returns {boolean} True if switched successfully
184
+ */
185
+ switchToPreviousTool() {
186
+ if (this.previousTool) {
187
+ return this.setActiveTool(this.previousTool.name);
188
+ }
189
+ return false;
190
+ }
191
+
192
+ /**
193
+ * Temporarily switch to a tool and back
194
+ * @param {string} toolName - Tool to switch to temporarily
195
+ * @param {Function} callback - Function to execute with temporary tool
196
+ * @returns {Promise<any>} Result of callback
197
+ */
198
+ async withTemporaryTool(toolName, callback) {
199
+ const currentTool = this.activeTool?.name;
200
+
201
+ if (!this.setActiveTool(toolName)) {
202
+ throw new Error(`Failed to activate temporary tool: ${toolName}`);
203
+ }
204
+
205
+ try {
206
+ const result = await callback(this.activeTool);
207
+ return result;
208
+ } finally {
209
+ // Restore previous tool
210
+ if (currentTool) {
211
+ this.setActiveTool(currentTool);
212
+ }
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Update tool configuration
218
+ * @param {string} toolName - Tool name
219
+ * @param {Object} config - Configuration updates
220
+ * @returns {boolean} True if updated successfully
221
+ */
222
+ updateToolConfig(toolName, config) {
223
+ const tool = this.tools.get(toolName);
224
+ if (!tool) {
225
+ console.error(`Tool '${toolName}' not found`);
226
+ return false;
227
+ }
228
+
229
+ if (tool.updateConfig) {
230
+ tool.updateConfig(config);
231
+
232
+ // Update cursor if this is the active tool
233
+ if (this.activeTool === tool && this.canvas.cursor) {
234
+ this.canvas.cursor.updateFromToolConfig(tool.config);
235
+ }
236
+
237
+ return true;
238
+ }
239
+
240
+ console.warn(`Tool '${toolName}' does not support configuration updates`);
241
+ return false;
242
+ }
243
+
244
+ /**
245
+ * Get tool configuration
246
+ * @param {string} toolName - Tool name
247
+ * @returns {Object|null} Tool configuration
248
+ */
249
+ getToolConfig(toolName) {
250
+ const tool = this.tools.get(toolName);
251
+ return tool ? tool.config || {} : null;
252
+ }
253
+
254
+ /**
255
+ * Check if a tool is registered
256
+ * @param {string} toolName - Tool name
257
+ * @returns {boolean} True if tool is registered
258
+ */
259
+ hasTool(toolName) {
260
+ return this.tools.has(toolName);
261
+ }
262
+
263
+ /**
264
+ * Check if a tool is enabled in configuration
265
+ * @param {string} toolName - Tool name
266
+ * @returns {boolean} True if tool is enabled
267
+ */
268
+ isToolEnabled(toolName) {
269
+ return this.canvas.config.enabledTools.includes(toolName);
270
+ }
271
+
272
+ /**
273
+ * Get tool capabilities
274
+ * @param {string} toolName - Tool name
275
+ * @returns {Object|null} Tool capabilities
276
+ */
277
+ getToolCapabilities(toolName) {
278
+ const tool = this.tools.get(toolName);
279
+ if (!tool) return null;
280
+
281
+ return {
282
+ name: tool.name,
283
+ displayName: tool.displayName,
284
+ description: tool.description,
285
+ shortcut: tool.shortcut,
286
+ category: tool.category,
287
+ supportsKeyboardShortcuts: typeof tool.onKeyboardShortcut === 'function',
288
+ supportsPressure: tool.supportsPressure || false,
289
+ supportsMultiTouch: tool.supportsMultiTouch || false,
290
+ configurable: typeof tool.updateConfig === 'function',
291
+ hasHelp: typeof tool.getHelpText === 'function'
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Handle keyboard shortcuts for tools
297
+ * @param {string} key - Key pressed
298
+ * @param {KeyboardEvent} event - Keyboard event
299
+ * @returns {boolean} True if shortcut was handled
300
+ */
301
+ handleKeyboardShortcut(key, event) {
302
+ // First, check for tool switching shortcuts
303
+ for (const [name, tool] of this.tools) {
304
+ if (tool.shortcut && tool.shortcut.toLowerCase() === key.toLowerCase()) {
305
+ this.setActiveTool(name);
306
+ return true;
307
+ }
308
+ }
309
+
310
+ // Then, delegate to active tool
311
+ if (this.activeTool && this.activeTool.onKeyboardShortcut) {
312
+ return this.activeTool.onKeyboardShortcut(key, event);
313
+ }
314
+
315
+ return false;
316
+ }
317
+
318
+ /**
319
+ * Get help text for all tools or specific tool
320
+ * @param {string} [toolName] - Optional tool name
321
+ * @returns {string|Object} Help text
322
+ */
323
+ getHelpText(toolName = null) {
324
+ if (toolName) {
325
+ const tool = this.tools.get(toolName);
326
+ if (tool && tool.getHelpText) {
327
+ return tool.getHelpText();
328
+ }
329
+ return `No help available for tool: ${toolName}`;
330
+ }
331
+
332
+ // Return help for all tools
333
+ const helpTexts = {};
334
+ for (const [name, tool] of this.tools) {
335
+ if (tool.getHelpText) {
336
+ helpTexts[name] = tool.getHelpText();
337
+ }
338
+ }
339
+ return helpTexts;
340
+ }
341
+
342
+ /**
343
+ * Get current tool manager state
344
+ * @returns {Object} Current state
345
+ */
346
+ getState() {
347
+ return {
348
+ activeToolName: this.activeTool?.name || null,
349
+ previousToolName: this.previousTool?.name || null,
350
+ registeredTools: this.getToolNames(),
351
+ enabledTools: this.canvas.config.enabledTools,
352
+ isDestroyed: this.isDestroyed
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Destroy the tool manager
358
+ */
359
+ destroy() {
360
+ if (this.isDestroyed) return;
361
+
362
+ // Deactivate current tool
363
+ if (this.activeTool) {
364
+ try {
365
+ this.activeTool.onDeactivate();
366
+ } catch (error) {
367
+ console.error('Error deactivating tool during destroy:', error);
368
+ }
369
+ }
370
+
371
+ // Destroy all tools if they have a destroy method
372
+ for (const [name, tool] of this.tools) {
373
+ if (tool.destroy) {
374
+ try {
375
+ tool.destroy();
376
+ } catch (error) {
377
+ console.error(`Error destroying tool '${name}':`, error);
378
+ }
379
+ }
380
+ }
381
+
382
+ // Clear references
383
+ this.tools.clear();
384
+ this.activeTool = null;
385
+ this.previousTool = null;
386
+ this.canvas = null;
387
+ this.isDestroyed = true;
388
+
389
+ }
394
390
  }