From 10522b71c368301a6b8949def72214dfbb6b7b4b Mon Sep 17 00:00:00 2001
From: Twirre Meulenbelt <43213592+TwirreM@users.noreply.github.com>
Date: Wed, 1 Oct 2025 22:56:03 +0200
Subject: [PATCH 01/14] chore: combined some branches, improved style
This demo branch contains code from multiple different branches. DO NOT MERGE this branch because it looks like I'm the author of all these changes.
---
package-lock.json | 241 +++++++++++-
package.json | 2 +
src/App.css | 109 ++++--
src/App.tsx | 20 +-
src/assets/data.ts | 361 ++++++++++++++++++
src/pages/Home/Home.module.css | 6 +
src/pages/Home/Home.tsx | 45 +--
src/pages/Logging/Logging.module.css | 17 +
src/pages/Logging/Logging.tsx | 78 ++++
src/pages/ServerComms/ServerComms.css | 0
src/pages/ServerComms/ServerComms.tsx | 86 +++--
src/pages/VisProgPage/VisProg.tsx | 11 +
src/visualProgrammingUI/VisProgUI.css | 7 +
src/visualProgrammingUI/VisProgUI.tsx | 132 +++++++
.../components/DragDropSidebar.tsx | 141 +++++++
.../components/NodeDefinitions.tsx | 111 ++++++
16 files changed, 1251 insertions(+), 116 deletions(-)
create mode 100644 src/assets/data.ts
create mode 100644 src/pages/Logging/Logging.module.css
create mode 100644 src/pages/Logging/Logging.tsx
delete mode 100644 src/pages/ServerComms/ServerComms.css
create mode 100644 src/pages/VisProgPage/VisProg.tsx
create mode 100644 src/visualProgrammingUI/VisProgUI.css
create mode 100644 src/visualProgrammingUI/VisProgUI.tsx
create mode 100644 src/visualProgrammingUI/components/DragDropSidebar.tsx
create mode 100644 src/visualProgrammingUI/components/NodeDefinitions.tsx
diff --git a/package-lock.json b/package-lock.json
index 0b92f5c..db54d49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
"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",
"react-router": "^7.9.3"
@@ -1006,6 +1008,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",
@@ -1404,6 +1412,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",
@@ -1422,7 +1479,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"
@@ -1730,6 +1787,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",
@@ -1916,6 +2005,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",
@@ -1978,9 +2073,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",
@@ -3287,6 +3487,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",
@@ -3438,6 +3647,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 6070bd8..252cf4f 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "@neodrag/react": "^2.3.1",
+ "@xyflow/react": "^12.8.6",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router": "^7.9.3"
diff --git a/src/App.css b/src/App.css
index dcb46cf..0c641e7 100644
--- a/src/App.css
+++ b/src/App.css
@@ -5,18 +5,6 @@
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);
-}
.logopepper {
@@ -32,27 +20,21 @@
filter: drop-shadow(0 0 10em #4eff14aa);
}
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-
@keyframes logo-pepper-spin {
- from {
- transform: rotate(-20deg);
+ 0% {
+ transform: rotate(0);
}
- to {
+ 25% {
transform: rotate(20deg);
}
+ 75% {
+ transform: rotate(-20deg);
+ }
+ 100% {
+ transform: rotate(0);
+ }
}
-
-
@keyframes logo-pepper-scale {
from {
transform: scale(1,1);
@@ -63,19 +45,13 @@
}
@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
+ .logopepper:hover {
+ animation: logo-pepper-spin infinite 1s linear;
}
}
@media (prefers-reduced-motion: no-preference) {
- .logopepper {
- animation: logo-pepper-spin infinite 1s linear alternate;
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .logoPepperScaling {
+ .logoPepperScaling:hover {
animation: logo-pepper-scale infinite 1s linear alternate;
}
}
@@ -113,3 +89,64 @@ button.movePage.right{
button.movePage:hover{
background-color: rgb(0, 176, 176);
}
+
+
+
+
+
+.flex-row {
+ display: flex;
+ flex-direction: row;
+}
+.flex-col {
+ display: flex;
+ flex-direction: column;
+}
+
+.flex-1 {
+ flex: 1;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.align-center {
+ align-items: center;
+}
+.justify-center {
+ justify-content: center;
+}
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-sm {
+ gap: .25rem;
+}
+.gap-md {
+ gap: .5rem;
+}
+.gap-lg {
+ gap: 1rem;
+}
+
+.padding-sm {
+ padding: .25rem;
+}
+.padding-md {
+ padding: .5rem;
+}
+.padding-lg {
+ padding: 1rem;
+}
+
+.round-sm {
+ border-radius: .25rem;
+}
+.round-md {
+ border-radius: .5rem;
+}
+.round-lg {
+ border-radius: 1rem;
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 757e769..0833bdb 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,25 @@
-import { Routes, Route } from 'react-router'
+import { Routes, Route, Link } from 'react-router'
import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import ServerComms from './pages/ServerComms/ServerComms.tsx'
+import Logging from './pages/Logging/Logging.tsx'
+import VisProg from "./pages/VisProgPage/VisProg.tsx";
function App(){
return (
-
- } />
- } />
- } />
-
+
+ {/* Should not use inline styles like this */}
+ Home
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
)
}
diff --git a/src/assets/data.ts b/src/assets/data.ts
new file mode 100644
index 0000000..c1eacfb
--- /dev/null
+++ b/src/assets/data.ts
@@ -0,0 +1,361 @@
+export const DATA: LogEntry[] = [
+ {
+ id: "1",
+ timestamp: "2025-10-01T12:00:00Z",
+ level: "info",
+ msg: "User said: Hello, Pepper!",
+ type: "speech",
+ },
+ {
+ id: "2",
+ timestamp: "2025-10-01T12:00:05Z",
+ level: "debug",
+ msg: "Proximity sensor value: 0.85",
+ type: "sensor",
+ },
+ {
+ id: "3",
+ timestamp: "2025-10-01T12:00:10Z",
+ level: "warn",
+ msg: "Battery level low: 15%",
+ type: "system",
+ },
+ {
+ id: "4",
+ timestamp: "2025-10-01T12:00:15Z",
+ level: "info",
+ msg: "User requested weather update.",
+ type: "speech",
+ },
+ {
+ id: "5",
+ timestamp: "2025-10-01T12:00:20Z",
+ level: "debug",
+ msg: "Microphone activated.",
+ type: "system",
+ },
+ {
+ id: "6",
+ timestamp: "2025-10-01T12:00:25Z",
+ level: "warn",
+ msg: "Obstacle detected in front.",
+ type: "sensor",
+ },
+ {
+ id: "7",
+ timestamp: "2025-10-01T12:00:30Z",
+ level: "info",
+ msg: "User said: Thank you!",
+ type: "speech",
+ },
+ {
+ id: "8",
+ timestamp: "2025-10-01T12:00:35Z",
+ level: "debug",
+ msg: "Network latency: 120ms",
+ type: "system",
+ },
+ {
+ id: "9",
+ timestamp: "2025-10-01T12:00:40Z",
+ level: "warn",
+ msg: "High CPU usage detected.",
+ type: "system",
+ },
+ {
+ id: "10",
+ timestamp: "2025-10-01T12:00:45Z",
+ level: "info",
+ msg: "User started a new session.",
+ type: "system",
+ },
+ {
+ id: "11",
+ timestamp: "2025-10-01T12:01:00Z",
+ level: "info",
+ msg: "User asked: What's the weather?",
+ type: "speech",
+ },
+ {
+ id: "12",
+ timestamp: "2025-10-01T12:01:05Z",
+ level: "debug",
+ msg: "Camera initialized.",
+ type: "system",
+ },
+ {
+ id: "13",
+ timestamp: "2025-10-01T12:01:10Z",
+ level: "warn",
+ msg: "Temperature sensor disconnected.",
+ type: "sensor",
+ },
+ {
+ id: "14",
+ timestamp: "2025-10-01T12:01:15Z",
+ level: "info",
+ msg: "User said: Play some music.",
+ type: "speech",
+ },
+ {
+ id: "15",
+ timestamp: "2025-10-01T12:01:20Z",
+ level: "debug",
+ msg: "Audio output device selected: Speaker.",
+ type: "system",
+ },
+ {
+ id: "16",
+ timestamp: "2025-10-01T12:01:25Z",
+ level: "warn",
+ msg: "Low light detected in room.",
+ type: "sensor",
+ },
+ {
+ id: "17",
+ timestamp: "2025-10-01T12:01:30Z",
+ level: "info",
+ msg: "User said: Turn on the lights.",
+ type: "speech",
+ },
+ {
+ id: "18",
+ timestamp: "2025-10-01T12:01:35Z",
+ level: "debug",
+ msg: "Light control signal sent.",
+ type: "system",
+ },
+ {
+ id: "19",
+ timestamp: "2025-10-01T12:01:40Z",
+ level: "warn",
+ msg: "Light bulb not responding.",
+ type: "system",
+ },
+ {
+ id: "20",
+ timestamp: "2025-10-01T12:01:45Z",
+ level: "info",
+ msg: "User said: Good night.",
+ type: "speech",
+ },
+ {
+ id: "21",
+ timestamp: "2025-10-01T12:02:00Z",
+ level: "info",
+ msg: "User asked: What's the time?",
+ type: "speech",
+ },
+ {
+ id: "22",
+ timestamp: "2025-10-01T12:02:05Z",
+ level: "debug",
+ msg: "Time module loaded.",
+ type: "system",
+ },
+ {
+ id: "23",
+ timestamp: "2025-10-01T12:02:10Z",
+ level: "warn",
+ msg: "WiFi signal weak.",
+ type: "system",
+ },
+ {
+ id: "24",
+ timestamp: "2025-10-01T12:02:15Z",
+ level: "info",
+ msg: "User said: Set an alarm for 7 AM.",
+ type: "speech",
+ },
+ {
+ id: "25",
+ timestamp: "2025-10-01T12:02:20Z",
+ level: "debug",
+ msg: "Alarm scheduled for 7:00 AM.",
+ type: "system",
+ },
+ {
+ id: "26",
+ timestamp: "2025-10-01T12:02:25Z",
+ level: "warn",
+ msg: "Alarm module not responding.",
+ type: "system",
+ },
+ {
+ id: "27",
+ timestamp: "2025-10-01T12:02:30Z",
+ level: "info",
+ msg: "User said: Cancel the alarm.",
+ type: "speech",
+ },
+ {
+ id: "28",
+ timestamp: "2025-10-01T12:02:35Z",
+ level: "debug",
+ msg: "Alarm cancellation requested.",
+ type: "system",
+ },
+ {
+ id: "29",
+ timestamp: "2025-10-01T12:02:40Z",
+ level: "warn",
+ msg: "Alarm cancellation failed.",
+ type: "system",
+ },
+ {
+ id: "30",
+ timestamp: "2025-10-01T12:02:45Z",
+ level: "info",
+ msg: "User said: Open the window.",
+ type: "speech",
+ },
+ {
+ id: "31",
+ timestamp: "2025-10-01T12:03:00Z",
+ level: "info",
+ msg: "User asked: What's on my calendar?",
+ type: "speech",
+ },
+ {
+ id: "32",
+ timestamp: "2025-10-01T12:03:05Z",
+ level: "debug",
+ msg: "Calendar module loaded.",
+ type: "system",
+ },
+ {
+ id: "33",
+ timestamp: "2025-10-01T12:03:10Z",
+ level: "warn",
+ msg: "Calendar sync failed.",
+ type: "system",
+ },
+ {
+ id: "34",
+ timestamp: "2025-10-01T12:03:15Z",
+ level: "info",
+ msg: "User said: Remind me to call John.",
+ type: "speech",
+ },
+ {
+ id: "35",
+ timestamp: "2025-10-01T12:03:20Z",
+ level: "debug",
+ msg: "Reminder set for John.",
+ type: "system",
+ },
+ {
+ id: "36",
+ timestamp: "2025-10-01T12:03:25Z",
+ level: "warn",
+ msg: "Reminder module not available.",
+ type: "system",
+ },
+ {
+ id: "37",
+ timestamp: "2025-10-01T12:03:30Z",
+ level: "info",
+ msg: "User said: What's the news?",
+ type: "speech",
+ },
+ {
+ id: "38",
+ timestamp: "2025-10-01T12:03:35Z",
+ level: "debug",
+ msg: "News API request sent.",
+ type: "system",
+ },
+ {
+ id: "39",
+ timestamp: "2025-10-01T12:03:40Z",
+ level: "warn",
+ msg: "News API rate limit reached.",
+ type: "system",
+ },
+ {
+ id: "40",
+ timestamp: "2025-10-01T12:03:45Z",
+ level: "info",
+ msg: "User said: Tell me a joke.",
+ type: "speech",
+ },
+ {
+ id: "41",
+ timestamp: "2025-10-01T12:04:00Z",
+ level: "info",
+ msg: "User asked: What's the temperature?",
+ type: "speech",
+ },
+ {
+ id: "42",
+ timestamp: "2025-10-01T12:04:05Z",
+ level: "debug",
+ msg: "Temperature sensor reading: 22°C.",
+ type: "sensor",
+ },
+ {
+ id: "43",
+ timestamp: "2025-10-01T12:04:10Z",
+ level: "warn",
+ msg: "Temperature sensor calibration needed.",
+ type: "sensor",
+ },
+ {
+ id: "44",
+ timestamp: "2025-10-01T12:04:15Z",
+ level: "info",
+ msg: "User said: Start cleaning.",
+ type: "speech",
+ },
+ {
+ id: "45",
+ timestamp: "2025-10-01T12:04:20Z",
+ level: "debug",
+ msg: "Vacuum motor started.",
+ type: "system",
+ },
+ {
+ id: "46",
+ timestamp: "2025-10-01T12:04:25Z",
+ level: "warn",
+ msg: "Vacuum bin full.",
+ type: "system",
+ },
+ {
+ id: "47",
+ timestamp: "2025-10-01T12:04:30Z",
+ level: "info",
+ msg: "User said: Stop cleaning.",
+ type: "speech",
+ },
+ {
+ id: "48",
+ timestamp: "2025-10-01T12:04:35Z",
+ level: "debug",
+ msg: "Vacuum motor stopped.",
+ type: "system",
+ },
+ {
+ id: "49",
+ timestamp: "2025-10-01T12:04:40Z",
+ level: "warn",
+ msg: "Obstacle detected during cleaning.",
+ type: "sensor",
+ },
+ {
+ id: "50",
+ timestamp: "2025-10-01T12:04:45Z",
+ level: "info",
+ msg: "User said: Goodbye!",
+ type: "speech",
+ },
+];
+
+interface LogEntry {
+ id: string;
+ type?: string;
+ timestamp: string;
+ level: "info" | "debug" | "warn";
+ msg?: string;
+}
+
diff --git a/src/pages/Home/Home.module.css b/src/pages/Home/Home.module.css
index aed0f27..d609fe9 100644
--- a/src/pages/Home/Home.module.css
+++ b/src/pages/Home/Home.module.css
@@ -4,3 +4,9 @@
.card {
padding: 2em;
}
+
+.links {
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+}
\ No newline at end of file
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index 8767db3..582357b 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -1,47 +1,20 @@
-//import { useState } from 'react'
import { Link } from 'react-router'
-import reactLogo from '../../assets/react.svg'
-import viteLogo from '../../assets/vite.svg'
import pepperLogo from '../../assets/pepper_transp2_small.svg'
-import style from './Home.module.css'
-import Counter from '../../components/components.tsx'
+import styles from './Home.module.css'
function Home() {
-
-
-
return (
<>
-
-
-
-
-
-
-
-
+
- Vite + React
-
-
-
- {}}>
- Page Cool --{'>'}
-
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
+
+ Robot interaction →
+ Node editor →
+ Logs →
+
>
)
}
diff --git a/src/pages/Logging/Logging.module.css b/src/pages/Logging/Logging.module.css
new file mode 100644
index 0000000..52191c3
--- /dev/null
+++ b/src/pages/Logging/Logging.module.css
@@ -0,0 +1,17 @@
+.DivToScroll{
+ background-color: color-mix(in srgb, canvas, #000 5%);
+ border: 1px solid color-mix(in srgb, canvas, #000 15%);
+ border-radius: 4px 0 4px 0;
+ color: #3B3C3E;
+ font-size: 12px;
+ font-weight: bold;
+ left: -1px;
+ padding: 10px 7px 5px;
+}
+
+.DivWithScroll{
+ height:50vh;
+ width:100vh;
+ overflow:scroll;
+ overflow-x:hidden;
+}
diff --git a/src/pages/Logging/Logging.tsx b/src/pages/Logging/Logging.tsx
new file mode 100644
index 0000000..31945c1
--- /dev/null
+++ b/src/pages/Logging/Logging.tsx
@@ -0,0 +1,78 @@
+import { useState } from 'react';
+import { DATA } from "../../assets/data";
+import styles from './Logging.module.css';
+
+
+// const dataType = DATA as { id: string; level: "debug"|"info"|"warn"|"error"; msg: string; timestamp?: string };
+type Level = "debug" | "info" | "warn" | "error";
+
+// make optional fields optional
+type LogEntry = {
+ id: string;
+ level: Level;
+ timestamp?: string;
+ msg?: string;
+ type?: "speech" | "sensor" | "system" | string;
+
+};
+
+function getLevelColor(level: Level) {
+ switch (level) {
+ case "debug":
+ return "gray";
+ case "info":
+ return "blue";
+ case "warn":
+ return "red";
+ case "error":
+ return "red";
+ default:
+ return "black";
+ }
+}
+
+function Logging() {
+ const [logs, setLogs] = useState([]);
+
+ const logDiv = (
+
+
+ {logs.map((log) => (
+
+
+ [{log.timestamp}]
+
+
+ {log.msg ? log.msg : "No message"}
+
+
+ ({log.level})
+
+
+ ))}
+
+
+ )
+ return (
+ <>
+ Log Screen
+ { logDiv }
+
+ setLogs(DATA)}>
+ Load sample logs
+
+
+
+ >
+ )
+}
+
+export default Logging
diff --git a/src/pages/ServerComms/ServerComms.css b/src/pages/ServerComms/ServerComms.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pages/ServerComms/ServerComms.tsx b/src/pages/ServerComms/ServerComms.tsx
index c16ec81..6d15524 100644
--- a/src/pages/ServerComms/ServerComms.tsx
+++ b/src/pages/ServerComms/ServerComms.tsx
@@ -1,15 +1,12 @@
-import { useState, useEffect } from 'react'
-import { Link } from 'react-router'
-//import Counter from '../../components/components.tsx'
+import { useState, useEffect, useRef } from 'react'
-
-//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 ServerComms() {
+export default function ServerComms() {
const [message, setMessage] = useState('');
- const [sseMessage, setSseMessage] = useState('');
- const [spoken, setSpoken] = useState("");
+
+ const [listening, setListening] = useState(false);
+ const [conversation, setConversation] = useState<{"role": "user" | "assistant", "content": string}[]>([])
+ const conversationRef = useRef(null);
+ const [conversationIndex, setConversationIndex] = useState(0);
const sendMessage = async () => {
try {
@@ -31,22 +28,31 @@ function ServerComms() {
const eventSource = new EventSource("http://localhost:8000/sse");
eventSource.onmessage = (event) => {
- setSseMessage(event.data);
-
try {
const data = JSON.parse(event.data);
- if (data.speech) setSpoken(data.speech);
- } catch {}
+ if ("voice_active" in data) setListening(data.voice_active);
+ if ("speech" in data) setConversation(conversation => [...conversation, {"role": "user", "content": data.speech}]);
+ if ("llm_response" in data) setConversation(conversation => [...conversation, {"role": "assistant", "content": data.llm_response}]);
+ } catch {
+ console.log("Unparsable SSE message:", event.data);
+ }
};
return () => {
eventSource.close();
};
- }, []);
+ }, [conversationIndex]);
+
+ useEffect(() => {
+ if (!conversationRef || !conversationRef.current) return;
+ conversationRef.current.scrollTop = conversationRef.current.scrollHeight;
+ }, [conversation]);
return (
-
-
+
+
Robot interaction
+
Force robot speech
+
e.key === "Enter" && sendMessage().then(() => setMessage(""))}
placeholder="Enter a message"
/>
- Send Message to Backend
+ Speak
-
-
Message from Server (SSE):
-
{sseMessage}
-
-
-
Spoken text (SSE):
-
{spoken}
-
-
-
{/* here you link to the homepage, in App.tsx you can link new pages */}
-
{}}>
- Page {'<'}-- Go Home
-
-
+
+
Conversation
+
Listening {listening ? "🟢" : "🔴"}
+
+ {conversation.map((item) => (
+
{item["content"]}
+ ))}
+
+
+ {
+ setConversationIndex((conversationIndex) => conversationIndex + 1)
+ setConversation([])
+ }}>Reset
+ {
+ setConversationIndex((conversationIndex) => conversationIndex == -1 ? 0 : -1)
+ setConversation([])
+ }}>{conversationIndex == -1 ? "Start" : "Stop"}
+
-
-
);
}
-
-export default ServerComms
\ No newline at end of file
diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
new file mode 100644
index 0000000..ec0055a
--- /dev/null
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -0,0 +1,11 @@
+import VisProgUI from "../../visualProgrammingUI/VisProgUI.tsx";
+
+function VisProgPage() {
+ return (
+ <>
+
+ >
+ )
+}
+
+export default VisProgPage
\ No newline at end of file
diff --git a/src/visualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css
new file mode 100644
index 0000000..8d82d09
--- /dev/null
+++ b/src/visualProgrammingUI/VisProgUI.css
@@ -0,0 +1,7 @@
+.default-node {
+ padding: 10px 20px;
+ background-color: canvas;
+ 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
new file mode 100644
index 0000000..a2b1e9c
--- /dev/null
+++ b/src/visualProgrammingUI/VisProgUI.tsx
@@ -0,0 +1,132 @@
+import './VisProgUI.css'
+
+import {
+ useCallback,
+ useRef
+} from 'react';
+import {
+ Background,
+ Controls,
+ ReactFlow,
+ ReactFlowProvider,
+ useNodesState,
+ useEdgesState,
+ reconnectEdge,
+ addEdge,
+ MarkerType,
+ type Edge,
+ type Connection,
+} from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import {
+ StartNode,
+ EndNode,
+ PhaseNode,
+ NormNode
+} from "./components/NodeDefinitions.tsx";
+
+import { Sidebar } from './components/DragDropSidebar.tsx';
+
+const nodeTypes = {
+ start: StartNode,
+ end: EndNode,
+ phase: PhaseNode,
+ norm: NormNode
+};
+
+const initialNodes = [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'genericPhase',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+];
+const initialEdges = [{id: 'start-end', source: 'start', target: 'end'}];
+
+const defaultEdgeOptions = {
+ type: 'floating',
+ markerEnd: {
+ type: MarkerType.ArrowClosed,
+ color: '#505050',
+ },
+};
+
+const VisProgUI = ()=> {
+ const edgeReconnectSuccessful = useRef(true);
+ const [nodes, , onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+
+ const onConnect = useCallback(
+ (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 (
+
+
+ );
+};
+
+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..b3926d9
--- /dev/null
+++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx
@@ -0,0 +1,141 @@
+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 = () => {
+ 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`},
+ };
+ case "norm":
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new norm node`},
+ };
+ default: {
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new default node`},
+ };
+ }
+ }
+ }
+
+ setNodes((nds) => nds.concat(newNode()));
+ }
+ },
+ [setNodes, screenToFlowPosition],
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx
new file mode 100644
index 0000000..b4547b2
--- /dev/null
+++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx
@@ -0,0 +1,111 @@
+import {Handle, NodeToolbar, Position, useReactFlow} from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import '../VisProgUI.css';
+
+// Datatypes for NodeTypes
+
+type defaultNodeData = {
+ label: string;
+};
+
+type startNodeData = defaultNodeData;
+type endNodeData = defaultNodeData;
+type normNodeData = defaultNodeData;
+type phaseNodeData = defaultNodeData & {
+ number: number;
+};
+
+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 (
+
+ delete
+ );
+}
+
+
+// Definitions of Nodes
+
+type StartNodeProps = {
+ id: string;
+ data: startNodeData;
+};
+
+export const StartNode= ({ id, data }: StartNodeProps) => {
+ return (
+ <>
+
+
+
data test {data.label}
+
+
+ >
+ );
+};
+
+type EndNodeProps = {
+ id: string;
+ data: endNodeData;
+};
+
+export const EndNode= ({ id, data }: EndNodeProps) => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+
+type PhaseNodeProps = {
+ id: string;
+ data: phaseNodeData;
+};
+
+export const PhaseNode= ({ id, data }: PhaseNodeProps) => {
+ return (
+ <>
+
+
+
phase {data.number} {data.label}
+
+
+
+
+ >
+ );
+};
+
+type NormNodeProps = {
+ id: string;
+ data: normNodeData;
+};
+
+export const NormNode= ({ id, data }: NormNodeProps) => {
+ return (
+ <>
+
+
+ >
+ );
+};
\ No newline at end of file
From b78cd53baa8f3fd73fc6f372cc3e42f2e07b614b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Tue, 7 Oct 2025 15:05:05 +0200
Subject: [PATCH 02/14] feat: Show connected robots in the UI when connection
event is received from CB.
Added two test buttons to mimic events from CB.
UI will listen to port localhost:8000 for data.
use the data.event = "robot_connected" and
data.event = "robot_disconnected".
(robot) ID is required, name and port are optional
but incentivized.
---
src/App.tsx | 2 +
src/pages/ConnectedRobots/ConnectedRobots.tsx | 116 ++++++++++++++++++
src/pages/Home/Home.tsx | 1 +
3 files changed, 119 insertions(+)
create mode 100644 src/pages/ConnectedRobots/ConnectedRobots.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 0833bdb..fe6a9ad 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import ServerComms from './pages/ServerComms/ServerComms.tsx'
+import ConnectedRobots from './pages/ConnectedRobots/ConnectedRobots.tsx'
import Logging from './pages/Logging/Logging.tsx'
import VisProg from "./pages/VisProgPage/VisProg.tsx";
@@ -18,6 +19,7 @@ function App(){
} />
} />
} />
+ } />
)
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
new file mode 100644
index 0000000..148cfbe
--- /dev/null
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -0,0 +1,116 @@
+import { mergeAriaLabelConfig } from '@xyflow/system';
+import { useState, useEffect } from 'react'
+
+// Define the robot type
+type Robot = {
+ id: string;
+ name: string;
+ port: number;
+};
+
+export default function ConnectedRobots() {
+ const [connectedRobots, setConnectedRobots] = useState
([]);
+
+ useEffect(() => {
+ const eventSource = new EventSource("http://localhost:8000/sse");
+ eventSource.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+
+ // Example: data = { event: "robot_connected", id: "pepper_robot1", name: "Pepper", port: 1234 }
+ if (data.event === "robot_connected") {
+
+ // Safeguard id in request.
+ if (data.id === null || data.id === undefined) {
+ console.log("Missing robot id in connection request.")
+ return () => eventSource.close();
+ }
+
+ // Safeguard duplicates
+ if (connectedRobots.some(robot => robot.id === data.id))
+ console.log("connection request was sent for id: ", data.id,
+ " however this id was already present at current time");
+
+ // Add to connected robots while checking name and port for undefineds.
+ else {
+ const name = data.name ?? "no given name";
+ const port = typeof data.port === "number" ? data.port : -1;
+ setConnectedRobots(robots => [...robots, { id: data.id, name: name, port: port }]);
+ }
+ }
+ if (data.event === "robot_disconnected") {
+ // Safeguard id in request.
+ if (data.id === null || data.id === undefined) {
+ console.log("Missing robot id in connection request.")
+ return () => eventSource.close();
+ }
+
+ // Filter out same ids (should only be one)
+ setConnectedRobots(robots => robots.filter(robot => robot.id !== data.id));
+ }
+ } catch {
+ console.log("Unparsable SSE message:", event.data);
+ }
+ };
+ return () => eventSource.close();
+ }, [connectedRobots]);
+
+ return (
+
+
Robots Connected
+
+
Connected Robots
+
+ {connectedRobots.map(robot =>
+
+ {robot.name} (ID: {robot.id}, Port: {robot.port === -1 ? "No given port" : robot.port})
+
+ )}
+
+
+
+
+ {
+ // Reload from CB database (other ticket)
+ }}>Reload from CB
+ {
+ // Example dummy robots
+ const connection_dummies: Robot[] = [
+ { id: "pepper_robot1", name: "Pepper One", port: 8001 },
+ { id: "pepper_robot2", name: "Pepper Two", port: 8002 },
+ { id: "naoqi_robot1", name: "Naoqi One", port: 9001 },
+ { id: "naoqi_robot2", name: "Naoqi Two", port: 9002 },
+ { id: "noport1", name: "I dont have a port in my request >:)", port: -1},
+ { id: "noname1", name: "no given name", port: 2001}
+ ];
+ const randomIndex = Math.floor(Math.random() * connection_dummies.length);
+ const randomConnectionDummy = connection_dummies[randomIndex];
+
+ if (connectedRobots.some(robot => robot.id === randomConnectionDummy.id))
+ console.log("connection request was sent for id: ", randomConnectionDummy.id,
+ " however this id was already present at current time");
+ else
+ setConnectedRobots(robots => [...robots, randomConnectionDummy]);
+ }}>'Sent connected event'
+ {
+ const disconnection_dummies = [
+ { id: "pepper_robot1" },
+ { id: "pepper_robot2" },
+ { id: "naoqi_robot1" },
+ { id: "naoqi_robot2" },
+ { id: "noport1", name: "I dont have a port in my request >:)", port: -1},
+ { id: "noname1", name: "no given name", port: 2001}
+ ];
+ const randomIndex = Math.floor(Math.random() * disconnection_dummies.length);
+ const randomDisconnectionDummy = disconnection_dummies[randomIndex];
+
+ setConnectedRobots(robots => robots.filter(robot => robot.id !== randomDisconnectionDummy.id));
+ }}>'Sent disconnected event'
+ {
+ setConnectedRobots([]);
+ }}>Reset
+
+
+
+ );
+}
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index 582357b..422ccfc 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -14,6 +14,7 @@ function Home() {
Robot interaction →
Node editor →
Logs →
+ Connected Robots →
>
)
From ec4f45b984528a4dde896486cf00fb132e16a903 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 8 Oct 2025 12:40:01 +0200
Subject: [PATCH 03/14] fix: Keep the conencted robots in a global list
ref: N25B-142
---
src/App.tsx | 23 +++++++++++++++++--
src/pages/ConnectedRobots/ConnectedRobots.tsx | 13 ++++++++---
2 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index fe6a9ad..eff9667 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,5 @@
import { Routes, Route, Link } from 'react-router'
+import { useState, useEffect } from 'react'
import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
@@ -8,7 +9,16 @@ import Logging from './pages/Logging/Logging.tsx'
import VisProg from "./pages/VisProgPage/VisProg.tsx";
function App(){
-
+
+ // Define what our conencted robot should include
+ type Robot = {
+ id: string;
+ name: string;
+ port: number;
+ };
+
+ const [connectedRobots, setConnectedRobots] = useState([]);
+
return (
{/* Should not use inline styles like this */}
@@ -19,7 +29,16 @@ function App(){
} />
} />
} />
- } />
+
+ }
+ />
)
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index 148cfbe..cd88158 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -8,15 +8,22 @@ type Robot = {
port: number;
};
-export default function ConnectedRobots() {
- const [connectedRobots, setConnectedRobots] = useState([]);
+// Define the expected arguments
+type ConnectedRobotsProps = {
+ connectedRobots: Robot[];
+ setConnectedRobots: React.Dispatch>;
+};
+export default function ConnectedRobots({
+ connectedRobots, setConnectedRobots}: ConnectedRobotsProps) {
+
useEffect(() => {
const eventSource = new EventSource("http://localhost:8000/sse");
eventSource.onmessage = (event) => {
try {
+ console.log("message received :", event.data)
const data = JSON.parse(event.data);
-
+
// Example: data = { event: "robot_connected", id: "pepper_robot1", name: "Pepper", port: 1234 }
if (data.event === "robot_connected") {
From 72d61e398506e135d8aefe037d474c18b1c5e638 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 8 Oct 2025 14:35:20 +0200
Subject: [PATCH 04/14] chore: fixed wrong imports and deleted some unnecessary
prints.
ref: N25B-142
---
src/App.tsx | 3 ++-
src/pages/ConnectedRobots/ConnectedRobots.tsx | 6 +++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index eff9667..1dd4c9d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,5 @@
import { Routes, Route, Link } from 'react-router'
-import { useState, useEffect } from 'react'
+import { useState } from 'react'
import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
@@ -17,6 +17,7 @@ function App(){
port: number;
};
+ // (Acces to) the array of connected robots
const [connectedRobots, setConnectedRobots] = useState([]);
return (
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index cd88158..45559dd 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -1,5 +1,4 @@
-import { mergeAriaLabelConfig } from '@xyflow/system';
-import { useState, useEffect } from 'react'
+import { useEffect } from 'react'
// Define the robot type
type Robot = {
@@ -21,7 +20,6 @@ export default function ConnectedRobots({
const eventSource = new EventSource("http://localhost:8000/sse");
eventSource.onmessage = (event) => {
try {
- console.log("message received :", event.data)
const data = JSON.parse(event.data);
// Example: data = { event: "robot_connected", id: "pepper_robot1", name: "Pepper", port: 1234 }
@@ -69,6 +67,7 @@ export default function ConnectedRobots({
Connected Robots
{connectedRobots.map(robot =>
+ // Map all the robots in an unordered list
{robot.name} (ID: {robot.id}, Port: {robot.port === -1 ? "No given port" : robot.port})
@@ -100,6 +99,7 @@ export default function ConnectedRobots({
setConnectedRobots(robots => [...robots, randomConnectionDummy]);
}}>'Sent connected event'
{
+ // Example disconnection bots
const disconnection_dummies = [
{ id: "pepper_robot1" },
{ id: "pepper_robot2" },
From 1a0fd92e0fc5572515bbba5dec2e6d64221cf226 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 8 Oct 2025 16:49:44 +0200
Subject: [PATCH 05/14] chore: complete merging with functionality
ref: N25B-142
additional comments: The reload from CB doesn't work yet.
---
src/App.tsx | 11 +++++++++++
src/pages/Home/Home.tsx | 1 +
2 files changed, 12 insertions(+)
diff --git a/src/App.tsx b/src/App.tsx
index 22be116..78c273f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,6 +4,7 @@ import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import Robot from './pages/Robot/Robot.tsx';
+import ConnectedRobots from './pages/ConnectedRobots/ConnectedRobots.tsx'
function App(){
@@ -27,6 +28,16 @@ function App(){
} />
} />
} />
+
+ }
+ />
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index cb70de0..0c811ac 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -13,6 +13,7 @@ function Home() {
Robot Interaction →
Template →
+ Connected Robots →
)
From fa046e6b2a8524643dbf41a70a370ec7e399cae1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 8 Oct 2025 17:41:29 +0200
Subject: [PATCH 06/14] feat: dummy reload from CB added.
ref: N25B-153
---
src/pages/ConnectedRobots/ConnectedRobots.tsx | 78 ++++++++++++++++---
1 file changed, 67 insertions(+), 11 deletions(-)
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index 45559dd..abce238 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -1,4 +1,5 @@
import { useEffect } from 'react'
+import Logging from '../Logging/Logging';
// Define the robot type
type Robot = {
@@ -14,20 +15,21 @@ type ConnectedRobotsProps = {
};
export default function ConnectedRobots({
- connectedRobots, setConnectedRobots}: ConnectedRobotsProps) {
-
+ connectedRobots, setConnectedRobots }: ConnectedRobotsProps) {
+
useEffect(() => {
const eventSource = new EventSource("http://localhost:8000/sse");
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
-
+
// Example: data = { event: "robot_connected", id: "pepper_robot1", name: "Pepper", port: 1234 }
if (data.event === "robot_connected") {
// Safeguard id in request.
if (data.id === null || data.id === undefined) {
- console.log("Missing robot id in connection request.")
+ console.log(`Missing robot id in connection request.
+ Use format: 'data: {event = 'robot_connected', id = , (optional) name = , (optional) port = }'.`)
return () => eventSource.close();
}
@@ -46,13 +48,23 @@ export default function ConnectedRobots({
if (data.event === "robot_disconnected") {
// Safeguard id in request.
if (data.id === null || data.id === undefined) {
- console.log("Missing robot id in connection request.")
+ console.log("Missing robot id in connection request. Use format: 'data: {event = 'robot_disconnected', id = }'.");
return () => eventSource.close();
}
// Filter out same ids (should only be one)
setConnectedRobots(robots => robots.filter(robot => robot.id !== data.id));
}
+ if (data.event === "robot_list") {
+ if (data.list === null || data.list === undefined) {
+ console.log("Missing list in robot_list request. Use format: 'data: {event = 'robot_list', list = }'.");
+ return () => eventSource.close();
+ }
+
+ // Set the robot list to the one found in CB
+ setConnectedRobots(data.list);
+ }
+
} catch {
console.log("Unparsable SSE message:", event.data);
}
@@ -77,8 +89,52 @@ export default function ConnectedRobots({
{
- // Reload from CB database (other ticket)
- }}>Reload from CB
+ // Let's test the reload function.
+ const example_list = [{ id: "pepper_robot1", name: "Pepper1", port: 1234 },
+ { id: "pepper_robot2", name: "Pepper2", port: 1235 },
+ { id: "pepper_robot3", name: "Pepper3", port: 1236 },
+ { id: "pepper_robot4", name: "Pepper4", port: 1237 }]
+
+ const example_event = `{
+ "event": "robot_list", "list":
+ [{ "id": "pepper_robot1",
+ "name": "Pepper1",
+ "port": 1234 },{
+
+ "id": "pepper_robot2",
+ "name": "Pepper2",
+ "port": 1235 },{
+
+ "id": "pepper_robot3",
+ "name": "Pepper3",
+ "port": 1236 }, {
+
+ "id": "pepper_robot4",
+ "name": "Pepper4",
+ "port": 1237 }]}`
+
+ // Now let's put it through the same steps as the event would do. :)
+ try {
+ const data = JSON.parse(example_event);
+ if (data.event === "robot_list") {
+ if (data.list === null || data.list === undefined) {
+ console.log("Missing list in robot_list request. Use format: 'data: {event = 'robot_list', list =
}'.");
+ return;
+ }
+ // Check if it is as expected.
+ if (JSON.stringify(data.list) !== JSON.stringify(example_list)) {
+ console.log("Dummy reload failed: list don't match.")
+ }
+ else {
+ console.log("Dummy reload succes!!")
+ }
+ } else {
+ console.log("Dummy reload failed, didn't parse to 'data.event === 'robot_list'.'")
+ }
+ } catch {
+ console.log("Dummy reload failed: didnt parse correctly.")
+ }
+ }}>Dummy Reload from CB
{
// Example dummy robots
const connection_dummies: Robot[] = [
@@ -86,8 +142,8 @@ export default function ConnectedRobots({
{ id: "pepper_robot2", name: "Pepper Two", port: 8002 },
{ id: "naoqi_robot1", name: "Naoqi One", port: 9001 },
{ id: "naoqi_robot2", name: "Naoqi Two", port: 9002 },
- { id: "noport1", name: "I dont have a port in my request >:)", port: -1},
- { id: "noname1", name: "no given name", port: 2001}
+ { id: "noport1", name: "I dont have a port in my request >:)", port: -1 },
+ { id: "noname1", name: "no given name", port: 2001 }
];
const randomIndex = Math.floor(Math.random() * connection_dummies.length);
const randomConnectionDummy = connection_dummies[randomIndex];
@@ -105,8 +161,8 @@ export default function ConnectedRobots({
{ id: "pepper_robot2" },
{ id: "naoqi_robot1" },
{ id: "naoqi_robot2" },
- { id: "noport1", name: "I dont have a port in my request >:)", port: -1},
- { id: "noname1", name: "no given name", port: 2001}
+ { id: "noport1", name: "I dont have a port in my request >:)", port: -1 },
+ { id: "noname1", name: "no given name", port: 2001 }
];
const randomIndex = Math.floor(Math.random() * disconnection_dummies.length);
const randomDisconnectionDummy = disconnection_dummies[randomIndex];
From 4181454a73b82180a4240a0789c5a196ac9291ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Thu, 30 Oct 2025 13:05:56 +0100
Subject: [PATCH 07/14] feat: show robots page easier - quick connected sign.
Quick reload - no need for manual reloads or anything.
ref: N25B-142
---
package-lock.json | 126 +++++++++++++
src/pages/ConnectedRobots/ConnectedRobots.tsx | 176 ++----------------
2 files changed, 144 insertions(+), 158 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index b4dc078..665dad5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1967,6 +1967,12 @@
"@tybys/wasm-util": "^0.10.0"
}
},
+ "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",
@@ -3769,6 +3775,12 @@
"dev": true,
"license": "MIT"
},
+ "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/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -3951,6 +3963,111 @@
"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/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
@@ -7591,6 +7708,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index abce238..a6f7ce7 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -1,178 +1,38 @@
-import { useEffect } from 'react'
-import Logging from '../Logging/Logging';
-
-// Define the robot type
-type Robot = {
- id: string;
- name: string;
- port: number;
-};
-
-// Define the expected arguments
-type ConnectedRobotsProps = {
- connectedRobots: Robot[];
- setConnectedRobots: React.Dispatch>;
-};
-
-export default function ConnectedRobots({
- connectedRobots, setConnectedRobots }: ConnectedRobotsProps) {
+import { useEffect, useState } from 'react'
+export default function ConnectedRobots() {
+
+ const [connected, setConnected] = useState(null);
useEffect(() => {
- const eventSource = new EventSource("http://localhost:8000/sse");
+ // We're excepting a stream of data like that looks like this: `data = False` or `data = True`
+ const eventSource = new EventSource("http://localhost:8000/robot/ping_stream");
eventSource.onmessage = (event) => {
+
+ // Receive message and parse
+ console.log("received message:", event.data);
try {
const data = JSON.parse(event.data);
- // Example: data = { event: "robot_connected", id: "pepper_robot1", name: "Pepper", port: 1234 }
- if (data.event === "robot_connected") {
-
- // Safeguard id in request.
- if (data.id === null || data.id === undefined) {
- console.log(`Missing robot id in connection request.
- Use format: 'data: {event = 'robot_connected', id = , (optional) name = , (optional) port = }'.`)
- return () => eventSource.close();
- }
-
- // Safeguard duplicates
- if (connectedRobots.some(robot => robot.id === data.id))
- console.log("connection request was sent for id: ", data.id,
- " however this id was already present at current time");
-
- // Add to connected robots while checking name and port for undefineds.
- else {
- const name = data.name ?? "no given name";
- const port = typeof data.port === "number" ? data.port : -1;
- setConnectedRobots(robots => [...robots, { id: data.id, name: name, port: port }]);
- }
+ // Set connected to value.
+ try {
+ setConnected(data)
}
- if (data.event === "robot_disconnected") {
- // Safeguard id in request.
- if (data.id === null || data.id === undefined) {
- console.log("Missing robot id in connection request. Use format: 'data: {event = 'robot_disconnected', id = }'.");
- return () => eventSource.close();
- }
-
- // Filter out same ids (should only be one)
- setConnectedRobots(robots => robots.filter(robot => robot.id !== data.id));
- }
- if (data.event === "robot_list") {
- if (data.list === null || data.list === undefined) {
- console.log("Missing list in robot_list request. Use format: 'data: {event = 'robot_list', list = }'.");
- return () => eventSource.close();
- }
-
- // Set the robot list to the one found in CB
- setConnectedRobots(data.list);
+ catch {
+ console.log("couldnt extract connected from incoming ping data")
}
} catch {
- console.log("Unparsable SSE message:", event.data);
+ console.log("Ping message not in correct format:", event.data);
}
};
return () => eventSource.close();
- }, [connectedRobots]);
+ });
return (
-
Robots Connected
+
Is robot currently connected?
-
Connected Robots
-
- {connectedRobots.map(robot =>
- // Map all the robots in an unordered list
-
- {robot.name} (ID: {robot.id}, Port: {robot.port === -1 ? "No given port" : robot.port})
-
- )}
-
-
-
-
- {
- // Let's test the reload function.
- const example_list = [{ id: "pepper_robot1", name: "Pepper1", port: 1234 },
- { id: "pepper_robot2", name: "Pepper2", port: 1235 },
- { id: "pepper_robot3", name: "Pepper3", port: 1236 },
- { id: "pepper_robot4", name: "Pepper4", port: 1237 }]
-
- const example_event = `{
- "event": "robot_list", "list":
- [{ "id": "pepper_robot1",
- "name": "Pepper1",
- "port": 1234 },{
-
- "id": "pepper_robot2",
- "name": "Pepper2",
- "port": 1235 },{
-
- "id": "pepper_robot3",
- "name": "Pepper3",
- "port": 1236 }, {
-
- "id": "pepper_robot4",
- "name": "Pepper4",
- "port": 1237 }]}`
-
- // Now let's put it through the same steps as the event would do. :)
- try {
- const data = JSON.parse(example_event);
- if (data.event === "robot_list") {
- if (data.list === null || data.list === undefined) {
- console.log("Missing list in robot_list request. Use format: 'data: {event = 'robot_list', list = }'.");
- return;
- }
- // Check if it is as expected.
- if (JSON.stringify(data.list) !== JSON.stringify(example_list)) {
- console.log("Dummy reload failed: list don't match.")
- }
- else {
- console.log("Dummy reload succes!!")
- }
- } else {
- console.log("Dummy reload failed, didn't parse to 'data.event === 'robot_list'.'")
- }
- } catch {
- console.log("Dummy reload failed: didnt parse correctly.")
- }
- }}>Dummy Reload from CB
- {
- // Example dummy robots
- const connection_dummies: Robot[] = [
- { id: "pepper_robot1", name: "Pepper One", port: 8001 },
- { id: "pepper_robot2", name: "Pepper Two", port: 8002 },
- { id: "naoqi_robot1", name: "Naoqi One", port: 9001 },
- { id: "naoqi_robot2", name: "Naoqi Two", port: 9002 },
- { id: "noport1", name: "I dont have a port in my request >:)", port: -1 },
- { id: "noname1", name: "no given name", port: 2001 }
- ];
- const randomIndex = Math.floor(Math.random() * connection_dummies.length);
- const randomConnectionDummy = connection_dummies[randomIndex];
-
- if (connectedRobots.some(robot => robot.id === randomConnectionDummy.id))
- console.log("connection request was sent for id: ", randomConnectionDummy.id,
- " however this id was already present at current time");
- else
- setConnectedRobots(robots => [...robots, randomConnectionDummy]);
- }}>'Sent connected event'
- {
- // Example disconnection bots
- const disconnection_dummies = [
- { id: "pepper_robot1" },
- { id: "pepper_robot2" },
- { id: "naoqi_robot1" },
- { id: "naoqi_robot2" },
- { id: "noport1", name: "I dont have a port in my request >:)", port: -1 },
- { id: "noname1", name: "no given name", port: 2001 }
- ];
- const randomIndex = Math.floor(Math.random() * disconnection_dummies.length);
- const randomDisconnectionDummy = disconnection_dummies[randomIndex];
-
- setConnectedRobots(robots => robots.filter(robot => robot.id !== randomDisconnectionDummy.id));
- }}>'Sent disconnected event'
- {
- setConnectedRobots([]);
- }}>Reset
-
+
Robot is currently: {connected == null ? "checking..." : (connected ? "connected! 🟢" : "not connected... 🔴")}
);
From 6a88aa3d755db2ec9f9ed76019545846f39f3a16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Thu, 30 Oct 2025 14:57:50 +0100
Subject: [PATCH 08/14] merge branch dev into show-connected-robots pt2
---
package-lock.json | 127 ------------------------------
src/App.tsx | 3 -
src/pages/VisProgPage/VisProg.tsx | 11 ---
3 files changed, 141 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index b3caba6..c7346fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3323,69 +3323,6 @@
"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"
- }
- },
-<<<<<<< HEAD
-=======
- "node_modules/@xyflow/react/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
- }
- }
- },
->>>>>>> origin/dev
- "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",
@@ -7740,21 +7677,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/use-sync-external-store": {
-<<<<<<< HEAD
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
- "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
-=======
- "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==",
->>>>>>> origin/dev
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -8237,55 +8159,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
- },
- "node_modules/zustand": {
-<<<<<<< HEAD
- "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"
-=======
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
- "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
- "license": "MIT",
- "engines": {
- "node": ">=12.20.0"
- },
- "peerDependencies": {
- "@types/react": ">=18.0.0",
- "immer": ">=9.0.6",
- "react": ">=18.0.0",
- "use-sync-external-store": ">=1.2.0"
->>>>>>> origin/dev
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "immer": {
- "optional": true
- },
- "react": {
- "optional": true
-<<<<<<< HEAD
-=======
- },
- "use-sync-external-store": {
- "optional": true
->>>>>>> origin/dev
- }
- }
}
}
}
diff --git a/src/App.tsx b/src/App.tsx
index fd9404c..819dae4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,11 +4,8 @@ import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import Robot from './pages/Robot/Robot.tsx';
-<<<<<<< HEAD
import ConnectedRobots from './pages/ConnectedRobots/ConnectedRobots.tsx'
-=======
import VisProg from "./pages/VisProgPage/VisProg.tsx";
->>>>>>> origin/dev
function App(){
diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index ec2078c..4b8944c 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -1,13 +1,3 @@
-<<<<<<< HEAD
-import VisProgUI from "../../visualProgrammingUI/VisProgUI.tsx";
-
-function VisProgPage() {
- return (
- <>
-
- >
- )
-=======
import {
Background,
Controls,
@@ -144,7 +134,6 @@ function VisProgPage() {
>
)
->>>>>>> origin/dev
}
export default VisProgPage
\ No newline at end of file
From 5e707224cffabb3320e87e8810f77fecb1b8305e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Thu, 30 Oct 2025 15:47:09 +0100
Subject: [PATCH 09/14] feat: Show connected robots finished with unit test 94%
coverage
ref: N25B-142
---
package-lock.json | 98 +++++++++++++++++
src/App.tsx | 22 +---
src/pages/ConnectedRobots/ConnectedRobots.tsx | 6 ++
.../connectedRobots/ConnectedRobots.test.tsx | 102 ++++++++++++++++++
4 files changed, 207 insertions(+), 21 deletions(-)
create mode 100644 test/pages/connectedRobots/ConnectedRobots.test.tsx
diff --git a/package-lock.json b/package-lock.json
index c7346fa..40f413f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3323,6 +3323,66 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/@xyflow/react": {
+ "version": "12.9.1",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.9.1.tgz",
+ "integrity": "sha512-JRPCT5p7NnPdVSIh15AFvUSSm+8GUyz2I6iuBEC1LG2lKgig/L48AM/ImMHCc3ZUCg+AgTOJDaX2fcRyPA9BTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@xyflow/system": "0.0.72",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@xyflow/react/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
+ }
+ }
+ },
+ "node_modules/@xyflow/system": {
+ "version": "0.0.72",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.72.tgz",
+ "integrity": "sha512-WBI5Aau0fXTXwxHPzceLNS6QdXggSWnGjDtj/gG669crApN8+SCmEtkBth1m7r6pStNo/5fI9McEi7Dk0ymCLA==",
+ "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",
@@ -7677,6 +7737,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -8159,6 +8228,35 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
+ "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/src/App.tsx b/src/App.tsx
index 819dae4..4fb4c42 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,4 @@
import { Routes, Route, Link } from 'react-router'
-import { useState } from 'react'
import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
@@ -9,16 +8,6 @@ import VisProg from "./pages/VisProgPage/VisProg.tsx";
function App(){
- // Define what our conencted robot should include
- type Robot = {
- id: string;
- name: string;
- port: number;
- };
-
- // (Acces to) the array of connected robots
- const [connectedRobots, setConnectedRobots] = useState([]);
-
return (
@@ -30,16 +19,7 @@ function App(){
} />
} />
} />
-
- }
- />
+ } />
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index a6f7ce7..8b4b898 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
export default function ConnectedRobots() {
const [connected, setConnected] = useState(null);
+
+
+
useEffect(() => {
// We're excepting a stream of data like that looks like this: `data = False` or `data = True`
const eventSource = new EventSource("http://localhost:8000/robot/ping_stream");
@@ -33,6 +36,9 @@ export default function ConnectedRobots() {
Is robot currently connected?
Robot is currently: {connected == null ? "checking..." : (connected ? "connected! 🟢" : "not connected... 🔴")}
+
+ {connected == null ? "If checking continues, make sure CB is properly loaded with robot at least once." : ""}
+
);
diff --git a/test/pages/connectedRobots/ConnectedRobots.test.tsx b/test/pages/connectedRobots/ConnectedRobots.test.tsx
new file mode 100644
index 0000000..ffea6e3
--- /dev/null
+++ b/test/pages/connectedRobots/ConnectedRobots.test.tsx
@@ -0,0 +1,102 @@
+import { render, screen, act, cleanup, waitFor } from '@testing-library/react';
+import ConnectedRobots from '../../../src/pages/ConnectedRobots/ConnectedRobots';
+
+// Mock event source
+const mockInstances: MockEventSource[] = [];
+class MockEventSource {
+ url: string;
+ onmessage: ((event: MessageEvent) => void) | null = null;
+ closed = false;
+
+ constructor(url: string) {
+ this.url = url;
+ mockInstances.push(this);
+ }
+
+ sendMessage(data: any) {
+ // Trigger whatever the component listens to
+ this.onmessage?.({ data } as MessageEvent);
+ }
+
+ close() {
+ this.closed = true;
+ }
+}
+
+// mock event source generation with fake function that returns our fake mock source
+beforeAll(() => {
+ (globalThis as any).EventSource = jest.fn((url: string) => new MockEventSource(url));
+});
+
+// clean after tests
+afterEach(() => {
+ cleanup();
+ jest.restoreAllMocks();
+ mockInstances.length = 0;
+});
+
+describe('ConnectedRobots', () => {
+ test('renders initial state correctly', () => {
+ render(
);
+
+ // Check initial texts (before connection)
+ expect(screen.getByText('Is robot currently connected?')).toBeInTheDocument();
+ expect(screen.getByText(/Robot is currently:\s*checking/i)).toBeInTheDocument();
+ expect(
+ screen.getByText(/If checking continues, make sure CB is properly loaded/i)
+ ).toBeInTheDocument();
+ });
+
+ test('updates to connected when message data is true', async () => {
+ render(
);
+ const eventSource = mockInstances[0];
+ expect(eventSource).toBeDefined();
+
+ // Check state after getting 'true' message
+ await act(async () => {
+ eventSource.sendMessage('true');
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText(/connected! 🟢/i)).toBeInTheDocument();
+ });
+ });
+
+ test('updates to not connected when message data is false', async () => {
+ render(
);
+ const eventSource = mockInstances[0];
+
+ // Check statew after getting 'false' message
+ await act(async () => {
+ eventSource.sendMessage('false');
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText(/not connected.*🔴/i)).toBeInTheDocument();
+ });
+ });
+
+ test('handles invalid JSON gracefully', async () => {
+ const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
+ render(
);
+ const eventSource = mockInstances[0];
+
+ await act(async () => {
+ eventSource.sendMessage('not-json');
+ });
+
+ expect(logSpy).toHaveBeenCalledWith(
+ 'Ping message not in correct format:',
+ 'not-json'
+ );
+ });
+
+ test('closes EventSource on unmount', () => {
+ render(
);
+ const eventSource = mockInstances[0];
+ const closeSpy = jest.spyOn(eventSource, 'close');
+ cleanup();
+ expect(closeSpy).toHaveBeenCalled();
+ expect(eventSource.closed).toBe(true);
+ });
+});
\ No newline at end of file
From 333bd6e6fd1b7db071e1b1399d0260de606a375f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 5 Nov 2025 16:11:36 +0100
Subject: [PATCH 10/14] chore: single typing change
---
test/pages/connectedRobots/ConnectedRobots.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/pages/connectedRobots/ConnectedRobots.test.tsx b/test/pages/connectedRobots/ConnectedRobots.test.tsx
index ffea6e3..c2d3749 100644
--- a/test/pages/connectedRobots/ConnectedRobots.test.tsx
+++ b/test/pages/connectedRobots/ConnectedRobots.test.tsx
@@ -13,7 +13,7 @@ class MockEventSource {
mockInstances.push(this);
}
- sendMessage(data: any) {
+ sendMessage(data: string) {
// Trigger whatever the component listens to
this.onmessage?.({ data } as MessageEvent);
}
From 1b8095376b31027fd7bc39199c9d8c96d4e9ba69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Wed, 5 Nov 2025 17:21:36 +0100
Subject: [PATCH 11/14] fix: fixed npx eslint (also accounting for justins
part)
ref: N25B-142
---
src/visualProgrammingUI/components/DragDropSidebar.tsx | 3 +--
test/pages/connectedRobots/ConnectedRobots.test.tsx | 4 +++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx
index b3926d9..f1bf6bd 100644
--- a/src/visualProgrammingUI/components/DragDropSidebar.tsx
+++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx
@@ -27,8 +27,7 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
const draggableRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
-
- // @ts-ignore
+ // @ts-expect-error we expect the null referece here.
useDraggable(draggableRef, {
position: position,
onDrag: ({ offsetX, offsetY }) => {
diff --git a/test/pages/connectedRobots/ConnectedRobots.test.tsx b/test/pages/connectedRobots/ConnectedRobots.test.tsx
index c2d3749..017b2a2 100644
--- a/test/pages/connectedRobots/ConnectedRobots.test.tsx
+++ b/test/pages/connectedRobots/ConnectedRobots.test.tsx
@@ -25,7 +25,9 @@ class MockEventSource {
// mock event source generation with fake function that returns our fake mock source
beforeAll(() => {
- (globalThis as any).EventSource = jest.fn((url: string) => new MockEventSource(url));
+ // Cast globalThis to a type exposing EventSource and assign a mocked constructor.
+ (globalThis as unknown as { EventSource?: typeof EventSource }).EventSource =
+ jest.fn((url: string) => new MockEventSource(url)) as unknown as typeof EventSource;
});
// clean after tests
From 8733bb3c040706f301ba08a9c042257e254577cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Tue, 11 Nov 2025 10:25:27 +0100
Subject: [PATCH 12/14] chore: remove old remnants from project
---
src/visualProgrammingUI/VisProgUI.css | 7 -
src/visualProgrammingUI/VisProgUI.tsx | 132 -----------------
.../components/DragDropSidebar.tsx | 140 ------------------
.../components/NodeDefinitions.tsx | 111 --------------
4 files changed, 390 deletions(-)
delete mode 100644 src/visualProgrammingUI/VisProgUI.css
delete mode 100644 src/visualProgrammingUI/VisProgUI.tsx
delete mode 100644 src/visualProgrammingUI/components/DragDropSidebar.tsx
delete mode 100644 src/visualProgrammingUI/components/NodeDefinitions.tsx
diff --git a/src/visualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css
deleted file mode 100644
index 8d82d09..0000000
--- a/src/visualProgrammingUI/VisProgUI.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.default-node {
- padding: 10px 20px;
- background-color: canvas;
- 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
deleted file mode 100644
index a2b1e9c..0000000
--- a/src/visualProgrammingUI/VisProgUI.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import './VisProgUI.css'
-
-import {
- useCallback,
- useRef
-} from 'react';
-import {
- Background,
- Controls,
- ReactFlow,
- ReactFlowProvider,
- useNodesState,
- useEdgesState,
- reconnectEdge,
- addEdge,
- MarkerType,
- type Edge,
- type Connection,
-} from '@xyflow/react';
-import '@xyflow/react/dist/style.css';
-import {
- StartNode,
- EndNode,
- PhaseNode,
- NormNode
-} from "./components/NodeDefinitions.tsx";
-
-import { Sidebar } from './components/DragDropSidebar.tsx';
-
-const nodeTypes = {
- start: StartNode,
- end: EndNode,
- phase: PhaseNode,
- norm: NormNode
-};
-
-const initialNodes = [
- {
- id: 'start',
- type: 'start',
- position: {x: 0, y: 0},
- data: {label: 'start'}
- },
- {
- id: 'genericPhase',
- type: 'phase',
- position: {x: 0, y: 150},
- data: {label: 'Generic Phase', number: 1},
- },
- {
- id: 'end',
- type: 'end',
- position: {x: 0, y: 300},
- data: {label: 'End'}
- }
-];
-const initialEdges = [{id: 'start-end', source: 'start', target: 'end'}];
-
-const defaultEdgeOptions = {
- type: 'floating',
- markerEnd: {
- type: MarkerType.ArrowClosed,
- color: '#505050',
- },
-};
-
-const VisProgUI = ()=> {
- const edgeReconnectSuccessful = useRef(true);
- const [nodes, , onNodesChange] = useNodesState(initialNodes);
- const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
-
- const onConnect = useCallback(
- (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 (
-
-
- );
-};
-
-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
deleted file mode 100644
index f1bf6bd..0000000
--- a/src/visualProgrammingUI/components/DragDropSidebar.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-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-expect-error we expect the null referece here.
- 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 = () => {
- 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`},
- };
- case "norm":
- return {
- id: getId(),
- type: nodeType,
- position,
- data: {label: `new norm node`},
- };
- default: {
- return {
- id: getId(),
- type: nodeType,
- position,
- data: {label: `new default node`},
- };
- }
- }
- }
-
- setNodes((nds) => nds.concat(newNode()));
- }
- },
- [setNodes, screenToFlowPosition],
- );
-
- return (
-
- );
-}
\ No newline at end of file
diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx
deleted file mode 100644
index b4547b2..0000000
--- a/src/visualProgrammingUI/components/NodeDefinitions.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import {Handle, NodeToolbar, Position, useReactFlow} from '@xyflow/react';
-import '@xyflow/react/dist/style.css';
-import '../VisProgUI.css';
-
-// Datatypes for NodeTypes
-
-type defaultNodeData = {
- label: string;
-};
-
-type startNodeData = defaultNodeData;
-type endNodeData = defaultNodeData;
-type normNodeData = defaultNodeData;
-type phaseNodeData = defaultNodeData & {
- number: number;
-};
-
-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 (
-
- delete
- );
-}
-
-
-// Definitions of Nodes
-
-type StartNodeProps = {
- id: string;
- data: startNodeData;
-};
-
-export const StartNode= ({ id, data }: StartNodeProps) => {
- return (
- <>
-
-
-
data test {data.label}
-
-
- >
- );
-};
-
-type EndNodeProps = {
- id: string;
- data: endNodeData;
-};
-
-export const EndNode= ({ id, data }: EndNodeProps) => {
- return (
- <>
-
-
- >
- );
-};
-
-
-type PhaseNodeProps = {
- id: string;
- data: phaseNodeData;
-};
-
-export const PhaseNode= ({ id, data }: PhaseNodeProps) => {
- return (
- <>
-
-
-
phase {data.number} {data.label}
-
-
-
-
- >
- );
-};
-
-type NormNodeProps = {
- id: string;
- data: normNodeData;
-};
-
-export const NormNode= ({ id, data }: NormNodeProps) => {
- return (
- <>
-
-
- >
- );
-};
\ No newline at end of file
From df4346150e2bf6a81d9629cdbc48884572bae3a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Tue, 11 Nov 2025 11:11:46 +0100
Subject: [PATCH 13/14] chore: remove old code pt 2
---
src/assets/data.ts | 361 ---------------------------
src/pages/Logging/Logging.module.css | 17 --
src/pages/Logging/Logging.tsx | 78 ------
3 files changed, 456 deletions(-)
delete mode 100644 src/assets/data.ts
delete mode 100644 src/pages/Logging/Logging.module.css
delete mode 100644 src/pages/Logging/Logging.tsx
diff --git a/src/assets/data.ts b/src/assets/data.ts
deleted file mode 100644
index c1eacfb..0000000
--- a/src/assets/data.ts
+++ /dev/null
@@ -1,361 +0,0 @@
-export const DATA: LogEntry[] = [
- {
- id: "1",
- timestamp: "2025-10-01T12:00:00Z",
- level: "info",
- msg: "User said: Hello, Pepper!",
- type: "speech",
- },
- {
- id: "2",
- timestamp: "2025-10-01T12:00:05Z",
- level: "debug",
- msg: "Proximity sensor value: 0.85",
- type: "sensor",
- },
- {
- id: "3",
- timestamp: "2025-10-01T12:00:10Z",
- level: "warn",
- msg: "Battery level low: 15%",
- type: "system",
- },
- {
- id: "4",
- timestamp: "2025-10-01T12:00:15Z",
- level: "info",
- msg: "User requested weather update.",
- type: "speech",
- },
- {
- id: "5",
- timestamp: "2025-10-01T12:00:20Z",
- level: "debug",
- msg: "Microphone activated.",
- type: "system",
- },
- {
- id: "6",
- timestamp: "2025-10-01T12:00:25Z",
- level: "warn",
- msg: "Obstacle detected in front.",
- type: "sensor",
- },
- {
- id: "7",
- timestamp: "2025-10-01T12:00:30Z",
- level: "info",
- msg: "User said: Thank you!",
- type: "speech",
- },
- {
- id: "8",
- timestamp: "2025-10-01T12:00:35Z",
- level: "debug",
- msg: "Network latency: 120ms",
- type: "system",
- },
- {
- id: "9",
- timestamp: "2025-10-01T12:00:40Z",
- level: "warn",
- msg: "High CPU usage detected.",
- type: "system",
- },
- {
- id: "10",
- timestamp: "2025-10-01T12:00:45Z",
- level: "info",
- msg: "User started a new session.",
- type: "system",
- },
- {
- id: "11",
- timestamp: "2025-10-01T12:01:00Z",
- level: "info",
- msg: "User asked: What's the weather?",
- type: "speech",
- },
- {
- id: "12",
- timestamp: "2025-10-01T12:01:05Z",
- level: "debug",
- msg: "Camera initialized.",
- type: "system",
- },
- {
- id: "13",
- timestamp: "2025-10-01T12:01:10Z",
- level: "warn",
- msg: "Temperature sensor disconnected.",
- type: "sensor",
- },
- {
- id: "14",
- timestamp: "2025-10-01T12:01:15Z",
- level: "info",
- msg: "User said: Play some music.",
- type: "speech",
- },
- {
- id: "15",
- timestamp: "2025-10-01T12:01:20Z",
- level: "debug",
- msg: "Audio output device selected: Speaker.",
- type: "system",
- },
- {
- id: "16",
- timestamp: "2025-10-01T12:01:25Z",
- level: "warn",
- msg: "Low light detected in room.",
- type: "sensor",
- },
- {
- id: "17",
- timestamp: "2025-10-01T12:01:30Z",
- level: "info",
- msg: "User said: Turn on the lights.",
- type: "speech",
- },
- {
- id: "18",
- timestamp: "2025-10-01T12:01:35Z",
- level: "debug",
- msg: "Light control signal sent.",
- type: "system",
- },
- {
- id: "19",
- timestamp: "2025-10-01T12:01:40Z",
- level: "warn",
- msg: "Light bulb not responding.",
- type: "system",
- },
- {
- id: "20",
- timestamp: "2025-10-01T12:01:45Z",
- level: "info",
- msg: "User said: Good night.",
- type: "speech",
- },
- {
- id: "21",
- timestamp: "2025-10-01T12:02:00Z",
- level: "info",
- msg: "User asked: What's the time?",
- type: "speech",
- },
- {
- id: "22",
- timestamp: "2025-10-01T12:02:05Z",
- level: "debug",
- msg: "Time module loaded.",
- type: "system",
- },
- {
- id: "23",
- timestamp: "2025-10-01T12:02:10Z",
- level: "warn",
- msg: "WiFi signal weak.",
- type: "system",
- },
- {
- id: "24",
- timestamp: "2025-10-01T12:02:15Z",
- level: "info",
- msg: "User said: Set an alarm for 7 AM.",
- type: "speech",
- },
- {
- id: "25",
- timestamp: "2025-10-01T12:02:20Z",
- level: "debug",
- msg: "Alarm scheduled for 7:00 AM.",
- type: "system",
- },
- {
- id: "26",
- timestamp: "2025-10-01T12:02:25Z",
- level: "warn",
- msg: "Alarm module not responding.",
- type: "system",
- },
- {
- id: "27",
- timestamp: "2025-10-01T12:02:30Z",
- level: "info",
- msg: "User said: Cancel the alarm.",
- type: "speech",
- },
- {
- id: "28",
- timestamp: "2025-10-01T12:02:35Z",
- level: "debug",
- msg: "Alarm cancellation requested.",
- type: "system",
- },
- {
- id: "29",
- timestamp: "2025-10-01T12:02:40Z",
- level: "warn",
- msg: "Alarm cancellation failed.",
- type: "system",
- },
- {
- id: "30",
- timestamp: "2025-10-01T12:02:45Z",
- level: "info",
- msg: "User said: Open the window.",
- type: "speech",
- },
- {
- id: "31",
- timestamp: "2025-10-01T12:03:00Z",
- level: "info",
- msg: "User asked: What's on my calendar?",
- type: "speech",
- },
- {
- id: "32",
- timestamp: "2025-10-01T12:03:05Z",
- level: "debug",
- msg: "Calendar module loaded.",
- type: "system",
- },
- {
- id: "33",
- timestamp: "2025-10-01T12:03:10Z",
- level: "warn",
- msg: "Calendar sync failed.",
- type: "system",
- },
- {
- id: "34",
- timestamp: "2025-10-01T12:03:15Z",
- level: "info",
- msg: "User said: Remind me to call John.",
- type: "speech",
- },
- {
- id: "35",
- timestamp: "2025-10-01T12:03:20Z",
- level: "debug",
- msg: "Reminder set for John.",
- type: "system",
- },
- {
- id: "36",
- timestamp: "2025-10-01T12:03:25Z",
- level: "warn",
- msg: "Reminder module not available.",
- type: "system",
- },
- {
- id: "37",
- timestamp: "2025-10-01T12:03:30Z",
- level: "info",
- msg: "User said: What's the news?",
- type: "speech",
- },
- {
- id: "38",
- timestamp: "2025-10-01T12:03:35Z",
- level: "debug",
- msg: "News API request sent.",
- type: "system",
- },
- {
- id: "39",
- timestamp: "2025-10-01T12:03:40Z",
- level: "warn",
- msg: "News API rate limit reached.",
- type: "system",
- },
- {
- id: "40",
- timestamp: "2025-10-01T12:03:45Z",
- level: "info",
- msg: "User said: Tell me a joke.",
- type: "speech",
- },
- {
- id: "41",
- timestamp: "2025-10-01T12:04:00Z",
- level: "info",
- msg: "User asked: What's the temperature?",
- type: "speech",
- },
- {
- id: "42",
- timestamp: "2025-10-01T12:04:05Z",
- level: "debug",
- msg: "Temperature sensor reading: 22°C.",
- type: "sensor",
- },
- {
- id: "43",
- timestamp: "2025-10-01T12:04:10Z",
- level: "warn",
- msg: "Temperature sensor calibration needed.",
- type: "sensor",
- },
- {
- id: "44",
- timestamp: "2025-10-01T12:04:15Z",
- level: "info",
- msg: "User said: Start cleaning.",
- type: "speech",
- },
- {
- id: "45",
- timestamp: "2025-10-01T12:04:20Z",
- level: "debug",
- msg: "Vacuum motor started.",
- type: "system",
- },
- {
- id: "46",
- timestamp: "2025-10-01T12:04:25Z",
- level: "warn",
- msg: "Vacuum bin full.",
- type: "system",
- },
- {
- id: "47",
- timestamp: "2025-10-01T12:04:30Z",
- level: "info",
- msg: "User said: Stop cleaning.",
- type: "speech",
- },
- {
- id: "48",
- timestamp: "2025-10-01T12:04:35Z",
- level: "debug",
- msg: "Vacuum motor stopped.",
- type: "system",
- },
- {
- id: "49",
- timestamp: "2025-10-01T12:04:40Z",
- level: "warn",
- msg: "Obstacle detected during cleaning.",
- type: "sensor",
- },
- {
- id: "50",
- timestamp: "2025-10-01T12:04:45Z",
- level: "info",
- msg: "User said: Goodbye!",
- type: "speech",
- },
-];
-
-interface LogEntry {
- id: string;
- type?: string;
- timestamp: string;
- level: "info" | "debug" | "warn";
- msg?: string;
-}
-
diff --git a/src/pages/Logging/Logging.module.css b/src/pages/Logging/Logging.module.css
deleted file mode 100644
index 52191c3..0000000
--- a/src/pages/Logging/Logging.module.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.DivToScroll{
- background-color: color-mix(in srgb, canvas, #000 5%);
- border: 1px solid color-mix(in srgb, canvas, #000 15%);
- border-radius: 4px 0 4px 0;
- color: #3B3C3E;
- font-size: 12px;
- font-weight: bold;
- left: -1px;
- padding: 10px 7px 5px;
-}
-
-.DivWithScroll{
- height:50vh;
- width:100vh;
- overflow:scroll;
- overflow-x:hidden;
-}
diff --git a/src/pages/Logging/Logging.tsx b/src/pages/Logging/Logging.tsx
deleted file mode 100644
index 31945c1..0000000
--- a/src/pages/Logging/Logging.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useState } from 'react';
-import { DATA } from "../../assets/data";
-import styles from './Logging.module.css';
-
-
-// const dataType = DATA as { id: string; level: "debug"|"info"|"warn"|"error"; msg: string; timestamp?: string };
-type Level = "debug" | "info" | "warn" | "error";
-
-// make optional fields optional
-type LogEntry = {
- id: string;
- level: Level;
- timestamp?: string;
- msg?: string;
- type?: "speech" | "sensor" | "system" | string;
-
-};
-
-function getLevelColor(level: Level) {
- switch (level) {
- case "debug":
- return "gray";
- case "info":
- return "blue";
- case "warn":
- return "red";
- case "error":
- return "red";
- default:
- return "black";
- }
-}
-
-function Logging() {
- const [logs, setLogs] = useState([]);
-
- const logDiv = (
-
-
- {logs.map((log) => (
-
-
- [{log.timestamp}]
-
-
- {log.msg ? log.msg : "No message"}
-
-
- ({log.level})
-
-
- ))}
-
-
- )
- return (
- <>
- Log Screen
- { logDiv }
-
- setLogs(DATA)}>
- Load sample logs
-
-
-
- >
- )
-}
-
-export default Logging
From 87cf723c95d2f0e235a53ce6d2d42273f80f8dea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?=
Date: Tue, 11 Nov 2025 11:42:28 +0100
Subject: [PATCH 14/14] chore: fixed merge request suggestion for adding
depency array
---
src/pages/ConnectedRobots/ConnectedRobots.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/pages/ConnectedRobots/ConnectedRobots.tsx b/src/pages/ConnectedRobots/ConnectedRobots.tsx
index 8b4b898..b7ec65f 100644
--- a/src/pages/ConnectedRobots/ConnectedRobots.tsx
+++ b/src/pages/ConnectedRobots/ConnectedRobots.tsx
@@ -4,8 +4,6 @@ export default function ConnectedRobots() {
const [connected, setConnected] = useState(null);
-
-
useEffect(() => {
// We're excepting a stream of data like that looks like this: `data = False` or `data = True`
const eventSource = new EventSource("http://localhost:8000/robot/ping_stream");
@@ -29,7 +27,7 @@ export default function ConnectedRobots() {
}
};
return () => eventSource.close();
- });
+ }, []);
return (