Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2060777
Add skeleton for Dashboard component
dudebehinddude Apr 21, 2025
10022bd
Create basic YAML configuration
dudebehinddude Apr 22, 2025
ab180f7
Merge pull request #15 from Gold-Rush-Robotics/feature/yaml-config
dudebehinddude Apr 22, 2025
7517f72
Implement node status and node health
dudebehinddude Apr 23, 2025
d7138c9
Remove old debug console.logs
dudebehinddude Apr 23, 2025
7013d26
Remove unnecessary console styling
dudebehinddude Apr 23, 2025
b645eb6
Merge branch 'main' of https://github.com/Gold-Rush-Robotics/hmi into…
dudebehinddude Apr 23, 2025
f00a6bd
It would help if I actually resolved merge conflicts correctly
dudebehinddude Apr 23, 2025
bc77324
Make Dashboard match new UI styling
dudebehinddude Apr 23, 2025
e6bbc81
Make start/stopped state based on last '/hmi_start_stop' topic message
dudebehinddude Apr 24, 2025
fd5fbd5
Properly implement running time and add a status history with extende…
dudebehinddude Apr 24, 2025
8f3fb43
Show extendedStatus under current task in dashboard
dudebehinddude Apr 24, 2025
5d7abf6
Minor documentation updates
dudebehinddude Apr 24, 2025
7b18fec
Fix EStop test
dudebehinddude Apr 24, 2025
64d4d5e
Improve handling of some elements on overflow
dudebehinddude Jul 29, 2025
0ca5026
Proper dark theming, and minor console/dashboard redesign
dudebehinddude Jul 30, 2025
332dbbe
Fix websocket tests when rosIp is changed from default in configs
dudebehinddude Jul 30, 2025
183f35b
Make task history dialog
dudebehinddude Sep 17, 2025
badbd28
Switch console font to Cascadia Code (freely licensed by Microsoft)
dudebehinddude Sep 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions app/.env.example

This file was deleted.

3 changes: 3 additions & 0 deletions app/config/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# The WebSocket IP to use to connect to ROS.
# Default: "ws://127.0.0.1:9090".
rosIp: "ws://127.0.0.1:9090"
4 changes: 4 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/inter": "^5.1.1",
"@modyfi/vite-plugin-yaml": "^1.1.1",
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.7",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@types/js-yaml": "^4.0.9",
"ajv": "8",
"date-fns": "^4.1.0",
"js-yaml": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
Binary file added app/public/fonts/CascadiaCode/CascadiaCode.woff2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added app/public/fonts/CascadiaCode/CascadiaMono.woff2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added app/public/yes-yes-sir.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 19 additions & 7 deletions app/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import { Box, Grid2 as Grid } from "@mui/material";
import { useContext, useState } from "react";
import { Status } from "../types/status";
import Console from "./Console/Console";
import Dashboard from "./Dashboard/Dashboard";
import NavBar from "./NavBar/NavBar";
import NodeManager from "./NodeManager/NodeManager";
import { GlobalStatusContext } from "./Providers/ROSProvider";
import "/src/css/App.css";

