seat-editor 3.4.7 → 3.5.0

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 (108) hide show
  1. package/dist/app/constant.d.ts +1 -1
  2. package/dist/app/graph-view/page.d.ts +1 -0
  3. package/dist/app/graph-view/page.js +343 -0
  4. package/dist/app/graph-view/page.jsx +445 -0
  5. package/dist/app/graph-view-new/constant.d.ts +581 -0
  6. package/dist/app/graph-view-new/constant.js +6973 -0
  7. package/dist/app/graph-view-new/page.d.ts +1 -0
  8. package/dist/app/graph-view-new/page.js +71 -0
  9. package/dist/app/graph-view-new/page.jsx +98 -0
  10. package/dist/app/new-board/page.js +43 -7
  11. package/dist/app/new-board/page.jsx +45 -12
  12. package/dist/components/button-tools/index.js +7 -5
  13. package/dist/components/button-tools/index.jsx +21 -9
  14. package/dist/components/form-tools/label.js +9 -20
  15. package/dist/components/form-tools/label.jsx +38 -28
  16. package/dist/components/form-tools/shape.js +5 -5
  17. package/dist/components/form-tools/shape.jsx +8 -8
  18. package/dist/components/layer-v3/index.js +44 -3
  19. package/dist/components/layer-v3/index.jsx +120 -3
  20. package/dist/components/layer-v4/index.js +3 -2
  21. package/dist/components/layer-v4/index.jsx +3 -2
  22. package/dist/components/layer-v5/constant.d.ts +60 -0
  23. package/dist/components/layer-v5/constant.js +93 -0
  24. package/dist/components/layer-v5/index.d.ts +24 -0
  25. package/dist/components/layer-v5/index.js +927 -0
  26. package/dist/components/layer-v5/index.jsx +1049 -0
  27. package/dist/features/board-v3/index.js +350 -72
  28. package/dist/features/board-v3/index.jsx +369 -75
  29. package/dist/features/board-v3/resize-element.js +5 -0
  30. package/dist/features/board-v3/utils.d.ts +8 -0
  31. package/dist/features/board-v3/utils.js +23 -7
  32. package/dist/features/package/index.d.ts +2 -0
  33. package/dist/features/package/index.js +1 -1
  34. package/dist/features/package/index.jsx +6 -1
  35. package/dist/features/panel/index.d.ts +8 -0
  36. package/dist/features/panel/index.js +160 -38
  37. package/dist/features/panel/index.jsx +173 -46
  38. package/dist/features/panel/polygon.d.ts +2 -0
  39. package/dist/features/panel/polygon.js +44 -0
  40. package/dist/features/panel/polygon.jsx +70 -0
  41. package/dist/features/panel/select-tool.js +3 -0
  42. package/dist/features/panel/select-tool.jsx +3 -0
  43. package/dist/features/panel/selected-group.js +24 -26
  44. package/dist/features/panel/selected-group.jsx +56 -51
  45. package/dist/features/panel/text-tool.js +17 -2
  46. package/dist/features/panel/text-tool.jsx +19 -2
  47. package/dist/features/panel/upload-tool.js +17 -3
  48. package/dist/features/panel/upload-tool.jsx +23 -4
  49. package/dist/features/side-tool/index.js +43 -6
  50. package/dist/features/side-tool/index.jsx +47 -10
  51. package/dist/features/view-only-4/connect-handle.d.ts +13 -0
  52. package/dist/features/view-only-4/connect-handle.js +23 -0
  53. package/dist/features/view-only-4/connect-handle.jsx +30 -0
  54. package/dist/features/view-only-4/connection-layer.d.ts +21 -0
  55. package/dist/features/view-only-4/connection-layer.js +219 -0
  56. package/dist/features/view-only-4/connection-layer.jsx +291 -0
  57. package/dist/features/view-only-4/index.d.ts +99 -0
  58. package/dist/features/view-only-4/index.js +684 -0
  59. package/dist/features/view-only-4/index.jsx +722 -0
  60. package/dist/features/view-only-4/integration-guide.d.ts +0 -0
  61. package/dist/features/view-only-4/integration-guide.js +0 -0
  62. package/dist/features/view-only-4/use-connection-graph.d.ts +41 -0
  63. package/dist/features/view-only-4/use-connection-graph.js +182 -0
  64. package/dist/features/view-only-4/utils.d.ts +74 -0
  65. package/dist/features/view-only-4/utils.js +106 -0
  66. package/dist/features/view-only-5/connect-handle.d.ts +30 -0
  67. package/dist/features/view-only-5/connect-handle.js +88 -0
  68. package/dist/features/view-only-5/connect-handle.jsx +96 -0
  69. package/dist/features/view-only-5/connection-layer.d.ts +34 -0
  70. package/dist/features/view-only-5/connection-layer.js +182 -0
  71. package/dist/features/view-only-5/connection-layer.jsx +265 -0
  72. package/dist/features/view-only-5/index.d.ts +102 -0
  73. package/dist/features/view-only-5/index.js +585 -0
  74. package/dist/features/view-only-5/index.jsx +614 -0
  75. package/dist/features/view-only-5/use-connection-graph.d.ts +57 -0
  76. package/dist/features/view-only-5/use-connection-graph.js +196 -0
  77. package/dist/features/view-only-5/utils.d.ts +52 -0
  78. package/dist/features/view-only-5/utils.js +80 -0
  79. package/dist/features/view-only-6/connect-handle.d.ts +13 -0
  80. package/dist/features/view-only-6/connect-handle.js +20 -0
  81. package/dist/features/view-only-6/connect-handle.jsx +21 -0
  82. package/dist/features/view-only-6/connection-layer.d.ts +22 -0
  83. package/dist/features/view-only-6/connection-layer.js +191 -0
  84. package/dist/features/view-only-6/connection-layer.jsx +244 -0
  85. package/dist/features/view-only-6/index.d.ts +99 -0
  86. package/dist/features/view-only-6/index.js +687 -0
  87. package/dist/features/view-only-6/index.jsx +724 -0
  88. package/dist/features/view-only-6/use-connection-graph.d.ts +26 -0
  89. package/dist/features/view-only-6/use-connection-graph.js +103 -0
  90. package/dist/features/view-only-6/utils.d.ts +66 -0
  91. package/dist/features/view-only-6/utils.js +96 -0
  92. package/dist/features/view-only-7/connect-handle.d.ts +13 -0
  93. package/dist/features/view-only-7/connect-handle.js +23 -0
  94. package/dist/features/view-only-7/connect-handle.jsx +30 -0
  95. package/dist/features/view-only-7/connection-layer.d.ts +22 -0
  96. package/dist/features/view-only-7/connection-layer.js +165 -0
  97. package/dist/features/view-only-7/connection-layer.jsx +217 -0
  98. package/dist/features/view-only-7/index.d.ts +99 -0
  99. package/dist/features/view-only-7/index.js +687 -0
  100. package/dist/features/view-only-7/index.jsx +724 -0
  101. package/dist/features/view-only-7/use-connection-graph.d.ts +26 -0
  102. package/dist/features/view-only-7/use-connection-graph.js +104 -0
  103. package/dist/features/view-only-7/utils.d.ts +69 -0
  104. package/dist/features/view-only-7/utils.js +144 -0
  105. package/dist/index.d.ts +2 -1
  106. package/dist/index.js +2 -1
  107. package/dist/seat-editor.css +1 -1
  108. package/package.json +1 -1
