/** * App.jsx — Saltybot Social + Telemetry + Fleet + Mission Dashboard root component. * * Social tabs (issue #107): * Status | Faces | Conversation | Personality | Navigation * * Telemetry tabs (issue #126): * IMU | Battery | Motors | Map | Control | Health | Cameras * * Fleet tabs (issue #139): * Fleet (self-contained via useFleet) * * Mission tabs (issue #145): * Missions (waypoint editor, route builder, geofence, schedule, execute) * * Camera viewer (issue #177): * CSI × 4 + D435i RGB/depth + panoramic, detection overlays, recording */ import { useState, useCallback } from 'react'; import { useRosbridge } from './hooks/useRosbridge.js'; // Social panels import { StatusPanel } from './components/StatusPanel.jsx'; import { StatusHeader } from './components/StatusHeader.jsx'; import { FaceGallery } from './components/FaceGallery.jsx'; import { ConversationLog } from './components/ConversationLog.jsx'; import { ConversationHistory } from './components/ConversationHistory.jsx'; import { PersonalityTuner } from './components/PersonalityTuner.jsx'; import { NavModeSelector } from './components/NavModeSelector.jsx'; import { AudioMeter } from './components/AudioMeter.jsx'; // Telemetry panels import PoseViewer from './components/PoseViewer.jsx'; import { BatteryPanel } from './components/BatteryPanel.jsx'; import { BatteryChart } from './components/BatteryChart.jsx'; import { MotorPanel } from './components/MotorPanel.jsx'; import { MotorCurrentGraph } from './components/MotorCurrentGraph.jsx'; import { MapViewer } from './components/MapViewer.jsx'; import { ControlMode } from './components/ControlMode.jsx'; import { SystemHealth } from './components/SystemHealth.jsx'; // Fleet panel (issue #139) import { FleetPanel } from './components/FleetPanel.jsx'; // Mission planner (issue #145) import { MissionPlanner } from './components/MissionPlanner.jsx'; // Settings panel (issue #160) import { SettingsPanel } from './components/SettingsPanel.jsx'; // Camera viewer (issue #177) import { CameraViewer } from './components/CameraViewer.jsx'; // Event log (issue #192) import { EventLog } from './components/EventLog.jsx'; // Log viewer (issue #275) import { LogViewer } from './components/LogViewer.jsx'; // Joystick teleop (issue #212) import JoystickTeleop from './components/JoystickTeleop.jsx'; // Network diagnostics (issue #222) import { NetworkPanel } from './components/NetworkPanel.jsx'; // Waypoint editor (issue #261) import { WaypointEditor } from './components/WaypointEditor.jsx'; // Bandwidth monitor (issue #287) import { BandwidthMonitor } from './components/BandwidthMonitor.jsx'; // Temperature gauge (issue #308) import { TempGauge } from './components/TempGauge.jsx'; // Node list viewer import { NodeList } from './components/NodeList.jsx'; // Gamepad teleoperation (issue #319) import { Teleop } from './components/Teleop.jsx'; // System diagnostics (issue #340) import { Diagnostics } from './components/Diagnostics.jsx'; // Hand tracking visualization (issue #344) import { HandTracker } from './components/HandTracker.jsx'; // Salty Face animated expression UI (issue #370) import { SaltyFace } from './components/SaltyFace.jsx'; // Parameter server (issue #471) import { ParameterServer } from './components/ParameterServer.jsx'; // Teleop web interface (issue #534) import { TeleopWebUI } from './components/TeleopWebUI.jsx'; // Gimbal control panel (issue #551) import { GimbalPanel } from './components/GimbalPanel.jsx'; const TAB_GROUPS = [ { label: 'TELEOP', color: 'text-orange-600', tabs: [ { id: 'teleop-webui', label: 'Drive' }, { id: 'gimbal', label: 'Gimbal' }, ], }, { label: 'DISPLAY', color: 'text-rose-600', tabs: [ { id: 'salty-face', label: 'Salty Face', }, ], }, { label: 'SOCIAL', color: 'text-cyan-600', tabs: [ { id: 'status', label: 'Status', }, { id: 'faces', label: 'Faces', }, { id: 'hands', label: 'Hands', }, { id: 'conversation', label: 'Convo', }, { id: 'history', label: 'History', }, { id: 'personality', label: 'Personality', }, { id: 'navigation', label: 'Nav Mode', }, { id: 'audio', label: 'Audio', }, ], }, { label: 'TELEMETRY', color: 'text-amber-600', tabs: [ { id: 'imu', label: 'IMU', }, { id: 'battery', label: 'Battery', }, { id: 'battery-chart', label: 'Battery History', }, { id: 'motors', label: 'Motors', }, { id: 'thermal', label: 'Thermal', }, { id: 'map', label: 'Map', }, { id: 'control', label: 'Control', }, { id: 'health', label: 'Health', }, ], }, { label: 'CAMERAS', color: 'text-rose-600', tabs: [ { id: 'cameras', label: 'Cameras' }, ], }, { label: 'NAVIGATION', color: 'text-teal-600', tabs: [ { id: 'waypoints', label: 'Waypoints' }, ], }, { label: 'FLEET', color: 'text-green-600', tabs: [ { id: 'fleet', label: 'Fleet' }, { id: 'missions', label: 'Missions' }, ], }, { label: 'MONITORING', color: 'text-yellow-600', tabs: [ { id: 'diagnostics', label: 'Diagnostics' }, { id: 'eventlog', label: 'Events' }, { id: 'bandwidth', label: 'Bandwidth' }, { id: 'nodes', label: 'Nodes' }, ], }, { label: 'CONFIG', color: 'text-purple-600', tabs: [ { id: 'parameters', label: 'Parameters' }, { id: 'network', label: 'Network' }, { id: 'settings', label: 'Settings' }, ], }, ]; const FLEET_TABS = new Set(['fleet', 'missions']); const DEFAULT_WS_URL = 'ws://localhost:9090'; function ConnectionBar({ url, setUrl, connected, error }) { const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(url); const handleApply = () => { setUrl(draft); setEditing(false); }; return (
{editing ? (
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleApply(); if (e.key === 'Escape') setEditing(false); }} autoFocus className="bg-gray-900 border border-cyan-800 rounded px-2 py-0.5 text-cyan-300 w-52 focus:outline-none" />
) : ( )}
); } export default function App() { const [wsUrl, setWsUrl] = useState(DEFAULT_WS_URL); const [activeTab, setActiveTab] = useState('status'); const { connected, error, subscribe, publish, callService, setParam } = useRosbridge(wsUrl); const publishFn = useCallback((name, type, data) => publish(name, type, data), [publish]); return (
{/* ── Top Bar ── */}
⚡ SALTYBOT DASHBOARD
{!FLEET_TABS.has(activeTab) && ( )}
{/* ── Status Header ── */} {/* ── Tab Navigation ── */} {/* ── Content ── */}
{activeTab === 'teleop-webui' && } {activeTab === 'gimbal' && } {activeTab === 'salty-face' && } {activeTab === 'status' && } {activeTab === 'faces' && } {activeTab === 'hands' && } {activeTab === 'conversation' && } {activeTab === 'history' && } {activeTab === 'personality' && } {activeTab === 'navigation' && } {activeTab === 'audio' && } {activeTab === 'accessibility' && } {activeTab === 'imu' && } {activeTab === 'battery' && } {activeTab === 'battery-chart' && } {activeTab === 'motors' && } {activeTab === 'motor-current-graph' && } {activeTab === 'thermal' && } {activeTab === 'map' && } {activeTab === 'control' && } {activeTab === 'health' && } {activeTab === 'cameras' && } {activeTab === 'waypoints' && } {activeTab === 'fleet' && } {activeTab === 'missions' && } {activeTab === 'diagnostics' && } {activeTab === 'eventlog' && } {activeTab === 'bandwidth' && } {activeTab === 'nodes' && } {activeTab === 'logs' && } {activeTab === 'parameters' && } {activeTab === 'network' && } {activeTab === 'settings' && }
{/* ── Footer ── */}
); }