From 79bea1ac08cd7c8825358c81fccedbea4fc24d03 Mon Sep 17 00:00:00 2001 From: Emil Valeev Date: Sat, 14 Oct 2023 01:36:54 +0600 Subject: [PATCH] feat(web): add 2 nums visual example --- web/package-lock.json | 30 +++++ web/package.json | 2 + web/src/App.css | 42 ------- web/src/App.tsx | 252 ++++++++++++++++++++++++++++++++++++++++-- web/src/index.css | 81 +++----------- 5 files changed, 291 insertions(+), 116 deletions(-) delete mode 100644 web/src/App.css diff --git a/web/package-lock.json b/web/package-lock.json index cf8ff3e9..00426e34 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,11 +8,13 @@ "name": "neva", "version": "0.0.0", "dependencies": { + "dagre": "^0.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", "reactflow": "^11.9.3" }, "devDependencies": { + "@types/dagre": "^0.7.50", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", @@ -1038,6 +1040,12 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/dagre": { + "version": "0.7.50", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.50.tgz", + "integrity": "sha512-3HxPUil6GwbcO+q3WxZhM6XMSXYaiXjjzKUDYsGk2tqP5Ko2WpN71I8g1kXLgX5nUkKg00+LlCTuaverWVADGA==", + "dev": true + }, "node_modules/@types/geojson": { "version": "7946.0.11", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.11.tgz", @@ -1562,6 +1570,15 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2028,6 +2045,14 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2204,6 +2229,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/web/package.json b/web/package.json index 2a6b6b66..9dc1bf66 100644 --- a/web/package.json +++ b/web/package.json @@ -11,11 +11,13 @@ "preview": "vite preview" }, "dependencies": { + "dagre": "^0.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", "reactflow": "^11.9.3" }, "devDependencies": { + "@types/dagre": "^0.7.50", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", diff --git a/web/src/App.css b/web/src/App.css deleted file mode 100644 index b9d355df..00000000 --- a/web/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/web/src/App.tsx b/web/src/App.tsx index ac5cf850..6e6ec1e7 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import ReactFlow, { MiniMap, Controls, @@ -9,28 +9,266 @@ import ReactFlow, { BackgroundVariant, Connection, Panel, - useNodesInitialized, + Handle, + Position, + Edge, + Node, } from "reactflow"; +import dagre from "dagre"; import "reactflow/dist/style.css"; +function generateValue(n: number, i: number): number { + if (n == 1) { + return 50; + } + return (i / (n - 1)) * 100; +} + +interface INormalNodeProps { + id: string; + data: { + ports: { + in: string[]; + out: string[]; + }; + }; +} + +function NormalNode(props: INormalNodeProps) { + console.log(props); + + return ( +
+ {props.data.ports.in.map((inportName, idx, arr) => ( + + ))} + {props.id} + {props.data.ports.out.map((outportName) => ( + + ))} +
+ ); +} + +const defaultPosition = { x: 0, y: 0 }; const initialNodes = [ - { id: "1", position: { x: 0, y: 0 }, data: { label: "1" } }, - { id: "2", position: { x: 0, y: 100 }, data: { label: "2" } }, + { + type: "normalNode", + id: "in", + position: defaultPosition, + isHidden: false, + data: { + ports: { + in: [], + out: ["enter"], + }, + }, + }, + { + type: "normalNode", + id: "out", + position: defaultPosition, + data: { + ports: { + in: ["exit"], + out: [], + }, + }, + }, + { + type: "normalNode", + id: "readFirstInt", + position: defaultPosition, + data: { + ports: { + in: ["sig"], + out: ["v"], + }, + }, + }, + { + type: "normalNode", + id: "readSecondInt", + position: defaultPosition, + data: { + ports: { + in: ["sig"], + out: ["v"], + }, + }, + }, + { + type: "normalNode", + id: "add", + position: defaultPosition, + data: { + ports: { + in: ["a", "b"], + out: ["v"], + }, + }, + }, + { + type: "normalNode", + id: "print", + position: defaultPosition, + data: { + ports: { + in: ["v"], + out: ["v"], + }, + }, + }, +]; +const initialEdges: Edge[] = [ + { + id: "in.enter -> readFirstInt.sig", + source: "in", + sourceHandle: "enter", + target: "readFirstInt", + targetHandle: "sig", + // type: "smoothstep" + }, + { + id: "readFirstInt.err -> print.v", + source: "readFirstInt", + sourceHandle: "err", + target: "print", + targetHandle: "v", + // type: "smoothstep" + }, + { + id: "readFirstInt.v -> add.a", + source: "readFirstInt", + sourceHandle: "v", + target: "add", + targetHandle: "a", + // type: "smoothstep" + }, + { + id: "readFirstInt.v -> readSecondInt.sig", + source: "readFirstInt", + sourceHandle: "v", + target: "readSecondInt", + targetHandle: "sig", + // type: "smoothstep" + }, + { + id: "readSecondInt.err -> print.v", + source: "readSecondInt", + sourceHandle: "err", + target: "print", + targetHandle: "v", + // type: "smoothstep" + }, + { + id: "readSecondInt.v -> add.b", + source: "readSecondInt", + sourceHandle: "v", + target: "add", + targetHandle: "b", + // type: "smoothstep" + }, + { + id: "add.v -> print.v", + source: "add", + sourceHandle: "v", + target: "print", + targetHandle: "v", + // type: "smoothstep" + }, + { + id: "print.v -> out.exit", + source: "print", + sourceHandle: "v", + target: "out", + targetHandle: "exit", + // type: "smoothstep" + }, ]; -const initialEdges = [{ id: "e1-2", source: "1", target: "2" }]; + +const dagreGraph = new dagre.graphlib.Graph(); +dagreGraph.setDefaultEdgeLabel(() => ({})); + +const nodeWidth = 342.5; +const nodeHeight = 70; + +const getLayoutedElements = ( + nodes: Node[], + edges: Edge[], + direction = "TB" +) => { + const isHorizontal = direction === "LR"; + dagreGraph.setGraph({ rankdir: direction }); + + nodes.forEach((node) => { + dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); + }); + + edges.forEach((edge) => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + nodes.forEach((node) => { + const nodeWithPosition = dagreGraph.node(node.id); + node.targetPosition = (isHorizontal ? "left" : "top") as Position; + node.sourcePosition = (isHorizontal ? "right" : "bottom") as Position; + + // We are shifting the dagre node position (anchor=center center) to the top left + // so it matches the React Flow node anchor point (top left). + node.position = { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeight / 2, + }; + + return node; + }); + + return { nodes, edges }; +}; + +const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + initialNodes, + initialEdges +); export default function App() { - const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const [nodes, _setNodes, onNodesChange] = useNodesState(layoutedNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges); const onConnect = useCallback( (params: Connection) => setEdges((eds) => addEdge(params, eds)), [setEdges] ); + const nodeTypes = useMemo( + () => ({ + normalNode: NormalNode, + }), + [] + ); + return (
instance.fitView()} nodes={nodes} edges={edges} diff --git a/web/src/index.css b/web/src/index.css index 2c3fac68..3a116f4b 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,69 +1,16 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; +.normalNode { + min-width: 100px; + text-align: center; + margin: auto; + height: 50px; + border: 1px solid #eee; + padding: 5px; + border-radius: 5px; + background: white; } -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +.normalNode label { + display: block; + color: #777; + font-size: 12px; +} \ No newline at end of file