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