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 MavlinkSession from './mavlink_session'; import dronecan from './dronecan'; import theme from './theme'; import NodeList from './NodeList'; import NodeLogs from './NodeLogs'; import NodeProperties from './NodeProperties'; import NodeParam from './NodeParam'; import About from './About'; import ToolsMenu from './ToolsMenu'; import PanelsMenu from './PanelsMenu'; import ConnectionSettingsModal from './ConnectionSettingsModal'; import DronecanLogo from './image/dronecan_logo.png'; import FileServer from './FileServer'; import './css/index.css'; import ConnectionIndicators from './ConnectionIndicators'; import DnsIcon from '@mui/icons-material/Dns'; import LanIcon from '@mui/icons-material/Lan'; import LanguageIcon from '@mui/icons-material/Language'; import CompactSidebar from './CompactSidebar'; import DynamicNodeIdServer from './services/DynamicNodeIdServer'; import { LanguageProvider, useTranslation } from './i18n/LanguageContext'; window.mavlinkSession = new MavlinkSession(); window.localNode = new dronecan.Node({name: "com.vimdrones.web_gui"}); localNode.on('sendFrame', (messageId, data, len) => { mavlinkSession.sendCanFrame(localNode.bus, messageId, data, len); }); localNode.on('uavcan.protocol.file.Read.Request', (transfer) => { FileServer.handleReadRequest(transfer, localNode); }); const App = () => { return ( ); }; const AppContent = () => { const { t, language, setLanguage } = useTranslation(); const [nodes, setNodes] = useState({}); const [nodesUpdateTimestamp, setNodesUpdateTimestamp] = useState(0); const [isConnected, setIsConnected] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [selectedNodeId, setSelectedNodeId] = useState(null); const [multiNodeEditorEnable, setMultiNodeEditorEnable] = useState(false); const [subWindowRef, setSubWindowRef] = useState({}); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); const [snackbarSeverity, setSnackbarSeverity] = useState('info'); const [selectedBus, setSelectedBus] = useState(0); const [dnaServerActive, setDnaServerActive] = useState(false); const openWindow = (windowTitle, windowPath, windowSize) => { if (subWindowRef[windowPath] && !subWindowRef[windowPath].closed) { subWindowRef[windowPath].focus(); return; } const newWindow = window.open(windowPath, windowTitle, windowSize); if (newWindow) { setSubWindowRef((prev) => ({ ...prev, [windowPath]: newWindow })); newWindow.addEventListener('beforeunload', () => { setSubWindowRef((prev) => { const next = { ...prev }; delete next[windowPath]; return next; }); }); } else { console.error(`Main: Failed to open ${windowTitle}`); } }; const showMessage = (message, severity = 'info') => { setSnackbarMessage(message); setSnackbarSeverity(severity); setSnackbarOpen(true); }; const handleConnectionStatusChange = (isConnected) => { setIsConnected(isConnected); showMessage( isConnected ? t('app.connected') : t('app.disconnected'), isConnected ? 'success' : 'info' ); }; const handleBusChange = (event) => { 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'); } }; const handleNodeList = (nodeList) => { setNodes(nodeList); setNodesUpdateTimestamp(Date.now()); }; useEffect(() => { localNode.on('nodeList', handleNodeList); return () => { localNode.off('nodeList'); }; }, []); const handleOpenModal = () => { setModalOpen(true); }; const handleCloseModal = () => { setModalOpen(false); }; const handleToggleDnaServer = () => { if (!window.dnaServer) { window.dnaServer = new DynamicNodeIdServer(window.localNode); } if (window.dnaServer.getStatus().isActive) { window.dnaServer.stop(); setDnaServerActive(false); showMessage(t('app.dna_stopped'), 'info'); } else { const success = window.dnaServer.start(1, 125); setDnaServerActive(success); showMessage(success ? t('app.dna_started') : t('app.dna_failed'), success ? 'success' : 'error'); } }; return ( <> DroneCAN {t('app.title')} setLanguage(language === 'en' ? 'zh' : 'en')} sx={{ mr: 0.5 }} > {selectedNodeId && ( )} {selectedNodeId === null && ( )} setSnackbarOpen(false)} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} > setSnackbarOpen(false)} severity={snackbarSeverity}> {snackbarMessage} ); }; export default App;