Live location tracking and playback for the game "manhunt"

Use tauri-plugin-dialog where possible, loading screens

bwc9876.dev 9aa686d9 0885ef15

verified
+40 -48
+2 -32
TODO.md
··· 1 1 # TODO 2 2 3 - ## Ben 3 + - [ ] Start on start game and game settings buttons 4 + - [ ] If host leaves, kick all from lobby 4 5 5 - - [x] Transport : Packet splitting 6 - - [x] Transport : Handle Errors 7 - - [x] Transport : Mark game started on client 8 - - [x] API : Command to check if a game exists and is open for fast error checking 9 - - [x] Transport : Switch to burst message processing for less time in the 10 - critical path 11 - - [x] State : Event history tracking 12 - - [x] State : Post game sync 13 - - [x] API : Handling Profile Syncing 14 - - [x] API : State Update Events 15 - - [x] API : Game Replay Screen 16 - - [x] Frontend : Scaffolding 17 - - [x] Meta : CI Setup 18 - - [x] Meta : README Instructions 19 - - [x] Meta : Recipes for type binding generation 20 - - [x] Signaling: All of it 21 - - [x] Backend : Better transport error handling 22 - - [x] Backend : Abstract lobby? Separate crate? 23 - - [x] Transport : Handle transport cancellation better 24 - - [x] Backend : Add checks for when the `powerup_locations` field is an empty array in settings 25 - - [ ] Backend : More tests 26 - - [x] Lobby tests 27 - - [x] Game end test for actual return from loop 28 - - [x] More transport crate tests 29 - - [x] Signaling is wrong, only kick everyone else on host leave if the lobby is open 30 - - [x] Organize signalling and seperate out more logic 31 - - [x] Signaling tests 32 - - [ ] Testing crate for integration testing? 33 - - [ ] NixOS VM tests wrapping the testing crate? 34 - - [ ] Nix : Cheat the dependency nightmare and use crane 35 - - [x] Nix : Fix manhunt.nix to actually build
+16 -5
frontend/src/components/LobbyScreen.tsx
··· 3 3 import { useTauriEvent } from "@/lib/hooks"; 4 4 import ProfilePicture, { iconForDecor, ProfileDecor } from "./ProfilePicture"; 5 5 import { tempSettings } from "./MenuScreen"; 6 + import { ask } from "@tauri-apps/plugin-dialog"; 6 7 import { 7 8 IconArrowBigLeftLinesFilled, 8 9 IconCircleCheckFilled, 9 10 IconCircleDashedPlus 10 11 } from "@tabler/icons-react"; 12 + import LoadingCover from "./LoadingCover"; 11 13 12 14 function ProfileList({ 13 15 profiles, ··· 63 65 64 66 export default function LobbyScreen() { 65 67 const [lobbyState, setLobbyState] = useState(initLobbyState); 68 + const [loadingCover, setLoadingCover] = useState(false); 66 69 67 70 useEffect(() => { 68 71 let cancel = false; 69 72 commands.getLobbyState().then((state) => { 70 73 if (!cancel) { 71 74 setLobbyState(state); 75 + setLoadingCover(false); 72 76 } 73 77 }); 74 78 return () => { ··· 93 97 const isSeeker = lobbyState.teams[lobbyState.self_id] ?? false; 94 98 95 99 const onLeaveLobby = () => { 96 - // TODO: Tauri plugin dialog instead 97 - const choice = window.confirm("Are you sure you want to leave this lobby?"); 100 + const hostMsg = lobbyState.is_host ? " You are the host so this will cancel the lobby" : ""; 101 + 102 + const msg = `Are you sure you want to leave this lobby?${hostMsg}`; 98 103 99 - if (choice) { 100 - commands.quitToMenu(); 101 - } 104 + ask(msg, { 105 + title: "Leave Lobby", 106 + kind: lobbyState.is_host ? "warning" : "info" 107 + }).then((choice) => { 108 + if (choice) { 109 + commands.quitToMenu(); 110 + } 111 + }); 102 112 }; 103 113 104 114 return ( 105 115 <> 116 + <LoadingCover text="Fetching Info" show={loadingCover} /> 106 117 <header> 107 118 <span className="grow">Lobby</span> 108 119 <span>Join: {lobbyState.join_code}</span>
+22 -11
frontend/src/components/MenuScreen.tsx
··· 8 8 import { commands, GameSettings, PlayerProfile } from "@/bindings"; 9 9 import ProfilePicture from "./ProfilePicture"; 10 10 import LoadingCover from "./LoadingCover"; 11 + import { message } from "@tauri-apps/plugin-dialog"; 11 12 12 13 // Temp settings for now. 13 14 export const tempSettings: GameSettings = { ··· 53 54 }, []); 54 55 55 56 const joinLobby = useCallback(() => { 57 + setLoadingCover(true); 56 58 const code = window.prompt("Enter join code"); 57 59 if (!code) { 60 + setLoadingCover(false); 58 61 return; 59 62 } 60 63 const cleanedCode = code.toUpperCase().trim(); 61 64 commands.checkRoomCode(cleanedCode).then((valid) => { 62 65 if (valid) { 63 - commands.startLobby(cleanedCode, tempSettings); 66 + commands.startLobby(cleanedCode, tempSettings).finally(() => { 67 + setLoadingCover(false); 68 + }); 64 69 } else { 65 - window.alert("Invalid Join Code"); 70 + message("Invalid Join Code", { kind: "error", title: "Failed to Join" }); 71 + setLoadingCover(false); 66 72 } 67 - }); 73 + }).catch(() => { setLoadingCover(false); }); 68 74 }, []); 69 75 70 76 const onEditName = () => { ··· 80 86 81 87 const onEditPicture = () => { 82 88 setLoadingCover(true); 83 - commands.createProfilePicture().then((newPic) => { 84 - if (newPic) { 85 - const newProfile = { ...profile, pfp_base64: newPic }; 86 - commands.updateProfile(newProfile); 87 - setProfile(newProfile); 88 - } 89 - }).finally(() => { setLoadingCover(false); }); 89 + commands 90 + .createProfilePicture() 91 + .then((newPic) => { 92 + if (newPic) { 93 + const newProfile = { ...profile, pfp_base64: newPic }; 94 + commands.updateProfile(newProfile); 95 + setProfile(newProfile); 96 + } 97 + }) 98 + .finally(() => { 99 + setLoadingCover(false); 100 + }); 90 101 }; 91 102 92 103 return ( ··· 115 126 Past Games 116 127 </button> 117 128 </main> 118 - <LoadingCover text="Processing Picture..." show={loadingCover} /> 129 + <LoadingCover show={loadingCover} /> 119 130 </> 120 131 ); 121 132 }