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 { Typography, Box, Paper, Divider, Link, Grid, Card, CardContent, List, ListItem, ListItemIcon, ListItemText, Stack, Chip, Button } from '@mui/material';
import packageInfo from '../package.json'; import packageInfo from '../package.json';
import vimdronesLogo from './image/vimdrones_logo.png'; 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 CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import UsbIcon from '@mui/icons-material/Usb'; import UsbIcon from '@mui/icons-material/Usb';
import WifiIcon from '@mui/icons-material/Wifi'; import WifiIcon from '@mui/icons-material/Wifi';
import ComputerIcon from '@mui/icons-material/Computer'; import ComputerIcon from '@mui/icons-material/Computer';
// Remove the DiscordIcon import since we're now using a custom image
const About = () => { const About = () => {
return ( return (
<Box sx={{ mx: 'auto', p: 2, width: '100%', flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 1 }} component={Paper} elevation={2}> <Box
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', mb: 1 }}> component={Paper}
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'baseline' }}> elevation={0}
<Typography variant="h4" component="h1" sx={{ fontWeight: 600 }}> 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 DroneCAN Web Tools
</Typography> </Typography>
<Chip <Chip
label={`v${packageInfo.version}`} label={`v${packageInfo.version}`}
size="small" size="small"
color="primary" color="primary"
variant="outlined" variant="outlined"
sx={{ ml: 2 }}
/> />
</Box> </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> </Box>
<Card variant="outlined"> <Card variant="outlined" sx={{ backgroundColor: 'background.default' }}>
<CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}> <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 container spacing={1}>
<Grid item xs={12} sm={6}> <Grid item xs={12} sm={6}>
<List dense disablePadding> <List dense disablePadding>
@@ -59,22 +75,22 @@ const About = () => {
</Grid> </Grid>
</CardContent> </CardContent>
</Card> </Card>
<Stack spacing={2} direction={{ xs: 'column', md: 'row' }}> <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 } }}> <CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<WifiIcon color="primary" sx={{ mr: 1 }} /> <WifiIcon color="primary" sx={{ mr: 1 }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>WebSocket via MAVProxy</Typography> <Typography variant="h6" sx={{ fontWeight: 600 }}>WebSocket via MAVProxy</Typography>
</Box> </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 mavproxy.py --master /dev/tty.usbmodem111401,115200 --out wsserver:127.0.0.1:5555
</Typography> </Typography>
<Typography variant="body2" sx={{ mt: 1, color: 'text.secondary' }}> <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> </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 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}>
<ComputerIcon color="info" sx={{ mr: 1, fontSize: '1rem' }} /> <ComputerIcon color="info" sx={{ mr: 1, fontSize: '1rem' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}> <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
@@ -87,8 +103,8 @@ const About = () => {
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
<Card variant="outlined" sx={{ flex: 1 }}> <Card variant="outlined" sx={{ flex: 1, backgroundColor: 'background.paper' }}>
<CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}> <CardContent sx={{ pb: 1, '&:last-child': { pb: 1 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<UsbIcon color="primary" sx={{ mr: 1 }} /> <UsbIcon color="primary" sx={{ mr: 1 }} />
@@ -109,23 +125,23 @@ const About = () => {
</ListItem> </ListItem>
<ListItem disablePadding sx={{ py: 0.25 }}> <ListItem disablePadding sx={{ py: 0.25 }}>
<ListItemIcon sx={{ minWidth: 30 }}> <ListItemIcon sx={{ minWidth: 30 }}>
<CheckCircleOutlineIcon color="success" fontSize="small" /> {/* Changed from info to success */} <CheckCircleOutlineIcon color="success" fontSize="small" />
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary="Standalone MAVCAN USB Adaptor" primary="Standalone MAVCAN USB Adaptor"
secondary={ secondary={
<Box component="span"> <Box component="span">
<Link <Link
href="https://github.com/VimDrones/MAVCAN_Bridge" href="https://github.com/VimDrones/MAVCAN_Bridge"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
sx={{ fontSize: '0.75rem' }} sx={{ fontSize: '0.75rem' }}
> >
MAVCAN Bridge MAVCAN Bridge
</Link> </Link>
<Typography <Typography
component="span" component="span"
variant="caption" variant="caption"
sx={{ ml: 0.5, fontSize: '0.75rem', color: 'text.secondary' }} sx={{ ml: 0.5, fontSize: '0.75rem', color: 'text.secondary' }}
> >
from Vimdrones from Vimdrones
@@ -138,22 +154,21 @@ const About = () => {
</CardContent> </CardContent>
</Card> </Card>
</Stack> </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 }} /> <Divider sx={{ my: 1.5 }} />
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
gap: 1, gap: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: { xs: 'flex-start', sm: 'center' } 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' } }}> <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"> <Typography variant="body2" color="text.secondary">
Author: <Link href="https://github.com/huibean" target="_blank" rel="noopener" sx={{ fontWeight: 500 }}>Huibean Luo</Link> Author: <Link href="https://github.com/huibean" target="_blank" rel="noopener" sx={{ fontWeight: 500 }}>Huibean Luo</Link>
</Typography> </Typography>
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
@@ -161,7 +176,7 @@ const About = () => {
href="https://discord.gg/xxCKsZXU4K" href="https://discord.gg/xxCKsZXU4K"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
sx={{ sx={{
borderRadius: 4, borderRadius: 4,
textTransform: 'none', textTransform: 'none',
fontSize: '0.75rem', fontSize: '0.75rem',
@@ -194,10 +209,10 @@ const About = () => {
} }
}} }}
> >
<img <img
style={{borderRadius: '50%'}} style={{borderRadius: '50%'}}
src={discordLogo} src={discordLogo}
alt="Discord" alt="Discord"
/> />
</Box> </Box>
Vimdrones Vimdrones
@@ -210,7 +225,7 @@ const About = () => {
href="https://discord.gg/vz7a499KXN" href="https://discord.gg/vz7a499KXN"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
sx={{ sx={{
borderRadius: 4, borderRadius: 4,
textTransform: 'none', textTransform: 'none',
fontSize: '0.75rem', fontSize: '0.75rem',
@@ -243,24 +258,24 @@ const About = () => {
} }
}} }}
> >
<img <img
style={{borderRadius: '50%'}} style={{borderRadius: '50%'}}
src={discordLogo} src={discordLogo}
alt="Discord" alt="Discord"
/> />
</Box> </Box>
DroneCAN DroneCAN
</Button> </Button>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
Sponsored by Sponsored by
</Typography> </Typography>
<Link href="https://dev.vimdrones.com" target="_blank" rel="noopener"> <Link href="https://dev.vimdrones.com" target="_blank" rel="noopener">
<img <img
src={vimdronesLogo} src={vimdronesLogo}
alt="Vimdrones" alt="Vimdrones"
style={{ height: '30px' }} style={{ height: '30px' }}
/> />
</Link> </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 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 MavlinkSession from './mavlink_session';
import dronecan from './dronecan'; import dronecan from './dronecan';
import theme from './theme'; import theme from './theme';
@@ -97,7 +97,7 @@ const AppContent = () => {
const newBus = event.target.value; const newBus = event.target.value;
if (newBus === selectedBus) return; if (newBus === selectedBus) return;
setSelectedBus(newBus); setSelectedBus(newBus);
if (window.localNode) { if (window.localNode) {
window.localNode.changeBus(newBus); window.localNode.changeBus(newBus);
showMessage(t('app.bus_switched', { bus: newBus }), 'info'); 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 ( return (
<> <>
<AppBar position="static"> <AppBar position="static" color="transparent" elevation={0} sx={{ borderBottom: surfaceBorder }}>
<Toolbar variant="dense" sx={{ display: 'flex', justifyContent: 'space-between' }}> <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'}}> <Box sx={{ width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
<ToolsMenu openWindow={openWindow.bind(this)} /> <ToolsMenu openWindow={openWindow} />
<PanelsMenu openWindow={openWindow.bind(this)} /> <PanelsMenu openWindow={openWindow} />
</Box> </Box>
<Box sx={{flexGrow: 2, display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center'}}> <Box sx={{ flexGrow: 2, display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 1 }}>
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center'}} ml={0.5} mr={0.5}> <Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<a href="https://dev.vimdrones.com" target="_blank" rel="noreferrer" style={{height: 30}}> <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}} /> <img src={DronecanLogo} alt="DroneCAN" style={{ height: 30 }} />
</a> </a>
</Box> </Box>
<Typography variant="caption"> <Typography variant="body2" sx={{ fontWeight: 600, letterSpacing: 0.2 }}>
{t('app.title')} {t('app.title')}
</Typography> </Typography>
</Box> </Box>
<Box sx={{width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', gap: 1}}> <Box sx={{ width: '30%', flexGrow: 1, display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', gap: 1 }}>
<ConnectionIndicators <ConnectionIndicators
isConnected={isConnected} isConnected={isConnected}
mavlinkSession={window.mavlinkSession} mavlinkSession={window.mavlinkSession}
localNode={window.localNode} localNode={window.localNode}
/> />
<FormControl <FormControl
size="small" size="small"
sx={{ sx={{
minWidth: 80, minWidth: 92,
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
height: 30, height: 32,
backgroundColor: '#faf9f5',
}, },
'& .MuiSelect-select': { '& .MuiSelect-select': {
paddingTop: 0.5, paddingTop: 0.5,
paddingBottom: 0.5, paddingBottom: 0.5,
fontSize: '0.8rem' fontSize: '0.8rem'
} }
@@ -195,11 +198,11 @@ const AppContent = () => {
</Select> </Select>
</FormControl> </FormControl>
<Button <Button
variant="outlined" variant="outlined"
color={dnaServerActive ? "success" : "primary"} color={dnaServerActive ? 'success' : 'primary'}
startIcon={ startIcon={
dnaServerActive ? dnaServerActive ?
<DnsIcon sx={{ <DnsIcon sx={{
animation: 'pulse 1.5s infinite', animation: 'pulse 1.5s infinite',
'@keyframes pulse': { '@keyframes pulse': {
@@ -207,27 +210,27 @@ const AppContent = () => {
'50%': { opacity: 1 }, '50%': { opacity: 1 },
'100%': { opacity: 0.6 }, '100%': { opacity: 0.6 },
} }
}} /> : }} /> :
<DnsIcon /> <DnsIcon />
} }
onClick={handleToggleDnaServer} onClick={handleToggleDnaServer}
sx={dnaServerActive ? { sx={dnaServerActive ? {
borderColor: 'success.main', borderColor: 'success.main',
'&:hover': { '&:hover': {
backgroundColor: 'rgba(76, 175, 80, 0.08)', backgroundColor: 'rgba(93, 184, 114, 0.08)',
borderColor: 'success.dark' borderColor: 'success.dark'
} }
} : {}} } : {}}
> >
{t('app.dna')} {t('app.dna')}
</Button> </Button>
<Tooltip title={language === 'en' ? '中文' : 'English'}> <Tooltip title={language === 'en' ? '中文' : 'English'}>
<IconButton <IconButton
size="small" size="small"
color="inherit" color="inherit"
onClick={() => setLanguage(language === 'en' ? 'zh' : 'en')} 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" /> <LanguageIcon fontSize="small" />
</IconButton> </IconButton>
@@ -244,47 +247,47 @@ const AppContent = () => {
</Box> </Box>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, height: '95vh', gap: 0.5, p: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'row', flexGrow: 1, minHeight: 'calc(100vh - 64px)', gap: 0.75, p: 1, backgroundColor: '#faf9f5' }}>
<CompactSidebar <CompactSidebar
nodes={nodes} nodes={nodes}
selectedNodeId={selectedNodeId} selectedNodeId={selectedNodeId}
setSelectedNodeId={setSelectedNodeId} setSelectedNodeId={setSelectedNodeId}
nodesUpdateTimestamp={nodesUpdateTimestamp} nodesUpdateTimestamp={nodesUpdateTimestamp}
/> />
<Box <Box
sx={{ sx={{
minWidth: 550, minWidth: 550,
display: { xs: 'none', md: 'flex' }, display: { xs: 'none', md: 'flex' },
flexDirection: 'column', flexDirection: 'column',
gap: 0.5, gap: 0.75,
}} }}
> >
<NodeList <NodeList
nodes={nodes} nodes={nodes}
selectedNodeId={selectedNodeId} selectedNodeId={selectedNodeId}
setSelectedNodeId={setSelectedNodeId.bind(this)} setSelectedNodeId={setSelectedNodeId}
nodesUpdateTimestamp={nodesUpdateTimestamp} nodesUpdateTimestamp={nodesUpdateTimestamp}
/> />
<NodeLogs/> <NodeLogs/>
</Box> </Box>
<Box <Box
display="flex" display="flex"
flexDirection="column" flexDirection="column"
sx={{ sx={{
gap: 0.5, gap: 0.75,
ml: { xs: 1, md: 0 }, ml: { xs: 1, md: 0 },
flexGrow: 1, flexGrow: 1,
}} }}
> >
{selectedNodeId && ( {selectedNodeId && (
<Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, gap: 0.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, gap: 0.75 }}>
<NodeProperties <NodeProperties
nodeId={selectedNodeId} nodeId={selectedNodeId}
nodes={nodes} nodes={nodes}
multiNodeEditorEnable={multiNodeEditorEnable} multiNodeEditorEnable={multiNodeEditorEnable}
setMultiNodeEditorEnable={setMultiNodeEditorEnable.bind(this)} setMultiNodeEditorEnable={setMultiNodeEditorEnable}
nodesUpdateTimestamp={nodesUpdateTimestamp} nodesUpdateTimestamp={nodesUpdateTimestamp}
/> />
<NodeParam <NodeParam
@@ -304,17 +307,17 @@ const AppContent = () => {
open={modalOpen} open={modalOpen}
onClose={handleCloseModal} onClose={handleCloseModal}
onConnectionStatusChange={handleConnectionStatusChange} onConnectionStatusChange={handleConnectionStatusChange}
showMessage={showMessage.bind(this)} showMessage={showMessage}
selectedBus={selectedBus} selectedBus={selectedBus}
onBusChange={handleBusChange} onBusChange={handleBusChange}
/> />
<Snackbar <Snackbar
open={snackbarOpen} open={snackbarOpen}
autoHideDuration={6000} autoHideDuration={6000}
onClose={() => setSnackbarOpen(false)} onClose={() => setSnackbarOpen(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
> >
<Alert onClose={() => setSnackbarOpen(false)} severity={snackbarSeverity}> <Alert onClose={() => setSnackbarOpen(false)} severity={snackbarSeverity} variant="filled">
{snackbarMessage} {snackbarMessage}
</Alert> </Alert>
</Snackbar> </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 { Box, Badge, Tooltip, CircularProgress, Typography } from '@mui/material';
import NotificationsIcon from '@mui/icons-material/Notifications'; import NotificationsIcon from '@mui/icons-material/Notifications';
const CompactSidebar = ({ const CompactSidebar = ({
nodes, nodes,
selectedNodeId, selectedNodeId,
setSelectedNodeId setSelectedNodeId
}) => { }) => {
const [logCounts, setLogCounts] = useState({}); const [logCounts, setLogCounts] = useState({});
useEffect(() => { useEffect(() => {
// Function to get log counts from the window.localNode events
const updateLogCounts = () => { const updateLogCounts = () => {
const counts = {}; const counts = {};
// Initialize counts for all nodes to 0
Object.keys(nodes).forEach(nodeId => { Object.keys(nodes).forEach(nodeId => {
counts[nodeId] = 0; counts[nodeId] = 0;
}); });
try { 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)) { if (window.dronecanLogs && Array.isArray(window.dronecanLogs)) {
window.dronecanLogs.forEach(log => { window.dronecanLogs.forEach(log => {
if (log.id) { 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) { } catch (error) {
console.error("Error accessing logs:", error); console.error("Error accessing logs:", error);
} }
setLogCounts(counts); setLogCounts(counts);
}; };
// Create log listener and counter
let lastLogCounts = {}; let lastLogCounts = {};
const handleLog = (transfer) => { const handleLog = (transfer) => {
const sourceNodeId = transfer.sourceNodeId; const sourceNodeId = transfer.sourceNodeId;
if (sourceNodeId) { if (sourceNodeId) {
// Update count for this node
lastLogCounts[sourceNodeId] = (lastLogCounts[sourceNodeId] || 0) + 1; lastLogCounts[sourceNodeId] = (lastLogCounts[sourceNodeId] || 0) + 1;
setLogCounts({...lastLogCounts}); setLogCounts({...lastLogCounts});
} }
}; };
// Listen for log messages
const localNode = window.localNode; const localNode = window.localNode;
if (localNode) { if (localNode) {
localNode.on('uavcan.protocol.debug.LogMessage', handleLog); localNode.on('uavcan.protocol.debug.LogMessage', handleLog);
} }
// Initial update
updateLogCounts(); updateLogCounts();
return () => { return () => {
// Remove event listener on cleanup
if (localNode) { if (localNode) {
localNode.off('uavcan.protocol.debug.LogMessage', handleLog); localNode.off('uavcan.protocol.debug.LogMessage', handleLog);
} }
}; };
}, [nodes]); }, [nodes]);
// Get color based on node mode (matching the same logic as NodeList)
const getModeColor = (mode) => { const getModeColor = (mode) => {
switch (mode) { switch (mode) {
case 'OPERATIONAL': case 'OPERATIONAL':
return '#f5f5f5'; // Light gray for operational (subtle) return '#5db872';
case 'INITIALIZATION': case 'INITIALIZATION':
return '#ffb74d'; // Warning color return '#d4a017';
case 'MAINTENANCE': case 'MAINTENANCE':
return '#9c27b0'; // Secondary/purple return '#5db8a6';
case 'SOFTWARE_UPDATE': case 'SOFTWARE_UPDATE':
return '#4caf50'; // Success/green return '#5db872';
case 'OFFLINE': case 'OFFLINE':
return '#f44336'; // Error/red return '#c64545';
default: default:
return '#f44336'; // Default to error color return '#c64545';
} }
}; };
// Handle click on a node
const handleNodeClick = (nodeId) => { const handleNodeClick = (nodeId) => {
if (nodeId === selectedNodeId) { if (nodeId === selectedNodeId) {
setSelectedNodeId(null); setSelectedNodeId(null);
@@ -93,10 +80,10 @@ const CompactSidebar = ({
setSelectedNodeId(Number(nodeId)); setSelectedNodeId(Number(nodeId));
} }
}; };
return ( return (
<Box <Box
sx={{ sx={{
display: { xs: 'flex', md: 'none', alignItems: 'center' }, display: { xs: 'flex', md: 'none', alignItems: 'center' },
flexDirection: 'column', flexDirection: 'column',
width: '60px', width: '60px',
@@ -105,17 +92,19 @@ const CompactSidebar = ({
p: 1, p: 1,
gap: 1, gap: 1,
overflowY: 'auto', overflowY: 'auto',
height: '100%' height: '100%',
backgroundColor: 'background.paper',
}} }}
> >
<Typography <Typography
variant="caption" variant="caption"
sx={{ sx={{
textAlign: 'center', textAlign: 'center',
display: 'block', display: 'block',
mb: 0.5, mb: 0.5,
fontWeight: 'bold', fontWeight: 700,
fontSize: '0.5rem' fontSize: '0.5rem',
color: 'text.secondary',
}} }}
> >
NODES NODES
@@ -130,11 +119,12 @@ const CompactSidebar = ({
const node = nodes[nodeId]; const node = nodes[nodeId];
const mode = node.status.getConstant('mode'); const mode = node.status.getConstant('mode');
const logCount = logCounts[nodeId] || 0; const logCount = logCounts[nodeId] || 0;
const selected = selectedNodeId === Number(nodeId);
return ( return (
<Tooltip <Tooltip
key={nodeId} key={nodeId}
title={`${node.name || 'Unknown'} (${mode})`} title={`${node.name || 'Unknown'} (${mode})`}
placement="right" placement="right"
> >
<Box <Box
@@ -148,25 +138,25 @@ const CompactSidebar = ({
justifyContent: 'center', justifyContent: 'center',
borderRadius: 1, borderRadius: 1,
border: '1px solid', border: '1px solid',
borderColor: selectedNodeId === Number(nodeId) ? 'primary.main' : 'divider', borderColor: selected ? 'primary.main' : 'divider',
backgroundColor: 'background.paper', backgroundColor: selected ? 'rgba(204, 120, 92, 0.08)' : 'background.default',
cursor: 'pointer', cursor: 'pointer',
'&:hover': { '&:hover': {
backgroundColor: 'action.hover', backgroundColor: 'rgba(245, 240, 232, 0.9)',
} }
}} }}
onClick={() => handleNodeClick(Number(nodeId))} onClick={() => handleNodeClick(Number(nodeId))}
> >
{/* Make NID larger and more prominent */} <Typography
<Typography
variant='caption' variant='caption'
sx={{ sx={{
color: getModeColor(mode), color: getModeColor(mode),
fontWeight: 600,
}} }}
> >
{nodeId} {nodeId}
</Typography> </Typography>
{logCount > 0 && ( {logCount > 0 && (
<Badge <Badge
badgeContent={logCount > 99 ? '99+' : logCount} 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 [rxActive, setRxActive] = useState(false);
const txTimeout = useRef(null); const txTimeout = useRef(null);
const rxTimeout = useRef(null); const rxTimeout = useRef(null);
useEffect(() => { useEffect(() => {
const handleFrameSend = () => { const handleFrameSend = () => {
setTxActive(true); setTxActive(true);
clearTimeout(txTimeout.current); clearTimeout(txTimeout.current);
txTimeout.current = setTimeout(() => { txTimeout.current = setTimeout(() => {
setTxActive(false); setTxActive(false);
}, 100); // Blink for 200ms }, 100);
}; };
const handleFrameReceive = () => { const handleFrameReceive = () => {
setRxActive(true); setRxActive(true);
clearTimeout(rxTimeout.current); clearTimeout(rxTimeout.current);
rxTimeout.current = setTimeout(() => { rxTimeout.current = setTimeout(() => {
setRxActive(false); setRxActive(false);
}, 100); // Blink for 200ms }, 100);
}; };
if (mavlinkSession) { if (mavlinkSession) {
mavlinkSession.on('mav-tx', handleFrameSend); mavlinkSession.on('mav-tx', handleFrameSend);
mavlinkSession.on('mav-rx', handleFrameReceive); mavlinkSession.on('mav-rx', handleFrameReceive);
} }
return () => { return () => {
if (mavlinkSession) { if (mavlinkSession) {
mavlinkSession.removeListener('mav-tx', handleFrameSend); mavlinkSession.removeListener('mav-tx', handleFrameSend);
@@ -39,14 +39,14 @@ const ConnectionIndicators = ({ isConnected, mavlinkSession, localNode }) => {
clearTimeout(rxTimeout.current); clearTimeout(rxTimeout.current);
}; };
}, [localNode, mavlinkSession]); }, [localNode, mavlinkSession]);
return ( 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 }}> <Box sx={{ display: 'flex', alignItems: 'center', opacity: isConnected ? 1 : 0.3 }}>
<CircleIcon <CircleIcon
fontSize="small" fontSize="small"
sx={{ sx={{
color: txActive && isConnected ? '#4caf50' : '#7e7e7e', color: txActive && isConnected ? '#5db872' : '#8e8b82',
width: '12px', width: '12px',
height: '12px', height: '12px',
transition: 'color 0.1s ease' 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> <Typography variant="caption" sx={{ ml: 0.5, color: 'text.secondary' }}>TX</Typography>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', opacity: isConnected ? 1 : 0.3 }}> <Box sx={{ display: 'flex', alignItems: 'center', opacity: isConnected ? 1 : 0.3 }}>
<CircleIcon <CircleIcon
fontSize="small" fontSize="small"
sx={{ sx={{
color: rxActive && isConnected ? '#2196f3' : '#7e7e7e', color: rxActive && isConnected ? '#5db8a6' : '#8e8b82',
width: '12px', width: '12px',
height: '12px', height: '12px',
transition: 'color 0.1s ease' 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) => { const renderNodeRow = (key) => {
let node = nodes[key]; const node = nodes[key];
let status = node.status; const status = node.status;
let health = status.getConstant('health'); const health = status.getConstant('health');
let mode = status.getConstant('mode'); const mode = status.getConstant('mode');
const isSelected = Number(key) === Number(selectedNodeId);
return ( 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>{key}</TableCell>
<TableCell>{node.name}</TableCell> <TableCell sx={{ width: 150 }}>{node.name}</TableCell>
<TableCell>{health}</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>{secondsToTime(status.uptime_sec)}</TableCell>
<TableCell>{node.status.vendor_specific_status_code}</TableCell> <TableCell>{node.status.vendor_specific_status_code}</TableCell>
</TableRow> </TableRow>
@@ -48,25 +59,17 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
return ( return (
<Box <Box
component={Paper} 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}}> <Box margin={1} sx={{ height: 20 }}>
<Typography variant="caption">Online Nodes</Typography> <Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600, letterSpacing: 0.2 }}>Online Nodes</Typography>
</Box> </Box>
<TableContainer <TableContainer sx={{ overflow: 'auto' }}>
sx={{ overflow: 'auto' }}
>
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>NID</TableCell> <TableCell>NID</TableCell>
<TableCell <TableCell>Name</TableCell>
sx={{
width: 150,
}}
>
Name
</TableCell>
<TableCell>Health</TableCell> <TableCell>Health</TableCell>
<TableCell>Mode</TableCell> <TableCell>Mode</TableCell>
<TableCell>Uptime</TableCell> <TableCell>Uptime</TableCell>
@@ -74,9 +77,7 @@ const NodeList = ({ nodes, selectedNodeId, setSelectedNodeId }) => {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{Object.keys(nodes).map((key) => ( {Object.keys(nodes).map((key) => renderNodeRow(key))}
renderNodeRow(key)
))}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </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) { if (paused) {
return; return;
} }
// console.log(transfer);
const msg = transfer.payload; const msg = transfer.payload;
const msgObj = msg.toObj(); const msgObj = msg.toObj();
setLogs((logs) => [...logs, { setLogs((logs) => [...logs, {
@@ -24,7 +23,7 @@ const NodeLogs = () => {
localTime: new Date().toLocaleTimeString(), localTime: new Date().toLocaleTimeString(),
level: msgObj.level.getConstant('value'), level: msgObj.level.getConstant('value'),
source: '', source: '',
text: msgObj.text text: msgObj.text
}]); }]);
}; };
localNode.on('uavcan.protocol.debug.LogMessage', handleLog); localNode.on('uavcan.protocol.debug.LogMessage', handleLog);
@@ -51,21 +50,22 @@ const NodeLogs = () => {
return ( return (
<Box <Box
component={Paper} 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={{ <Box sx={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
height: 20 height: 20
}} margin={1}> }} 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 }}> <Box sx={{ display: 'flex', gap: 0.5 }}>
<IconButton <IconButton
sx={{ sx={{
width: 20, width: 20,
height: 20, height: 20,
padding: 0 padding: 0,
color: 'text.secondary',
}} }}
size='small' size='small'
onClick={() => setPaused(!paused)} onClick={() => setPaused(!paused)}
@@ -73,22 +73,20 @@ const NodeLogs = () => {
{paused ? <PlayArrowIcon sx={{ fontSize: 16 }} /> : <PauseIcon sx={{ fontSize: 16 }} />} {paused ? <PlayArrowIcon sx={{ fontSize: 16 }} /> : <PauseIcon sx={{ fontSize: 16 }} />}
</IconButton> </IconButton>
<IconButton <IconButton
sx={{ sx={{
width: 20, width: 20,
height: 20, height: 20,
padding: 0 padding: 0,
color: 'warning.main',
}} }}
size='small' size='small'
color="warning"
onClick={() => setLogs([])} onClick={() => setLogs([])}
> >
<CleaningServicesIcon sx={{ fontSize: 16 }} /> <CleaningServicesIcon sx={{ fontSize: 16 }} />
</IconButton> </IconButton>
</Box> </Box>
</Box> </Box>
<TableContainer <TableContainer sx={{ overflow: 'auto' }}>
sx={{ overflow: 'auto' }}
>
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
@@ -101,10 +99,10 @@ const NodeLogs = () => {
</TableHead> </TableHead>
<TableBody> <TableBody>
{logs.map((log, index) => ( {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.id}</TableCell>
<TableCell>{log.localTime}</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} {log.level}
</TableCell> </TableCell>
<TableCell>{log.source}</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 ( return (
<Box <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} component={Paper}
p={1} p={1}
> >
<Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}> <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')} {t('props.title')}
</Typography> </Typography>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}> <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) }} /> <Switch checked={multiNodeEditorEnable} onChange={(e) => { setMultiNodeEditorEnable(e.target.checked) }} />
</Stack> </Stack>
</Box> </Box>
@@ -199,7 +199,7 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
> >
{t('props.controls')} {t('props.controls')}
</Typography> </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 <Button
sx={{ mr: 1 }} sx={{ mr: 1 }}
color="error" color="error"
@@ -226,10 +226,10 @@ const NodeProperties = ({ nodeId, nodes, multiNodeEditorEnable, setMultiNodeEdit
</Button> </Button>
</Box> </Box>
</Box> </Box>
<FirmwareUpdateModal <FirmwareUpdateModal
open={firmwareModalOpen} open={firmwareModalOpen}
onClose={() => setFirmwareModalOpen(false)} onClose={() => setFirmwareModalOpen(false)}
targetNodeId={nodeId} targetNodeId={nodeId}
/> />
<ConfirmRestartModal <ConfirmRestartModal
open={restartModalOpen} 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 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 VideogameAssetIcon from '@mui/icons-material/VideogameAsset';
import { useTranslation } from './i18n/LanguageContext'; import { useTranslation } from './i18n/LanguageContext';
@@ -41,8 +41,12 @@ const PanelsMenu = ({openWindow}) => {
aria-expanded={open ? 'true' : undefined} aria-expanded={open ? 'true' : undefined}
disableElevation disableElevation
onClick={handleClick} onClick={handleClick}
color="default" color="inherit"
startIcon={<VideogameAssetIcon />} startIcon={<VideogameAssetIcon />}
sx={{
border: '1px solid rgba(230, 223, 216, 0.95)',
backgroundColor: 'background.default',
}}
> >
{t('panels.title')} {t('panels.title')}
</Button> </Button>
@@ -70,4 +74,4 @@ const PanelsMenu = ({openWindow}) => {
</Box> </Box>
); );
} }
export default PanelsMenu; export default PanelsMenu;

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react'; 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 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'; import { useTranslation } from './i18n/LanguageContext';
const ToolsMenu =({openWindow}) => { const ToolsMenu =({openWindow}) => {
@@ -43,8 +41,12 @@ const ToolsMenu =({openWindow}) => {
aria-expanded={open ? 'true' : undefined} aria-expanded={open ? 'true' : undefined}
disableElevation disableElevation
onClick={handleClick} onClick={handleClick}
color="default" color="inherit"
startIcon={<BuildIcon />} startIcon={<BuildIcon />}
sx={{
border: '1px solid rgba(230, 223, 216, 0.95)',
backgroundColor: 'background.default',
}}
> >
{t('tools.title')} {t('tools.title')}
</Button> </Button>
@@ -73,4 +75,4 @@ const ToolsMenu =({openWindow}) => {
); );
} }
export default ToolsMenu; export default ToolsMenu;

View File

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

View File

@@ -1,49 +1,164 @@
import { createTheme } from '@mui/material/styles'; 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({ const theme = createTheme({
palette: { palette: {
mode: 'dark', mode: 'light',
text: { primary: {
primary: '#ffffff', // Set the primary text color to white 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: { typography: {
fontSize: 12, // Default font size in px (reduced from the default 14px) fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
htmlFontSize: 14, // Base HTML font-size (was 16px by default) fontSize: 13,
// Customize specific variants htmlFontSize: 14,
h1: { h1: {
fontSize: '2rem', // 24px fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '2rem',
fontWeight: 400,
letterSpacing: '-0.04em',
}, },
h2: { h2: {
fontSize: '1.75rem', // 21px fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.75rem',
fontWeight: 400,
letterSpacing: '-0.03em',
}, },
h3: { h3: {
fontSize: '1.5rem', // 18px fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.5rem',
fontWeight: 400,
letterSpacing: '-0.02em',
}, },
h4: { h4: {
fontSize: '1.25rem', // 15px fontFamily: 'Cormorant Garamond, Georgia, serif',
fontSize: '1.25rem',
fontWeight: 400,
letterSpacing: '-0.02em',
}, },
h5: { h5: {
fontSize: '1.1rem', // 13.2px fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: '1.05rem',
fontWeight: 500,
}, },
h6: { h6: {
fontSize: '1rem', // 12px fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: '0.95rem',
fontWeight: 500,
}, },
body1: { body1: {
fontSize: '0.875rem', // 10.5px fontSize: '0.9rem',
}, },
body2: { body2: {
fontSize: '0.825rem', // 9.9px fontSize: '0.82rem',
}, },
button: { button: {
fontSize: '0.825rem', // 9.9px fontSize: '0.82rem',
fontWeight: 500,
textTransform: 'none',
}, },
caption: { caption: {
fontSize: '0.75rem', // 9px fontSize: '0.75rem',
}, },
}, },
components: { 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: { MuiButton: {
defaultProps: { defaultProps: {
size: 'small', size: 'small',
@@ -51,19 +166,55 @@ const theme = createTheme({
styleOverrides: { styleOverrides: {
root: { root: {
textTransform: 'none', 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: { MuiTableCell: {
styleOverrides: { styleOverrides: {
root: { root: {
fontSize: '0.8rem', fontSize: '0.8rem',
padding: '4px 8px', padding: '6px 10px',
borderBottom: `1px solid ${hairlineSoft}`,
}, },
head: { head: {
fontWeight: 'bold', fontWeight: 600,
fontSize: '0.8rem', color: muted,
backgroundColor: surfaceSoft,
},
},
},
MuiTableRow: {
styleOverrides: {
root: {
'&:hover': {
backgroundColor: surfaceSoft,
},
}, },
}, },
}, },
@@ -74,25 +225,19 @@ const theme = createTheme({
styleOverrides: { styleOverrides: {
root: { root: {
'& .MuiInputBase-root': { '& .MuiInputBase-root': {
fontSize: '0.8rem', fontSize: '0.82rem',
backgroundColor: canvas,
}, },
'& .MuiInputLabel-root': { '& .MuiInputLabel-root': {
fontSize: '0.8rem', fontSize: '0.8rem',
transform: 'translate(14px, 9px) scale(1)',
},
'& .MuiInputLabel-shrink': {
transform: 'translate(14px, -6px) scale(0.75)',
}, },
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
padding: '4px 8px', borderRadius: 8,
},
'& .MuiOutlinedInput-input': {
padding: '4px',
}, },
}, },
}, },
}, },
MuiTable: { MuiFormControl: {
defaultProps: { defaultProps: {
size: 'small', size: 'small',
}, },
@@ -102,63 +247,56 @@ const theme = createTheme({
size: 'small', size: 'small',
}, },
}, },
MuiFormControl: {
defaultProps: {
size: 'small',
},
},
MuiInputLabel: { MuiInputLabel: {
defaultProps: { defaultProps: {
size: 'small', size: 'small',
}, },
}, },
MuiIconButton: { MuiMenuItem: {
defaultProps: { styleOverrides: {
size: 'small', root: {
fontSize: '0.85rem',
},
}, },
}, },
MuiFab: { MuiDivider: {
defaultProps: { styleOverrides: {
size: 'small', root: {
borderColor: hairline,
},
}, },
}, },
MuiCheckbox: { MuiDialog: {
defaultProps: { styleOverrides: {
size: 'small', paper: {
}, backgroundColor: surfaceCard,
}, border: `1px solid ${hairline}`,
MuiRadio: { boxShadow: '0 12px 32px rgba(20, 20, 19, 0.12)',
defaultProps: { },
size: 'small',
},
},
MuiSwitch: {
defaultProps: {
size: 'small',
}, },
}, },
MuiDialogTitle: { MuiDialogTitle: {
styleOverrides: { styleOverrides: {
root: { root: {
fontSize: '1.25rem', // Set your desired font size here color: ink,
},
},
},
MuiDialogContent: {
styleOverrides: {
root: {
color: body,
}, },
}, },
}, },
MuiTypography: { MuiTypography: {
styleOverrides: { styleOverrides: {
root: { root: {
color: '#ffffff', // Set the default text color to white color: ink,
}, },
}, },
}, },
AppBar: {
styleOverrides: {
defaultProps: {
size: 'small',
},
}
}
}, },
}); });
export default theme; export default theme;