@canmingir/link 1.2.51 → 1.2.53
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/package.json +1 -1
- package/src/http/index.js +12 -17
- package/src/layouts/DashboardLayout/header.jsx +1 -5
- package/src/layouts/common/nav-toggle-button.jsx +2 -2
- package/src/lib/Flow/connectors/DynamicConnector.jsx +19 -12
- package/src/lib/Flow/core/Flow.jsx +7 -3
- package/src/lib/Flow/core/FlowBoard.jsx +78 -0
- package/src/lib/Flow/core/FlowBoardItem.jsx +144 -0
- package/src/lib/Flow/core/FlowNode.jsx +2 -0
- package/src/lib/Flow/core/FlowViewport.jsx +3 -2
- package/src/lib/Flow/index.js +1 -0
- package/src/lib/index.js +1 -1
package/package.json
CHANGED
package/src/http/index.js
CHANGED
|
@@ -40,9 +40,8 @@ instance.interceptors.response.use(
|
|
|
40
40
|
async (error) => {
|
|
41
41
|
publish("LOADED", { loading: false });
|
|
42
42
|
const statusCode = error.response?.status;
|
|
43
|
-
const identityProvider = storage.get("link", "identityProvider");
|
|
44
43
|
|
|
45
|
-
if (statusCode === 500
|
|
44
|
+
if (statusCode === 500) {
|
|
46
45
|
const token = storage.get("link", "accessToken");
|
|
47
46
|
try {
|
|
48
47
|
const decodedToken = jwtDecode(token);
|
|
@@ -155,22 +154,18 @@ refreshInterceptor(instance, refreshAuthLogic, {
|
|
|
155
154
|
return false;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
if (identityProvider === "DEMO") {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const decodedToken = jwtDecode(token);
|
|
165
|
-
return decodedToken.exp * 1000 < Date.now();
|
|
166
|
-
} catch (_) {
|
|
167
|
-
// If we can't decode, attempt refresh once.
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
157
|
+
if (identityProvider === "DEMO") {
|
|
158
|
+
if (statusCode !== 500) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
try {
|
|
163
|
+
const decodedToken = jwtDecode(token);
|
|
164
|
+
return decodedToken.exp * 1000 < Date.now();
|
|
165
|
+
} catch (_) {
|
|
166
|
+
// If we can't decode, attempt refresh once.
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
try {
|
|
@@ -26,7 +26,7 @@ export default function NavToggleButton({ sx, ...other }) {
|
|
|
26
26
|
onClick={() =>
|
|
27
27
|
settings.onUpdate(
|
|
28
28
|
"themeLayout",
|
|
29
|
-
settings.themeLayout === "vertical" ? "mini" : "vertical"
|
|
29
|
+
settings.themeLayout === "vertical" ? "mini" : "vertical",
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
sx={{
|
|
@@ -34,7 +34,7 @@ export default function NavToggleButton({ sx, ...other }) {
|
|
|
34
34
|
top: 32,
|
|
35
35
|
position: "fixed",
|
|
36
36
|
left: NAV.W_VERTICAL - 12,
|
|
37
|
-
zIndex:
|
|
37
|
+
zIndex: theme.zIndex.appBar + 1,
|
|
38
38
|
border: `dashed 1px ${theme.palette.divider}`,
|
|
39
39
|
...bgBlur({ opacity: 0.48, color: theme.palette.background.default }),
|
|
40
40
|
"&:hover": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Box } from "@mui/material";
|
|
2
|
-
|
|
3
1
|
import React, { useId, useLayoutEffect, useMemo, useState } from "react";
|
|
4
2
|
|
|
3
|
+
import { Box } from "@mui/material";
|
|
4
|
+
|
|
5
5
|
const DynamicConnector = ({
|
|
6
6
|
containerEl,
|
|
7
7
|
parentEl,
|
|
@@ -43,41 +43,48 @@ const DynamicConnector = ({
|
|
|
43
43
|
const cRect = containerEl.getBoundingClientRect();
|
|
44
44
|
const pRect = parentEl.getBoundingClientRect();
|
|
45
45
|
|
|
46
|
+
const scaleX = containerEl.offsetWidth
|
|
47
|
+
? cRect.width / containerEl.offsetWidth || 1
|
|
48
|
+
: 1;
|
|
49
|
+
const scaleY = containerEl.offsetHeight
|
|
50
|
+
? cRect.height / containerEl.offsetHeight || 1
|
|
51
|
+
: 1;
|
|
52
|
+
|
|
46
53
|
let parentPoint;
|
|
47
54
|
let childPoints = [];
|
|
48
55
|
|
|
49
56
|
if (isHorizontal) {
|
|
50
57
|
parentPoint = {
|
|
51
|
-
x: pRect.right - cRect.left,
|
|
52
|
-
y: pRect.top + pRect.height / 2 - cRect.top,
|
|
58
|
+
x: (pRect.right - cRect.left) / scaleX,
|
|
59
|
+
y: (pRect.top + pRect.height / 2 - cRect.top) / scaleY,
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
childPoints = childEls.map((el) => {
|
|
56
63
|
if (!el) return { x: parentPoint.x + 100, y: parentPoint.y };
|
|
57
64
|
const r = el.getBoundingClientRect();
|
|
58
65
|
return {
|
|
59
|
-
x: r.left - cRect.left,
|
|
60
|
-
y: r.top + r.height / 2 - cRect.top,
|
|
66
|
+
x: (r.left - cRect.left) / scaleX,
|
|
67
|
+
y: (r.top + r.height / 2 - cRect.top) / scaleY,
|
|
61
68
|
};
|
|
62
69
|
});
|
|
63
70
|
} else {
|
|
64
71
|
parentPoint = {
|
|
65
|
-
x: pRect.left + pRect.width / 2 - cRect.left,
|
|
66
|
-
y: pRect.bottom - cRect.top,
|
|
72
|
+
x: (pRect.left + pRect.width / 2 - cRect.left) / scaleX,
|
|
73
|
+
y: (pRect.bottom - cRect.top) / scaleY,
|
|
67
74
|
};
|
|
68
75
|
|
|
69
76
|
childPoints = childEls.map((el) => {
|
|
70
77
|
if (!el) return { x: parentPoint.x, y: parentPoint.y + 100 };
|
|
71
78
|
const r = el.getBoundingClientRect();
|
|
72
79
|
return {
|
|
73
|
-
x: r.left + r.width / 2 - cRect.left,
|
|
74
|
-
y: r.top - cRect.top,
|
|
80
|
+
x: (r.left + r.width / 2 - cRect.left) / scaleX,
|
|
81
|
+
y: (r.top - cRect.top) / scaleY,
|
|
75
82
|
};
|
|
76
83
|
});
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
setPoints({ parent: parentPoint, children: childPoints });
|
|
80
|
-
setDims({ w: cRect.width, h: cRect.height });
|
|
87
|
+
setDims({ w: cRect.width / scaleX, h: cRect.height / scaleY });
|
|
81
88
|
};
|
|
82
89
|
|
|
83
90
|
update();
|
|
@@ -95,7 +102,7 @@ const DynamicConnector = ({
|
|
|
95
102
|
gradient: `gradient-${uniqueId}`,
|
|
96
103
|
arrow: `arrow-${uniqueId}`,
|
|
97
104
|
}),
|
|
98
|
-
[uniqueId]
|
|
105
|
+
[uniqueId],
|
|
99
106
|
);
|
|
100
107
|
|
|
101
108
|
const getPath = (from, to) => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import FlowNode from "./FlowNode";
|
|
2
|
-
import { useGraphOperations } from "../hooks/useGraphOperations";
|
|
3
|
-
|
|
4
1
|
import { Box, alpha } from "@mui/material";
|
|
5
2
|
import React, { useMemo, useState } from "react";
|
|
6
3
|
import { assertLinkedGraph, buildTreeFromLinked } from "../utils/flowUtils";
|
|
7
4
|
|
|
5
|
+
import FlowNode from "./FlowNode";
|
|
6
|
+
import { useGraphOperations } from "../hooks/useGraphOperations";
|
|
7
|
+
|
|
8
8
|
export const Flow = ({
|
|
9
9
|
data,
|
|
10
10
|
variant = "simple",
|
|
@@ -13,6 +13,7 @@ export const Flow = ({
|
|
|
13
13
|
editable = false,
|
|
14
14
|
onChange,
|
|
15
15
|
height,
|
|
16
|
+
initialZoom,
|
|
16
17
|
}) => {
|
|
17
18
|
const [floatingNodes, setFloatingNodes] = useState([]);
|
|
18
19
|
|
|
@@ -65,6 +66,8 @@ export const Flow = ({
|
|
|
65
66
|
return (
|
|
66
67
|
<Box
|
|
67
68
|
sx={{
|
|
69
|
+
height,
|
|
70
|
+
flexShrink: 0,
|
|
68
71
|
backgroundImage: (theme) => `
|
|
69
72
|
radial-gradient(
|
|
70
73
|
${alpha(theme.palette.divider, 0.08)} 1px,
|
|
@@ -86,6 +89,7 @@ export const Flow = ({
|
|
|
86
89
|
onConnect={editable ? handleConnect : undefined}
|
|
87
90
|
floatingNodes={floatingNodes}
|
|
88
91
|
height={height}
|
|
92
|
+
initialZoom={initialZoom}
|
|
89
93
|
/>
|
|
90
94
|
</Box>
|
|
91
95
|
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import FlowBoardItem from "./FlowBoardItem";
|
|
4
|
+
import FlowViewport from "./FlowViewport";
|
|
5
|
+
import { SelectionProvider } from "../selection/SelectionContext";
|
|
6
|
+
import { getBaseStyleForVariant } from "../styles";
|
|
7
|
+
|
|
8
|
+
export const FlowBoard = ({
|
|
9
|
+
flows = [],
|
|
10
|
+
variant = "simple",
|
|
11
|
+
style,
|
|
12
|
+
plugin,
|
|
13
|
+
initialZoom = 1,
|
|
14
|
+
height = "100vh",
|
|
15
|
+
gap = 480,
|
|
16
|
+
onFlowPositionChange,
|
|
17
|
+
}) => {
|
|
18
|
+
const baseStyle = getBaseStyleForVariant(variant);
|
|
19
|
+
const selectionColor = baseStyle.selectionColor ?? "#64748b";
|
|
20
|
+
|
|
21
|
+
const positionedFlows = useMemo(() => {
|
|
22
|
+
const count = flows.length;
|
|
23
|
+
return flows.map((flow, index) => {
|
|
24
|
+
const fallback = {
|
|
25
|
+
x: (index - (count - 1) / 2) * gap,
|
|
26
|
+
y: 0,
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
flow,
|
|
30
|
+
id: flow?.id ?? index,
|
|
31
|
+
position: flow?.position ?? fallback,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}, [flows, gap]);
|
|
35
|
+
|
|
36
|
+
const mergedNodesById = useMemo(() => {
|
|
37
|
+
const merged = {};
|
|
38
|
+
for (const { flow } of positionedFlows) {
|
|
39
|
+
if (flow?.nodes) Object.assign(merged, flow.nodes);
|
|
40
|
+
}
|
|
41
|
+
return merged;
|
|
42
|
+
}, [positionedFlows]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<SelectionProvider>
|
|
46
|
+
<FlowViewport
|
|
47
|
+
selectionColor={selectionColor}
|
|
48
|
+
nodesById={mergedNodesById}
|
|
49
|
+
variant={variant}
|
|
50
|
+
style={style}
|
|
51
|
+
plugin={plugin}
|
|
52
|
+
height={height}
|
|
53
|
+
initialZoom={initialZoom}
|
|
54
|
+
>
|
|
55
|
+
{positionedFlows.map(({ flow, id, position }) => (
|
|
56
|
+
<FlowBoardItem
|
|
57
|
+
key={id}
|
|
58
|
+
flow={flow}
|
|
59
|
+
flowId={id}
|
|
60
|
+
label={flow?.label}
|
|
61
|
+
divider={flow?.divider}
|
|
62
|
+
position={position}
|
|
63
|
+
onPositionChange={
|
|
64
|
+
onFlowPositionChange
|
|
65
|
+
? (pos) => onFlowPositionChange(id, pos)
|
|
66
|
+
: undefined
|
|
67
|
+
}
|
|
68
|
+
variant={flow?.variant ?? variant}
|
|
69
|
+
style={style}
|
|
70
|
+
plugin={plugin}
|
|
71
|
+
/>
|
|
72
|
+
))}
|
|
73
|
+
</FlowViewport>
|
|
74
|
+
</SelectionProvider>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default FlowBoard;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { assertLinkedGraph, buildTreeFromLinked } from "../utils/flowUtils";
|
|
3
|
+
|
|
4
|
+
import { Box } from "@mui/material";
|
|
5
|
+
import FlowNodeView from "../nodes/FlowNodeView";
|
|
6
|
+
|
|
7
|
+
const FlowBoardItem = ({
|
|
8
|
+
flow,
|
|
9
|
+
flowId,
|
|
10
|
+
position: initialPosition,
|
|
11
|
+
onPositionChange,
|
|
12
|
+
variant,
|
|
13
|
+
style,
|
|
14
|
+
plugin,
|
|
15
|
+
label,
|
|
16
|
+
divider,
|
|
17
|
+
}) => {
|
|
18
|
+
const [position, setPosition] = useState(
|
|
19
|
+
() => initialPosition || { x: 0, y: 0 },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (initialPosition) setPosition({ ...initialPosition });
|
|
24
|
+
}, [initialPosition?.x, initialPosition?.y]);
|
|
25
|
+
|
|
26
|
+
const didDragRef = useRef(false);
|
|
27
|
+
|
|
28
|
+
const namespacedFlow = useMemo(() => {
|
|
29
|
+
if (flowId == null) return flow;
|
|
30
|
+
const prefix = `${flowId}::`;
|
|
31
|
+
const ns = (id) =>
|
|
32
|
+
typeof id === "string" && !id.startsWith(prefix) ? `${prefix}${id}` : id;
|
|
33
|
+
|
|
34
|
+
const nodes = {};
|
|
35
|
+
for (const [id, node] of Object.entries(flow?.nodes || {})) {
|
|
36
|
+
const nextArr = Array.isArray(node.next)
|
|
37
|
+
? node.next.map((n) => (typeof n === "string" ? ns(n) : n))
|
|
38
|
+
: node.next != null
|
|
39
|
+
? ns(node.next)
|
|
40
|
+
: undefined;
|
|
41
|
+
nodes[ns(id)] = {
|
|
42
|
+
...node,
|
|
43
|
+
id: ns(id),
|
|
44
|
+
next: nextArr,
|
|
45
|
+
previous: node.previous != null ? ns(node.previous) : undefined,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const roots = Array.isArray(flow?.roots) ? flow.roots.map(ns) : flow?.roots;
|
|
49
|
+
return { ...flow, nodes, roots };
|
|
50
|
+
}, [flow, flowId]);
|
|
51
|
+
|
|
52
|
+
const { nodesById, roots } = useMemo(
|
|
53
|
+
() => assertLinkedGraph(namespacedFlow),
|
|
54
|
+
[namespacedFlow],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const treesData = useMemo(() => {
|
|
58
|
+
if (!roots?.length) return [];
|
|
59
|
+
return roots
|
|
60
|
+
.map((rootId) => buildTreeFromLinked(rootId, nodesById))
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
}, [nodesById, roots]);
|
|
63
|
+
|
|
64
|
+
const handleMouseDown = (e) => {
|
|
65
|
+
if (e.button !== 0) return;
|
|
66
|
+
if (e.target?.closest?.(".MuiCard-root") || e.target?.closest?.("button"))
|
|
67
|
+
return;
|
|
68
|
+
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
didDragRef.current = false;
|
|
71
|
+
|
|
72
|
+
const startX = e.clientX;
|
|
73
|
+
const startY = e.clientY;
|
|
74
|
+
const startPosition = { ...position };
|
|
75
|
+
let lastPosition = startPosition;
|
|
76
|
+
|
|
77
|
+
const onMove = (ev) => {
|
|
78
|
+
const dx = ev.clientX - startX;
|
|
79
|
+
const dy = ev.clientY - startY;
|
|
80
|
+
if (!didDragRef.current && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) {
|
|
81
|
+
didDragRef.current = true;
|
|
82
|
+
}
|
|
83
|
+
lastPosition = { x: startPosition.x + dx, y: startPosition.y + dy };
|
|
84
|
+
setPosition(lastPosition);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const onUp = () => {
|
|
88
|
+
window.removeEventListener("mousemove", onMove);
|
|
89
|
+
window.removeEventListener("mouseup", onUp);
|
|
90
|
+
if (didDragRef.current) onPositionChange?.(lastPosition);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
window.addEventListener("mousemove", onMove);
|
|
94
|
+
window.addEventListener("mouseup", onUp);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (!treesData.length) return null;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Box
|
|
101
|
+
data-flow-id={label}
|
|
102
|
+
onMouseDown={handleMouseDown}
|
|
103
|
+
sx={{
|
|
104
|
+
position: "absolute",
|
|
105
|
+
left: "50%",
|
|
106
|
+
top: "50%",
|
|
107
|
+
transform: `translate(${position.x}px, ${position.y}px)`,
|
|
108
|
+
cursor: "grab",
|
|
109
|
+
"&:active": { cursor: "grabbing" },
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{label != null && (
|
|
113
|
+
<Box
|
|
114
|
+
sx={{
|
|
115
|
+
position: "absolute",
|
|
116
|
+
top: -28,
|
|
117
|
+
left: 0,
|
|
118
|
+
fontSize: 12,
|
|
119
|
+
fontWeight: 600,
|
|
120
|
+
opacity: 0.6,
|
|
121
|
+
pointerEvents: "none",
|
|
122
|
+
whiteSpace: "nowrap",
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{label}
|
|
126
|
+
</Box>
|
|
127
|
+
)}
|
|
128
|
+
{divider != null && <Box sx={{ width: "100%", mb: 1 }}>{divider}</Box>}
|
|
129
|
+
<Box sx={{ display: "flex", gap: 4, alignItems: "flex-start" }}>
|
|
130
|
+
{treesData.map((tree, idx) => (
|
|
131
|
+
<FlowNodeView
|
|
132
|
+
key={tree.id || `tree-${idx}`}
|
|
133
|
+
node={tree}
|
|
134
|
+
variant={variant}
|
|
135
|
+
style={style}
|
|
136
|
+
plugin={plugin}
|
|
137
|
+
/>
|
|
138
|
+
))}
|
|
139
|
+
</Box>
|
|
140
|
+
</Box>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default FlowBoardItem;
|
|
@@ -17,6 +17,7 @@ const FlowNode = ({
|
|
|
17
17
|
plugin,
|
|
18
18
|
node,
|
|
19
19
|
height,
|
|
20
|
+
initialZoom,
|
|
20
21
|
...props
|
|
21
22
|
}) => {
|
|
22
23
|
if (!isRoot) {
|
|
@@ -50,6 +51,7 @@ const FlowNode = ({
|
|
|
50
51
|
style={style}
|
|
51
52
|
plugin={plugin}
|
|
52
53
|
height={height}
|
|
54
|
+
initialZoom={initialZoom}
|
|
53
55
|
>
|
|
54
56
|
{node && (
|
|
55
57
|
<FlowNodeView
|
|
@@ -17,13 +17,14 @@ const FlowViewport = ({
|
|
|
17
17
|
style,
|
|
18
18
|
plugin,
|
|
19
19
|
height = "100vh",
|
|
20
|
+
initialZoom = 1,
|
|
20
21
|
sx = {},
|
|
21
22
|
...rest
|
|
22
23
|
}) => {
|
|
23
24
|
const clampZoom = (zoom) => Math.min(2.5, Math.max(0.25, zoom));
|
|
24
25
|
|
|
25
26
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
26
|
-
const [zoom, setZoom] = useState(
|
|
27
|
+
const [zoom, setZoom] = useState(initialZoom);
|
|
27
28
|
const [isDragging, setIsDragging] = useState(false);
|
|
28
29
|
const [selectionBox, setSelectionBox] = useState(null);
|
|
29
30
|
const [shouldCenter, setShouldCenter] = useState(true);
|
|
@@ -291,7 +292,7 @@ const FlowViewport = ({
|
|
|
291
292
|
onContextMenu={(e) => e.preventDefault()}
|
|
292
293
|
sx={{
|
|
293
294
|
width: "100%",
|
|
294
|
-
height:
|
|
295
|
+
height: height,
|
|
295
296
|
overflow: "hidden",
|
|
296
297
|
bgcolor: "none",
|
|
297
298
|
cursor: isDragging ? "grabbing" : "default",
|
package/src/lib/Flow/index.js
CHANGED
package/src/lib/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { default as useTable } from "./useTable/useTable";
|
|
|
23
23
|
export { default as useChart } from "./useChart/useChart";
|
|
24
24
|
|
|
25
25
|
export { default as Flow } from "./Flow/core/Flow";
|
|
26
|
-
|
|
26
|
+
export { default as FlowBoard } from "./Flow/core/FlowBoard";
|
|
27
27
|
export {
|
|
28
28
|
HeaderCard,
|
|
29
29
|
MediaAvatarCard,
|