update visual design system

This commit is contained in:
2026-05-24 19:47:05 +08:00
parent 1850c58197
commit 43d8563b79
11 changed files with 452 additions and 300 deletions

View File

@@ -2,35 +2,51 @@ import React from 'react';
import { Typography, Box, Paper, Divider, Link, Grid, Card, CardContent, List, ListItem, ListItemIcon, ListItemText, Stack, Chip, Button } from '@mui/material';
import packageInfo from '../package.json';
import vimdronesLogo from './image/vimdrones_logo.png';
import discordLogo from './image/discord_logo.png'; // Import Discord logo
import discordLogo from './image/discord_logo.png';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import UsbIcon from '@mui/icons-material/Usb';
import WifiIcon from '@mui/icons-material/Wifi';
import ComputerIcon from '@mui/icons-material/Computer';
// Remove the DiscordIcon import since we're now using a custom image
const About = () => {
return (
<Box sx={{ mx: 'auto', p: 2, width: '100%', flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 1 }} component={Paper} elevation={2}>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', mb: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'baseline' }}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 600 }}>
<Box
component={Paper}
elevation={0}
sx={{
mx: 'auto',
p: 3,
width: '100%',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
gap: 2,
backgroundColor: 'background.paper',
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', mb: 0.5 }}>
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'baseline', gap: 1 }}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 400, letterSpacing: '-0.03em' }}>
DroneCAN Web Tools
</Typography>
<Chip
<Chip
label={`v${packageInfo.version}`}
size="small"
color="primary"
variant="outlined"
sx={{ ml: 2 }}
/>
</Box>
<Typography variant="body2" sx={{ mt: 1, color: 'text.secondary', textAlign: 'center', maxWidth: 720 }}>
A browser-based console for discovery, parameters, firmware, monitoring, and panel workflows.
</Typography>
</Box>
<Card variant="outlined">
<Card variant="outlined" sx={{ backgroundColor: 'background.default' }}>
<CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}>
<Typography variant="h6" sx={{ mb: 1.5, fontWeight: 500, color: 'primary.main' }}>Key Features</Typography>
<Typography variant="h6" sx={{ mb: 1.5, fontWeight: 600, color: 'primary.main' }}>Key Features</Typography>
<Grid container spacing={1}>
<Grid item xs={12} sm={6}>
<List dense disablePadding>
@@ -59,22 +75,22 @@ const About = () => {
</Grid>
</CardContent>
</Card>
<Stack spacing={2} direction={{ xs: 'column', md: 'row' }}>
<Card variant="outlined" sx={{ flex: 1 }}>
<Card variant="outlined" sx={{ flex: 1, backgroundColor: 'background.paper' }}>
<CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<WifiIcon color="primary" sx={{ mr: 1 }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>WebSocket via MAVProxy</Typography>
</Box>
<Typography variant="body2" sx={{ bgcolor: 'action.hover', p: 1, borderRadius: 1, fontFamily: 'monospace' }}>
<Typography variant="body2" sx={{ bgcolor: 'background.default', p: 1.25, borderRadius: 1, fontFamily: 'monospace', border: '1px solid', borderColor: 'divider' }}>
mavproxy.py --master /dev/tty.usbmodem111401,115200 --out wsserver:127.0.0.1:5555
</Typography>
<Typography variant="body2" sx={{ mt: 1, color: 'text.secondary' }}>
Connect to <Box component="span" sx={{ fontWeight: 'bold' }}>ws://127.0.0.1:5555</Box> in the Adapter Settings
Connect to <Box component="span" sx={{ fontWeight: 'bold' }}>ws://127.0.0.1:5555</Box> in the Adapter Settings
</Typography>
<Box sx={{ mt: 2, pt: 1, borderTop: '1px dashed', borderColor: 'divider' }}>
<Box sx={{ mt: 2, pt: 1, borderTop: '1px solid', borderColor: 'divider' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}>
<ComputerIcon color="info" sx={{ mr: 1, fontSize: '1rem' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
@@ -87,8 +103,8 @@ const About = () => {
</Box>
</CardContent>
</Card>
<Card variant="outlined" sx={{ flex: 1 }}>
<Card variant="outlined" sx={{ flex: 1, backgroundColor: 'background.paper' }}>
<CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<UsbIcon color="primary" sx={{ mr: 1 }} />
@@ -109,23 +125,23 @@ const About = () => {
</ListItem>
<ListItem disablePadding sx={{ py: 0.25 }}>
<ListItemIcon sx={{ minWidth: 30 }}>
<CheckCircleOutlineIcon color="success" fontSize="small" /> {/* Changed from info to success */}
<CheckCircleOutlineIcon color="success" fontSize="small" />
</ListItemIcon>
<ListItemText
primary="Standalone MAVCAN USB Adaptor"
<ListItemText
primary="Standalone MAVCAN USB Adaptor"
secondary={
<Box component="span">
<Link
<Link
href="https://github.com/VimDrones/MAVCAN_Bridge"
target="_blank"
target="_blank"
rel="noopener"
sx={{ fontSize: '0.75rem' }}
>
MAVCAN Bridge
</Link>
<Typography
component="span"
variant="caption"
<Typography
component="span"
variant="caption"
sx={{ ml: 0.5, fontSize: '0.75rem', color: 'text.secondary' }}
>
from Vimdrones
@@ -138,22 +154,21 @@ const About = () => {
</CardContent>
</Card>
</Stack>
{/* Footer */}
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end'}}>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
<Divider sx={{ my: 1.5 }} />
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1,
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', sm: 'center' }
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1,
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', sm: 'center' }
}}>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1, mr: 5, alignItems: { xs: 'flex-start', sm: 'center' } }}>
<Typography variant="body2" color="text.secondary">
Author: <Link href="https://github.com/huibean" target="_blank" rel="noopener" sx={{ fontWeight: 500 }}>Huibean Luo</Link>
</Typography>
<Button
variant="outlined"
size="small"
@@ -161,7 +176,7 @@ const About = () => {
href="https://discord.gg/xxCKsZXU4K"
target="_blank"
rel="noopener"
sx={{
sx={{
borderRadius: 4,
textTransform: 'none',
fontSize: '0.75rem',
@@ -194,10 +209,10 @@ const About = () => {
}
}}
>
<img
<img
style={{borderRadius: '50%'}}
src={discordLogo}
alt="Discord"
src={discordLogo}
alt="Discord"
/>
</Box>
Vimdrones
@@ -210,7 +225,7 @@ const About = () => {
href="https://discord.gg/vz7a499KXN"
target="_blank"
rel="noopener"
sx={{
sx={{
borderRadius: 4,
textTransform: 'none',
fontSize: '0.75rem',
@@ -243,24 +258,24 @@ const About = () => {
}
}}
>
<img
<img
style={{borderRadius: '50%'}}
src={discordLogo}
alt="Discord"
src={discordLogo}
alt="Discord"
/>
</Box>
DroneCAN
</Button>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
Sponsored by
</Typography>
<Link href="https://dev.vimdrones.com" target="_blank" rel="noopener">
<img
src={vimdronesLogo}
alt="Vimdrones"
<img
src={vimdronesLogo}
alt="Vimdrones"
style={{ height: '30px' }}
/>
</Link>
@@ -271,4 +286,4 @@ const About = () => {
);
}
export default About;
export default About;

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { AppBar, Toolbar, Typography, Box, Button, ThemeProvider, IconButton, Snackbar, Alert, Tooltip, FormControl, Select, MenuItem, InputLabel, Chip } from '@mui/material';
import { AppBar, Toolbar, Typography, Box, Button, ThemeProvider, IconButton, Snackbar, Alert, Tooltip, FormControl, Select, MenuItem } from '@mui/material';
import MavlinkSession from './mavlink_session';
import dronecan from './dronecan';
import theme from './theme';
@@ -97,7 +97,7 @@ const AppContent = () => {
const newBus = event.target.value;
if (newBus === selectedBus) return;
setSelectedBus(newBus);
if (window.localNode) {
window.localNode.changeBus(newBus);
showMessage(t('app.bus_switched', { bus: newBus }), 'info');
@@ -140,40 +140,43 @@ const AppContent = () => {
}
};
const surfaceBorder = '1px solid rgba(230, 223, 216, 0.95)';
return (
<>
<AppBar position="static">
<Toolbar variant="dense" sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Box sx={{width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center'}}>
<ToolsMenu openWindow={openWindow.bind(this)} />
<PanelsMenu openWindow={openWindow.bind(this)} />
<AppBar position="static" color="transparent" elevation={0} sx={{ borderBottom: surfaceBorder }}>
<Toolbar variant="dense" sx={{ display: 'flex', justifyContent: 'space-between', minHeight: 64, px: 1.5, gap: 1 }}>
<Box sx={{ width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
<ToolsMenu openWindow={openWindow} />
<PanelsMenu openWindow={openWindow} />
</Box>
<Box sx={{flexGrow: 2, display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center'}}>
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center'}} ml={0.5} mr={0.5}>
<a href="https://dev.vimdrones.com" target="_blank" rel="noreferrer" style={{height: 30}}>
<img src={DronecanLogo} alt="DroneCAN" style={{ height: 30}} />
<Box sx={{ flexGrow: 2, display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<a href="https://dev.vimdrones.com" target="_blank" rel="noreferrer" style={{ height: 30, display: 'flex', alignItems: 'center' }}>
<img src={DronecanLogo} alt="DroneCAN" style={{ height: 30 }} />
</a>
</Box>
<Typography variant="caption">
<Typography variant="body2" sx={{ fontWeight: 600, letterSpacing: 0.2 }}>
{t('app.title')}
</Typography>
</Box>
<Box sx={{width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', gap: 1}}>
<ConnectionIndicators
<Box sx={{ width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', gap: 1 }}>
<ConnectionIndicators
isConnected={isConnected}
mavlinkSession={window.mavlinkSession}
localNode={window.localNode}
/>
<FormControl
size="small"
sx={{
minWidth: 80,
<FormControl
size="small"
sx={{
minWidth: 92,
'& .MuiOutlinedInput-root': {
height: 30,
height: 32,
backgroundColor: '#faf9f5',
},
'& .MuiSelect-select': {
paddingTop: 0.5,
paddingTop: 0.5,
paddingBottom: 0.5,
fontSize: '0.8rem'
}
@@ -195,11 +198,11 @@ const AppContent = () => {
</Select>
</FormControl>
<Button
variant="outlined"
color={dnaServerActive ? "success" : "primary"}
<Button
variant="outlined"
color={dnaServerActive ? 'success' : 'primary'}
startIcon={
dnaServerActive ?
dnaServerActive ?
<DnsIcon sx={{
animation: 'pulse 1.5s infinite',
'@keyframes pulse': {
@@ -207,27 +210,27 @@ const AppContent = () => {
'50%': { opacity: 1 },
'100%': { opacity: 0.6 },
}
}} /> :
}} /> :
<DnsIcon />
}
onClick={handleToggleDnaServer}
sx={dnaServerActive ? {
borderColor: 'success.main',
'&:hover': {
backgroundColor: 'rgba(76, 175, 80, 0.08)',
backgroundColor: 'rgba(93, 184, 114, 0.08)',
borderColor: 'success.dark'
}
} : {}}
>
{t('app.dna')}
</Button>
<Tooltip title={language === 'en' ? '中文' : 'English'}>
<IconButton
size="small"
color="inherit"
onClick={() => setLanguage(language === 'en' ? 'zh' : 'en')}
sx={{ mr: 0.5 }}
sx={{ mr: 0.5, border: '1px solid rgba(230, 223, 216, 0.95)', backgroundColor: '#faf9f5' }}
>
<LanguageIcon fontSize="small" />
</IconButton>
@@ -244,47 +247,47 @@ const AppContent = () => {
</Box>
</Toolbar>
</AppBar>
<Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, height: '95vh', gap: 0.5, p: 1 }}>
<CompactSidebar
<Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, minHeight: 'calc(100vh - 64px)', gap: 0.75, p: 1, backgroundColor: '#faf9f5' }}>
<CompactSidebar
nodes={nodes}
selectedNodeId={selectedNodeId}
setSelectedNodeId={setSelectedNodeId}
nodesUpdateTimestamp={nodesUpdateTimestamp}
/>
<Box
sx={{
minWidth: 550,
display: { xs: 'none', md: 'flex' },
flexDirection: 'column',
gap: 0.5,
<Box
sx={{
minWidth: 550,
display: { xs: 'none', md: 'flex' },
flexDirection: 'column',
gap: 0.75,
}}
>
<NodeList
nodes={nodes}
selectedNodeId={selectedNodeId}
setSelectedNodeId={setSelectedNodeId.bind(this)}
setSelectedNodeId={setSelectedNodeId}
nodesUpdateTimestamp={nodesUpdateTimestamp}
/>
<NodeLogs/>
<NodeLogs/>
</Box>
<Box
display="flex"
flexDirection="column"
<Box
display="flex"
flexDirection="column"
sx={{
gap: 0.5,
gap: 0.75,
ml: { xs: 1, md: 0 },
flexGrow: 1,
}}
>
{selectedNodeId && (
<Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, gap: 0.5 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, gap: 0.75 }}>
<NodeProperties
nodeId={selectedNodeId}
nodes={nodes}
multiNodeEditorEnable={multiNodeEditorEnable}
setMultiNodeEditorEnable={setMultiNodeEditorEnable.bind(this)}
setMultiNodeEditorEnable={setMultiNodeEditorEnable}
nodesUpdateTimestamp={nodesUpdateTimestamp}
/>
<NodeParam
@@ -304,17 +307,17 @@ const AppContent = () => {
open={modalOpen}
onClose={handleCloseModal}
onConnectionStatusChange={handleConnectionStatusChange}
showMessage={showMessage.bind(this)}
showMessage={showMessage}
selectedBus={selectedBus}
onBusChange={handleBusChange}
/>
<Snackbar
open={snackbarOpen}
autoHideDuration={6000}
open={snackbarOpen}
autoHideDuration={6000}
onClose={() => setSnackbarOpen(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={() => setSnackbarOpen(false)} severity={snackbarSeverity}>
<Alert onClose={() => setSnackbarOpen(false)} severity={snackbarSeverity} variant="filled">
{snackbarMessage}
</Alert>
</Snackbar>
@@ -322,4 +325,4 @@ const AppContent = () => {
);
};
export default App;
export default App;

View File

@@ -2,26 +2,22 @@ import React, { useEffect, useState } from 'react';
import { Box, Badge, Tooltip, CircularProgress, Typography } from '@mui/material';
import NotificationsIcon from '@mui/icons-material/Notifications';
const CompactSidebar = ({
nodes,
selectedNodeId,
setSelectedNodeId
const CompactSidebar = ({
nodes,
selectedNodeId,
setSelectedNodeId
}) => {
const [logCounts, setLogCounts] = useState({});
useEffect(() => {
// Function to get log counts from the window.localNode events
const updateLogCounts = () => {
const counts = {};
// Initialize counts for all nodes to 0
Object.keys(nodes).forEach(nodeId => {
counts[nodeId] = 0;
});
try {
// Access logs if they're stored somewhere else in the app
// Option 1: If logs are stored in a global state/context
if (window.dronecanLogs && Array.isArray(window.dronecanLogs)) {
window.dronecanLogs.forEach(log => {
if (log.id) {
@@ -29,63 +25,54 @@ const CompactSidebar = ({
}
});
}
// Option 2: Listen for log events and maintain our own count
// This is already set up in the useEffect for event listening below
} catch (error) {
console.error("Error accessing logs:", error);
}
setLogCounts(counts);
};
// Create log listener and counter
let lastLogCounts = {};
const handleLog = (transfer) => {
const sourceNodeId = transfer.sourceNodeId;
if (sourceNodeId) {
// Update count for this node
lastLogCounts[sourceNodeId] = (lastLogCounts[sourceNodeId] || 0) + 1;
setLogCounts({...lastLogCounts});
}
};
// Listen for log messages
const localNode = window.localNode;
if (localNode) {
localNode.on('uavcan.protocol.debug.LogMessage', handleLog);
}
// Initial update
updateLogCounts();
return () => {
// Remove event listener on cleanup
if (localNode) {
localNode.off('uavcan.protocol.debug.LogMessage', handleLog);
}
};
}, [nodes]);
// Get color based on node mode (matching the same logic as NodeList)
const getModeColor = (mode) => {
switch (mode) {
case 'OPERATIONAL':
return '#f5f5f5'; // Light gray for operational (subtle)
return '#5db872';
case 'INITIALIZATION':
return '#ffb74d'; // Warning color
return '#d4a017';
case 'MAINTENANCE':
return '#9c27b0'; // Secondary/purple
return '#5db8a6';
case 'SOFTWARE_UPDATE':
return '#4caf50'; // Success/green
return '#5db872';
case 'OFFLINE':
return '#f44336'; // Error/red
return '#c64545';
default:
return '#f44336'; // Default to error color
return '#c64545';
}
};
// Handle click on a node
const handleNodeClick = (nodeId) => {
if (nodeId === selectedNodeId) {
setSelectedNodeId(null);
@@ -93,10 +80,10 @@ const CompactSidebar = ({
setSelectedNodeId(Number(nodeId));
}
};
return (
<Box
sx={{
<Box
sx={{
display: { xs: 'flex', md: 'none', alignItems: 'center' },
flexDirection: 'column',
width: '60px',
@@ -105,17 +92,19 @@ const CompactSidebar = ({
p: 1,
gap: 1,
overflowY: 'auto',
height: '100%'
height: '100%',
backgroundColor: 'background.paper',
}}
>
<Typography
variant="caption"
sx={{
<Typography
variant="caption"
sx={{
textAlign: 'center',
display: 'block',
mb: 0.5,
fontWeight: 'bold',
fontSize: '0.5rem'
fontWeight: 700,
fontSize: '0.5rem',
color: 'text.secondary',
}}
>
NODES
@@ -130,11 +119,12 @@ const CompactSidebar = ({
const node = nodes[nodeId];
const mode = node.status.getConstant('mode');
const logCount = logCounts[nodeId] || 0;
const selected = selectedNodeId === Number(nodeId);
return (
<Tooltip
<Tooltip
key={nodeId}
title={`${node.name || 'Unknown'} (${mode})`}
title={`${node.name || 'Unknown'} (${mode})`}
placement="right"
>
<Box
@@ -148,25 +138,25 @@ const CompactSidebar = ({
justifyContent: 'center',
borderRadius: 1,
border: '1px solid',
borderColor: selectedNodeId === Number(nodeId) ? 'primary.main' : 'divider',
backgroundColor: 'background.paper',
borderColor: selected ? 'primary.main' : 'divider',
backgroundColor: selected ? 'rgba(204, 120, 92, 0.08)' : 'background.default',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'action.hover',
backgroundColor: 'rgba(245, 240, 232, 0.9)',
}
}}
onClick={() => handleNodeClick(Number(nodeId))}
>
{/* Make NID larger and more prominent */}
<Typography
<Typography
variant='caption'
sx={{
color: getModeColor(mode),
fontWeight: 600,
}}
>
{nodeId}
</Typography>
{logCount > 0 && (
<Badge
badgeContent={logCount > 99 ? '99+' : logCount}
@@ -190,4 +180,4 @@ const CompactSidebar = ({
);
};
export default CompactSidebar;
export default CompactSidebar;

View File

@@ -7,29 +7,29 @@ const ConnectionIndicators = ({ isConnected, mavlinkSession, localNode }) => {
const [rxActive, setRxActive] = useState(false);
const txTimeout = useRef(null);
const rxTimeout = useRef(null);
useEffect(() => {
const handleFrameSend = () => {
setTxActive(true);
clearTimeout(txTimeout.current);
txTimeout.current = setTimeout(() => {
setTxActive(false);
}, 100); // Blink for 200ms
}, 100);
};
const handleFrameReceive = () => {
setRxActive(true);
clearTimeout(rxTimeout.current);
rxTimeout.current = setTimeout(() => {
setRxActive(false);
}, 100); // Blink for 200ms
}, 100);
};
if (mavlinkSession) {
mavlinkSession.on('mav-tx', handleFrameSend);
mavlinkSession.on('mav-rx', handleFrameReceive);
}
return () => {
if (mavlinkSession) {
mavlinkSession.removeListener('mav-tx', handleFrameSend);
@@ -39,14 +39,14 @@ const ConnectionIndicators = ({ isConnected, mavlinkSession, localNode }) => {
clearTimeout(rxTimeout.current);
};
}, [localNode, mavlinkSession]);
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mr: 5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mr: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', opacity: isConnected ? 1 : 0.3 }}>
<CircleIcon
fontSize="small"
sx={{
color: txActive && isConnected ? '#4caf50' : '#7e7e7e',
<CircleIcon
fontSize="small"
sx={{
color: txActive && isConnected ? '#5db872' : '#8e8b82',
width: '12px',
height: '12px',
transition: 'color 0.1s ease'
@@ -54,12 +54,12 @@ const ConnectionIndicators = ({ isConnected, mavlinkSession, localNode }) => {
/>
<Typography variant="caption" sx={{ ml: 0.5, color: 'text.secondary' }}>TX</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', opacity: isConnected ? 1 : 0.3 }}>
<CircleIcon
<CircleIcon
fontSize="small"
sx={{
color: rxActive && isConnected ? '#2196f3' : '#7e7e7e',
sx={{
color: rxActive && isConnected ? '#5db8a6' : '#8e8b82',
width: '12px',
height: '12px',
transition: 'color 0.1s ease'
@@ -71,4 +71,4 @@ const ConnectionIndicators = ({ isConnected, mavlinkSession, localNode }) => {
);
};
export default ConnectionIndicators;
export default ConnectionIndicators;

View File

@@ -29,16 +29,27 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
};
const renderNodeRow = (key) => {
let node = nodes[key];
let status = node.status;
let health = status.getConstant('health');
let mode = status.getConstant('mode');
const node = nodes[key];
const status = node.status;
const health = status.getConstant('health');
const mode = status.getConstant('mode');
const isSelected = Number(key) === Number(selectedNodeId);
return (
<TableRow key={key} onClick={() => handleRowClick((Number(key)))} style={{ cursor: 'pointer' }}>
<TableRow
key={key}
onClick={() => handleRowClick(Number(key))}
sx={{
cursor: 'pointer',
backgroundColor: isSelected ? 'rgba(204, 120, 92, 0.08)' : 'transparent',
'&:hover': {
backgroundColor: 'rgba(245, 240, 232, 0.9)',
},
}}
>
<TableCell>{key}</TableCell>
<TableCell>{node.name}</TableCell>
<TableCell sx={{ width: 150 }}>{node.name}</TableCell>
<TableCell>{health}</TableCell>
<TableCell sx={{bgcolor: getModeColor(mode)}}>{mode}</TableCell>
<TableCell sx={{ bgcolor: getModeColor(mode), color: getModeColor(mode) ? 'common.white' : 'inherit' }}>{mode}</TableCell>
<TableCell>{secondsToTime(status.uptime_sec)}</TableCell>
<TableCell>{node.status.vendor_specific_status_code}</TableCell>
</TableRow>
@@ -48,25 +59,17 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
return (
<Box
component={Paper}
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, height: '50%'}}
sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, height: '50%', backgroundColor: 'background.paper', border: '1px solid', borderColor: 'divider' }}
>
<Box margin={1} sx={{height: 20}}>
<Typography variant="caption">Online Nodes</Typography>
<Box margin={1} sx={{ height: 20 }}>
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600, letterSpacing: 0.2 }}>Online Nodes</Typography>
</Box>
<TableContainer
sx={{ overflow: 'auto' }}
>
<TableContainer sx={{ overflow: 'auto' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>NID</TableCell>
<TableCell
sx={{
width: 150,
}}
>
Name
</TableCell>
<TableCell>Name</TableCell>
<TableCell>Health</TableCell>
<TableCell>Mode</TableCell>
<TableCell>Uptime</TableCell>
@@ -74,9 +77,7 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
</TableRow>
</TableHead>
<TableBody>
{Object.keys(nodes).map((key) => (
renderNodeRow(key)
))}
{Object.keys(nodes).map((key) => renderNodeRow(key))}
</TableBody>
</Table>
</TableContainer>
@@ -84,4 +85,4 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
);
};
export default NodeList;
export default NodeList;

View File

@@ -16,7 +16,6 @@ const NodeLogs = () => {
if (paused) {
return;
}
// console.log(transfer);
const msg = transfer.payload;
const msgObj = msg.toObj();
setLogs((logs) => [...logs, {
@@ -24,7 +23,7 @@ const NodeLogs = () => {
localTime: new Date().toLocaleTimeString(),
level: msgObj.level.getConstant('value'),
source: '',
text: msgObj.text
text: msgObj.text
}]);
};
localNode.on('uavcan.protocol.debug.LogMessage', handleLog);
@@ -51,21 +50,22 @@ const NodeLogs = () => {
return (
<Box
component={Paper}
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, height: '50%'}}
sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, height: '50%', backgroundColor: 'background.paper', border: '1px solid', borderColor: 'divider' }}
>
<Box sx={{
display: 'flex',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
alignItems: 'center',
height: 20
}} margin={1}>
<Typography variant="caption" flexGrow={1}>{t('logs.title')}</Typography>
<Typography variant="caption" flexGrow={1} sx={{ color: 'text.secondary', fontWeight: 600, letterSpacing: 0.2 }}>{t('logs.title')}</Typography>
<Box sx={{ display: 'flex', gap: 0.5 }}>
<IconButton
sx={{
width: 20,
sx={{
width: 20,
height: 20,
padding: 0
padding: 0,
color: 'text.secondary',
}}
size='small'
onClick={() => setPaused(!paused)}
@@ -73,22 +73,20 @@ const NodeLogs = () => {
{paused ? <PlayArrowIcon sx={{ fontSize: 16 }} /> : <PauseIcon sx={{ fontSize: 16 }} />}
</IconButton>
<IconButton
sx={{
width: 20,
sx={{
width: 20,
height: 20,
padding: 0
padding: 0,
color: 'warning.main',
}}
size='small'
color="warning"
onClick={() => setLogs([])}
>
<CleaningServicesIcon sx={{ fontSize: 16 }} />
</IconButton>
</Box>
</Box>
<TableContainer
sx={{ overflow: 'auto' }}
>
<TableContainer sx={{ overflow: 'auto' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
@@ -101,10 +99,10 @@ const NodeLogs = () => {
</TableHead>
<TableBody>
{logs.map((log, index) => (
<TableRow key={index}>
<TableRow key={index} sx={{ '&:hover': { backgroundColor: 'rgba(245, 240, 232, 0.9)' } }}>
<TableCell>{log.id}</TableCell>
<TableCell>{log.localTime}</TableCell>
<TableCell sx={{bgcolor: getLevelColor(log.level)}}>
<TableCell sx={{ bgcolor: getLevelColor(log.level), color: getLevelColor(log.level) ? 'common.white' : 'inherit' }}>
{log.level}
</TableCell>
<TableCell>{log.source}</TableCell>
@@ -120,4 +118,4 @@ const NodeLogs = () => {
);
};
export default NodeLogs;
export default NodeLogs;

View File

@@ -67,16 +67,16 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
return (
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', height: 340}}
sx={{ flexGrow: 1, bgcolor: 'background.paper', height: 340, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}
component={Paper}
p={1}
>
<Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="caption">
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600, letterSpacing: 0.2 }}>
{t('props.title')}
</Typography>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
<Typography variant="caption">{t('props.multi_editor')}</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>{t('props.multi_editor')}</Typography>
<Switch checked={multiNodeEditorEnable} onChange={(e) => { setMultiNodeEditorEnable(e.target.checked) }} />
</Stack>
</Box>
@@ -199,7 +199,7 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
>
{t('props.controls')}
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, border: 1, borderColor: 'grey.500', borderRadius: 1, p: 0.5 }}>
<Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, border: 1, borderColor: 'divider', borderRadius: 1, p: 0.5, backgroundColor: 'background.default' }}>
<Button
sx={{ mr: 1 }}
color="error"
@@ -226,10 +226,10 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
</Button>
</Box>
</Box>
<FirmwareUpdateModal
open={firmwareModalOpen}
onClose={() => setFirmwareModalOpen(false)}
targetNodeId={nodeId}
<FirmwareUpdateModal
open={firmwareModalOpen}
onClose={() => setFirmwareModalOpen(false)}
targetNodeId={nodeId}
/>
<ConfirmRestartModal
open={restartModalOpen}
@@ -240,4 +240,4 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
);
};
export default NodeProperties;
export default NodeProperties;

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Box, Button, Menu, MenuItem, Divider } from '@mui/material';
import { Box, Button, Menu, MenuItem } from '@mui/material';
import VideogameAssetIcon from '@mui/icons-material/VideogameAsset';
import { useTranslation } from './i18n/LanguageContext';
@@ -41,8 +41,12 @@ const PanelsMenu = ({openWindow}) => {
aria-expanded={open ? 'true' : undefined}
disableElevation
onClick={handleClick}
color="default"
startIcon={<VideogameAssetIcon />}
color="inherit"
startIcon={<VideogameAssetIcon />}
sx={{
border: '1px solid rgba(230, 223, 216, 0.95)',
backgroundColor: 'background.default',
}}
>
{t('panels.title')}
</Button>
@@ -70,4 +74,4 @@ const PanelsMenu = ({openWindow}) => {
</Box>
);
}
export default PanelsMenu;
export default PanelsMenu;

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react';
import { Box, Button, Menu, MenuItem, Divider } from '@mui/material';
import { Box, Button, Menu, MenuItem } from '@mui/material';
import BuildIcon from '@mui/icons-material/Build';
import MessageIcon from '@mui/icons-material/Message';
import SettingsInputCompositeIcon from '@mui/icons-material/SettingsInputComposite';
import { useTranslation } from './i18n/LanguageContext';
const ToolsMenu =({openWindow}) => {
@@ -43,8 +41,12 @@ const ToolsMenu =({openWindow}) => {
aria-expanded={open ? 'true' : undefined}
disableElevation
onClick={handleClick}
color="default"
startIcon={<BuildIcon />}
color="inherit"
startIcon={<BuildIcon />}
sx={{
border: '1px solid rgba(230, 223, 216, 0.95)',
backgroundColor: 'background.default',
}}
>
{t('tools.title')}
</Button>
@@ -73,4 +75,4 @@ const ToolsMenu =({openWindow}) => {
);
}
export default ToolsMenu;
export default ToolsMenu;

View File

@@ -5,8 +5,9 @@
}
body {
background-color: rgb(22, 13, 13);
background-color: #faf9f5;
overflow: hidden;
color: #141413;
}
#root {

View File

@@ -1,49 +1,164 @@
import { createTheme } from '@mui/material/styles';
const canvas = '#faf9f5';
const surfaceSoft = '#f5f0e8';
const surfaceCard = '#efe9de';
const surfaceCreamStrong = '#e8e0d2';
const surfaceDark = '#181715';
const surfaceDarkElevated = '#252320';
const surfaceDarkSoft = '#1f1e1b';
const hairline = '#e6dfd8';
const hairlineSoft = '#ebe6df';
const ink = '#141413';
const body = '#3d3d3a';
const muted = '#6c6a64';
const mutedSoft = '#8e8b82';
const primary = '#cc785c';
const primaryActive = '#a9583e';
const primaryDisabled = '#e6dfd8';
const onPrimary = '#ffffff';
const success = '#5db872';
const warning = '#d4a017';
const error = '#c64545';
const accentTeal = '#5db8a6';
const theme = createTheme({
palette: {
mode: 'dark',
text: {
primary: '#ffffff', // Set the primary text color to white
mode: 'light',
primary: {
main: primary,
dark: primaryActive,
light: '#d89278',
contrastText: onPrimary,
},
secondary: {
main: accentTeal,
},
success: {
main: success,
},
warning: {
main: warning,
},
error: {
main: error,
},
background: {
default: canvas,
paper: surfaceCard,
},
text: {
primary: ink,
secondary: body,
disabled: mutedSoft,
},
divider: hairline,
},
shape: {
borderRadius: 12,
},
// Add typography settings to make all fonts smaller
typography: {
fontSize: 12, // Default font size in px (reduced from the default 14px)
htmlFontSize: 14, // Base HTML font-size (was 16px by default)
// Customize specific variants
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: 13,
htmlFontSize: 14,
h1: {
fontSize: '2rem', // 24px
fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '2rem',
fontWeight: 400,
letterSpacing: '-0.04em',
},
h2: {
fontSize: '1.75rem', // 21px
fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.75rem',
fontWeight: 400,
letterSpacing: '-0.03em',
},
h3: {
fontSize: '1.5rem', // 18px
fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.5rem',
fontWeight: 400,
letterSpacing: '-0.02em',
},
h4: {
fontSize: '1.25rem', // 15px
fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.25rem',
fontWeight: 400,
letterSpacing: '-0.02em',
},
h5: {
fontSize: '1.1rem', // 13.2px
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: '1.05rem',
fontWeight: 500,
},
h6: {
fontSize: '1rem', // 12px
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: '0.95rem',
fontWeight: 500,
},
body1: {
fontSize: '0.875rem', // 10.5px
fontSize: '0.9rem',
},
body2: {
fontSize: '0.825rem', // 9.9px
fontSize: '0.82rem',
},
button: {
fontSize: '0.825rem', // 9.9px
fontSize: '0.82rem',
fontWeight: 500,
textTransform: 'none',
},
caption: {
fontSize: '0.75rem', // 9px
fontSize: '0.75rem',
},
},
components: {
MuiCssBaseline: {
styleOverrides: {
body: {
backgroundColor: canvas,
color: ink,
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
backgroundColor: canvas,
color: ink,
backgroundImage: 'none',
borderBottom: `1px solid ${hairline}`,
boxShadow: 'none',
},
},
},
MuiToolbar: {
styleOverrides: {
root: {
minHeight: 64,
},
},
},
MuiPaper: {
styleOverrides: {
root: {
backgroundImage: 'none',
backgroundColor: surfaceCard,
color: ink,
border: `1px solid ${hairline}`,
},
elevation1: {
boxShadow: '0 1px 3px rgba(20, 20, 19, 0.08)',
},
},
},
MuiCard: {
styleOverrides: {
root: {
backgroundImage: 'none',
backgroundColor: surfaceCard,
border: `1px solid ${hairline}`,
},
},
},
MuiButton: {
defaultProps: {
size: 'small',
@@ -51,19 +166,55 @@ const theme = createTheme({
styleOverrides: {
root: {
textTransform: 'none',
borderRadius: 8,
boxShadow: 'none',
},
containedPrimary: {
backgroundColor: primary,
color: onPrimary,
'&:hover': {
backgroundColor: primaryActive,
boxShadow: 'none',
},
},
outlined: {
borderColor: hairline,
color: ink,
backgroundColor: canvas,
'&:hover': {
backgroundColor: surfaceSoft,
borderColor: hairline,
},
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
color: ink,
},
},
},
// Add table cell specific overrides
MuiTableCell: {
styleOverrides: {
root: {
fontSize: '0.8rem',
padding: '4px 8px',
padding: '6px 10px',
borderBottom: `1px solid ${hairlineSoft}`,
},
head: {
fontWeight: 'bold',
fontSize: '0.8rem',
fontWeight: 600,
color: muted,
backgroundColor: surfaceSoft,
},
},
},
MuiTableRow: {
styleOverrides: {
root: {
'&:hover': {
backgroundColor: surfaceSoft,
},
},
},
},
@@ -74,25 +225,19 @@ const theme = createTheme({
styleOverrides: {
root: {
'& .MuiInputBase-root': {
fontSize: '0.8rem',
fontSize: '0.82rem',
backgroundColor: canvas,
},
'& .MuiInputLabel-root': {
fontSize: '0.8rem',
transform: 'translate(14px, 9px) scale(1)',
},
'& .MuiInputLabel-shrink': {
transform: 'translate(14px, -6px) scale(0.75)',
},
'& .MuiOutlinedInput-root': {
padding: '4px 8px',
},
'& .MuiOutlinedInput-input': {
padding: '4px',
borderRadius: 8,
},
},
},
},
MuiTable: {
MuiFormControl: {
defaultProps: {
size: 'small',
},
@@ -102,63 +247,56 @@ const theme = createTheme({
size: 'small',
},
},
MuiFormControl: {
defaultProps: {
size: 'small',
},
},
MuiInputLabel: {
defaultProps: {
size: 'small',
},
},
MuiIconButton: {
defaultProps: {
size: 'small',
MuiMenuItem: {
styleOverrides: {
root: {
fontSize: '0.85rem',
},
},
},
MuiFab: {
defaultProps: {
size: 'small',
MuiDivider: {
styleOverrides: {
root: {
borderColor: hairline,
},
},
},
MuiCheckbox: {
defaultProps: {
size: 'small',
},
},
MuiRadio: {
defaultProps: {
size: 'small',
},
},
MuiSwitch: {
defaultProps: {
size: 'small',
MuiDialog: {
styleOverrides: {
paper: {
backgroundColor: surfaceCard,
border: `1px solid ${hairline}`,
boxShadow: '0 12px 32px rgba(20, 20, 19, 0.12)',
},
},
},
MuiDialogTitle: {
styleOverrides: {
root: {
fontSize: '1.25rem', // Set your desired font size here
color: ink,
},
},
},
MuiDialogContent: {
styleOverrides: {
root: {
color: body,
},
},
},
MuiTypography: {
styleOverrides: {
root: {
color: '#ffffff', // Set the default text color to white
color: ink,
},
},
},
AppBar: {
styleOverrides: {
defaultProps: {
size: 'small',
},
}
}
},
});
export default theme;
export default theme;