@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,473 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * SketchEngine - Main engine entry point
4
+ *
5
+ * Initializes the SVG canvas, sets up global state (including RoughJS from npm),
6
+ * then dynamically imports all tool and shape modules.
7
+ *
8
+ * IMPORTANT: All globals must be set BEFORE importing modules, because many
9
+ * modules run code at the top level (e.g. `const rc = rough.svg(svg)`) that
10
+ * depends on these globals existing.
11
+ */
12
+
13
+ import rough from 'roughjs';
14
+
15
+ class SketchEngine {
16
+ constructor(svgElement, options = {}) {
17
+ if (!svgElement || svgElement.tagName !== 'svg') {
18
+ throw new Error('SketchEngine requires an SVG element');
19
+ }
20
+
21
+ this.svg = svgElement;
22
+ this.options = {
23
+ initialZoom: 1,
24
+ minZoom: 0.4,
25
+ maxZoom: 30,
26
+ ...options
27
+ };
28
+
29
+ // Event callback for framework consumers (React, Vue, vanilla, VS Code, etc.)
30
+ this.onEvent = options.onEvent || (() => {});
31
+
32
+ // Public API surfaces (populated after init)
33
+ this.scene = null;
34
+ this.shapes = null;
35
+
36
+ this._modules = {};
37
+ this._initialized = false;
38
+ }
39
+
40
+ /**
41
+ * Emit an event to the consumer callback.
42
+ * @param {string} type - Event type (e.g. 'sidebar:select', 'zoom:change')
43
+ * @param {*} data - Event payload
44
+ */
45
+ emit(type, data) {
46
+ try { this.onEvent(type, data); } catch (e) { console.warn('[SketchEngine] onEvent error:', e); }
47
+ }
48
+
49
+ /**
50
+ * Set up all the global variables that the tool/shape modules depend on.
51
+ * Must be called BEFORE any module imports.
52
+ */
53
+ _initGlobals() {
54
+ // Core SVG reference
55
+ window.svg = this.svg;
56
+ window.freehandCanvas = this.svg;
57
+
58
+ // RoughJS from npm
59
+ window.rough = rough;
60
+ window.roughCanvas = rough.svg(this.svg);
61
+ window.roughGenerator = window.roughCanvas.generator;
62
+
63
+ // Shape storage
64
+ window.shapes = window.shapes || [];
65
+ window.currentShape = window.currentShape || null;
66
+ window.lastMousePos = window.lastMousePos || null;
67
+
68
+ // Zoom state
69
+ window.currentZoom = this.options.initialZoom;
70
+ window.minScale = this.options.minZoom;
71
+ window.maxScale = this.options.maxZoom;
72
+ window.minZoom = this.options.minZoom;
73
+ window.maxZoom = this.options.maxZoom;
74
+
75
+ // ViewBox state
76
+ window.currentViewBox = window.currentViewBox || {
77
+ x: 0, y: 0,
78
+ width: window.innerWidth,
79
+ height: window.innerHeight
80
+ };
81
+
82
+ // Tool activation flags
83
+ window.isPaintToolActive = false;
84
+ window.isTextToolActive = false;
85
+ window.isCircleToolActive = false;
86
+ window.isSquareToolActive = false;
87
+ window.isLaserToolActive = false;
88
+ window.isEraserToolActive = false;
89
+ window.isImageToolActive = false;
90
+ window.isArrowToolActive = false;
91
+ window.isLineToolActive = false;
92
+ window.isSelectionToolActive = true;
93
+ window.isPanningToolActive = false;
94
+ window.isFrameToolActive = false;
95
+ window.isIconToolActive = false;
96
+ window.isCodeToolActive = false;
97
+ window.isTextInCodeMode = false;
98
+
99
+ // Pan state
100
+ window.isPanning = false;
101
+ window.panStart = null;
102
+ window.startCanvasX = 0;
103
+ window.startCanvasY = 0;
104
+
105
+ // Transform state
106
+ window.currentMatrix = new DOMMatrix();
107
+ window.currentTranslation = { x: 0, y: 0 };
108
+
109
+ // Action type constants
110
+ window.ACTION_CREATE = 'create';
111
+ window.ACTION_DELETE = 'delete';
112
+ window.ACTION_MODIFY = 'modify';
113
+ window.ACTION_PASTE = 'paste';
114
+
115
+ // History stacks
116
+ window.historyStack = window.historyStack || [];
117
+ window.redoStack = window.redoStack || [];
118
+
119
+ // Sidebar element stubs — React sidebars handle UI, but legacy code
120
+ // queries these at top level. Provide dummy elements so it doesn't crash.
121
+ const dummyEl = document.createElement('div');
122
+ dummyEl.classList.add('hidden');
123
+ window.paintBrushSideBar = document.getElementById('paintBrushToolBar') || dummyEl;
124
+ window.lineSideBar = document.getElementById('lineSideBar') || dummyEl;
125
+ window.squareSideBar = document.getElementById('squareSideBar') || dummyEl;
126
+ window.circleSideBar = document.getElementById('circleSideBar') || dummyEl;
127
+ window.arrowSideBar = document.getElementById('arrowSideBar') || dummyEl;
128
+ window.textSideBar = document.getElementById('textToolBar') || dummyEl;
129
+ window.frameSideBar = document.getElementById('frameSideBar') || dummyEl;
130
+
131
+ // Zoom control element refs
132
+ window.zoomInBtn = document.getElementById('zoomIn') || dummyEl;
133
+ window.zoomOutBtn = document.getElementById('zoomOut') || dummyEl;
134
+ window.zoomPercentSpan = document.getElementById('zoomPercent') || dummyEl;
135
+
136
+ // Container
137
+ window.container = document.querySelector('.container') || document.body;
138
+
139
+ // Sidebar control — bridge legacy code to consumer UI
140
+ const engine = this;
141
+ window.disableAllSideBars = function() {
142
+ // Hide all legacy sidebar elements
143
+ [window.paintBrushSideBar, window.lineSideBar, window.squareSideBar,
144
+ window.circleSideBar, window.arrowSideBar, window.textSideBar, window.frameSideBar
145
+ ].forEach(el => { if (el) el.classList.add('hidden'); });
146
+ // Notify consumer via onEvent + legacy bridge
147
+ engine.emit('sidebar:clear');
148
+ if (window.__sketchStoreApi) {
149
+ window.__sketchStoreApi.clearSelectedShapeSidebar();
150
+ }
151
+ };
152
+
153
+ // toolExtraPopup — legacy UI function, no-op in React
154
+ window.toolExtraPopup = window.toolExtraPopup || function() {};
155
+
156
+ // updateUndoRedoButtons — legacy UI function, no-op in React
157
+ window.updateUndoRedoButtons = window.updateUndoRedoButtons || function() {};
158
+
159
+ // Bridge for shape selection -> consumer UI
160
+ // Maps shape.shapeName to the sidebar key
161
+ window.__showSidebarForShape = function(shapeName) {
162
+ const sidebarMap = {
163
+ 'rectangle': 'rectangle',
164
+ 'circle': 'circle',
165
+ 'arrow': 'arrow',
166
+ 'line': 'line',
167
+ 'freehandStroke': 'paintbrush',
168
+ 'text': 'text',
169
+ 'code': 'text',
170
+ 'frame': 'frame',
171
+ 'image': 'image',
172
+ };
173
+ const sidebar = sidebarMap[shapeName];
174
+ // Emit to consumer callback
175
+ engine.emit('sidebar:select', { sidebar, shapeName });
176
+ // Legacy bridge for React
177
+ if (sidebar && window.__sketchStoreApi) {
178
+ window.__sketchStoreApi.setSelectedShapeSidebar(sidebar);
179
+ }
180
+ window.__selectedShapeIsCode = (shapeName === 'code');
181
+ if (window.__onCodeModeChanged) window.__onCodeModeChanged(shapeName === 'code');
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Initialize engine: set globals first, then import modules.
187
+ */
188
+ async init() {
189
+ if (this._initialized) return;
190
+
191
+ // CRITICAL: Set up ALL globals BEFORE importing modules
192
+ this._initGlobals();
193
+
194
+ try {
195
+ // Import shape classes first
196
+ const [
197
+ { Rectangle },
198
+ { Circle },
199
+ { Arrow },
200
+ { Line },
201
+ { TextShape },
202
+ { CodeShape },
203
+ { ImageShape },
204
+ { IconShape },
205
+ { Frame },
206
+ { FreehandStroke }
207
+ ] = await Promise.all([
208
+ import('./shapes/Rectangle.js'),
209
+ import('./shapes/Circle.js'),
210
+ import('./shapes/Arrow.js'),
211
+ import('./shapes/Line.js'),
212
+ import('./shapes/TextShape.js'),
213
+ import('./shapes/CodeShape.js'),
214
+ import('./shapes/ImageShape.js'),
215
+ import('./shapes/IconShape.js'),
216
+ import('./shapes/Frame.js'),
217
+ import('./shapes/FreehandStroke.js')
218
+ ]);
219
+
220
+ // Expose shape classes globally
221
+ window.Rectangle = Rectangle;
222
+ window.Circle = Circle;
223
+ window.Arrow = Arrow;
224
+ window.Line = Line;
225
+ window.TextShape = TextShape;
226
+ window.CodeShape = CodeShape;
227
+ window.ImageShape = ImageShape;
228
+ window.IconShape = IconShape;
229
+ window.Frame = Frame;
230
+ window.FreehandStroke = FreehandStroke;
231
+
232
+ this._modules.shapes = {
233
+ Rectangle, Circle, Arrow, Line,
234
+ TextShape, CodeShape, ImageShape, IconShape,
235
+ Frame, FreehandStroke
236
+ };
237
+
238
+ // Import tool handlers (they run top-level code that reads globals)
239
+ const [
240
+ rectangleTool, circleTool, arrowTool, lineTool,
241
+ textTool, codeTool, imageTool, iconTool,
242
+ frameTool, freehandTool
243
+ ] = await Promise.all([
244
+ import('./tools/rectangleTool.js'),
245
+ import('./tools/circleTool.js'),
246
+ import('./tools/arrowTool.js'),
247
+ import('./tools/lineTool.js'),
248
+ import('./tools/textTool.js'),
249
+ import('./tools/codeTool.js'),
250
+ import('./tools/imageTool.js'),
251
+ import('./tools/iconTool.js'),
252
+ import('./tools/frameTool.js'),
253
+ import('./tools/freehandTool.js')
254
+ ]);
255
+
256
+ this._modules.tools = {
257
+ rectangleTool, circleTool, arrowTool, lineTool,
258
+ textTool, codeTool, imageTool, iconTool,
259
+ frameTool, freehandTool
260
+ };
261
+
262
+ // Import core modules (EventDispatcher attaches SVG listeners at top level)
263
+ const [
264
+ eventDispatcher, undoRedo, selection,
265
+ zoomPan, copyPaste, eraserTrail,
266
+ resizeShapes, resizeCode
267
+ ] = await Promise.all([
268
+ import('./core/EventDispatcher.js'),
269
+ import('./core/UndoRedo.js'),
270
+ import('./core/Selection.js'),
271
+ import('./core/ZoomPan.js'),
272
+ import('./core/CopyPaste.js'),
273
+ import('./core/EraserTrail.js'),
274
+ import('./core/ResizeShapes.js'),
275
+ import('./core/ResizeCode.js')
276
+ ]);
277
+
278
+ this._modules.core = {
279
+ eventDispatcher, undoRedo, selection,
280
+ zoomPan, copyPaste, eraserTrail,
281
+ resizeShapes, resizeCode
282
+ };
283
+
284
+ // Re-bind event listeners to the current SVG element
285
+ if (eventDispatcher.initEventDispatcher) {
286
+ eventDispatcher.initEventDispatcher(this.svg);
287
+ }
288
+
289
+ // Import standalone tools
290
+ await Promise.all([
291
+ import('./tools/eraserTool.js'),
292
+ import('./tools/laserTool.js')
293
+ ]);
294
+
295
+ // Expose key functions globally
296
+ if (undoRedo.undo) window.undo = undoRedo.undo;
297
+ if (undoRedo.redo) window.redo = undoRedo.redo;
298
+ if (undoRedo.pushCreateAction) window.pushCreateAction = undoRedo.pushCreateAction;
299
+ if (undoRedo.pushDeleteAction) window.pushDeleteAction = undoRedo.pushDeleteAction;
300
+ if (selection.multiSelection) window.multiSelection = selection.multiSelection;
301
+ if (selection.clearAllSelections) window.clearAllSelections = selection.clearAllSelections;
302
+
303
+ // Initialize centralized copy/paste system
304
+ if (copyPaste.initCopyPaste) copyPaste.initCopyPaste();
305
+
306
+ // Initialize AI renderer bridge
307
+ const aiRenderer = await import('./core/AIRenderer.js');
308
+ if (aiRenderer.initAIRenderer) aiRenderer.initAIRenderer();
309
+
310
+ // Initialize graph engine bridge
311
+ const graphEngine = await import('./core/GraphEngine.js');
312
+ if (graphEngine.initGraphEngine) graphEngine.initGraphEngine();
313
+
314
+ // Initialize scene serializer bridge
315
+ const sceneSerializer = await import('./core/SceneSerializer.js');
316
+ if (sceneSerializer.initSceneSerializer) sceneSerializer.initSceneSerializer();
317
+
318
+ // Initialize layer ordering
319
+ const layerOrder = await import('./core/LayerOrder.js');
320
+ if (layerOrder.initLayerOrder) layerOrder.initLayerOrder();
321
+
322
+ // Initialize LixScript programmatic diagram engine
323
+ const lixScript = await import('./core/LixScriptParser.js');
324
+ if (lixScript.initLixScriptBridge) lixScript.initLixScriptBridge();
325
+
326
+ // ── Public API surfaces ──
327
+
328
+ // Scene operations (save, load, export, etc.)
329
+ this.scene = window.__sceneSerializer || {
330
+ save: sceneSerializer.saveScene,
331
+ load: sceneSerializer.loadScene,
332
+ download: sceneSerializer.downloadScene,
333
+ upload: sceneSerializer.uploadScene,
334
+ exportPNG: sceneSerializer.exportAsPNG,
335
+ exportPDF: sceneSerializer.exportAsPDF,
336
+ copyAsPNG: sceneSerializer.copyAsPNG,
337
+ copyAsSVG: sceneSerializer.copyAsSVG,
338
+ reset: sceneSerializer.resetCanvas,
339
+ findText: sceneSerializer.findTextOnCanvas,
340
+ };
341
+
342
+ // Shape array reference
343
+ this.shapes = window.shapes;
344
+
345
+ // Undo/redo
346
+ this.undo = undoRedo.undo || (() => {});
347
+ this.redo = undoRedo.redo || (() => {});
348
+
349
+ // LixScript execution
350
+ this.lixscript = {
351
+ parse: lixScript.parseLixScript || (() => null),
352
+ execute: lixScript.executeLixScript || (lixScript.parseLixScript ? (code) => {
353
+ const parsed = lixScript.parseLixScript(code);
354
+ if (parsed && lixScript.resolveShapeRefs) lixScript.resolveShapeRefs(parsed);
355
+ return parsed;
356
+ } : (() => null)),
357
+ };
358
+
359
+ // Store module refs for advanced consumers
360
+ this._modules.sceneSerializer = sceneSerializer;
361
+ this._modules.lixScript = lixScript;
362
+
363
+ this._initialized = true;
364
+ console.log('[SketchEngine] Initialized successfully');
365
+ } catch (err) {
366
+ console.error('[SketchEngine] Initialization failed:', err);
367
+ throw err;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Sync tool flags from Zustand activeTool value.
373
+ */
374
+ setActiveTool(toolName) {
375
+ // Deselect current shape when switching tools
376
+ if (window.currentShape && typeof window.currentShape.removeSelection === 'function') {
377
+ window.currentShape.removeSelection();
378
+ window.currentShape = null;
379
+ }
380
+ if (typeof window.clearAllSelections === 'function') {
381
+ window.clearAllSelections();
382
+ }
383
+ if (typeof window.disableAllSideBars === 'function') {
384
+ window.disableAllSideBars();
385
+ }
386
+
387
+ // Force cleanup eraser trail when switching tools
388
+ if (typeof window.forceCleanupEraserTrail === 'function') {
389
+ window.forceCleanupEraserTrail();
390
+ }
391
+
392
+ window.isPaintToolActive = false;
393
+ window.isSquareToolActive = false;
394
+ window.isCircleToolActive = false;
395
+ window.isArrowToolActive = false;
396
+ window.isTextToolActive = false;
397
+ window.isLaserToolActive = false;
398
+ window.isLineToolActive = false;
399
+ window.isEraserToolActive = false;
400
+ window.isSelectionToolActive = false;
401
+ window.isImageToolActive = false;
402
+ window.isPanningToolActive = false;
403
+ window.isFrameToolActive = false;
404
+ window.isIconToolActive = false;
405
+ window.isCodeToolActive = false;
406
+
407
+ const flagMap = {
408
+ select: 'isSelectionToolActive',
409
+ pan: 'isPanningToolActive',
410
+ rectangle: 'isSquareToolActive',
411
+ circle: 'isCircleToolActive',
412
+ line: 'isLineToolActive',
413
+ arrow: 'isArrowToolActive',
414
+ freehand: 'isPaintToolActive',
415
+ text: 'isTextToolActive',
416
+ code: 'isCodeToolActive',
417
+ eraser: 'isEraserToolActive',
418
+ laser: 'isLaserToolActive',
419
+ image: 'isImageToolActive',
420
+ frame: 'isFrameToolActive',
421
+ icon: 'isIconToolActive',
422
+ };
423
+
424
+ const flag = flagMap[toolName];
425
+ if (flag) window[flag] = true;
426
+
427
+ if (toolName === 'text' && window.isTextInCodeMode) {
428
+ window.isCodeToolActive = true;
429
+ }
430
+
431
+ // Show image source picker when image tool is activated
432
+ if (toolName === 'image' && window.__showImageSourcePicker) {
433
+ window.__showImageSourcePicker();
434
+ }
435
+
436
+ // Set appropriate cursor for the active tool
437
+ const cursorMap = {
438
+ select: 'default',
439
+ pan: 'grab',
440
+ rectangle: 'crosshair',
441
+ circle: 'crosshair',
442
+ line: 'crosshair',
443
+ arrow: 'crosshair',
444
+ freehand: 'crosshair',
445
+ text: 'crosshair',
446
+ code: 'crosshair',
447
+ eraser: 'crosshair',
448
+ laser: 'crosshair',
449
+ image: 'crosshair',
450
+ frame: 'crosshair',
451
+ icon: 'crosshair',
452
+ };
453
+ if (this.svg) {
454
+ this.svg.style.cursor = cursorMap[toolName] || 'default';
455
+ }
456
+ }
457
+
458
+ cleanup() {
459
+ // Remove event listeners from the SVG element
460
+ if (this._modules.core?.eventDispatcher?.cleanupEventDispatcher) {
461
+ this._modules.core.eventDispatcher.cleanupEventDispatcher();
462
+ }
463
+ window.shapes = [];
464
+ window.currentShape = null;
465
+ window.lastMousePos = null;
466
+ this._modules = {};
467
+ this._initialized = false;
468
+ console.log('[SketchEngine] Cleaned up');
469
+ }
470
+ }
471
+
472
+ export { SketchEngine };
473
+ export default SketchEngine;