From e076331cfcb4d36653297479ebe12cd85596a994 Mon Sep 17 00:00:00 2001 From: JGerla Date: Tue, 30 Sep 2025 13:32:45 +0200 Subject: [PATCH 01/61] feat: added ReactFlow-based node graph Added ReactFlow to dependencies (@xyflow/react). Added a basic reactflow template to test functionality of reactFlow and to build the visual programming UI on top of. ref: N25B-114 --- package-lock.json | 234 +++++++++++++++++++++++++- package.json | 1 + src/VisualProgrammingUI/VisProgUI.css | 0 src/VisualProgrammingUI/VisProgUI.tsx | 56 ++++++ src/main.tsx | 2 +- 5 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 src/VisualProgrammingUI/VisProgUI.css create mode 100644 src/VisualProgrammingUI/VisProgUI.tsx diff --git a/package-lock.json b/package-lock.json index c11331b..c20c730 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "pepperplus-ui", "version": "0.0.0", "dependencies": { + "@xyflow/react": "^12.8.6", "react": "^19.1.1", "react-dom": "^19.1.1" }, @@ -1403,6 +1404,55 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1421,7 +1471,7 @@ "version": "19.1.13", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1729,6 +1779,38 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@xyflow/react": { + "version": "12.8.6", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.6.tgz", + "integrity": "sha512-SksAm2m4ySupjChphMmzvm55djtgMDPr+eovPDdTnyGvShf73cvydfoBfWDFllooIQ4IaiUL5yfxHRwU0c37EA==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.70", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.70", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.70.tgz", + "integrity": "sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1915,6 +1997,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1968,9 +2056,114 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3249,6 +3442,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", @@ -3400,6 +3602,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 6ed4958..2757ba3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@xyflow/react": "^12.8.6", "react": "^19.1.1", "react-dom": "^19.1.1" }, diff --git a/src/VisualProgrammingUI/VisProgUI.css b/src/VisualProgrammingUI/VisProgUI.css new file mode 100644 index 0000000..e69de29 diff --git a/src/VisualProgrammingUI/VisProgUI.tsx b/src/VisualProgrammingUI/VisProgUI.tsx new file mode 100644 index 0000000..2f58a64 --- /dev/null +++ b/src/VisualProgrammingUI/VisProgUI.tsx @@ -0,0 +1,56 @@ +import './VisProgUI.css' + +import { useState, useCallback } from 'react'; +import { + ReactFlow, + applyNodeChanges, + applyEdgeChanges, + addEdge, + type NodeChange, + type EdgeChange, + type Edge, type Connection +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; + +const initialNodes = [ + {id: 'n1', position: {x: 0, y: 0}, data: {label: 'Node 1'}}, + {id: 'n2', position: {x: 0, y: 100}, data: {label: 'Node 2'}}, +]; +const initialEdges = [{id: 'n1-n2', source: 'n1', target: 'n2'}]; + +export default function App() { + const [nodes, setNodes] = useState(initialNodes); + const [edges, setEdges] = useState(initialEdges); + + const onNodesChange = useCallback( + (changes: NodeChange<{ + id: string; + position: { x: number; y: number; }; + data: { label: string; }; + }>[]) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)), + [], + ); + const onEdgesChange = useCallback( + (changes: EdgeChange<{ id: string; source: string; target: string; }>[]) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)), + [], + ); + + + const onConnect = useCallback( + (params: Edge | Connection) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)), + [], + ); + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index bef5202..21c1bf7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import App from './App.tsx' +import App from './VisualProgrammingUI/VisProgUI.tsx' createRoot(document.getElementById('root')!).render( From 0eb5b65f6752b614200864a18a969156861cf7fc Mon Sep 17 00:00:00 2001 From: JGerla Date: Tue, 30 Sep 2025 14:53:53 +0200 Subject: [PATCH 02/61] feat: Added reconnectable edges Modified edges to support being disconnected and reconnected upon dragging their connection away from the currently connected node. ref: N25B-114 --- src/VisualProgrammingUI/VisProgUI.tsx | 75 +++++++++++++++++---------- src/main.tsx | 4 +- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/VisualProgrammingUI/VisProgUI.tsx b/src/VisualProgrammingUI/VisProgUI.tsx index 2f58a64..40e858d 100644 --- a/src/VisualProgrammingUI/VisProgUI.tsx +++ b/src/VisualProgrammingUI/VisProgUI.tsx @@ -1,14 +1,17 @@ import './VisProgUI.css' -import { useState, useCallback } from 'react'; import { + useCallback, + useRef +} from 'react'; +import { + Background, + Controls, ReactFlow, - applyNodeChanges, - applyEdgeChanges, - addEdge, - type NodeChange, - type EdgeChange, - type Edge, type Connection + useNodesState, + useEdgesState, + reconnectEdge, + addEdge, type Edge, type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; @@ -18,39 +21,55 @@ const initialNodes = [ ]; const initialEdges = [{id: 'n1-n2', source: 'n1', target: 'n2'}]; -export default function App() { - const [nodes, setNodes] = useState(initialNodes); - const [edges, setEdges] = useState(initialEdges); +const VisualProgrammingUI = ()=> { + const edgeReconnectSuccessful = useRef(true); + const [nodes, , onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + - const onNodesChange = useCallback( - (changes: NodeChange<{ - id: string; - position: { x: number; y: number; }; - data: { label: string; }; - }>[]) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)), - [], - ); - const onEdgesChange = useCallback( - (changes: EdgeChange<{ id: string; source: string; target: string; }>[]) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)), - [], - ); - const onConnect = useCallback( - (params: Edge | Connection) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)), - [], + (params: Edge | Connection) => setEdges((els) => addEdge(params, els)), + [setEdges], ); + const onReconnectStart = useCallback(() => { + edgeReconnectSuccessful.current = false; + }, []); + + const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => { + edgeReconnectSuccessful.current = true; + setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); + }, [setEdges]); + + const onReconnectEnd = useCallback((_: unknown, edge: { id: string; }) => { + if (!edgeReconnectSuccessful.current) { + setEdges((eds) => eds.filter((e) => e.id !== edge.id)); + } + + edgeReconnectSuccessful.current = true; + }, [setEdges]); + return ( -
+
+ attributionPosition="top-right" + > + + +
); -} \ No newline at end of file +}; + +export default VisualProgrammingUI \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 21c1bf7..9d67972 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,12 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import App from './VisualProgrammingUI/VisProgUI.tsx' +import App from './App.tsx' +import VisualProgrammingUI from "./VisualProgrammingUI/VisProgUI.tsx"; createRoot(document.getElementById('root')!).render( + , ) From 10e5db057b6caa5a3c5bf0cad56eba3519b879b5 Mon Sep 17 00:00:00 2001 From: JGerla Date: Tue, 30 Sep 2025 15:20:16 +0200 Subject: [PATCH 03/61] feat: updated styles of visProgUI Added a rounded outline to the editor and changed the edge to be an arrow so program-flow can be interpreted more easily from the UI. ref: N25B-114 --- src/VisualProgrammingUI/VisProgUI.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/VisualProgrammingUI/VisProgUI.tsx b/src/VisualProgrammingUI/VisProgUI.tsx index 40e858d..0df6a4c 100644 --- a/src/VisualProgrammingUI/VisProgUI.tsx +++ b/src/VisualProgrammingUI/VisProgUI.tsx @@ -11,16 +11,28 @@ import { useNodesState, useEdgesState, reconnectEdge, - addEdge, type Edge, type Connection, + addEdge, + MarkerType, + type Edge, + type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; + const initialNodes = [ - {id: 'n1', position: {x: 0, y: 0}, data: {label: 'Node 1'}}, - {id: 'n2', position: {x: 0, y: 100}, data: {label: 'Node 2'}}, + {id: 'n1', position: {x: 0, y: 0}, data: {label: 'Start'}}, + {id: 'n2', position: {x: 0, y: 100}, data: {label: 'End'}}, ]; const initialEdges = [{id: 'n1-n2', source: 'n1', target: 'n2'}]; +const defaultEdgeOptions = { + type: 'floating', + markerEnd: { + type: MarkerType.ArrowClosed, + color: '#505050', + }, +}; + const VisualProgrammingUI = ()=> { const edgeReconnectSuccessful = useRef(true); const [nodes, , onNodesChange] = useNodesState(initialNodes); @@ -51,10 +63,11 @@ const VisualProgrammingUI = ()=> { }, [setEdges]); return ( -
+
{ onReconnectEnd={onReconnectEnd} onConnect={onConnect} fitView - attributionPosition="top-right" + proOptions={{hideAttribution: true }} > From e098ffebd6c3d2ec8c819d7cd6a41d137eb6c928 Mon Sep 17 00:00:00 2001 From: JGerla Date: Tue, 30 Sep 2025 16:13:48 +0200 Subject: [PATCH 04/61] feat: added a custom start node Defined a basic start node; it does not contain any further functionality but does provide a basis for implementing future custom nodes. ref: N25B-114 --- src/main.tsx | 2 +- .../VisProgUI.css | 0 .../VisProgUI.tsx | 27 ++++++++++++++++--- .../components/StartNode.tsx | 15 +++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) rename src/{VisualProgrammingUI => visualProgrammingUI}/VisProgUI.css (100%) rename src/{VisualProgrammingUI => visualProgrammingUI}/VisProgUI.tsx (79%) create mode 100644 src/visualProgrammingUI/components/StartNode.tsx diff --git a/src/main.tsx b/src/main.tsx index 9d67972..c2e7460 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,7 +2,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' -import VisualProgrammingUI from "./VisualProgrammingUI/VisProgUI.tsx"; +import VisualProgrammingUI from "./visualProgrammingUI/VisProgUI.tsx"; createRoot(document.getElementById('root')!).render( diff --git a/src/VisualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css similarity index 100% rename from src/VisualProgrammingUI/VisProgUI.css rename to src/visualProgrammingUI/VisProgUI.css diff --git a/src/VisualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx similarity index 79% rename from src/VisualProgrammingUI/VisProgUI.tsx rename to src/visualProgrammingUI/VisProgUI.tsx index 0df6a4c..d71af49 100644 --- a/src/VisualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -17,13 +17,33 @@ import { type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; +import StartNode from './components/StartNode.tsx'; +const nodeTypes = { + startNode: StartNode, +}; const initialNodes = [ - {id: 'n1', position: {x: 0, y: 0}, data: {label: 'Start'}}, - {id: 'n2', position: {x: 0, y: 100}, data: {label: 'End'}}, + { + id: 'start', + type: 'startNode', + position: {x: 0, y: 0}, + data: {label: 'Start'}, + }, + { + id: 'genericPhase', + type: 'default', + position: {x: 0, y: 150}, + data: {label: 'Generic Phase'}, + }, + { + id: 'end', + type: 'output', + position: {x: 0, y: 300}, + data: {label: 'End'} + } ]; -const initialEdges = [{id: 'n1-n2', source: 'n1', target: 'n2'}]; +const initialEdges = [{id: 'start-end', source: 'start', target: 'end'}]; const defaultEdgeOptions = { type: 'floating', @@ -70,6 +90,7 @@ const VisualProgrammingUI = ()=> { defaultEdgeOptions={defaultEdgeOptions} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} + nodeTypes={nodeTypes} snapToGrid onReconnect={onReconnect} onReconnectStart={onReconnectStart} diff --git a/src/visualProgrammingUI/components/StartNode.tsx b/src/visualProgrammingUI/components/StartNode.tsx new file mode 100644 index 0000000..d92ede1 --- /dev/null +++ b/src/visualProgrammingUI/components/StartNode.tsx @@ -0,0 +1,15 @@ +import { Handle, Position } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; + +// @ts-ignore +export const StartNode = ({ data }) => { + return ( + <> +
+
data test {data.label}
+ +
+ + ); +}; +export default StartNode; \ No newline at end of file From b991e92c37bce47faa9e1dc43e191ab48cef7157 Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 10:38:35 +0200 Subject: [PATCH 05/61] feat: changed startNode to use custom type for data changed to startNode file to be a general file for custom node types, and created a custom type for the data property of StartNode. BREAKING: renamed StartNode.tsx to NodeDefinitions.tsx ref: N25B-114 --- src/visualProgrammingUI/VisProgUI.css | 7 +++++++ src/visualProgrammingUI/VisProgUI.tsx | 4 ++-- .../{StartNode.tsx => NodeDefinitions.tsx} | 13 ++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) rename src/visualProgrammingUI/components/{StartNode.tsx => NodeDefinitions.tsx} (51%) diff --git a/src/visualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css index e69de29..35645a7 100644 --- a/src/visualProgrammingUI/VisProgUI.css +++ b/src/visualProgrammingUI/VisProgUI.css @@ -0,0 +1,7 @@ +.default-node { + padding: 10px 20px; + background-color: white; + outline-style: solid; + border-radius: 5pt; + outline-width: 1pt; +} \ No newline at end of file diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index d71af49..ae526c6 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -17,7 +17,7 @@ import { type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import StartNode from './components/StartNode.tsx'; +import StartNode from "./components/NodeDefinitions.tsx"; const nodeTypes = { startNode: StartNode, @@ -28,7 +28,7 @@ const initialNodes = [ id: 'start', type: 'startNode', position: {x: 0, y: 0}, - data: {label: 'Start'}, + data: {label: 'start'} }, { id: 'genericPhase', diff --git a/src/visualProgrammingUI/components/StartNode.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx similarity index 51% rename from src/visualProgrammingUI/components/StartNode.tsx rename to src/visualProgrammingUI/components/NodeDefinitions.tsx index d92ede1..e9fc0b9 100644 --- a/src/visualProgrammingUI/components/StartNode.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -1,12 +1,19 @@ import { Handle, Position } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; +import '../VisProgUI.css'; -// @ts-ignore -export const StartNode = ({ data }) => { +// Datatypes for NodeTypes + +type startNodeData = { label: string; }; + +// Definitions of Nodes + + +export const StartNode= ({ data } : {data : startNodeData}) => { return ( <>
-
data test {data.label}
+
data test {data.label}
From 54b5935829974f19ef95325b87610524a8234a46 Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 10:55:24 +0200 Subject: [PATCH 06/61] feat: created new nodes and a default nodeType added a type for defaultNodeData, this can house common data that all nodes should have. the other types can build on this defaultData. Also added an endNode and phaseNode to NodeDefinitions.tsx, together with a nodeData type for each new node type. ref: N25B-114 --- src/visualProgrammingUI/VisProgUI.tsx | 18 ++++++--- .../components/NodeDefinitions.tsx | 37 ++++++++++++++++++- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index ae526c6..48c987f 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -17,28 +17,34 @@ import { type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import StartNode from "./components/NodeDefinitions.tsx"; +import { + StartNode, + EndNode, + PhaseNode +} from "./components/NodeDefinitions.tsx"; const nodeTypes = { - startNode: StartNode, + start: StartNode, + end: EndNode, + phase: PhaseNode, }; const initialNodes = [ { id: 'start', - type: 'startNode', + type: 'start', position: {x: 0, y: 0}, data: {label: 'start'} }, { id: 'genericPhase', - type: 'default', + type: 'phase', position: {x: 0, y: 150}, - data: {label: 'Generic Phase'}, + data: {label: 'Generic Phase', number: 1}, }, { id: 'end', - type: 'output', + type: 'end', position: {x: 0, y: 300}, data: {label: 'End'} } diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx index e9fc0b9..c3c011b 100644 --- a/src/visualProgrammingUI/components/NodeDefinitions.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -4,7 +4,17 @@ import '../VisProgUI.css'; // Datatypes for NodeTypes -type startNodeData = { label: string; }; +type defaultNodeData = { + label: string; +}; + +type startNodeData = defaultNodeData; +type endNodeData = defaultNodeData; +type phaseNodeData = defaultNodeData & { + number: number; +}; + +export type nodeData = defaultNodeData | startNodeData | phaseNodeData | endNodeData; // Definitions of Nodes @@ -19,4 +29,27 @@ export const StartNode= ({ data } : {data : startNodeData}) => { ); }; -export default StartNode; \ No newline at end of file + + +export const EndNode= ({ data } : {data : endNodeData}) => { + return ( + <> +
+
{data.label}
+ +
+ + ); +}; + +export const PhaseNode= ({ data } : {data : phaseNodeData}) => { + return ( + <> +
+
phase {data.number} {data.label}
+ + +
+ + ); +}; From 79f0827b39f9acb100276e59d2d54370618a4bbb Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 10:55:24 +0200 Subject: [PATCH 07/61] feat: created new nodes and a default nodeType added a type for defaultNodeData, this can house common data that all nodes should have. the other types can build on this defaultData. Also added an endNode and phaseNode to NodeDefinitions.tsx, together with a nodeData type for each new node type. ref: N25B-114 --- src/visualProgrammingUI/VisProgUI.tsx | 18 +++++--- .../components/NodeDefinitions.tsx | 41 +++++++++++++++++-- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index ae526c6..48c987f 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -17,28 +17,34 @@ import { type Connection, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import StartNode from "./components/NodeDefinitions.tsx"; +import { + StartNode, + EndNode, + PhaseNode +} from "./components/NodeDefinitions.tsx"; const nodeTypes = { - startNode: StartNode, + start: StartNode, + end: EndNode, + phase: PhaseNode, }; const initialNodes = [ { id: 'start', - type: 'startNode', + type: 'start', position: {x: 0, y: 0}, data: {label: 'start'} }, { id: 'genericPhase', - type: 'default', + type: 'phase', position: {x: 0, y: 150}, - data: {label: 'Generic Phase'}, + data: {label: 'Generic Phase', number: 1}, }, { id: 'end', - type: 'output', + type: 'end', position: {x: 0, y: 300}, data: {label: 'End'} } diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx index e9fc0b9..99ad1da 100644 --- a/src/visualProgrammingUI/components/NodeDefinitions.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -4,7 +4,17 @@ import '../VisProgUI.css'; // Datatypes for NodeTypes -type startNodeData = { label: string; }; +type defaultNodeData = { + label: string; +}; + +type startNodeData = defaultNodeData; +type endNodeData = defaultNodeData; +type phaseNodeData = defaultNodeData & { + number: number; +}; + +export type nodeData = defaultNodeData | startNodeData | phaseNodeData | endNodeData; // Definitions of Nodes @@ -12,11 +22,34 @@ type startNodeData = { label: string; }; export const StartNode= ({ data } : {data : startNodeData}) => { return ( <> -
-
data test {data.label}
+
+
data test {data.label}
); }; -export default StartNode; \ No newline at end of file + + +export const EndNode= ({ data } : {data : endNodeData}) => { + return ( + <> +
+
{data.label}
+ +
+ + ); +}; + +export const PhaseNode= ({ data } : {data : phaseNodeData}) => { + return ( + <> +
+
phase {data.number} {data.label}
+ + +
+ + ); +}; From 9df46c90a379ab8b6fa77836610d89c93661b01a Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 13:29:32 +0200 Subject: [PATCH 08/61] feat: added drag and drop menu for adding new nodes to flow added a sidebar that supports drag and dropping new nodes from the sidebar into the flow editor. also added a new package (neodrag) for easy draggable behavior outside the reactFlow editor. ref: N25B-114 --- package-lock.json | 7 ++ package.json | 1 + src/visualProgrammingUI/VisProgUI.tsx | 59 ++++++---- .../components/DragDropSidebar.tsx | 104 ++++++++++++++++++ 4 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 src/visualProgrammingUI/components/DragDropSidebar.tsx diff --git a/package-lock.json b/package-lock.json index c20c730..81c5e48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "pepperplus-ui", "version": "0.0.0", "dependencies": { + "@neodrag/react": "^2.3.1", "@xyflow/react": "^12.8.6", "react": "^19.1.1", "react-dom": "^19.1.1" @@ -1006,6 +1007,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@neodrag/react": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@neodrag/react/-/react-2.3.1.tgz", + "integrity": "sha512-mOVefo3mFmaVLs9PB5F5wMXnnclG81qjOaPHyf8YZUnw/Ciz0pAqyJDwDJk0nPTIK5I2x1JdjXSchGNdCxZNRQ==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 2757ba3..cbea372 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@neodrag/react": "^2.3.1", "@xyflow/react": "^12.8.6", "react": "^19.1.1", "react-dom": "^19.1.1" diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index 48c987f..55c5a85 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -8,6 +8,7 @@ import { Background, Controls, ReactFlow, + ReactFlowProvider, useNodesState, useEdgesState, reconnectEdge, @@ -23,6 +24,8 @@ import { PhaseNode } from "./components/NodeDefinitions.tsx"; +import { Sidebar } from './components/DragDropSidebar.tsx'; + const nodeTypes = { start: StartNode, end: EndNode, @@ -59,7 +62,7 @@ const defaultEdgeOptions = { }, }; -const VisualProgrammingUI = ()=> { +const VisProgUI = ()=> { const edgeReconnectSuccessful = useRef(true); const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); @@ -89,27 +92,41 @@ const VisualProgrammingUI = ()=> { }, [setEdges]); return ( -
- - - - +
+
+ + + + +
+
+ +
+ ); }; -export default VisualProgrammingUI \ No newline at end of file +function VisualProgrammingUI(){ + return ( + + + + ); +} + +export default VisualProgrammingUI; \ No newline at end of file diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx new file mode 100644 index 0000000..9c1e3dd --- /dev/null +++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx @@ -0,0 +1,104 @@ +import { useDraggable } from '@neodrag/react'; +import { + useReactFlow, + type XYPosition +} from '@xyflow/react'; +import { + type ReactNode, + useCallback, + useRef, + useState +} from 'react'; + + +// improve later to create better automatic IDs +let id = 0; +const getId = () => `dndnode_${id++}`; + + +interface DraggableNodeProps { + className?: string; + children: ReactNode; + nodeType: string; + onDrop: (nodeType: string, position: XYPosition) => void; +} + +function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) { + const draggableRef = useRef(null); + const [position, setPosition] = useState({ x: 0, y: 0 }); + + + // @ts-ignore + useDraggable(draggableRef, { + position: position, + onDrag: ({ offsetX, offsetY }) => { + // Calculate position relative to the viewport + setPosition({ + x: offsetX, + y: offsetY, + }); + }, + onDragEnd: ({ event }) => { + setPosition({ x: 0, y: 0 }); + onDrop(nodeType, { + x: event.clientX, + y: event.clientY, + }); + }, + }); + + return ( +
+ {children} +
+ ); +} + +export function Sidebar() { + const { setNodes, screenToFlowPosition } = useReactFlow(); + + const handleNodeDrop = useCallback( + (nodeType: string, screenPosition: XYPosition) => { + const flow = document.querySelector('.react-flow'); + const flowRect = flow?.getBoundingClientRect(); + const isInFlow = + flowRect && + screenPosition.x >= flowRect.left && + screenPosition.x <= flowRect.right && + screenPosition.y >= flowRect.top && + screenPosition.y <= flowRect.bottom; + + // Create a new node and add it to the flow + if (isInFlow) { + const position = screenToFlowPosition(screenPosition); + + const newNode = { + id: getId(), + type: nodeType, + position, + data: { label: `${nodeType} node` }, + }; + + setNodes((nds) => nds.concat(newNode)); + } + }, + [setNodes, screenToFlowPosition], + ); + + return ( + + ); +} \ No newline at end of file From affdc0c3cdd7e35be5c99d5d9c5756c063f5583d Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 13:45:20 +0200 Subject: [PATCH 09/61] feat: modified DnD sidebar to provide different node types Modified the drag and drop sidebar to create a node of the correct type instead of creating only default nodes, regardless of specified node types for a respective option. ref: N25B-114 --- .../components/DragDropSidebar.tsx | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx index 9c1e3dd..219e3a2 100644 --- a/src/visualProgrammingUI/components/DragDropSidebar.tsx +++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx @@ -72,14 +72,41 @@ export function Sidebar() { if (isInFlow) { const position = screenToFlowPosition(screenPosition); - const newNode = { - id: getId(), - type: nodeType, - position, - data: { label: `${nodeType} node` }, - }; + const newNode = () => { + switch (nodeType) { + case "phase": + return { + id: getId(), + type: nodeType, + position, + data: {label: `"new"`, number: (-1)}, + }; + case "start": + return { + id: getId(), + type: nodeType, + position, + data: {label: `new start node`}, + }; + case "end": + return { + id: getId(), + type: nodeType, + position, + data: {label: `new end node`}, + }; + default: { + return { + id: getId(), + type: nodeType, + position, + data: {label: `new default node`}, + }; + } + } + } - setNodes((nds) => nds.concat(newNode)); + setNodes((nds) => nds.concat(newNode())); } }, [setNodes, screenToFlowPosition], From b1b1c83d73a35f9531e67d5166c6a9bdd834c7da Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 14:22:31 +0200 Subject: [PATCH 10/61] feat: added VisProgUI to a page ref: N25B-114 --- src/App.tsx | 5 +++-- src/pages/Home/Home.tsx | 3 +-- src/pages/TemplatePage/Template.tsx | 3 +-- src/pages/VisProgPage/VisProg.module.css | 4 ++++ src/pages/VisProgPage/VisProg.tsx | 23 +++++++++++++++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 src/pages/VisProgPage/VisProg.module.css create mode 100644 src/pages/VisProgPage/VisProg.tsx diff --git a/src/App.tsx b/src/App.tsx index fe7cb61..d8d5cb2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,15 @@ import { Routes, Route } from 'react-router' import './App.css' -import TemplatePage from './pages/TemplatePage/Template.tsx' +import VisProg from './pages/VisProgPage/VisProg.tsx' import Home from './pages/Home/Home.tsx' + function App(){ return ( } /> - } /> + } /> ) } diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 5c4a72d..956506c 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react' import { Link } from 'react-router' import reactLogo from '../../assets/react.svg' import viteLogo from '../../assets/vite.svg' @@ -29,7 +28,7 @@ function Home() {

Vite + React

- + diff --git a/src/pages/TemplatePage/Template.tsx b/src/pages/TemplatePage/Template.tsx index 4cb3118..0f08c25 100644 --- a/src/pages/TemplatePage/Template.tsx +++ b/src/pages/TemplatePage/Template.tsx @@ -1,7 +1,6 @@ -import { useState } from 'react' + import { Link } from 'react-router' import Counter from '../../components/components.tsx' -import style from './Template.module.css' //this is your css file where you can style your buttons and such //you can still use css parts from App.css, but also overwrite them diff --git a/src/pages/VisProgPage/VisProg.module.css b/src/pages/VisProgPage/VisProg.module.css new file mode 100644 index 0000000..8526661 --- /dev/null +++ b/src/pages/VisProgPage/VisProg.module.css @@ -0,0 +1,4 @@ +button.reset:hover { + background-color: yellow; +} + diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx new file mode 100644 index 0000000..953e3f8 --- /dev/null +++ b/src/pages/VisProgPage/VisProg.tsx @@ -0,0 +1,23 @@ +import { Link } from 'react-router' +import VisProgUI from "../../visualProgrammingUI/VisProgUI.tsx"; + + +//this is your css file where you can style your buttons and such +//you can still use css parts from App.css, but also overwrite them + +function VisProgPage() { + + + return ( + <> + + {/* here you link to the homepage, in App.tsx you can link new pages */} + + + + ) +} + +export default VisProgPage \ No newline at end of file From f9f5a71c474466ff2e8f4ce7972105a4fede31ab Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 15:51:24 +0200 Subject: [PATCH 11/61] feat: added delete option to Nodes added a delete option to nodes, nodes now have strongly typed nodeProps to conform to typescript type safety norms. ref: N25B-114 --- .../components/NodeDefinitions.tsx | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx index 99ad1da..7c712b3 100644 --- a/src/visualProgrammingUI/components/NodeDefinitions.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -1,4 +1,4 @@ -import { Handle, Position } from '@xyflow/react'; +import {Handle, NodeToolbar, Position, useReactFlow} from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import '../VisProgUI.css'; @@ -16,12 +16,37 @@ type phaseNodeData = defaultNodeData & { export type nodeData = defaultNodeData | startNodeData | phaseNodeData | endNodeData; +// Node Toolbar definition + +type ToolbarProps= { + nodeId: string; +}; + +export function Toolbar({nodeId}:ToolbarProps) { + const { setNodes, setEdges } = useReactFlow(); + + const handleDelete = () => { + setNodes((nds) => nds.filter((n) => n.id !== nodeId)); + setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId)); + }; + return ( + + + ); +} + + // Definitions of Nodes +type StartNodeProps = { + id: string; + data: startNodeData; +}; -export const StartNode= ({ data } : {data : startNodeData}) => { +export const StartNode= ({ id, data }: StartNodeProps) => { return ( <> +
data test {data.label}
@@ -30,10 +55,15 @@ export const StartNode= ({ data } : {data : startNodeData}) => { ); }; +type EndNodeProps = { + id: string; + data: endNodeData; +}; -export const EndNode= ({ data } : {data : endNodeData}) => { +export const EndNode= ({ id, data }: EndNodeProps) => { return ( <> +
{data.label}
@@ -42,9 +72,16 @@ export const EndNode= ({ data } : {data : endNodeData}) => { ); }; -export const PhaseNode= ({ data } : {data : phaseNodeData}) => { + +type PhaseNodeProps = { + id: string; + data: phaseNodeData; +}; + +export const PhaseNode= ({ id, data }: PhaseNodeProps) => { return ( <> +
phase {data.number} {data.label}
@@ -53,3 +90,4 @@ export const PhaseNode= ({ data } : {data : phaseNodeData}) => { ); }; + From 6c22286b28d64d87c1c6f5bdd2d7efbf48862cbf Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 15:51:24 +0200 Subject: [PATCH 12/61] feat: added delete option to Nodes added a delete option to nodes, nodes now have strongly typed nodeProps to conform to typescript type safety norms. ref: N25B-114 --- .../components/NodeDefinitions.tsx | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx index 99ad1da..7463e84 100644 --- a/src/visualProgrammingUI/components/NodeDefinitions.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -1,7 +1,8 @@ -import { Handle, Position } from '@xyflow/react'; +import {Handle, NodeToolbar, Position, useReactFlow} from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import '../VisProgUI.css'; + // Datatypes for NodeTypes type defaultNodeData = { @@ -16,12 +17,38 @@ type phaseNodeData = defaultNodeData & { export type nodeData = defaultNodeData | startNodeData | phaseNodeData | endNodeData; + +// Node Toolbar definition + +type ToolbarProps= { + nodeId: string; +}; + +export function Toolbar({nodeId}:ToolbarProps) { + const { setNodes, setEdges } = useReactFlow(); + + const handleDelete = () => { + setNodes((nds) => nds.filter((n) => n.id !== nodeId)); + setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId)); + }; + return ( + + + ); +} + + // Definitions of Nodes +type StartNodeProps = { + id: string; + data: startNodeData; +}; -export const StartNode= ({ data } : {data : startNodeData}) => { +export const StartNode= ({ id, data }: StartNodeProps) => { return ( <> +
data test {data.label}
@@ -30,10 +57,15 @@ export const StartNode= ({ data } : {data : startNodeData}) => { ); }; +type EndNodeProps = { + id: string; + data: endNodeData; +}; -export const EndNode= ({ data } : {data : endNodeData}) => { +export const EndNode= ({ id, data }: EndNodeProps) => { return ( <> +
{data.label}
@@ -42,9 +74,16 @@ export const EndNode= ({ data } : {data : endNodeData}) => { ); }; -export const PhaseNode= ({ data } : {data : phaseNodeData}) => { + +type PhaseNodeProps = { + id: string; + data: phaseNodeData; +}; + +export const PhaseNode= ({ id, data }: PhaseNodeProps) => { return ( <> +
phase {data.number} {data.label}
@@ -53,3 +92,4 @@ export const PhaseNode= ({ data } : {data : phaseNodeData}) => { ); }; + From 77808784473e07a483e9a6b146ad0ec0ace65a42 Mon Sep 17 00:00:00 2001 From: JGerla Date: Wed, 1 Oct 2025 17:13:55 +0200 Subject: [PATCH 13/61] feat: added Norm nodes and an extra handle on phase nodes ref: N25B-114 --- src/visualProgrammingUI/VisProgUI.tsx | 6 +++--- .../components/DragDropSidebar.tsx | 10 ++++++++++ .../components/NodeDefinitions.tsx | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index 55c5a85..a2b1e9c 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -21,7 +21,8 @@ import '@xyflow/react/dist/style.css'; import { StartNode, EndNode, - PhaseNode + PhaseNode, + NormNode } from "./components/NodeDefinitions.tsx"; import { Sidebar } from './components/DragDropSidebar.tsx'; @@ -30,6 +31,7 @@ const nodeTypes = { start: StartNode, end: EndNode, phase: PhaseNode, + norm: NormNode }; const initialNodes = [ @@ -67,8 +69,6 @@ const VisProgUI = ()=> { const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - - const onConnect = useCallback( (params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges], diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx index 219e3a2..b3926d9 100644 --- a/src/visualProgrammingUI/components/DragDropSidebar.tsx +++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx @@ -95,6 +95,13 @@ export function Sidebar() { position, data: {label: `new end node`}, }; + case "norm": + return { + id: getId(), + type: nodeType, + position, + data: {label: `new norm node`}, + }; default: { return { id: getId(), @@ -126,6 +133,9 @@ export function Sidebar() { phase Node + + norm Node + ); } \ No newline at end of file diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx index 7c712b3..b4547b2 100644 --- a/src/visualProgrammingUI/components/NodeDefinitions.tsx +++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx @@ -10,6 +10,7 @@ type defaultNodeData = { type startNodeData = defaultNodeData; type endNodeData = defaultNodeData; +type normNodeData = defaultNodeData; type phaseNodeData = defaultNodeData & { number: number; }; @@ -85,9 +86,26 @@ export const PhaseNode= ({ id, data }: PhaseNodeProps) => {
phase {data.number} {data.label}
+
); }; +type NormNodeProps = { + id: string; + data: normNodeData; +}; + +export const NormNode= ({ id, data }: NormNodeProps) => { + return ( + <> + +
+
Norm {data.label}
+ +
+ + ); +}; \ No newline at end of file From 70ebb16359f25999b77fbe83e4c6f89d7d266ca7 Mon Sep 17 00:00:00 2001 From: JGerla Date: Thu, 2 Oct 2025 12:43:44 +0200 Subject: [PATCH 14/61] feat: modified css styling for nodes to have different colors per type updated the nodes to have a different colour per NodeType, so it is easier to see what nodes are of what type in the graph. ref: N25B-114 --- src/visualProgrammingUI/VisProgUI.css | 38 +++++++++++++++++-- .../components/DragDropSidebar.tsx | 10 ++--- .../components/NodeDefinitions.tsx | 8 ++-- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/visualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css index 35645a7..a9727c2 100644 --- a/src/visualProgrammingUI/VisProgUI.css +++ b/src/visualProgrammingUI/VisProgUI.css @@ -1,7 +1,39 @@ .default-node { - padding: 10px 20px; + padding: 10px 15px; background-color: white; - outline-style: solid; border-radius: 5pt; - outline-width: 1pt; + outline: black solid 2pt; + filter: drop-shadow(0 0 0.75rem black); +} + +.default-node__norm { + padding: 10px 15px; + background-color: white; + border-radius: 5pt; + outline: forestgreen solid 2pt; + filter: drop-shadow(0 0 0.25rem forestgreen); +} + +.default-node__phase { + padding: 10px 15px; + background-color: white; + border-radius: 5pt; + outline: dodgerblue solid 2pt; + filter: drop-shadow(0 0 0.25rem dodgerblue); +} + +.default-node__start { + padding: 10px 15px; + background-color: white; + border-radius: 5pt; + outline: orange solid 2pt; + filter: drop-shadow(0 0 0.25rem orange); +} + +.default-node__end { + padding: 10px 15px; + background-color: white; + border-radius: 5pt; + outline: red solid 2pt; + filter: drop-shadow(0 0 0.25rem red); } \ No newline at end of file diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx index b3926d9..08ce022 100644 --- a/src/visualProgrammingUI/components/DragDropSidebar.tsx +++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx @@ -120,20 +120,20 @@ export function Sidebar() { ); return ( -