mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -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-title" content="Multi Roomba Rover" />
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -17,6 +17,7 @@ import RightPaneTabs from './components/RightPaneTabs.jsx';
|
||||
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
|
||||
import SessionSnapshot from './components/SessionSnapshot.jsx';
|
||||
import HomeAssistantControls from './components/HomeAssistantControls.jsx';
|
||||
import TurnAlertListener from './components/TurnAlertListener.jsx';
|
||||
import UserListPanel from './components/UserListPanel.jsx';
|
||||
import ChatPanel from './components/ChatPanel.jsx';
|
||||
import FullscreenPrompt from './components/FullscreenPrompt.jsx';
|
||||
@@ -141,6 +142,7 @@ function App() {
|
||||
{renderedLayout}
|
||||
</main>
|
||||
<AlertFeed />
|
||||
<TurnAlertListener />
|
||||
<ModeGateOverlay />
|
||||
<FullscreenPrompt
|
||||
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) =>
|
||||
emitWithAck('homeAssistant:setState', { entityId, state }),
|
||||
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],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user