diff --git a/src/components/panels/MosaicDashboard.tsx b/src/components/panels/MosaicDashboard.tsx index 2571027..d04c65b 100644 --- a/src/components/panels/MosaicDashboard.tsx +++ b/src/components/panels/MosaicDashboard.tsx @@ -36,60 +36,82 @@ import { ROVER_IP } from '@/constants'; interface TileDefinition { title: string; + category: TileCategory; render: () => ReactElement; } const TILE_REGISTRY = { mapView: { title: 'Map View', + category: 'navigation', render: () => (
), }, - rosMonitor: { title: 'System Telemetry', render: () => }, - networkHealthMonitor: { title: 'Connection Health', render: () => }, - orientationDisplay: { title: 'Rover Orientation', render: () => }, - videoControls: { title: 'Video Stream', render: () => }, - rtpStats: { title: 'RTP Statistics', render: () => }, - driveThrottlePanel: { title: 'Drive Throttle', render: () => }, - waypointList: { title: 'Waypoint List', render: () => }, - goalSetter: { title: 'Nav2', render: () => }, - motorStatusPanel: { title: 'Motor Status', render: () => }, - nodeStatusPanel: { title: 'Node Status', render: () => }, - antennaControlPanel: { title: 'Antenna Control', render: () => }, - scienceControlPanel: { title: 'Science Motor Control', render: () => }, - scienceSensorPanel: { title: 'Science Sensor Readouts', render: () => }, - pdbRails: { title: 'PDB Rails', render: () => }, - armControlPanel: { title: 'Arm Control', render: () => }, + rosMonitor: { title: 'System Telemetry', category: 'system', render: () => }, + networkHealthMonitor: { title: 'Connection Health', category: 'comms', render: () => }, + orientationDisplay: { title: 'Rover Orientation', category: 'armScience', render: () => }, + videoControls: { title: 'Video Stream', category: 'video', render: () => }, + rtpStats: { title: 'RTP Statistics', category: 'video', render: () => }, + driveThrottlePanel: { title: 'Drive Throttle', category: 'drive', render: () => }, + waypointList: { title: 'Waypoint List', category: 'navigation', render: () => }, + goalSetter: { title: 'Nav2', category: 'navigation', render: () => }, + motorStatusPanel: { title: 'Motor Status', category: 'drive', render: () => }, + nodeStatusPanel: { title: 'Node Status', category: 'system', render: () => }, + antennaControlPanel: { title: 'Antenna Control', category: 'comms', render: () => }, + scienceControlPanel: { title: 'Science Motor Control', category: 'armScience', render: () => }, + scienceSensorPanel: { title: 'Science Sensor Readouts', category: 'armScience', render: () => }, + pdbRails: { title: 'PDB Rails', category: 'system', render: () => }, + armControlPanel: { title: 'Arm Control', category: 'armScience', render: () => }, webRTCClient: { title: 'WebRTC Client', + category: 'video', render: () => , }, - timerPanel: { title: 'Multi-Timer', render: () => }, - headlightControlPanel: { title: 'Headlights', render: () => }, - morseTransmissionPanel: { title: 'Morse Transmission', render: () => }, - echoPanel: { title: 'Topic Echo', render: () => }, - motorCurrentGraphPanel: { title: 'Motor Current', render: () => }, + timerPanel: { title: 'Multi-Timer', category: 'system', render: () => }, + headlightControlPanel: { title: 'Headlights', category: 'drive', render: () => }, + morseTransmissionPanel: { title: 'Morse Transmission', category: 'armScience', render: () => }, + echoPanel: { title: 'Topic Echo', category: 'system', render: () => }, + motorCurrentGraphPanel: { title: 'Motor Current', category: 'drive', render: () => }, } satisfies Record; +const TILE_CATEGORIES = { + navigation: { label: 'Navigation', color: '#4fc3f7' }, // light blue + drive: { label: 'Drive', color: '#ffb74d' }, // orange + video: { label: 'Video', color: '#ce93d8' }, // purple + comms: { label: 'Comms', color: '#81c784' }, // green + armScience: { label: 'Arm & Science', color: '#f48fb1' }, // pink + system: { label: 'System & Debug', color: '#b0bec5' }, // grey +}; + +type TileCategory = keyof typeof TILE_CATEGORIES; type TileType = keyof typeof TILE_REGISTRY; type TileId = `${TileType}:${number}`; // Object.keys preserves insertion order for string keys const ALL_TILE_TYPES = Object.keys(TILE_REGISTRY) as TileType[]; +const TILE_GROUPS = (Object.keys(TILE_CATEGORIES) as TileCategory[]) + .map((category) => ({ + category, + types: ALL_TILE_TYPES + .filter((t) => TILE_REGISTRY[t].category === category) + .sort((a, b) => TILE_REGISTRY[a].title.localeCompare(TILE_REGISTRY[b].title)), + })) + .filter((g) => g.types.length > 0) + function tileTypeOf(id: TileId): TileType { return id.split(':', 1)[0] as TileType; } type PendingAdd = | { - pathKey: string; - path: MosaicPath; - direction: 'row' | 'column'; - } + pathKey: string; + path: MosaicPath; + direction: 'row' | 'column'; + } | null; function collectMaxTileNumber(node: MosaicNode | null): number { @@ -248,11 +270,18 @@ const Controls = memo<{ - {ALL_TILE_TYPES.map((t) => ( - - ))} + {TILE_GROUPS.map(({ category, types }) => { + const { label, color } = TILE_CATEGORIES[category]; + return ( + + {types.map((t) => ( + + ))} + + ); + })} ) : null}