@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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/fonts/fonts.css +29 -0
  4. package/fonts/lixCode.ttf +0 -0
  5. package/fonts/lixDefault.ttf +0 -0
  6. package/fonts/lixDocs.ttf +0 -0
  7. package/fonts/lixFancy.ttf +0 -0
  8. package/fonts/lixFont.woff2 +0 -0
  9. package/package.json +49 -0
  10. package/src/SketchEngine.js +473 -0
  11. package/src/core/AIRenderer.js +1390 -0
  12. package/src/core/CopyPaste.js +655 -0
  13. package/src/core/EraserTrail.js +234 -0
  14. package/src/core/EventDispatcher.js +371 -0
  15. package/src/core/GraphEngine.js +150 -0
  16. package/src/core/GraphMathParser.js +231 -0
  17. package/src/core/GraphRenderer.js +255 -0
  18. package/src/core/LayerOrder.js +91 -0
  19. package/src/core/LixScriptParser.js +1299 -0
  20. package/src/core/MermaidFlowchartRenderer.js +475 -0
  21. package/src/core/MermaidSequenceParser.js +197 -0
  22. package/src/core/MermaidSequenceRenderer.js +479 -0
  23. package/src/core/ResizeCode.js +175 -0
  24. package/src/core/ResizeShapes.js +318 -0
  25. package/src/core/SceneSerializer.js +778 -0
  26. package/src/core/Selection.js +1861 -0
  27. package/src/core/SnapGuides.js +273 -0
  28. package/src/core/UndoRedo.js +1358 -0
  29. package/src/core/ZoomPan.js +258 -0
  30. package/src/core/ai-system-prompt.js +663 -0
  31. package/src/index.js +69 -0
  32. package/src/shapes/Arrow.js +1979 -0
  33. package/src/shapes/Circle.js +751 -0
  34. package/src/shapes/CodeShape.js +244 -0
  35. package/src/shapes/Frame.js +1460 -0
  36. package/src/shapes/FreehandStroke.js +724 -0
  37. package/src/shapes/IconShape.js +265 -0
  38. package/src/shapes/ImageShape.js +270 -0
  39. package/src/shapes/Line.js +738 -0
  40. package/src/shapes/Rectangle.js +794 -0
  41. package/src/shapes/TextShape.js +225 -0
  42. package/src/tools/arrowTool.js +581 -0
  43. package/src/tools/circleTool.js +619 -0
  44. package/src/tools/codeTool.js +2103 -0
  45. package/src/tools/eraserTool.js +131 -0
  46. package/src/tools/frameTool.js +241 -0
  47. package/src/tools/freehandTool.js +620 -0
  48. package/src/tools/iconTool.js +1344 -0
  49. package/src/tools/imageTool.js +1323 -0
  50. package/src/tools/laserTool.js +317 -0
  51. package/src/tools/lineTool.js +502 -0
  52. package/src/tools/rectangleTool.js +544 -0
  53. package/src/tools/textTool.js +1823 -0
  54. package/src/utils/imageCompressor.js +107 -0
