/** * 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 { 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 { 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'; // 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'; const TAB_GROUPS = [ { label: 'SOCIAL', color: 'text-cyan-600', tabs: [ { id: 'status', label: 'Status', }, { id: 'faces', label: 'Faces', }, { 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: 'map', label: 'Map', }, { id: 'control', label: 'Control', }, { id: 'health', label: 'Health', }, { 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: 'eventlog', label: 'Events' }, { id: 'bandwidth', label: 'Bandwidth' }, ], }, { label: 'CONFIG', color: 'text-purple-600', tabs: [ { 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 === 'status' && } {activeTab === 'faces' && } {activeTab === 'conversation' && } {activeTab === 'history' && } {activeTab === 'personality' && } {activeTab === 'navigation' && } {activeTab === 'audio' && } {activeTab === 'imu' && } {activeTab === 'battery' && } {activeTab === 'battery-chart' && } {activeTab === 'motors' && } {activeTab === 'map' && } {activeTab === 'control' && (
)} {activeTab === 'health' && } {activeTab === 'cameras' && } {activeTab === 'waypoints' && } {activeTab === 'fleet' && } {activeTab === 'missions' && } {activeTab === 'eventlog' && } {activeTab === 'bandwidth' && } {activeTab === 'logs' && } {activeTab === 'network' && } {activeTab === 'settings' && }
{/* ── Footer ── */}
); }