@canmingir/link 1.2.8 → 1.2.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.
Files changed (35) hide show
  1. package/.github/workflows/publish.yml +64 -7
  2. package/package.json +1 -1
  3. package/src/lib/Flow/connectors/DynamicConnector.jsx +247 -0
  4. package/src/lib/Flow/core/Flow.jsx +79 -0
  5. package/src/lib/Flow/core/FlowNode.jsx +68 -0
  6. package/src/lib/Flow/core/FlowViewport.jsx +259 -0
  7. package/src/lib/Flow/graph/FloatingGraph.jsx +44 -0
  8. package/src/lib/Flow/hooks/useGraphOperations.js +362 -0
  9. package/src/lib/Flow/hooks/useNodeStyle.js +56 -0
  10. package/src/lib/Flow/index.js +1 -1
  11. package/src/lib/Flow/layouts/InfoNode.jsx +115 -56
  12. package/src/lib/Flow/nodes/DefaultCard.jsx +107 -0
  13. package/src/lib/Flow/nodes/DraggableNode.jsx +162 -0
  14. package/src/lib/Flow/nodes/FlowNodeView.jsx +214 -0
  15. package/src/lib/Flow/selection/SelectionContext.jsx +259 -0
  16. package/src/lib/Flow/selection/SelectionOverlay.jsx +31 -0
  17. package/src/lib/Flow/styles.js +59 -19
  18. package/src/lib/Flow/utils/flowUtils.js +268 -0
  19. package/src/lib/index.js +1 -1
  20. package/.idea/codeStyles/Project.xml +0 -84
  21. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  22. package/.idea/copilot.data.migration.agent.xml +0 -6
  23. package/.idea/copilot.data.migration.ask.xml +0 -6
  24. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  25. package/.idea/copilot.data.migration.edit.xml +0 -6
  26. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  27. package/.idea/misc.xml +0 -5
  28. package/.idea/modules.xml +0 -8
  29. package/.idea/platform.iml +0 -9
  30. package/.idea/vcs.xml +0 -6
  31. package/src/lib/Flow/DraggableNode.jsx +0 -62
  32. package/src/lib/Flow/DynamicConnector.jsx +0 -176
  33. package/src/lib/Flow/Flow.jsx +0 -40
  34. package/src/lib/Flow/FlowNode.jsx +0 -371
  35. package/src/lib/Flow/flowUtils.js +0 -111
