mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
ui changes tweaks and flings. new quickstart overlay to replace help overlay.
This commit is contained in:
+29
-9
@@ -28,6 +28,7 @@ import FloatingFullscreenButton from './components/FloatingFullscreenButton/inde
|
||||
import { useFullscreenPrompt } from './hooks/useFullscreenPrompt.js';
|
||||
import { useSettingsNamespace } from './settings/index.js';
|
||||
import HelpOverlay from './components/HelpOverlay/index.jsx';
|
||||
import QuickstartOverlay from './components/QuickstartOverlay/index.jsx';
|
||||
import HelpPanel from './components/HelpPanel/index.jsx';
|
||||
import SettingsPanel from './components/SettingsPanel/index.jsx';
|
||||
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './components/Tabs/index.jsx';
|
||||
@@ -250,9 +251,13 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
|
||||
const {
|
||||
value: helpSettings,
|
||||
status: helpStatus,
|
||||
save: saveHelpSettings,
|
||||
} = useSettingsNamespace('help', { showOnLoad: true });
|
||||
const {
|
||||
value: quickstartSettings,
|
||||
status: quickstartStatus,
|
||||
save: saveQuickstartSettings,
|
||||
} = useSettingsNamespace('quickstart', { showOnLoad: true });
|
||||
const { value: pageSettings } = useSettingsNamespace('page', {
|
||||
swapMobileControlColumns: false,
|
||||
});
|
||||
@@ -260,15 +265,17 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
const fullscreenButtonSide = swapMobileControlColumns ? 'left' : 'right';
|
||||
const showFloatingFullscreenButton = !isDesktop && (fullscreenIsIOS || fullscreenNativeSupported);
|
||||
const [helpVisible, setHelpVisible] = useState(false);
|
||||
const [quickstartVisible, setQuickstartVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (helpStatus === 'ready') {
|
||||
setHelpVisible(helpSettings?.showOnLoad !== false);
|
||||
if (quickstartStatus === 'ready') {
|
||||
setQuickstartVisible(quickstartSettings?.showOnLoad !== false);
|
||||
}
|
||||
}, [helpStatus, helpSettings?.showOnLoad]);
|
||||
}, [quickstartStatus, quickstartSettings?.showOnLoad]);
|
||||
|
||||
const openHelp = useCallback(() => setHelpVisible(true), []);
|
||||
const closeHelp = useCallback(() => setHelpVisible(false), []);
|
||||
const closeQuickstart = useCallback(() => setQuickstartVisible(false), []);
|
||||
const handleFloatingFullscreen = useCallback(async () => {
|
||||
if (fullscreenIsIOS) {
|
||||
showPrompt();
|
||||
@@ -279,16 +286,20 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
showPrompt();
|
||||
}
|
||||
}, [enterFullscreen, fullscreenIsIOS, showPrompt]);
|
||||
const setShowOnLoad = useCallback(
|
||||
const setQuickstartShowOnLoad = useCallback(
|
||||
(enabled) => {
|
||||
const next = Boolean(enabled);
|
||||
saveHelpSettings((current) => ({ ...(current ?? {}), showOnLoad: next }));
|
||||
saveQuickstartSettings((current) => ({ ...(current ?? {}), showOnLoad: next }));
|
||||
if (!next) {
|
||||
setHelpVisible(false);
|
||||
setQuickstartVisible(false);
|
||||
}
|
||||
},
|
||||
[saveHelpSettings],
|
||||
[saveQuickstartSettings],
|
||||
);
|
||||
const openHelpFromQuickstart = useCallback(() => {
|
||||
setQuickstartVisible(false);
|
||||
setHelpVisible(true);
|
||||
}, []);
|
||||
|
||||
const renderedLayout = useMemo(
|
||||
() =>
|
||||
@@ -312,12 +323,13 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
<RewardRunOverlay />
|
||||
<TurnAlertListener />
|
||||
<ModeGateOverlay />
|
||||
|
||||
<HelpOverlay
|
||||
visible={helpVisible}
|
||||
layout={layout}
|
||||
onClose={closeHelp}
|
||||
showOnLoad={helpSettings?.showOnLoad !== false}
|
||||
onToggleShowOnLoad={setShowOnLoad}
|
||||
onToggleShowOnLoad={(enabled) => saveHelpSettings((current) => ({ ...(current ?? {}), showOnLoad: Boolean(enabled) }))}
|
||||
/>
|
||||
<FullscreenPrompt
|
||||
visible={fullscreenVisible}
|
||||
@@ -325,6 +337,14 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
onEnterFullscreen={enterFullscreen}
|
||||
onDismiss={dismiss}
|
||||
/>
|
||||
<QuickstartOverlay
|
||||
visible={quickstartVisible}
|
||||
layout={layout}
|
||||
showOnLoad={quickstartSettings?.showOnLoad !== false}
|
||||
onToggleShowOnLoad={setQuickstartShowOnLoad}
|
||||
onOpenHelp={openHelpFromQuickstart}
|
||||
onClose={closeQuickstart}
|
||||
/>
|
||||
{showFloatingFullscreenButton ? (
|
||||
<FloatingFullscreenButton
|
||||
side={fullscreenButtonSide}
|
||||
|
||||
@@ -6,14 +6,14 @@ export default function FullscreenPrompt({ visible, mode, onEnterFullscreen, onD
|
||||
const isIOSMode = mode === 'pwa-hint';
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-40 flex items-end justify-center p-2 pointer-events-none sm:items-center">
|
||||
<div className="pointer-events-auto w-full max-w-sm rounded-lg border border-cyan-500/40 bg-zinc-950/95 shadow-xl">
|
||||
<div className="space-y-0.5 p-4 text-sm text-slate-100">
|
||||
<h2 className="text-base font-semibold text-white">Better in fullscreen</h2>
|
||||
<div className="fixed inset-0 z-40 flex items-center justify-center p-1 pointer-events-none bg-black/20">
|
||||
<div className="pointer-events-auto w-full max-w-sm surface center">
|
||||
<div className="space-y-0.5 p-1 text-sm text-slate-100">
|
||||
<h2 className="text-base font-semibold text-white border-b border-slate-700">Better in fullscreen!</h2>
|
||||
{isIOSMode ? (
|
||||
<p className="text-slate-300">
|
||||
For fullscreen on iOS, open Safari's share menu and pick <strong>Add to Home Screen</strong>. Launching from
|
||||
the home screen removes the browser chrome.
|
||||
For fullscreen on iOS, open Safari's share menu and pick <strong>Add to Home Screen</strong>. Launching from
|
||||
the home screen then makes it fullscreen.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-slate-300">
|
||||
@@ -21,6 +21,8 @@ export default function FullscreenPrompt({ visible, mode, onEnterFullscreen, onD
|
||||
via the system back or home gesture.
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs border-t border-b border-slate-700 p-0.5 text-blue-300 text-center">This will only show once just to let you know. There is a fullscreen button in the bottom right for later use.</p>
|
||||
|
||||
<div className="flex justify-end gap-0.5 pt-1 text-sm">
|
||||
<button type="button" className="rounded border border-slate-600 px-3 py-1 text-slate-200" onClick={onDismiss}>
|
||||
{isIOSMode ? 'Got it' : 'Not now'}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function ModeGateOverlay() {
|
||||
const details = getModeDetails(mode);
|
||||
|
||||
return (
|
||||
<div className="pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black/85 px-0.5 py-0.5">
|
||||
<div className="pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black px-0.5 py-0.5">
|
||||
<div className="surface w-full max-w-md space-y-0.5 text-slate-100 shadow-2xl">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-lg font-semibold">{details.title}</p>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useControlSystem } from '../../controls/index.js';
|
||||
import { formatKeyLabel } from '../../controls/keymapUtils.js';
|
||||
import NicknameForm from '../NicknameForm/index.jsx';
|
||||
import SocialButton from '../SocialButton/index.jsx';
|
||||
import KeyPill from '../vip/VipAudioUploadCard/KeyPill.jsx';
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
|
||||
function ControlRow({ label, keyLabel }) {
|
||||
return (
|
||||
<div className="surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.35 text-[0.8rem] text-slate-200">
|
||||
<span>{label}</span>
|
||||
<KeyPill label={keyLabel} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DesktopQuickstart({ keymap }) {
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200">1. Press "Start Driving" put your rover into driving mode.</p>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200">2. Use the drive controls to move your rover:</p>
|
||||
<div className="space-y-0.5">
|
||||
<ControlRow label="Forward" keyLabel={formatKeyLabel(keymap?.driveForward?.[0])} />
|
||||
<ControlRow label="Backward" keyLabel={formatKeyLabel(keymap?.driveBackward?.[0])} />
|
||||
<ControlRow label="Turn Left" keyLabel={formatKeyLabel(keymap?.driveLeft?.[0])} />
|
||||
<ControlRow label="Turn Right" keyLabel={formatKeyLabel(keymap?.driveRight?.[0])} />
|
||||
<ControlRow label="Move faster" keyLabel={formatKeyLabel(keymap?.boostModifier?.[0])} />
|
||||
<ControlRow label="Move slower" keyLabel={formatKeyLabel(keymap?.slowModifier?.[0])} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-slate-200">3. When done, please dock your rover! Line up with the dock and press "Dock and Charge".</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileQuickstart() {
|
||||
return (
|
||||
<div className="space-y-0.5 text-sm text-slate-200">
|
||||
<p>1. Press "Start Driving" put your rover into driving mode.</p>
|
||||
<p>2. Touch and hold in Joystick area to move.</p>
|
||||
<p>3. Use the other column for motor, horn, and camera controls.</p>
|
||||
<p>4. When done, please dock your rover! Line up with the dock and press "Dock and Charge".</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function QuickstartOverlay({
|
||||
visible,
|
||||
layout,
|
||||
showOnLoad,
|
||||
onToggleShowOnLoad,
|
||||
onOpenHelp,
|
||||
onClose,
|
||||
}) {
|
||||
const { state } = useControlSystem();
|
||||
const isDesktop = layout === 'desktop';
|
||||
const discordUrl = useSessionSelector((sessionState) => {
|
||||
const socials = sessionState.session?.socials || [];
|
||||
const entry = socials.find((item) => {
|
||||
const key = String(item?.id || item?.label || '').toLowerCase();
|
||||
return key === 'discord';
|
||||
});
|
||||
return entry?.url || sessionState.session?.discord?.invite || null;
|
||||
});
|
||||
|
||||
const keymap = useMemo(() => state?.keymap || {}, [state?.keymap]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
const handleCheckbox = (event) => {
|
||||
const keepShowing = !event.target.checked;
|
||||
onToggleShowOnLoad?.(keepShowing);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-40 flex items-end justify-center bg-black/75 p-0.5 items-center">
|
||||
<div className="pointer-events-auto surface w-full max-w-3xl overflow-hidden shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-slate-700 px-0.5 py-0.35 text-sm text-slate-200">
|
||||
<span className="font-semibold text-xl">Welcome! To get started:</span>
|
||||
{/* <button type="button" onClick={onClose} className="button-dark px-1 py-0.25 text-[0.8rem]">
|
||||
Close
|
||||
</button> */}
|
||||
</div>
|
||||
<div className={`grid gap-0.5 p-0.5 ${isDesktop ? 'md:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)]' : 'grid-cols-1'}`}>
|
||||
<section className="space-y-0.5 border-b border-slate-700">
|
||||
{isDesktop ? <DesktopQuickstart keymap={keymap} /> : <MobileQuickstart />}
|
||||
</section>
|
||||
{/* {!isDesktop? <div className='w-full h-1 bg-blue-500'></div> : null} */}
|
||||
<section className="space-y-0.5">
|
||||
<p className='text-left'>Next...</p>
|
||||
<div className="surface space-y-0.5 p-0.5 border-b border-slate-700">
|
||||
<p className="text-xl font-semibold text-slate-200">Set your nickname</p>
|
||||
<p className="text-sm font-semibold text-slate-200">Nicknames are assigned randomly by default, you can change yours here.</p>
|
||||
<NicknameForm compact />
|
||||
</div>
|
||||
<div className="surface p-0.5">
|
||||
<p className="text-xl font-semibold text-slate-200">Join our Discord server!</p>
|
||||
<p className="text-sm font-semibold text-slate-200">We have an active and welcoming community :3</p>
|
||||
<SocialButton id="discord" label="Join Discord" url={discordUrl} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-0.5 border-t border-slate-700 px-0.5 py-0.35 text-[0.8rem]">
|
||||
<label className="flex items-center gap-0.5 text-slate-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!showOnLoad}
|
||||
onChange={handleCheckbox}
|
||||
className="accent-cyan-500"
|
||||
/>
|
||||
<span>Don't show again</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{/* <button type="button" onClick={onOpenHelp} className="button-dark px-1 py-0.25">
|
||||
Open full Help
|
||||
</button> */}
|
||||
<button type="button" onClick={onClose} className="button-dark px-1 py-0.25 text-2xl bg-green-600 hover:bg-green-400 hover:border-green-100 border-green-400">
|
||||
Got it! Let me in!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -72,10 +72,10 @@ export default function VipLiftCard({ lift, onUp, onDown, fullWidth = false }) {
|
||||
<div className="absolute inset-0 z-20 flex items-center justify-center rounded-md bg-slate-950/80 px-1.5 text-center">
|
||||
<div className="space-y-0.25">
|
||||
<p className="text-sm font-semibold text-slate-100">
|
||||
{busy ? 'Motion in progress' : 'Motion cooldown active'}
|
||||
{busy ? 'Preparing to move' : 'Moving!'}
|
||||
</p>
|
||||
<p className="text-xs text-slate-300">
|
||||
Controls are disabled while the lift is moving, otherwise it's tiny brain would get confused.
|
||||
Controls are disabled while the lift is moving, otherwise its tiny brain would get confused.
|
||||
</p>
|
||||
{!busy && cooldownActive ? (
|
||||
<p className="text-xs text-slate-400">About {cooldownSeconds}s remaining.</p>
|
||||
|
||||
Reference in New Issue
Block a user