mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
add turn alerts client-side
This commit is contained in:
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -11,7 +11,7 @@
|
|||||||
<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="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<title>Multi Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-TQSYlxub.js"></script>
|
<script type="module" crossorigin src="/assets/index-BW9FtSio.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CkWekXQn.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CkWekXQn.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import RightPaneTabs from './components/RightPaneTabs.jsx';
|
|||||||
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
|
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
|
||||||
import SessionSnapshot from './components/SessionSnapshot.jsx';
|
import SessionSnapshot from './components/SessionSnapshot.jsx';
|
||||||
import HomeAssistantControls from './components/HomeAssistantControls.jsx';
|
import HomeAssistantControls from './components/HomeAssistantControls.jsx';
|
||||||
|
import TurnAlertListener from './components/TurnAlertListener.jsx';
|
||||||
import UserListPanel from './components/UserListPanel.jsx';
|
import UserListPanel from './components/UserListPanel.jsx';
|
||||||
import ChatPanel from './components/ChatPanel.jsx';
|
import ChatPanel from './components/ChatPanel.jsx';
|
||||||
import FullscreenPrompt from './components/FullscreenPrompt.jsx';
|
import FullscreenPrompt from './components/FullscreenPrompt.jsx';
|
||||||
@@ -141,6 +142,7 @@ function App() {
|
|||||||
{renderedLayout}
|
{renderedLayout}
|
||||||
</main>
|
</main>
|
||||||
<AlertFeed />
|
<AlertFeed />
|
||||||
|
<TurnAlertListener />
|
||||||
<ModeGateOverlay />
|
<ModeGateOverlay />
|
||||||
<FullscreenPrompt
|
<FullscreenPrompt
|
||||||
visible={fullscreenVisible}
|
visible={fullscreenVisible}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import turnSound from '../assets/turn_alert.mp3';
|
||||||
|
import { useSession } from '../context/SessionContext.jsx';
|
||||||
|
|
||||||
|
function useAudio(src) {
|
||||||
|
const audioRef = useRef(null);
|
||||||
|
useEffect(() => {
|
||||||
|
audioRef.current = new Audio(src);
|
||||||
|
audioRef.current.load();
|
||||||
|
}, [src]);
|
||||||
|
const play = () => {
|
||||||
|
if (!audioRef.current) return;
|
||||||
|
audioRef.current.currentTime = 0;
|
||||||
|
audioRef.current.play().catch(() => {});
|
||||||
|
};
|
||||||
|
return play;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TurnAlertListener() {
|
||||||
|
const { session, pushAlert } = useSession();
|
||||||
|
const playSound = useAudio(turnSound);
|
||||||
|
const seenRoversRef = useRef(new Set());
|
||||||
|
|
||||||
|
const assignments = useMemo(() => session?.turnQueues || {}, [session?.turnQueues]);
|
||||||
|
const socketId = session?.socketId || null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socketId) return;
|
||||||
|
const newlyMine = [];
|
||||||
|
Object.entries(assignments).forEach(([roverId, info]) => {
|
||||||
|
if (!info || !info.current || info.current !== socketId) return;
|
||||||
|
if (!seenRoversRef.current.has(roverId)) {
|
||||||
|
newlyMine.push(roverId);
|
||||||
|
seenRoversRef.current.add(roverId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(assignments).forEach((roverId) => {
|
||||||
|
if (!assignments[roverId] || assignments[roverId].current !== socketId) {
|
||||||
|
seenRoversRef.current.delete(roverId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (newlyMine.length === 0) return;
|
||||||
|
newlyMine.forEach((roverId) => {
|
||||||
|
const roverName = session?.roster?.find((r) => String(r.id) === String(roverId))?.name || roverId;
|
||||||
|
pushAlert({
|
||||||
|
title: 'Your turn!',
|
||||||
|
message: `You now control ${roverName}.`,
|
||||||
|
color: 'success',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
playSound();
|
||||||
|
}, [assignments, playSound, pushAlert, session?.roster, socketId]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -96,6 +96,11 @@ export function SessionProvider({ children }) {
|
|||||||
homeAssistantSetState: (entityId, state) =>
|
homeAssistantSetState: (entityId, state) =>
|
||||||
emitWithAck('homeAssistant:setState', { entityId, state }),
|
emitWithAck('homeAssistant:setState', { entityId, state }),
|
||||||
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
|
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
|
||||||
|
pushAlert: (alert) =>
|
||||||
|
setAlerts((prev) => [
|
||||||
|
...prev.slice(-49),
|
||||||
|
{ ...alert, receivedAt: Date.now(), id: alert.id || Math.random().toString(36).slice(2) },
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
[emitWithAck],
|
[emitWithAck],
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user