fullscreen prompt cookie saving, and more...

This commit is contained in:
legop3
2026-05-19 21:52:03 -04:00
parent 3a2c1bee35
commit 064aaee76c
11 changed files with 183 additions and 186 deletions
+4 -10
View File
@@ -1,14 +1,8 @@
1. add faster way for admins to login, like an invisible button in top left or something 1. add faster way for admins to login, like an invisible button in top left or something
2. make camera card not wait for ack before sending next command. speed is inconsistent and this is bad. 2. add discord bot typing thing for when someone requests a replay
3. add tool call embeds or something for the llm bot in discord, probably not in web ui 3. change replay title for ones requested from discord, something other than "requester driving rover"
1. fix overseer prompt so it doesnt talk every run 4. fix rover request spam queue cheat
2. rename neato locate to neato playsound for overseer
3. you can't control the rovers
4. add discord bot typing thing for when someone requests a replay
5. change replay title for ones requested from discord, something other than "requester driving rover"
6. fix rover request spam queue cheat
7. make replays more instant, probably make segments shorter
1. fix replay UI so it doesnt save anything in cookie
# relative pipe dreams: # relative pipe dreams:
1. VPS video forwarding 1. VPS video forwarding
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
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<title>Roomba Rover</title> <title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-BWZIF1Sp.js"></script> <script type="module" crossorigin src="/assets/index-DHhBFQ9m.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-jwQkh99Y.css"> <link rel="stylesheet" crossorigin href="/assets/index-Dd7xX8GI.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
@@ -114,7 +114,7 @@ function buildVoteStatus() {
else noCount += 1; else noCount += 1;
}); });
const onlineCount = yesCount + noCount; const onlineCount = yesCount + noCount;
const gatePassed = yesCount > noCount; const gatePassed = eligibleCount === 0 ? true : yesCount > noCount;
return { return {
yesCount, yesCount,
noCount, noCount,
+15 -1
View File
@@ -22,6 +22,9 @@ import ModeGateOverlay from './components/ModeGateOverlay/index.jsx';
import HomeAssistantControls from './components/HomeAssistantControls/index.jsx'; import HomeAssistantControls from './components/HomeAssistantControls/index.jsx';
import TurnAlertListener from './components/TurnAlertListener/index.jsx'; import TurnAlertListener from './components/TurnAlertListener/index.jsx';
import RawUserPilePanel from './components/RawUserPilePanel/index.jsx'; import RawUserPilePanel from './components/RawUserPilePanel/index.jsx';
import OverseerPreferencePanel from './components/OverseerPreferencePanel/index.jsx';
import NicknameForm from './components/NicknameForm/index.jsx';
import SocialButtonsGrid from './components/SocialButtonsGrid/index.jsx';
import ChatPanel from './components/ChatPanel/index.jsx'; import ChatPanel from './components/ChatPanel/index.jsx';
import FullscreenPrompt from './components/FullscreenPrompt/index.jsx'; import FullscreenPrompt from './components/FullscreenPrompt/index.jsx';
import FloatingFullscreenButton from './components/FloatingFullscreenButton/index.jsx'; import FloatingFullscreenButton from './components/FloatingFullscreenButton/index.jsx';
@@ -142,7 +145,18 @@ function MobileFeatureTabs({
<TabPanel id="chat"> <TabPanel id="chat">
<div className="space-y-0.5"> <div className="space-y-0.5">
<ChatPanel /> <ChatPanel />
<RawUserPilePanel /> <div className="space-y-0.5">
<div className="grid gap-0.5 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
<div className="surface flex w-full items-center px-0 py-0">
<NicknameForm />
</div>
<SocialButtonsGrid />
</div>
<div className="grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
<OverseerPreferencePanel />
<RawUserPilePanel hideNicknameForm />
</div>
</div>
</div> </div>
</TabPanel> </TabPanel>
<TabPanel id="vip" keepMounted> <TabPanel id="vip" keepMounted>
@@ -22,12 +22,14 @@ export default function OverseerPreferencePanel() {
return ( return (
<section className="surface p-1 text-xs text-slate-200 text-center"> <section className="surface p-1 text-xs text-slate-200 text-center">
<label className="flex items-center justify-center gap-1.5"> <label className="flex items-center justify-center gap-1.5 bg-gray-700 surface">
<input type="checkbox" checked={enabled} onChange={onToggle} /> <input type="checkbox" checked={enabled} onChange={onToggle} />
<span>Enable Overseer LLM</span> <span>Enable Overseer LLM</span>
</label> </label>
<span className='text-xs'>Cast your to enable Overseer in chat.</span>
<div className="mt-0.5 text-slate-400"> <div className="mt-0.5 text-slate-400">
Yes?: {Number(vote?.yesCount || 0)}, No!: {Number(vote?.noCount || 0)} Yes!: {Number(vote?.yesCount || 0)}, No...: {Number(vote?.noCount || 0)}
</div> </div>
<div className="mt-0.5 flex justify-center"> <div className="mt-0.5 flex justify-center">
<span <span
+22 -36
View File
@@ -1,39 +1,10 @@
// Hook: useFullscreenPrompt // Hook: useFullscreenPrompt
// Purpose: Manages fullscreen prompt visibility and dismissal logic across screen sizes/devices. Scope: Provides reusable fullscreen UX state and action handlers. // Purpose: Manages fullscreen prompt visibility and dismissal logic across screen sizes/devices. Scope: Provides reusable fullscreen UX state and action handlers.
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSettingsNamespace } from '../settings/index.js';
const SESSION_KEY = 'fullscreenPromptDismissed';
const MOBILE_LAYOUTS = new Set(['mobile-portrait', 'mobile-landscape']); const MOBILE_LAYOUTS = new Set(['mobile-portrait', 'mobile-landscape']);
const getStorage = () => {
if (typeof window === 'undefined') return null;
try {
return window.sessionStorage;
} catch {
return null;
}
};
const isDismissed = () => {
const storage = getStorage();
if (!storage) return false;
try {
return storage.getItem(SESSION_KEY) === '1';
} catch {
return false;
}
};
const markDismissed = () => {
const storage = getStorage();
if (!storage) return;
try {
storage.setItem(SESSION_KEY, '1');
} catch {
/* ignore */
}
};
const detectIOS = () => { const detectIOS = () => {
if (typeof navigator === 'undefined') return false; if (typeof navigator === 'undefined') return false;
const ua = navigator.userAgent || navigator.platform || ''; const ua = navigator.userAgent || navigator.platform || '';
@@ -52,20 +23,35 @@ const supportsFullscreen = () => {
}; };
export function useFullscreenPrompt(layout) { export function useFullscreenPrompt(layout) {
const { value: pageSettings, save: savePageSettings, status: settingsStatus } = useSettingsNamespace('page', {
fullscreenPromptShown: false,
});
const isMobileLayout = MOBILE_LAYOUTS.has(layout); const isMobileLayout = MOBILE_LAYOUTS.has(layout);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [mode, setMode] = useState('native'); const [mode, setMode] = useState('native');
const [forceVisible, setForceVisible] = useState(false); const [forceVisible, setForceVisible] = useState(false);
const hasShownPrompt = Boolean(pageSettings?.fullscreenPromptShown);
const isIOS = useMemo(() => detectIOS(), []); const isIOS = useMemo(() => detectIOS(), []);
const nativeSupported = useMemo(() => supportsFullscreen(), []); const nativeSupported = useMemo(() => supportsFullscreen(), []);
const markShown = useCallback(() => {
if (settingsStatus !== 'ready') return;
savePageSettings((current) => {
if (current?.fullscreenPromptShown) return current;
return { ...(current || {}), fullscreenPromptShown: true };
});
}, [savePageSettings, settingsStatus]);
const reevaluate = useCallback(() => { const reevaluate = useCallback(() => {
if (settingsStatus !== 'ready') {
setVisible(false);
return;
}
if (!isMobileLayout) { if (!isMobileLayout) {
setVisible(false); setVisible(false);
setForceVisible(false); setForceVisible(false);
return; return;
} }
if (!forceVisible && isDismissed()) { if (!forceVisible && hasShownPrompt) {
setVisible(false); setVisible(false);
return; return;
} }
@@ -84,7 +70,7 @@ export function useFullscreenPrompt(layout) {
} }
setMode('native'); setMode('native');
setVisible(true); setVisible(true);
}, [forceVisible, isIOS, isMobileLayout]); }, [forceVisible, hasShownPrompt, isIOS, isMobileLayout, settingsStatus]);
useEffect(() => { useEffect(() => {
reevaluate(); reevaluate();
@@ -109,10 +95,10 @@ export function useFullscreenPrompt(layout) {
}, [reevaluate]); }, [reevaluate]);
const dismiss = useCallback(() => { const dismiss = useCallback(() => {
markDismissed(); markShown();
setForceVisible(false); setForceVisible(false);
setVisible(false); setVisible(false);
}, []); }, [markShown]);
const enterFullscreen = useCallback(async () => { const enterFullscreen = useCallback(async () => {
if (!isMobileLayout || isIOS) return false; if (!isMobileLayout || isIOS) return false;
@@ -131,7 +117,7 @@ export function useFullscreenPrompt(layout) {
if (result && typeof result.then === 'function') { if (result && typeof result.then === 'function') {
await result; await result;
} }
markDismissed(); markShown();
setForceVisible(false); setForceVisible(false);
setVisible(false); setVisible(false);
return true; return true;
@@ -139,7 +125,7 @@ export function useFullscreenPrompt(layout) {
console.warn('Failed to enter fullscreen', error); console.warn('Failed to enter fullscreen', error);
return false; return false;
} }
}, [isIOS, isMobileLayout]); }, [isIOS, isMobileLayout, markShown]);
const showPrompt = useCallback(() => { const showPrompt = useCallback(() => {
if (!isMobileLayout) return; if (!isMobileLayout) return;
+3 -2
View File
@@ -30,9 +30,10 @@ export function SettingsProvider({ children }) {
const setNamespace = useCallback((namespace, updater) => { const setNamespace = useCallback((namespace, updater) => {
setState((prev) => { setState((prev) => {
const current = prev.data[namespace] || {}; const baseData = prev.status === 'ready' ? prev.data : loadSettings() ?? {};
const current = baseData[namespace] || {};
const nextValue = typeof updater === 'function' ? updater(current) : { ...current, ...(updater ?? {}) }; const nextValue = typeof updater === 'function' ? updater(current) : { ...current, ...(updater ?? {}) };
const nextData = { ...prev.data, [namespace]: nextValue }; const nextData = { ...baseData, [namespace]: nextValue };
const success = saveSettings(nextData); const success = saveSettings(nextData);
if (!success) { if (!success) {
return { ...prev, status: 'error' }; return { ...prev, status: 'error' };