@@ -1 +1 @@
1
- export { default as Flow } from "./Flow";
1
+ export { default as Flow } from "./core/Flow";
@@ -9,12 +9,12 @@ const MainContainer = styled("div", {
9
9
  shouldForwardProp: (prop) => prop !== "$style",
10
10
  })(({ theme, $style = {} }) => {
11
11
  const {
12
- minWidth = 120,
13
- minHeight = 120,
14
- maxWidth = 160,
15
- maxHeight = 160,
16
- borderRadius = 16,
17
- borderColor = "rgba(255, 255, 255, 0.2)",
12
+ minWidth = 280,
13
+ minHeight = 220,
14
+ maxWidth = 320,
15
+ maxHeight = "auto",
16
+ borderRadius = 20,
17
+ borderColor = "rgba(255, 255, 255, 0.08)",
18
18
  bgFrom,
19
19
  bgTo,
20
20
  } = $style;
@@ -28,10 +28,11 @@ const MainContainer = styled("div", {
28
28
  return {
29
29
  display: "flex",
30
30
  flexDirection: "column",
31
- alignItems: "center",
31
+ alignItems: "flex-start",
32
+ justifyContent: "flex-start",
32
33
  color: "#ffffff",
33
34
  borderRadius,
34
- padding: "16px 20px",
35
+ padding: "24px",
35
36
  border: `1px solid ${borderColor}`,
36
37
  backdropFilter: "blur(10px)",
37
38
  transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
@@ -56,26 +57,38 @@ const MainContainer = styled("div", {
56
57
  };
57
58
  });
58
59
 
60
+ const HeaderRow = styled("div")({
61
+ display: "flex",
62
+ flexDirection: "row",
63
+ alignItems: "center",
64
+ width: "100%",
65
+ gap: "16px",
66
+ marginBottom: "16px",
67
+ minHeight: "44px",
68
+ });
69
+
59
70
  const IconContainer = styled("div", {
60
71
  shouldForwardProp: (prop) => prop !== "$style",
61
72
  })(({ $style = {} }) => {
62
73
  const {
63
74
  bg = "rgba(255, 255, 255, 0.1)",
64
- hoverBg = "rgba(255, 255, 255, 0.15)",
65
- borderRadius = 12,
66
- padding = 8,
67
- borderColor = "rgba(255, 255, 255, 0.3)",
68
- marginBottom = 12,
75
+ hoverBg = "rgba(255, 255, 255, 0.2)",
76
+ borderRadius = 14,
77
+ padding = 10,
78
+ borderColor = "rgba(255, 255, 255, 0.1)",
69
79
  } = $style;
70
80
 
71
81
  return {
72
82
  backgroundColor: bg,
73
83
  borderRadius,
74
84
  padding,
75
- marginBottom,
76
85
  transition: "all 0.4s ease",
77
86
  backdropFilter: "blur(5px)",
78
87
  border: `1px solid ${borderColor}`,
88
+ flexShrink: 0,
89
+ display: "flex",
90
+ alignItems: "center",
91
+ justifyContent: "center",
79
92
 
80
93
  '[data-hovered="true"] &': {
81
94
  backgroundColor: hoverBg,
@@ -88,44 +101,90 @@ const LabelText = styled("div", {
88
101
  })(({ $style = {} }) => {
89
102
  const {
90
103
  color = "#ffffff",
91
- fontWeight = 600,
92
- fontSize = 13,
93
- letterSpacing = 0.5,
104
+ fontWeight = 700,
105
+ fontSize = 15,
106
+ letterSpacing = 0.3,
94
107
  } = $style;
95
108
 
96
109
  return {
97
110
  fontWeight,
98
111
  fontSize,
99
112
  letterSpacing: `${letterSpacing}px`,
100
- textAlign: "center",
113
+ textAlign: "left",
101
114
  transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
102
- textShadow: "none",
103
- lineHeight: "1.4",
115
+ lineHeight: "1.2",
104
116
  opacity: 0,
105
- transform: "translateY(10px)",
117
+ transform: "translateX(10px)",
106
118
  color,
119
+ flex: 1,
107
120
 
108
121
  '&[data-animated="true"]': {
109
122
  opacity: 1,
110
- transform: "translateY(0)",
123
+ transform: "translateX(0)",
111
124
  },
112
125
  };
113
126
  });
114
127
 
128
+ const DescriptionText = styled("div")(({ theme }) => ({
129
+ fontSize: "12px",
130
+ fontWeight: 400,
131
+ color: alpha("#fff", 0.6),
132
+ textAlign: "left",
133
+ lineHeight: "1.5",
134
+ width: "100%",
135
+ flex: 1,
136
+ opacity: 0,
137
+ transform: "translateY(10px)",
138
+ transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s",
139
+ marginBottom: "16px",
140
+ display: "-webkit-box",
141
+ WebkitLineClamp: 4,
142
+ WebkitBoxOrient: "vertical",
143
+ overflow: "hidden",
144
+
145
+ '&[data-animated="true"]': {
146
+ opacity: 1,
147
+ transform: "translateY(0)",
148
+ },
149
+ }));
150
+
151
+ const ActionText = styled("div")(({ theme }) => ({
152
+ fontSize: "10px",
153
+ fontWeight: 700,
154
+ textTransform: "uppercase",
155
+ letterSpacing: "1px",
156
+ color: alpha("#fff", 0.4),
157
+ marginTop: "auto",
158
+ textAlign: "left",
159
+ width: "100%",
160
+ fontFamily: "monospace",
161
+ opacity: 0,
162
+ transform: "translateY(5px)",
163
+ transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s",
164
+
165
+ '&[data-animated="true"]': {
166
+ opacity: 1,
167
+ transform: "translateY(0)",
168
+ },
169
+ }));
170
+
115
171
  const getIcon = (node) => {
116
172
  if (node.icon) return node.icon;
117
173
  if (node.type === "CONDITION" || node.type === "decision")
118
174
  return "mdi:help-circle-outline";
119
175
  if (node.type === "NORMAL") return "mdi:checkbox-blank-circle-outline";
120
- return "mdi:cube-outline";
176
+ return "mdi:map-marker";
121
177
  };
122
178
 
123
- const InfoNode = ({ node, nodeStyle = {} }) => {
179
+ const InfoNode = ({ node, nodeStyle = {}, children }) => {
124
180
  const [animated, setAnimated] = useState(false);
125
181
  const [isHovered, setIsHovered] = useState(false);
126
182
 
127
- const label =
128
- node.label || node.title || node.name || node.id || "Responsibility";
183
+ const label = node.label || node.title || node.name || node.id || "Get Data";
184
+
185
+ const action = node.action || "SYSTEM:DEFAULT_ACTION";
186
+ const description =
187
+ node.description || "Description of the operation goes here.";
129
188
 
130
189
  useEffect(() => {
131
190
  const timer = setTimeout(() => setAnimated(true), ANIMATION_DELAY_MS);
@@ -133,14 +192,9 @@ const InfoNode = ({ node, nodeStyle = {} }) => {
133
192
  }, []);
134
193
 
135
194
  const layoutStyle = {
136
- minWidth: nodeStyle.cardWidth || nodeStyle.minWidth || 120,
137
- minHeight: nodeStyle.minHeight || 120,
138
- maxWidth: nodeStyle.maxWidth || 160,
139
- maxHeight: nodeStyle.maxHeight || 160,
140
- borderRadius: typeof nodeStyle.shape === "number" ? nodeStyle.shape : 16,
141
- borderColor: nodeStyle.borderColor || "rgba(255, 255, 255, 0.2)",
142
- bgFrom: nodeStyle.bgFrom,
143
- bgTo: nodeStyle.bgTo,
195
+ ...nodeStyle,
196
+ minWidth: nodeStyle.cardWidth || nodeStyle.minWidth || 280,
197
+ minHeight: nodeStyle.minHeight || 220,
144
198
  };
145
199
 
146
200
  const iconContainerStyle = {
@@ -149,7 +203,6 @@ const InfoNode = ({ node, nodeStyle = {} }) => {
149
203
  iconBorderRadius: nodeStyle.iconRadius,
150
204
  iconPadding: nodeStyle.iconPadding,
151
205
  iconBorderColor: nodeStyle.iconBorderColor,
152
- iconMarginBottom: nodeStyle.iconMarginBottom,
153
206
  };
154
207
 
155
208
  const labelStyle = {
@@ -168,27 +221,33 @@ const InfoNode = ({ node, nodeStyle = {} }) => {
168
221
  onMouseEnter={() => setIsHovered(true)}
169
222
  onMouseLeave={() => setIsHovered(false)}
170
223
  >
171
- <IconContainer $style={iconContainerStyle}>
172
- <Iconify
173
- icon={icon}
174
- width={28}
175
- height={28}
176
- sx={{
177
- transition: "all 0.6s cubic-bezier(0.4, 0, 0.2, 1)",
178
- transform: animated
179
- ? isHovered
180
- ? "rotate(360deg) scale(1.1)"
181
- : "rotate(0deg) scale(1)"
182
- : "rotate(-90deg) scale(0.8)",
183
- filter: "none",
184
- color: nodeStyle.iconColor || "#ffffff",
185
- }}
186
- />
187
- </IconContainer>
188
-
189
- <LabelText data-animated={animated} $style={labelStyle}>
190
- {label}
191
- </LabelText>
224
+ <HeaderRow>
225
+ <IconContainer $style={iconContainerStyle}>
226
+ <Iconify
227
+ icon={icon}
228
+ width={24}
229
+ height={24}
230
+ sx={{
231
+ filter: "none",
232
+ color: nodeStyle.iconColor || "#ffffff",
233
+ }}
234
+ />
235
+ </IconContainer>
236
+
237
+ <LabelText data-animated={animated} $style={labelStyle}>
238
+ {label}
239
+ </LabelText>
240
+ </HeaderRow>
241
+
242
+ {description && (
243
+ <DescriptionText data-animated={animated}>
244
+ {description}
245
+ </DescriptionText>
246
+ )}
247
+
248
+ {action && <ActionText data-animated={animated}>{action}</ActionText>}
249
+
250
+ {children}
192
251
  </MainContainer>
193
252
  );
194
253
  };
@@ -0,0 +1,107 @@
1
+ import React from "react";
2
+
3
+ import { Box, Card, Typography } from "@mui/material";
4
+
5
+ const DefaultNodeCard = ({
6
+ title,
7
+ subtitle,
8
+ metaEntries,
9
+ nodeStyle,
10
+ baseStyle,
11
+ variant,
12
+ borderWidth,
13
+ borderColor,
14
+ cardWidth,
15
+ shape,
16
+ shadowLevel,
17
+ minHeight,
18
+ nodeSx = {},
19
+ }) => {
20
+ const effectiveWidth = cardWidth || 220;
21
+ const effectiveBorderWidth = borderWidth || 1;
22
+ const effectiveRadius =
23
+ typeof shape === "number" ? shape : baseStyle.shape || 4;
24
+
25
+ const effectiveShadow =
26
+ typeof shadowLevel === "number" ? shadowLevel : variant === "card" ? 2 : 1;
27
+
28
+ const effectiveMinHeight = minHeight || 80;
29
+
30
+ return (
31
+ <Card
32
+ sx={{
33
+ p: 2,
34
+ width: effectiveWidth,
35
+ minHeight: effectiveMinHeight,
36
+ display: "flex",
37
+ flexDirection: "row",
38
+ alignItems: "center",
39
+ justifyContent: "center",
40
+ gap: 1,
41
+ position: "relative",
42
+ borderRadius: effectiveRadius,
43
+ bgcolor: nodeStyle.bg || "background.paper",
44
+ border: `${effectiveBorderWidth}px solid ${
45
+ borderColor || "transparent"
46
+ }`,
47
+ boxShadow: effectiveShadow,
48
+ transition: "background-color 0.3s ease, box-shadow 0.3s ease",
49
+ "&:hover": {
50
+ bgcolor: nodeStyle.hoverBg || nodeStyle.bg || "grey.100",
51
+ boxShadow: effectiveShadow + 1,
52
+ cursor: "pointer",
53
+ },
54
+ ...nodeSx,
55
+ }}
56
+ >
57
+ <Box sx={{ textAlign: "left", width: "100%" }}>
58
+ <Typography
59
+ variant="subtitle2"
60
+ sx={{
61
+ textAlign: "center",
62
+ fontWeight: 600,
63
+ fontSize: 13,
64
+ mb: subtitle ? 0.5 : 0,
65
+ }}
66
+ >
67
+ {title}
68
+ </Typography>
69
+
70
+ {subtitle && (
71
+ <Typography
72
+ variant="body2"
73
+ color="text.secondary"
74
+ sx={{
75
+ textAlign: "center",
76
+ fontSize: 11,
77
+ mb: metaEntries.length ? 0.5 : 0,
78
+ }}
79
+ >
80
+ {subtitle}
81
+ </Typography>
82
+ )}
83
+
84
+ {metaEntries.length > 0 && (
85
+ <Box sx={{ mt: 0.25 }}>
86
+ {metaEntries.map(([key, value]) => (
87
+ <Typography
88
+ key={key}
89
+ variant="caption"
90
+ color="text.secondary"
91
+ sx={{
92
+ textAlign: "center",
93
+ display: "block",
94
+ fontSize: 10,
95
+ }}
96
+ >
97
+ {key}: {String(value)}
98
+ </Typography>
99
+ ))}
100
+ </Box>
101
+ )}
102
+ </Box>
103
+ </Card>
104
+ );
105
+ };
106
+
107
+ export default DefaultNodeCard;
@@ -0,0 +1,162 @@
1
+ import { Box } from "@mui/material";
2
+ import { useSelection } from "../selection/SelectionContext";
3
+
4
+ import React, { useCallback, useEffect, useRef, useState } from "react";
5
+
6
+ const DraggableNode = ({
7
+ children,
8
+ registerRef,
9
+ onDrag,
10
+ nodeId,
11
+ selectionColor = "#64748b",
12
+ initialPosition,
13
+ onConnect,
14
+ }) => {
15
+ const [offset, setOffset] = useState(() =>
16
+ initialPosition ? { ...initialPosition } : { x: 0, y: 0 }
17
+ );
18
+
19
+ useEffect(() => {
20
+ if (initialPosition) {
21
+ setOffset({ ...initialPosition });
22
+ } else {
23
+ setOffset({ x: 0, y: 0 });
24
+ }
25
+ }, [initialPosition]);
26
+
27
+ const localRef = useRef(null);
28
+ const lastDeltaRef = useRef({ x: 0, y: 0 });
29
+ const onDragRef = useRef(onDrag);
30
+
31
+ const {
32
+ isSelected,
33
+ selectNode,
34
+ toggleSelection,
35
+ clearSelection,
36
+ registerNodeHandlers,
37
+ moveSelectedNodes,
38
+ selectedIds,
39
+ } = useSelection();
40
+
41
+ const selected = isSelected(nodeId);
42
+
43
+ useEffect(() => {
44
+ onDragRef.current = onDrag;
45
+ }, [onDrag]);
46
+
47
+ useEffect(() => {
48
+ if (!nodeId) return;
49
+
50
+ return registerNodeHandlers(nodeId, {
51
+ setOffset,
52
+ onDrag: () => onDragRef.current?.(),
53
+ });
54
+ }, [nodeId, registerNodeHandlers]);
55
+
56
+ const setRef = useCallback(
57
+ (el) => {
58
+ localRef.current = el;
59
+ registerRef?.(el);
60
+ },
61
+ [registerRef]
62
+ );
63
+
64
+ const handleMouseDown = useCallback(
65
+ (e) => {
66
+ if (e.button !== 0) return;
67
+ e.stopPropagation();
68
+
69
+ if (onConnect && e.altKey) {
70
+ e.preventDefault();
71
+ onConnect(nodeId, [...selectedIds]);
72
+ return;
73
+ }
74
+
75
+ if (e.shiftKey || e.ctrlKey || e.metaKey) {
76
+ toggleSelection(nodeId);
77
+ return;
78
+ }
79
+
80
+ if (!selected) {
81
+ clearSelection();
82
+ selectNode(nodeId);
83
+ }
84
+
85
+ const startX = e.clientX;
86
+ const startY = e.clientY;
87
+ const startOffset = { ...offset };
88
+ lastDeltaRef.current = { x: 0, y: 0 };
89
+
90
+ const handleMove = (ev) => {
91
+ const dx = ev.clientX - startX;
92
+ const dy = ev.clientY - startY;
93
+
94
+ const deltaDx = dx - lastDeltaRef.current.x;
95
+ const deltaDy = dy - lastDeltaRef.current.y;
96
+ lastDeltaRef.current = { x: dx, y: dy };
97
+
98
+ setOffset({
99
+ x: startOffset.x + dx,
100
+ y: startOffset.y + dy,
101
+ });
102
+
103
+ if (selectedIds.size > 1) {
104
+ moveSelectedNodes(deltaDx, deltaDy, nodeId);
105
+ }
106
+
107
+ onDragRef.current?.();
108
+ };
109
+
110
+ const handleUp = () => {
111
+ window.removeEventListener("mousemove", handleMove);
112
+ window.removeEventListener("mouseup", handleUp);
113
+ };
114
+
115
+ window.addEventListener("mousemove", handleMove);
116
+ window.addEventListener("mouseup", handleUp);
117
+ },
118
+ [
119
+ nodeId,
120
+ offset,
121
+ selected,
122
+ selectedIds,
123
+ selectNode,
124
+ toggleSelection,
125
+ clearSelection,
126
+ moveSelectedNodes,
127
+ onConnect,
128
+ ]
129
+ );
130
+
131
+ return (
132
+ <Box
133
+ ref={setRef}
134
+ data-node-id={nodeId}
135
+ onMouseDown={handleMouseDown}
136
+ sx={{
137
+ display: "inline-flex",
138
+ flexDirection: "column",
139
+ alignItems: "center",
140
+ position: "relative",
141
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
142
+ cursor: "grab",
143
+ "&:active": { cursor: "grabbing" },
144
+ ...(selected && {
145
+ "&::after": {
146
+ content: '""',
147
+ position: "absolute",
148
+ inset: -6,
149
+ border: `2px solid ${selectionColor}`,
150
+ borderRadius: "12px",
151
+ pointerEvents: "none",
152
+ boxShadow: `0 0 8px ${selectionColor}66`,
153
+ },
154
+ }),
155
+ }}
156
+ >
157
+ {children}
158
+ </Box>
159
+ );
160
+ };
161
+
162
+ export default DraggableNode;