mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 10:00:46 -04:00
it begins
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Auto-fitting single-line text component for mini summary layout.
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function AutoFitText({ children, className = '', maxSize = 1000, minSize = 14, 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 raf = null;
|
||||
const fit = () => {
|
||||
const width = container.clientWidth;
|
||||
if (!width) {
|
||||
scheduleFit();
|
||||
return;
|
||||
}
|
||||
let low = minSize;
|
||||
let high = maxSize;
|
||||
let best = minSize;
|
||||
while (low <= high) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
textEl.style.fontSize = `${mid}px`;
|
||||
const fits = textEl.scrollWidth <= width;
|
||||
if (fits) {
|
||||
best = mid;
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
setFontSize(best);
|
||||
};
|
||||
|
||||
const scheduleFit = () => {
|
||||
if (raf) cancelAnimationFrame(raf);
|
||||
raf = requestAnimationFrame(fit);
|
||||
};
|
||||
|
||||
scheduleFit();
|
||||
const ro = new ResizeObserver(scheduleFit);
|
||||
ro.observe(container);
|
||||
return () => {
|
||||
if (raf) cancelAnimationFrame(raf);
|
||||
ro.disconnect();
|
||||
};
|
||||
}, [children, maxSize, minSize]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full min-w-0">
|
||||
<div
|
||||
ref={textRef}
|
||||
className={`whitespace-nowrap ${className}`}
|
||||
style={{ fontSize: `${fontSize}px`, lineHeight: 1.1, ...(style || {}) }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 4:3 viewport fitting frame for active mini-summary video.
|
||||
export default function FitViewportFrame({ children }) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-start overflow-hidden bg-black">
|
||||
<div
|
||||
className="relative flex items-center justify-center overflow-hidden bg-black"
|
||||
style={{
|
||||
width: 'min(100%, calc(100vh * 4 / 3))',
|
||||
height: 'min(100%, calc(100vw * 3 / 4))',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
aspectRatio: '4 / 3',
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full w-full items-center justify-center overflow-hidden">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Rover information and optional preview column for mini summary UI.
|
||||
import VideoTile from '../../../components/VideoTile.jsx';
|
||||
import BatteryBar from '../../../components/BatteryBar.jsx';
|
||||
import { roverNameChromeStyle } from '../../../lib/roverColor.js';
|
||||
import { getBatteryVisual } from '../utils.js';
|
||||
import AutoFitText from './AutoFitText.jsx';
|
||||
|
||||
export default function InfoColumn({
|
||||
rover,
|
||||
frame,
|
||||
driverLabel,
|
||||
sessionInfo,
|
||||
videoMode = 'snapshot',
|
||||
snapshotFeed,
|
||||
withDivider = false,
|
||||
showPreview = true,
|
||||
variant = 'stacked',
|
||||
}) {
|
||||
const batteryVisual = getBatteryVisual({ rover, frame });
|
||||
const batteryPercent = batteryVisual?.available ? batteryVisual.percentDisplay : null;
|
||||
const isActiveView = variant === 'active';
|
||||
return (
|
||||
<div
|
||||
className={`relative flex min-w-0 flex-1 flex-col gap-4 overflow-hidden bg-black px-0 py-0 ${
|
||||
withDivider ? 'border-r border-slate-700/60' : ''
|
||||
}`}
|
||||
>
|
||||
<BatteryBar
|
||||
visual={batteryVisual}
|
||||
orientation={isActiveView ? 'vertical' : 'horizontal'}
|
||||
variant="background"
|
||||
/>
|
||||
{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]"
|
||||
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">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={1000} minSize={16}>
|
||||
{driverLabel}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="relative min-w-0 overflow-hidden bg-transparent px-0 py-0 leading-none">
|
||||
<div className="relative">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={1000} minSize={16}>
|
||||
{batteryPercent == null ? '--%' : `${batteryPercent}%`}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<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]"
|
||||
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">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={1000} minSize={16}>
|
||||
{driverLabel}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="relative min-w-0 overflow-hidden bg-transparent px-0 py-0 leading-none">
|
||||
<div className="relative">
|
||||
<AutoFitText className="font-semibold leading-none text-white" maxSize={80} minSize={16}>
|
||||
{batteryPercent == null ? '--%' : `${batteryPercent}%`}
|
||||
</AutoFitText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex min-w-0 flex-1 flex-col gap-4">
|
||||
<div className="flex w-full min-w-0 flex-1 flex-col items-center gap-4">
|
||||
{showPreview ? (
|
||||
<div className="mt-auto w-full">
|
||||
<div className="w-full aspect-[4/3]">
|
||||
<VideoTile
|
||||
sessionInfo={sessionInfo}
|
||||
videoMode={videoMode}
|
||||
snapshotFeed={snapshotFeed}
|
||||
audioSessionInfo={null}
|
||||
label={rover.name || rover.id}
|
||||
roverColor={rover.color || null}
|
||||
telemetryFrame={frame}
|
||||
batteryConfig={rover.battery}
|
||||
layoutFormat="mobile"
|
||||
hudVariant="none"
|
||||
fitParent
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user