mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
colorcoding
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -3,6 +3,7 @@ package roverd
|
||||
type helloMessage struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Battery BatteryConfig `json:"battery"`
|
||||
MaxWheelSpeed int `json:"maxWheelSpeed"`
|
||||
Media MediaConfig `json:"media"`
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -123,6 +125,7 @@ type AutoSideBrushConfig struct {
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name"`
|
||||
Color string `yaml:"color" json:"color,omitempty"`
|
||||
ServerURL string `yaml:"serverUrl"`
|
||||
Serial SerialConfig `yaml:"serial"`
|
||||
BRC BRCConfig `yaml:"brc"`
|
||||
@@ -207,6 +210,11 @@ func LoadConfig(path string) (*Config, error) {
|
||||
if cfg.Name == "" {
|
||||
return nil, errors.New("missing name")
|
||||
}
|
||||
normalizedColor, err := normalizeHexColor(cfg.Color)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Color = normalizedColor
|
||||
if cfg.ServerURL == "" {
|
||||
return nil, errors.New("missing serverUrl")
|
||||
}
|
||||
@@ -410,3 +418,16 @@ func deriveSRTURL(serverURL, streamName string, port int, mode string) (string,
|
||||
escaped := url.PathEscape(streamName)
|
||||
return fmt.Sprintf("srt://%s:%d?streamid=#!::r=%s,m=%s&latency=10&mode=caller&transtype=live&pkt_size=1316", host, port, escaped, mode), nil
|
||||
}
|
||||
|
||||
var hexColorRe = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)
|
||||
|
||||
func normalizeHexColor(raw string) (string, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return "", nil
|
||||
}
|
||||
if !hexColorRe.MatchString(trimmed) {
|
||||
return "", fmt.Errorf("color must be #RRGGBB, got %q", raw)
|
||||
}
|
||||
return strings.ToUpper(trimmed), nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Sample configuration for roverd
|
||||
name: roomba-alpha
|
||||
color: "#4DB6AC"
|
||||
serverUrl: ws://control-server.local:8080/rover
|
||||
serial:
|
||||
device: /dev/ttyAMA0
|
||||
|
||||
@@ -113,6 +113,7 @@ func (c *WSClient) sendHello(ctx context.Context, conn *websocket.Conn) error {
|
||||
msg := helloMessage{
|
||||
Type: "hello",
|
||||
Name: c.cfg.Name,
|
||||
Color: c.cfg.Color,
|
||||
Battery: c.cfg.Battery,
|
||||
MaxWheelSpeed: c.cfg.MaxWheelMMs,
|
||||
Media: c.cfg.Media,
|
||||
|
||||
@@ -30,4 +30,20 @@ This means that locking / unlocking will act a little different than standard ro
|
||||
- private rovers don't show in the list for normal users unless they are unlocked
|
||||
- they will only show for lockdown admins
|
||||
- when unlocked, they show for everyone
|
||||
- with a different color in the rover list
|
||||
- with a different color in the rover list
|
||||
|
||||
## . . .
|
||||
this will be kind of invasive, touching a lot of systems server-side, long story short:
|
||||
- private rovers are set as private in the roverd config
|
||||
- by default:
|
||||
- locked to only lockdown admins
|
||||
- cant be spectated by anyone
|
||||
- any user who isnt a lockdown admin cannot know that it exists in any way at all
|
||||
- not included by most automated systems like LLM integration, discord alerts, etc
|
||||
- still included in safties like auto docking
|
||||
- limitations dont apply because its lockdown admin only anyway
|
||||
- when opened up (can only be opened by lockdown admins):
|
||||
- only verified users can drive them
|
||||
- anyone can spectate them
|
||||
- limits apply
|
||||
- included in all automated systems just like a normal rover
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
- audio files first [x]
|
||||
- then voice chat [x]
|
||||
8. mobile controls column swapping (optional joystick on left) [x]
|
||||
9. fix fullscreen on mobile so that you can re-enter it
|
||||
9. fix fullscreen on mobile so that you can re-enter it [x]
|
||||
10. home assistant rover mute switch
|
||||
|
||||
# relative pipe dreams:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||
<title>Multi Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-Dnr0s1G9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-r3OnudWV.css">
|
||||
<script type="module" crossorigin src="/assets/index-BSallfbn.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DFsFpeU2.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -83,6 +83,12 @@ function resolveRoverId(socketId) {
|
||||
return assignment?.roverId || null;
|
||||
}
|
||||
|
||||
function resolveRoverColor(roverId) {
|
||||
if (!roverId) return null;
|
||||
const record = roverManager.rovers.get(String(roverId));
|
||||
return record?.meta?.color || null;
|
||||
}
|
||||
|
||||
function normalizeUserText(raw) {
|
||||
if (typeof raw !== 'string') return '';
|
||||
return raw.replace(/\\n/g, '\n');
|
||||
@@ -90,6 +96,7 @@ function normalizeUserText(raw) {
|
||||
|
||||
function buildMessage(socket, text, meta = {}) {
|
||||
const roverId = meta.roverId || resolveRoverId(socket?.id);
|
||||
const roverColor = meta.roverColor ?? resolveRoverColor(roverId);
|
||||
return {
|
||||
id: uuidv4(),
|
||||
ts: Date.now(),
|
||||
@@ -97,6 +104,7 @@ function buildMessage(socket, text, meta = {}) {
|
||||
nickname: meta.nickname || getNickname(socket) || null,
|
||||
role: meta.role || getRole(socket),
|
||||
roverId,
|
||||
roverColor,
|
||||
fromDiscord: Boolean(meta.fromDiscord),
|
||||
discordGuildId: meta.discordGuildId || null,
|
||||
discordGuildName: meta.discordGuildName || null,
|
||||
@@ -219,6 +227,7 @@ function buildRoverCtxSnapshot(roverId) {
|
||||
|
||||
function buildTypingPayload(socket, meta = {}) {
|
||||
const roverId = meta.roverId || resolveRoverId(socket?.id);
|
||||
const roverColor = meta.roverColor ?? resolveRoverColor(roverId);
|
||||
const socketId = socket?.id || null;
|
||||
const fromDiscord = Boolean(meta.fromDiscord);
|
||||
let typingId = meta.typingId || null;
|
||||
@@ -250,6 +259,7 @@ function buildTypingPayload(socket, meta = {}) {
|
||||
nickname: meta.nickname || getNickname(socket) || null,
|
||||
role: meta.role || getRole(socket),
|
||||
roverId,
|
||||
roverColor,
|
||||
fromDiscord,
|
||||
discordGuildId: meta.discordGuildId || null,
|
||||
discordGuildName: meta.discordGuildName || null,
|
||||
|
||||
@@ -6,6 +6,7 @@ function getReplaySources() {
|
||||
type: 'rover',
|
||||
id: String(rover.id),
|
||||
label: rover.name || rover.id,
|
||||
color: rover.color || null,
|
||||
}));
|
||||
const roomSources = getRoomCameras().map((camera) => ({
|
||||
type: 'room',
|
||||
|
||||
@@ -145,6 +145,7 @@ function getRoster() {
|
||||
return Array.from(rovers.values()).map((record) => ({
|
||||
id: record.id,
|
||||
name: record.meta?.name || record.id,
|
||||
color: record.meta?.color || null,
|
||||
battery: record.meta?.battery,
|
||||
batteryState: record.batteryState,
|
||||
maxWheelSpeed: record.meta?.maxWheelSpeed,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FaDiscord } from 'react-icons/fa';
|
||||
import { roverBadgeStyle } from '../lib/roverColor.js';
|
||||
|
||||
function roleColors(role) {
|
||||
switch (role) {
|
||||
@@ -88,7 +89,12 @@ export function ChatIdentity({ message }) {
|
||||
</span>
|
||||
) : null}
|
||||
{message.roverId && (
|
||||
<span className="rounded bg-slate-800 px-1 text-[0.7rem]">{message.roverId}</span>
|
||||
<span
|
||||
className="rounded bg-slate-800 px-1 text-[0.7rem]"
|
||||
style={roverBadgeStyle(message.roverColor, 0.14)}
|
||||
>
|
||||
{message.roverId}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -120,6 +120,7 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
snapshotFeed={snapshotFeed}
|
||||
audioSessionInfo={audioInfo}
|
||||
label={roverLabel}
|
||||
roverColor={batteryRecord?.color || null}
|
||||
telemetryFrame={frame}
|
||||
batteryConfig={batteryConfig}
|
||||
layoutFormat={layoutFormat}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../settings/index.js';
|
||||
import { roverSwatchStyle } from '../lib/roverColor.js';
|
||||
|
||||
function normalizeSources(list = []) {
|
||||
return list
|
||||
@@ -10,6 +11,7 @@ function normalizeSources(list = []) {
|
||||
type: entry.type,
|
||||
id: String(entry.id),
|
||||
label: entry.label || String(entry.id),
|
||||
color: entry.color || null,
|
||||
key: `${entry.type}:${entry.id}`,
|
||||
};
|
||||
})
|
||||
@@ -143,6 +145,13 @@ function GroupList({ title, items, selected, onToggle }) {
|
||||
onChange={() => onToggle(item.key)}
|
||||
className="accent-emerald-400"
|
||||
/>
|
||||
{item.type === 'rover' && item.color ? (
|
||||
<span
|
||||
className="inline-block h-2 w-2 rounded-full border border-white/30"
|
||||
style={roverSwatchStyle(item.color)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<span className="truncate">{item.label}</span>
|
||||
</label>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { roverNameStyle } from '../lib/roverColor.js';
|
||||
|
||||
function classNames(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
@@ -131,7 +132,9 @@ export default function RoverQueuesPanel({ title = 'Rovers' }) {
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div className="flex items-center justify-between gap-0.5">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<p className="text-slate-200">{rover.name}</p>
|
||||
<p className="text-slate-200" style={roverNameStyle(rover.color)}>
|
||||
{rover.name}
|
||||
</p>
|
||||
{showTimer ? (
|
||||
<span className="rounded bg-slate-800 px-1 text-[0.7rem] text-slate-200">
|
||||
{isSelfCurrent ? `${remainingSeconds}s left` : `Your turn in ${remainingSeconds}s`}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { roverNameStyle } from '../lib/roverColor.js';
|
||||
|
||||
function classNames(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
}
|
||||
@@ -37,7 +39,9 @@ export default function RoverRoster({
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<p className="text-slate-200">{rover.name}</p>
|
||||
<p className="text-slate-200" style={roverNameStyle(rover.color)}>
|
||||
{rover.name}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 flex flex-wrap items-center gap-0.5">
|
||||
<span>{rover.locked ? 'locked' : 'free'}</span>
|
||||
{rover.lockReason && <span className="rounded bg-black/30 px-1">{rover.lockReason}</span>}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import NicknameForm from './NicknameForm.jsx';
|
||||
import SocialButtonsGrid from './SocialButtonsGrid.jsx';
|
||||
import { roverBadgeStyle, roverNameStyle } from '../lib/roverColor.js';
|
||||
|
||||
export function NicknameEntryPanel({ compact = false }) {
|
||||
return (
|
||||
@@ -75,8 +76,8 @@ export default function UserListPanel({
|
||||
[selfId, users],
|
||||
);
|
||||
|
||||
const rosterName = useCallback(
|
||||
(roverId) => roster.find((r) => String(r.id) === String(roverId))?.name || roverId,
|
||||
const rosterEntry = useCallback(
|
||||
(roverId) => roster.find((r) => String(r.id) === String(roverId)) || null,
|
||||
[roster],
|
||||
);
|
||||
|
||||
@@ -124,7 +125,12 @@ export default function UserListPanel({
|
||||
>
|
||||
<p className={`font-semibold ${roleColors(user.role)}`}>{formatLabel(user, selfId)}</p>
|
||||
{user.roverId ? (
|
||||
<span className="rounded bg-slate-800 px-1 text-[0.7rem]">rover {user.roverId}</span>
|
||||
<span
|
||||
className="rounded bg-slate-800 px-1 text-[0.7rem]"
|
||||
style={roverBadgeStyle(rosterEntry(user.roverId)?.color, 0.12)}
|
||||
>
|
||||
rover {rosterEntry(user.roverId)?.name || user.roverId}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[0.7rem] text-slate-500">no rover</span>
|
||||
)}
|
||||
@@ -211,7 +217,9 @@ export default function UserListPanel({
|
||||
return (
|
||||
<div key={roverId} className={`surface-muted flex flex-col gap-0.5 ${compact ? 'text-[0.8rem] py-0.25' : 'text-sm'}`}>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<p className="font-semibold text-slate-200">{rosterName(roverId)}</p>
|
||||
<p className="font-semibold text-slate-200" style={roverNameStyle(rosterEntry(roverId)?.color)}>
|
||||
{rosterEntry(roverId)?.name || roverId}
|
||||
</p>
|
||||
{remaining != null && (
|
||||
<span className="rounded bg-slate-800 px-1 text-[0.7rem]">
|
||||
{remaining}s left
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AUDIO_SETTINGS_DEFAULTS } from '../settings/namespaces.js';
|
||||
import SocialButton from './SocialButton.jsx';
|
||||
import BatteryBar from './BatteryBar.jsx';
|
||||
import { buildBatteryVisual } from '../lib/battery.js';
|
||||
import { roverNameStyle } from '../lib/roverColor.js';
|
||||
|
||||
const RESTART_DELAY_MS = 2000;
|
||||
const UNMUTE_RETRY_MS = 3000;
|
||||
@@ -23,6 +24,7 @@ export default function VideoTile({
|
||||
snapshotFeed = null,
|
||||
qualityNotice = null,
|
||||
label,
|
||||
roverColor = null,
|
||||
forceMute = false,
|
||||
telemetryFrame,
|
||||
batteryConfig,
|
||||
@@ -606,6 +608,7 @@ export default function VideoTile({
|
||||
frame={telemetryFrame}
|
||||
sensors={sensors}
|
||||
label={label}
|
||||
roverColor={roverColor}
|
||||
status={renderedStatus}
|
||||
audioStatus={renderedAudioStatus}
|
||||
levelStatus={levelIndicator}
|
||||
@@ -721,6 +724,7 @@ function HudOverlay({
|
||||
frame,
|
||||
sensors,
|
||||
label,
|
||||
roverColor = null,
|
||||
status,
|
||||
audioStatus,
|
||||
levelStatus,
|
||||
@@ -845,7 +849,9 @@ function HudOverlay({
|
||||
<div
|
||||
className={`flex items-center gap-0.5 bg-black/80 text-slate-100 ${labelPadClass} ${labelTextClass}`}
|
||||
>
|
||||
<span className="font-semibold text-white">{label || 'Unnamed Rover'}</span>
|
||||
<span className="font-semibold text-white" style={roverNameStyle(roverColor)}>
|
||||
{label || 'Unnamed Rover'}
|
||||
</span>
|
||||
{driverLabel ? <span className="text-slate-300">• {driverLabel}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -882,7 +888,9 @@ function HudOverlay({
|
||||
) : null}
|
||||
<div className={`absolute ${labelPosClass} left-1/2`} style={labelWrapperStyle}>
|
||||
<div className={`flex gap-0.5 bg-black/80 text-slate-100 ${labelPadClass} ${labelTextClass}`}>
|
||||
<span>Rover: "{label || 'Unnamed Rover'}"</span>
|
||||
<span>
|
||||
Rover: "<span style={roverNameStyle(roverColor)}>{label || 'Unnamed Rover'}</span>"
|
||||
</span>
|
||||
{/* <span>{pulse ? 'Sensors active' : 'No recent sensors'}</span> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const HEX_RE = /^#[0-9A-Fa-f]{6}$/;
|
||||
|
||||
export function normalizeRoverColor(value) {
|
||||
const raw = typeof value === 'string' ? value.trim() : '';
|
||||
if (!raw || !HEX_RE.test(raw)) return null;
|
||||
return raw.toUpperCase();
|
||||
}
|
||||
|
||||
export function roverNameStyle(color) {
|
||||
const normalized = normalizeRoverColor(color);
|
||||
if (!normalized) return undefined;
|
||||
return { color: normalized };
|
||||
}
|
||||
|
||||
export function roverBadgeStyle(color, alpha = 0.2) {
|
||||
const normalized = normalizeRoverColor(color);
|
||||
if (!normalized) return undefined;
|
||||
const safeAlpha = Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 0.2;
|
||||
const rgb = hexToRgb(normalized);
|
||||
if (!rgb) return undefined;
|
||||
return {
|
||||
borderColor: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.55)`,
|
||||
backgroundColor: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${safeAlpha})`,
|
||||
color: normalized,
|
||||
};
|
||||
}
|
||||
|
||||
export function roverSwatchStyle(color) {
|
||||
const normalized = normalizeRoverColor(color);
|
||||
if (!normalized) return undefined;
|
||||
return { backgroundColor: normalized };
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
const normalized = normalizeRoverColor(hex);
|
||||
if (!normalized) return null;
|
||||
return {
|
||||
r: parseInt(normalized.slice(1, 3), 16),
|
||||
g: parseInt(normalized.slice(3, 5), 16),
|
||||
b: parseInt(normalized.slice(5, 7), 16),
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import AlertFeed from '../components/AlertFeed.jsx';
|
||||
import useDefaultNickname from '../hooks/useDefaultNickname.js';
|
||||
import BatteryBar from '../components/BatteryBar.jsx';
|
||||
import { buildBatteryVisual } from '../lib/battery.js';
|
||||
import { roverNameStyle } from '../lib/roverColor.js';
|
||||
|
||||
const ROTATE_MS = 20000;
|
||||
const HARD_REFRESH_MS = 1 * 60 * 60 * 1000;
|
||||
@@ -57,7 +58,12 @@ function InfoColumn({
|
||||
{isActiveView ? (
|
||||
<div className="relative z-10 flex min-w-0 flex-1 flex-col justify-between text-center">
|
||||
<div className="min-w-0 bg-transparent px-0 py-0 leading-none">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={1000} minSize={18}>
|
||||
<AutoFitText
|
||||
className="font-semibold leading-none text-white"
|
||||
maxSize={1000}
|
||||
minSize={18}
|
||||
style={roverNameStyle(rover.color)}
|
||||
>
|
||||
{rover.name || rover.id}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
@@ -82,7 +88,12 @@ function InfoColumn({
|
||||
<>
|
||||
<div className="relative z-10 flex min-w-0 flex-col gap-1 text-center">
|
||||
<div className="min-w-0 bg-transparent px-0 py-0 leading-none">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={1000} minSize={18}>
|
||||
<AutoFitText
|
||||
className="font-semibold leading-none text-white"
|
||||
maxSize={1000}
|
||||
minSize={18}
|
||||
style={roverNameStyle(rover.color)}
|
||||
>
|
||||
{rover.name || rover.id}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
@@ -113,6 +124,7 @@ function InfoColumn({
|
||||
snapshotFeed={snapshotFeed}
|
||||
audioSessionInfo={null}
|
||||
label={rover.name || rover.id}
|
||||
roverColor={rover.color || null}
|
||||
telemetryFrame={frame}
|
||||
batteryConfig={rover.battery}
|
||||
layoutFormat="mobile"
|
||||
@@ -130,7 +142,7 @@ function InfoColumn({
|
||||
);
|
||||
}
|
||||
|
||||
function AutoFitText({ children, className = '', maxSize = 1000, minSize = 14 }) {
|
||||
function AutoFitText({ children, className = '', maxSize = 1000, minSize = 14, style = undefined }) {
|
||||
const containerRef = useRef(null);
|
||||
const textRef = useRef(null);
|
||||
const [fontSize, setFontSize] = useState(maxSize);
|
||||
@@ -183,7 +195,7 @@ function AutoFitText({ children, className = '', maxSize = 1000, minSize = 14 })
|
||||
<div
|
||||
ref={textRef}
|
||||
className={`whitespace-nowrap ${className}`}
|
||||
style={{ fontSize: `${fontSize}px`, lineHeight: 1.1 }}
|
||||
style={{ fontSize: `${fontSize}px`, lineHeight: 1.1, ...(style || {}) }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -344,6 +356,7 @@ function MiniSummaryContent() {
|
||||
audioSessionInfo={isActive ? activeAudio : null}
|
||||
forceMute={!isActive}
|
||||
label={rover.name || rover.id}
|
||||
roverColor={rover.color || null}
|
||||
telemetryFrame={frames[rover.id] || null}
|
||||
batteryConfig={rover.battery}
|
||||
layoutFormat="mobile"
|
||||
@@ -361,6 +374,7 @@ function MiniSummaryContent() {
|
||||
snapshotFeed={activeSnapshot}
|
||||
audioSessionInfo={activeAudio}
|
||||
label={activeRover.name || activeRover.id}
|
||||
roverColor={activeRover.color || null}
|
||||
telemetryFrame={activeFrame}
|
||||
batteryConfig={activeRover.battery}
|
||||
layoutFormat="mobile"
|
||||
|
||||
@@ -63,6 +63,7 @@ function RoverSpectatorCard({ rover, frame, sessionInfo, videoMode, snapshotFeed
|
||||
snapshotFeed={snapshotFeed}
|
||||
audioSessionInfo={audioInfo}
|
||||
label={rover.name}
|
||||
roverColor={rover.color || null}
|
||||
telemetryFrame={frame}
|
||||
batteryConfig={rover.battery}
|
||||
hudVariant="spectator"
|
||||
|
||||
Reference in New Issue
Block a user