mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
spectator 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
@@ -11,8 +11,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||
<title>Multi Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-Sryc1K8v.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CwxAi21V.css">
|
||||
<script type="module" crossorigin src="/assets/index-C2Pt63lU.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DsiEQJXa.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -5,7 +5,7 @@ const MOBILE_DISMISS_MS = 10000;
|
||||
const MAX_FONT_PX = 28;
|
||||
const MIN_FONT_PX = 14;
|
||||
|
||||
export default function CommunityGoalBanner({ layout = 'desktop', className = '' }) {
|
||||
export default function CommunityGoalBanner({ layout = 'desktop', className = '', dismissable = true }) {
|
||||
const { session } = useSession();
|
||||
const goalText = session?.communityGoal?.text ? String(session.communityGoal.text).trim() : '';
|
||||
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape' || layout === 'mobile';
|
||||
@@ -22,7 +22,7 @@ export default function CommunityGoalBanner({ layout = 'desktop', className = ''
|
||||
return undefined;
|
||||
}
|
||||
setVisible(true);
|
||||
if (!isMobile) return undefined;
|
||||
if (!isMobile || !dismissable) return undefined;
|
||||
const startedAt = Date.now();
|
||||
setRemainingMs(MOBILE_DISMISS_MS);
|
||||
const timer = setTimeout(() => setVisible(false), MOBILE_DISMISS_MS);
|
||||
@@ -81,14 +81,16 @@ export default function CommunityGoalBanner({ layout = 'desktop', className = ''
|
||||
|
||||
const remainingSeconds = isMobile ? Math.ceil(remainingMs / 1000) : null;
|
||||
|
||||
const dismissProps = dismissable
|
||||
? { onClick: () => setVisible(false), role: 'button', tabIndex: 0 }
|
||||
: {};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={containerClass}
|
||||
style={{ fontSize: `${fontSize}px`, lineHeight: 1.1 }}
|
||||
onClick={() => setVisible(false)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
{...dismissProps}
|
||||
>
|
||||
<span className="flex w-full items-stretch gap-0.5 whitespace-nowrap rounded-md">
|
||||
<span className="flex flex-col justify-center border-r border-slate-700/60 px-0.5 text-[0.55em] font-semibold leading-tight text-slate-400">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SettingsProvider } from '../settings/index.js';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSpectatorMode } from '../hooks/useSpectatorMode.js';
|
||||
@@ -14,6 +15,33 @@ import CommunityGoalBanner from '../components/CommunityGoalBanner.jsx';
|
||||
import RoverQueuesPanel from '../components/RoverQueuesPanel.jsx';
|
||||
import RawUserPilePanel from '../components/RawUserPilePanel.jsx';
|
||||
|
||||
function usePortraitLayout() {
|
||||
const [isPortrait, setIsPortrait] = useState(() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window.matchMedia('(max-aspect-ratio: 4/3)').matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return undefined;
|
||||
const media = window.matchMedia('(max-aspect-ratio: 4/3)');
|
||||
const handleChange = (event) => setIsPortrait(event.matches);
|
||||
if (media.addEventListener) {
|
||||
media.addEventListener('change', handleChange);
|
||||
} else {
|
||||
media.addListener(handleChange);
|
||||
}
|
||||
return () => {
|
||||
if (media.removeEventListener) {
|
||||
media.removeEventListener('change', handleChange);
|
||||
} else {
|
||||
media.removeListener(handleChange);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isPortrait;
|
||||
}
|
||||
|
||||
function formatDriverLabel({ roverId, session }) {
|
||||
const activeDriverId = session?.activeDrivers?.[roverId] || null;
|
||||
const user = (session?.users || []).find((entry) => entry.socketId === activeDriverId);
|
||||
@@ -98,6 +126,7 @@ function SpectatorContent() {
|
||||
const inLockdown = session?.mode === 'lockdown';
|
||||
useDefaultNickname();
|
||||
useSpectatorMode();
|
||||
const isPortraitLayout = usePortraitLayout();
|
||||
const frames = useTelemetryFrames();
|
||||
const roster = session?.roster ?? [];
|
||||
const snapshotFeeds = useRoverSnapshots(
|
||||
@@ -122,10 +151,46 @@ function SpectatorContent() {
|
||||
);
|
||||
}
|
||||
|
||||
const mainClass = isPortraitLayout
|
||||
? 'flex min-h-screen flex-col bg-black text-slate-100 md:h-screen md:overflow-hidden'
|
||||
: 'grid min-h-screen grid-cols-1 gap-0.5 bg-black text-slate-100 md:h-full md:min-h-0 md:grid-cols-[minmax(0,1fr)_18rem] lg:grid-cols-[minmax(0,1fr)_20rem]';
|
||||
const contentClass = isPortraitLayout
|
||||
? 'order-2 flex min-h-0 min-w-0 flex-1 flex-col gap-0.5 overflow-y-auto'
|
||||
: 'order-1 flex min-h-0 min-w-0 flex-col gap-0.5 md:overflow-y-auto';
|
||||
const sidebarClass = isPortraitLayout
|
||||
? 'order-1 flex min-h-0 w-full items-stretch gap-0.5 overflow-x-auto border-b border-slate-800/60 bg-slate-950/90 p-0.5'
|
||||
: 'order-2 flex min-h-0 min-w-0 flex-col gap-0.5 border-l border-slate-800/60 bg-slate-950/90 md:h-full md:overflow-y-auto';
|
||||
const topBarItemClass = isPortraitLayout ? 'min-w-[16rem] max-w-[22rem] flex-1' : '';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-slate-100 md:h-screen md:overflow-hidden">
|
||||
<main className="grid min-h-screen grid-cols-1 gap-0.5 p-0 md:h-full md:min-h-0 md:grid-cols-[minmax(0,1fr)_18rem] lg:grid-cols-[minmax(0,1fr)_20rem]">
|
||||
<section className="flex min-h-0 min-w-0 flex-col gap-0.5 md:overflow-y-auto">
|
||||
<main className={mainClass}>
|
||||
<section className={sidebarClass}>
|
||||
<div className={topBarItemClass}>
|
||||
<CommunityGoalBanner layout="desktop" dismissable={false} className="text-sm" />
|
||||
</div>
|
||||
<div className={`${topBarItemClass} ${isPortraitLayout ? 'h-32 overflow-y-auto' : ''}`}>
|
||||
<div className="panel h-full">
|
||||
<RoverQueuesPanel title="Rovers" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${topBarItemClass} ${isPortraitLayout ? 'h-32' : 'flex-1 min-h-0'}`}>
|
||||
<RawUserPilePanel
|
||||
hideNicknameForm
|
||||
hideHeader
|
||||
compact
|
||||
fillHeight
|
||||
className="h-full"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${topBarItemClass} ${isPortraitLayout ? 'h-32' : 'flex-[1.1] min-h-0'}`}>
|
||||
<ChatPanel hideInput hideSpectatorNotice fillHeight />
|
||||
</div>
|
||||
<div className={`${topBarItemClass} ${isPortraitLayout ? 'h-32' : ''}`}>
|
||||
<LogsRow className={`${isPortraitLayout ? 'h-full' : 'h-40'} overflow-hidden`} />
|
||||
</div>
|
||||
</section>
|
||||
<section className={contentClass}>
|
||||
<RoverRow
|
||||
roster={roster}
|
||||
frames={frames}
|
||||
@@ -135,21 +200,6 @@ function SpectatorContent() {
|
||||
/>
|
||||
<SecondaryRow />
|
||||
</section>
|
||||
<section className="flex min-h-0 min-w-0 flex-col gap-0.5 md:h-full">
|
||||
<CommunityGoalBanner layout="desktop" />
|
||||
<div className="panel">
|
||||
<RoverQueuesPanel title="Rovers" />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
||||
<RawUserPilePanel hideNicknameForm hideHeader fillHeight className="h-full" />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0 flex-[1.1] overflow-hidden">
|
||||
<ChatPanel hideInput hideSpectatorNotice fillHeight />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0">
|
||||
<LogsRow className="h-40 overflow-hidden" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<AlertFeed />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user