Live location tracking and playback for the game "manhunt"

Smoother view transitions and loading screen

bwc9876.dev 291b733a 7d4959da

verified
+26 -12
+2 -1
frontend/src/components/App.tsx
··· 5 import MenuScreen from "./MenuScreen"; 6 import LobbyScreen from "./LobbyScreen"; 7 import GameScreen from "./GameScreen"; 8 9 function ScreenRouter({ screen }: { screen: AppScreen }) { 10 switch (screen) { ··· 30 }; 31 32 if (document.startViewTransition) { 33 - document.startViewTransition(update); 34 } else { 35 update(); 36 }
··· 5 import MenuScreen from "./MenuScreen"; 6 import LobbyScreen from "./LobbyScreen"; 7 import GameScreen from "./GameScreen"; 8 + import { flushSync } from "react-dom"; 9 10 function ScreenRouter({ screen }: { screen: AppScreen }) { 11 switch (screen) { ··· 31 }; 32 33 if (document.startViewTransition) { 34 + document.startViewTransition(() => flushSync(update)); 35 } else { 36 update(); 37 }
+10 -7
frontend/src/components/LobbyScreen.tsx
··· 69 70 export default function LobbyScreen() { 71 const [lobbyState, setLobbyState] = useState(initLobbyState); 72 - const [loadingCover, setLoadingCover] = useState(false); 73 74 useEffect(() => { 75 let cancel = false; 76 - commands.getLobbyState().then((state) => { 77 - if (!cancel) { 78 - setLobbyState(state); 79 - setLoadingCover(false); 80 - } 81 - }); 82 return () => { 83 cancel = true; 84 }; 85 }, [setLobbyState]); 86
··· 69 70 export default function LobbyScreen() { 71 const [lobbyState, setLobbyState] = useState(initLobbyState); 72 + const [loadingCover, setLoadingCover] = useState(true); 73 74 useEffect(() => { 75 let cancel = false; 76 + const clear = setTimeout(() => { 77 + commands.getLobbyState().then((state) => { 78 + if (!cancel) { 79 + setLobbyState(state); 80 + setLoadingCover(false); 81 + } 82 + }); 83 + }, 300); 84 return () => { 85 cancel = true; 86 + clearTimeout(clear); 87 }; 88 }, [setLobbyState]); 89
+6 -4
frontend/src/components/MenuScreen.tsx
··· 44 45 const startLobby = () => { 46 setLoadingCover(true); 47 - commands.startLobby(null, defaultSettings()).finally(() => { 48 - setLoadingCover(false); 49 - }); 50 }; 51 52 const joinLobby = () => { ··· 61 .checkRoomCode(cleanedCode) 62 .then((valid) => { 63 if (valid) { 64 - commands.startLobby(cleanedCode, defaultSettings()).finally(() => { 65 setLoadingCover(false); 66 }); 67 } else {
··· 44 45 const startLobby = () => { 46 setLoadingCover(true); 47 + setTimeout(() => { 48 + commands.startLobby(null, defaultSettings()).catch(() => { 49 + setLoadingCover(false); 50 + }); 51 + }, 100); 52 }; 53 54 const joinLobby = () => { ··· 63 .checkRoomCode(cleanedCode) 64 .then((valid) => { 65 if (valid) { 66 + commands.startLobby(cleanedCode, defaultSettings()).catch(() => { 67 setLoadingCover(false); 68 }); 69 } else {
+8
frontend/src/style.css
··· 183 justify-content: center; 184 z-index: 500; 185 flex-direction: column; 186 gap: var(--2); 187 188 strong { ··· 192 193 .spinner { 194 font-size: 48pt; 195 } 196 } 197 198 @keyframes rotation {
··· 183 justify-content: center; 184 z-index: 500; 185 flex-direction: column; 186 + backdrop-filter: blur(1px); 187 gap: var(--2); 188 189 strong { ··· 193 194 .spinner { 195 font-size: 48pt; 196 + view-transition-name: spinner; 197 + opacity: 0.85; 198 } 199 + } 200 + 201 + :root::view-transition-group(spinner) { 202 + animation-duration: 0s; 203 + animation-delay: 0s; 204 } 205 206 @keyframes rotation {