leaderboard api polling
This commit is contained in:
		
							parent
							
								
									ba7f9ce7d1
								
							
						
					
					
						commit
						472f5de928
					
				| @ -8,9 +8,14 @@ import Game from "./components/Game" | |||||||
| import About from "./components/About" | import About from "./components/About" | ||||||
| 
 | 
 | ||||||
| import Polling from './reducer/polling'; | import Polling from './reducer/polling'; | ||||||
|  | import { LeaderboardApi } from './api/leaderboard'; | ||||||
|  | // import { UserApi } from './api/user';
 | ||||||
| 
 | 
 | ||||||
| function App() { | function App() { | ||||||
|   const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) |   const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) | ||||||
|  |   | ||||||
|  |   const leaderboard = LeaderboardApi(polling, dispatchPolling).get(); | ||||||
|  |   // const user = UserApi(polling, dispatchPolling).get();
 | ||||||
| 
 | 
 | ||||||
|   return <div className="App"> |   return <div className="App"> | ||||||
|     <BrowserRouter> |     <BrowserRouter> | ||||||
| @ -22,7 +27,7 @@ function App() { | |||||||
|         <Route path="/game/proposal" element={<Game />} /> |         <Route path="/game/proposal" element={<Game />} /> | ||||||
|         <Route path="/game/active" element={<Game />} /> |         <Route path="/game/active" element={<Game />} /> | ||||||
|         <Route path="/game/archive" element={<Game />} /> |         <Route path="/game/archive" element={<Game />} /> | ||||||
|         <Route path="/leaderboard" element={<Leaderboard />} /> |         <Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} />} /> | ||||||
|         <Route path="/about" element={<About />} /> |         <Route path="/about" element={<About />} /> | ||||||
|       </Routes> |       </Routes> | ||||||
|     </BrowserRouter> |     </BrowserRouter> | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								webapp/src/api/leaderboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								webapp/src/api/leaderboard.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import usePolling from "../util/Polling" | ||||||
|  | 
 | ||||||
|  | const uri = '/api/leaderboard'; | ||||||
|  | 
 | ||||||
