mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
dead socket kicking and connection info pill
This commit is contained in:
@@ -42,6 +42,7 @@ import VipPanel from './components/VipPanel/index.jsx';
|
||||
import { useSessionSelector } from './context/SessionContext.jsx';
|
||||
import ButtonBoxPanel from './components/ButtonBoxPanel/index.jsx';
|
||||
import RewardRunOverlay from './components/RewardRunOverlay/index.jsx';
|
||||
import SocketConnectionPill from './components/SocketConnectionPill/index.jsx';
|
||||
|
||||
function useLayoutMode() {
|
||||
const [mode, setMode] = useState(() => {
|
||||
@@ -333,6 +334,7 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
<RewardRunOverlay />
|
||||
<TurnAlertListener />
|
||||
<ModeGateOverlay />
|
||||
<SocketConnectionPill />
|
||||
|
||||
<HelpOverlay
|
||||
visible={helpVisible}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSocket } from '../../context/SocketContext.jsx';
|
||||
|
||||
const CONNECTED_FADE_DELAY_MS = 4000;
|
||||
|
||||
function cls(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
export default function SocketConnectionPill() {
|
||||
const socket = useSocket();
|
||||
const [connected, setConnected] = useState(Boolean(socket?.connected));
|
||||
const [lastReason, setLastReason] = useState('');
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [faded, setFaded] = useState(false);
|
||||
const fadeTimerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const clearFadeTimer = () => {
|
||||
if (fadeTimerRef.current) {
|
||||
clearTimeout(fadeTimerRef.current);
|
||||
fadeTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const show = () => {
|
||||
clearFadeTimer();
|
||||
setVisible(true);
|
||||
setFaded(false);
|
||||
};
|
||||
|
||||
const scheduleFade = () => {
|
||||
clearFadeTimer();
|
||||
fadeTimerRef.current = setTimeout(() => {
|
||||
setFaded(true);
|
||||
}, CONNECTED_FADE_DELAY_MS);
|
||||
};
|
||||
|
||||
const onConnect = () => {
|
||||
setConnected(true);
|
||||
setLastReason('');
|
||||
show();
|
||||
scheduleFade();
|
||||
};
|
||||
|
||||
const onDisconnect = (reason) => {
|
||||
setConnected(false);
|
||||
setLastReason(typeof reason === 'string' ? reason : 'disconnected');
|
||||
show();
|
||||
};
|
||||
|
||||
const onConnectError = (err) => {
|
||||
setConnected(false);
|
||||
setLastReason(err?.message || 'connect error');
|
||||
show();
|
||||
};
|
||||
|
||||
const onReconnectAttempt = () => {
|
||||
setConnected(false);
|
||||
show();
|
||||
};
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
socket.on('disconnect', onDisconnect);
|
||||
socket.on('connect_error', onConnectError);
|
||||
socket.io.on('reconnect_attempt', onReconnectAttempt);
|
||||
|
||||
if (socket.connected) {
|
||||
scheduleFade();
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearFadeTimer();
|
||||
socket.off('connect', onConnect);
|
||||
socket.off('disconnect', onDisconnect);
|
||||
socket.off('connect_error', onConnectError);
|
||||
socket.io.off('reconnect_attempt', onReconnectAttempt);
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const offline = typeof navigator !== 'undefined' ? navigator.onLine === false : false;
|
||||
const statusLabel = connected
|
||||
? 'Connected to socket.io'
|
||||
: offline
|
||||
? 'Offline'
|
||||
: 'Reconnecting...';
|
||||
const detail = connected ? '' : lastReason;
|
||||
|
||||
const toneClass = connected
|
||||
? 'border-emerald-300/40 bg-emerald-700/70 text-emerald-100'
|
||||
: offline
|
||||
? 'border-red-300/50 bg-red-800/80 text-red-100'
|
||||
: 'border-amber-300/50 bg-amber-800/80 text-amber-100';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls(
|
||||
'fixed bottom-2 left-2 z-[120] transition-opacity duration-500',
|
||||
!visible && 'hidden',
|
||||
faded ? 'opacity-0 pointer-events-none' : 'opacity-100',
|
||||
)}
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className={cls('rounded-md border px-2 py-1 text-xs shadow-lg backdrop-blur-sm', toneClass)}>
|
||||
<span className="font-semibold">{statusLabel}</span>
|
||||
{detail ? <span className="ml-1 opacity-85">({detail})</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -95,5 +95,14 @@ export default function useUserIdentitySync() {
|
||||
};
|
||||
}, [sendIdentify, socket?.connected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready || !socket?.connected) return undefined;
|
||||
const timer = setInterval(() => {
|
||||
if (!socket?.connected) return;
|
||||
sendIdentify();
|
||||
}, 60000);
|
||||
return () => clearInterval(timer);
|
||||
}, [ready, sendIdentify, socket?.connected]);
|
||||
|
||||
useEffect(() => () => clearRetry(), [clearRetry]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user