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
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
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-PXEt8KKN.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BvjNDQTC.css">
<script type="module" crossorigin src="/assets/index-Dd_cj-I7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BLKXqzg3.css">
</head>
<body>
<div id="root"></div>
@@ -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">