mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
fullscreen prompt cookie saving, and more...
This commit is contained in:
+15
-1
@@ -22,6 +22,9 @@ import ModeGateOverlay from './components/ModeGateOverlay/index.jsx';
|
||||
import HomeAssistantControls from './components/HomeAssistantControls/index.jsx';
|
||||
import TurnAlertListener from './components/TurnAlertListener/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 FullscreenPrompt from './components/FullscreenPrompt/index.jsx';
|
||||
import FloatingFullscreenButton from './components/FloatingFullscreenButton/index.jsx';
|
||||
@@ -142,7 +145,18 @@ function MobileFeatureTabs({
|
||||
<TabPanel id="chat">
|
||||
<div className="space-y-0.5">
|
||||
<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>
|
||||
</TabPanel>
|
||||
<TabPanel id="vip" keepMounted>
|
||||
|
||||
@@ -22,12 +22,14 @@ export default function OverseerPreferencePanel() {
|
||||
|
||||
return (
|
||||
<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} />
|
||||
<span>Enable Overseer LLM</span>
|
||||
</label>
|
||||
<span className='text-xs'>Cast your to enable Overseer in chat.</span>
|
||||
|
||||
<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 className="mt-0.5 flex justify-center">
|
||||
<span
|
||||
|
||||
@@ -1,39 +1,10 @@
|
||||
// Hook: useFullscreenPrompt
|
||||
// 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 { useSettingsNamespace } from '../settings/index.js';
|
||||
|
||||
const SESSION_KEY = 'fullscreenPromptDismissed';
|
||||
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 = () => {
|
||||
if (typeof navigator === 'undefined') return false;
|
||||
const ua = navigator.userAgent || navigator.platform || '';
|
||||
@@ -52,20 +23,35 @@ const supportsFullscreen = () => {
|
||||
};
|
||||
|
||||
export function useFullscreenPrompt(layout) {
|
||||
const { value: pageSettings, save: savePageSettings, status: settingsStatus } = useSettingsNamespace('page', {
|
||||
fullscreenPromptShown: false,
|
||||
});
|
||||
const isMobileLayout = MOBILE_LAYOUTS.has(layout);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [mode, setMode] = useState('native');
|
||||
const [forceVisible, setForceVisible] = useState(false);
|
||||
const hasShownPrompt = Boolean(pageSettings?.fullscreenPromptShown);
|
||||
const isIOS = useMemo(() => detectIOS(), []);
|
||||
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(() => {
|
||||
if (settingsStatus !== 'ready') {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
if (!isMobileLayout) {
|
||||
setVisible(false);
|
||||
setForceVisible(false);
|
||||
return;
|
||||
}
|
||||
if (!forceVisible && isDismissed()) {
|
||||
if (!forceVisible && hasShownPrompt) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +70,7 @@ export function useFullscreenPrompt(layout) {
|
||||
}
|
||||
setMode('native');
|
||||
setVisible(true);
|
||||
}, [forceVisible, isIOS, isMobileLayout]);
|
||||
}, [forceVisible, hasShownPrompt, isIOS, isMobileLayout, settingsStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
reevaluate();
|
||||
@@ -109,10 +95,10 @@ export function useFullscreenPrompt(layout) {
|
||||
}, [reevaluate]);
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
markDismissed();
|
||||
markShown();
|
||||
setForceVisible(false);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
}, [markShown]);
|
||||
|
||||
const enterFullscreen = useCallback(async () => {
|
||||
if (!isMobileLayout || isIOS) return false;
|
||||
@@ -131,7 +117,7 @@ export function useFullscreenPrompt(layout) {
|
||||
if (result && typeof result.then === 'function') {
|
||||
await result;
|
||||
}
|
||||
markDismissed();
|
||||
markShown();
|
||||
setForceVisible(false);
|
||||
setVisible(false);
|
||||
return true;
|
||||
@@ -139,7 +125,7 @@ export function useFullscreenPrompt(layout) {
|
||||
console.warn('Failed to enter fullscreen', error);
|
||||
return false;
|
||||
}
|
||||
}, [isIOS, isMobileLayout]);
|
||||
}, [isIOS, isMobileLayout, markShown]);
|
||||
|
||||
const showPrompt = useCallback(() => {
|
||||
if (!isMobileLayout) return;
|
||||
|
||||
@@ -30,9 +30,10 @@ export function SettingsProvider({ children }) {
|
||||
|
||||
const setNamespace = useCallback((namespace, updater) => {
|
||||
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 nextData = { ...prev.data, [namespace]: nextValue };
|
||||
const nextData = { ...baseData, [namespace]: nextValue };
|
||||
const success = saveSettings(nextData);
|
||||
if (!success) {
|
||||
return { ...prev, status: 'error' };
|
||||
|
||||
Reference in New Issue
Block a user