it begins

This commit is contained in:
legop3
2026-04-28 14:02:14 -04:00
parent 9b59718a37
commit 13e9ebc909
11 changed files with 536 additions and 501 deletions
@@ -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>
);
}