|  | export function LeaderboardApi(polling, dispatchPolling) { | ||||||
|  | 
 | ||||||
|  |     const useGet = () => { | ||||||
|  |         const mode = (polling.enabled === true) | ||||||
|  |             ? { interval_sec: 300 }     // update leaderbord stats every 5 min
 | ||||||
|  |             : { interval_stop: true }   // user has fliped OfflineToggel
 | ||||||
|  | 
 | ||||||
|  |         const [leaderboard, isFetching] = usePolling(uri, mode); | ||||||
|  | 
 | ||||||
|  |         if (polling.leaderboard !== isFetching) { | ||||||
|  |             dispatchPolling({ type: 'setLeaderboard', value: isFetching }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return leaderboard; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |         get: useGet | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,31 +1,30 @@ | |||||||
| import React from "react" | import React from "react" | ||||||
| import './index.css'; | import './index.css'; | ||||||
| import { AppData } from "../../context/data" |  | ||||||
| 
 | 
 | ||||||
| export default function Leaderboard() { | export default function Leaderboard({ leaderboard }) { | ||||||
|   const [data] = React.useContext(AppData) |  | ||||||
| 
 | 
 | ||||||
|   if (data.leaderboard == null)  |   if (leaderboard == null) | ||||||
|     return <p>Loading...</p> |     return <p>Loading...</p> | ||||||
| 
 | 
 | ||||||
| //  var listItems = Object.keys(data).map(playerName => { |   //  var listItems = Object.keys(data).map(playerName => { | ||||||
| //    var rank = data[playerName]; |   //    var rank = data[playerName]; | ||||||
| // |   // | ||||||
| //    return <li key={playerName}> |   //    return <li key={playerName}> | ||||||
| //      {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} |   //      {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} | ||||||
| //      </li> |   //      </li> | ||||||
| //  }); |   //  }); | ||||||
| //  return <ul>{listItems}</ul>; |   //  return <ul>{listItems}</ul>; | ||||||
| 
 | 
 | ||||||
|   const tableRows = Object.keys(data.leaderboard).map(playerName => { |   const tableRows = Object.keys(leaderboard).map(playerName => { | ||||||
|     var rank = data.leaderboard[playerName]; |     var rank = leaderboard[playerName]; | ||||||
| 
 | 
 | ||||||
|     return <tr key={playerName} className={data.isCurrentUser(playerName) && 'username'}> |     // TODO tr:  className={data.isCurrentUser(playerName) && 'username'} | ||||||
|  |     return <tr key={playerName} > | ||||||
|       <td>{playerName}</td> |       <td>{playerName}</td> | ||||||
|       <td>{rank.gamesPlayed}</td> |       <td>{rank.gamesPlayed}</td> | ||||||
|       <td>{rank.gamesWon}</td> |       <td>{rank.gamesWon}</td> | ||||||
|       <td>{rank.gamesDraw}</td> |       <td>{rank.gamesDraw}</td> | ||||||
|       </tr> |     </tr> | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   return <div className="Leaderboard"> |   return <div className="Leaderboard"> | ||||||
| @ -39,8 +38,8 @@ export default function Leaderboard() { | |||||||
|         </tr> |         </tr> | ||||||
|       </thead> |       </thead> | ||||||
|       <tbody> |       <tbody> | ||||||
|         { tableRows } |         {tableRows} | ||||||
|       </tbody> |       </tbody> | ||||||
|     </table> |     </table> | ||||||
|     </div> |   </div> | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { useLocalStorage } from './util/PersistentStorage' | import { useLocalStorage } from '../util/PersistentStorage' | ||||||
| 
 | 
 | ||||||
| const Persistent = (() => { | const Persistent = (() => { | ||||||
|   const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); |   const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); | ||||||
| @ -26,18 +26,18 @@ export function pollingReducer(state, action) { | |||||||
|       enabled: Persistent.setEnabled(!state.enabled) |       enabled: Persistent.setEnabled(!state.enabled) | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     case 'games': return { |     case 'setGames': return { | ||||||
|       ...state, |       ...state, | ||||||
|       games: action.value |       games: action.value | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     case 'leaderboard': return { |     case 'setLeaderboard': return { | ||||||
|       ...state, |       ...state, | ||||||
|       leaderboard: action.value |       leaderboard: action.value | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     default: |     default: | ||||||
|       throw Error('Unknown action:' + action.type); |       throw Error('Unknown action.type:' + action.type); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										53
									
								
								webapp/src/util/Polling.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								webapp/src/util/Polling.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | import { useState, useCallback, useEffect, } from "react" | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |   - uri: string | ||||||
|  |   - mode: | ||||||
|  |     - null  - default, return cashed value while polling fresh one from server) | ||||||
|  |     - interval_sec | ||||||
|  |     - interval_stop | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | export default function usePolling(url, mode) { | ||||||
|  |     const [cache, setCache] = useState(null); | ||||||
|  |     const [isFetching, setFetching] = useState(false); | ||||||
|  |     const [delayID, setDelayID] = useState(null); | ||||||
|  | 
 | ||||||
|  |     const fetchData = useCallback(() => { | ||||||
|  |         setDelayID(null); | ||||||
|  |         setFetching(true); | ||||||
|  | 
 | ||||||
|  |         fetch(url) | ||||||
|  |             .then((response) => response.json()) | ||||||
|  |             .then((freshData) => { | ||||||
|  |                 setCache(freshData); | ||||||
|  |                 setFetching(false); | ||||||
|  |             }) | ||||||
|  |             .catch((err) => { | ||||||
|  |                 console.warn(err.message); | ||||||
|  |                 setFetching(false); | ||||||
|  |             }) | ||||||
|  |     }, [url]) | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         if (cache === null && isFetching === false) { | ||||||
|  |             fetchData(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mode?.interval_sec && delayID === null) { | ||||||
|  |             const timeoutID = setTimeout(fetchData, mode.interval_sec * 1000) | ||||||
|  |             setDelayID(timeoutID) | ||||||
|  |             console.log("Fetch '" + url + "' scheduled in " + mode.interval_sec + " sec") | ||||||
|  |         } | ||||||
|  |         else if (mode?.interval_stop) { | ||||||
|  |             clearTimeout(delayID);  // cancel already scheduled fetch
 | ||||||
|  |             setDelayID(null);        | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     }, [url, mode, isFetching, cache, fetchData, delayID]); | ||||||
|  | 
 | ||||||
|  |     return [ | ||||||
|  |         cache, // API response
 | ||||||
|  |         isFetching  // true / false
 | ||||||
|  |     ] | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user