@elixpo/lixsketch 5.6.9 → 5.6.11
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/{AIRenderer-R5M2CIDO.js → AIRenderer-JNEZYDYQ.js} +4 -4
- package/dist/react/Arrow-QMR77LGQ.js +9 -0
- package/dist/react/Circle-5WL5HZA5.js +11 -0
- package/dist/react/{CopyPaste-PPDS3AD6.js → CopyPaste-V7AY56H3.js} +74 -30
- package/dist/react/CopyPaste-V7AY56H3.js.map +7 -0
- package/dist/react/{EventDispatcher-UV7LFSAS.js → EventDispatcher-E4OJCMY6.js} +154 -57
- package/dist/react/{EventDispatcher-UV7LFSAS.js.map → EventDispatcher-E4OJCMY6.js.map} +3 -3
- package/dist/react/Frame-OZCDVPMI.js +11 -0
- package/dist/react/FreehandStroke-QSSYWBFK.js +9 -0
- package/dist/react/IconShape-HXUV4VRT.js +10 -0
- package/dist/react/ImageShape-JGLT2UGG.js +10 -0
- package/dist/react/Line-YONPLUYT.js +11 -0
- package/dist/react/{MermaidFlowchartRenderer-EL3Q2MNW.js → MermaidFlowchartRenderer-FSH43RUP.js} +26 -7
- package/dist/react/{MermaidFlowchartRenderer-EL3Q2MNW.js.map → MermaidFlowchartRenderer-FSH43RUP.js.map} +2 -2
- package/dist/react/{MermaidSequenceRenderer-K4KHHEVC.js → MermaidSequenceRenderer-FRPNOF2V.js} +11 -9
- package/dist/react/{MermaidSequenceRenderer-K4KHHEVC.js.map → MermaidSequenceRenderer-FRPNOF2V.js.map} +2 -2
- package/dist/react/{MermaidStructuredRenderer-6LN3WISP.js → MermaidStructuredRenderer-BZGQLRIH.js} +13 -6
- package/dist/react/{MermaidStructuredRenderer-6LN3WISP.js.map → MermaidStructuredRenderer-BZGQLRIH.js.map} +2 -2
- package/dist/react/Rectangle-7KMA3SC6.js +11 -0
- package/dist/react/{SceneSerializer-H6KMQCO2.js → SceneSerializer-7O72LEZP.js} +13 -12
- package/dist/react/{SceneSerializer-H6KMQCO2.js.map → SceneSerializer-7O72LEZP.js.map} +1 -1
- package/dist/react/{Selection-CFWRGVKH.js → Selection-S4FYSZIF.js} +5 -5
- package/dist/react/{SketchEngine-UFLZJQXS.js → SketchEngine-VTWURP2K.js} +26 -26
- package/dist/react/{UndoRedo-RXO7YHA2.js → UndoRedo-YLQJ6DXW.js} +8 -2
- package/dist/react/{ZoomPan-RR7KUYZT.js → ZoomPan-AEKWKUCH.js} +2 -2
- package/dist/react/{arrowTool-DO327FKU.js → arrowTool-C4RDWQEX.js} +3 -3
- package/dist/react/{chunk-X5ENBMGL.js → chunk-2U47AU6Y.js} +3 -3
- package/dist/react/{chunk-X5ENBMGL.js.map → chunk-2U47AU6Y.js.map} +1 -1
- package/dist/react/{chunk-RK3NVAER.js → chunk-75OPJOWN.js} +7 -7
- package/dist/react/chunk-75OPJOWN.js.map +7 -0
- package/dist/react/{chunk-DODPOWNX.js → chunk-CWE5IFG7.js} +5 -5
- package/dist/react/{chunk-DODPOWNX.js.map → chunk-CWE5IFG7.js.map} +1 -1
- package/dist/react/{chunk-O7KXLFZN.js → chunk-IS2MKWGH.js} +8 -7
- package/dist/react/chunk-IS2MKWGH.js.map +7 -0
- package/dist/react/{chunk-ZNZAICWY.js → chunk-JEHOEQ6Z.js} +13 -5
- package/dist/react/chunk-JEHOEQ6Z.js.map +7 -0
- package/dist/react/{chunk-BOZEDUAF.js → chunk-JYBJXTLM.js} +40 -15
- package/dist/react/chunk-JYBJXTLM.js.map +7 -0
- package/dist/react/{chunk-6LWUR7XB.js → chunk-LXEHYCKH.js} +4 -4
- package/dist/react/{chunk-6LWUR7XB.js.map → chunk-LXEHYCKH.js.map} +1 -1
- package/dist/react/chunk-M7OYYJC6.js +17 -0
- package/dist/react/chunk-M7OYYJC6.js.map +7 -0
- package/dist/react/chunk-MS4TIQ6Z.js +131 -0
- package/dist/react/chunk-MS4TIQ6Z.js.map +7 -0
- package/dist/react/{chunk-2ALS5JGS.js → chunk-OCJN32IL.js} +2 -2
- package/dist/react/{chunk-KZBKYHZO.js → chunk-P7VIYR6R.js} +8 -5
- package/dist/react/chunk-P7VIYR6R.js.map +7 -0
- package/dist/react/{chunk-KC26KJW4.js → chunk-PX2635H5.js} +32 -10
- package/dist/react/chunk-PX2635H5.js.map +7 -0
- package/dist/react/{chunk-37PF7FG2.js → chunk-SDG4U7TP.js} +7 -6
- package/dist/react/chunk-SDG4U7TP.js.map +7 -0
- package/dist/react/{chunk-CGMUXYPJ.js → chunk-STJERZ25.js} +8 -4
- package/dist/react/chunk-STJERZ25.js.map +7 -0
- package/dist/react/{chunk-VZPH7OAM.js → chunk-TCWCC4Z4.js} +7 -3
- package/dist/react/chunk-TCWCC4Z4.js.map +7 -0
- package/dist/react/{chunk-VZX55VQL.js → chunk-TFCBCWL5.js} +90 -33
- package/dist/react/chunk-TFCBCWL5.js.map +7 -0
- package/dist/react/{chunk-GQLXUZHQ.js → chunk-TWHJ6PJK.js} +5 -3
- package/dist/react/chunk-TWHJ6PJK.js.map +7 -0
- package/dist/react/{chunk-NH3NVSWT.js → chunk-UCOK4V5Z.js} +4 -3
- package/dist/react/{chunk-NH3NVSWT.js.map → chunk-UCOK4V5Z.js.map} +2 -2
- package/dist/react/{chunk-W7XUY4GR.js → chunk-VJDNKJBX.js} +56 -4
- package/dist/react/chunk-VJDNKJBX.js.map +7 -0
- package/dist/react/{chunk-BT2EIJD4.js → chunk-WCA3FGWE.js} +6 -3
- package/dist/react/chunk-WCA3FGWE.js.map +7 -0
- package/dist/react/{chunk-I7TVGJFF.js → chunk-Y3FJMYPX.js} +4 -4
- package/dist/react/{chunk-I7TVGJFF.js.map → chunk-Y3FJMYPX.js.map} +1 -1
- package/dist/react/{chunk-7DD2JRBC.js → chunk-YCGLGY4C.js} +2 -2
- package/dist/react/{circleTool-NK4RVDAR.js → circleTool-QQV2QEVF.js} +4 -4
- package/dist/react/{codeTool-3WF4HEOW.js → codeTool-PTCPSZ4L.js} +5 -5
- package/dist/react/{frameTool-VCNV2NG2.js → frameTool-7GQPLU4Y.js} +4 -4
- package/dist/react/{freehandTool-TBAOD46K.js → freehandTool-3RJXTVAK.js} +4 -4
- package/dist/react/{iconTool-65JCAKYF.js → iconTool-DW5YJX7P.js} +5 -5
- package/dist/react/{imageTool-UQZUBOK2.js → imageTool-7GZQKWYT.js} +5 -5
- package/dist/react/index.js +68 -63
- package/dist/react/index.js.map +2 -2
- package/dist/react/{lineTool-762KBYOM.js → lineTool-5CY7BHND.js} +4 -4
- package/dist/react/{rectangleTool-IQK3HZK6.js → rectangleTool-DK4QL4QW.js} +4 -4
- package/dist/react/styles.css +4 -0
- package/dist/react/{textTool-ITG2SBJK.js → textTool-GP5QQYF3.js} +6 -6
- package/package.json +1 -1
- package/src/core/CopyPaste.js +66 -18
- package/src/core/EventDispatcher.js +71 -47
- package/src/core/MermaidFlowchartRenderer.js +34 -8
- package/src/core/MermaidSequenceRenderer.js +10 -8
- package/src/core/MermaidStructuredRenderer.js +12 -5
- package/src/core/ScreenSpaceControls.js +102 -13
- package/src/core/Selection.js +26 -7
- package/src/core/SelectionGesture.js +50 -0
- package/src/core/UndoRedo.js +108 -35
- package/src/react/components/sidebars/CircleSidebar.jsx +11 -5
- package/src/react/components/sidebars/IconSidebar.jsx +10 -4
- package/src/react/components/sidebars/RectangleSidebar.jsx +11 -5
- package/src/react/components/sidebars/ShapeSidebar.jsx +29 -37
- package/src/react/components/sidebars/TextSidebar.jsx +10 -4
- package/src/react/store/useUIStore.js +8 -1
- package/src/react/styles.css +13 -0
- package/src/react/utils/canvasBackgrounds.js +11 -2
- package/src/shapes/Arrow.js +4 -3
- package/src/shapes/Circle.js +58 -2
- package/src/shapes/Frame.js +11 -4
- package/src/shapes/FreehandStroke.js +2 -1
- package/src/shapes/Line.js +47 -20
- package/src/shapes/Rectangle.js +3 -1
- package/src/tools/arrowTool.js +1 -1
- package/src/tools/circleTool.js +5 -2
- package/src/tools/codeTool.js +1 -1
- package/src/tools/frameTool.js +3 -2
- package/src/tools/freehandTool.js +1 -1
- package/src/tools/iconTool.js +4 -3
- package/src/tools/imageTool.js +3 -2
- package/src/tools/lineTool.js +5 -1
- package/src/tools/rectangleTool.js +1 -1
- package/src/tools/shapeRecognitionTool.js +1 -1
- package/src/tools/textTool.js +2 -2
- package/src/utils/themeColors.js +13 -0
- package/dist/react/Arrow-EF7ALH2Y.js +0 -8
- package/dist/react/Circle-HMM5PKJ2.js +0 -10
- package/dist/react/CopyPaste-PPDS3AD6.js.map +0 -7
- package/dist/react/Frame-DELCMYV6.js +0 -11
- package/dist/react/FreehandStroke-WXIJKD5I.js +0 -9
- package/dist/react/IconShape-PZHMZSLR.js +0 -10
- package/dist/react/ImageShape-ZMBZARRI.js +0 -10
- package/dist/react/Line-VPVPL6ET.js +0 -10
- package/dist/react/Rectangle-4U4CD2ER.js +0 -10
- package/dist/react/chunk-37PF7FG2.js.map +0 -7
- package/dist/react/chunk-BOZEDUAF.js.map +0 -7
- package/dist/react/chunk-BT2EIJD4.js.map +0 -7
- package/dist/react/chunk-CGMUXYPJ.js.map +0 -7
- package/dist/react/chunk-GQLXUZHQ.js.map +0 -7
- package/dist/react/chunk-KC26KJW4.js.map +0 -7
- package/dist/react/chunk-KZBKYHZO.js.map +0 -7
- package/dist/react/chunk-O7KXLFZN.js.map +0 -7
- package/dist/react/chunk-PAKA6HUX.js +0 -59
- package/dist/react/chunk-PAKA6HUX.js.map +0 -7
- package/dist/react/chunk-RK3NVAER.js.map +0 -7
- package/dist/react/chunk-VZPH7OAM.js.map +0 -7
- package/dist/react/chunk-VZX55VQL.js.map +0 -7
- package/dist/react/chunk-W7XUY4GR.js.map +0 -7
- package/dist/react/chunk-ZNZAICWY.js.map +0 -7
- /package/dist/react/{AIRenderer-R5M2CIDO.js.map → AIRenderer-JNEZYDYQ.js.map} +0 -0
- /package/dist/react/{Arrow-EF7ALH2Y.js.map → Arrow-QMR77LGQ.js.map} +0 -0
- /package/dist/react/{Circle-HMM5PKJ2.js.map → Circle-5WL5HZA5.js.map} +0 -0
- /package/dist/react/{Frame-DELCMYV6.js.map → Frame-OZCDVPMI.js.map} +0 -0
- /package/dist/react/{FreehandStroke-WXIJKD5I.js.map → FreehandStroke-QSSYWBFK.js.map} +0 -0
- /package/dist/react/{IconShape-PZHMZSLR.js.map → IconShape-HXUV4VRT.js.map} +0 -0
- /package/dist/react/{ImageShape-ZMBZARRI.js.map → ImageShape-JGLT2UGG.js.map} +0 -0
- /package/dist/react/{Line-VPVPL6ET.js.map → Line-YONPLUYT.js.map} +0 -0
- /package/dist/react/{Rectangle-4U4CD2ER.js.map → Rectangle-7KMA3SC6.js.map} +0 -0
- /package/dist/react/{Selection-CFWRGVKH.js.map → Selection-S4FYSZIF.js.map} +0 -0
- /package/dist/react/{SketchEngine-UFLZJQXS.js.map → SketchEngine-VTWURP2K.js.map} +0 -0
- /package/dist/react/{UndoRedo-RXO7YHA2.js.map → UndoRedo-YLQJ6DXW.js.map} +0 -0
- /package/dist/react/{ZoomPan-RR7KUYZT.js.map → ZoomPan-AEKWKUCH.js.map} +0 -0
- /package/dist/react/{arrowTool-DO327FKU.js.map → arrowTool-C4RDWQEX.js.map} +0 -0
- /package/dist/react/{chunk-2ALS5JGS.js.map → chunk-OCJN32IL.js.map} +0 -0
- /package/dist/react/{chunk-7DD2JRBC.js.map → chunk-YCGLGY4C.js.map} +0 -0
- /package/dist/react/{circleTool-NK4RVDAR.js.map → circleTool-QQV2QEVF.js.map} +0 -0
- /package/dist/react/{codeTool-3WF4HEOW.js.map → codeTool-PTCPSZ4L.js.map} +0 -0
- /package/dist/react/{frameTool-VCNV2NG2.js.map → frameTool-7GQPLU4Y.js.map} +0 -0
- /package/dist/react/{freehandTool-TBAOD46K.js.map → freehandTool-3RJXTVAK.js.map} +0 -0
- /package/dist/react/{iconTool-65JCAKYF.js.map → iconTool-DW5YJX7P.js.map} +0 -0
- /package/dist/react/{imageTool-UQZUBOK2.js.map → imageTool-7GZQKWYT.js.map} +0 -0
- /package/dist/react/{lineTool-762KBYOM.js.map → lineTool-5CY7BHND.js.map} +0 -0
- /package/dist/react/{rectangleTool-IQK3HZK6.js.map → rectangleTool-DK4QL4QW.js.map} +0 -0
- /package/dist/react/{textTool-ITG2SBJK.js.map → textTool-GP5QQYF3.js.map} +0 -0
|
@@ -301,8 +301,8 @@ export function renderChartPreviewSVG(chart) {
|
|
|
301
301
|
function push(shape, frame) {
|
|
302
302
|
if (!shape) return null;
|
|
303
303
|
window.shapes.push(shape);
|
|
304
|
-
if (window.pushCreateAction) window.pushCreateAction(shape);
|
|
305
304
|
if (frame?.addShapeToFrame) frame.addShapeToFrame(shape);
|
|
305
|
+
if (window.pushCreateAction) window.pushCreateAction(shape);
|
|
306
306
|
return shape;
|
|
307
307
|
}
|
|
308
308
|
|
|
@@ -343,7 +343,8 @@ function createFrame(x, y, width, height, name, type) {
|
|
|
343
343
|
frameName: name, labelColor: TK.text,
|
|
344
344
|
});
|
|
345
345
|
frame._diagramType = type;
|
|
346
|
-
push(frame);
|
|
346
|
+
window.shapes.push(frame);
|
|
347
|
+
if (window.pushCreateAction) window.pushCreateAction(frame, { frameCreation: true, containedShapes: [] });
|
|
347
348
|
return frame;
|
|
348
349
|
}
|
|
349
350
|
|
|
@@ -360,6 +361,7 @@ function selectOnlyFrame(frame) {
|
|
|
360
361
|
|
|
361
362
|
export function renderEROnCanvas(diagram) {
|
|
362
363
|
if (!diagram?.entities?.length || !window.Rectangle || !window.Arrow || !window.Frame) return false;
|
|
364
|
+
const undoBatch = window.beginUndoBatch?.();
|
|
363
365
|
const TK = theme();
|
|
364
366
|
const entities = layoutER(diagram);
|
|
365
367
|
const width = Math.max(...entities.map(entity => entity.x + entity.width)) + 40;
|
|
@@ -411,6 +413,7 @@ export function renderEROnCanvas(diagram) {
|
|
|
411
413
|
}
|
|
412
414
|
}
|
|
413
415
|
if (first?.selectShape) { window.currentShape = first; first.selectShape(); }
|
|
416
|
+
window.endUndoBatch?.(undoBatch, 'mermaid-er-create');
|
|
414
417
|
return true;
|
|
415
418
|
}
|
|
416
419
|
|
|
@@ -432,6 +435,7 @@ function rectBoundaryPoint(box, target) {
|
|
|
432
435
|
export function renderChartOnCanvas(chart) {
|
|
433
436
|
if (!chart?.series?.length || !window.Rectangle || !window.Circle || !window.Line || !window.FreehandStroke || !window.Frame) return false;
|
|
434
437
|
if (chart.kind === 'pie') return renderPieOnCanvas(chart);
|
|
438
|
+
const undoBatch = window.beginUndoBatch?.();
|
|
435
439
|
const TK = theme();
|
|
436
440
|
const width = 720;
|
|
437
441
|
const height = 450;
|
|
@@ -497,18 +501,20 @@ export function renderChartOnCanvas(chart) {
|
|
|
497
501
|
}
|
|
498
502
|
});
|
|
499
503
|
selectOnlyFrame(frame);
|
|
504
|
+
window.endUndoBatch?.(undoBatch, 'mermaid-chart-create');
|
|
500
505
|
return true;
|
|
501
506
|
}
|
|
502
507
|
|
|
503
508
|
function renderPieOnCanvas(chart) {
|
|
504
509
|
if (!window.FreehandStroke || !window.TextShape) return false;
|
|
510
|
+
const values = chart.series[0].values.map(value => Math.max(0, value));
|
|
511
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
512
|
+
if (total <= 0) return false;
|
|
513
|
+
const undoBatch = window.beginUndoBatch?.();
|
|
505
514
|
const width = 720;
|
|
506
515
|
const height = 450;
|
|
507
516
|
const origin = canvasOrigin(width, height);
|
|
508
517
|
const frame = createFrame(origin.x, origin.y, width, height, chart.title, 'mermaid-pie');
|
|
509
|
-
const values = chart.series[0].values.map(value => Math.max(0, value));
|
|
510
|
-
const total = values.reduce((sum, value) => sum + value, 0);
|
|
511
|
-
if (total <= 0) return false;
|
|
512
518
|
|
|
513
519
|
const cx = origin.x + 255;
|
|
514
520
|
const cy = origin.y + 235;
|
|
@@ -559,5 +565,6 @@ function renderPieOnCanvas(chart) {
|
|
|
559
565
|
});
|
|
560
566
|
|
|
561
567
|
selectOnlyFrame(frame);
|
|
568
|
+
window.endUndoBatch?.(undoBatch, 'mermaid-pie-create');
|
|
562
569
|
return true;
|
|
563
570
|
}
|
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
|
|
3
3
|
const ROTATION_SELECTOR = '[data-screen-space-rotation-anchor="true"]';
|
|
4
|
+
const OUTLINE_SELECTOR = '.selection-outline, .selection-box, .multi-selection-outline';
|
|
5
|
+
const HANDLE_SELECTOR = '.anchor, .resize-anchor, .resize-handle, .multi-selection-anchor';
|
|
6
|
+
|
|
7
|
+
export const SELECTION_CHROME = Object.freeze({
|
|
8
|
+
outlineWidth: 1.5,
|
|
9
|
+
outlineDash: '4 2',
|
|
10
|
+
handleSize: 10,
|
|
11
|
+
handleStrokeWidth: 2,
|
|
12
|
+
rotationRadius: 8,
|
|
13
|
+
rotationGap: 30,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export function getSelectionChromeMetrics(zoom = 1) {
|
|
17
|
+
const normalizedZoom = Math.max(0.001, Number(zoom) || 1);
|
|
18
|
+
return {
|
|
19
|
+
outlineWidth: SELECTION_CHROME.outlineWidth,
|
|
20
|
+
outlineDash: SELECTION_CHROME.outlineDash,
|
|
21
|
+
handleSize: SELECTION_CHROME.handleSize / normalizedZoom,
|
|
22
|
+
rotationRadius: SELECTION_CHROME.rotationRadius / normalizedZoom,
|
|
23
|
+
rotationGap: SELECTION_CHROME.rotationGap / normalizedZoom,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
4
26
|
|
|
5
27
|
function getZoom() {
|
|
6
|
-
return Math.max(0.001, Number(window
|
|
28
|
+
return Math.max(0.001, Number(globalThis.window?.currentZoom) || 1);
|
|
7
29
|
}
|
|
8
30
|
|
|
9
31
|
export function registerRotationAnchor(anchor, options = {}) {
|
|
10
32
|
if (!anchor) return;
|
|
11
33
|
const zoom = getZoom();
|
|
12
|
-
const radius =
|
|
13
|
-
const gap =
|
|
34
|
+
const radius = SELECTION_CHROME.rotationRadius;
|
|
35
|
+
const gap = SELECTION_CHROME.rotationGap;
|
|
14
36
|
const edgeY = Number(options.edgeY);
|
|
15
37
|
|
|
16
38
|
anchor.setAttribute('data-screen-space-rotation-anchor', 'true');
|
|
@@ -18,6 +40,9 @@ export function registerRotationAnchor(anchor, options = {}) {
|
|
|
18
40
|
anchor.setAttribute('data-screen-gap', String(gap));
|
|
19
41
|
anchor.setAttribute('data-applied-zoom', String(zoom));
|
|
20
42
|
anchor.setAttribute('r', String(radius / zoom));
|
|
43
|
+
anchor.setAttribute('stroke', '#5B57D1');
|
|
44
|
+
anchor.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
|
|
45
|
+
anchor.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
21
46
|
if (Number.isFinite(edgeY)) anchor.setAttribute('cy', String(edgeY - gap / zoom));
|
|
22
47
|
|
|
23
48
|
anchor.__rotationConnector = options.line || null;
|
|
@@ -31,6 +56,49 @@ function syncRotationConnector(anchor) {
|
|
|
31
56
|
const end = anchor.__rotationConnectorEnd === '1' ? '1' : '2';
|
|
32
57
|
line.setAttribute(`x${end}`, anchor.getAttribute('cx'));
|
|
33
58
|
line.setAttribute(`y${end}`, anchor.getAttribute('cy'));
|
|
59
|
+
line.setAttribute('stroke', '#5B57D1');
|
|
60
|
+
line.setAttribute('stroke-width', '1');
|
|
61
|
+
line.setAttribute('stroke-dasharray', SELECTION_CHROME.outlineDash);
|
|
62
|
+
line.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resizeHandleInScreenSpace(handle, zoom) {
|
|
66
|
+
const size = SELECTION_CHROME.handleSize / zoom;
|
|
67
|
+
const tagName = handle.tagName?.toLowerCase();
|
|
68
|
+
|
|
69
|
+
if (tagName === 'rect') {
|
|
70
|
+
const oldWidth = Number(handle.getAttribute('width')) || size;
|
|
71
|
+
const oldHeight = Number(handle.getAttribute('height')) || size;
|
|
72
|
+
const centerX = (Number(handle.getAttribute('x')) || 0) + oldWidth / 2;
|
|
73
|
+
const centerY = (Number(handle.getAttribute('y')) || 0) + oldHeight / 2;
|
|
74
|
+
handle.setAttribute('x', String(centerX - size / 2));
|
|
75
|
+
handle.setAttribute('y', String(centerY - size / 2));
|
|
76
|
+
handle.setAttribute('width', String(size));
|
|
77
|
+
handle.setAttribute('height', String(size));
|
|
78
|
+
} else if (tagName === 'circle' && !handle.matches(ROTATION_SELECTOR)) {
|
|
79
|
+
handle.setAttribute('r', String(size / 2));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handle.setAttribute('stroke', '#5B57D1');
|
|
83
|
+
handle.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
|
|
84
|
+
handle.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function syncSelectionChromeToZoom(root = document) {
|
|
88
|
+
if (!root?.querySelectorAll) return;
|
|
89
|
+
const zoom = getZoom();
|
|
90
|
+
|
|
91
|
+
root.querySelectorAll(OUTLINE_SELECTOR).forEach((outline) => {
|
|
92
|
+
outline.setAttribute('fill', 'none');
|
|
93
|
+
outline.setAttribute('stroke', '#5B57D1');
|
|
94
|
+
outline.setAttribute('stroke-width', String(SELECTION_CHROME.outlineWidth));
|
|
95
|
+
outline.setAttribute('stroke-dasharray', SELECTION_CHROME.outlineDash);
|
|
96
|
+
outline.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
root.querySelectorAll(HANDLE_SELECTOR).forEach((handle) => {
|
|
100
|
+
resizeHandleInScreenSpace(handle, zoom);
|
|
101
|
+
});
|
|
34
102
|
}
|
|
35
103
|
|
|
36
104
|
export function syncRotationAnchorsToZoom() {
|
|
@@ -38,23 +106,44 @@ export function syncRotationAnchorsToZoom() {
|
|
|
38
106
|
const zoom = getZoom();
|
|
39
107
|
document.querySelectorAll(ROTATION_SELECTOR).forEach((anchor) => {
|
|
40
108
|
const previousZoom = Math.max(0.001, Number(anchor.getAttribute('data-applied-zoom')) || zoom);
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
109
|
+
const radius = SELECTION_CHROME.rotationRadius;
|
|
110
|
+
const gap = SELECTION_CHROME.rotationGap;
|
|
111
|
+
if (Math.abs(previousZoom - zoom) >= 0.000001) {
|
|
112
|
+
const currentY = Number(anchor.getAttribute('cy')) || 0;
|
|
113
|
+
const edgeY = currentY + gap / previousZoom;
|
|
114
|
+
anchor.setAttribute('cy', String(edgeY - gap / zoom));
|
|
115
|
+
anchor.setAttribute('data-applied-zoom', String(zoom));
|
|
116
|
+
}
|
|
48
117
|
anchor.setAttribute('r', String(radius / zoom));
|
|
49
|
-
anchor.setAttribute('
|
|
118
|
+
anchor.setAttribute('stroke', '#5B57D1');
|
|
119
|
+
anchor.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
|
|
120
|
+
anchor.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
50
121
|
syncRotationConnector(anchor);
|
|
51
122
|
});
|
|
123
|
+
syncSelectionChromeToZoom(document);
|
|
52
124
|
}
|
|
53
125
|
|
|
54
126
|
export function installScreenSpaceControlSync(svgElement) {
|
|
55
127
|
if (!svgElement || typeof MutationObserver === 'undefined') return;
|
|
56
128
|
window.__screenSpaceControlObserver?.disconnect?.();
|
|
57
|
-
|
|
58
|
-
|
|
129
|
+
let scheduled = false;
|
|
130
|
+
const scheduleSync = () => {
|
|
131
|
+
if (scheduled) return;
|
|
132
|
+
scheduled = true;
|
|
133
|
+
const run = () => {
|
|
134
|
+
scheduled = false;
|
|
135
|
+
syncRotationAnchorsToZoom();
|
|
136
|
+
};
|
|
137
|
+
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(run);
|
|
138
|
+
else queueMicrotask(run);
|
|
139
|
+
};
|
|
140
|
+
const observer = new MutationObserver(scheduleSync);
|
|
141
|
+
observer.observe(svgElement, {
|
|
142
|
+
attributes: true,
|
|
143
|
+
attributeFilter: ['viewBox'],
|
|
144
|
+
childList: true,
|
|
145
|
+
subtree: true,
|
|
146
|
+
});
|
|
59
147
|
window.__screenSpaceControlObserver = observer;
|
|
148
|
+
scheduleSync();
|
|
60
149
|
}
|
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 { beginUndoBatch, endUndoBatch, pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
5
|
+
import { beginUndoBatch, captureFrameChildStates, endUndoBatch, pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
6
6
|
import { calculateSnap, clearSnapGuides } from './SnapGuides.js';
|
|
7
7
|
import { registerRotationAnchor } from './ScreenSpaceControls.js';
|
|
8
8
|
|
|
@@ -270,6 +270,15 @@ class MultiSelection {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
_captureShapeState(shape) {
|
|
273
|
+
if (shape.shapeName === 'frame') {
|
|
274
|
+
return {
|
|
275
|
+
x: shape.x, y: shape.y,
|
|
276
|
+
width: shape.width || 0, height: shape.height || 0,
|
|
277
|
+
rotation: shape.rotation || 0,
|
|
278
|
+
parentFrame: shape.parentFrame || null,
|
|
279
|
+
containedShapes: captureFrameChildStates(shape),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
273
282
|
switch (shape.shapeName) {
|
|
274
283
|
case 'line':
|
|
275
284
|
case 'arrow':
|
|
@@ -300,6 +309,13 @@ class MultiSelection {
|
|
|
300
309
|
// Check if anything actually changed
|
|
301
310
|
const changed = Object.keys(oldState).some(key => {
|
|
302
311
|
if (key === 'points' || key === 'startPoint' || key === 'endPoint') return JSON.stringify(oldState[key]) !== JSON.stringify(newState[key]);
|
|
312
|
+
if (key === 'containedShapes') {
|
|
313
|
+
const compact = entries => (entries || []).map(entry => ({
|
|
314
|
+
shapeID: entry.shape?.shapeID,
|
|
315
|
+
state: entry.state,
|
|
316
|
+
}));
|
|
317
|
+
return JSON.stringify(compact(oldState[key])) !== JSON.stringify(compact(newState[key]));
|
|
318
|
+
}
|
|
303
319
|
return oldState[key] !== newState[key];
|
|
304
320
|
});
|
|
305
321
|
if (changed) {
|
|
@@ -475,7 +491,6 @@ class MultiSelection {
|
|
|
475
491
|
}
|
|
476
492
|
|
|
477
493
|
createOutline(x, y, width, height) {
|
|
478
|
-
const zoom = window.currentZoom || 1;
|
|
479
494
|
const outlinePoints = [
|
|
480
495
|
[x, y],
|
|
481
496
|
[x + width, y],
|
|
@@ -485,11 +500,12 @@ class MultiSelection {
|
|
|
485
500
|
];
|
|
486
501
|
|
|
487
502
|
this.outline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
503
|
+
this.outline.setAttribute('class', 'multi-selection-outline');
|
|
488
504
|
this.outline.setAttribute('points', outlinePoints.map(p => p.join(',')).join(' '));
|
|
489
505
|
this.outline.setAttribute('fill', 'none');
|
|
490
506
|
this.outline.setAttribute('stroke', '#5B57D1');
|
|
491
|
-
this.outline.setAttribute('stroke-width',
|
|
492
|
-
this.outline.setAttribute('stroke-dasharray',
|
|
507
|
+
this.outline.setAttribute('stroke-width', 1.5);
|
|
508
|
+
this.outline.setAttribute('stroke-dasharray', '4 2');
|
|
493
509
|
this.outline.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
494
510
|
this.outline.setAttribute('style', 'pointer-events: none;');
|
|
495
511
|
this.group.appendChild(this.outline);
|
|
@@ -497,7 +513,7 @@ class MultiSelection {
|
|
|
497
513
|
|
|
498
514
|
createResizeAnchors(x, y, width, height) {
|
|
499
515
|
const zoom = window.currentZoom || 1;
|
|
500
|
-
const anchorSize =
|
|
516
|
+
const anchorSize = 10 / zoom;
|
|
501
517
|
const anchorPositions = [
|
|
502
518
|
{ x: x, y: y, index: 0 },
|
|
503
519
|
{ x: x + width, y: y, index: 1 },
|
|
@@ -558,7 +574,7 @@ class MultiSelection {
|
|
|
558
574
|
this.rotationLine.setAttribute('y2', y);
|
|
559
575
|
this.rotationLine.setAttribute('stroke', '#5B57D1');
|
|
560
576
|
this.rotationLine.setAttribute('stroke-width', 1);
|
|
561
|
-
this.rotationLine.setAttribute('stroke-dasharray',
|
|
577
|
+
this.rotationLine.setAttribute('stroke-dasharray', '4 2');
|
|
562
578
|
this.rotationLine.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
563
579
|
this.rotationLine.setAttribute('style', 'pointer-events: none;');
|
|
564
580
|
|
|
@@ -1036,10 +1052,12 @@ createRotatedControls(angleDiff = 0) {
|
|
|
1036
1052
|
rect.setAttribute('width', b.width + 6);
|
|
1037
1053
|
rect.setAttribute('height', b.height + 6);
|
|
1038
1054
|
rect.setAttribute('fill', 'none');
|
|
1055
|
+
rect.setAttribute('class', 'selection-sub-outline');
|
|
1039
1056
|
rect.setAttribute('stroke', '#5B57D1');
|
|
1040
1057
|
rect.setAttribute('stroke-width', 1);
|
|
1041
1058
|
rect.setAttribute('stroke-opacity', 0.35);
|
|
1042
|
-
rect.setAttribute('stroke-dasharray', '4
|
|
1059
|
+
rect.setAttribute('stroke-dasharray', '4 2');
|
|
1060
|
+
rect.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
1043
1061
|
rect.setAttribute('rx', 3);
|
|
1044
1062
|
rect.setAttribute('style', 'pointer-events: none;');
|
|
1045
1063
|
this.group.appendChild(rect);
|
|
@@ -1292,6 +1310,7 @@ createRotatedControls(angleDiff = 0) {
|
|
|
1292
1310
|
this.selectedShapes.forEach(shape => {
|
|
1293
1311
|
let shapeData;
|
|
1294
1312
|
shape.removeSelection();
|
|
1313
|
+
if (shape.shapeName === 'line') shape.scheduleDragRender?.();
|
|
1295
1314
|
switch (shape.shapeName) {
|
|
1296
1315
|
case 'rectangle':
|
|
1297
1316
|
case 'icon':
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
function normalizePointerId(pointerId) {
|
|
4
|
+
return Number.isFinite(Number(pointerId)) ? Number(pointerId) : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createSelectionGestureOwner() {
|
|
8
|
+
let activeGesture = null;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
captureShape(shape, pointerId) {
|
|
12
|
+
if (!shape) return null;
|
|
13
|
+
activeGesture = {
|
|
14
|
+
kind: 'shape',
|
|
15
|
+
shape,
|
|
16
|
+
shapeName: shape.shapeName,
|
|
17
|
+
pointerId: normalizePointerId(pointerId),
|
|
18
|
+
};
|
|
19
|
+
return activeGesture;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
captureMulti(pointerId) {
|
|
23
|
+
activeGesture = {
|
|
24
|
+
kind: 'multi',
|
|
25
|
+
pointerId: normalizePointerId(pointerId),
|
|
26
|
+
};
|
|
27
|
+
return activeGesture;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
get(pointerId) {
|
|
31
|
+
if (!activeGesture) return null;
|
|
32
|
+
const requestedPointer = normalizePointerId(pointerId);
|
|
33
|
+
if (activeGesture.pointerId !== null && requestedPointer !== null && activeGesture.pointerId !== requestedPointer) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return activeGesture;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
clear(pointerId) {
|
|
40
|
+
const gesture = this.get(pointerId);
|
|
41
|
+
if (!gesture) return false;
|
|
42
|
+
activeGesture = null;
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
clearAll() {
|
|
47
|
+
activeGesture = null;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/core/UndoRedo.js
CHANGED
|
@@ -55,6 +55,14 @@ export function pushCreateAction(shape, meta = null) {
|
|
|
55
55
|
type: 'create',
|
|
56
56
|
shape: shape
|
|
57
57
|
};
|
|
58
|
+
const parentFrame = meta?.parentFrame || shape?.parentFrame || null;
|
|
59
|
+
if (parentFrame) {
|
|
60
|
+
action.parentFrame = parentFrame;
|
|
61
|
+
const frameIndex = Number.isInteger(meta?.frameIndex)
|
|
62
|
+
? meta.frameIndex
|
|
63
|
+
: parentFrame.containedShapes?.indexOf(shape);
|
|
64
|
+
if (Number.isInteger(frameIndex) && frameIndex >= 0) action.frameIndex = frameIndex;
|
|
65
|
+
}
|
|
58
66
|
if (meta?.frameCreation && shape?.shapeName === 'frame') {
|
|
59
67
|
action.frameCreation = true;
|
|
60
68
|
action.containedShapes = [...(meta.containedShapes || shape.containedShapes || [])];
|
|
@@ -108,6 +116,35 @@ export function clearUndoHistory() {
|
|
|
108
116
|
redoStack.length = 0;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
// Collaboration scene loads rebuild shape instances. Keep this client's
|
|
120
|
+
// history private, but point its actions at the newly materialized objects so
|
|
121
|
+
// Ctrl+Z never mutates detached SVG nodes after a peer update.
|
|
122
|
+
export function rebindUndoHistory(currentShapes = []) {
|
|
123
|
+
const shapeById = new Map(currentShapes
|
|
124
|
+
.filter(shape => shape?.shapeID)
|
|
125
|
+
.map(shape => [shape.shapeID, shape]));
|
|
126
|
+
const visited = new WeakSet();
|
|
127
|
+
|
|
128
|
+
const rebind = (value) => {
|
|
129
|
+
if (!value || typeof value !== 'object') return value;
|
|
130
|
+
if (value.shapeID) return shapeById.get(value.shapeID) || value;
|
|
131
|
+
if (visited.has(value)) return value;
|
|
132
|
+
visited.add(value);
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
for (let index = 0; index < value.length; index += 1) value[index] = rebind(value[index]);
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
for (const key of Object.keys(value)) {
|
|
138
|
+
const child = value[key];
|
|
139
|
+
if (child && typeof child === 'object') value[key] = rebind(child);
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
undoStack.forEach(rebind);
|
|
145
|
+
redoStack.forEach(rebind);
|
|
146
|
+
}
|
|
147
|
+
|
|
111
148
|
// Enhanced delete action to clean up attached arrows
|
|
112
149
|
export function pushDeleteActionWithAttachments(shape) {
|
|
113
150
|
let affectedArrows = [];
|
|
@@ -174,23 +211,20 @@ export function pushCreateActionWithAttachments(shape) {
|
|
|
174
211
|
|
|
175
212
|
export function pushTransformAction(shape, oldPos, newPos) {
|
|
176
213
|
if (shape.shapeName === 'frame') {
|
|
177
|
-
//
|
|
214
|
+
// Frame transforms can resize and rotate heterogeneous children.
|
|
215
|
+
// Reconstructing their old geometry from the frame delta loses arrow
|
|
216
|
+
// endpoints, circle radii and freehand points. Prefer the snapshot
|
|
217
|
+
// captured at pointer-down, with a translation-only fallback for old
|
|
218
|
+
// callers that have not supplied one yet.
|
|
219
|
+
const oldSnapshots = new Map((oldPos.containedShapes || []).map(entry => [entry.shape, entry.state]));
|
|
178
220
|
const containedShapesStates = shape.containedShapes.map(containedShape => ({
|
|
179
221
|
shape: containedShape,
|
|
180
|
-
oldState: {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
height: containedShape.height || 0,
|
|
185
|
-
rotation: containedShape.rotation || 0
|
|
222
|
+
oldState: oldSnapshots.get(containedShape) || {
|
|
223
|
+
...captureShapeGeometry(containedShape),
|
|
224
|
+
x: typeof containedShape.x === 'number' ? containedShape.x - (newPos.x - oldPos.x) : containedShape.x,
|
|
225
|
+
y: typeof containedShape.y === 'number' ? containedShape.y - (newPos.y - oldPos.y) : containedShape.y,
|
|
186
226
|
},
|
|
187
|
-
newState:
|
|
188
|
-
x: containedShape.x,
|
|
189
|
-
y: containedShape.y,
|
|
190
|
-
width: containedShape.width || 0,
|
|
191
|
-
height: containedShape.height || 0,
|
|
192
|
-
rotation: containedShape.rotation || 0
|
|
193
|
-
}
|
|
227
|
+
newState: captureShapeGeometry(containedShape),
|
|
194
228
|
}));
|
|
195
229
|
|
|
196
230
|
undoStack.push({
|
|
@@ -451,6 +485,7 @@ export function pushTransformAction(shape, oldPos, newPos) {
|
|
|
451
485
|
}))
|
|
452
486
|
});
|
|
453
487
|
}
|
|
488
|
+
|
|
454
489
|
else if (shape.shapeName === 'freehandStroke') {
|
|
455
490
|
// Handle freehand stroke transform
|
|
456
491
|
undoStack.push({
|
|
@@ -472,6 +507,47 @@ export function pushTransformAction(shape, oldPos, newPos) {
|
|
|
472
507
|
notifyCollaboration();
|
|
473
508
|
}
|
|
474
509
|
|
|
510
|
+
function clonePoint(point) {
|
|
511
|
+
return point ? { ...point } : point;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function captureShapeGeometry(shape) {
|
|
515
|
+
const state = {
|
|
516
|
+
x: shape.x,
|
|
517
|
+
y: shape.y,
|
|
518
|
+
width: shape.width,
|
|
519
|
+
height: shape.height,
|
|
520
|
+
rotation: shape.rotation || 0,
|
|
521
|
+
};
|
|
522
|
+
if (typeof shape.rx === 'number') state.rx = shape.rx;
|
|
523
|
+
if (typeof shape.ry === 'number') state.ry = shape.ry;
|
|
524
|
+
if (shape.startPoint) state.startPoint = clonePoint(shape.startPoint);
|
|
525
|
+
if (shape.endPoint) state.endPoint = clonePoint(shape.endPoint);
|
|
526
|
+
if (shape.controlPoint1) state.controlPoint1 = clonePoint(shape.controlPoint1);
|
|
527
|
+
if (shape.controlPoint2) state.controlPoint2 = clonePoint(shape.controlPoint2);
|
|
528
|
+
if (Array.isArray(shape.points)) state.points = shape.points.map(point => Array.isArray(point) ? [...point] : point);
|
|
529
|
+
if (typeof shape._moveOffsetX === 'number') state._moveOffsetX = shape._moveOffsetX;
|
|
530
|
+
if (typeof shape._moveOffsetY === 'number') state._moveOffsetY = shape._moveOffsetY;
|
|
531
|
+
return state;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function captureFrameChildStates(frame) {
|
|
535
|
+
return (frame?.containedShapes || []).map(shape => ({ shape, state: captureShapeGeometry(shape) }));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function restoreShapeGeometry(shape, state) {
|
|
539
|
+
if (!shape || !state) return;
|
|
540
|
+
for (const key of ['x', 'y', 'width', 'height', 'rotation', 'rx', 'ry', '_moveOffsetX', '_moveOffsetY']) {
|
|
541
|
+
if (state[key] !== undefined) shape[key] = state[key];
|
|
542
|
+
}
|
|
543
|
+
for (const key of ['startPoint', 'endPoint', 'controlPoint1', 'controlPoint2']) {
|
|
544
|
+
if (state[key] !== undefined) shape[key] = clonePoint(state[key]);
|
|
545
|
+
}
|
|
546
|
+
if (state.points) shape.points = state.points.map(point => Array.isArray(point) ? [...point] : point);
|
|
547
|
+
if (typeof shape.updateBoundingBox === 'function') shape.updateBoundingBox();
|
|
548
|
+
if (typeof shape.draw === 'function') shape.draw();
|
|
549
|
+
}
|
|
550
|
+
|
|
475
551
|
export function pushOptionsChangeAction(shape, oldOptions, newOptions = null) {
|
|
476
552
|
undoStack.push({
|
|
477
553
|
type: 'optionsChange',
|
|
@@ -533,6 +609,16 @@ function detachDeletedShapeFrame(action) {
|
|
|
533
609
|
}
|
|
534
610
|
}
|
|
535
611
|
|
|
612
|
+
function restoreCreatedShapeFrame(action) {
|
|
613
|
+
if (!action.parentFrame) return;
|
|
614
|
+
restoreDeletedShapeFrame(action);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function detachCreatedShapeFrame(action) {
|
|
618
|
+
if (!action.parentFrame) return;
|
|
619
|
+
detachDeletedShapeFrame(action);
|
|
620
|
+
}
|
|
621
|
+
|
|
536
622
|
export function undo() {
|
|
537
623
|
if (undoStack.length === 0) return;
|
|
538
624
|
const action = undoStack.pop();
|
|
@@ -565,16 +651,7 @@ export function undo() {
|
|
|
565
651
|
|
|
566
652
|
// Restore contained shapes to their old positions
|
|
567
653
|
action.containedShapes.forEach(shapeData => {
|
|
568
|
-
|
|
569
|
-
shape.x = shapeData.oldState.x;
|
|
570
|
-
shape.y = shapeData.oldState.y;
|
|
571
|
-
shape.width = shapeData.oldState.width;
|
|
572
|
-
shape.height = shapeData.oldState.height;
|
|
573
|
-
shape.rotation = shapeData.oldState.rotation;
|
|
574
|
-
|
|
575
|
-
if (typeof shape.draw === 'function') {
|
|
576
|
-
shape.draw();
|
|
577
|
-
}
|
|
654
|
+
restoreShapeGeometry(shapeData.shape, shapeData.oldState);
|
|
578
655
|
});
|
|
579
656
|
|
|
580
657
|
action.shape.isSelected = false;
|
|
@@ -670,6 +747,7 @@ export function undo() {
|
|
|
670
747
|
}
|
|
671
748
|
|
|
672
749
|
if (action.type === 'create') {
|
|
750
|
+
detachCreatedShapeFrame(action);
|
|
673
751
|
if (action.frameCreation && action.shape.shapeName === 'frame') {
|
|
674
752
|
// Treat a manually-created frame and its initial membership as one
|
|
675
753
|
// atomic action. Children remain on the canvas when the frame is
|
|
@@ -1104,16 +1182,7 @@ export function redo() {
|
|
|
1104
1182
|
|
|
1105
1183
|
// Restore contained shapes to their new positions
|
|
1106
1184
|
action.containedShapes.forEach(shapeData => {
|
|
1107
|
-
|
|
1108
|
-
shape.x = shapeData.newState.x;
|
|
1109
|
-
shape.y = shapeData.newState.y;
|
|
1110
|
-
shape.width = shapeData.newState.width;
|
|
1111
|
-
shape.height = shapeData.newState.height;
|
|
1112
|
-
shape.rotation = shapeData.newState.rotation;
|
|
1113
|
-
|
|
1114
|
-
if (typeof shape.draw === 'function') {
|
|
1115
|
-
shape.draw();
|
|
1116
|
-
}
|
|
1185
|
+
restoreShapeGeometry(shapeData.shape, shapeData.newState);
|
|
1117
1186
|
});
|
|
1118
1187
|
|
|
1119
1188
|
action.shape.isSelected = false;
|
|
@@ -1245,11 +1314,12 @@ export function redo() {
|
|
|
1245
1314
|
}
|
|
1246
1315
|
} else {
|
|
1247
1316
|
// Handle other shape creation redo
|
|
1248
|
-
shapes.push(action.shape);
|
|
1317
|
+
if (!shapes.includes(action.shape)) shapes.push(action.shape);
|
|
1249
1318
|
if (svg) {
|
|
1250
1319
|
svg.appendChild(action.shape.group);
|
|
1251
1320
|
}
|
|
1252
1321
|
}
|
|
1322
|
+
restoreCreatedShapeFrame(action);
|
|
1253
1323
|
undoStack.push(action);
|
|
1254
1324
|
return;
|
|
1255
1325
|
}
|
|
@@ -1601,10 +1671,13 @@ document.getElementById('redo')?.addEventListener('click', redo);
|
|
|
1601
1671
|
|
|
1602
1672
|
// Expose globally for plain scripts
|
|
1603
1673
|
window.pushCreateAction = pushCreateAction;
|
|
1674
|
+
window.beginUndoBatch = beginUndoBatch;
|
|
1675
|
+
window.endUndoBatch = endUndoBatch;
|
|
1604
1676
|
window.pushDeleteAction = pushDeleteAction;
|
|
1605
1677
|
window.pushCanvasResetAction = pushCanvasResetAction;
|
|
1606
1678
|
window.invalidateCanvasResetUndo = invalidateCanvasResetUndo;
|
|
1607
1679
|
window.clearUndoHistory = clearUndoHistory;
|
|
1680
|
+
window.rebindUndoHistory = rebindUndoHistory;
|
|
1608
1681
|
window.pushDeleteActionWithAttachments = pushDeleteActionWithAttachments;
|
|
1609
1682
|
window.pushTransformAction = pushTransformAction;
|
|
1610
1683
|
window.pushOptionsChangeAction = pushOptionsChangeAction;
|
|
@@ -4,8 +4,9 @@ import useSketchStore, { TOOLS } from '../../store/useSketchStore'
|
|
|
4
4
|
import ShapeSidebar, { ToolbarButton, Divider, LayerControls } from './ShapeSidebar'
|
|
5
5
|
import { useState, useCallback } from 'react'
|
|
6
6
|
import { useTranslation } from '../../hooks/useTranslation'
|
|
7
|
+
import useUIStore from '../../store/useUIStore'
|
|
7
8
|
|
|
8
|
-
const STROKE_COLORS = ['#
|
|
9
|
+
const STROKE_COLORS = ['#000000', '#ffffff', '#FF8383', '#3A994C', '#56A2E8', '#FFD700', '#FF69B4', '#A855F7']
|
|
9
10
|
const BG_COLORS = ['transparent', '#f0f0f0', '#ffcccb', '#90ee90', '#add8e6', '#FFE4B5', '#DDA0DD', '#2d2d2d']
|
|
10
11
|
|
|
11
12
|
const FILLS = [
|
|
@@ -46,7 +47,12 @@ export default function CircleSidebar() {
|
|
|
46
47
|
const { t } = useTranslation()
|
|
47
48
|
const activeTool = useSketchStore((s) => s.activeTool)
|
|
48
49
|
const selectedShapeSidebar = useSketchStore((s) => s.selectedShapeSidebar)
|
|
49
|
-
const
|
|
50
|
+
const theme = useUIStore((s) => s.theme)
|
|
51
|
+
const resolvedTheme = theme === 'system' && typeof window !== 'undefined'
|
|
52
|
+
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
53
|
+
: theme
|
|
54
|
+
const [strokeColor, setStrokeColor] = useState(null)
|
|
55
|
+
const displayedStrokeColor = strokeColor ?? (resolvedTheme === 'light' ? '#000000' : '#ffffff')
|
|
50
56
|
const [bgColor, setBgColor] = useState('transparent')
|
|
51
57
|
const [thickness, setThickness] = useState(2)
|
|
52
58
|
const [lineStyle, setLineStyle] = useState('solid')
|
|
@@ -62,10 +68,10 @@ export default function CircleSidebar() {
|
|
|
62
68
|
<ShapeSidebar visible={activeTool === TOOLS.CIRCLE || selectedShapeSidebar === 'circle'}>
|
|
63
69
|
<ToolbarButton
|
|
64
70
|
tooltip={t('sidebar.strokeColor')}
|
|
65
|
-
preview={<span className="w-4 h-4 rounded-md border border-white/20" style={{ backgroundColor:
|
|
71
|
+
preview={<span className="w-4 h-4 rounded-md border border-white/20" style={{ backgroundColor: displayedStrokeColor }} />}
|
|
66
72
|
>
|
|
67
73
|
<p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.sectionHeader.stroke')}</p>
|
|
68
|
-
<ColorGrid colors={STROKE_COLORS} selected={
|
|
74
|
+
<ColorGrid colors={STROKE_COLORS} selected={displayedStrokeColor} onSelect={updateStroke} />
|
|
69
75
|
</ToolbarButton>
|
|
70
76
|
|
|
71
77
|
<Divider />
|
|
@@ -106,7 +112,7 @@ export default function CircleSidebar() {
|
|
|
106
112
|
<button key={s.v} onClick={() => updateStyle(s.v)}
|
|
107
113
|
className={`w-11 h-8 flex items-center justify-center rounded-lg transition-all duration-100 ${lineStyle === s.v ? 'bg-[#7667a8]/20' : 'hover:bg-white/[0.06]'}`}
|
|
108
114
|
>
|
|
109
|
-
<svg width="28" height="4" viewBox="0 0 28 4"><line x1="0" y1="2" x2="28" y2="2" stroke="
|
|
115
|
+
<svg width="28" height="4" viewBox="0 0 28 4"><line x1="0" y1="2" x2="28" y2="2" stroke="currentColor" strokeWidth="2" strokeDasharray={s.d} strokeLinecap="round" /></svg>
|
|
110
116
|
</button>
|
|
111
117
|
))}
|
|
112
118
|
</div>
|