/**
* The entry point of the program.
*/
function App() {
const [selectedNode, setSelectedNode] = useState<string | null>(null);
const { globalStatus, setGlobalStatus } = useContext(GlobalStatusContext);
const { globalStatusHistory, setGlobalStatusHistory } =
useContext(GlobalStatusContext);

const mainSection =
selectedNode == null ? (
<Dashboard />
) : (
<Console
selectedNode={selectedNode}
clearSelectedNode={() => setSelectedNode(null)}
/>
);

return (
<Box sx={{ height: "100dvh", display: "flex", flexDirection: "column" }}>
<NavBar status={globalStatus} setStatus={setGlobalStatus} />
<NavBar
status={globalStatusHistory.at(-1)?.status ?? Status.Unknown}
setStatus={setGlobalStatusHistory}
/>
<Grid
container
spacing={0}
Expand All @@ -31,10 +46,7 @@ function App() {
/>
</Grid>
<Grid size={8} height="100%">
<Console
selectedNode={selectedNode}
clearSelectedNode={() => setSelectedNode(null)}
/>
{mainSection}
</Grid>
</Grid>
</Box>
Expand Down
41 changes: 25 additions & 16 deletions app/src/components/Console/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function Console({ ...props }: Console) {
const consoleTitle = props.selectedNode
? `Console: ${props.selectedNode}`
: "Console";
const visibility = props.selectedNode ? undefined : { visibility: "hidden" };
const consoleOutputRef = useRef<HTMLDivElement>(null);
const rawSocketHistory = useContext(WSHistoryContext);
const filteredSocketHistory = rawSocketHistory[props.selectedNode || ""];
Expand Down Expand Up @@ -59,7 +58,7 @@ function Console({ ...props }: Console) {
// This isn't perfect but it mostly works.
if (!autoScroll && socketHistory.length === maxHistoryLength) {
const { current } = consoleOutputRef;
current.scrollTop -= 14; // 14px represents roughly 1 line
current.scrollTop -= 12; // 12px represents roughly 1 line
}
}, [socketHistory, autoScroll]);

Expand Down Expand Up @@ -97,9 +96,10 @@ function Console({ ...props }: Console) {
return `[${format(msg.timestamp, "HH:mm:ss.SSS")}] [${msg.topic}] ${msg.message}`;
})
.join("\n");
if (text === "")
if (text === "") {
text =
"No messages received yet. Perhaps you should try changing your filters?";
}
if (socketHistory.length === maxHistoryLength) {
text = `...history limited to ${maxHistoryLength} lines\n${text}`;
}
Expand All @@ -112,39 +112,42 @@ function Console({ ...props }: Console) {
height: "inherit",
display: "flex",
flexDirection: "column",
borderRadius: "20px 0 0 0",
borderRadius: "12px 0 0 0",
}}
>
<Box sx={{ pl: 2, pr: 2, pt: 1.5 }}>
<Box sx={{ px: 1.5, py: 1 }}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{ mb: 1 }}
>
<Typography
variant="h6"
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: 600,
}}
>
{consoleTitle}
</Typography>
<IconButton
onClick={props.clearSelectedNode}
sx={{
borderRadius: 25,
borderRadius: 20,
scale: 0.95,
border: "1px solid",
borderColor: "lightgray",
borderColor: "divider",
bgcolor: "#404040",
":hover": {
borderColor: "gray",
borderColor: "primary.main",
bgcolor: "#505050",
},
...visibility,
}}
>
<Close />
<Close sx={{ fontSize: "1.2rem" }} />
</IconButton>
</Stack>
<ConsoleFilters
Expand All @@ -157,18 +160,24 @@ function Console({ ...props }: Console) {
ref={consoleOutputRef}
onScroll={checkEnableAutoScroll}
sx={{
bgcolor: "#222",
height: "60vh",
color: "#eee",
bgcolor: "background.default",
textAlign: "left",
overflow: "scroll",
flexGrow: 1,
borderRadius: "6px",
m: 1,
p: 1,
p: 1.5,
}}
>
<pre style={{ margin: 0, padding: 0, whiteSpace: "pre-wrap" }}>
<pre
style={{
margin: 0,
padding: 0,
whiteSpace: "pre-wrap",
fontSize: "0.75rem",
lineHeight: 1.2,
fontFamily: "Cascadia Code, monospace",
}}
>
{renderConsoleText()}
</pre>
</Paper>
Expand Down
42 changes: 35 additions & 7 deletions app/src/components/Console/ConsoleFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
}

