diff --git a/ui/social-bot/package-lock.json b/ui/social-bot/package-lock.json index 3ada47e..0e85cc6 100644 --- a/ui/social-bot/package-lock.json +++ b/ui/social-bot/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "roslib": "^1.4.1" + "roslib": "^1.4.1", + "three": "^0.170.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", @@ -2695,6 +2696,12 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/ui/social-bot/package.json b/ui/social-bot/package.json index bbd601d..9127ae4 100644 --- a/ui/social-bot/package.json +++ b/ui/social-bot/package.json @@ -12,7 +12,8 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "roslib": "^1.4.1" + "roslib": "^1.4.1", + "three": "^0.170.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", diff --git a/ui/social-bot/src/App.jsx b/ui/social-bot/src/App.jsx index 5a587cd..2c4c78a 100644 --- a/ui/social-bot/src/App.jsx +++ b/ui/social-bot/src/App.jsx @@ -1,26 +1,55 @@ /** - * App.jsx — Saltybot Social Dashboard root component. + * App.jsx — Saltybot Social + Telemetry Dashboard root component. * - * Layout: - * [TopBar: connection config + pipeline state badge] - * [TabNav: Status | Faces | Conversation | Personality | Navigation] - * [TabContent] + * Social tabs (issue #107): + * Status | Faces | Conversation | Personality | Navigation + * + * Telemetry tabs (issue #126): + * IMU | Battery | Motors | Map | Control | Health */ import { useState, useCallback } from 'react'; import { useRosbridge } from './hooks/useRosbridge.js'; -import { StatusPanel } from './components/StatusPanel.jsx'; -import { FaceGallery } from './components/FaceGallery.jsx'; -import { ConversationLog } from './components/ConversationLog.jsx'; -import { PersonalityTuner } from './components/PersonalityTuner.jsx'; -import { NavModeSelector } from './components/NavModeSelector.jsx'; -const TABS = [ - { id: 'status', label: 'Status', icon: '⬤' }, - { id: 'faces', label: 'Faces', icon: '◉' }, - { id: 'conversation', label: 'Conversation', icon: '◌' }, - { id: 'personality', label: 'Personality', icon: '◈' }, - { id: 'navigation', label: 'Navigation', icon: '◫' }, +// Social panels +import { StatusPanel } from './components/StatusPanel.jsx'; +import { FaceGallery } from './components/FaceGallery.jsx'; +import { ConversationLog } from './components/ConversationLog.jsx'; +import { PersonalityTuner } from './components/PersonalityTuner.jsx'; +import { NavModeSelector } from './components/NavModeSelector.jsx'; + +// Telemetry panels +import { ImuPanel } from './components/ImuPanel.jsx'; +import { BatteryPanel } from './components/BatteryPanel.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'; + +const TAB_GROUPS = [ + { + label: 'SOCIAL', + color: 'text-cyan-600', + tabs: [ + { id: 'status', label: 'Status', }, + { id: 'faces', label: 'Faces', }, + { id: 'conversation', label: 'Convo', }, + { id: 'personality', label: 'Personality', }, + { id: 'navigation', label: 'Nav Mode', }, + ], + }, + { + label: 'TELEMETRY', + color: 'text-amber-600', + tabs: [ + { id: 'imu', label: 'IMU', }, + { id: 'battery', label: 'Battery', }, + { id: 'motors', label: 'Motors', }, + { id: 'map', label: 'Map', }, + { id: 'control', label: 'Control', }, + { id: 'health', label: 'Health', }, + ], + }, ]; const DEFAULT_WS_URL = 'ws://localhost:9090'; @@ -29,42 +58,47 @@ function ConnectionBar({ url, setUrl, connected, error }) { const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(url); - const handleApply = () => { - setUrl(draft); - setEditing(false); - }; + const handleApply = () => { setUrl(draft); setEditing(false); }; return (
- {/* Connection dot */}
- {editing ? (
setDraft(e.target.value)} - onKeyDown={(e) => { if (e.key === 'Enter') handleApply(); if (e.key === 'Escape') setEditing(false); }} + 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" /> - - + +
) : ( )} @@ -73,84 +107,71 @@ function ConnectionBar({ url, setUrl, connected, error }) { } export default function App() { - const [wsUrl, setWsUrl] = useState(DEFAULT_WS_URL); + const [wsUrl, setWsUrl] = useState(DEFAULT_WS_URL); const [activeTab, setActiveTab] = useState('status'); const { connected, error, subscribe, publish, callService, setParam } = useRosbridge(wsUrl); - - // Memoized publish for NavModeSelector (avoids recreating on every render) - const publishFn = useCallback( - (name, type, data) => publish(name, type, data), - [publish] - ); + const publishFn = useCallback((name, type, data) => publish(name, type, data), [publish]); return (
+ {/* ── Top Bar ── */}
⚡ SALTYBOT - SOCIAL DASHBOARD + DASHBOARD
- - +
- {/* ── Tab Nav ── */} -