diff --git a/package-lock.json b/package-lock.json index 64eadec..46ca205 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/lang-yaml": "^6.0.2", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-menubar": "^1.1.16", "@radix-ui/react-select": "^2.2.6", @@ -20,7 +19,6 @@ "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "js-yaml": "^4.1.0", "lucide-react": "^0.577.0", "react": "18.2.0", "react-dom": "18.2.0", @@ -29,7 +27,6 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@types/js-yaml": "^4.0.9", "@types/node": "^25.3.3", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", @@ -589,21 +586,6 @@ "@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": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", @@ -1490,17 +1472,6 @@ "@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": { "version": "1.0.2", "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==", "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": { "version": "25.3.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", @@ -3572,6 +3536,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -5628,6 +5593,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" diff --git a/src/App.tsx b/src/App.tsx index 614a8b6..a6c2599 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import ConfigNode from './components/graph/ConfigNode' import FunctionNode from './components/graph/FunctionNode' import RenderingNode from './components/graph/RenderingNode' import VariableNode from './components/graph/VariableNode' +import { AnimatedEdge } from './components/graph/AnimatedEdge' import FlowContext from './lib/flowContext' import { useTheme } from './lib/themeContext' import { @@ -53,7 +54,9 @@ const initialNodes: Node[] = [ }, ].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() { const { theme, toggleTheme } = useTheme() @@ -70,6 +73,8 @@ export default function App() { [] ) + const edgeTypes = React.useMemo(() => ({ animated: AnimatedEdge }), []) + const onConnect = React.useCallback( (params: Connection) => setEdges((eds) => addEdge(params, eds)), [setEdges] @@ -172,6 +177,8 @@ export default function App() { onEdgesChange={onEdgesChange} onConnect={onConnect} nodeTypes={nodeTypes} + edgeTypes={edgeTypes} + defaultEdgeOptions={{ type: 'animated' }} colorMode={theme as ColorMode} snapToGrid snapGrid={SNAP_GRID} diff --git a/src/components/graph/AnimatedEdge.tsx b/src/components/graph/AnimatedEdge.tsx new file mode 100644 index 0000000..5b7272b --- /dev/null +++ b/src/components/graph/AnimatedEdge.tsx @@ -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 ( + <> + + + + + + + + + + + ) +} diff --git a/src/styles.css b/src/styles.css index 045f8b9..9ef5f24 100644 --- a/src/styles.css +++ b/src/styles.css @@ -32,6 +32,25 @@ body { 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 */ pre { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;