rover labels and theme tweakings

This commit is contained in:
legop3
2026-06-02 02:44:47 -04:00
parent e1f8000cce
commit 98156676ee
20 changed files with 261 additions and 215 deletions
@@ -1,7 +1,7 @@
// Replay Snapshot Health
// Purpose: Defines the Replay Snapshot Health module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { roverNameChromeStyle } from '../../lib/roverColor.js';
import RoverLabel from '../RoverLabel/index.jsx';
export default function ReplaySnapshotHealth({ health, roster = [] }) {
if (!health) return null;
@@ -35,12 +35,11 @@ export default function ReplaySnapshotHealth({ health, roster = [] }) {
<div className="space-y-0.5 text-xs text-slate-300">
{replay.sources.map((source) => (
<div key={`${source.type}:${source.id}`} className="flex items-center justify-between">
<span
className={`${source.type === 'rover' ? 'rounded px-1 py-[1px] border border-transparent' : ''}`}
style={source.type === 'rover' ? roverNameChromeStyle(roverColorFor(source.id), 0.16) : undefined}
>
{source.label}
</span>
{source.type === 'rover' ? (
<RoverLabel name={source.label} color={roverColorFor(source.id)} fallback={source.id} />
) : (
<span>{source.label}</span>
)}
<span className={source.ready ? 'text-emerald-300' : 'text-amber-300'}>
{source.recentCount}/{source.neededCount}
</span>
@@ -50,12 +49,7 @@ export default function ReplaySnapshotHealth({ health, roster = [] }) {
<div className="space-y-0.5 text-xs text-slate-300">
{snapshots.rovers.map((entry) => (
<div key={`rover:${entry.id}`} className="flex items-center justify-between">
<span
className="rounded px-1 py-[1px] border border-transparent"
style={roverNameChromeStyle(roverColorFor(entry.id), 0.16)}
>
{entry.name}
</span>
<RoverLabel name={entry.name} color={roverColorFor(entry.id)} fallback={entry.id} />
<span className={entry.stale ? 'text-amber-300' : 'text-emerald-300'}>{entry.stale ? 'stale' : 'ok'}</span>
</div>
))}
+12 -4
View File
@@ -50,17 +50,24 @@ export default function CardFrame({
return rover?.color || null;
});
const accentRgb = hexToRgb(ownRoverColor);
const cardStyle = accentRgb ? { borderColor: rgba(accentRgb, 0.5) } : undefined;
// swap these to toggle rover card border colors stuff
const cardStyle = accentRgb ? { borderColor: rgba(accentRgb, 0.3) } : undefined;
// const cardStyle = undefined;
const headerStyle = accentRgb
? {
backgroundImage: `linear-gradient(90deg, rgba(23,23,23,0.96) 0%, rgba(38,38,38,0.94) 30%, ${rgba(accentRgb, 0.28)} 100%)`,
backgroundImage: `linear-gradient(90deg, rgba(23,23,23,0.96) 0%, rgba(38,38,38,0.94) 0%, ${rgba(accentRgb, 0.1)} 100%)`,
// backgroundImage: `linear-gradient(90deg, ${rgba(accentRgb, 0.1)} 100%)`,
// backgroundImage: `background-color: ${rgba(accentRgb, 0.2)}`
}
: undefined;
return (
<section
className={cx(
'panel-section border border-neutral-500/60 bg-neutral-900/95',
'panel-section border border-neutral-500/60 bg-neutral-900/100',
clipOverflow ? 'overflow-hidden' : 'overflow-visible',
fillHeight && 'flex h-full min-h-0 flex-col',
className,
@@ -71,7 +78,8 @@ export default function CardFrame({
// Header row
<header
className={cx(
'flex items-center justify-between gap-0.5 border-b border-neutral-500/50 bg-gradient-to-r from-neutral-800 via-neutral-700 to-neutral-600 px-0.5 py-0.5',
'flex items-center justify-between gap-0.5 border-b border-neutral-500/50 bg-slate-800 px-0.5 py-0.5',
// 'flex items-center justify-between gap-0.5 border-b border-neutral-500/50 bg-gradient-to-r from-neutral-800 via-neutral-700 to-neutral-600 px-0.5 py-0.5',
headerClassName,
)}
style={headerStyle}
@@ -3,8 +3,8 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useState } from 'react';
import { FaDiscord } from 'react-icons/fa';
import { roverBadgeStyle } from '../../lib/roverColor.js';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import RoverLabel from '../RoverLabel/index.jsx';
function roleColors(role) {
switch (role) {
@@ -107,12 +107,12 @@ export function ChatIdentity({ message, toolsToggle = null }) {
</span>
) : null}
{message.roverId && (
<span
className="shrink-0 rounded bg-slate-800 px-1 text-[0.7rem]"
style={roverBadgeStyle(message.roverColor, 0.14)}
>
{message.roverId}
</span>
<RoverLabel
roverId={message.roverId}
color={message.roverColor}
fallback={message.roverId}
className="shrink-0 text-[0.7rem]"
/>
)}
{toolsToggle}
</span>
@@ -1,4 +1,4 @@
import { roverNameChromeStyle } from '../../../lib/roverColor.js';
import RoverLabel from '../../RoverLabel/index.jsx';
export default function RoverLabelOverlay({
variant = 'default',
@@ -20,12 +20,7 @@ export default function RoverLabelOverlay({
return (
<div className={`absolute ${labelPosClass} left-1/2`} style={labelWrapperStyle}>
<div className={`flex items-center gap-0.5 bg-black/80 text-slate-100 ${labelPadClass} ${labelTextClass}`}>
<span
className="font-semibold text-white rounded px-1 py-[1px] border border-transparent"
style={roverNameChromeStyle(roverColor, 0.18)}
>
{label || 'No rover'}
</span>
<RoverLabel name={label} color={roverColor} />
{driverLabel ? <span className="text-slate-300"> {driverLabel}</span> : null}
</div>
</div>
@@ -35,15 +30,7 @@ export default function RoverLabelOverlay({
return (
<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:{' '}
<span
className="rounded px-1 py-[1px] border border-transparent"
style={roverNameChromeStyle(roverColor, 0.18)}
>
"{label || 'No rover'}"
</span>
</span>
<RoverLabel name={label} color={roverColor} />
</div>
</div>
);
@@ -4,8 +4,8 @@
import { useEffect, useMemo, useState } from 'react';
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js';
import { roverNameChromeStyle } from '../../lib/roverColor.js';
import CardFrame from '../CardFrame/index.jsx';
import RoverLabel from '../RoverLabel/index.jsx';
function normalizeSources(list = []) {
return list
@@ -215,12 +215,11 @@ function GroupList({ title, items, selected, onToggle }) {
onChange={() => onToggle(item.key)}
className="accent-emerald-400"
/>
<span
className={`truncate ${item.type === 'rover' ? 'rounded px-1 py-[1px] border border-transparent' : ''}`}
style={item.type === 'rover' ? roverNameChromeStyle(item.color, 0.16) : undefined}
>
{item.label}
</span>
{item.type === 'rover' ? (
<RoverLabel name={item.label} color={item.color} fallback={item.id} className="truncate" />
) : (
<span className="truncate">{item.label}</span>
)}
</label>
))}
</div>
+3 -1
View File
@@ -37,7 +37,9 @@ function TopDownMapPanel() {
const sensors = frame?.sensors || {};
return (
<CardFrame hideHeader>
<CardFrame
title = "Roomba sensor view"
>
<div className="aspect-square w-full">
<TopDownMap sensors={sensors} />
</div>
@@ -10,7 +10,7 @@ import CardFrame from '../CardFrame/index.jsx';
function EmptyState() {
return (
<CardFrame title="Room cameras" bodyClassName="space-y-0.5 text-sm">
<CardFrame title="Room Cameras" bodyClassName="space-y-0.5 text-sm">
<p className="text-center text-slate-400">No room cameras configured.</p>
<p className="text-center text-slate-500">Add entries to server config to populate this list.</p>
</CardFrame>
+62
View File
@@ -0,0 +1,62 @@
import { useMemo } from 'react';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import { roverNameChromeStyle } from '../../lib/roverColor.js';
function classNames(...values) {
return values.filter(Boolean).join(' ');
}
function resolveText({ rover, name, fallback, roverId, rosterEntry }) {
const candidates = [
name,
rover?.name,
rosterEntry?.name,
fallback,
roverId,
rover?.id,
rosterEntry?.id,
'No rover',
];
for (const candidate of candidates) {
const text = String(candidate || '').trim();
if (text) return text;
}
return 'No rover';
}
export default function RoverLabel({
rover = null,
roverId = null,
name = null,
color = null,
fallback = null,
className = '',
as: Component = 'span',
style = undefined,
...props
}) {
const resolvedRoverId = roverId ?? rover?.id ?? null;
const rosterEntry = useSessionSelector((state) => {
if (!resolvedRoverId) return null;
const roster = Array.isArray(state.session?.roster) ? state.session.roster : [];
return roster.find((entry) => String(entry?.id) === String(resolvedRoverId)) || null;
});
const resolvedColor = color ?? rover?.color ?? rosterEntry?.color ?? null;
const label = useMemo(
() => resolveText({ rover, name, fallback, roverId: resolvedRoverId, rosterEntry }),
[fallback, name, resolvedRoverId, rosterEntry, rover],
);
return (
<Component
className={classNames(
'inline-block rounded border border-transparent px-1 py-[1px] font-semibold text-white',
className,
)}
style={{ ...(style || {}), ...(roverNameChromeStyle(resolvedColor, 0.26) || {}) }}
{...props}
>
{label}
</Component>
);
}
@@ -3,8 +3,8 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useMemo, useState } from 'react';
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
import { roverNameChromeStyle } from '../../lib/roverColor.js';
import CardFrame from '../CardFrame/index.jsx';
import RoverLabel from '../RoverLabel/index.jsx';
function classNames(...values) {
return values.filter(Boolean).join(' ');
@@ -188,9 +188,7 @@ export default function RoverQueuesPanel({ title = 'Rovers' }) {
<div className="flex items-center justify-between gap-0.5">
<div className="flex min-w-0 items-center gap-0.5">
<p className="min-w-0 flex items-center gap-0.5 whitespace-nowrap text-slate-200">
<span className="rounded px-1 py-[1px] border border-transparent" style={roverNameChromeStyle(rover.color, 0.16)}>
{rover.name}
</span>
<RoverLabel rover={rover} fallback={roverId} />
{rover.description ? (
<span className="min-w-0 flex-1 truncate text-[0.7rem] text-slate-400">
{rover.description}
+3 -5
View File
@@ -1,7 +1,7 @@
// Rover Roster
// Purpose: Defines the Rover Roster module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { roverNameChromeStyle } from '../../lib/roverColor.js';
import RoverLabel from '../RoverLabel/index.jsx';
function classNames(...values) {
return values.filter(Boolean).join(' ');
@@ -49,10 +49,8 @@ export default function RoverRoster({
)}
>
<div>
<p className="text-slate-200">
<span className="rounded px-1 py-[1px] border border-transparent" style={roverNameChromeStyle(rover.color, 0.16)}>
{rover.name}
</span>
<p>
<RoverLabel rover={rover} fallback={rover.id} />
</p>
<p className="text-xs text-slate-500 flex flex-wrap items-center gap-0.5">
<span>{rover.locked ? 'locked' : 'free'}</span>
@@ -16,7 +16,7 @@ import {
function TopDownMapContent({ sensors = {}, variant = 'full', size: overrideSize, overlay = false }) {
const size = overrideSize || (variant === 'mini' ? 190 : 260);
const center = size / 2;
const offsetY = size * 0.07;
const offsetY = size * 0.05;
const centerX = center;
const centerY = center + offsetY;
const innerCircle = center * 0.8;
+8 -13
View File
@@ -5,8 +5,8 @@ import { useEffect, useMemo, useState, useCallback } from 'react';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import NicknameForm from '../NicknameForm/index.jsx';
import SocialButtonsGrid from '../SocialButtonsGrid/index.jsx';
import { roverBadgeStyle, roverNameChromeStyle } from '../../lib/roverColor.js';
import CardFrame from '../CardFrame/index.jsx';
import RoverLabel from '../RoverLabel/index.jsx';
export function NicknameEntryPanel({ compact = false }) {
return (
@@ -130,12 +130,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]"
style={roverBadgeStyle(rosterEntry(user.roverId)?.color, 0.12)}
>
rover {rosterEntry(user.roverId)?.name || user.roverId}
</span>
<RoverLabel
roverId={user.roverId}
color={rosterEntry(user.roverId)?.color}
fallback={user.roverId}
className="text-[0.7rem]"
/>
) : (
<span className="text-[0.7rem] text-slate-500">no rover</span>
)}
@@ -214,12 +214,7 @@ export default function UserListPanel({
<div key={roverId} className={`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">
<span
className="rounded px-1 py-[1px] border border-transparent"
style={roverNameChromeStyle(rosterEntry(roverId)?.color, 0.16)}
>
{rosterEntry(roverId)?.name || roverId}
</span>
<RoverLabel roverId={roverId} fallback={roverId} />
</p>
{remaining != null && (
<span className="rounded bg-slate-800 px-1 text-[0.7rem]">
@@ -4,6 +4,7 @@
import { useMemo, useState } from 'react';
import CardFrame from '../CardFrame/index.jsx';
import { flowWrapClass, innerFlowClass } from './constants.js';
import RoverLabel from '../RoverLabel/index.jsx';
export default function VipPrivateRoverAccessCard({
requestableRovers = [],
@@ -56,7 +57,9 @@ export default function VipPrivateRoverAccessCard({
return (
<div key={roverId} className="surface-muted flex items-center justify-between gap-0.5 px-1 py-0.5">
<div className="min-w-0 text-left">
<p className="truncate text-xs font-semibold text-slate-100">{rover.name || roverId}</p>
<p className="truncate text-xs">
<RoverLabel rover={rover} fallback={roverId} className="max-w-full truncate align-middle" />
</p>
<p className="truncate text-[0.7rem] text-slate-500">{roverId}</p>
</div>
{pending ? (
+1 -1
View File
@@ -62,7 +62,7 @@ body {
.pride-page-bg {
background-color: #050505;
background-image:
linear-gradient(rgba(0, 0, 0, 0.24), rgba(0, 0, 0, 0.24)),
linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0)),
repeating-linear-gradient(
45deg,
#5bcefa 0 1.818%,
@@ -3,7 +3,7 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import RoverMediaPlayer from '../../../components/RoverMediaPlayer/index.jsx';
import BatteryBar from '../../../components/BatteryBar/index.jsx';
import { roverNameChromeStyle } from '../../../lib/roverColor.js';
import RoverLabel from '../../../components/RoverLabel/index.jsx';
import { getBatteryVisual } from '../utils.js';
import AutoFitText from './AutoFitText.jsx';
@@ -35,14 +35,14 @@ export default 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 rounded border border-transparent px-1 py-[1px]"
<RoverLabel
rover={rover}
fallback={rover.id}
as={AutoFitText}
className="leading-none"
maxSize={1000}
minSize={18}
style={roverNameChromeStyle(rover.color, 0.18)}
>
{rover.name || rover.id}
</AutoFitText>
/>
</div>
{driverLabel ? (
<div className="min-w-0 bg-transparent px-0 py-0 leading-none">
@@ -65,14 +65,14 @@ export default 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 rounded border border-transparent px-1 py-[1px]"
<RoverLabel
rover={rover}
fallback={rover.id}
as={AutoFitText}
className="leading-none"
maxSize={1000}
minSize={18}
style={roverNameChromeStyle(rover.color, 0.18)}
>
{rover.name || rover.id}
</AutoFitText>
/>
</div>
{driverLabel ? (
<div className="min-w-0 bg-transparent px-0 py-0 leading-none">