@elixpo/lixsketch 4.5.8
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.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/fonts/fonts.css +29 -0
- package/fonts/lixCode.ttf +0 -0
- package/fonts/lixDefault.ttf +0 -0
- package/fonts/lixDocs.ttf +0 -0
- package/fonts/lixFancy.ttf +0 -0
- package/fonts/lixFont.woff2 +0 -0
- package/package.json +49 -0
- package/src/SketchEngine.js +473 -0
- package/src/core/AIRenderer.js +1390 -0
- package/src/core/CopyPaste.js +655 -0
- package/src/core/EraserTrail.js +234 -0
- package/src/core/EventDispatcher.js +371 -0
- package/src/core/GraphEngine.js +150 -0
- package/src/core/GraphMathParser.js +231 -0
- package/src/core/GraphRenderer.js +255 -0
- package/src/core/LayerOrder.js +91 -0
- package/src/core/LixScriptParser.js +1299 -0
- package/src/core/MermaidFlowchartRenderer.js +475 -0
- package/src/core/MermaidSequenceParser.js +197 -0
- package/src/core/MermaidSequenceRenderer.js +479 -0
- package/src/core/ResizeCode.js +175 -0
- package/src/core/ResizeShapes.js +318 -0
- package/src/core/SceneSerializer.js +778 -0
- package/src/core/Selection.js +1861 -0
- package/src/core/SnapGuides.js +273 -0
- package/src/core/UndoRedo.js +1358 -0
- package/src/core/ZoomPan.js +258 -0
- package/src/core/ai-system-prompt.js +663 -0
- package/src/index.js +69 -0
- package/src/shapes/Arrow.js +1979 -0
- package/src/shapes/Circle.js +751 -0
- package/src/shapes/CodeShape.js +244 -0
- package/src/shapes/Frame.js +1460 -0
- package/src/shapes/FreehandStroke.js +724 -0
- package/src/shapes/IconShape.js +265 -0
- package/src/shapes/ImageShape.js +270 -0
- package/src/shapes/Line.js +738 -0
- package/src/shapes/Rectangle.js +794 -0
- package/src/shapes/TextShape.js +225 -0
- package/src/tools/arrowTool.js +581 -0
- package/src/tools/circleTool.js +619 -0
- package/src/tools/codeTool.js +2103 -0
- package/src/tools/eraserTool.js +131 -0
- package/src/tools/frameTool.js +241 -0
- package/src/tools/freehandTool.js +620 -0
- package/src/tools/iconTool.js +1344 -0
- package/src/tools/imageTool.js +1323 -0
- package/src/tools/laserTool.js +317 -0
- package/src/tools/lineTool.js +502 -0
- package/src/tools/rectangleTool.js +544 -0
- package/src/tools/textTool.js +1823 -0
- package/src/utils/imageCompressor.js +107 -0
|
@@ -0,0 +1,1344 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Icon tool event handlers - extracted from icons.js
|
|
3
|
+
import { pushCreateAction, pushDeleteAction, pushTransformAction, pushFrameAttachmentAction } from '../core/UndoRedo.js';
|
|
4
|
+
import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
let isDraggingIcon = false;
|
|
8
|
+
let iconToPlace = null;
|
|
9
|
+
let iconX = 0;
|
|
10
|
+
let iconY = 0;
|
|
11
|
+
let scaleFactor = 0.2;
|
|
12
|
+
let currentIconElement = null;
|
|
13
|
+
|
|
14
|
+
let selectedIcon = null;
|
|
15
|
+
let originalX, originalY, originalWidth, originalHeight;
|
|
16
|
+
let currentAnchor = null;
|
|
17
|
+
let isDragging = false;
|
|
18
|
+
let isRotatingIcon = false;
|
|
19
|
+
let dragOffsetX, dragOffsetY;
|
|
20
|
+
let startRotationMouseAngle = null;
|
|
21
|
+
let startIconRotation = null;
|
|
22
|
+
let iconRotation = 0;
|
|
23
|
+
let aspect_ratio_lock = true;
|
|
24
|
+
const minIconSize = 25;
|
|
25
|
+
const miniatureSize = 40;
|
|
26
|
+
const placedIconSize = 40;
|
|
27
|
+
let draggedShapeInitialFrameIcon = null;
|
|
28
|
+
let hoveredFrameIcon = null;
|
|
29
|
+
let _pendingDragChecker = null; // module-level ref so cancelDragPrep can remove it
|
|
30
|
+
|
|
31
|
+
const iconSearchInput = document.getElementById('iconSearchInput') || document.createElement('input');
|
|
32
|
+
let searchTimeout = null;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function getSVGElement() {
|
|
36
|
+
return document.getElementById('freehand-canvas');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function removeSelection() {
|
|
41
|
+
const svg = getSVGElement();
|
|
42
|
+
if (!svg) return;
|
|
43
|
+
|
|
44
|
+
const outline = svg.querySelector(".selection-outline");
|
|
45
|
+
if (outline) {
|
|
46
|
+
svg.removeChild(outline);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
removeResizeAnchors();
|
|
50
|
+
removeRotationAnchor();
|
|
51
|
+
|
|
52
|
+
if (selectedIcon) {
|
|
53
|
+
selectedIcon.removeEventListener('mousedown', startDrag);
|
|
54
|
+
selectedIcon.removeEventListener('mouseup', stopDrag);
|
|
55
|
+
selectedIcon.removeEventListener('mouseleave', stopDrag);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function wrapIconElement(element) {
|
|
60
|
+
const iconShape = new IconShape(element);
|
|
61
|
+
return iconShape;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
document.getElementById("importIcon")?.addEventListener('click', () => {
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const iconContainer = document.getElementById('iconsToolBar');
|
|
68
|
+
if (iconContainer) {
|
|
69
|
+
if(iconContainer.classList.contains('hidden')) {
|
|
70
|
+
iconContainer.classList.remove('hidden');
|
|
71
|
+
iconSearchInput.focus();
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
iconContainer.classList.add('hidden');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function getSVGCoordsFromMouse(e) {
|
|
82
|
+
const svg = getSVGElement();
|
|
83
|
+
const viewBox = svg.viewBox.baseVal;
|
|
84
|
+
const rect = svg.getBoundingClientRect();
|
|
85
|
+
const mouseX = e.clientX - rect.left;
|
|
86
|
+
const mouseY = e.clientY - rect.top;
|
|
87
|
+
const svgX = viewBox.x + (mouseX / rect.width) * viewBox.width;
|
|
88
|
+
const svgY = viewBox.y + (mouseY / rect.height) * viewBox.height;
|
|
89
|
+
return { x: svgX, y: svgY };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const handleMouseMoveIcon = (e) => {
|
|
93
|
+
if (!isDraggingIcon || !iconToPlace || !isIconToolActive) return;
|
|
94
|
+
|
|
95
|
+
const { x, y } = getSVGCoordsFromMouse(e);
|
|
96
|
+
iconX = x;
|
|
97
|
+
iconY = y;
|
|
98
|
+
|
|
99
|
+
drawMiniatureIcon();
|
|
100
|
+
|
|
101
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
102
|
+
shapes.forEach(frame => {
|
|
103
|
+
if (frame.shapeName === 'frame') {
|
|
104
|
+
const tempIconBounds = {
|
|
105
|
+
x: iconX - 50,
|
|
106
|
+
y: iconY - 50,
|
|
107
|
+
width: 100,
|
|
108
|
+
height: 100
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (frame.isShapeInFrame(tempIconBounds)) {
|
|
112
|
+
frame.highlightFrame();
|
|
113
|
+
hoveredFrameIcon = frame;
|
|
114
|
+
if (window.__iconShapeState) window.__iconShapeState.hoveredFrameIcon = frame;
|
|
115
|
+
} else if (hoveredFrameIcon === frame) {
|
|
116
|
+
frame.removeHighlight();
|
|
117
|
+
hoveredFrameIcon = null;
|
|
118
|
+
if (window.__iconShapeState) window.__iconShapeState.hoveredFrameIcon = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const drawMiniatureIcon = () => {
|
|
126
|
+
if (!isDraggingIcon || !iconToPlace || !isIconToolActive) return;
|
|
127
|
+
|
|
128
|
+
const svg = getSVGElement();
|
|
129
|
+
if (!svg) {
|
|
130
|
+
console.error('SVG element not found for miniature icon');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (currentIconElement) {
|
|
135
|
+
svg.removeChild(currentIconElement);
|
|
136
|
+
currentIconElement = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const tempDiv = document.createElement('div');
|
|
140
|
+
tempDiv.innerHTML = iconToPlace;
|
|
141
|
+
const svgElement = tempDiv.querySelector('svg');
|
|
142
|
+
|
|
143
|
+
if (svgElement) {
|
|
144
|
+
const iconGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
145
|
+
|
|
146
|
+
const viewBox = svgElement.getAttribute('viewBox');
|
|
147
|
+
let vbWidth = 24, vbHeight = 24;
|
|
148
|
+
|
|
149
|
+
if (viewBox) {
|
|
150
|
+
const [, , widthStr, heightStr] = viewBox.split(/\s+/);
|
|
151
|
+
vbWidth = parseFloat(widthStr) || 24;
|
|
152
|
+
vbHeight = parseFloat(heightStr) || 24;
|
|
153
|
+
} else {
|
|
154
|
+
const width = svgElement.getAttribute('width');
|
|
155
|
+
const height = svgElement.getAttribute('height');
|
|
156
|
+
if (width && height) {
|
|
157
|
+
vbWidth = parseFloat(width) || 24;
|
|
158
|
+
vbHeight = parseFloat(height) || 24;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const scale = miniatureSize / Math.max(vbWidth, vbHeight);
|
|
163
|
+
iconGroup.setAttribute("transform", `translate(${iconX - miniatureSize / 2}, ${iconY - miniatureSize / 2}) scale(${scale})`);
|
|
164
|
+
|
|
165
|
+
const allChildren = svgElement.children;
|
|
166
|
+
for (let i = 0; i < allChildren.length; i++) {
|
|
167
|
+
const clonedChild = allChildren[i].cloneNode(true);
|
|
168
|
+
|
|
169
|
+
const applyGrayStyle = (element) => {
|
|
170
|
+
if (element.nodeType === 1) {
|
|
171
|
+
element.setAttribute('fill', '#666');
|
|
172
|
+
element.setAttribute('stroke', '#666');
|
|
173
|
+
|
|
174
|
+
for (let j = 0; j < element.children.length; j++) {
|
|
175
|
+
applyGrayStyle(element.children[j]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
applyGrayStyle(clonedChild);
|
|
181
|
+
iconGroup.appendChild(clonedChild);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
iconGroup.setAttribute("style", "pointer-events: none; opacity: 0.7;");
|
|
185
|
+
iconGroup.setAttribute("class", "miniature-icon");
|
|
186
|
+
|
|
187
|
+
currentIconElement = iconGroup;
|
|
188
|
+
svg.appendChild(currentIconElement);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleMouseDownIcon = async (e) => {
|
|
193
|
+
if (isSelectionToolActive) {
|
|
194
|
+
const clickedIcon = e.target.closest('[type="icon"]');
|
|
195
|
+
if (clickedIcon) {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
e.stopPropagation();
|
|
198
|
+
|
|
199
|
+
if (selectedIcon === clickedIcon) {
|
|
200
|
+
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
201
|
+
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
202
|
+
originalWidth = parseFloat(selectedIcon.getAttribute('width')) || placedIconSize;
|
|
203
|
+
originalHeight = parseFloat(selectedIcon.getAttribute('height')) || placedIconSize;
|
|
204
|
+
|
|
205
|
+
const { x, y } = getSVGCoordsFromMouse(e);
|
|
206
|
+
dragOffsetX = x - originalX;
|
|
207
|
+
dragOffsetY = y - originalY;
|
|
208
|
+
|
|
209
|
+
const initialMouseX = x;
|
|
210
|
+
const initialMouseY = y;
|
|
211
|
+
|
|
212
|
+
// Threshold in screen pixels converted to SVG units, so zoom doesn't affect sensitivity
|
|
213
|
+
const svgEl = getSVGElement();
|
|
214
|
+
const svgRect2 = svgEl ? svgEl.getBoundingClientRect() : { width: 1536 };
|
|
215
|
+
const svgViewW = svgEl ? svgEl.viewBox.baseVal.width : 1536;
|
|
216
|
+
const dragThreshold = 12 * (svgViewW / svgRect2.width); // ~12 screen pixels
|
|
217
|
+
|
|
218
|
+
function checkDragStartWithThreshold(moveEvent) {
|
|
219
|
+
const { x: currentX, y: currentY } = getSVGCoordsFromMouse(moveEvent);
|
|
220
|
+
const deltaX = Math.abs(currentX - initialMouseX);
|
|
221
|
+
const deltaY = Math.abs(currentY - initialMouseY);
|
|
222
|
+
|
|
223
|
+
if (deltaX > dragThreshold || deltaY > dragThreshold) {
|
|
224
|
+
document.removeEventListener('mousemove', checkDragStartWithThreshold);
|
|
225
|
+
document.removeEventListener('mouseup', cancelDragPrep);
|
|
226
|
+
window.removeEventListener('mouseup', cancelDragPrep);
|
|
227
|
+
|
|
228
|
+
const svg = getSVGElement();
|
|
229
|
+
if (svg) svg.removeEventListener('mouseup', cancelDragPrep);
|
|
230
|
+
|
|
231
|
+
startDrag(moveEvent);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_pendingDragChecker = checkDragStartWithThreshold;
|
|
236
|
+
document.addEventListener('mousemove', checkDragStartWithThreshold);
|
|
237
|
+
document.addEventListener('mouseup', cancelDragPrep);
|
|
238
|
+
window.addEventListener('mouseup', cancelDragPrep);
|
|
239
|
+
|
|
240
|
+
const svg = getSVGElement();
|
|
241
|
+
if (svg) svg.addEventListener('mouseup', cancelDragPrep);
|
|
242
|
+
|
|
243
|
+
return;
|
|
244
|
+
} else {
|
|
245
|
+
selectIcon(e);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!isDraggingIcon || !iconToPlace || !isIconToolActive) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let placedIconShape = null;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const svg = getSVGElement();
|
|
259
|
+
if (!svg) {
|
|
260
|
+
throw new Error('SVG element not found');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (currentIconElement) {
|
|
264
|
+
svg.removeChild(currentIconElement);
|
|
265
|
+
currentIconElement = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const { x: placedX, y: placedY } = getSVGCoordsFromMouse(e);
|
|
269
|
+
|
|
270
|
+
const tempDiv = document.createElement('div');
|
|
271
|
+
tempDiv.innerHTML = iconToPlace;
|
|
272
|
+
const originalSvgElement = tempDiv.querySelector('svg');
|
|
273
|
+
|
|
274
|
+
if (!originalSvgElement) {
|
|
275
|
+
throw new Error('Invalid SVG content');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const finalIconGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
279
|
+
const finalX = placedX - placedIconSize / 2;
|
|
280
|
+
const finalY = placedY - placedIconSize / 2;
|
|
281
|
+
|
|
282
|
+
const viewBox = originalSvgElement.getAttribute('viewBox');
|
|
283
|
+
let vbWidth = 24, vbHeight = 24;
|
|
284
|
+
|
|
285
|
+
if (viewBox) {
|
|
286
|
+
const [, , widthStr, heightStr] = viewBox.split(/\s+/);
|
|
287
|
+
vbWidth = parseFloat(widthStr) || 24;
|
|
288
|
+
vbHeight = parseFloat(heightStr) || 24;
|
|
289
|
+
} else {
|
|
290
|
+
const width = originalSvgElement.getAttribute('width');
|
|
291
|
+
const height = originalSvgElement.getAttribute('height');
|
|
292
|
+
if (width && height) {
|
|
293
|
+
vbWidth = parseFloat(width) || 24;
|
|
294
|
+
vbHeight = parseFloat(height) || 24;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const scale = placedIconSize / Math.max(vbWidth, vbHeight);
|
|
299
|
+
const localCenterX = placedIconSize / 2 / scale;
|
|
300
|
+
const localCenterY = placedIconSize / 2 / scale;
|
|
301
|
+
finalIconGroup.setAttribute('transform', `translate(${finalX}, ${finalY}) scale(${scale}) rotate(0, ${localCenterX}, ${localCenterY})`);
|
|
302
|
+
finalIconGroup.setAttribute('data-viewbox-width', vbWidth);
|
|
303
|
+
finalIconGroup.setAttribute('data-viewbox-height', vbHeight);
|
|
304
|
+
|
|
305
|
+
const backgroundRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
306
|
+
backgroundRect.setAttribute('x', 0);
|
|
307
|
+
backgroundRect.setAttribute('y', 0);
|
|
308
|
+
backgroundRect.setAttribute('width', vbWidth);
|
|
309
|
+
backgroundRect.setAttribute('height', vbHeight);
|
|
310
|
+
backgroundRect.setAttribute('fill', 'transparent');
|
|
311
|
+
backgroundRect.setAttribute('stroke', 'none');
|
|
312
|
+
backgroundRect.setAttribute('style', 'pointer-events: all; cursor: pointer;');
|
|
313
|
+
finalIconGroup.appendChild(backgroundRect);
|
|
314
|
+
|
|
315
|
+
const allChildren = originalSvgElement.children;
|
|
316
|
+
for (let i = 0; i < allChildren.length; i++) {
|
|
317
|
+
const clonedChild = allChildren[i].cloneNode(true);
|
|
318
|
+
|
|
319
|
+
// Apply white fill/stroke so icons are visible on dark canvas
|
|
320
|
+
const applyWhiteStyle = (element) => {
|
|
321
|
+
if (element.nodeType === 1) {
|
|
322
|
+
const fill = element.getAttribute('fill');
|
|
323
|
+
const stroke = element.getAttribute('stroke');
|
|
324
|
+
// Replace black/dark fills with white; leave 'none'/'transparent' alone
|
|
325
|
+
if (!fill || fill === '#000' || fill === '#000000' || fill === 'black' || fill === 'currentColor') {
|
|
326
|
+
element.setAttribute('fill', '#ffffff');
|
|
327
|
+
}
|
|
328
|
+
if (stroke === '#000' || stroke === '#000000' || stroke === 'black' || stroke === 'currentColor') {
|
|
329
|
+
element.setAttribute('stroke', '#ffffff');
|
|
330
|
+
}
|
|
331
|
+
for (let j = 0; j < element.children.length; j++) {
|
|
332
|
+
applyWhiteStyle(element.children[j]);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
applyWhiteStyle(clonedChild);
|
|
337
|
+
|
|
338
|
+
finalIconGroup.appendChild(clonedChild);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
finalIconGroup.setAttribute('x', finalX);
|
|
342
|
+
finalIconGroup.setAttribute('y', finalY);
|
|
343
|
+
finalIconGroup.setAttribute('width', placedIconSize);
|
|
344
|
+
finalIconGroup.setAttribute('height', placedIconSize);
|
|
345
|
+
finalIconGroup.setAttribute('type', 'icon');
|
|
346
|
+
finalIconGroup.setAttribute('data-shape-x', finalX);
|
|
347
|
+
finalIconGroup.setAttribute('data-shape-y', finalY);
|
|
348
|
+
finalIconGroup.setAttribute('data-shape-width', placedIconSize);
|
|
349
|
+
finalIconGroup.setAttribute('data-shape-height', placedIconSize);
|
|
350
|
+
finalIconGroup.setAttribute('data-shape-rotation', 0);
|
|
351
|
+
finalIconGroup.shapeID = `icon-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;
|
|
352
|
+
finalIconGroup.setAttribute('id', finalIconGroup.shapeID);
|
|
353
|
+
finalIconGroup.setAttribute('style', 'cursor: pointer; pointer-events: all;');
|
|
354
|
+
|
|
355
|
+
svg.appendChild(finalIconGroup);
|
|
356
|
+
|
|
357
|
+
const iconShape = wrapIconElement(finalIconGroup);
|
|
358
|
+
placedIconShape = iconShape;
|
|
359
|
+
|
|
360
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
361
|
+
shapes.push(iconShape);
|
|
362
|
+
console.log('Icon added to shapes array for arrow attachment and frame functionality');
|
|
363
|
+
} else {
|
|
364
|
+
console.warn('shapes array not found - arrows and frames may not work with icons');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const finalFrame = hoveredFrameIcon;
|
|
368
|
+
if (finalFrame) {
|
|
369
|
+
finalFrame.addShapeToFrame(iconShape);
|
|
370
|
+
pushFrameAttachmentAction(finalFrame, iconShape, 'attach', null);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
pushCreateAction(iconShape);
|
|
374
|
+
|
|
375
|
+
// mousedown handles selection; click listener removed to avoid double-selection conflicts
|
|
376
|
+
|
|
377
|
+
if (hoveredFrameIcon) {
|
|
378
|
+
hoveredFrameIcon.removeHighlight();
|
|
379
|
+
hoveredFrameIcon = null;
|
|
380
|
+
if (window.__iconShapeState) window.__iconShapeState.hoveredFrameIcon = null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log('Icon placed successfully:', finalIconGroup);
|
|
384
|
+
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error("Error placing icon:", error);
|
|
387
|
+
isDraggingIcon = false;
|
|
388
|
+
iconToPlace = null;
|
|
389
|
+
} finally {
|
|
390
|
+
isDraggingIcon = false;
|
|
391
|
+
iconToPlace = null;
|
|
392
|
+
document.body.style.cursor = 'default';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Select the placed icon but stay in icon tool so the sidebar remains open
|
|
396
|
+
if (placedIconShape) {
|
|
397
|
+
currentShape = placedIconShape;
|
|
398
|
+
currentShape.isSelected = true;
|
|
399
|
+
requestAnimationFrame(() => {
|
|
400
|
+
placedIconShape.selectShape();
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const handleMouseUpIcon = (e) => {
|
|
406
|
+
if (isSelectionToolActive) {
|
|
407
|
+
const clickedElement = e.target;
|
|
408
|
+
const isIconElement = clickedElement.closest('[type="icon"]');
|
|
409
|
+
const isAnchorElement = clickedElement.classList.contains('resize-anchor') ||
|
|
410
|
+
clickedElement.classList.contains('rotation-anchor') ||
|
|
411
|
+
clickedElement.classList.contains('selection-outline');
|
|
412
|
+
|
|
413
|
+
if (!isIconElement && !isAnchorElement && selectedIcon) {
|
|
414
|
+
removeSelection();
|
|
415
|
+
selectedIcon = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (hoveredFrameIcon) {
|
|
420
|
+
hoveredFrameIcon.removeHighlight();
|
|
421
|
+
hoveredFrameIcon = null;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
function addSelectionOutline() {
|
|
426
|
+
if (!selectedIcon) return;
|
|
427
|
+
|
|
428
|
+
const svg = getSVGElement();
|
|
429
|
+
if (!svg) return;
|
|
430
|
+
|
|
431
|
+
// Use stored SVG-space data attributes — reliable even immediately after DOM insertion
|
|
432
|
+
const x = parseFloat(selectedIcon.getAttribute('data-shape-x')) || 0;
|
|
433
|
+
const y = parseFloat(selectedIcon.getAttribute('data-shape-y')) || 0;
|
|
434
|
+
const width = parseFloat(selectedIcon.getAttribute('data-shape-width')) || 40;
|
|
435
|
+
const height = parseFloat(selectedIcon.getAttribute('data-shape-height')) || 40;
|
|
436
|
+
const rotation = parseFloat(selectedIcon.getAttribute('data-shape-rotation')) || 0;
|
|
437
|
+
|
|
438
|
+
const centerX = x + width / 2;
|
|
439
|
+
const centerY = y + height / 2;
|
|
440
|
+
|
|
441
|
+
const selectionPadding = Math.max(4, width * 0.08);
|
|
442
|
+
const expandedX = x - selectionPadding;
|
|
443
|
+
const expandedY = y - selectionPadding;
|
|
444
|
+
const expandedWidth = width + 2 * selectionPadding;
|
|
445
|
+
const expandedHeight = height + 2 * selectionPadding;
|
|
446
|
+
|
|
447
|
+
removeSelection();
|
|
448
|
+
|
|
449
|
+
const outline = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
450
|
+
outline.setAttribute("x", expandedX);
|
|
451
|
+
outline.setAttribute("y", expandedY);
|
|
452
|
+
outline.setAttribute("width", expandedWidth);
|
|
453
|
+
outline.setAttribute("height", expandedHeight);
|
|
454
|
+
outline.setAttribute("fill", "none");
|
|
455
|
+
outline.setAttribute("stroke", "#5B57D1");
|
|
456
|
+
outline.setAttribute("stroke-width", 1.5);
|
|
457
|
+
outline.setAttribute("stroke-dasharray", "4 3");
|
|
458
|
+
outline.setAttribute("style", "pointer-events: none;");
|
|
459
|
+
outline.setAttribute("class", "selection-outline");
|
|
460
|
+
if (rotation !== 0) {
|
|
461
|
+
outline.setAttribute("transform", `rotate(${rotation}, ${centerX}, ${centerY})`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
svg.appendChild(outline);
|
|
465
|
+
|
|
466
|
+
addResizeAnchors(expandedX, expandedY, expandedWidth, expandedHeight, centerX, centerY, width, rotation);
|
|
467
|
+
addRotationAnchor(expandedX, expandedY, expandedWidth, expandedHeight, centerX, centerY, width, rotation);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function checkDragStart(event) {
|
|
471
|
+
document.removeEventListener('mousemove', checkDragStart);
|
|
472
|
+
document.removeEventListener('mouseup', cancelDragPrep);
|
|
473
|
+
startDrag(event);
|
|
474
|
+
}
|
|
475
|
+
function cancelDragPrep(event) {
|
|
476
|
+
if (_pendingDragChecker) {
|
|
477
|
+
document.removeEventListener('mousemove', _pendingDragChecker);
|
|
478
|
+
_pendingDragChecker = null;
|
|
479
|
+
}
|
|
480
|
+
document.removeEventListener('mouseup', cancelDragPrep);
|
|
481
|
+
window.removeEventListener('mouseup', cancelDragPrep);
|
|
482
|
+
|
|
483
|
+
const svg = getSVGElement();
|
|
484
|
+
if (svg) {
|
|
485
|
+
svg.removeEventListener('mouseup', cancelDragPrep);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function startDrag(event) {
|
|
490
|
+
console.log("start dragging icon");
|
|
491
|
+
if (!isSelectionToolActive || !selectedIcon) return;
|
|
492
|
+
|
|
493
|
+
isDragging = true;
|
|
494
|
+
if (window.__iconShapeState) window.__iconShapeState.isDragging = true;
|
|
495
|
+
|
|
496
|
+
let iconShape = null;
|
|
497
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
498
|
+
iconShape = shapes.find(shape => shape.shapeName === 'icon' && shape.element === selectedIcon);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (iconShape) {
|
|
502
|
+
draggedShapeInitialFrameIcon = iconShape.parentFrame || null;
|
|
503
|
+
|
|
504
|
+
if (iconShape.parentFrame) {
|
|
505
|
+
iconShape.parentFrame.temporarilyRemoveFromFrame(iconShape);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
document.addEventListener('mousemove', dragIcon);
|
|
510
|
+
document.addEventListener('mouseup', stopDrag);
|
|
511
|
+
|
|
512
|
+
window.addEventListener('mouseup', stopDrag);
|
|
513
|
+
|
|
514
|
+
const svg = getSVGElement();
|
|
515
|
+
if (svg) {
|
|
516
|
+
svg.addEventListener('mouseup', stopDrag);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
document.addEventListener('dragstart', preventDefaultDrag);
|
|
520
|
+
document.addEventListener('selectstart', preventDefaultDrag);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function preventDefaultDrag(e) {
|
|
524
|
+
e.preventDefault();
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function selectIcon(event) {
|
|
529
|
+
if (!isSelectionToolActive) return;
|
|
530
|
+
|
|
531
|
+
event.stopPropagation();
|
|
532
|
+
|
|
533
|
+
let targetIcon = event.target.closest('[type="icon"]');
|
|
534
|
+
if (!targetIcon) {
|
|
535
|
+
let current = event.target;
|
|
536
|
+
while (current && current !== document) {
|
|
537
|
+
if (current.getAttribute && current.getAttribute('type') === 'icon') {
|
|
538
|
+
targetIcon = current;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
current = current.parentElement;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (selectedIcon !== targetIcon) {
|
|
546
|
+
if (selectedIcon) {
|
|
547
|
+
removeSelection();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
selectedIcon = targetIcon;
|
|
551
|
+
|
|
552
|
+
if (!selectedIcon) {
|
|
553
|
+
console.warn('Could not find icon to select');
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const transform = selectedIcon.getAttribute('transform');
|
|
558
|
+
if (transform) {
|
|
559
|
+
const rotateMatch = transform.match(/rotate\(([^,\s]+)/);
|
|
560
|
+
if (rotateMatch) {
|
|
561
|
+
iconRotation = parseFloat(rotateMatch[1]);
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
iconRotation = 0;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
addSelectionOutline();
|
|
568
|
+
|
|
569
|
+
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
570
|
+
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
571
|
+
originalWidth = parseFloat(selectedIcon.getAttribute('width')) || placedIconSize;
|
|
572
|
+
originalHeight = parseFloat(selectedIcon.getAttribute('height')) || placedIconSize;
|
|
573
|
+
|
|
574
|
+
// Set currentShape so EventDispatcher routes subsequent events to icon handler
|
|
575
|
+
const iconShape = (typeof shapes !== 'undefined' && Array.isArray(shapes))
|
|
576
|
+
? shapes.find(s => s.shapeName === 'icon' && s.element === selectedIcon)
|
|
577
|
+
: null;
|
|
578
|
+
if (iconShape) {
|
|
579
|
+
currentShape = iconShape;
|
|
580
|
+
currentShape.isSelected = true;
|
|
581
|
+
if (window.__showSidebarForShape) window.__showSidebarForShape('icon');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function addResizeAnchors(x, y, width, height, centerX, centerY, iconWidth, rotation) {
|
|
587
|
+
const svg = getSVGElement();
|
|
588
|
+
if (!svg) return;
|
|
589
|
+
|
|
590
|
+
const zoom = window.currentZoom || 1;
|
|
591
|
+
const anchorSize = Math.max(8, Math.min(16, iconWidth * 0.15)) / zoom;
|
|
592
|
+
const anchorStrokeWidth = Math.max(1.5, anchorSize * 0.15);
|
|
593
|
+
|
|
594
|
+
const positions = [
|
|
595
|
+
{ x: x, y: y, cursor: "nw-resize" },
|
|
596
|
+
{ x: x + width, y: y, cursor: "ne-resize" },
|
|
597
|
+
{ x: x, y: y + height, cursor: "sw-resize" },
|
|
598
|
+
{ x: x + width, y: y + height, cursor: "se-resize" }
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
positions.forEach((pos) => {
|
|
602
|
+
const anchor = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
603
|
+
anchor.setAttribute("x", pos.x - anchorSize / 2);
|
|
604
|
+
anchor.setAttribute("y", pos.y - anchorSize / 2);
|
|
605
|
+
anchor.setAttribute("width", anchorSize);
|
|
606
|
+
anchor.setAttribute("height", anchorSize);
|
|
607
|
+
anchor.setAttribute("fill", "#121212");
|
|
608
|
+
anchor.setAttribute("stroke", "#5B57D1");
|
|
609
|
+
anchor.setAttribute("stroke-width", anchorStrokeWidth);
|
|
610
|
+
anchor.setAttribute("class", "resize-anchor");
|
|
611
|
+
anchor.setAttribute("transform", `rotate(${rotation}, ${centerX}, ${centerY})`);
|
|
612
|
+
anchor.style.cursor = pos.cursor;
|
|
613
|
+
|
|
614
|
+
svg.appendChild(anchor);
|
|
615
|
+
|
|
616
|
+
anchor.addEventListener('mousedown', startResize);
|
|
617
|
+
anchor.addEventListener('mouseup', stopResize);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function addRotationAnchor(x, y, width, height, centerX, centerY, iconWidth, rotation) {
|
|
622
|
+
const svg = getSVGElement();
|
|
623
|
+
if (!svg) return;
|
|
624
|
+
|
|
625
|
+
const anchorRadius = Math.max(6, Math.min(12, iconWidth * 0.12));
|
|
626
|
+
const anchorStrokeWidth = Math.max(1.5, anchorRadius * 0.2);
|
|
627
|
+
const rotationDistance = Math.max(25, iconWidth * 0.4);
|
|
628
|
+
|
|
629
|
+
const rotationAnchorX = x + width / 2;
|
|
630
|
+
const rotationAnchorY = y - rotationDistance;
|
|
631
|
+
|
|
632
|
+
const rotationAnchor = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
633
|
+
rotationAnchor.setAttribute('cx', rotationAnchorX);
|
|
634
|
+
rotationAnchor.setAttribute('cy', rotationAnchorY);
|
|
635
|
+
rotationAnchor.setAttribute('r', anchorRadius);
|
|
636
|
+
rotationAnchor.setAttribute('class', 'rotation-anchor');
|
|
637
|
+
rotationAnchor.setAttribute('fill', '#121212');
|
|
638
|
+
rotationAnchor.setAttribute('stroke', '#5B57D1');
|
|
639
|
+
rotationAnchor.setAttribute('stroke-width', anchorStrokeWidth);
|
|
640
|
+
rotationAnchor.setAttribute('style', 'pointer-events: all; cursor: grab;');
|
|
641
|
+
rotationAnchor.setAttribute('transform', `rotate(${rotation}, ${centerX}, ${centerY})`);
|
|
642
|
+
|
|
643
|
+
svg.appendChild(rotationAnchor);
|
|
644
|
+
|
|
645
|
+
rotationAnchor.addEventListener('mousedown', startRotation);
|
|
646
|
+
rotationAnchor.addEventListener('mouseup', stopRotation);
|
|
647
|
+
|
|
648
|
+
rotationAnchor.addEventListener('mouseover', function() {
|
|
649
|
+
if (!isRotatingIcon && !isDragging) {
|
|
650
|
+
rotationAnchor.style.cursor = 'grab';
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
rotationAnchor.addEventListener('mouseout', function() {
|
|
655
|
+
if (!isRotatingIcon && !isDragging) {
|
|
656
|
+
rotationAnchor.style.cursor = 'default';
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function removeRotationAnchor() {
|
|
662
|
+
const svg = getSVGElement();
|
|
663
|
+
if (!svg) return;
|
|
664
|
+
|
|
665
|
+
const rotationAnchor = svg.querySelector(".rotation-anchor");
|
|
666
|
+
if (rotationAnchor) {
|
|
667
|
+
svg.removeChild(rotationAnchor);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function removeResizeAnchors() {
|
|
672
|
+
const svg = getSVGElement();
|
|
673
|
+
if (!svg) return;
|
|
674
|
+
|
|
675
|
+
const anchors = svg.querySelectorAll(".resize-anchor");
|
|
676
|
+
anchors.forEach(anchor => svg.removeChild(anchor));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function startResize(event) {
|
|
680
|
+
event.preventDefault();
|
|
681
|
+
event.stopPropagation();
|
|
682
|
+
|
|
683
|
+
currentAnchor = event.target;
|
|
684
|
+
|
|
685
|
+
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
686
|
+
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
687
|
+
originalWidth = parseFloat(selectedIcon.getAttribute('width')) || 100;
|
|
688
|
+
originalHeight = parseFloat(selectedIcon.getAttribute('height')) || 100;
|
|
689
|
+
|
|
690
|
+
const { x: mouseX, y: mouseY } = getSVGCoordsFromMouse(event);
|
|
691
|
+
currentAnchor.startMouseX = mouseX;
|
|
692
|
+
currentAnchor.startMouseY = mouseY;
|
|
693
|
+
|
|
694
|
+
currentAnchor.iconRotation = iconRotation;
|
|
695
|
+
|
|
696
|
+
const svg = getSVGElement();
|
|
697
|
+
if (svg) {
|
|
698
|
+
svg.addEventListener('mousemove', resizeIcon);
|
|
699
|
+
}
|
|
700
|
+
document.addEventListener('mouseup', stopResize);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function stopResize(event) {
|
|
704
|
+
stopInteracting();
|
|
705
|
+
document.removeEventListener('mouseup', stopResize);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function resizeIcon(event) {
|
|
709
|
+
if (!selectedIcon || !currentAnchor) return;
|
|
710
|
+
|
|
711
|
+
const { x: currentMouseX, y: currentMouseY } = getSVGCoordsFromMouse(event);
|
|
712
|
+
|
|
713
|
+
const rotation = currentAnchor.iconRotation || 0;
|
|
714
|
+
const rotationRad = (rotation * Math.PI) / 180;
|
|
715
|
+
|
|
716
|
+
const rawDeltaX = currentMouseX - currentAnchor.startMouseX;
|
|
717
|
+
const rawDeltaY = currentMouseY - currentAnchor.startMouseY;
|
|
718
|
+
|
|
719
|
+
// Rotate delta to local (unrotated) space
|
|
720
|
+
const deltaX = rawDeltaX * Math.cos(-rotationRad) - rawDeltaY * Math.sin(-rotationRad);
|
|
721
|
+
const deltaY = rawDeltaX * Math.sin(-rotationRad) + rawDeltaY * Math.cos(-rotationRad);
|
|
722
|
+
|
|
723
|
+
let newWidth = originalWidth;
|
|
724
|
+
let newHeight = originalHeight;
|
|
725
|
+
let newX = originalX;
|
|
726
|
+
let newY = originalY;
|
|
727
|
+
|
|
728
|
+
// Track which corner is fixed (relative to original rect)
|
|
729
|
+
let fixedRelX, fixedRelY;
|
|
730
|
+
|
|
731
|
+
switch (currentAnchor.style.cursor) {
|
|
732
|
+
case "nw-resize":
|
|
733
|
+
newWidth = Math.max(minIconSize, originalWidth - deltaX);
|
|
734
|
+
newHeight = Math.max(minIconSize, originalHeight - deltaY);
|
|
735
|
+
if (aspect_ratio_lock) {
|
|
736
|
+
const scale = Math.min(newWidth / originalWidth, newHeight / originalHeight);
|
|
737
|
+
newWidth = originalWidth * scale;
|
|
738
|
+
newHeight = originalHeight * scale;
|
|
739
|
+
}
|
|
740
|
+
newX = originalX + (originalWidth - newWidth);
|
|
741
|
+
newY = originalY + (originalHeight - newHeight);
|
|
742
|
+
fixedRelX = originalWidth; fixedRelY = originalHeight;
|
|
743
|
+
break;
|
|
744
|
+
case "ne-resize":
|
|
745
|
+
newWidth = Math.max(minIconSize, originalWidth + deltaX);
|
|
746
|
+
newHeight = Math.max(minIconSize, originalHeight - deltaY);
|
|
747
|
+
if (aspect_ratio_lock) {
|
|
748
|
+
const scale = Math.max(newWidth / originalWidth, newHeight / originalHeight);
|
|
749
|
+
newWidth = originalWidth * scale;
|
|
750
|
+
newHeight = originalHeight * scale;
|
|
751
|
+
}
|
|
752
|
+
newX = originalX;
|
|
753
|
+
newY = originalY + (originalHeight - newHeight);
|
|
754
|
+
fixedRelX = 0; fixedRelY = originalHeight;
|
|
755
|
+
break;
|
|
756
|
+
case "sw-resize":
|
|
757
|
+
newWidth = Math.max(minIconSize, originalWidth - deltaX);
|
|
758
|
+
newHeight = Math.max(minIconSize, originalHeight + deltaY);
|
|
759
|
+
if (aspect_ratio_lock) {
|
|
760
|
+
const scale = Math.max(newWidth / originalWidth, newHeight / originalHeight);
|
|
761
|
+
newWidth = originalWidth * scale;
|
|
762
|
+
newHeight = originalHeight * scale;
|
|
763
|
+
}
|
|
764
|
+
newX = originalX + (originalWidth - newWidth);
|
|
765
|
+
newY = originalY;
|
|
766
|
+
fixedRelX = originalWidth; fixedRelY = 0;
|
|
767
|
+
break;
|
|
768
|
+
case "se-resize":
|
|
769
|
+
newWidth = Math.max(minIconSize, originalWidth + deltaX);
|
|
770
|
+
newHeight = Math.max(minIconSize, originalHeight + deltaY);
|
|
771
|
+
if (aspect_ratio_lock) {
|
|
772
|
+
const scale = Math.max(newWidth / originalWidth, newHeight / originalHeight);
|
|
773
|
+
newWidth = originalWidth * scale;
|
|
774
|
+
newHeight = originalHeight * scale;
|
|
775
|
+
}
|
|
776
|
+
newX = originalX;
|
|
777
|
+
newY = originalY;
|
|
778
|
+
fixedRelX = 0; fixedRelY = 0;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Compensate for rotation center shift when rotated
|
|
783
|
+
if (rotation !== 0) {
|
|
784
|
+
const cosR = Math.cos(rotationRad);
|
|
785
|
+
const sinR = Math.sin(rotationRad);
|
|
786
|
+
|
|
787
|
+
// Compute rotated world position of the fixed corner in the original rect
|
|
788
|
+
const oldCX = originalX + originalWidth / 2;
|
|
789
|
+
const oldCY = originalY + originalHeight / 2;
|
|
790
|
+
const odx = (originalX + fixedRelX) - oldCX;
|
|
791
|
+
const ody = (originalY + fixedRelY) - oldCY;
|
|
792
|
+
const fixedWorldX = oldCX + odx * cosR - ody * sinR;
|
|
793
|
+
const fixedWorldY = oldCY + odx * sinR + ody * cosR;
|
|
794
|
+
|
|
795
|
+
// Determine where the fixed corner sits in the new rect
|
|
796
|
+
const fixedNewRelX = fixedRelX === 0 ? 0 : newWidth;
|
|
797
|
+
const fixedNewRelY = fixedRelY === 0 ? 0 : newHeight;
|
|
798
|
+
|
|
799
|
+
// Solve for new origin so the fixed corner stays in place
|
|
800
|
+
const ncx = newWidth / 2;
|
|
801
|
+
const ncy = newHeight / 2;
|
|
802
|
+
const ndx = fixedNewRelX - ncx;
|
|
803
|
+
const ndy = fixedNewRelY - ncy;
|
|
804
|
+
const rotX = ncx + ndx * cosR - ndy * sinR;
|
|
805
|
+
const rotY = ncy + ndx * sinR + ndy * cosR;
|
|
806
|
+
|
|
807
|
+
newX = fixedWorldX - rotX;
|
|
808
|
+
newY = fixedWorldY - rotY;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
selectedIcon.setAttribute('width', newWidth);
|
|
812
|
+
selectedIcon.setAttribute('height', newHeight);
|
|
813
|
+
selectedIcon.setAttribute('x', newX);
|
|
814
|
+
selectedIcon.setAttribute('y', newY);
|
|
815
|
+
|
|
816
|
+
selectedIcon.setAttribute('data-shape-x', newX);
|
|
817
|
+
selectedIcon.setAttribute('data-shape-y', newY);
|
|
818
|
+
selectedIcon.setAttribute('data-shape-width', newWidth);
|
|
819
|
+
selectedIcon.setAttribute('data-shape-height', newHeight);
|
|
820
|
+
|
|
821
|
+
const vbWidth = parseFloat(selectedIcon.getAttribute('data-viewbox-width')) || 24;
|
|
822
|
+
const vbHeight = parseFloat(selectedIcon.getAttribute('data-viewbox-height')) || 24;
|
|
823
|
+
const iconScale = newWidth / Math.max(vbWidth, vbHeight);
|
|
824
|
+
const localCenterX = newWidth / 2 / iconScale;
|
|
825
|
+
const localCenterY = newHeight / 2 / iconScale;
|
|
826
|
+
selectedIcon.setAttribute('transform', `translate(${newX}, ${newY}) scale(${iconScale}) rotate(${iconRotation}, ${localCenterX}, ${localCenterY})`);
|
|
827
|
+
|
|
828
|
+
updateArrowsForShape(selectedIcon);
|
|
829
|
+
|
|
830
|
+
addSelectionOutline();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function dragIcon(event) {
|
|
834
|
+
if (!isDragging || !selectedIcon) return;
|
|
835
|
+
|
|
836
|
+
const { x, y } = getSVGCoordsFromMouse(event);
|
|
837
|
+
let newX = x - dragOffsetX;
|
|
838
|
+
let newY = y - dragOffsetY;
|
|
839
|
+
|
|
840
|
+
selectedIcon.setAttribute('x', newX);
|
|
841
|
+
selectedIcon.setAttribute('y', newY);
|
|
842
|
+
|
|
843
|
+
selectedIcon.setAttribute('data-shape-x', newX);
|
|
844
|
+
selectedIcon.setAttribute('data-shape-y', newY);
|
|
845
|
+
|
|
846
|
+
const width = parseFloat(selectedIcon.getAttribute('width')) || 100;
|
|
847
|
+
const height = parseFloat(selectedIcon.getAttribute('height')) || 100;
|
|
848
|
+
const vbWidth = parseFloat(selectedIcon.getAttribute('data-viewbox-width')) || 24;
|
|
849
|
+
const vbHeight = parseFloat(selectedIcon.getAttribute('data-viewbox-height')) || 24;
|
|
850
|
+
const scale = width / Math.max(vbWidth, vbHeight);
|
|
851
|
+
const localCenterX = width / 2 / scale;
|
|
852
|
+
const localCenterY = height / 2 / scale;
|
|
853
|
+
|
|
854
|
+
selectedIcon.setAttribute('transform', `translate(${newX}, ${newY}) scale(${scale}) rotate(${iconRotation}, ${localCenterX}, ${localCenterY})`);
|
|
855
|
+
|
|
856
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
857
|
+
const iconShape = shapes.find(shape => shape.shapeName === 'icon' && shape.element === selectedIcon);
|
|
858
|
+
if (iconShape) {
|
|
859
|
+
iconShape.x = newX;
|
|
860
|
+
iconShape.y = newY;
|
|
861
|
+
iconShape.updateFrameContainment();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
updateArrowsForShape(selectedIcon);
|
|
866
|
+
|
|
867
|
+
removeSelection();
|
|
868
|
+
addSelectionOutline();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function stopDrag(event) {
|
|
872
|
+
if (!isDragging) return;
|
|
873
|
+
|
|
874
|
+
console.log("stop dragging icon");
|
|
875
|
+
isDragging = false;
|
|
876
|
+
if (window.__iconShapeState) window.__iconShapeState.isDragging = false;
|
|
877
|
+
|
|
878
|
+
document.removeEventListener('mousemove', dragIcon);
|
|
879
|
+
document.removeEventListener('mouseup', stopDrag);
|
|
880
|
+
window.removeEventListener('mouseup', stopDrag);
|
|
881
|
+
|
|
882
|
+
const svg = getSVGElement();
|
|
883
|
+
if (svg) {
|
|
884
|
+
svg.removeEventListener('mouseup', stopDrag);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
document.removeEventListener('dragstart', preventDefaultDrag);
|
|
888
|
+
document.removeEventListener('selectstart', preventDefaultDrag);
|
|
889
|
+
|
|
890
|
+
stopInteracting();
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function startRotation(event) {
|
|
894
|
+
event.preventDefault();
|
|
895
|
+
event.stopPropagation();
|
|
896
|
+
|
|
897
|
+
if (!selectedIcon) return;
|
|
898
|
+
|
|
899
|
+
isRotatingIcon = true;
|
|
900
|
+
console.log(selectedIcon)
|
|
901
|
+
const iconX = parseFloat(selectedIcon.getAttribute('x'));
|
|
902
|
+
const iconY = parseFloat(selectedIcon.getAttribute('y'));
|
|
903
|
+
const iconWidth = parseFloat(selectedIcon.getAttribute('width'));
|
|
904
|
+
const iconHeight = parseFloat(selectedIcon.getAttribute('height'));
|
|
905
|
+
|
|
906
|
+
const centerX = iconX + iconWidth / 2;
|
|
907
|
+
const centerY = iconY + iconHeight / 2;
|
|
908
|
+
|
|
909
|
+
const { x: mouseX, y: mouseY } = getSVGCoordsFromMouse(event);
|
|
910
|
+
|
|
911
|
+
startRotationMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
|
|
912
|
+
console.log('Start rotation mouse angle:', startRotationMouseAngle);
|
|
913
|
+
startIconRotation = iconRotation;
|
|
914
|
+
|
|
915
|
+
const svg = getSVGElement();
|
|
916
|
+
if (svg) {
|
|
917
|
+
svg.addEventListener('mousemove', rotateIcon);
|
|
918
|
+
svg.style.cursor = 'grabbing';
|
|
919
|
+
}
|
|
920
|
+
document.addEventListener('mouseup', stopRotation);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function rotateIcon(event) {
|
|
924
|
+
if (!isRotatingIcon || !selectedIcon) return;
|
|
925
|
+
|
|
926
|
+
const iconX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
927
|
+
const iconY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
928
|
+
const iconWidth = parseFloat(selectedIcon.getAttribute('width')) || 100;
|
|
929
|
+
const iconHeight = parseFloat(selectedIcon.getAttribute('height')) || 100;
|
|
930
|
+
|
|
931
|
+
const centerX = iconX + iconWidth / 2;
|
|
932
|
+
const centerY = iconY + iconHeight / 2;
|
|
933
|
+
|
|
934
|
+
const { x: mouseX, y: mouseY } = getSVGCoordsFromMouse(event);
|
|
935
|
+
|
|
936
|
+
const currentMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
|
|
937
|
+
const angleDiff = currentMouseAngle - startRotationMouseAngle;
|
|
938
|
+
|
|
939
|
+
iconRotation = startIconRotation + angleDiff;
|
|
940
|
+
iconRotation = iconRotation % 360;
|
|
941
|
+
if (iconRotation < 0) iconRotation += 360;
|
|
942
|
+
|
|
943
|
+
const vbWidth = parseFloat(selectedIcon.getAttribute('data-viewbox-width')) || 24;
|
|
944
|
+
const vbHeight = parseFloat(selectedIcon.getAttribute('data-viewbox-height')) || 24;
|
|
945
|
+
const scale = iconWidth / Math.max(vbWidth, vbHeight);
|
|
946
|
+
const localCenterX = iconWidth / 2 / scale;
|
|
947
|
+
const localCenterY = iconHeight / 2 / scale;
|
|
948
|
+
|
|
949
|
+
selectedIcon.setAttribute('transform', `translate(${iconX}, ${iconY}) scale(${scale}) rotate(${iconRotation}, ${localCenterX}, ${localCenterY})`);
|
|
950
|
+
|
|
951
|
+
selectedIcon.setAttribute('data-shape-rotation', iconRotation);
|
|
952
|
+
|
|
953
|
+
updateArrowsForShape(selectedIcon);
|
|
954
|
+
|
|
955
|
+
removeSelection();
|
|
956
|
+
addSelectionOutline();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function stopRotation(event) {
|
|
960
|
+
if (!isRotatingIcon) return;
|
|
961
|
+
stopInteracting();
|
|
962
|
+
isRotatingIcon = false;
|
|
963
|
+
startRotationMouseAngle = null;
|
|
964
|
+
startIconRotation = null;
|
|
965
|
+
|
|
966
|
+
const svg = getSVGElement();
|
|
967
|
+
if (svg) {
|
|
968
|
+
svg.removeEventListener('mousemove', rotateIcon);
|
|
969
|
+
svg.style.cursor = 'default';
|
|
970
|
+
}
|
|
971
|
+
document.removeEventListener('mouseup', stopRotation);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function stopInteracting() {
|
|
975
|
+
if (selectedIcon && (isDragging || isRotatingIcon || currentAnchor)) {
|
|
976
|
+
const newPos = {
|
|
977
|
+
x: parseFloat(selectedIcon.getAttribute('x')) || 0,
|
|
978
|
+
y: parseFloat(selectedIcon.getAttribute('y')) || 0,
|
|
979
|
+
width: parseFloat(selectedIcon.getAttribute('width')) || 100,
|
|
980
|
+
height: parseFloat(selectedIcon.getAttribute('height')) || 100,
|
|
981
|
+
rotation: iconRotation
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
let originalRotation = iconRotation;
|
|
985
|
+
if (isRotatingIcon && startIconRotation !== null) {
|
|
986
|
+
originalRotation = startIconRotation;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const oldPos = {
|
|
990
|
+
x: originalX,
|
|
991
|
+
y: originalY,
|
|
992
|
+
width: originalWidth,
|
|
993
|
+
height: originalHeight,
|
|
994
|
+
rotation: originalRotation
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
let iconShape = null;
|
|
998
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
999
|
+
iconShape = shapes.find(shape => shape.shapeName === 'icon' && shape.element === selectedIcon);
|
|
1000
|
+
if (iconShape) {
|
|
1001
|
+
iconShape.x = newPos.x;
|
|
1002
|
+
iconShape.y = newPos.y;
|
|
1003
|
+
iconShape.width = newPos.width;
|
|
1004
|
+
iconShape.height = newPos.height;
|
|
1005
|
+
iconShape.rotation = newPos.rotation;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const oldPosWithFrame = {
|
|
1010
|
+
...oldPos,
|
|
1011
|
+
parentFrame: draggedShapeInitialFrameIcon
|
|
1012
|
+
};
|
|
1013
|
+
const newPosWithFrame = {
|
|
1014
|
+
...newPos,
|
|
1015
|
+
parentFrame: iconShape ? iconShape.parentFrame : null
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const stateChanged = newPos.x !== oldPos.x || newPos.y !== oldPos.y ||
|
|
1019
|
+
newPos.width !== oldPos.width || newPos.height !== oldPos.height ||
|
|
1020
|
+
newPos.rotation !== oldPos.rotation;
|
|
1021
|
+
const frameChanged = oldPosWithFrame.parentFrame !== newPosWithFrame.parentFrame;
|
|
1022
|
+
|
|
1023
|
+
if ((stateChanged || frameChanged) && iconShape) {
|
|
1024
|
+
pushTransformAction(iconShape, oldPosWithFrame, newPosWithFrame);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (isDragging && iconShape) {
|
|
1028
|
+
const finalFrame = hoveredFrameIcon;
|
|
1029
|
+
|
|
1030
|
+
if (draggedShapeInitialFrameIcon !== finalFrame) {
|
|
1031
|
+
if (draggedShapeInitialFrameIcon) {
|
|
1032
|
+
draggedShapeInitialFrameIcon.removeShapeFromFrame(iconShape);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (finalFrame) {
|
|
1036
|
+
finalFrame.addShapeToFrame(iconShape);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (frameChanged) {
|
|
1040
|
+
pushFrameAttachmentAction(finalFrame || draggedShapeInitialFrameIcon, iconShape,
|
|
1041
|
+
finalFrame ? 'attach' : 'detach', draggedShapeInitialFrameIcon);
|
|
1042
|
+
}
|
|
1043
|
+
} else if (draggedShapeInitialFrameIcon) {
|
|
1044
|
+
draggedShapeInitialFrameIcon.restoreToFrame(iconShape);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
draggedShapeInitialFrameIcon = null;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (hoveredFrameIcon) {
|
|
1052
|
+
hoveredFrameIcon.removeHighlight();
|
|
1053
|
+
hoveredFrameIcon = null;
|
|
1054
|
+
if (window.__iconShapeState) window.__iconShapeState.hoveredFrameIcon = null;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
isDragging = false;
|
|
1058
|
+
if (window.__iconShapeState) window.__iconShapeState.isDragging = false;
|
|
1059
|
+
isRotatingIcon = false;
|
|
1060
|
+
|
|
1061
|
+
const svg = getSVGElement();
|
|
1062
|
+
if (svg) {
|
|
1063
|
+
svg.removeEventListener('mousemove', dragIcon);
|
|
1064
|
+
svg.removeEventListener('mousemove', resizeIcon);
|
|
1065
|
+
svg.removeEventListener('mousemove', rotateIcon);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
document.removeEventListener('mousemove', dragIcon);
|
|
1069
|
+
document.removeEventListener('mousemove', resizeIcon);
|
|
1070
|
+
document.removeEventListener('mousemove', rotateIcon);
|
|
1071
|
+
|
|
1072
|
+
currentAnchor = null;
|
|
1073
|
+
startRotationMouseAngle = null;
|
|
1074
|
+
startIconRotation = null;
|
|
1075
|
+
|
|
1076
|
+
if (selectedIcon) {
|
|
1077
|
+
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
1078
|
+
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
1079
|
+
originalWidth = parseFloat(selectedIcon.getAttribute('width')) || 100;
|
|
1080
|
+
originalHeight = parseFloat(selectedIcon.getAttribute('height')) || 100;
|
|
1081
|
+
|
|
1082
|
+
selectedIcon.setAttribute('data-shape-x', originalX);
|
|
1083
|
+
selectedIcon.setAttribute('data-shape-y', originalY);
|
|
1084
|
+
selectedIcon.setAttribute('data-shape-width', originalWidth);
|
|
1085
|
+
selectedIcon.setAttribute('data-shape-height', originalHeight);
|
|
1086
|
+
selectedIcon.setAttribute('data-shape-rotation', iconRotation);
|
|
1087
|
+
|
|
1088
|
+
updateArrowsForShape(selectedIcon);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function deleteCurrentIcon() {
|
|
1093
|
+
if (selectedIcon) {
|
|
1094
|
+
let iconShape = null;
|
|
1095
|
+
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
1096
|
+
iconShape = shapes.find(shape => shape.shapeName === 'icon' && shape.element === selectedIcon);
|
|
1097
|
+
if (iconShape) {
|
|
1098
|
+
const idx = shapes.indexOf(iconShape);
|
|
1099
|
+
if (idx !== -1) shapes.splice(idx, 1);
|
|
1100
|
+
|
|
1101
|
+
if (iconShape.group && iconShape.group.parentNode) {
|
|
1102
|
+
iconShape.group.parentNode.removeChild(iconShape.group);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (!iconShape && selectedIcon.parentNode) {
|
|
1108
|
+
selectedIcon.parentNode.removeChild(selectedIcon);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (typeof cleanupAttachments === 'function') {
|
|
1112
|
+
cleanupAttachments(selectedIcon);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (iconShape) {
|
|
1116
|
+
pushDeleteAction(iconShape);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
removeSelection();
|
|
1120
|
+
selectedIcon = null;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
document.addEventListener('keydown', (e) => {
|
|
1125
|
+
if (e.key === 'Delete' && selectedIcon) {
|
|
1126
|
+
deleteCurrentIcon();
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
async function fetchIconsFromServer() {
|
|
1131
|
+
try {
|
|
1132
|
+
const apiUrl = "/api/icons/feed?offset=0&limit=20";
|
|
1133
|
+
|
|
1134
|
+
const response = await fetch(apiUrl);
|
|
1135
|
+
if (!response.ok) {
|
|
1136
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1137
|
+
}
|
|
1138
|
+
const data = await response.json();
|
|
1139
|
+
return data.results;
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
console.error('Failed to fetch icons from server:', error);
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
async function renderIconsFromServer() {
|
|
1147
|
+
const icons = await fetchIconsFromServer();
|
|
1148
|
+
if (icons) {
|
|
1149
|
+
document.getElementById("iconsContainer").innerHTML = '';
|
|
1150
|
+
|
|
1151
|
+
for (const icon of icons) {
|
|
1152
|
+
try {
|
|
1153
|
+
const response = await fetch(`/api/icons/serve?name=${encodeURIComponent(icon.filename)}`);
|
|
1154
|
+
if (!response.ok) {
|
|
1155
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1156
|
+
}
|
|
1157
|
+
const svgContent = await response.text();
|
|
1158
|
+
|
|
1159
|
+
const normalizedSVG = normalizeSVGSize(svgContent);
|
|
1160
|
+
|
|
1161
|
+
// Use normalizedSVG for display, not original svgContent
|
|
1162
|
+
let svgIcon = `<div class="icons" data-url="${icon.filename}" data-svg="${encodeURIComponent(normalizedSVG)}">
|
|
1163
|
+
${normalizedSVG}
|
|
1164
|
+
</div>`;
|
|
1165
|
+
document.getElementById("iconsContainer").innerHTML += svgIcon;
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
console.error('Failed to render icon:', icon.filename, error);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
addIconClickListeners();
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function searchAndRenderIcons(query) {
|
|
1176
|
+
try {
|
|
1177
|
+
const apiUrl = `/api/icons/search?q=${encodeURIComponent(query)}`;
|
|
1178
|
+
|
|
1179
|
+
const response = await fetch(apiUrl);
|
|
1180
|
+
if (!response.ok) {
|
|
1181
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1182
|
+
}
|
|
1183
|
+
const searchResults = await response.json();
|
|
1184
|
+
|
|
1185
|
+
document.getElementById("iconsContainer").innerHTML = '';
|
|
1186
|
+
|
|
1187
|
+
for (const icon of searchResults) {
|
|
1188
|
+
try {
|
|
1189
|
+
const svgResponse = await fetch(`/api/icons/serve?name=${encodeURIComponent(icon.filename)}`);
|
|
1190
|
+
if (!svgResponse.ok) {
|
|
1191
|
+
throw new Error(`HTTP error! status: ${svgResponse.status}`);
|
|
1192
|
+
}
|
|
1193
|
+
const svgContent = await svgResponse.text();
|
|
1194
|
+
|
|
1195
|
+
const normalizedSVG = normalizeSVGSize(svgContent);
|
|
1196
|
+
|
|
1197
|
+
// Use normalizedSVG for display, not original svgContent
|
|
1198
|
+
let svgIcon = `<div class="icons" data-url="${icon.filename}">
|
|
1199
|
+
${normalizedSVG}
|
|
1200
|
+
</div>`;
|
|
1201
|
+
document.getElementById("iconsContainer").innerHTML += svgIcon;
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
console.error('Failed to render search result:', icon.filename, error);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
addIconClickListeners();
|
|
1208
|
+
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
console.error('Failed to search icons:', error);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function normalizeSVGSize(svgContent, fillColor = '#fff', strokeColor = null) {
|
|
1215
|
+
const tempDiv = document.createElement('div');
|
|
1216
|
+
tempDiv.innerHTML = svgContent;
|
|
1217
|
+
const svgElement = tempDiv.querySelector('svg');
|
|
1218
|
+
|
|
1219
|
+
if (svgElement) {
|
|
1220
|
+
const originalViewBox = svgElement.getAttribute('viewBox');
|
|
1221
|
+
|
|
1222
|
+
svgElement.setAttribute('width', '35');
|
|
1223
|
+
svgElement.setAttribute('height', '35');
|
|
1224
|
+
|
|
1225
|
+
if (!originalViewBox) {
|
|
1226
|
+
const width = svgElement.getAttribute('width') || '24';
|
|
1227
|
+
const height = svgElement.getAttribute('height') || '24';
|
|
1228
|
+
const numWidth = parseFloat(width);
|
|
1229
|
+
const numHeight = parseFloat(height);
|
|
1230
|
+
|
|
1231
|
+
if (!isNaN(numWidth) && !isNaN(numHeight)) {
|
|
1232
|
+
svgElement.setAttribute('viewBox', `0 0 ${numWidth} ${numHeight}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Apply custom styling with more selective logic
|
|
1237
|
+
const applyCustomStyling = (element) => {
|
|
1238
|
+
if (element.nodeType === 1) {
|
|
1239
|
+
const tagName = element.tagName.toLowerCase();
|
|
1240
|
+
|
|
1241
|
+
// Only apply to shape elements, not container elements like 'g'
|
|
1242
|
+
if (['path', 'circle', 'rect', 'polygon', 'ellipse', 'polyline', 'line'].includes(tagName)) {
|
|
1243
|
+
const currentFill = element.getAttribute('fill');
|
|
1244
|
+
const currentStroke = element.getAttribute('stroke');
|
|
1245
|
+
|
|
1246
|
+
// Check if parent <g> has explicit colors - if so, don't override
|
|
1247
|
+
let parentG = element.parentElement;
|
|
1248
|
+
let hasParentColor = false;
|
|
1249
|
+
|
|
1250
|
+
while (parentG && parentG.tagName.toLowerCase() === 'g') {
|
|
1251
|
+
if (parentG.getAttribute('fill') || parentG.getAttribute('stroke')) {
|
|
1252
|
+
hasParentColor = true;
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1255
|
+
parentG = parentG.parentElement;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Only apply white fill if:
|
|
1259
|
+
// 1. Element doesn't have explicit 'none' fill
|
|
1260
|
+
// 2. Element doesn't already have a specific color (other than black/default)
|
|
1261
|
+
// 3. Parent <g> doesn't have explicit colors
|
|
1262
|
+
if (!hasParentColor && currentFill !== 'none') {
|
|
1263
|
+
// Only override if it's black, default, or unset
|
|
1264
|
+
if (!currentFill || currentFill === '#000' || currentFill === '#000000' || currentFill === 'black' || currentFill === 'currentColor') {
|
|
1265
|
+
element.setAttribute('fill', fillColor);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Handle stroke similarly
|
|
1270
|
+
if (strokeColor && !hasParentColor) {
|
|
1271
|
+
if (currentStroke && currentStroke !== 'none') {
|
|
1272
|
+
if (!currentStroke || currentStroke === '#000' || currentStroke === '#000000' || currentStroke === 'black' || currentStroke === 'currentColor') {
|
|
1273
|
+
element.setAttribute('stroke', strokeColor);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Recursively process children
|
|
1280
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
1281
|
+
applyCustomStyling(element.children[i]);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
applyCustomStyling(svgElement);
|
|
1287
|
+
|
|
1288
|
+
return svgElement.outerHTML;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
return svgContent;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function addIconClickListeners() {
|
|
1295
|
+
const iconElements = document.querySelectorAll('#iconsContainer .icons');
|
|
1296
|
+
iconElements.forEach(iconElement => {
|
|
1297
|
+
iconElement.addEventListener('click', (e) => {
|
|
1298
|
+
const filename = iconElement.getAttribute('data-url') || 'unknown';
|
|
1299
|
+
handleIconClick(e, filename);
|
|
1300
|
+
});
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Legacy category/search listeners disabled — React IconSidebar handles all icon UI
|
|
1305
|
+
|
|
1306
|
+
function handleIconClick(event, filename) {
|
|
1307
|
+
event.stopPropagation();
|
|
1308
|
+
|
|
1309
|
+
const iconElement = event.currentTarget;
|
|
1310
|
+
const svgElement = iconElement.querySelector('svg');
|
|
1311
|
+
|
|
1312
|
+
if (svgElement) {
|
|
1313
|
+
iconToPlace = svgElement.outerHTML;
|
|
1314
|
+
isDraggingIcon = true;
|
|
1315
|
+
isIconToolActive = true;
|
|
1316
|
+
|
|
1317
|
+
console.log('Icon selected and ready to place:', filename);
|
|
1318
|
+
|
|
1319
|
+
const iconContainer = document.getElementById('iconsToolBar');
|
|
1320
|
+
if (iconContainer) {
|
|
1321
|
+
iconContainer.classList.add('hidden');
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
document.body.style.cursor = 'crosshair';
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
// Bridge for React sidebar to trigger icon placement
|
|
1331
|
+
window.prepareIconPlacement = function(svgContent) {
|
|
1332
|
+
iconToPlace = svgContent;
|
|
1333
|
+
isDraggingIcon = true;
|
|
1334
|
+
window.isIconToolActive = true;
|
|
1335
|
+
document.body.style.cursor = 'crosshair';
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
// Bridge for IconShape to call selectIcon and removeSelection
|
|
1339
|
+
window.__iconToolSelectIcon = selectIcon;
|
|
1340
|
+
window.__iconToolRemoveSelection = removeSelection;
|
|
1341
|
+
|
|
1342
|
+
// Legacy icon rendering disabled — React IconSidebar handles icon UI
|
|
1343
|
+
|
|
1344
|
+
export { handleMouseDownIcon, handleMouseMoveIcon, handleMouseUpIcon, startDrag, stopDrag}
|