improve edges
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -9,7 +9,6 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-yaml": "^6.0.2",
|
|
||||||
"@radix-ui/react-context-menu": "^2.2.16",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@radix-ui/react-menubar": "^1.1.16",
|
"@radix-ui/react-menubar": "^1.1.16",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
"@xyflow/react": "^12.10.1",
|
"@xyflow/react": "^12.10.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
@@ -29,7 +27,6 @@
|
|||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "^4.0.9",
|
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
@@ -589,21 +586,6 @@
|
|||||||
"@lezer/javascript": "^1.0.0"
|
"@lezer/javascript": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/lang-yaml": {
|
|
||||||
"version": "6.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
|
||||||
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
|
||||||
"@codemirror/language": "^6.0.0",
|
|
||||||
"@codemirror/state": "^6.0.0",
|
|
||||||
"@lezer/common": "^1.2.0",
|
|
||||||
"@lezer/highlight": "^1.2.0",
|
|
||||||
"@lezer/lr": "^1.0.0",
|
|
||||||
"@lezer/yaml": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.12.2",
|
"version": "6.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
|
||||||
@@ -1490,17 +1472,6 @@
|
|||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/yaml": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@lezer/common": "^1.2.0",
|
|
||||||
"@lezer/highlight": "^1.0.0",
|
|
||||||
"@lezer/lr": "^1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@marijn/find-cluster-break": {
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
@@ -3268,13 +3239,6 @@
|
|||||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/js-yaml": {
|
|
||||||
"version": "4.0.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
|
||||||
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.3.3",
|
"version": "25.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
|
||||||
@@ -3572,6 +3536,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/aria-hidden": {
|
"node_modules/aria-hidden": {
|
||||||
@@ -5628,6 +5593,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import ConfigNode from './components/graph/ConfigNode'
|
|||||||
import FunctionNode from './components/graph/FunctionNode'
|
import FunctionNode from './components/graph/FunctionNode'
|
||||||
import RenderingNode from './components/graph/RenderingNode'
|
import RenderingNode from './components/graph/RenderingNode'
|
||||||
import VariableNode from './components/graph/VariableNode'
|
import VariableNode from './components/graph/VariableNode'
|
||||||
|
import { AnimatedEdge } from './components/graph/AnimatedEdge'
|
||||||
import FlowContext from './lib/flowContext'
|
import FlowContext from './lib/flowContext'
|
||||||
import { useTheme } from './lib/themeContext'
|
import { useTheme } from './lib/themeContext'
|
||||||
import {
|
import {
|
||||||
@@ -53,7 +54,9 @@ const initialNodes: Node[] = [
|
|||||||
},
|
},
|
||||||
].map((n) => ({ ...n, id: n.id ?? genId() }))
|
].map((n) => ({ ...n, id: n.id ?? genId() }))
|
||||||
|
|
||||||
const initialEdges: Edge[] = [{ id: 'e-config-render', source: 'config-1', target: 'render-1' }]
|
const initialEdges: Edge[] = [
|
||||||
|
{ id: 'e-config-render', source: 'config-1', target: 'render-1', type: 'animated' },
|
||||||
|
]
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { theme, toggleTheme } = useTheme()
|
const { theme, toggleTheme } = useTheme()
|
||||||
@@ -70,6 +73,8 @@ export default function App() {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const edgeTypes = React.useMemo(() => ({ animated: AnimatedEdge }), [])
|
||||||
|
|
||||||
const onConnect = React.useCallback(
|
const onConnect = React.useCallback(
|
||||||
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
||||||
[setEdges]
|
[setEdges]
|
||||||
@@ -172,6 +177,8 @@ export default function App() {
|
|||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
|
defaultEdgeOptions={{ type: 'animated' }}
|
||||||
colorMode={theme as ColorMode}
|
colorMode={theme as ColorMode}
|
||||||
snapToGrid
|
snapToGrid
|
||||||
snapGrid={SNAP_GRID}
|
snapGrid={SNAP_GRID}
|
||||||
|
|||||||
99
src/components/graph/AnimatedEdge.tsx
Normal file
99
src/components/graph/AnimatedEdge.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
BaseEdge,
|
||||||
|
getBezierPath,
|
||||||
|
type EdgeProps,
|
||||||
|
} from '@xyflow/react'
|
||||||
|
|
||||||
|
const EDGE_STROKE_WIDTH = 2
|
||||||
|
const DOT_MARKER_R = 1.5
|
||||||
|
|
||||||
|
export function AnimatedEdge({
|
||||||
|
id,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition,
|
||||||
|
targetPosition,
|
||||||
|
style,
|
||||||
|
label,
|
||||||
|
labelStyle,
|
||||||
|
labelShowBg,
|
||||||
|
labelBgStyle,
|
||||||
|
labelBgPadding,
|
||||||
|
labelBgBorderRadius,
|
||||||
|
labelX,
|
||||||
|
labelY,
|
||||||
|
interactionWidth,
|
||||||
|
}: EdgeProps) {
|
||||||
|
const [edgePath] = getBezierPath({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition,
|
||||||
|
targetPosition,
|
||||||
|
})
|
||||||
|
|
||||||
|
const startId = `animated-edge-dot-start-${id}`
|
||||||
|
const endId = `animated-edge-dot-end-${id}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<defs>
|
||||||
|
<marker
|
||||||
|
id={startId}
|
||||||
|
markerWidth={DOT_MARKER_R * 2}
|
||||||
|
markerHeight={DOT_MARKER_R * 2}
|
||||||
|
refX={DOT_MARKER_R + 5}
|
||||||
|
refY={DOT_MARKER_R}
|
||||||
|
orient="auto"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
r={DOT_MARKER_R}
|
||||||
|
cx={DOT_MARKER_R}
|
||||||
|
cy={DOT_MARKER_R}
|
||||||
|
className="fill-primary stroke-background"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id={endId}
|
||||||
|
markerWidth={DOT_MARKER_R * 2}
|
||||||
|
markerHeight={DOT_MARKER_R * 2}
|
||||||
|
refX={DOT_MARKER_R - 5}
|
||||||
|
refY={DOT_MARKER_R}
|
||||||
|
orient="auto"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
r={DOT_MARKER_R}
|
||||||
|
cx={DOT_MARKER_R}
|
||||||
|
cy={DOT_MARKER_R}
|
||||||
|
className="fill-primary stroke-background"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<BaseEdge
|
||||||
|
path={edgePath}
|
||||||
|
markerStart={`url(#${startId})`}
|
||||||
|
markerEnd={`url(#${endId})`}
|
||||||
|
style={{
|
||||||
|
strokeWidth: EDGE_STROKE_WIDTH,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
className="animated-edge-path"
|
||||||
|
label={label}
|
||||||
|
labelStyle={labelStyle}
|
||||||
|
labelShowBg={labelShowBg}
|
||||||
|
labelBgStyle={labelBgStyle}
|
||||||
|
labelBgPadding={labelBgPadding}
|
||||||
|
labelBgBorderRadius={labelBgBorderRadius}
|
||||||
|
labelX={labelX}
|
||||||
|
labelY={labelY}
|
||||||
|
interactionWidth={interactionWidth}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -32,6 +32,25 @@ body {
|
|||||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Animated React Flow edges: thicker stroke + path animation */
|
||||||
|
.react-flow__edge path.animated-edge-path,
|
||||||
|
.animated-edge-path {
|
||||||
|
stroke-width: 4;
|
||||||
|
stroke: hsl(var(--foreground) / 0.6);
|
||||||
|
fill: none;
|
||||||
|
stroke-dasharray: 8 6;
|
||||||
|
animation: edge-flow 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes edge-flow {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Small helper for monospace pre output */
|
/* Small helper for monospace pre output */
|
||||||
pre {
|
pre {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;
|
||||||
|
|||||||
Reference in New Issue
Block a user