mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
colorcoding
This commit is contained in:
@@ -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