mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
HELP!!!!
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// Auto Fit Text
|
||||
// Purpose: Sizes one unwrapped label to the largest font that fits its container.
|
||||
// Scope: Supports the existing width-only labels and full-box overlays from one shared implementation.
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function AutoFitText({
|
||||
children,
|
||||
className = '',
|
||||
containerClassName = '',
|
||||
maxSize = 1000,
|
||||
minSize = 14,
|
||||
fitHeight = false,
|
||||
style = undefined,
|
||||
}) {
|
||||
const containerRef = useRef(null);
|
||||
const textRef = useRef(null);
|
||||
const [fontSize, setFontSize] = useState(maxSize);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
const textEl = textRef.current;
|
||||
if (!container || !textEl) return undefined;
|
||||
|
||||
let animationFrame = null;
|
||||
const fit = () => {
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
if (!width || (fitHeight && !height)) {
|
||||
scheduleFit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Binary search avoids stepping through hundreds of possible font sizes.
|
||||
// Overlay labels test both axes; ordinary rover labels preserve their
|
||||
// previous width-only behavior so this shared move changes no layout.
|
||||
let low = minSize;
|
||||
let high = maxSize;
|
||||
let best = minSize;
|
||||
while (low <= high) {
|
||||
const middle = Math.floor((low + high) / 2);
|
||||
textEl.style.fontSize = `${middle}px`;
|
||||
const fitsWidth = textEl.scrollWidth <= width;
|
||||
const fitsHeight = !fitHeight || textEl.scrollHeight <= height;
|
||||
if (fitsWidth && fitsHeight) {
|
||||
best = middle;
|
||||
low = middle + 1;
|
||||
} else {
|
||||
high = middle - 1;
|
||||
}
|
||||
}
|
||||
setFontSize(best);
|
||||
};
|
||||
|
||||
const scheduleFit = () => {
|
||||
if (animationFrame) cancelAnimationFrame(animationFrame);
|
||||
animationFrame = requestAnimationFrame(fit);
|
||||
};
|
||||
|
||||
scheduleFit();
|
||||
const observer = new ResizeObserver(scheduleFit);
|
||||
observer.observe(container);
|
||||
return () => {
|
||||
if (animationFrame) cancelAnimationFrame(animationFrame);
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [children, fitHeight, maxSize, minSize]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`${fitHeight ? 'flex h-full items-center justify-center' : ''} w-full min-w-0 ${containerClassName}`}
|
||||
>
|
||||
<div
|
||||
ref={textRef}
|
||||
className={`whitespace-nowrap ${className}`}
|
||||
style={{ fontSize: `${fontSize}px`, lineHeight: fitHeight ? 1 : 1.1, ...(style || {}) }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// Scope: Owns row chrome, queue chips, timer labels, and row/button event plumbing;
|
||||
// callers still own target-specific permission checks and request actions.
|
||||
import RoverLabel from '../RoverLabel/index.jsx';
|
||||
import RoverHelpOverlay from '../RoverHelpOverlay/index.jsx';
|
||||
|
||||
function classNames(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
@@ -105,7 +106,7 @@ export default function QueueTargetRow({
|
||||
return (
|
||||
<li
|
||||
className={classNames(
|
||||
'surface flex flex-wrap items-start justify-between gap-0.5',
|
||||
'surface relative flex flex-wrap items-start justify-between gap-0.5 overflow-hidden',
|
||||
canClick && 'cursor-pointer',
|
||||
locked
|
||||
? 'bg-red-900/40'
|
||||
@@ -191,6 +192,9 @@ export default function QueueTargetRow({
|
||||
{buttonLabel}
|
||||
</button>
|
||||
) : null}
|
||||
{/* Queue rows use the exact overlay component as the large video and
|
||||
display surfaces; its measured font automatically adapts to this box. */}
|
||||
<RoverHelpOverlay active={Boolean(target?.needsHelp)} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Rover Help Overlay
|
||||
// Purpose: Gives every rover surface one unmistakable, responsive HELP treatment.
|
||||
// Scope: Renders only the shared visual; server roster state decides when it is active.
|
||||
import AutoFitText from '../AutoFitText/index.jsx';
|
||||
import './styles.css';
|
||||
|
||||
export default function RoverHelpOverlay({ active = false }) {
|
||||
if (!active) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rover-help-overlay pointer-events-none absolute inset-0 z-50 overflow-hidden border-[clamp(0.2rem,1.2cqw,1rem)] border-white bg-red-600 p-[clamp(0.2rem,2cqw,1.5rem)] text-white"
|
||||
role="status"
|
||||
aria-label="Rover needs help"
|
||||
>
|
||||
<AutoFitText
|
||||
fitHeight
|
||||
minSize={8}
|
||||
maxSize={1400}
|
||||
className="font-black tracking-tight text-white drop-shadow-[0_0_0.08em_rgba(0,0,0,1)]"
|
||||
>
|
||||
HELP
|
||||
</AutoFitText>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* A one-second stepped cycle is urgent and room-readable without becoming a
|
||||
rapid strobe. Opacity applies to the entire shared treatment, so its border,
|
||||
background, and auto-fit word flash together on every rover surface. */
|
||||
@keyframes rover-help-flash {
|
||||
0%, 49% { opacity: 0.96; }
|
||||
50%, 100% { opacity: 0.12; }
|
||||
}
|
||||
|
||||
.rover-help-overlay {
|
||||
container-type: size;
|
||||
animation: rover-help-flash 1s steps(1, end) infinite;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import RoverDescriptionOverlay from '../HudOverlays/RoverDescriptionOverlay/inde
|
||||
import OvercurrentOverlay from '../HudOverlays/OvercurrentOverlay/index.jsx';
|
||||
import LowBatteryOverlay from '../HudOverlays/LowBatteryOverlay/index.jsx';
|
||||
import VerticalBatteryOverlay from '../HudOverlays/VerticalBatteryOverlay/index.jsx';
|
||||
import RoverHelpOverlay from '../RoverHelpOverlay/index.jsx';
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
|
||||
export default function SpectateVideo({
|
||||
@@ -11,6 +12,7 @@ export default function SpectateVideo({
|
||||
label,
|
||||
fitParent = false,
|
||||
layoutFormat = 'desktop',
|
||||
needsHelp = false,
|
||||
}) {
|
||||
const isExternalSpectatorSnapshotOnly = useSessionSelector((state) =>
|
||||
state.session?.role === 'spectator' &&
|
||||
@@ -46,6 +48,7 @@ export default function SpectateVideo({
|
||||
<OvercurrentOverlay roverId={roverId} compact={false} />
|
||||
<LowBatteryOverlay roverId={roverId} compact={false} />
|
||||
<VerticalBatteryOverlay show roverId={roverId} mobileHud={false} />
|
||||
<RoverHelpOverlay active={needsHelp} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user