@@ -0,0 +1,131 @@
1
+ /* eslint-disable */
2
+ // Eraser tool - extracted from eraserTool.js
3
+ // Depends on globals: svg, isEraserToolActive, ACTION_DELETE, historyStack, redoStack, clearAllSelections
4
+ import { createEraserTrail, updateEraserTrail, fadeOutEraserTrail, forceCleanupEraserTrail, getIsErasing, setIsErasing, getTargetedElements } from '../core/EraserTrail.js';
5
+
6
+ const eraserCursorSVG = `data:image/svg+xml;base64,${btoa('<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="#222" stroke="white" stroke-width="2"/></svg>')}`;
7
+
8
+ // Expose force cleanup on window so SketchEngine can call it on tool switch
9
+ window.forceCleanupEraserTrail = forceCleanupEraserTrail;
10
+
11
+ // Walk up from any element to find its top-level SVG child (direct child of svg)
12
+ function findTopLevelGroup(element) {
13
+ if (!element || element === svg) return null;
14
+ let current = element;
15
+ while (current && current.parentNode !== svg) {
16
+ current = current.parentNode;
17
+ if (!current || current === document.body) return null;
18
+ }
19
+ return current;
20
+ }
21
+
22
+ // --- Function to highlight elements under the eraser ---
23
+ function handleElementHighlight(clientX, clientY) {
24
+ if (!getIsErasing()) return;
25
+ const targetedElements = getTargetedElements();
26
+
27
+ const element = document.elementFromPoint(clientX, clientY);
28
+ if (!element || element === svg) return;
29
+
30
+ // Find the top-level SVG child group for this element
31
+ let elementToHighlight = findTopLevelGroup(element);
32
+
33
+ // Skip the eraser trail paths and any pointer-events:none elements
34
+ if (!elementToHighlight) return;
35
+ if (elementToHighlight.style.pointerEvents === 'none') return;
36
+
37
+ if (!targetedElements.has(elementToHighlight)) {
38
+ targetedElements.add(elementToHighlight);
39
+ elementToHighlight.setAttribute("data-original-opacity", elementToHighlight.style.opacity || "1");
40
+ elementToHighlight.dataset.storedOpacity = elementToHighlight.getAttribute("data-original-opacity");
41
+ elementToHighlight.style.opacity = "0.3";
42
+ }
43
+ }
44
+
45
+ function removeHighlight() {
46
+ const targetedElements = getTargetedElements();
47
+ targetedElements.forEach(element => {
48
+ element.style.opacity = element.getAttribute("data-original-opacity") || "1";
49
+ });
50
+ targetedElements.clear();
51
+ }
52
+
53
+ function removeTargetedElements() {
54
+ const targetedElements = getTargetedElements();
55
+ const elementsToRemove = Array.from(targetedElements);
56
+
57
+ if (elementsToRemove.length > 0) {
58
+ const deletionActions = [];
59
+
60
+ elementsToRemove.forEach(element => {
61
+ const originalOpacity = element.dataset.storedOpacity || "1";
62
+ deletionActions.push({
63
+ type: ACTION_DELETE,
64
+ element: element,
65
+ parent: element.parentNode,
66
+ nextSibling: element.nextSibling,
67
+ originalOpacity: originalOpacity,
68
+ });
69
+
70
+ // Remove from DOM
71
+ if (element.parentNode) {
72
+ element.parentNode.removeChild(element);
73
+ }
74
+
75
+ // Also remove from the global shapes array
76
+ if (window.shapes) {
77
+ const idx = window.shapes.findIndex(s =>
78
+ s.element === element ||
79
+ s.group === element ||
80
+ s.wrapper === element
81
+ );
82
+ if (idx !== -1) {
83
+ window.shapes.splice(idx, 1);
84
+ }
85
+ }
86
+ });
87
+
88
+ if (deletionActions.length > 0 && window.historyStack) {
89
+ window.historyStack.push(...deletionActions);
90
+ }
91
+
92
+ window.redoStack = [];
93
+ if (typeof clearAllSelections === 'function') clearAllSelections();
94
+ if (typeof updateUndoRedoButtons === 'function') updateUndoRedoButtons();
95
+ targetedElements.clear();
96
+ }
97
+ }
98
+
99
+ // --- Event Listeners ---
100
+ svg.addEventListener("pointerdown", (e) => {
101
+ if (isEraserToolActive) {
102
+ setIsErasing(true);
103
+ createEraserTrail(e.clientX, e.clientY);
104
+ handleElementHighlight(e.clientX, e.clientY);
105
+ }
106
+ });
107
+
108
+ svg.addEventListener("pointermove", (e) => {
109
+ if (isEraserToolActive) {
110
+ svg.style.cursor = `url(${eraserCursorSVG}) 10 10, auto`;
111
+ }
112
+ if (!getIsErasing()) return;
113
+ updateEraserTrail(e.clientX, e.clientY);
114
+ handleElementHighlight(e.clientX, e.clientY);
115
+ });
116
+
117
+ svg.addEventListener("pointerup", () => {
118
+ if (!getIsErasing()) return;
119
+ setIsErasing(false);
120
+ removeTargetedElements();
121
+ fadeOutEraserTrail();
122
+ svg.style.cursor = "default";
123
+ });
124
+
125
+ svg.addEventListener("pointerleave", (e) => {
126
+ if (!getIsErasing()) return;
127
+ setIsErasing(false);
128
+ removeTargetedElements();
129
+ fadeOutEraserTrail();
130
+ svg.style.cursor = "default";
131
+ });
@@ -0,0 +1,241 @@
1
+ /* eslint-disable */
2
+ // Frame tool event handlers - extracted from frameHolder.js
3
+ import { pushCreateAction, pushDeleteAction, pushTransformAction, pushFrameAttachmentAction } from '../core/UndoRedo.js';
4
+ import { updateAttachedArrows, cleanupAttachments } from './arrowTool.js';
5
+
6
+ let currentFrame = null;
7
+ let isResizing = false;
8
+ let isDragging = false;
9
+ let activeAnchor = null;
10
+ let isDrawingFrame = false;
11
+ let frameStrokeColor = "#888";
12
+ let frameStrokeThickness = 2;
13
+ let frameFillColor = "transparent";
14
+ let frameOpacity = 1;
15
+ let startX, startY;
16
+ let dragOldPosFrame = null;
17
+
18
+ function getSVGCoordsFromMouse(e) {
19
+ const viewBox = svg.viewBox.baseVal;
20
+ const rect = svg.getBoundingClientRect();
21
+ const mouseX = e.clientX - rect.left;
22
+ const mouseY = e.clientY - rect.top;
23
+ const svgX = viewBox.x + (mouseX / rect.width) * viewBox.width;
24
+ const svgY = viewBox.y + (mouseY / rect.height) * viewBox.height;
25
+ return { x: svgX, y: svgY };
26
+ }
27
+
28
+
29
+ // Event handlers for frame tool
30
+ const handleMouseDown = (e) => {
31
+ if (!isFrameToolActive && !isSelectionToolActive) return;
32
+
33
+ const { x, y } = getSVGCoordsFromMouse(e);
34
+
35
+ if (isFrameToolActive) {
36
+ isDrawingFrame = true;
37
+ startX = x;
38
+ startY = y;
39
+ currentFrame = new Frame(x, y, 0, 0, {
40
+ stroke: frameStrokeColor,
41
+ strokeWidth: frameStrokeThickness,
42
+ fill: frameFillColor,
43
+ opacity: frameOpacity
44
+ });
45
+ shapes.push(currentFrame);
46
+ currentShape = currentFrame;
47
+ } else if (isSelectionToolActive) {
48
+ // Handle frame selection — but first check if a contained shape was clicked
49
+ for (let i = shapes.length - 1; i >= 0; i--) {
50
+ if (shapes[i].shapeName === 'frame' && shapes[i].contains(x, y)) {
51
+ const clickedFrame = shapes[i];
52
+
53
+ // Check if click is on a shape inside this frame — let shape handlers deal with it
54
+ let clickedContainedShape = false;
55
+ if (clickedFrame.containedShapes && clickedFrame.containedShapes.length > 0) {
56
+ for (let j = clickedFrame.containedShapes.length - 1; j >= 0; j--) {
57
+ const inner = clickedFrame.containedShapes[j];
58
+ if (inner.contains && inner.contains(x, y)) {
59
+ clickedContainedShape = true;
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ // If a contained shape was clicked, don't select the frame — let the
65
+ // EventDispatcher fall through to shape-specific handlers
66
+ if (clickedContainedShape) return;
67
+
68
+ if (currentShape && currentShape !== clickedFrame) {
69
+ currentShape.removeSelection();
70
+ }
71
+ currentShape = clickedFrame;
72
+ currentShape.selectFrame();
73
+
74
+ // Check for anchor interaction
75
+ const anchorIndex = typeof currentShape.isNearAnchor === 'function' ? currentShape.isNearAnchor(x, y) : null;
76
+ if (anchorIndex !== null) {
77
+ activeAnchor = anchorIndex;
78
+ isResizing = true;
79
+ } else {
80
+ isDragging = true;
81
+ startX = x;
82
+ startY = y;
83
+ dragOldPosFrame = {
84
+ x: currentShape.x,
85
+ y: currentShape.y,
86
+ width: currentShape.width,
87
+ height: currentShape.height,
88
+ rotation: currentShape.rotation
89
+ };
90
+ }
91
+ return;
92
+ }
93
+ }
94
+
95
+ // If no frame was clicked, deselect current
96
+ if (currentShape && currentShape.shapeName === 'frame') {
97
+ currentShape.removeSelection();
98
+ currentShape = null;
99
+ }
100
+ }
101
+ };
102
+
103
+ const handleMouseMove = (e) => {
104
+ const { x, y } = getSVGCoordsFromMouse(e);
105
+
106
+ if (isDrawingFrame && currentFrame) {
107
+ const width = Math.abs(x - startX);
108
+ const height = Math.abs(y - startY);
109
+ currentFrame.x = Math.min(startX, x);
110
+ currentFrame.y = Math.min(startY, y);
111
+ currentFrame.width = width;
112
+ currentFrame.height = height;
113
+ currentFrame.draw();
114
+ } else if (isDragging && currentShape && currentShape.shapeName === 'frame') {
115
+ const dx = x - startX;
116
+ const dy = y - startY;
117
+
118
+ // Use the frame's move method which properly handles contained shapes
119
+ currentShape.move(dx, dy);
120
+
121
+ startX = x;
122
+ startY = y;
123
+ }
124
+ };
125
+
126
+ const handleMouseUp = (e) => {
127
+ if (isDrawingFrame && currentFrame) {
128
+ // Check if frame is too small
129
+ if (currentFrame.width < 10 || currentFrame.height < 10) {
130
+ currentFrame.destroy();
131
+ currentFrame = null;
132
+ currentShape = null;
133
+ } else {
134
+ pushCreateAction(currentFrame);
135
+ // Check for shapes that should be contained in the new frame
136
+ currentFrame.updateContainedShapes(true); // Apply clipping immediately for new frames
137
+
138
+ // Auto-select the new frame and switch to selection tool
139
+ const placedFrame = currentFrame;
140
+ if (window.__sketchStoreApi) {
141
+ window.__sketchStoreApi.setActiveTool('select', { afterDraw: true });
142
+ } else {
143
+ window.isSelectionToolActive = true;
144
+ }
145
+ currentShape = placedFrame;
146
+ placedFrame.selectFrame();
147
+ }
148
+ isDrawingFrame = false;
149
+ }
150
+
151
+ if (isDragging && dragOldPosFrame && currentShape) {
152
+ const newPos = {
153
+ x: currentShape.x,
154
+ y: currentShape.y,
155
+ width: currentShape.width,
156
+ height: currentShape.height,
157
+ rotation: currentShape.rotation
158
+ };
159
+ pushTransformAction(currentShape, dragOldPosFrame, newPos);
160
+ dragOldPosFrame = null;
161
+
162
+ // Update frame containment after moving frame (but don't re-move contained shapes)
163
+ if (currentShape.shapeName === 'frame') {
164
+ currentShape.updateClipPath();
165
+ }
166
+ }
167
+
168
+ // Only check frame containment for shapes that aren't already in frames
169
+ if (!isDragging) {
170
+ shapes.forEach(shape => {
171
+ if (shape.shapeName !== 'frame' && !shape.parentFrame) {
172
+ shapes.forEach(frame => {
173
+ if (frame.shapeName === 'frame') {
174
+ if (frame.isShapeInFrame(shape)) {
175
+ frame.addShapeToFrame(shape);
176
+ }
177
+ }
178
+ });
179
+ }
180
+ });
181
+ }
182
+
183
+ isDrawingFrame = false;
184
+ isDragging = false;
185
+ isResizing = false;
186
+ activeAnchor = null;
187
+ svg.style.cursor = 'default';
188
+ };
189
+
190
+ Frame.prototype.isNearAnchor = function(x, y) {
191
+ const anchorSize = 10 / currentZoom;
192
+
193
+ // Define anchor positions
194
+ const anchorPositions = [
195
+ { x: this.x, y: this.y },
196
+ { x: this.x + this.width / 2, y: this.y },
197
+ { x: this.x + this.width, y: this.y },
198
+ { x: this.x + this.width, y: this.y + this.height / 2 },
199
+ { x: this.x + this.width, y: this.y + this.height },
200
+ { x: this.x + this.width / 2, y: this.y + this.height },
201
+ { x: this.x, y: this.y + this.height },
202
+ { x: this.x, y: this.y + this.height / 2 },
203
+ { x: this.x + this.width / 2, y: this.y - 30 / currentZoom } // Rotation handle
204
+ ];
205
+
206
+ for (let i = 0; i < anchorPositions.length; i++) {
207
+ const pos = anchorPositions[i];
208
+ const distance = Math.sqrt((x - pos.x) ** 2 + (y - pos.y) ** 2);
209
+ if (distance <= anchorSize) {
210
+ return i;
211
+ }
212
+ }
213
+ return null;
214
+ };
215
+
216
+ // Delete functionality
217
+ function deleteCurrentFrame() {
218
+ if (currentShape && currentShape.shapeName === 'frame') {
219
+ const idx = shapes.indexOf(currentShape);
220
+ if (idx !== -1) shapes.splice(idx, 1);
221
+ if (currentShape.group.parentNode) {
222
+ currentShape.group.parentNode.removeChild(currentShape.group);
223
+ }
224
+ pushDeleteAction(currentShape);
225
+ currentShape = null;
226
+ }
227
+ }
228
+
229
+ document.addEventListener('keydown', (e) => {
230
+ if (e.key === 'Delete' && currentShape && currentShape.shapeName === 'frame') {
231
+ deleteCurrentFrame();
232
+ }
233
+ });
234
+
235
+ window.Frame = Frame;
236
+
237
+ export {
238
+ handleMouseDown as handleMouseDownFrame,
239
+ handleMouseMove as handleMouseMoveFrame,
240
+ handleMouseUp as handleMouseUpFrame
241
+ };