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
File without changes
@@ -0,0 +1,41 @@
1
+ import { EdgeType, NodeType } from "./utils";
2
+ import { PropertiesProps } from "@/dto/table";
3
+ import { TableMatchKey } from ".";
4
+ export type UseConnectionGraphOptions = {
5
+ svgRef: React.RefObject<SVGSVGElement>;
6
+ /**
7
+ * Fungsi untuk convert komponen dari store ke NodeType.
8
+ * Karena node kamu bukan node biasa tapi komponen dari board,
9
+ * kamu perlu provide ini untuk mapping id -> posisi
10
+ */
11
+ getNodeById: (id: string) => NodeType | null;
12
+ onEdgesChange?: (edges: EdgeType[], table: PropertiesProps[]) => void;
13
+ keyNode?: string;
14
+ mappingKey?: string;
15
+ tableMatchKey?: TableMatchKey[];
16
+ statusKey?: string;
17
+ };
18
+ export declare const useConnectionGraph: ({ svgRef, getNodeById, onEdgesChange, keyNode, mappingKey, tableMatchKey, statusKey, }: UseConnectionGraphOptions) => {
19
+ edges: EdgeType[];
20
+ setEdges: import("react").Dispatch<import("react").SetStateAction<EdgeType[]>>;
21
+ selectedEdge: string;
22
+ connecting: {
23
+ fromId: string;
24
+ };
25
+ mousePos: {
26
+ x: number;
27
+ y: number;
28
+ };
29
+ startConnect: (fromId: string) => void;
30
+ endConnect: (toId: string) => void;
31
+ cancelConnect: () => void;
32
+ startDragWaypoint: (e: React.MouseEvent, edgeId: string, index: number) => void;
33
+ insertWaypoint: (e: React.MouseEvent, edgeId: string, insertIndex: number, x: number, y: number) => void;
34
+ removeWaypoint: (e: React.MouseEvent, edgeId: string, index: number) => void;
35
+ selectEdge: (edgeId: string) => void;
36
+ deleteEdge: (edgeId: string) => void;
37
+ deleteSelectedEdge: () => void;
38
+ clearSelection: () => void;
39
+ onMouseMove: (e: React.MouseEvent<SVGSVGElement> | MouseEvent) => void;
40
+ onMouseUp: () => void;
41
+ };
@@ -0,0 +1,182 @@
1
+ // hooks/use-connection-graph.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { defaultWaypoints, getRectEdge, snap, } from "./utils"; // sesuaikan path
4
+ import { useAppSelector } from "@/hooks/use-redux";
5
+ export const useConnectionGraph = ({ svgRef, getNodeById, onEdgesChange, keyNode, mappingKey, tableMatchKey, statusKey, }) => {
6
+ const components = useAppSelector((state) => state.board.components);
7
+ const [selectedWaypoint, setSelectedWaypoint] = useState(null);
8
+ const [edges, setEdges] = useState([]);
9
+ const [selectedEdge, setSelectedEdge] = useState(null);
10
+ const [connecting, setConnecting] = useState(null);
11
+ const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
12
+ // Dragging waypoint state
13
+ const draggingWP = useRef(null);
14
+ // Convert client coords -> SVG coords (dengan transform matrix)
15
+ const getSVGPoint = useCallback((clientX, clientY) => {
16
+ var _a;
17
+ const svg = svgRef.current;
18
+ if (!svg)
19
+ return { x: 0, y: 0 };
20
+ const pt = svg.createSVGPoint();
21
+ pt.x = clientX;
22
+ pt.y = clientY;
23
+ const svgP = pt.matrixTransform((_a = svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse());
24
+ return { x: svgP.x, y: svgP.y };
25
+ }, [svgRef]);
26
+ // ─── Connect ─────────────────────────────────────────────────────────────
27
+ const startConnect = useCallback((fromId) => {
28
+ setConnecting({ fromId });
29
+ setSelectedEdge(null);
30
+ }, []);
31
+ const endConnect = useCallback((toId) => {
32
+ if (!connecting || connecting.fromId === toId) {
33
+ setConnecting(null);
34
+ return;
35
+ }
36
+ const fromNode = getNodeById(connecting.fromId);
37
+ const toNode = getNodeById(toId);
38
+ if (!fromNode || !toNode) {
39
+ setConnecting(null);
40
+ return;
41
+ }
42
+ // Cegah duplikat
43
+ setEdges((prev) => {
44
+ const exists = prev.find((e) => (e.from === connecting.fromId && e.to === toId) ||
45
+ (e.from === toId && e.to === connecting.fromId));
46
+ if (exists)
47
+ return prev;
48
+ const start = getRectEdge(fromNode, toNode, fromNode.width, fromNode.height);
49
+ const end = getRectEdge(toNode, fromNode, toNode.width, toNode.height);
50
+ return [
51
+ ...prev,
52
+ {
53
+ id: `edge-${Date.now()}`,
54
+ from: connecting.fromId,
55
+ to: toId,
56
+ waypoints: defaultWaypoints(start, end),
57
+ },
58
+ ];
59
+ });
60
+ setConnecting(null);
61
+ }, [connecting, getNodeById]);
62
+ const cancelConnect = useCallback(() => setConnecting(null), []);
63
+ // ─── Mouse move (track mouse + drag waypoint) ─────────────────────────────
64
+ const onMouseMove = useCallback((e) => {
65
+ const clientX = "clientX" in e ? e.clientX : 0;
66
+ const clientY = "clientY" in e ? e.clientY : 0;
67
+ const svgPt = getSVGPoint(clientX, clientY);
68
+ setMousePos(svgPt);
69
+ if (draggingWP.current) {
70
+ const { edgeId, index } = draggingWP.current;
71
+ setEdges((prev) => prev.map((ed) => {
72
+ if (ed.id !== edgeId)
73
+ return ed;
74
+ return Object.assign(Object.assign({}, ed), { waypoints: ed.waypoints.map((wp, i) => i === index ? { x: snap(svgPt.x), y: snap(svgPt.y) } : wp) });
75
+ }));
76
+ }
77
+ }, [getSVGPoint]);
78
+ const onMouseUp = useCallback(() => {
79
+ draggingWP.current = null;
80
+ }, []);
81
+ // ─── Waypoint drag ────────────────────────────────────────────────────────
82
+ const startDragWaypoint = useCallback((e, edgeId, index) => {
83
+ e.stopPropagation();
84
+ draggingWP.current = { edgeId, index };
85
+ setSelectedEdge(edgeId);
86
+ }, []);
87
+ // Insert waypoint di midpoint
88
+ const insertWaypoint = useCallback((e, edgeId, insertIndex, x, y) => {
89
+ e.stopPropagation();
90
+ setEdges((prev) => prev.map((ed) => {
91
+ if (ed.id !== edgeId)
92
+ return ed;
93
+ const newWP = [...ed.waypoints];
94
+ newWP.splice(insertIndex, 0, { x: snap(x), y: snap(y) });
95
+ return Object.assign(Object.assign({}, ed), { waypoints: newWP });
96
+ }));
97
+ // Langsung set drag ke waypoint baru
98
+ draggingWP.current = { edgeId, index: insertIndex };
99
+ }, []);
100
+ // Remove waypoint kanan klik
101
+ const removeWaypoint = useCallback((e, edgeId, index) => {
102
+ e.preventDefault();
103
+ e.stopPropagation();
104
+ setEdges((prev) => prev.map((ed) => {
105
+ if (ed.id !== edgeId)
106
+ return ed;
107
+ if (ed.waypoints.length <= 1)
108
+ return ed;
109
+ return Object.assign(Object.assign({}, ed), { waypoints: ed.waypoints.filter((_, i) => i !== index) });
110
+ }));
111
+ }, []);
112
+ // ─── Edge select / delete ─────────────────────────────────────────────────
113
+ const selectEdge = useCallback((edgeId) => {
114
+ setSelectedEdge(edgeId);
115
+ }, []);
116
+ const deleteEdge = useCallback((edgeId) => {
117
+ setEdges((prev) => prev.filter((e) => e.id !== edgeId));
118
+ setSelectedEdge(null);
119
+ }, []);
120
+ const deleteSelectedEdge = useCallback(() => {
121
+ if (selectedEdge)
122
+ deleteEdge(selectedEdge);
123
+ }, [selectedEdge, deleteEdge]);
124
+ const clearSelection = useCallback(() => {
125
+ setSelectedEdge(null);
126
+ }, []);
127
+ // Tambahkan di dalam useConnectionGraph hook
128
+ useEffect(() => {
129
+ const handleKeyDown = (e) => {
130
+ if (e.key === "Delete" || e.key === "Backspace") {
131
+ // Pastikan user tidak sedang ketik di input/textarea
132
+ const tag = e.target.tagName.toLowerCase();
133
+ if (tag === "input" || tag === "textarea")
134
+ return;
135
+ if (selectedEdge) {
136
+ deleteSelectedEdge();
137
+ }
138
+ }
139
+ if (e.key === "Escape") {
140
+ cancelConnect();
141
+ clearSelection();
142
+ }
143
+ };
144
+ window.addEventListener("keydown", handleKeyDown);
145
+ return () => window.removeEventListener("keydown", handleKeyDown);
146
+ }, [selectedEdge, deleteSelectedEdge, cancelConnect, clearSelection]);
147
+ useEffect(() => {
148
+ const sourceToTargets = new Map();
149
+ edges.forEach((e) => {
150
+ if (!sourceToTargets.has(e.from))
151
+ sourceToTargets.set(e.from, []);
152
+ sourceToTargets.get(e.from).push(e.to);
153
+ });
154
+ const result = components === null || components === void 0 ? void 0 : components.map((table) => {
155
+ var _a, _b;
156
+ return (Object.assign(Object.assign({}, table), { [keyNode]: (_b = sourceToTargets.get(String((_a = table === null || table === void 0 ? void 0 : table[mappingKey]) === null || _a === void 0 ? void 0 : _a.id))) !== null && _b !== void 0 ? _b : [] }));
157
+ });
158
+ onEdgesChange === null || onEdgesChange === void 0 ? void 0 : onEdgesChange(edges, result);
159
+ }, [edges]);
160
+ return {
161
+ // state
162
+ edges,
163
+ setEdges,
164
+ selectedEdge,
165
+ connecting,
166
+ mousePos,
167
+ // actions
168
+ startConnect,
169
+ endConnect,
170
+ cancelConnect,
171
+ startDragWaypoint,
172
+ insertWaypoint,
173
+ removeWaypoint,
174
+ selectEdge,
175
+ deleteEdge,
176
+ deleteSelectedEdge,
177
+ clearSelection,
178
+ // event handlers untuk SVG
179
+ onMouseMove,
180
+ onMouseUp,
181
+ };
182
+ };
@@ -0,0 +1,74 @@
1
+ import { ComponentProps, TableMatchKey } from ".";
2
+ export declare const GRID_SIZE = 20;
3
+ export declare const NODE_WIDTH = 120;
4
+ export declare const NODE_HEIGHT = 50;
5
+ export declare const snap: (v: number) => number;
6
+ export type EdgeType = {
7
+ id: string;
8
+ from: string;
9
+ to: string;
10
+ waypoints: {
11
+ x: number;
12
+ y: number;
13
+ }[];
14
+ };
15
+ export type NodeType = {
16
+ id: string;
17
+ x: number;
18
+ y: number;
19
+ width?: number;
20
+ height?: number;
21
+ rotation?: number;
22
+ };
23
+ /**
24
+ * Rotate point (px, py) sekitar origin (0,0) sebesar deg derajat.
25
+ */
26
+ export declare const rotatePoint: (px: number, py: number, deg: number) => {
27
+ x: number;
28
+ y: number;
29
+ };
30
+ /**
31
+ * Hitung titik edge pada sisi rect node yang menghadap ke arah `to`,
32
+ * dengan mempertimbangkan rotation node.
33
+ */
34
+ export declare const getRectEdge: (from: {
35
+ x: number;
36
+ y: number;
37
+ width?: number;
38
+ height?: number;
39
+ rotation?: number;
40
+ }, to: {
41
+ x: number;
42
+ y: number;
43
+ }, width: number, height: number) => {
44
+ x: number;
45
+ y: number;
46
+ };
47
+ /**
48
+ * Build SVG path melalui array of points.
49
+ * Selalu berakhir tepat di titik tujuan.
50
+ *
51
+ * Routing per segmen:
52
+ * - Sejajar vertikal (dx <= threshold) → V
53
+ * - Sejajar horizontal (dy <= threshold) → H
54
+ * - Dominan vertikal (dy > dx) → H midX V curr.y H curr.x
55
+ * - Dominan horizontal (dx >= dy) → H midX V curr.y H curr.x
56
+ *
57
+ * Semua routing diakhiri dengan koordinat EKSAK curr.x, curr.y
58
+ * sehingga path selalu sampai ke titik tujuan.
59
+ */
60
+ export declare const buildPath: (points: {
61
+ x: number;
62
+ y: number;
63
+ }[]) => string;
64
+ export declare const defaultWaypoints: (start: {
65
+ x: number;
66
+ y: number;
67
+ }, end: {
68
+ x: number;
69
+ y: number;
70
+ }) => {
71
+ x: number;
72
+ y: number;
73
+ }[];
74
+ export declare const renderElements: (elementEditor: ComponentProps[], mappingKey?: string, tableMatchKey?: TableMatchKey[], statusKey?: string) => ComponentProps[];
@@ -0,0 +1,106 @@
1
+ // utils.ts
2
+ export const GRID_SIZE = 20;
3
+ export const NODE_WIDTH = 120;
4
+ export const NODE_HEIGHT = 50;
5
+ export const snap = (v) => Math.round(v / GRID_SIZE) * GRID_SIZE;
6
+ /**
7
+ * Rotate point (px, py) sekitar origin (0,0) sebesar deg derajat.
8
+ */
9
+ export const rotatePoint = (px, py, deg) => {
10
+ const rad = (deg * Math.PI) / 180;
11
+ return {
12
+ x: px * Math.cos(rad) - py * Math.sin(rad),
13
+ y: px * Math.sin(rad) + py * Math.cos(rad),
14
+ };
15
+ };
16
+ /**
17
+ * Hitung titik edge pada sisi rect node yang menghadap ke arah `to`,
18
+ * dengan mempertimbangkan rotation node.
19
+ */
20
+ export const getRectEdge = (from, to, width, height) => {
21
+ var _a, _b, _c;
22
+ const w = (_a = from.width) !== null && _a !== void 0 ? _a : NODE_WIDTH;
23
+ const h = (_b = from.height) !== null && _b !== void 0 ? _b : NODE_HEIGHT;
24
+ const rotation = (_c = from.rotation) !== null && _c !== void 0 ? _c : 0;
25
+ const dx = to.x - from.x;
26
+ const dy = to.y - from.y;
27
+ if (dx === 0 && dy === 0)
28
+ return { x: from.x, y: from.y };
29
+ const angle = Math.atan2(dy, dx);
30
+ // Un-rotate arah ke local space node
31
+ const rad = -(rotation * Math.PI) / 180;
32
+ const localAngle = angle + rad;
33
+ const absCos = Math.abs(Math.cos(localAngle));
34
+ const absSin = Math.abs(Math.sin(localAngle));
35
+ const offset = (h / 2) * absCos <= (w / 2) * absSin
36
+ ? (h / 2) / absSin
37
+ : (w / 2) / absCos;
38
+ const localEdgeX = Math.cos(localAngle) * offset;
39
+ const localEdgeY = Math.sin(localAngle) * offset;
40
+ // Re-rotate balik ke world space
41
+ const worldEdge = rotatePoint(localEdgeX, localEdgeY, rotation);
42
+ return {
43
+ x: snap(from.x + worldEdge.x),
44
+ y: snap(from.y + worldEdge.y),
45
+ };
46
+ };
47
+ /**
48
+ * Build SVG path melalui array of points.
49
+ * Selalu berakhir tepat di titik tujuan.
50
+ *
51
+ * Routing per segmen:
52
+ * - Sejajar vertikal (dx <= threshold) → V
53
+ * - Sejajar horizontal (dy <= threshold) → H
54
+ * - Dominan vertikal (dy > dx) → H midX V curr.y H curr.x
55
+ * - Dominan horizontal (dx >= dy) → H midX V curr.y H curr.x
56
+ *
57
+ * Semua routing diakhiri dengan koordinat EKSAK curr.x, curr.y
58
+ * sehingga path selalu sampai ke titik tujuan.
59
+ */
60
+ export const buildPath = (points) => {
61
+ if (points.length < 2)
62
+ return "";
63
+ let d = `M ${points[0].x} ${points[0].y}`;
64
+ const threshold = 2; // presisi tinggi agar tidak salah deteksi lurus
65
+ for (let i = 1; i < points.length; i++) {
66
+ const prev = points[i - 1];
67
+ const curr = points[i];
68
+ const dx = Math.abs(curr.x - prev.x);
69
+ const dy = Math.abs(curr.y - prev.y);
70
+ if (dx <= threshold) {
71
+ // Lurus vertikal — snap ke curr.x agar tidak offset kecil
72
+ d += ` V ${curr.y}`;
73
+ }
74
+ else if (dy <= threshold) {
75
+ // Lurus horizontal
76
+ d += ` H ${curr.x}`;
77
+ }
78
+ else {
79
+ // Orthogonal — selalu akhiri dengan posisi EKSAK curr.x curr.y
80
+ const midX = snap((prev.x + curr.x) / 2);
81
+ d += ` H ${midX} V ${curr.y} H ${curr.x}`;
82
+ }
83
+ }
84
+ return d;
85
+ };
86
+ export const defaultWaypoints = (start, end) => {
87
+ return [
88
+ {
89
+ x: snap((start.x + end.x) / 2),
90
+ y: snap((start.y + end.y) / 2),
91
+ },
92
+ ];
93
+ };
94
+ export const renderElements = (elementEditor, mappingKey, tableMatchKey, statusKey) => {
95
+ return elementEditor.map((editorItem) => {
96
+ const isUsingMapping = mappingKey &&
97
+ typeof editorItem[mappingKey] === "object" &&
98
+ editorItem[mappingKey] !== null;
99
+ let finalProps = isUsingMapping ? editorItem[mappingKey] : editorItem;
100
+ if (tableMatchKey) {
101
+ const tableMatch = tableMatchKey.find((item) => item.key == (editorItem === null || editorItem === void 0 ? void 0 : editorItem[statusKey]));
102
+ finalProps = Object.assign(Object.assign(Object.assign({}, finalProps), tableMatch === null || tableMatch === void 0 ? void 0 : tableMatch.properties), { className: tableMatch === null || tableMatch === void 0 ? void 0 : tableMatch.className });
103
+ }
104
+ return finalProps;
105
+ });
106
+ };
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ type Props = {
3
+ cx: number;
4
+ cy: number;
5
+ width: number;
6
+ height: number;
7
+ nodeId: string;
8
+ rotation?: number;
9
+ isConnecting: boolean;
10
+ isDraggingAnchor: boolean;
11
+ onStartConnect: (nodeId: string, clickPos: {
12
+ x: number;
13
+ y: number;
14
+ }) => void;
15
+ onEndConnect: (nodeId: string, clickPos: {
16
+ x: number;
17
+ y: number;
18
+ }) => void;
19
+ isConnectEdge?: boolean;
20
+ onSelectNode: (nodeId: string) => void;
21
+ isSelectNode?: boolean;
22
+ onSelectEdge?: (edgeId: string) => void;
23
+ edges: {
24
+ id: string;
25
+ from: string;
26
+ to: string;
27
+ }[];
28
+ };
29
+ export declare const ConnectHandle: React.FC<Props>;
30
+ export {};
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // connect-handle.tsx
3
+ import { useState } from "react";
4
+ // 4 titik per sisi dalam local space
5
+ const getLocalAnchors = (w, h) => {
6
+ const hw = w / 2;
7
+ const hh = h / 2;
8
+ return [
9
+ { x: 0, y: -hh }, // top center
10
+ { x: hw, y: 0 }, // right center
11
+ { x: 0, y: hh }, // bottom center
12
+ { x: -hw, y: 0 }, // left center
13
+ ];
14
+ };
15
+ const nearestAnchor = (lx, ly, anchors) => {
16
+ let best = anchors[0];
17
+ let bestD = Infinity;
18
+ for (const a of anchors) {
19
+ const d = Math.hypot(a.x - lx, a.y - ly);
20
+ if (d < bestD) {
21
+ bestD = d;
22
+ best = a;
23
+ }
24
+ }
25
+ return best;
26
+ };
27
+ export const ConnectHandle = ({ cx, cy, width, height, nodeId, rotation = 0, isConnecting, isDraggingAnchor, onStartConnect, onEndConnect, isConnectEdge, onSelectNode, isSelectNode, onSelectEdge, edges }) => {
28
+ const hw = width / 2;
29
+ const hh = height / 2;
30
+ const [hovered, setHovered] = useState(false);
31
+ const isActive = isConnecting || isDraggingAnchor;
32
+ const isActiveSelectEdge = isSelectNode;
33
+ const anchors = getLocalAnchors(width, height);
34
+ const toLocalPos = (e) => {
35
+ var _a;
36
+ const svg = e.currentTarget.closest("svg");
37
+ const pt = svg.createSVGPoint();
38
+ pt.x = e.clientX;
39
+ pt.y = e.clientY;
40
+ const world = pt.matrixTransform((_a = svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse());
41
+ const dx = world.x - cx;
42
+ const dy = world.y - cy;
43
+ const rad = -(rotation * Math.PI) / 180;
44
+ return {
45
+ x: dx * Math.cos(rad) - dy * Math.sin(rad),
46
+ y: dx * Math.sin(rad) + dy * Math.cos(rad),
47
+ };
48
+ };
49
+ const toWorld = (lx, ly) => {
50
+ const rad = (rotation * Math.PI) / 180;
51
+ return {
52
+ x: cx + lx * Math.cos(rad) - ly * Math.sin(rad),
53
+ y: cy + lx * Math.sin(rad) + ly * Math.cos(rad),
54
+ };
55
+ };
56
+ const handlePointerDown = (e) => {
57
+ e.stopPropagation();
58
+ const local = toLocalPos(e);
59
+ const snapped = nearestAnchor(local.x, local.y, anchors);
60
+ const worldPos = toWorld(snapped.x, snapped.y);
61
+ console.log({ isActiveSelectEdge });
62
+ if (isActiveSelectEdge) {
63
+ const findEdge = edges.find(e => e.from === nodeId || e.to === nodeId);
64
+ console.log({ findEdge });
65
+ onSelectEdge(findEdge === null || findEdge === void 0 ? void 0 : findEdge.id);
66
+ onSelectNode(nodeId);
67
+ return;
68
+ }
69
+ else if (isActive) {
70
+ onEndConnect(nodeId, worldPos);
71
+ return;
72
+ }
73
+ else {
74
+ onStartConnect(nodeId, worldPos);
75
+ }
76
+ };
77
+ return (_jsxs("g", { transform: `translate(${cx}, ${cy}) rotate(${rotation})`, children: [_jsx("rect", { x: -hw - 4, y: -hh - 4, width: width + 8, height: height + 8, fill: "transparent", stroke: isActive ? "#a78bfa" : hovered ? "#38bdf8" : "transparent", strokeWidth: isActive ? 2 : 1.5, strokeDasharray: isActive ? "4 2" : "none", rx: 4, style: { pointerEvents: "none" } }), _jsx("rect", { x: -hw - 4, y: -hh - 4, width: width + 8, height: height + 8, fill: "transparent", style: {
78
+ cursor: isConnectEdge ? "crosshair" : "pointer",
79
+ pointerEvents: isConnectEdge ? "all" : "none",
80
+ }, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), onPointerDown: (e) => {
81
+ console.log({ isConnectEdge, isSelectNode });
82
+ // console.log({ isConnectEdge, isSelectNode })
83
+ // if (!isConnectEdge ) return;
84
+ // if (!isSelectNode) return;
85
+ handlePointerDown(e);
86
+ } }), ((hovered && isConnectEdge) || isActive) &&
87
+ anchors.map((a, i) => (_jsx("circle", { cx: a.x, cy: a.y, r: 3, fill: isActive ? "#a78bfa" : "#38bdf8", opacity: 0.5, style: { pointerEvents: "none" } }, i)))] }));
88
+ };
@@ -0,0 +1,96 @@
1
+ // connect-handle.tsx
2
+ import React, { useState } from "react";
3
+ // 4 titik per sisi dalam local space
4
+ const getLocalAnchors = (w, h) => {
5
+ const hw = w / 2;
6
+ const hh = h / 2;
7
+ return [
8
+ { x: 0, y: -hh }, // top center
9
+ { x: hw, y: 0 }, // right center
10
+ { x: 0, y: hh }, // bottom center
11
+ { x: -hw, y: 0 }, // left center
12
+ ];
13
+ };
14
+ const nearestAnchor = (lx, ly, anchors) => {
15
+ let best = anchors[0];
16
+ let bestD = Infinity;
17
+ for (const a of anchors) {
18
+ const d = Math.hypot(a.x - lx, a.y - ly);
19
+ if (d < bestD) {
20
+ bestD = d;
21
+ best = a;
22
+ }
23
+ }
24
+ return best;
25
+ };
26
+ export const ConnectHandle = ({ cx, cy, width, height, nodeId, rotation = 0, isConnecting, isDraggingAnchor, onStartConnect, onEndConnect, isConnectEdge, onSelectNode, isSelectNode, onSelectEdge, edges }) => {
27
+ const hw = width / 2;
28
+ const hh = height / 2;
29
+ const [hovered, setHovered] = useState(false);
30
+ const isActive = isConnecting || isDraggingAnchor;
31
+ const isActiveSelectEdge = isSelectNode;
32
+ const anchors = getLocalAnchors(width, height);
33
+ const toLocalPos = (e) => {
34
+ var _a;
35
+ const svg = e.currentTarget.closest("svg");
36
+ const pt = svg.createSVGPoint();
37
+ pt.x = e.clientX;
38
+ pt.y = e.clientY;
39
+ const world = pt.matrixTransform((_a = svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse());
40
+ const dx = world.x - cx;
41
+ const dy = world.y - cy;
42
+ const rad = -(rotation * Math.PI) / 180;
43
+ return {
44
+ x: dx * Math.cos(rad) - dy * Math.sin(rad),
45
+ y: dx * Math.sin(rad) + dy * Math.cos(rad),
46
+ };
47
+ };
48
+ const toWorld = (lx, ly) => {
49
+ const rad = (rotation * Math.PI) / 180;
50
+ return {
51
+ x: cx + lx * Math.cos(rad) - ly * Math.sin(rad),
52
+ y: cy + lx * Math.sin(rad) + ly * Math.cos(rad),
53
+ };
54
+ };
55
+ const handlePointerDown = (e) => {
56
+ e.stopPropagation();
57
+ const local = toLocalPos(e);
58
+ const snapped = nearestAnchor(local.x, local.y, anchors);
59
+ const worldPos = toWorld(snapped.x, snapped.y);
60
+ console.log({ isActiveSelectEdge });
61
+ if (isActiveSelectEdge) {
62
+ const findEdge = edges.find(e => e.from === nodeId || e.to === nodeId);
63
+ console.log({ findEdge });
64
+ onSelectEdge(findEdge === null || findEdge === void 0 ? void 0 : findEdge.id);
65
+ onSelectNode(nodeId);
66
+ return;
67
+ }
68
+ else if (isActive) {
69
+ onEndConnect(nodeId, worldPos);
70
+ return;
71
+ }
72
+ else {
73
+ onStartConnect(nodeId, worldPos);
74
+ }
75
+ };
76
+ return (<g transform={`translate(${cx}, ${cy}) rotate(${rotation})`}>
77
+ {/* Border visual */}
78
+ <rect x={-hw - 4} y={-hh - 4} width={width + 8} height={height + 8} fill="transparent" stroke={isActive ? "#a78bfa" : hovered ? "#38bdf8" : "transparent"} strokeWidth={isActive ? 2 : 1.5} strokeDasharray={isActive ? "4 2" : "none"} rx={4} style={{ pointerEvents: "none" }}/>
79
+
80
+ {/* Hit area */}
81
+ <rect x={-hw - 4} y={-hh - 4} width={width + 8} height={height + 8} fill="transparent" style={{
82
+ cursor: isConnectEdge ? "crosshair" : "pointer",
83
+ pointerEvents: isConnectEdge ? "all" : "none",
84
+ }} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} onPointerDown={(e) => {
85
+ console.log({ isConnectEdge, isSelectNode });
86
+ // console.log({ isConnectEdge, isSelectNode })
87
+ // if (!isConnectEdge ) return;
88
+ // if (!isSelectNode) return;
89
+ handlePointerDown(e);
90
+ }}/>
91
+
92
+ {/* Anchor dots — muncul saat hover atau active */}
93
+ {((hovered && isConnectEdge) || isActive) &&
94
+ anchors.map((a, i) => (<circle key={i} cx={a.x} cy={a.y} r={3} fill={isActive ? "#a78bfa" : "#38bdf8"} opacity={0.5} style={{ pointerEvents: "none" }}/>))}
95
+ </g>);
96
+ };
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { EdgeType, NodeType } from "./utils";
3
+ export type DraggingAnchor = {
4
+ edgeId: string;
5
+ side: "from" | "to";
6
+ };
7
+ type Props = {
8
+ edges: EdgeType[];
9
+ selectedEdge: string | null;
10
+ connecting: {
11
+ fromId: string;
12
+ fromPos: {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ } | null;
17
+ draggingAnchor: DraggingAnchor | null;
18
+ mousePos: {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ getNodeById: (id: string) => NodeType | null;
23
+ onSelectEdge: (edgeId: string) => void;
24
+ onDeleteEdge: (edgeId: string) => void;
25
+ onStartDragWaypoint: (e: React.MouseEvent, edgeId: string, index: number) => void;
26
+ onInsertWaypoint: (e: React.MouseEvent, edgeId: string, insertIndex: number, x: number, y: number) => void;
27
+ onRemoveWaypoint: (e: React.MouseEvent, edgeId: string, index: number) => void;
28
+ onStartDragAnchor: (e: React.MouseEvent, edgeId: string, side: "from" | "to") => void;
29
+ isConnectEdge: boolean;
30
+ isSelectNode: boolean;
31
+ selectedNodeId: string | null;
32
+ };
33
+ export declare const ConnectionLayer: React.FC<Props>;
34
+ export {};