mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
rover labels and theme tweakings
This commit is contained in:
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user