mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
rover labels and theme tweakings
This commit is contained in:
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="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>
|
||||
))}
|
||||
|
||||
@@ -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