if (enabledChips.length + disabledChips.length === 0) {
return <Chip label="No topics in node!" sx={{ ml: 1 }} />;
return (
<Chip
label="No topics in node!"
sx={{ ml: 0.5, height: "28px", fontSize: "0.8rem" }}
/>
);
}
return [...enabledChips, ...disabledChips]; // so they are organized :)
}
Expand All @@ -46,7 +51,15 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
<Chip
key={topic}
label={topic}
sx={{ ml: 1 }}
size="small"
sx={{
ml: 0.5,
height: "28px",
fontSize: "0.8rem",
"& .MuiChip-label": {
px: 1.5,
},
}}
color="primary"
onDelete={() => disableTopic(topic)}
onClick={() => disableTopic(topic)}
Expand All @@ -57,7 +70,15 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
<Chip
key={topic}
label={topic}
sx={{ ml: 1 }}
size="small"
sx={{
ml: 0.5,
height: "28px",
fontSize: "0.8rem",
"& .MuiChip-label": {
px: 1.5,
},
}}
variant="outlined"
onClick={() => enableTopic(topic)}
/>
Expand Down Expand Up @@ -97,10 +118,16 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
return (
<ToggleButton
sx={{
borderRadius: 10,
scale: 0.85,
borderRadius: 8,
scale: 0.8,
height: "28px",
minWidth: "36px",
border: "1px solid",
borderColor: "divider",
bgcolor: "#404040",
":hover": {
borderColor: "gray",
borderColor: "primary.main",
bgcolor: "#505050",
},
}}
size="small"
Expand All @@ -110,7 +137,7 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
setShowFilters((prev) => !prev);
}}
>
<FilterList />
<FilterList sx={{ fontSize: "1rem" }} />
</ToggleButton>
);
}
Expand All @@ -129,6 +156,7 @@ function ConsoleFilters({ ...props }: ConsoleFilters) {
},
msOverflowStyle: "none", // IE and Edge
whiteSpace: "nowrap", // Keeps content in a single line
py: 0.5,
}}
>
{renderToggleShowButton()}
Expand Down
74 changes: 74 additions & 0 deletions app/src/components/Dashboard/CurrentTask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Typography } from "@mui/material";
import { useContext, useState } from "react";
import { formatTimeDifference } from "../../util/util";
import { GlobalStatusContext } from "../Providers/ROSProvider";
import DashboardCard from "./DashboardCard";
import TaskHistoryDialog from "./TaskHistoryDialog";

/**
* "Current Task" section of the dashboard, displays the extended
* status of the latest status message (or "Unknown if that is
* undefined"). The latest status should be the last item in the
* `globalStatusHistory` array.
*/
function CurrentTask() {
const { globalStatusHistory } = useContext(GlobalStatusContext);
const [historyDialogOpen, setHistoryDialogOpen] = useState(false);

const handleOpenHistory = () => {
setHistoryDialogOpen(true);
};

const handleCloseHistory = () => {
setHistoryDialogOpen(false);
};

const prevTasks = globalStatusHistory.length - 1;

return (
<>
<DashboardCard
title="Current Task"
action={{
label: "History",
onClick: handleOpenHistory,
}}
>
<Typography
sx={{
color: "text.secondary",
fontSize: "0.8rem",
lineHeight: 1.3,
}}
>
{prevTasks} previous task
{/* Make plural when prevTasks != 1 */}
{prevTasks !== 1 && "s"}
</Typography>
<Typography
sx={{
color: "text.secondary",
fontSize: "0.8rem",
lineHeight: 1.3,
}}
>
{globalStatusHistory.at(-1)?.extendedStatus ?? "Unknown"} (
{!!globalStatusHistory.at(-1)?.timestamp &&
formatTimeDifference(
globalStatusHistory.at(-1)!.timestamp,
new Date(),
)}
)
</Typography>
</DashboardCard>

<TaskHistoryDialog
open={historyDialogOpen}
onClose={handleCloseHistory}
statusHistory={globalStatusHistory}
/>
</>
);
}

export default CurrentTask;
Loading