@@ -0,0 +1,182 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { buildPath, getRectEdge, GRID_SIZE, NODE_WIDTH, NODE_HEIGHT, snap, } from "./utils";
4
+ const LEVEL_COLORS = [
5
+ "#22c55e",
6
+ "#f59e0b",
7
+ "#f97316",
8
+ "#ef4444",
9
+ "#a78bfa",
10
+ "#38bdf8",
11
+ ];
12
+ const getLevelColor = (level) => LEVEL_COLORS[Math.min(level, LEVEL_COLORS.length - 1)];
13
+ const floodFill = (seedIds, edges) => {
14
+ const levelMap = new Map();
15
+ const queue = [];
16
+ seedIds.forEach((id) => {
17
+ levelMap.set(id, 0);
18
+ queue.push({ id, level: 0 });
19
+ });
20
+ while (queue.length > 0) {
21
+ const { id, level } = queue.shift();
22
+ edges.forEach((edge) => {
23
+ const neighbors = [];
24
+ if (edge.from === id)
25
+ neighbors.push(edge.to);
26
+ if (edge.to === id)
27
+ neighbors.push(edge.from);
28
+ neighbors.forEach((nId) => {
29
+ if (!levelMap.has(nId)) {
30
+ levelMap.set(nId, level + 1);
31
+ queue.push({ id: nId, level: level + 1 });
32
+ }
33
+ });
34
+ });
35
+ }
36
+ return levelMap;
37
+ };
38
+ const computePoints = (fromPos, toPos, waypoints) => [fromPos, ...waypoints, toPos];
39
+ // Hitung midpoint kurva quadratic bezier pada t=0.5
40
+ const quadraticMidpoint = (from, to) => {
41
+ const dx = to.x - from.x;
42
+ const dy = to.y - from.y;
43
+ const dist = Math.sqrt(dx * dx + dy * dy);
44
+ const bend = Math.min(dist * 0.35, 80);
45
+ const mx = (from.x + to.x) / 2;
46
+ const my = (from.y + to.y) / 2;
47
+ const cpx = dist > 0 ? mx - (dy / dist) * bend : mx;
48
+ const cpy = dist > 0 ? my + (dx / dist) * bend : my;
49
+ const t = 0.5, mt = 0.5;
50
+ return {
51
+ x: mt * mt * from.x + 2 * mt * t * cpx + t * t * to.x,
52
+ y: mt * mt * from.y + 2 * mt * t * cpy + t * t * to.y,
53
+ };
54
+ };
55
+ export const ConnectionLayer = ({ edges, selectedEdge, connecting, draggingAnchor, mousePos, isConnectEdge, isSelectNode, getNodeById, onSelectEdge, onDeleteEdge, onStartDragWaypoint, onInsertWaypoint, onRemoveWaypoint, onStartDragAnchor, selectedNodeId, }) => {
56
+ const [hoveredEdge, setHoveredEdge] = useState(null);
57
+ console.log({ selectedEdge });
58
+ const nodeHighlightMap = useMemo(() => {
59
+ if (!selectedEdge)
60
+ return new Map();
61
+ const sel = edges.find((e) => e.id === selectedEdge);
62
+ if (!sel)
63
+ return new Map();
64
+ return floodFill([sel.from, sel.to], edges);
65
+ }, [selectedEdge, edges]);
66
+ const nodeHighlightMapByNode = useMemo(() => {
67
+ if (selectedNodeId) {
68
+ return floodFill([selectedNodeId], edges);
69
+ }
70
+ if (selectedEdge) {
71
+ const sel = edges.find((e) => e.id === selectedEdge);
72
+ if (!sel)
73
+ return new Map();
74
+ return floodFill([sel.from, sel.to], edges);
75
+ }
76
+ return new Map();
77
+ }, [selectedEdge, selectedNodeId, edges]);
78
+ const edgePaths = useMemo(() => {
79
+ const map = new Map();
80
+ edges.forEach((edge) => {
81
+ var _a, _b, _c;
82
+ const fromNode = getNodeById(edge.from);
83
+ const toNode = getNodeById(edge.to);
84
+ if (!fromNode || !toNode)
85
+ return;
86
+ const isDraggingFrom = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "from";
87
+ const isDraggingTo = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "to";
88
+ const fromPos = isDraggingFrom
89
+ ? mousePos
90
+ : (_a = edge.fromPos) !== null && _a !== void 0 ? _a : getRectEdge(fromNode, toNode, fromNode.width, fromNode.height);
91
+ const toPos = isDraggingTo
92
+ ? mousePos
93
+ : (_b = edge.toPos) !== null && _b !== void 0 ? _b : getRectEdge(toNode, fromNode, toNode.width, toNode.height);
94
+ const pts = computePoints(fromPos, toPos, (_c = edge.waypoints) !== null && _c !== void 0 ? _c : []);
95
+ const pathD = buildPath(pts);
96
+ const mid = quadraticMidpoint(fromPos, toPos);
97
+ map.set(edge.id, {
98
+ pathD,
99
+ allPoints: pts,
100
+ fromPos,
101
+ toPos,
102
+ midX: mid.x,
103
+ midY: mid.y,
104
+ });
105
+ });
106
+ return map;
107
+ }, [edges, getNodeById, draggingAnchor, mousePos]);
108
+ return (_jsxs("g", { id: "connection-layer", children: [Array.from(nodeHighlightMap.entries()).map(([nodeId, level]) => {
109
+ var _a, _b, _c;
110
+ const node = getNodeById(nodeId);
111
+ if (!node)
112
+ return null;
113
+ const w = (_a = node.width) !== null && _a !== void 0 ? _a : NODE_WIDTH;
114
+ const h = (_b = node.height) !== null && _b !== void 0 ? _b : NODE_HEIGHT;
115
+ const rotation = (_c = node.rotation) !== null && _c !== void 0 ? _c : 0;
116
+ const color = getLevelColor(level);
117
+ const padding = 4;
118
+ return (_jsxs("g", { transform: `translate(${node.x}, ${node.y}) rotate(${rotation})`, style: { pointerEvents: "none" }, children: [_jsx("rect", { x: -w / 2 - padding, y: -h / 2 - padding, width: w + padding * 2, height: h + padding * 2, fill: "none", stroke: color, strokeWidth: 8, rx: 5, opacity: 0.08 }), _jsx("rect", { x: -w / 2 - padding, y: -h / 2 - padding, width: w + padding * 2, height: h + padding * 2, fill: "none", stroke: color, strokeWidth: level === 0 ? 2.5 : 1.5, strokeDasharray: level === 0 ? "none" : "5 3", rx: 5, opacity: level === 0 ? 1 : 0.75 }), _jsxs("g", { transform: `translate(${w / 2 + padding - 2}, ${-h / 2 - padding})`, children: [_jsx("circle", { r: 7, fill: color, opacity: 0.9 }), _jsx("text", { textAnchor: "middle", dominantBaseline: "middle", fill: "white", fontSize: 7, fontFamily: "monospace", fontWeight: 700, style: { userSelect: "none" }, children: level })] })] }, `highlight-${nodeId}`));
119
+ }), Array.from(nodeHighlightMapByNode.entries()).map(([nodeId, level]) => {
120
+ var _a, _b, _c;
121
+ const node = getNodeById(nodeId);
122
+ if (!node)
123
+ return null;
124
+ const w = (_a = node.width) !== null && _a !== void 0 ? _a : NODE_WIDTH;
125
+ const h = (_b = node.height) !== null && _b !== void 0 ? _b : NODE_HEIGHT;
126
+ const rotation = (_c = node.rotation) !== null && _c !== void 0 ? _c : 0;
127
+ const color = getLevelColor(level);
128
+ const padding = 4;
129
+ return (_jsxs("g", { transform: `translate(${node.x}, ${node.y}) rotate(${rotation})`, style: { pointerEvents: "none" }, children: [_jsx("rect", { x: -w / 2 - padding, y: -h / 2 - padding, width: w + padding * 2, height: h + padding * 2, fill: "none", stroke: color, strokeWidth: 8, rx: 5, opacity: 0.08 }), _jsx("rect", { x: -w / 2 - padding, y: -h / 2 - padding, width: w + padding * 2, height: h + padding * 2, fill: "none", stroke: color, strokeWidth: level === 0 ? 2.5 : 1.5, strokeDasharray: level === 0 ? "none" : "5 3", rx: 5, opacity: level === 0 ? 1 : 0.75 }), _jsxs("g", { transform: `translate(${w / 2 + padding - 2}, ${-h / 2 - padding})`, children: [_jsx("circle", { r: 7, fill: color, opacity: 0.9 }), _jsx("text", { textAnchor: "middle", dominantBaseline: "middle", fill: "white", fontSize: 7, fontFamily: "monospace", fontWeight: 700, style: { userSelect: "none" }, children: level })] })] }, `highlight-${nodeId}`));
130
+ }), edges.map((edge) => {
131
+ var _a;
132
+ const ep = edgePaths.get(edge.id);
133
+ if (!ep)
134
+ return null;
135
+ const { pathD, allPoints, fromPos, toPos, midX, midY } = ep;
136
+ const isSel = selectedEdge === edge.id;
137
+ const isHov = hoveredEdge === edge.id;
138
+ const fromLevel = nodeHighlightMap.get(edge.from);
139
+ const toLevel = nodeHighlightMap.get(edge.to);
140
+ const isInNetwork = fromLevel !== undefined && toLevel !== undefined;
141
+ console.log({ isInNetwork, fromLevel, toLevel }, selectedEdge, !isSel && !isInNetwork);
142
+ const networkColor = isInNetwork
143
+ ? getLevelColor(Math.max(fromLevel, toLevel))
144
+ : "#38bdf8";
145
+ const isDraggingFrom = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "from";
146
+ const isDraggingTo = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "to";
147
+ return (_jsxs("g", { children: [_jsx("path", { d: pathD, stroke: "transparent", strokeWidth: 14, fill: "none", style: { cursor: "pointer" }, onClick: (e) => {
148
+ e.stopPropagation();
149
+ onSelectEdge(edge.id);
150
+ }, onMouseEnter: () => setHoveredEdge(edge.id), onMouseLeave: () => setHoveredEdge(null) }), (isSel || isHov) && (_jsx("path", { d: pathD, stroke: isSel ? "#a78bfa" : "#38bdf8", strokeWidth: 8, fill: "none", opacity: isSel ? 0.12 : 0.06, style: { pointerEvents: "none" } })), _jsx("path", { d: pathD, stroke: isSel
151
+ ? "#a78bfa"
152
+ : isInNetwork && selectedEdge
153
+ ? networkColor
154
+ : "#38bdf8", strokeWidth: isSel ? 2 : isHov ? 2 : 1.5, fill: "none", opacity: selectedEdge && !isSel && !isInNetwork ? 0.25 : 0.9, strokeLinecap: "round", style: { pointerEvents: "none" } }), isSel && (_jsxs("g", { children: [isDraggingFrom && (_jsx("circle", { cx: fromPos.x, cy: fromPos.y, r: 12, fill: "#38bdf8", opacity: 0.15, style: { pointerEvents: "none" } })), _jsx("circle", { cx: fromPos.x, cy: fromPos.y, r: 10, fill: "transparent", style: { cursor: "grab" }, onMouseDown: (e) => {
155
+ e.stopPropagation();
156
+ onStartDragAnchor(e, edge.id, "from");
157
+ } }), _jsx("circle", { cx: fromPos.x, cy: fromPos.y, r: 6, fill: isDraggingFrom ? "#a78bfa" : "#38bdf8", stroke: "#0f172a", strokeWidth: 1.5, style: { pointerEvents: "none" } }), _jsx("circle", { cx: fromPos.x, cy: fromPos.y, r: 2.5, fill: "#0f172a", style: { pointerEvents: "none" } })] })), isSel && (_jsxs("g", { children: [isDraggingTo && (_jsx("circle", { cx: toPos.x, cy: toPos.y, r: 12, fill: "#22c55e", opacity: 0.15, style: { pointerEvents: "none" } })), _jsx("circle", { cx: toPos.x, cy: toPos.y, r: 10, fill: "transparent", style: { cursor: "grab" }, onMouseDown: (e) => {
158
+ e.stopPropagation();
159
+ onStartDragAnchor(e, edge.id, "to");
160
+ } }), _jsx("circle", { cx: toPos.x, cy: toPos.y, r: 6, fill: isDraggingTo ? "#a78bfa" : "#22c55e", stroke: "#0f172a", strokeWidth: 1.5, style: { pointerEvents: "none" } }), _jsx("circle", { cx: toPos.x, cy: toPos.y, r: 2.5, fill: "#0f172a", style: { pointerEvents: "none" } })] })), ((_a = edge.waypoints) !== null && _a !== void 0 ? _a : []).map((wp, i) => (_jsxs("g", { children: [_jsx("circle", { cx: wp.x, cy: wp.y, r: 12, fill: "transparent", style: { cursor: "grab" }, onMouseDown: (e) => onStartDragWaypoint(e, edge.id, i), onContextMenu: (e) => onRemoveWaypoint(e, edge.id, i) }), _jsx("circle", { cx: wp.x, cy: wp.y, r: isSel ? 8 : 5, fill: isSel ? "#1e1b2e" : "#0f172a", stroke: isSel ? "#a78bfa" : "#38bdf8", strokeWidth: isSel ? 2 : 1.2, style: { pointerEvents: "none" } }), _jsx("circle", { cx: wp.x, cy: wp.y, r: isSel ? 3 : 2, fill: isSel ? "#a78bfa" : "#38bdf8", style: { pointerEvents: "none" } })] }, `wp-${i}`))), isSel &&
161
+ isConnectEdge &&
162
+ allPoints.slice(0, -1).map((pt, i) => {
163
+ var _a;
164
+ const next = allPoints[i + 1];
165
+ const mx = snap((pt.x + next.x) / 2);
166
+ const my = snap((pt.y + next.y) / 2);
167
+ const tooClose = ((_a = edge.waypoints) !== null && _a !== void 0 ? _a : []).some((wp) => Math.abs(wp.x - mx) < GRID_SIZE &&
168
+ Math.abs(wp.y - my) < GRID_SIZE);
169
+ if (tooClose)
170
+ return null;
171
+ return (_jsxs("g", { children: [_jsx("circle", { cx: mx, cy: my, r: 10, fill: "transparent", style: { cursor: "crosshair" }, onMouseDown: (e) => onInsertWaypoint(e, edge.id, i, mx, my) }), _jsx("circle", { cx: mx, cy: my, r: 4, fill: "#1e1b2e", stroke: "#a78bfa", strokeWidth: 1, strokeDasharray: "2 2", style: { pointerEvents: "none" } }), _jsx("circle", { cx: mx, cy: my, r: 1.5, fill: "#a78bfa", style: { pointerEvents: "none" } })] }, `mid-${i}`));
172
+ }), isSel && isConnectEdge && (_jsxs("g", { onClick: (e) => {
173
+ e.stopPropagation();
174
+ onDeleteEdge(edge.id);
175
+ }, style: { cursor: "pointer" }, children: [_jsx("rect", { x: midX - 16, y: midY - 10, width: 32, height: 18, rx: 4, fill: "#1e1b2e", stroke: "#a78bfa", strokeWidth: 1, opacity: 0.95 }), _jsx("text", { x: midX, y: midY, textAnchor: "middle", dominantBaseline: "middle", fill: "#a78bfa", fontSize: 9, fontFamily: "monospace", fontWeight: 600, children: "DEL" })] }))] }, edge.id));
176
+ }), connecting &&
177
+ (() => {
178
+ const pts = computePoints(connecting.fromPos, mousePos, []);
179
+ const pathD = buildPath(pts);
180
+ return (_jsxs("g", { children: [_jsx("circle", { cx: connecting.fromPos.x, cy: connecting.fromPos.y, r: 4, fill: "#a78bfa", stroke: "#0f172a", strokeWidth: 0.8, opacity: 0.9, style: { pointerEvents: "none" } }), _jsx("path", { d: pathD, stroke: "#a78bfa", strokeWidth: 1.5, fill: "none", strokeDasharray: "6 3", strokeLinecap: "round", opacity: 0.8, style: { pointerEvents: "none" } })] }));
181
+ })(), draggingAnchor && (_jsx("text", { x: mousePos.x + 14, y: mousePos.y - 8, fill: "#a78bfa", fontSize: 9, fontFamily: "monospace", opacity: 0.8, style: { pointerEvents: "none", userSelect: "none" }, children: "klik node tujuan" }))] }));
182
+ };
@@ -0,0 +1,265 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { buildPath, getRectEdge, GRID_SIZE, NODE_WIDTH, NODE_HEIGHT, snap, } from "./utils";
3
+ const LEVEL_COLORS = [
4
+ "#22c55e",
5
+ "#f59e0b",
6
+ "#f97316",
7
+ "#ef4444",
8
+ "#a78bfa",
9
+ "#38bdf8",
10
+ ];
11
+ const getLevelColor = (level) => LEVEL_COLORS[Math.min(level, LEVEL_COLORS.length - 1)];
12
+ const floodFill = (seedIds, edges) => {
13
+ const levelMap = new Map();
14
+ const queue = [];
15
+ seedIds.forEach((id) => {
16
+ levelMap.set(id, 0);
17
+ queue.push({ id, level: 0 });
18
+ });
19
+ while (queue.length > 0) {
20
+ const { id, level } = queue.shift();
21
+ edges.forEach((edge) => {
22
+ const neighbors = [];
23
+ if (edge.from === id)
24
+ neighbors.push(edge.to);
25
+ if (edge.to === id)
26
+ neighbors.push(edge.from);
27
+ neighbors.forEach((nId) => {
28
+ if (!levelMap.has(nId)) {
29
+ levelMap.set(nId, level + 1);
30
+ queue.push({ id: nId, level: level + 1 });
31
+ }
32
+ });
33
+ });
34
+ }
35
+ return levelMap;
36
+ };
37
+ const computePoints = (fromPos, toPos, waypoints) => [fromPos, ...waypoints, toPos];
38
+ // Hitung midpoint kurva quadratic bezier pada t=0.5
39
+ const quadraticMidpoint = (from, to) => {
40
+ const dx = to.x - from.x;
41
+ const dy = to.y - from.y;
42
+ const dist = Math.sqrt(dx * dx + dy * dy);
43
+ const bend = Math.min(dist * 0.35, 80);
44
+ const mx = (from.x + to.x) / 2;
45
+ const my = (from.y + to.y) / 2;
46
+ const cpx = dist > 0 ? mx - (dy / dist) * bend : mx;
47
+ const cpy = dist > 0 ? my + (dx / dist) * bend : my;
48
+ const t = 0.5, mt = 0.5;
49
+ return {
50
+ x: mt * mt * from.x + 2 * mt * t * cpx + t * t * to.x,
51
+ y: mt * mt * from.y + 2 * mt * t * cpy + t * t * to.y,
52
+ };
53
+ };
54
+ export const ConnectionLayer = ({ edges, selectedEdge, connecting, draggingAnchor, mousePos, isConnectEdge, isSelectNode, getNodeById, onSelectEdge, onDeleteEdge, onStartDragWaypoint, onInsertWaypoint, onRemoveWaypoint, onStartDragAnchor, selectedNodeId, }) => {
55
+ const [hoveredEdge, setHoveredEdge] = useState(null);
56
+ console.log({ selectedEdge });
57
+ const nodeHighlightMap = useMemo(() => {
58
+ if (!selectedEdge)
59
+ return new Map();
60
+ const sel = edges.find((e) => e.id === selectedEdge);
61
+ if (!sel)
62
+ return new Map();
63
+ return floodFill([sel.from, sel.to], edges);
64
+ }, [selectedEdge, edges]);
65
+ const nodeHighlightMapByNode = useMemo(() => {
66
+ if (selectedNodeId) {
67
+ return floodFill([selectedNodeId], edges);
68
+ }
69
+ if (selectedEdge) {
70
+ const sel = edges.find((e) => e.id === selectedEdge);
71
+ if (!sel)
72
+ return new Map();
73
+ return floodFill([sel.from, sel.to], edges);
74
+ }
75
+ return new Map();
76
+ }, [selectedEdge, selectedNodeId, edges]);
77
+ const edgePaths = useMemo(() => {
78
+ const map = new Map();
79
+ edges.forEach((edge) => {
80
+ var _a, _b, _c;
81
+ const fromNode = getNodeById(edge.from);
82
+ const toNode = getNodeById(edge.to);
83
+ if (!fromNode || !toNode)
84
+ return;
85
+ const isDraggingFrom = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "from";
86
+ const isDraggingTo = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "to";
87
+ const fromPos = isDraggingFrom
88
+ ? mousePos
89
+ : (_a = edge.fromPos) !== null && _a !== void 0 ? _a : getRectEdge(fromNode, toNode, fromNode.width, fromNode.height);
90
+ const toPos = isDraggingTo
91
+ ? mousePos
92
+ : (_b = edge.toPos) !== null && _b !== void 0 ? _b : getRectEdge(toNode, fromNode, toNode.width, toNode.height);
93
+ const pts = computePoints(fromPos, toPos, (_c = edge.waypoints) !== null && _c !== void 0 ? _c : []);
94
+ const pathD = buildPath(pts);
95
+ const mid = quadraticMidpoint(fromPos, toPos);
96
+ map.set(edge.id, {
97
+ pathD,
98
+ allPoints: pts,
99
+ fromPos,
100
+ toPos,
101
+ midX: mid.x,
102
+ midY: mid.y,
103
+ });
104
+ });
105
+ return map;
106
+ }, [edges, getNodeById, draggingAnchor, mousePos]);
107
+ return (<g id="connection-layer">
108
+ {/* Node highlight flood fill */}
109
+ {Array.from(nodeHighlightMap.entries()).map(([nodeId, level]) => {
110
+ var _a, _b, _c;
111
+ const node = getNodeById(nodeId);
112
+ if (!node)
113
+ return null;
114
+ const w = (_a = node.width) !== null && _a !== void 0 ? _a : NODE_WIDTH;
115
+ const h = (_b = node.height) !== null && _b !== void 0 ? _b : NODE_HEIGHT;
116
+ const rotation = (_c = node.rotation) !== null && _c !== void 0 ? _c : 0;
117
+ const color = getLevelColor(level);
118
+ const padding = 4;
119
+ return (<g key={`highlight-${nodeId}`} transform={`translate(${node.x}, ${node.y}) rotate(${rotation})`} style={{ pointerEvents: "none" }}>
120
+ <rect x={-w / 2 - padding} y={-h / 2 - padding} width={w + padding * 2} height={h + padding * 2} fill="none" stroke={color} strokeWidth={8} rx={5} opacity={0.08}/>
121
+ <rect x={-w / 2 - padding} y={-h / 2 - padding} width={w + padding * 2} height={h + padding * 2} fill="none" stroke={color} strokeWidth={level === 0 ? 2.5 : 1.5} strokeDasharray={level === 0 ? "none" : "5 3"} rx={5} opacity={level === 0 ? 1 : 0.75}/>
122
+ <g transform={`translate(${w / 2 + padding - 2}, ${-h / 2 - padding})`}>
123
+ <circle r={7} fill={color} opacity={0.9}/>
124
+ <text textAnchor="middle" dominantBaseline="middle" fill="white" fontSize={7} fontFamily="monospace" fontWeight={700} style={{ userSelect: "none" }}>
125
+ {level}
126
+ </text>
127
+ </g>
128
+ </g>);
129
+ })}
130
+
131
+ {Array.from(nodeHighlightMapByNode.entries()).map(([nodeId, level]) => {
132
+ var _a, _b, _c;
133
+ const node = getNodeById(nodeId);
134
+ if (!node)
135
+ return null;
136
+ const w = (_a = node.width) !== null && _a !== void 0 ? _a : NODE_WIDTH;
137
+ const h = (_b = node.height) !== null && _b !== void 0 ? _b : NODE_HEIGHT;
138
+ const rotation = (_c = node.rotation) !== null && _c !== void 0 ? _c : 0;
139
+ const color = getLevelColor(level);
140
+ const padding = 4;
141
+ return (<g key={`highlight-${nodeId}`} transform={`translate(${node.x}, ${node.y}) rotate(${rotation})`} style={{ pointerEvents: "none" }}>
142
+ <rect x={-w / 2 - padding} y={-h / 2 - padding} width={w + padding * 2} height={h + padding * 2} fill="none" stroke={color} strokeWidth={8} rx={5} opacity={0.08}/>
143
+ <rect x={-w / 2 - padding} y={-h / 2 - padding} width={w + padding * 2} height={h + padding * 2} fill="none" stroke={color} strokeWidth={level === 0 ? 2.5 : 1.5} strokeDasharray={level === 0 ? "none" : "5 3"} rx={5} opacity={level === 0 ? 1 : 0.75}/>
144
+ <g transform={`translate(${w / 2 + padding - 2}, ${-h / 2 - padding})`}>
145
+ <circle r={7} fill={color} opacity={0.9}/>
146
+ <text textAnchor="middle" dominantBaseline="middle" fill="white" fontSize={7} fontFamily="monospace" fontWeight={700} style={{ userSelect: "none" }}>
147
+ {level}
148
+ </text>
149
+ </g>
150
+ </g>);
151
+ })}
152
+
153
+ {/* Edges */}
154
+ {edges.map((edge) => {
155
+ var _a;
156
+ const ep = edgePaths.get(edge.id);
157
+ if (!ep)
158
+ return null;
159
+ const { pathD, allPoints, fromPos, toPos, midX, midY } = ep;
160
+ const isSel = selectedEdge === edge.id;
161
+ const isHov = hoveredEdge === edge.id;
162
+ const fromLevel = nodeHighlightMap.get(edge.from);
163
+ const toLevel = nodeHighlightMap.get(edge.to);
164
+ const isInNetwork = fromLevel !== undefined && toLevel !== undefined;
165
+ console.log({ isInNetwork, fromLevel, toLevel }, selectedEdge, !isSel && !isInNetwork);
166
+ const networkColor = isInNetwork
167
+ ? getLevelColor(Math.max(fromLevel, toLevel))
168
+ : "#38bdf8";
169
+ const isDraggingFrom = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "from";
170
+ const isDraggingTo = (draggingAnchor === null || draggingAnchor === void 0 ? void 0 : draggingAnchor.edgeId) === edge.id && draggingAnchor.side === "to";
171
+ return (<g key={edge.id}>
172
+ {/* Hit area */}
173
+ <path d={pathD} stroke="transparent" strokeWidth={14} fill="none" style={{ cursor: "pointer" }} onClick={(e) => {
174
+ e.stopPropagation();
175
+ onSelectEdge(edge.id);
176
+ }} onMouseEnter={() => setHoveredEdge(edge.id)} onMouseLeave={() => setHoveredEdge(null)}/>
177
+
178
+ {/* Glow */}
179
+ {(isSel || isHov) && (<path d={pathD} stroke={isSel ? "#a78bfa" : "#38bdf8"} strokeWidth={8} fill="none" opacity={isSel ? 0.12 : 0.06} style={{ pointerEvents: "none" }}/>)}
180
+
181
+ {/* Curve */}
182
+ <path d={pathD} stroke={isSel
183
+ ? "#a78bfa"
184
+ : isInNetwork && selectedEdge
185
+ ? networkColor
186
+ : "#38bdf8"} strokeWidth={isSel ? 2 : isHov ? 2 : 1.5} fill="none" opacity={selectedEdge && !isSel && !isInNetwork ? 0.25 : 0.9} strokeLinecap="round" style={{ pointerEvents: "none" }}/>
187
+
188
+ {/* FROM dot */}
189
+ {isSel && (<g>
190
+ {isDraggingFrom && (<circle cx={fromPos.x} cy={fromPos.y} r={12} fill="#38bdf8" opacity={0.15} style={{ pointerEvents: "none" }}/>)}
191
+ <circle cx={fromPos.x} cy={fromPos.y} r={10} fill="transparent" style={{ cursor: "grab" }} onMouseDown={(e) => {
192
+ e.stopPropagation();
193
+ onStartDragAnchor(e, edge.id, "from");
194
+ }}/>
195
+ <circle cx={fromPos.x} cy={fromPos.y} r={6} fill={isDraggingFrom ? "#a78bfa" : "#38bdf8"} stroke="#0f172a" strokeWidth={1.5} style={{ pointerEvents: "none" }}/>
196
+ <circle cx={fromPos.x} cy={fromPos.y} r={2.5} fill="#0f172a" style={{ pointerEvents: "none" }}/>
197
+ </g>)}
198
+
199
+ {/* TO dot */}
200
+ {isSel && (<g>
201
+ {isDraggingTo && (<circle cx={toPos.x} cy={toPos.y} r={12} fill="#22c55e" opacity={0.15} style={{ pointerEvents: "none" }}/>)}
202
+ <circle cx={toPos.x} cy={toPos.y} r={10} fill="transparent" style={{ cursor: "grab" }} onMouseDown={(e) => {
203
+ e.stopPropagation();
204
+ onStartDragAnchor(e, edge.id, "to");
205
+ }}/>
206
+ <circle cx={toPos.x} cy={toPos.y} r={6} fill={isDraggingTo ? "#a78bfa" : "#22c55e"} stroke="#0f172a" strokeWidth={1.5} style={{ pointerEvents: "none" }}/>
207
+ <circle cx={toPos.x} cy={toPos.y} r={2.5} fill="#0f172a" style={{ pointerEvents: "none" }}/>
208
+ </g>)}
209
+
210
+ {/* Waypoints */}
211
+ {((_a = edge.waypoints) !== null && _a !== void 0 ? _a : []).map((wp, i) => (<g key={`wp-${i}`}>
212
+ <circle cx={wp.x} cy={wp.y} r={12} fill="transparent" style={{ cursor: "grab" }} onMouseDown={(e) => onStartDragWaypoint(e, edge.id, i)} onContextMenu={(e) => onRemoveWaypoint(e, edge.id, i)}/>
213
+ <circle cx={wp.x} cy={wp.y} r={isSel ? 8 : 5} fill={isSel ? "#1e1b2e" : "#0f172a"} stroke={isSel ? "#a78bfa" : "#38bdf8"} strokeWidth={isSel ? 2 : 1.2} style={{ pointerEvents: "none" }}/>
214
+ <circle cx={wp.x} cy={wp.y} r={isSel ? 3 : 2} fill={isSel ? "#a78bfa" : "#38bdf8"} style={{ pointerEvents: "none" }}/>
215
+ </g>))}
216
+
217
+ {/* Midpoint insert handles */}
218
+ {isSel &&
219
+ isConnectEdge &&
220
+ allPoints.slice(0, -1).map((pt, i) => {
221
+ var _a;
222
+ const next = allPoints[i + 1];
223
+ const mx = snap((pt.x + next.x) / 2);
224
+ const my = snap((pt.y + next.y) / 2);
225
+ const tooClose = ((_a = edge.waypoints) !== null && _a !== void 0 ? _a : []).some((wp) => Math.abs(wp.x - mx) < GRID_SIZE &&
226
+ Math.abs(wp.y - my) < GRID_SIZE);
227
+ if (tooClose)
228
+ return null;
229
+ return (<g key={`mid-${i}`}>
230
+ <circle cx={mx} cy={my} r={10} fill="transparent" style={{ cursor: "crosshair" }} onMouseDown={(e) => onInsertWaypoint(e, edge.id, i, mx, my)}/>
231
+ <circle cx={mx} cy={my} r={4} fill="#1e1b2e" stroke="#a78bfa" strokeWidth={1} strokeDasharray="2 2" style={{ pointerEvents: "none" }}/>
232
+ <circle cx={mx} cy={my} r={1.5} fill="#a78bfa" style={{ pointerEvents: "none" }}/>
233
+ </g>);
234
+ })}
235
+
236
+ {/* DEL badge */}
237
+ {isSel && isConnectEdge && (<g onClick={(e) => {
238
+ e.stopPropagation();
239
+ onDeleteEdge(edge.id);
240
+ }} style={{ cursor: "pointer" }}>
241
+ <rect x={midX - 16} y={midY - 10} width={32} height={18} rx={4} fill="#1e1b2e" stroke="#a78bfa" strokeWidth={1} opacity={0.95}/>
242
+ <text x={midX} y={midY} textAnchor="middle" dominantBaseline="middle" fill="#a78bfa" fontSize={9} fontFamily="monospace" fontWeight={600}>
243
+ DEL
244
+ </text>
245
+ </g>)}
246
+ </g>);
247
+ })}
248
+
249
+ {/* Preview saat connecting */}
250
+ {connecting &&
251
+ (() => {
252
+ const pts = computePoints(connecting.fromPos, mousePos, []);
253
+ const pathD = buildPath(pts);
254
+ return (<g>
255
+ <circle cx={connecting.fromPos.x} cy={connecting.fromPos.y} r={4} fill="#a78bfa" stroke="#0f172a" strokeWidth={0.8} opacity={0.9} style={{ pointerEvents: "none" }}/>
256
+ <path d={pathD} stroke="#a78bfa" strokeWidth={1.5} fill="none" strokeDasharray="6 3" strokeLinecap="round" opacity={0.8} style={{ pointerEvents: "none" }}/>
257
+ </g>);
258
+ })()}
259
+
260
+ {/* Hint drag anchor */}
261
+ {draggingAnchor && (<text x={mousePos.x + 14} y={mousePos.y - 8} fill="#a78bfa" fontSize={9} fontFamily="monospace" opacity={0.8} style={{ pointerEvents: "none", userSelect: "none" }}>
262
+ klik node tujuan
263
+ </text>)}
264
+ </g>);
265
+ };
@@ -0,0 +1,102 @@
1
+ import React, { CSSProperties, SVGAttributes } from "react";
2
+ import { ReactZoomPanPinchRef, ReactZoomPanPinchProps, ReactZoomPanPinchContentRef } from "react-zoom-pan-pinch";
3
+ import { PropertiesProps } from "../../dto/table";
4
+ import { EventHandleType } from "../../dto/event-handler";
5
+ import { EdgeType } from "./utils";
6
+ export type TableGhost = {
7
+ table: PropertiesProps;
8
+ event: EventHandleType;
9
+ };
10
+ export type TableMatchKey = {
11
+ key: string | number;
12
+ properties?: PropertiesProps;
13
+ className?: string;
14
+ };
15
+ export type TableMatchEvent = {
16
+ event: EventHandleType;
17
+ properties: PropertiesProps;
18
+ };
19
+ export type ComponentProps<T = undefined> = T extends undefined ? PropertiesProps : Omit<PropertiesProps, keyof T> & T;
20
+ export type OnCurrentStateChange<TMeta = undefined> = ({ components, extraComponents, background, boundingBox, }: {
21
+ components: ComponentProps<TMeta>[];
22
+ extraComponents: ComponentProps[];
23
+ background: string;
24
+ boundingBox: PropertiesProps | null;
25
+ }) => void;
26
+ export type TransformProps = React.ForwardRefExoticComponent<Omit<ReactZoomPanPinchProps, "ref"> & React.RefAttributes<ReactZoomPanPinchContentRef>>;
27
+ export type RefLayerView = {
28
+ svgRef: SVGSVGElement;
29
+ transformRef: ReactZoomPanPinchRef;
30
+ containerRef: HTMLDivElement;
31
+ tableGhost: SVGGElement;
32
+ hoverUnderghost: ComponentProps;
33
+ };
34
+ export type TypeActionProps<TMeta = undefined> = {
35
+ targetTable: ComponentProps<TMeta>;
36
+ sourceTable: ComponentProps<TMeta>;
37
+ };
38
+ export interface LayerViewProps<TMeta = undefined> {
39
+ componentProps?: ComponentProps<TMeta>[];
40
+ extraComponentProps?: ComponentProps[];
41
+ onSelectComponent?: (component: ComponentProps<TMeta>) => void;
42
+ onDoubleClick?: (component: ComponentProps<TMeta>) => void;
43
+ mappingKey?: string;
44
+ tableMatchKey?: TableMatchKey[];
45
+ eventMatchTable?: TableMatchEvent[];
46
+ statusKey?: string;
47
+ defaultBackground?: string;
48
+ transformProps?: ReactZoomPanPinchProps;
49
+ containerProps?: React.HTMLAttributes<HTMLDivElement>;
50
+ svgProps?: SVGAttributes<SVGSVGElement>;
51
+ ghostAttributes?: SVGAttributes<SVGGElement>;
52
+ iconTags?: {
53
+ icon: React.JSX.Element;
54
+ key: string;
55
+ }[];
56
+ privilegedTags?: {
57
+ key: string;
58
+ items: string[];
59
+ }[];
60
+ tooltipProps?: {
61
+ className?: string;
62
+ style?: CSSProperties;
63
+ minWidth?: number;
64
+ children: React.ReactNode;
65
+ };
66
+ dragTableBlockKey?: {
67
+ key: string;
68
+ value: string | number | null;
69
+ }[];
70
+ onRightClick?: (e: MouseEvent, component: ComponentProps<TMeta>) => void;
71
+ allowTooltip?: boolean;
72
+ onDrop?: (e: React.MouseEvent<SVGSVGElement>, component: TypeActionProps<TMeta>) => void;
73
+ onSwitch?: (e: MouseEvent, component: TypeActionProps<TMeta>) => void;
74
+ refs?: React.ForwardedRef<RefLayerView>;
75
+ viewStyles?: {
76
+ paddingTop?: number;
77
+ paddingLeft?: number;
78
+ paddingRight?: number;
79
+ paddingBottom?: number;
80
+ };
81
+ loadingRender?: {
82
+ state: boolean;
83
+ element: React.JSX.Element;
84
+ };
85
+ defaultBoundingBox?: PropertiesProps;
86
+ viewOnly?: boolean;
87
+ actionPrivileged?: {
88
+ select?: boolean;
89
+ move?: boolean;
90
+ switch?: boolean;
91
+ drop?: boolean;
92
+ rightClick?: boolean;
93
+ double?: boolean;
94
+ };
95
+ onEdgesChange?: (edges: EdgeType[], table: PropertiesProps[]) => void;
96
+ keyNode?: string;
97
+ isConnectEdge?: boolean;
98
+ isSelectNode?: boolean;
99
+ onMakeSelection?: (component: ComponentProps<TMeta>[]) => void;
100
+ }
101
+ declare const LayerView: <TMeta>(props: LayerViewProps<TMeta>) => import("react/jsx-runtime").JSX.Element;
102
+ export default LayerView;