@elixpo/lixsketch 5.5.20 → 5.5.21
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/dist/react/{CopyPaste-KJZ34MIP.js → CopyPaste-FJN7K5LS.js} +39 -11
- package/dist/react/CopyPaste-FJN7K5LS.js.map +7 -0
- package/dist/react/{EventDispatcher-XDRQS7BD.js → EventDispatcher-O7HCQQNM.js} +13 -13
- package/dist/react/Frame-RLSMJJSK.js +11 -0
- package/dist/react/{GraphEngine-KQXHNTZQ.js → GraphEngine-MM6ALA2N.js} +105 -148
- package/dist/react/{GraphEngine-KQXHNTZQ.js.map → GraphEngine-MM6ALA2N.js.map} +2 -2
- package/dist/react/IconShape-7OPXAKLK.js +10 -0
- package/dist/react/ImageShape-LRNJQCF3.js +10 -0
- package/dist/react/Line-WWQFBNX5.js +10 -0
- package/dist/react/{SceneSerializer-VY3HY4PF.js → SceneSerializer-GSJR7ZHC.js} +32 -8
- package/dist/react/SceneSerializer-GSJR7ZHC.js.map +7 -0
- package/dist/react/{Selection-U2AR6NQS.js → Selection-ZL3G6IAO.js} +4 -4
- package/dist/react/{SketchEngine-ZFYUTKET.js → SketchEngine-BFLHJPCG.js} +21 -21
- package/dist/react/{UndoRedo-BZ6W4QTV.js → UndoRedo-NL7KLR5B.js} +2 -2
- package/dist/react/{arrowTool-LKFSZKXC.js → arrowTool-CNVGM6RX.js} +3 -3
- package/dist/react/{chunk-RFHMFWVR.js → chunk-2FMFOPGW.js} +5 -6
- package/dist/react/{chunk-RFHMFWVR.js.map → chunk-2FMFOPGW.js.map} +2 -2
- package/dist/react/{chunk-SBSM3GQD.js → chunk-2THKDSWW.js} +4 -4
- package/dist/react/{chunk-JOPQ27YH.js → chunk-4BLMNGTR.js} +3 -3
- package/dist/react/{chunk-C62SXFBL.js → chunk-4TAS67SL.js} +23 -22
- package/dist/react/chunk-4TAS67SL.js.map +7 -0
- package/dist/react/{chunk-7VBI7S7T.js → chunk-AH4PWNEB.js} +10 -11
- package/dist/react/chunk-AH4PWNEB.js.map +7 -0
- package/dist/react/{chunk-R5QXEVZ5.js → chunk-GC3KTQDA.js} +74 -15
- package/dist/react/chunk-GC3KTQDA.js.map +7 -0
- package/dist/react/{chunk-2V7G2PLX.js → chunk-GPIYXREE.js} +3 -3
- package/dist/react/{chunk-73VLPCBC.js → chunk-HFQZYCHJ.js} +3 -3
- package/dist/react/{chunk-C72OHQLE.js → chunk-ILGAFR56.js} +3 -3
- package/dist/react/{chunk-75G7XSZU.js → chunk-KVAQPCUH.js} +17 -3
- package/dist/react/chunk-KVAQPCUH.js.map +7 -0
- package/dist/react/{chunk-WEWMB35W.js → chunk-OFGJJKPH.js} +3 -3
- package/dist/react/{chunk-6G3ITAS5.js → chunk-SZD7DKYJ.js} +18 -9
- package/dist/react/chunk-SZD7DKYJ.js.map +7 -0
- package/dist/react/{chunk-MYYBFZD3.js → chunk-U3TB4KRS.js} +45 -6
- package/dist/react/chunk-U3TB4KRS.js.map +7 -0
- package/dist/react/{chunk-UQBT7F4K.js → chunk-V2QTYSBK.js} +2 -2
- package/dist/react/{chunk-2I3GH65Z.js → chunk-XFQECTTF.js} +44 -19
- package/dist/react/chunk-XFQECTTF.js.map +7 -0
- package/dist/react/{chunk-O64N5JPO.js → chunk-YF3TK7SC.js} +2 -2
- package/dist/react/{circleTool-RDOIMKKV.js → circleTool-KICNRQCI.js} +4 -4
- package/dist/react/{codeTool-VBDB2ES6.js → codeTool-NCQ73R6M.js} +4 -4
- package/dist/react/{frameTool-Z5ZDCX6H.js → frameTool-Y6KB67DO.js} +4 -4
- package/dist/react/{freehandTool-AAQCTMVM.js → freehandTool-MMULHCQ7.js} +4 -4
- package/dist/react/{iconTool-5WNDHI5T.js → iconTool-LSW2DYGB.js} +4 -4
- package/dist/react/{imageTool-ECPMT3BL.js → imageTool-2DSR32RX.js} +4 -4
- package/dist/react/index.js +141 -66
- package/dist/react/index.js.map +3 -3
- package/dist/react/{lineTool-XLOOZE3E.js → lineTool-N5I7IGT7.js} +4 -4
- package/dist/react/{rectangleTool-AEYDDJCA.js → rectangleTool-YUT7ZK2E.js} +4 -4
- package/dist/react/{textTool-5IJKTWVQ.js → textTool-IQOYPELP.js} +5 -5
- package/package.json +1 -1
- package/src/core/CopyPaste.js +34 -4
- package/src/core/GraphEngine.js +129 -185
- package/src/core/SceneSerializer.js +26 -0
- package/src/core/Selection.js +12 -11
- package/src/core/UndoRedo.js +47 -3
- package/src/react/components/canvas/MultiSelectActions.jsx +4 -1
- package/src/react/components/sidebars/IconSidebar.jsx +14 -23
- package/src/react/components/sidebars/ImageSidebar.jsx +47 -24
- package/src/react/store/useUIStore.js +71 -14
- package/src/shapes/Frame.js +19 -2
- package/src/shapes/IconShape.js +23 -25
- package/src/shapes/ImageShape.js +6 -2
- package/src/tools/frameTool.js +19 -9
- package/src/tools/iconTool.js +60 -25
- package/src/tools/imageTool.js +77 -13
- package/dist/react/CopyPaste-KJZ34MIP.js.map +0 -7
- package/dist/react/Frame-QLBAVLGW.js +0 -11
- package/dist/react/IconShape-32EYI2MA.js +0 -10
- package/dist/react/ImageShape-MYP2DVVH.js +0 -10
- package/dist/react/Line-FCCF2BOB.js +0 -10
- package/dist/react/SceneSerializer-VY3HY4PF.js.map +0 -7
- package/dist/react/chunk-2I3GH65Z.js.map +0 -7
- package/dist/react/chunk-6G3ITAS5.js.map +0 -7
- package/dist/react/chunk-75G7XSZU.js.map +0 -7
- package/dist/react/chunk-7VBI7S7T.js.map +0 -7
- package/dist/react/chunk-C62SXFBL.js.map +0 -7
- package/dist/react/chunk-MYYBFZD3.js.map +0 -7
- package/dist/react/chunk-R5QXEVZ5.js.map +0 -7
- /package/dist/react/{EventDispatcher-XDRQS7BD.js.map → EventDispatcher-O7HCQQNM.js.map} +0 -0
- /package/dist/react/{Frame-QLBAVLGW.js.map → Frame-RLSMJJSK.js.map} +0 -0
- /package/dist/react/{IconShape-32EYI2MA.js.map → IconShape-7OPXAKLK.js.map} +0 -0
- /package/dist/react/{ImageShape-MYP2DVVH.js.map → ImageShape-LRNJQCF3.js.map} +0 -0
- /package/dist/react/{Line-FCCF2BOB.js.map → Line-WWQFBNX5.js.map} +0 -0
- /package/dist/react/{Selection-U2AR6NQS.js.map → Selection-ZL3G6IAO.js.map} +0 -0
- /package/dist/react/{SketchEngine-ZFYUTKET.js.map → SketchEngine-BFLHJPCG.js.map} +0 -0
- /package/dist/react/{UndoRedo-BZ6W4QTV.js.map → UndoRedo-NL7KLR5B.js.map} +0 -0
- /package/dist/react/{arrowTool-LKFSZKXC.js.map → arrowTool-CNVGM6RX.js.map} +0 -0
- /package/dist/react/{chunk-SBSM3GQD.js.map → chunk-2THKDSWW.js.map} +0 -0
- /package/dist/react/{chunk-JOPQ27YH.js.map → chunk-4BLMNGTR.js.map} +0 -0
- /package/dist/react/{chunk-2V7G2PLX.js.map → chunk-GPIYXREE.js.map} +0 -0
- /package/dist/react/{chunk-73VLPCBC.js.map → chunk-HFQZYCHJ.js.map} +0 -0
- /package/dist/react/{chunk-C72OHQLE.js.map → chunk-ILGAFR56.js.map} +0 -0
- /package/dist/react/{chunk-WEWMB35W.js.map → chunk-OFGJJKPH.js.map} +0 -0
- /package/dist/react/{chunk-UQBT7F4K.js.map → chunk-V2QTYSBK.js.map} +0 -0
- /package/dist/react/{chunk-O64N5JPO.js.map → chunk-YF3TK7SC.js.map} +0 -0
- /package/dist/react/{circleTool-RDOIMKKV.js.map → circleTool-KICNRQCI.js.map} +0 -0
- /package/dist/react/{codeTool-VBDB2ES6.js.map → codeTool-NCQ73R6M.js.map} +0 -0
- /package/dist/react/{frameTool-Z5ZDCX6H.js.map → frameTool-Y6KB67DO.js.map} +0 -0
- /package/dist/react/{freehandTool-AAQCTMVM.js.map → freehandTool-MMULHCQ7.js.map} +0 -0
- /package/dist/react/{iconTool-5WNDHI5T.js.map → iconTool-LSW2DYGB.js.map} +0 -0
- /package/dist/react/{imageTool-ECPMT3BL.js.map → imageTool-2DSR32RX.js.map} +0 -0
- /package/dist/react/{lineTool-XLOOZE3E.js.map → lineTool-N5I7IGT7.js.map} +0 -0
- /package/dist/react/{rectangleTool-AEYDDJCA.js.map → rectangleTool-YUT7ZK2E.js.map} +0 -0
- /package/dist/react/{textTool-5IJKTWVQ.js.map → textTool-IQOYPELP.js.map} +0 -0
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
handleMouseDown,
|
|
4
4
|
handleMouseMove,
|
|
5
5
|
handleMouseUp
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-ILGAFR56.js";
|
|
7
|
+
import "./chunk-YF3TK7SC.js";
|
|
8
|
+
import "./chunk-U3TB4KRS.js";
|
|
9
9
|
export {
|
|
10
10
|
handleMouseDown as handleMouseDownLine,
|
|
11
11
|
handleMouseMove as handleMouseMoveLine,
|
|
12
12
|
handleMouseUp as handleMouseUpLine
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=lineTool-
|
|
14
|
+
//# sourceMappingURL=lineTool-N5I7IGT7.js.map
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
handleMouseDownRect,
|
|
4
4
|
handleMouseMoveRect,
|
|
5
5
|
handleMouseUpRect
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-4BLMNGTR.js";
|
|
7
|
+
import "./chunk-YF3TK7SC.js";
|
|
8
|
+
import "./chunk-U3TB4KRS.js";
|
|
9
9
|
export {
|
|
10
10
|
handleMouseDownRect,
|
|
11
11
|
handleMouseMoveRect,
|
|
12
12
|
handleMouseUpRect
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=rectangleTool-
|
|
14
|
+
//# sourceMappingURL=rectangleTool-YUT7ZK2E.js.map
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
handleTextMouseMove,
|
|
7
7
|
handleTextMouseUp,
|
|
8
8
|
updateCodeToggleForShape
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-2THKDSWW.js";
|
|
10
|
+
import "./chunk-HFQZYCHJ.js";
|
|
11
11
|
import "./chunk-PAKA6HUX.js";
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-YF3TK7SC.js";
|
|
13
|
+
import "./chunk-U3TB4KRS.js";
|
|
14
14
|
export {
|
|
15
15
|
deselectElement as deselectTextElement,
|
|
16
16
|
enterEditMode,
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
handleTextMouseUp,
|
|
20
20
|
updateCodeToggleForShape
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=textTool-
|
|
22
|
+
//# sourceMappingURL=textTool-IQOYPELP.js.map
|
package/package.json
CHANGED
package/src/core/CopyPaste.js
CHANGED
|
@@ -147,7 +147,13 @@ function serializeShape(shape) {
|
|
|
147
147
|
width: shape.width,
|
|
148
148
|
height: shape.height,
|
|
149
149
|
rotation: shape.rotation,
|
|
150
|
-
href: el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || ''
|
|
150
|
+
href: el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || '',
|
|
151
|
+
storageProvider: el.getAttribute('data-storage-provider') || null,
|
|
152
|
+
storageCloudName: el.getAttribute('data-storage-cloud-name') || null,
|
|
153
|
+
frameType: shape._frameType || null,
|
|
154
|
+
graphData: shape._frameType === 'graph' && shape._graphData
|
|
155
|
+
? cloneOptions(shape._graphData)
|
|
156
|
+
: null
|
|
151
157
|
};
|
|
152
158
|
}
|
|
153
159
|
|
|
@@ -383,11 +389,22 @@ function createShapeFromData(data, offsetX, offsetY) {
|
|
|
383
389
|
imgEl.setAttribute('data-shape-height', data.height);
|
|
384
390
|
imgEl.setAttribute('preserveAspectRatio', 'none');
|
|
385
391
|
imgEl.setAttribute('style', 'cursor: pointer;');
|
|
392
|
+
if (data.storageProvider) imgEl.setAttribute('data-storage-provider', data.storageProvider);
|
|
393
|
+
if (data.storageCloudName) imgEl.setAttribute('data-storage-cloud-name', data.storageCloudName);
|
|
386
394
|
|
|
387
395
|
// ImageShape constructor moves element into a group and appends to svg
|
|
388
396
|
svgEl.appendChild(imgEl);
|
|
389
397
|
const imageShape = new ImageShape(imgEl);
|
|
390
398
|
imageShape.rotation = data.rotation;
|
|
399
|
+
if (data.frameType === 'graph' && data.graphData) {
|
|
400
|
+
if (typeof window.__hydrateGraphShape === 'function') {
|
|
401
|
+
window.__hydrateGraphShape(imageShape, data.graphData);
|
|
402
|
+
} else {
|
|
403
|
+
imageShape._frameType = 'graph';
|
|
404
|
+
imageShape._graphData = cloneOptions(data.graphData);
|
|
405
|
+
imageShape.element.setAttribute('data-graph-shape', 'true');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
391
408
|
return imageShape;
|
|
392
409
|
}
|
|
393
410
|
|
|
@@ -605,15 +622,25 @@ function handlePasteEvent(e) {
|
|
|
605
622
|
const rawDataUrl = ev.target.result;
|
|
606
623
|
const isSvg = (blob.type || '').toLowerCase() === 'image/svg+xml';
|
|
607
624
|
let placedDataUrl = rawDataUrl;
|
|
625
|
+
let placedSize = blob.size;
|
|
608
626
|
if (!isSvg) {
|
|
609
627
|
try {
|
|
610
628
|
const compressed = await compressImage(rawDataUrl);
|
|
611
|
-
if (compressed?.dataUrl)
|
|
629
|
+
if (compressed?.dataUrl) {
|
|
630
|
+
placedDataUrl = compressed.dataUrl;
|
|
631
|
+
placedSize = compressed.compressedSize || placedSize;
|
|
632
|
+
}
|
|
612
633
|
} catch (err) {
|
|
613
634
|
console.warn('[CopyPaste] Pre-placement compression failed, using raw:', err);
|
|
614
635
|
}
|
|
615
636
|
}
|
|
616
|
-
|
|
637
|
+
const limit = Number(window.__roomImageLimitBytes) || 2 * 1024 * 1024;
|
|
638
|
+
const personalStorage = window.__personalCloudinary?.connected && window.__personalCloudinary?.useForUploads;
|
|
639
|
+
if (!personalStorage && (window.__roomImageBytesUsed || 0) + placedSize > limit) {
|
|
640
|
+
alert(`Workspace image limit reached (${Math.round(limit / (1024 * 1024))} MB).`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
placeImageFromDataUrl(placedDataUrl, placedSize);
|
|
617
644
|
};
|
|
618
645
|
reader.readAsDataURL(blob);
|
|
619
646
|
return; // only handle first image
|
|
@@ -621,7 +648,7 @@ function handlePasteEvent(e) {
|
|
|
621
648
|
}
|
|
622
649
|
}
|
|
623
650
|
|
|
624
|
-
function placeImageFromDataUrl(dataUrl) {
|
|
651
|
+
function placeImageFromDataUrl(dataUrl, sizeBytes = 0) {
|
|
625
652
|
const svgEl = getSVGElement();
|
|
626
653
|
if (!svgEl || !window.ImageShape) return;
|
|
627
654
|
|
|
@@ -648,6 +675,9 @@ function placeImageFromDataUrl(dataUrl) {
|
|
|
648
675
|
imgEl.setAttribute('data-shape-height', displayH);
|
|
649
676
|
imgEl.setAttribute('type', 'image');
|
|
650
677
|
imgEl.setAttribute('preserveAspectRatio', 'none');
|
|
678
|
+
imgEl.__fileSize = sizeBytes;
|
|
679
|
+
imgEl.setAttribute('data-file-size', String(sizeBytes));
|
|
680
|
+
window.__roomImageBytesUsed = (window.__roomImageBytesUsed || 0) + sizeBytes;
|
|
651
681
|
|
|
652
682
|
svgEl.appendChild(imgEl);
|
|
653
683
|
const imageShape = new ImageShape(imgEl);
|
package/src/core/GraphEngine.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
/**
|
|
3
|
-
* GraphEngine -
|
|
4
|
-
*
|
|
3
|
+
* GraphEngine - bridge between graph rendering and the sketch canvas.
|
|
4
|
+
*
|
|
5
|
+
* Canvas graphs use ImageShape with an SVG data URL. This keeps the graph a
|
|
6
|
+
* single crisp vector element while reusing the engine's established select,
|
|
7
|
+
* translate, resize, rotate, copy/paste, and frame-containment behaviour.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
import { parseExpression, isValidExpression } from './GraphMathParser.js';
|
|
@@ -10,209 +13,150 @@ import { renderGraphSVG, renderGraphPreviewSVG, GRAPH_COLORS } from './GraphRend
|
|
|
10
13
|
const NS = 'http://www.w3.org/2000/svg';
|
|
11
14
|
const GRAPH_WIDTH = 600;
|
|
12
15
|
const GRAPH_HEIGHT = 420;
|
|
16
|
+
const MAX_PLACED_SCREEN_WIDTH = 480;
|
|
17
|
+
const MAX_VIEWPORT_WIDTH_RATIO = 0.68;
|
|
18
|
+
const MAX_VIEWPORT_HEIGHT_RATIO = 0.58;
|
|
19
|
+
|
|
20
|
+
function cloneGraphData(equations, settings) {
|
|
21
|
+
return {
|
|
22
|
+
equations: (equations || []).map((equation) => ({
|
|
23
|
+
expression: equation.expression,
|
|
24
|
+
color: equation.color,
|
|
25
|
+
})),
|
|
26
|
+
settings: { ...(settings || {}) },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function graphSvgDataUrl(svgMarkup) {
|
|
31
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgMarkup)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function refreshGraphImage(shape) {
|
|
35
|
+
if (!shape?._graphData || !shape.element) return false;
|
|
36
|
+
const svgMarkup = renderGraphSVG(shape._graphData.equations, {
|
|
37
|
+
...shape._graphData.settings,
|
|
38
|
+
width: GRAPH_WIDTH,
|
|
39
|
+
height: GRAPH_HEIGHT,
|
|
40
|
+
});
|
|
41
|
+
if (!svgMarkup) return false;
|
|
42
|
+
const href = graphSvgDataUrl(svgMarkup);
|
|
43
|
+
shape.element.setAttribute('href', href);
|
|
44
|
+
shape.element.setAttribute('data-href', href);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Attach graph behaviour to a regular ImageShape, including restored and
|
|
49
|
+
* pasted graphs. The function is intentionally idempotent. */
|
|
50
|
+
function hydrateGraphShape(shape, graphData) {
|
|
51
|
+
if (!shape?.element || !graphData) return shape;
|
|
52
|
+
shape._frameType = 'graph';
|
|
53
|
+
shape._graphData = cloneGraphData(graphData.equations, graphData.settings);
|
|
54
|
+
shape.uploadStatus = 'done';
|
|
55
|
+
shape.element.setAttribute('data-graph-shape', 'true');
|
|
56
|
+
shape.element.setAttribute('preserveAspectRatio', 'none');
|
|
57
|
+
shape.element.style.cursor = 'move';
|
|
58
|
+
shape._refreshGraphImage = () => refreshGraphImage(shape);
|
|
59
|
+
shape.adaptToBackground = () => refreshGraphImage(shape);
|
|
60
|
+
return shape;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function selectOnlyGraph(shape) {
|
|
64
|
+
if (!shape) return;
|
|
65
|
+
|
|
66
|
+
if (window.multiSelection?.selectedShapes?.size) {
|
|
67
|
+
window.multiSelection.clearSelection();
|
|
68
|
+
}
|
|
69
|
+
if (window.currentShape && window.currentShape !== shape
|
|
70
|
+
&& typeof window.currentShape.removeSelection === 'function') {
|
|
71
|
+
window.currentShape.removeSelection();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Generated content always lands ready to manipulate. Use the store bridge
|
|
75
|
+
// so the toolbar and legacy engine flags change together.
|
|
76
|
+
window.__sketchStoreApi?.setActiveTool?.('select');
|
|
77
|
+
window.currentShape = shape;
|
|
78
|
+
shape.selectShape?.();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function placedGraphSize(viewBox) {
|
|
82
|
+
const zoom = Math.max(0.001, Number(window.currentZoom) || 1);
|
|
83
|
+
const byScreen = MAX_PLACED_SCREEN_WIDTH / zoom;
|
|
84
|
+
const byViewportWidth = viewBox.width * MAX_VIEWPORT_WIDTH_RATIO;
|
|
85
|
+
const byViewportHeight = viewBox.height * MAX_VIEWPORT_HEIGHT_RATIO * (GRAPH_WIDTH / GRAPH_HEIGHT);
|
|
86
|
+
const width = Math.max(80 / zoom, Math.min(GRAPH_WIDTH, byScreen, byViewportWidth, byViewportHeight));
|
|
87
|
+
return { width, height: width * (GRAPH_HEIGHT / GRAPH_WIDTH) };
|
|
88
|
+
}
|
|
13
89
|
|
|
14
90
|
/**
|
|
15
|
-
* Place a graph
|
|
91
|
+
* Place a graph as one editable vector-image shape, or update an existing
|
|
92
|
+
* graph in place without losing its canvas position, size, or rotation.
|
|
16
93
|
*/
|
|
17
|
-
function renderGraphOnCanvas(equations, settings) {
|
|
18
|
-
|
|
19
|
-
if (
|
|
94
|
+
function renderGraphOnCanvas(equations, settings, existingShape = null) {
|
|
95
|
+
const validEquations = (equations || []).filter((equation) => equation?.expression?.trim());
|
|
96
|
+
if (validEquations.length === 0) return false;
|
|
97
|
+
if (!window.svg || !window.ImageShape) {
|
|
20
98
|
console.error('[GraphEngine] Engine not initialized');
|
|
21
99
|
return false;
|
|
22
100
|
}
|
|
23
101
|
|
|
24
|
-
|
|
25
|
-
const svgMarkup = renderGraphSVG(equations, {
|
|
26
|
-
...settings,
|
|
27
|
-
width: GRAPH_WIDTH,
|
|
28
|
-
height: GRAPH_HEIGHT,
|
|
29
|
-
});
|
|
30
|
-
if (!svgMarkup) return false;
|
|
102
|
+
const graphData = cloneGraphData(validEquations, settings);
|
|
31
103
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Build title from equations
|
|
43
|
-
const eqLabels = equations
|
|
44
|
-
.filter(eq => eq.expression && eq.expression.trim())
|
|
45
|
-
.map(eq => eq.expression.trim())
|
|
46
|
-
.slice(0, 3);
|
|
47
|
-
const title = eqLabels.length > 0
|
|
48
|
-
? 'Graph: ' + eqLabels.join(', ')
|
|
49
|
-
: 'Graph';
|
|
50
|
-
|
|
51
|
-
// Phase 5 (issue #22): no wrapper frame. The graph is conceptually a
|
|
52
|
-
// single rendering — axes, grid, curves all interlocked — so we don't
|
|
53
|
-
// split it into per-curve shapes. Instead the graph becomes ONE
|
|
54
|
-
// first-class shape on the canvas: a `graphShape` that implements the
|
|
55
|
-
// shape API the engine relies on (contains / selectShape / etc.) so
|
|
56
|
-
// it can be selected, dragged, attached to by arrows, picked up by
|
|
57
|
-
// the rect-drag — exactly like a user-drawn shape.
|
|
58
|
-
try {
|
|
59
|
-
const parser = new DOMParser();
|
|
60
|
-
const doc = parser.parseFromString(svgMarkup, 'image/svg+xml');
|
|
61
|
-
const svgEl = doc.querySelector('svg');
|
|
62
|
-
if (!svgEl) return false;
|
|
63
|
-
|
|
64
|
-
const graphGroup = document.createElementNS(NS, 'g');
|
|
65
|
-
graphGroup.setAttribute('data-type', 'graph-group');
|
|
66
|
-
graphGroup.setAttribute('transform', `translate(${frameX + 20}, ${frameY + 20})`);
|
|
67
|
-
|
|
68
|
-
// Copy defs (clip paths)
|
|
69
|
-
const defs = svgEl.querySelector('defs');
|
|
70
|
-
if (defs) {
|
|
71
|
-
const defsClone = defs.cloneNode(true);
|
|
72
|
-
window.svg.querySelector('defs')?.appendChild(defsClone.firstChild) ||
|
|
73
|
-
window.svg.insertBefore(defsClone, window.svg.firstChild);
|
|
74
|
-
}
|
|
104
|
+
if (existingShape?._frameType === 'graph' && existingShape.element) {
|
|
105
|
+
hydrateGraphShape(existingShape, graphData);
|
|
106
|
+
if (!refreshGraphImage(existingShape)) return false;
|
|
107
|
+
selectOnlyGraph(existingShape);
|
|
108
|
+
window.__adaptCanvasContrast?.(
|
|
109
|
+
window.getComputedStyle(window.svg).backgroundColor || '#15111f'
|
|
110
|
+
);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
75
113
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
const viewBox = window.currentViewBox || {
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0,
|
|
117
|
+
width: window.innerWidth,
|
|
118
|
+
height: window.innerHeight,
|
|
119
|
+
};
|
|
120
|
+
const size = placedGraphSize(viewBox);
|
|
121
|
+
const x = viewBox.x + (viewBox.width - size.width) / 2;
|
|
122
|
+
const y = viewBox.y + (viewBox.height - size.height) / 2;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const image = document.createElementNS(NS, 'image');
|
|
126
|
+
image.setAttribute('x', x);
|
|
127
|
+
image.setAttribute('y', y);
|
|
128
|
+
image.setAttribute('width', size.width);
|
|
129
|
+
image.setAttribute('height', size.height);
|
|
130
|
+
image.setAttribute('data-shape-x', x);
|
|
131
|
+
image.setAttribute('data-shape-y', y);
|
|
132
|
+
image.setAttribute('data-shape-width', size.width);
|
|
133
|
+
image.setAttribute('data-shape-height', size.height);
|
|
134
|
+
|
|
135
|
+
const graphShape = hydrateGraphShape(new window.ImageShape(image), graphData);
|
|
136
|
+
if (!refreshGraphImage(graphShape)) {
|
|
137
|
+
graphShape.group?.remove();
|
|
138
|
+
return false;
|
|
80
139
|
}
|
|
81
|
-
window.svg.appendChild(graphGroup);
|
|
82
|
-
|
|
83
|
-
const graphShape = {
|
|
84
|
-
shapeName: 'graph', // first-class shapeName
|
|
85
|
-
shapeID: `graph-${Date.now().toString(36)}-${Math.floor(Math.random() * 10000)}`,
|
|
86
|
-
group: graphGroup,
|
|
87
|
-
element: graphGroup,
|
|
88
|
-
x: frameX + 20,
|
|
89
|
-
y: frameY + 20,
|
|
90
|
-
width: GRAPH_WIDTH,
|
|
91
|
-
height: GRAPH_HEIGHT,
|
|
92
|
-
rotation: 0,
|
|
93
|
-
isSelected: false,
|
|
94
|
-
_selectionRect: null,
|
|
95
|
-
_graphData: {
|
|
96
|
-
equations: equations.map(eq => ({ expression: eq.expression, color: eq.color })),
|
|
97
|
-
settings: { ...settings },
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
// ── Shape API the engine relies on ────────────────────────
|
|
101
|
-
contains(px, py) {
|
|
102
|
-
return px >= this.x && px <= this.x + this.width
|
|
103
|
-
&& py >= this.y && py <= this.y + this.height;
|
|
104
|
-
},
|
|
105
|
-
move(dx, dy) {
|
|
106
|
-
this.x += dx; this.y += dy;
|
|
107
|
-
this.group.setAttribute('transform', `translate(${this.x}, ${this.y})`);
|
|
108
|
-
this._updateSelectionRect();
|
|
109
|
-
},
|
|
110
|
-
selectShape() {
|
|
111
|
-
this.isSelected = true;
|
|
112
|
-
if (this._selectionRect) return;
|
|
113
|
-
const r = document.createElementNS(NS, 'rect');
|
|
114
|
-
r.setAttribute('fill', 'none');
|
|
115
|
-
r.setAttribute('stroke', '#9b7bf7');
|
|
116
|
-
r.setAttribute('stroke-width', '1.5');
|
|
117
|
-
r.setAttribute('stroke-dasharray', '4 3');
|
|
118
|
-
r.setAttribute('pointer-events', 'none');
|
|
119
|
-
window.svg.appendChild(r);
|
|
120
|
-
this._selectionRect = r;
|
|
121
|
-
this._updateSelectionRect();
|
|
122
|
-
},
|
|
123
|
-
removeSelection() {
|
|
124
|
-
this.isSelected = false;
|
|
125
|
-
if (this._selectionRect && this._selectionRect.parentNode) {
|
|
126
|
-
this._selectionRect.parentNode.removeChild(this._selectionRect);
|
|
127
|
-
}
|
|
128
|
-
this._selectionRect = null;
|
|
129
|
-
},
|
|
130
|
-
_updateSelectionRect() {
|
|
131
|
-
if (!this._selectionRect) return;
|
|
132
|
-
const pad = 4;
|
|
133
|
-
this._selectionRect.setAttribute('x', this.x - pad);
|
|
134
|
-
this._selectionRect.setAttribute('y', this.y - pad);
|
|
135
|
-
this._selectionRect.setAttribute('width', this.width + pad * 2);
|
|
136
|
-
this._selectionRect.setAttribute('height', this.height + pad * 2);
|
|
137
|
-
},
|
|
138
|
-
updateAttachedArrows() {
|
|
139
|
-
if (typeof window.updateAttachedArrows === 'function') {
|
|
140
|
-
window.updateAttachedArrows(this);
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
adaptToBackground(background, foreground, muted) {
|
|
144
|
-
const ensureContrast = window.__ensureCanvasContrast || ((color) => color);
|
|
145
|
-
|
|
146
|
-
this.group.querySelectorAll('rect').forEach((rect) => {
|
|
147
|
-
const fill = rect.getAttribute('fill');
|
|
148
|
-
const stroke = rect.getAttribute('stroke');
|
|
149
|
-
if (fill && fill !== 'none') rect.setAttribute('fill', background);
|
|
150
|
-
if (stroke && stroke !== 'none') {
|
|
151
|
-
rect.setAttribute('stroke', muted);
|
|
152
|
-
rect.setAttribute('stroke-opacity', '0.45');
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
this.group.querySelectorAll('line').forEach((line) => {
|
|
157
|
-
const width = Number.parseFloat(line.getAttribute('stroke-width') || '1');
|
|
158
|
-
line.setAttribute('stroke', foreground);
|
|
159
|
-
line.setAttribute('stroke-opacity', width <= 0.5 ? '0.18' : '0.5');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
this.group.querySelectorAll('path[stroke]').forEach((path) => {
|
|
163
|
-
const original = path.dataset.graphColor || path.getAttribute('stroke');
|
|
164
|
-
if (!original || original === 'none') return;
|
|
165
|
-
path.dataset.graphColor = original;
|
|
166
|
-
path.setAttribute('stroke', ensureContrast(original, background, 3));
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
this.group.querySelectorAll('circle[fill]').forEach((circle) => {
|
|
170
|
-
const original = circle.dataset.graphColor || circle.getAttribute('fill');
|
|
171
|
-
if (!original || original === 'none') return;
|
|
172
|
-
circle.dataset.graphColor = original;
|
|
173
|
-
circle.setAttribute('fill', ensureContrast(original, background, 3));
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
this.group.querySelectorAll('text').forEach((text) => {
|
|
177
|
-
const original = text.dataset.graphColor || text.getAttribute('fill');
|
|
178
|
-
if (original) text.dataset.graphColor = original;
|
|
179
|
-
const isAxisLabel = !original || ['#8b949e', '#62627a'].includes(original.toLowerCase());
|
|
180
|
-
text.setAttribute('fill', isAxisLabel
|
|
181
|
-
? muted
|
|
182
|
-
: ensureContrast(original, background, 4.5));
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
140
|
|
|
187
141
|
window.shapes.push(graphShape);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Auto-select so the user sees something landed.
|
|
191
|
-
window.currentShape = graphShape;
|
|
192
|
-
graphShape.selectShape();
|
|
142
|
+
window.pushCreateAction?.(graphShape);
|
|
143
|
+
selectOnlyGraph(graphShape);
|
|
193
144
|
window.__adaptCanvasContrast?.(
|
|
194
|
-
window.getComputedStyle(window.svg).backgroundColor || '#
|
|
145
|
+
window.getComputedStyle(window.svg).backgroundColor || '#15111f'
|
|
195
146
|
);
|
|
196
|
-
|
|
197
|
-
|
|
147
|
+
return true;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[GraphEngine] SVG insertion failed:', error);
|
|
198
150
|
return false;
|
|
199
151
|
}
|
|
200
|
-
|
|
201
|
-
return true;
|
|
202
152
|
}
|
|
203
153
|
|
|
204
|
-
/**
|
|
205
|
-
* Initialize the graph engine — expose bridge functions on window.
|
|
206
|
-
*/
|
|
154
|
+
/** Initialize graph bridges used by the modal and scene restoration. */
|
|
207
155
|
export function initGraphEngine() {
|
|
208
|
-
window.__graphPreview = (equations, settings) =>
|
|
209
|
-
return renderGraphPreviewSVG(equations, settings);
|
|
210
|
-
};
|
|
156
|
+
window.__graphPreview = (equations, settings) => renderGraphPreviewSVG(equations, settings);
|
|
211
157
|
window.__graphRenderer = renderGraphOnCanvas;
|
|
212
|
-
window.
|
|
213
|
-
|
|
214
|
-
return fn ? true : false;
|
|
215
|
-
};
|
|
158
|
+
window.__hydrateGraphShape = hydrateGraphShape;
|
|
159
|
+
window.__graphParser = (expression) => Boolean(parseExpression(expression));
|
|
216
160
|
window.__graphValidate = isValidExpression;
|
|
217
161
|
window.__graphColors = GRAPH_COLORS;
|
|
218
162
|
}
|
|
@@ -180,6 +180,14 @@ function serializeShape(shape) {
|
|
|
180
180
|
width: shape.width, height: shape.height,
|
|
181
181
|
rotation: shape.rotation,
|
|
182
182
|
href: el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || '',
|
|
183
|
+
sizeBytes: Number(el.__fileSize || el.getAttribute('data-file-size')) || 0,
|
|
184
|
+
cloudinaryId: el.getAttribute('data-cloudinary-id') || null,
|
|
185
|
+
storageProvider: el.getAttribute('data-storage-provider') || 'platform_cloudinary',
|
|
186
|
+
storageCloudName: el.getAttribute('data-storage-cloud-name') || null,
|
|
187
|
+
frameType: shape._frameType || null,
|
|
188
|
+
graphData: shape._frameType === 'graph' && shape._graphData
|
|
189
|
+
? cloneOptions(shape._graphData)
|
|
190
|
+
: null,
|
|
183
191
|
};
|
|
184
192
|
}
|
|
185
193
|
|
|
@@ -300,10 +308,27 @@ function deserializeShape(data) {
|
|
|
300
308
|
imgEl.setAttribute('height', data.height);
|
|
301
309
|
imgEl.setAttribute('href', data.href);
|
|
302
310
|
imgEl.setAttribute('preserveAspectRatio', 'none');
|
|
311
|
+
if (data.sizeBytes) {
|
|
312
|
+
imgEl.__fileSize = Number(data.sizeBytes) || 0;
|
|
313
|
+
imgEl.setAttribute('data-file-size', String(imgEl.__fileSize));
|
|
314
|
+
window.__roomImageBytesUsed = (window.__roomImageBytesUsed || 0) + imgEl.__fileSize;
|
|
315
|
+
}
|
|
316
|
+
if (data.cloudinaryId) imgEl.setAttribute('data-cloudinary-id', data.cloudinaryId);
|
|
317
|
+
if (data.storageProvider) imgEl.setAttribute('data-storage-provider', data.storageProvider);
|
|
318
|
+
if (data.storageCloudName) imgEl.setAttribute('data-storage-cloud-name', data.storageCloudName);
|
|
303
319
|
svgEl.appendChild(imgEl);
|
|
304
320
|
const shape = new ImageShape(imgEl);
|
|
305
321
|
if (data.rotation) shape.rotation = data.rotation;
|
|
306
322
|
if (data.shapeID) shape.shapeID = data.shapeID;
|
|
323
|
+
if (data.frameType === 'graph' && data.graphData) {
|
|
324
|
+
if (typeof window.__hydrateGraphShape === 'function') {
|
|
325
|
+
window.__hydrateGraphShape(shape, data.graphData);
|
|
326
|
+
} else {
|
|
327
|
+
shape._frameType = 'graph';
|
|
328
|
+
shape._graphData = cloneOptions(data.graphData);
|
|
329
|
+
shape.element.setAttribute('data-graph-shape', 'true');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
307
332
|
return shape;
|
|
308
333
|
}
|
|
309
334
|
|
|
@@ -387,6 +412,7 @@ export function loadScene(sceneData) {
|
|
|
387
412
|
// Clear current scene
|
|
388
413
|
const svgEl = window.svg;
|
|
389
414
|
if (!svgEl) return false;
|
|
415
|
+
window.__roomImageBytesUsed = 0;
|
|
390
416
|
|
|
391
417
|
// Remove all existing shape DOM elements
|
|
392
418
|
const existingShapes = window.shapes || [];
|
package/src/core/Selection.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Multi-selection system - copied from selection.js
|
|
3
3
|
|
|
4
4
|
import { cleanupAttachments } from '../tools/arrowTool.js';
|
|
5
|
-
import { pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
5
|
+
import { pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
6
6
|
import { calculateSnap, clearSnapGuides } from './SnapGuides.js';
|
|
7
7
|
import { registerRotationAnchor } from './ScreenSpaceControls.js';
|
|
8
8
|
|
|
@@ -1943,6 +1943,11 @@ function deleteSelectedShapes() {
|
|
|
1943
1943
|
function frameSelectedShapes() {
|
|
1944
1944
|
if (multiSelection.selectedShapes.size < 2) return;
|
|
1945
1945
|
|
|
1946
|
+
const shapesToFrame = Array.from(multiSelection.selectedShapes);
|
|
1947
|
+
// Frames cannot be nested through the Frame it action, and objects that
|
|
1948
|
+
// already belong to a frame must first be detached explicitly.
|
|
1949
|
+
if (shapesToFrame.some(shape => shape?.shapeName === 'frame' || shape?.parentFrame)) return;
|
|
1950
|
+
|
|
1946
1951
|
const bounds = multiSelection.getBounds();
|
|
1947
1952
|
if (!bounds) return;
|
|
1948
1953
|
|
|
@@ -1962,17 +1967,7 @@ function frameSelectedShapes() {
|
|
|
1962
1967
|
const frame = new FrameClass(fx, fy, fw, fh);
|
|
1963
1968
|
shapes.push(frame);
|
|
1964
1969
|
|
|
1965
|
-
// Push undo action for frame creation
|
|
1966
|
-
if (window.historyStack) {
|
|
1967
|
-
window.historyStack.push({
|
|
1968
|
-
type: window.ACTION_CREATE || 'create',
|
|
1969
|
-
shape: frame,
|
|
1970
|
-
shapeName: 'frame'
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
1970
|
// Add each selected shape into the frame
|
|
1975
|
-
const shapesToFrame = Array.from(multiSelection.selectedShapes);
|
|
1976
1971
|
multiSelection.clearSelection();
|
|
1977
1972
|
|
|
1978
1973
|
for (const shape of shapesToFrame) {
|
|
@@ -1996,6 +1991,12 @@ function frameSelectedShapes() {
|
|
|
1996
1991
|
}
|
|
1997
1992
|
}
|
|
1998
1993
|
|
|
1994
|
+
// Record after reordering so redo restores the same hit-test/layer index.
|
|
1995
|
+
pushCreateAction(frame, {
|
|
1996
|
+
frameCreation: true,
|
|
1997
|
+
containedShapes: shapesToFrame
|
|
1998
|
+
});
|
|
1999
|
+
|
|
1999
2000
|
// Select the new frame
|
|
2000
2001
|
if (typeof frame.selectFrame === 'function') {
|
|
2001
2002
|
frame.selectFrame();
|
package/src/core/UndoRedo.js
CHANGED
|
@@ -34,11 +34,17 @@ export function updateSelectedElement(element) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Call this after creating a new shape (e.g., Rectangle)
|
|
37
|
-
export function pushCreateAction(shape) {
|
|
38
|
-
|
|
37
|
+
export function pushCreateAction(shape, meta = null) {
|
|
38
|
+
const action = {
|
|
39
39
|
type: 'create',
|
|
40
40
|
shape: shape
|
|
41
|
-
}
|
|
41
|
+
};
|
|
42
|
+
if (meta?.frameCreation && shape?.shapeName === 'frame') {
|
|
43
|
+
action.frameCreation = true;
|
|
44
|
+
action.containedShapes = [...(meta.containedShapes || shape.containedShapes || [])];
|
|
45
|
+
action.shapeIndex = Array.isArray(shapes) ? shapes.indexOf(shape) : -1;
|
|
46
|
+
}
|
|
47
|
+
undoStack.push(action);
|
|
42
48
|
|
|
43
49
|
// Clear redo stack when new action is performed
|
|
44
50
|
redoStack.length = 0;
|
|
@@ -567,6 +573,23 @@ export function undo() {
|
|
|
567
573
|
}
|
|
568
574
|
|
|
569
575
|
if (action.type === 'create') {
|
|
576
|
+
if (action.frameCreation && action.shape.shapeName === 'frame') {
|
|
577
|
+
// Treat a manually-created frame and its initial membership as one
|
|
578
|
+
// atomic action. Children remain on the canvas when the frame is
|
|
579
|
+
// undone and are clipped back into it on redo.
|
|
580
|
+
for (const child of action.containedShapes || []) {
|
|
581
|
+
if (child?.parentFrame === action.shape) action.shape.removeShapeFromFrame(child);
|
|
582
|
+
}
|
|
583
|
+
if (currentShape === action.shape) {
|
|
584
|
+
action.shape.removeSelection?.();
|
|
585
|
+
currentShape = null;
|
|
586
|
+
}
|
|
587
|
+
const idx = shapes.indexOf(action.shape);
|
|
588
|
+
if (idx !== -1) shapes.splice(idx, 1);
|
|
589
|
+
action.shape.group?.remove();
|
|
590
|
+
action.shape.clipGroup?.remove();
|
|
591
|
+
action.shape.clipPath?.remove();
|
|
592
|
+
} else
|
|
570
593
|
if (action.shape.type === 'text') {
|
|
571
594
|
// Handle text creation undo
|
|
572
595
|
if (action.shape.element && action.shape.element.parentNode) {
|
|
@@ -1049,6 +1072,27 @@ export function redo() {
|
|
|
1049
1072
|
}
|
|
1050
1073
|
|
|
1051
1074
|
if (action.type === 'create') {
|
|
1075
|
+
if (action.frameCreation && action.shape.shapeName === 'frame') {
|
|
1076
|
+
const frame = action.shape;
|
|
1077
|
+
let defs = svg?.querySelector('defs');
|
|
1078
|
+
if (!defs && svg) {
|
|
1079
|
+
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
1080
|
+
svg.appendChild(defs);
|
|
1081
|
+
}
|
|
1082
|
+
if (defs && frame.clipPath && !frame.clipPath.parentNode) defs.appendChild(frame.clipPath);
|
|
1083
|
+
if (svg && frame.group && !frame.group.parentNode) svg.appendChild(frame.group);
|
|
1084
|
+
if (svg && frame.clipGroup && !frame.clipGroup.parentNode) svg.appendChild(frame.clipGroup);
|
|
1085
|
+
if (shapes.indexOf(frame) === -1) {
|
|
1086
|
+
const index = action.shapeIndex >= 0 ? Math.min(action.shapeIndex, shapes.length) : shapes.length;
|
|
1087
|
+
shapes.splice(index, 0, frame);
|
|
1088
|
+
}
|
|
1089
|
+
frame.containedShapes = [];
|
|
1090
|
+
frame.draw();
|
|
1091
|
+
frame.updateClipPath();
|
|
1092
|
+
for (const child of action.containedShapes || []) {
|
|
1093
|
+
if (child && shapes.includes(child)) frame.addShapeToFrame(child);
|
|
1094
|
+
}
|
|
1095
|
+
} else
|
|
1052
1096
|
if (action.shape.type === 'text') {
|
|
1053
1097
|
// Handle text creation redo
|
|
1054
1098
|
if (svg) {
|