@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,291 +1,292 @@
1
- import {omdColor} from '../../src/omdColor.js';
2
- import { jsvgRect, jsvgGroup } from '@teachinglab/jsvg';
3
- import { jsvgButton } from '@teachinglab/jsvg';
4
-
5
- export class Toolbar {
6
- /**
7
- * @param {OMDCanvas} canvas - Canvas instance
8
- */
9
- constructor(canvas) {
10
- this.canvas = canvas;
11
- this.buttons = new Map();
12
- this.activeButton = null;
13
- this.omdColor = omdColor; // Use omdColor for consistent styling
14
-
15
-
16
-
17
- // Create toolbar element
18
- this._createElement();
19
-
20
- // Create tool buttons
21
- this._createToolButtons();
22
-
23
- // Listen for tool changes
24
- this.canvas.on('toolChanged', (event) => {
25
- this._updateActiveButton(event.detail.name);
26
- });
27
-
28
-
29
- }
30
-
31
- /**
32
- * Create the toolbar SVG element
33
- * @private
34
- */
35
- _createElement() {
36
-
37
-
38
- // Create a jsvgGroup for the toolbar
39
- this.toolbarGroup = new jsvgGroup();
40
-
41
-
42
- // Create background rectangle
43
- this.background = new jsvgRect();
44
-
45
-
46
- // Initial size, will be updated after buttons are created
47
- this.background.setWidthAndHeight(100, 54);
48
- this.background.setCornerRadius(27); // Pill shape
49
- this.background.setFillColor(this.omdColor.mediumGray); // Modern dark, semi-transparent
50
-
51
-
52
-
53
-
54
- // Debug the background SVG object
55
-
56
-
57
- this.toolbarGroup.addChild(this.background);
58
-
59
-
60
- // Position the toolbar at bottom center
61
- this._updatePosition();``
62
-
63
- // Add to main SVG so it is rendered
64
- this.canvas.svg.appendChild(this.toolbarGroup.svgObject);
65
- }
66
-
67
- /**
68
- * Update toolbar position to bottom center
69
- * @private
70
- */
71
- _updatePosition() {
72
- const canvasRect = this.canvas.container.getBoundingClientRect();
73
- const toolbarWidth = this.background.width;
74
- const toolbarHeight = this.background.height;
75
- // Bottom center, 24px from bottom
76
- const x = (canvasRect.width - toolbarWidth) / 2;
77
- const y = canvasRect.height - toolbarHeight - 24;
78
-
79
- // Ensure toolbar stays within canvas bounds
80
- const clampedX = Math.max(0, Math.min(x, canvasRect.width - toolbarWidth));
81
- const clampedY = Math.max(0, Math.min(y, canvasRect.height - toolbarHeight));
82
- this.toolbarGroup.setPosition(x, y);
83
-
84
- // Debug the SVG object
85
- console.log('Toolbar group SVG object:', this.toolbarGroup.svgObject);
86
- console.log('Toolbar group SVG object style:', this.toolbarGroup.svgObject.style);
87
- console.log('Toolbar group SVG object parent:', this.toolbarGroup.svgObject.parentNode);
88
-
89
- // Set z-index to ensure it's on top
90
- this.toolbarGroup.svgObject.style.zIndex = '1000';
91
- this.toolbarGroup.svgObject.style.pointerEvents = 'auto';
92
-
93
- // Fix the viewBox to include the toolbar position
94
- const bgWidth = this.background.width;
95
- const bgHeight = this.background.height;
96
- const viewBoxX = x;
97
- const viewBoxY = y;
98
- const viewBoxWidth = Math.max(500, x + bgWidth);
99
- const viewBoxHeight = Math.max(500, y + bgHeight);
100
-
101
- this.toolbarGroup.svgObject.setAttribute('viewBox', `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`);
102
- console.log('Updated viewBox to:', `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`);
103
-
104
- // Don't set x/y attributes - let setPosition handle it via transform
105
- // this.toolbarGroup.svgObject.setAttribute('x', x);
106
- // this.toolbarGroup.svgObject.setAttribute('y', y);
107
- }
108
-
109
- /**
110
- * Create tool buttons
111
- * @private
112
- */
113
- _createToolButtons() {
114
- const tools = this.canvas.toolManager.getAllToolMetadata();
115
- console.log('Creating tool buttons for tools:', tools);
116
-
117
- const buttonSize = 48;
118
- const spacing = 8;
119
- const padding = 6;
120
- let xPos = padding;
121
- const yPos = padding;
122
- tools.forEach(toolMeta => {
123
- const button = this._createJsvgButton(toolMeta, buttonSize);
124
- button.setPosition(xPos, yPos);
125
- this.toolbarGroup.addChild(button);
126
- this.buttons.set(toolMeta.name, button);
127
- xPos += buttonSize + spacing;
128
- });
129
- // Remove last spacing
130
- const totalWidth = xPos - spacing + padding;
131
- const totalHeight = buttonSize + 2 * padding;
132
- this.background.setWidthAndHeight(totalWidth, totalHeight);
133
- this.background.setCornerRadius(totalHeight / 2);
134
- // Reposition after sizing
135
- this._updatePosition();
136
- console.log('Toolbar background width set to:', totalWidth);
137
-
138
- // Set initial active button
139
- const activeTool = this.canvas.toolManager.getActiveTool();
140
- if (activeTool) {
141
- this._updateActiveButton(activeTool.name);
142
- }
143
- }
144
-
145
- /**
146
- * Create individual tool button using jsvgButton
147
- * @private
148
- */
149
- _createJsvgButton(toolMeta, size = 48) {
150
- console.log('Creating jsvgButton for tool:', toolMeta.name);
151
-
152
- const button = new jsvgButton();
153
- button.setWidthAndHeight(size, size);
154
- button.setCornerRadius(size / 2); // Make it circular
155
- button.setFillColor('white');
156
-
157
- // Remove any default text from the button group (if present)
158
- // jsvgButton may add a <text> element by default; remove it
159
- button.setText(''); // Clear any default text
160
-
161
- // Set the icon SVG
162
- const iconSvg = this._getToolIconSvg(toolMeta.name);
163
- if (iconSvg) {
164
- const dataURI = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(iconSvg);
165
- button.addImage(dataURI, size * 0.5, size * 0.5); // Icon at 50% of button size
166
- }
167
-
168
- // Set click callback
169
- button.setClickCallback(() => {
170
- this.canvas.toolManager.setActiveTool(toolMeta.name);
171
- });
172
-
173
- // Store tool metadata
174
- button.toolMeta = toolMeta;
175
- return button;
176
- }
177
-
178
- /**
179
- * Get SVG icon for tool
180
- * @param {string} toolName - Tool name
181
- * @returns {string} SVG string
182
- * @private
183
- */
184
- _getToolIconSvg(toolName) {
185
- const icons = {
186
- 'pencil': `<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
187
- <path d="M13.3658 4.68008C13.7041 4.34179 13.8943 3.88294 13.8943 3.40447C13.8944 2.926 13.7044 2.4671 13.3661 2.12872C13.0278 1.79035 12.5689 1.60022 12.0905 1.60016C11.612 1.6001 11.1531 1.79011 10.8147 2.1284L2.27329 10.6718C2.12469 10.8199 2.0148 11.0023 1.95329 11.203L1.10785 13.9882C1.09131 14.0436 1.09006 14.1024 1.10423 14.1584C1.11841 14.2144 1.14748 14.2655 1.18836 14.3063C1.22924 14.3471 1.28041 14.3761 1.33643 14.3902C1.39246 14.4043 1.45125 14.403 1.50657 14.3863L4.29249 13.5415C4.49292 13.4806 4.67532 13.3713 4.82369 13.2234L13.3658 4.68008Z" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
188
- <path d="M9.41443 3.52039L11.9744 6.08039" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
189
- </svg>`,
190
- 'eraser': `<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
191
- <path d="M13.2591 12.76H4.93909C4.77032 12.7604 4.60314 12.7274 4.44717 12.663C4.29121 12.5985 4.14953 12.5038 4.03029 12.3844L1.47413 9.825C1.23417 9.58496 1.09937 9.25945 1.09937 8.92004C1.09937 8.58063 1.23417 8.25511 1.47413 8.01508L7.87413 1.61508C7.993 1.49616 8.13413 1.40183 8.28946 1.33747C8.44479 1.27312 8.61128 1.23999 8.77941 1.23999C8.94755 1.23999 9.11404 1.27312 9.26937 1.33747C9.4247 1.40183 9.56583 1.49616 9.68469 1.61508L13.5241 5.45508C13.764 5.69511 13.8988 6.02063 13.8988 6.36004C13.8988 6.69945 13.764 7.02496 13.5241 7.265L8.03285 12.76" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
192
- <path d="M3.07159 6.41772L8.72151 12.0676" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
193
- </svg>`,
194
- 'select': `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
195
- <path d="M1.63448 2.04462C1.60922 1.98633 1.60208 1.92179 1.61397 1.85938C1.62585 1.79697 1.65623 1.73958 1.70116 1.69466C1.74608 1.64973 1.80347 1.61935 1.86588 1.60747C1.92829 1.59558 1.99283 1.60272 2.05112 1.62798L12.2911 5.78798C12.3534 5.81335 12.4061 5.85768 12.4417 5.91469C12.4774 5.9717 12.4941 6.03849 12.4897 6.10557C12.4852 6.17266 12.4597 6.23663 12.4169 6.28842C12.374 6.3402 12.3159 6.37717 12.2508 6.39406L8.33144 7.40526C8.11 7.46219 7.90784 7.5774 7.74599 7.73891C7.58415 7.90042 7.46852 8.10234 7.41112 8.32366L6.40056 12.2443C6.38367 12.3094 6.3467 12.3675 6.29492 12.4104C6.24313 12.4532 6.17916 12.4787 6.11207 12.4832C6.04499 12.4876 5.9782 12.4709 5.92119 12.4352C5.86419 12.3996 5.81985 12.3469 5.79448 12.2846L1.63448 2.04462Z" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
196
- </svg>`
197
- };
198
-
199
- return icons[toolName] || icons['pencil'];
200
- }
201
-
202
- /**
203
- * Update active button styling
204
- * @param {string} toolName - Active tool name
205
- * @private
206
- */
207
- _updateActiveButton(toolName) {
208
- // Reset all buttons
209
- this.buttons.forEach(button => {
210
- button.setFillColor('white');
211
- });
212
-
213
- // Highlight active button
214
- const activeButton = this.buttons.get(toolName);
215
- if (activeButton) {
216
- activeButton.setFillColor(this.omdColor.lightGray);
217
- this.activeButton = activeButton;
218
- } else {
219
- this.activeButton = null;
220
- }
221
- }
222
-
223
- /**
224
- * Add custom button to toolbar
225
- * @param {Object} config - Button configuration
226
- * @param {string} config.id - Button ID
227
- * @param {string} config.icon - SVG icon string
228
- * @param {Function} config.callback - Click callback
229
- * @param {string} [config.tooltip] - Tooltip text
230
- */
231
- addButton(config) {
232
- const button = this._createJsvgButton({ name: config.id }, 48);
233
-
234
- // Set icon
235
- if (config.icon) {
236
- const dataURI = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(config.icon);
237
- button.addImage(dataURI, 24, 24);
238
- }
239
-
240
- // Set click callback
241
- if (config.callback) {
242
- button.setClickCallback(config.callback);
243
- }
244
-
245
- // Add to toolbar group
246
- this.toolbarGroup.addChild(button);
247
-
248
- // Store button
249
- this.buttons.set(config.id, button);
250
-
251
- return button;
252
- }
253
-
254
- /**
255
- * Remove button from toolbar
256
- * @param {string} buttonId - Button ID to remove
257
- */
258
- removeButton(buttonId) {
259
- const button = this.buttons.get(buttonId);
260
- if (button) {
261
- this.toolbarGroup.removeChild(button);
262
- this.buttons.delete(buttonId);
263
- }
264
- }
265
-
266
- /**
267
- * Show toolbar
268
- */
269
- show() {
270
- this.toolbarGroup.svgObject.style.display = 'block';
271
- }
272
-
273
- /**
274
- * Hide toolbar
275
- */
276
- hide() {
277
- this.toolbarGroup.svgObject.style.display = 'none';
278
- }
279
-
280
-
281
-
282
- /**
283
- * Destroy toolbar
284
- */
285
- destroy() {
286
- if (this.toolbarGroup.svgObject.parentNode) {
287
- this.toolbarGroup.svgObject.parentNode.removeChild(this.toolbarGroup.svgObject);
288
- }
289
- this.buttons.clear();
290
- }
1
+ import {omdColor} from '../../src/omdColor.js';
2
+ import { jsvgRect, jsvgGroup } from '@teachinglab/jsvg';
3
+ import { jsvgButton } from '@teachinglab/jsvg';
4
+
5
+ export class Toolbar {
6
+ /**
7
+ * @param {OMDCanvas} canvas - Canvas instance
8
+ */
9
+ constructor(canvas) {
10
+ this.canvas = canvas;
11
+ this.buttons = new Map();
12
+ this.activeButton = null;
13
+ this.omdColor = omdColor; // Use omdColor for consistent styling
14
+
15
+
16
+
17
+ // Create toolbar element
18
+ this._createElement();
19
+
20
+ // Create tool buttons
21
+ this._createToolButtons();
22
+
23
+ // Listen for tool changes
24
+ this.canvas.on('toolChanged', (event) => {
25
+ this._updateActiveButton(event.detail.name);
26
+ });
27
+
28
+
29
+ }
30
+
31
+ /**
32
+ * Create the toolbar SVG element
33
+ * @private
34
+ */
35
+ _createElement() {
36
+
37
+
38
+ // Create a jsvgGroup for the toolbar
39
+ this.toolbarGroup = new jsvgGroup();
40
+
41
+
42
+ // Create background rectangle
43
+ this.background = new jsvgRect();
44
+
45
+
46
+ // Initial size, will be updated after buttons are created
47
+ this.background.setWidthAndHeight(100, 54);
48
+ this.background.setCornerRadius(27); // Pill shape
49
+ this.background.setFillColor(this.omdColor.mediumGray); // Modern dark, semi-transparent
50
+
51
+
52
+
53
+
54
+ // Debug the background SVG object
55
+
56
+
57
+ this.toolbarGroup.addChild(this.background);
58
+
59
+
60
+ // Position the toolbar at bottom center
61
+ this._updatePosition();``
62
+
63
+ // Add to main SVG so it is rendered
64
+ this.canvas.svg.appendChild(this.toolbarGroup.svgObject);
65
+ }
66
+
67
+ /**
68
+ * Update toolbar position to bottom center
69
+ * @private
70
+ */
71
+ _updatePosition() {
72
+ const canvasRect = this.canvas.container.getBoundingClientRect();
73
+ const toolbarWidth = this.background.width;
74
+ const toolbarHeight = this.background.height;
75
+ // Bottom center, 24px from bottom
76
+ const x = (canvasRect.width - toolbarWidth) / 2;
77
+ const y = canvasRect.height - toolbarHeight - 24;
78
+
79
+ // Ensure toolbar stays within canvas bounds
80
+ const clampedX = Math.max(0, Math.min(x, canvasRect.width - toolbarWidth));
81
+ const clampedY = Math.max(0, Math.min(y, canvasRect.height - toolbarHeight));
82
+ this.toolbarGroup.setPosition(x, y);
83
+
84
+ // Debug the SVG object
85
+
86
+ // Set z-index to ensure it's on top
87
+ this.toolbarGroup.svgObject.style.zIndex = '1000';
88
+ this.toolbarGroup.svgObject.style.pointerEvents = 'auto';
89
+
90
+ // Fix the viewBox to include the toolbar position
91
+ const bgWidth = this.background.width;
92
+ const bgHeight = this.background.height;
93
+ const viewBoxX = x;
94
+ const viewBoxY = y;
95
+ const viewBoxWidth = Math.max(500, x + bgWidth);
96
+ const viewBoxHeight = Math.max(500, y + bgHeight);
97
+
98
+ this.toolbarGroup.svgObject.setAttribute('viewBox', `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`);
99
+
100
+ // Don't set x/y attributes - let setPosition handle it via transform
101
+ // this.toolbarGroup.svgObject.setAttribute('x', x);
102
+ // this.toolbarGroup.svgObject.setAttribute('y', y);
103
+ }
104
+
105
+ /**
106
+ * Create tool buttons
107
+ * @private
108
+ */
109
+ _createToolButtons() {
110
+ const tools = this.canvas.toolManager.getAllToolMetadata();
111
+
112
+ const buttonSize = 48;
113
+ const spacing = 8;
114
+ const padding = 6;
115
+ let xPos = padding;
116
+ const yPos = padding;
117
+ tools.forEach(toolMeta => {
118
+ const button = this._createJsvgButton(toolMeta, buttonSize);
119
+ button.setPosition(xPos, yPos);
120
+ this.toolbarGroup.addChild(button);
121
+ this.buttons.set(toolMeta.name, button);
122
+ xPos += buttonSize + spacing;
123
+ });
124
+ // Remove last spacing
125
+ const totalWidth = xPos - spacing + padding;
126
+ const totalHeight = buttonSize + 2 * padding;
127
+ this.background.setWidthAndHeight(totalWidth, totalHeight);
128
+ this.background.setCornerRadius(totalHeight / 2);
129
+ // Reposition after sizing
130
+ this._updatePosition();
131
+
132
+ // Set initial active button
133
+ const activeTool = this.canvas.toolManager.getActiveTool();
134
+ if (activeTool) {
135
+ this._updateActiveButton(activeTool.name);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Create individual tool button using jsvgButton
141
+ * @private
142
+ */
143
+ _createJsvgButton(toolMeta, size = 48) {
144
+
145
+ const button = new jsvgButton();
146
+ button.setWidthAndHeight(size, size);
147
+ button.setCornerRadius(size / 2); // Make it circular
148
+ button.setFillColor('white');
149
+
150
+ // Remove any default text from the button group (if present)
151
+ // jsvgButton may add a <text> element by default; remove it
152
+ button.setText(''); // Clear any default text
153
+
154
+ // Set the icon SVG
155
+ const iconSvg = this._getToolIconSvg(toolMeta.name);
156
+ if (iconSvg) {
157
+ const dataURI = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(iconSvg);
158
+ button.addImage(dataURI, size * 0.5, size * 0.5); // Icon at 50% of button size
159
+ }
160
+
161
+ // Set click callback
162
+ button.setClickCallback(() => {
163
+ this.canvas.toolManager.setActiveTool(toolMeta.name);
164
+ });
165
+
166
+ // Store tool metadata
167
+ button.toolMeta = toolMeta;
168
+ return button;
169
+ }
170
+
171
+ /**
172
+ * Get SVG icon for tool
173
+ * @param {string} toolName - Tool name
174
+ * @returns {string} SVG string
175
+ * @private
176
+ */
177
+ _getToolIconSvg(toolName) {
178
+ const icons = {
179
+ 'pointer': `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
180
+ <path d="M2 1L2 11L5 8L7 12L9 11L7 7L11 7L2 1Z" fill="black" stroke="black" stroke-width="0.8" stroke-linejoin="round"/>
181
+ </svg>`,
182
+ 'pencil': `<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
183
+ <path d="M13.3658 4.68008C13.7041 4.34179 13.8943 3.88294 13.8943 3.40447C13.8944 2.926 13.7044 2.4671 13.3661 2.12872C13.0278 1.79035 12.5689 1.60022 12.0905 1.60016C11.612 1.6001 11.1531 1.79011 10.8147 2.1284L2.27329 10.6718C2.12469 10.8199 2.0148 11.0023 1.95329 11.203L1.10785 13.9882C1.09131 14.0436 1.09006 14.1024 1.10423 14.1584C1.11841 14.2144 1.14748 14.2655 1.18836 14.3063C1.22924 14.3471 1.28041 14.3761 1.33643 14.3902C1.39246 14.4043 1.45125 14.403 1.50657 14.3863L4.29249 13.5415C4.49292 13.4806 4.67532 13.3713 4.82369 13.2234L13.3658 4.68008Z" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
184
+ <path d="M9.41443 3.52039L11.9744 6.08039" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
185
+ </svg>`,
186
+ 'eraser': `<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
187
+ <path d="M13.2591 12.76H4.93909C4.77032 12.7604 4.60314 12.7274 4.44717 12.663C4.29121 12.5985 4.14953 12.5038 4.03029 12.3844L1.47413 9.825C1.23417 9.58496 1.09937 9.25945 1.09937 8.92004C1.09937 8.58063 1.23417 8.25511 1.47413 8.01508L7.87413 1.61508C7.993 1.49616 8.13413 1.40183 8.28946 1.33747C8.44479 1.27312 8.61128 1.23999 8.77941 1.23999C8.94755 1.23999 9.11404 1.27312 9.26937 1.33747C9.4247 1.40183 9.56583 1.49616 9.68469 1.61508L13.5241 5.45508C13.764 5.69511 13.8988 6.02063 13.8988 6.36004C13.8988 6.69945 13.764 7.02496 13.5241 7.265L8.03285 12.76" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
188
+ <path d="M3.07159 6.41772L8.72151 12.0676" stroke="black" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
189
+ </svg>`,
190
+ 'select': `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
191
+ <rect x="1.5" y="1.5" width="6" height="6" stroke="black" stroke-width="1.2" stroke-dasharray="2 2"/>
192
+ <path d="M9 8L12 11M12 11L12 9M12 11L10 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
193
+ <circle cx="2.5" cy="2.5" r="1" fill="black"/>
194
+ <circle cx="6.5" cy="2.5" r="1" fill="black"/>
195
+ <circle cx="2.5" cy="6.5" r="1" fill="black"/>
196
+ <circle cx="6.5" cy="6.5" r="1" fill="black"/>
197
+ </svg>`
198
+ };
199
+
200
+ return icons[toolName] || icons['pencil'];
201
+ }
202
+
203
+ /**
204
+ * Update active button styling
205
+ * @param {string} toolName - Active tool name
206
+ * @private
207
+ */
208
+ _updateActiveButton(toolName) {
209
+ // Reset all buttons
210
+ this.buttons.forEach(button => {
211
+ button.setFillColor('white');
212
+ });
213
+
214
+ // Highlight active button
215
+ const activeButton = this.buttons.get(toolName);
216
+ if (activeButton) {
217
+ activeButton.setFillColor(this.omdColor.lightGray);
218
+ this.activeButton = activeButton;
219
+ } else {
220
+ this.activeButton = null;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Add custom button to toolbar
226
+ * @param {Object} config - Button configuration
227
+ * @param {string} config.id - Button ID
228
+ * @param {string} config.icon - SVG icon string
229
+ * @param {Function} config.callback - Click callback
230
+ * @param {string} [config.tooltip] - Tooltip text
231
+ */
232
+ addButton(config) {
233
+ const button = this._createJsvgButton({ name: config.id }, 48);
234
+
235
+ // Set icon
236
+ if (config.icon) {
237
+ const dataURI = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(config.icon);
238
+ button.addImage(dataURI, 24, 24);
239
+ }
240
+
241
+ // Set click callback
242
+ if (config.callback) {
243
+ button.setClickCallback(config.callback);
244
+ }
245
+
246
+ // Add to toolbar group
247
+ this.toolbarGroup.addChild(button);
248
+
249
+ // Store button
250
+ this.buttons.set(config.id, button);
251
+
252
+ return button;
253
+ }
254
+
255
+ /**
256
+ * Remove button from toolbar
257
+ * @param {string} buttonId - Button ID to remove
258
+ */
259
+ removeButton(buttonId) {
260
+ const button = this.buttons.get(buttonId);
261
+ if (button) {
262
+ this.toolbarGroup.removeChild(button);
263
+ this.buttons.delete(buttonId);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Show toolbar
269
+ */
270
+ show() {
271
+ this.toolbarGroup.svgObject.style.display = 'block';
272
+ }
273
+
274
+ /**
275
+ * Hide toolbar
276
+ */
277
+ hide() {
278
+ this.toolbarGroup.svgObject.style.display = 'none';
279
+ }
280
+
281
+
282
+
283
+ /**
284
+ * Destroy toolbar
285
+ */
286
+ destroy() {
287
+ if (this.toolbarGroup.svgObject.parentNode) {
288
+ this.toolbarGroup.svgObject.parentNode.removeChild(this.toolbarGroup.svgObject);
289
+ }
290
+ this.buttons.clear();
291
+ }
291
292
  }