diff --git a/webapp/src/App.js b/webapp/src/App.js index 04ad760..dd7a425 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -8,9 +8,14 @@ import Game from "./components/Game" import About from "./components/About" import Polling from './reducer/polling'; +import { LeaderboardApi } from './api/leaderboard'; +// import { UserApi } from './api/user'; function App() { const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) + + const leaderboard = LeaderboardApi(polling, dispatchPolling).get(); + // const user = UserApi(polling, dispatchPolling).get(); return
@@ -22,7 +27,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js new file mode 100644 index 0000000..993932c --- /dev/null +++ b/webapp/src/api/leaderboard.js @@ -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 + } +} \ No newline at end of file diff --git a/webapp/src/components/Leaderboard/index.jsx b/webapp/src/components/Leaderboard/index.jsx index 7d893fb..2dd542d 100644 --- a/webapp/src/components/Leaderboard/index.jsx +++ b/webapp/src/components/Leaderboard/index.jsx @@ -1,31 +1,30 @@ import React from "react" import './index.css'; -import { AppData } from "../../context/data" -export default function Leaderboard() { - const [data] = React.useContext(AppData) +export default function Leaderboard({ leaderboard }) { - if (data.leaderboard == null) + if (leaderboard == null) return

Loading...

-// var listItems = Object.keys(data).map(playerName => { -// var rank = data[playerName]; -// -// return
  • -// {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} -//
  • -// }); -// return ; + // var listItems = Object.keys(data).map(playerName => { + // var rank = data[playerName]; + // + // return
  • + // {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} + //
  • + // }); + // return ; - const tableRows = Object.keys(data.leaderboard).map(playerName => { - var rank = data.leaderboard[playerName]; + const tableRows = Object.keys(leaderboard).map(playerName => { + var rank = leaderboard[playerName]; - return + // TODO tr: className={data.isCurrentUser(playerName) && 'username'} + return {playerName} {rank.gamesPlayed} {rank.gamesWon} {rank.gamesDraw} - + }); return
    @@ -39,8 +38,8 @@ export default function Leaderboard() { - { tableRows } + {tableRows} -
    +
    }; diff --git a/webapp/src/reducer/polling.js b/webapp/src/reducer/polling.js index 89ebf9a..88573a3 100644 --- a/webapp/src/reducer/polling.js +++ b/webapp/src/reducer/polling.js @@ -1,4 +1,4 @@ -import { useLocalStorage } from './util/PersistentStorage' +import { useLocalStorage } from '../util/PersistentStorage' const Persistent = (() => { const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); @@ -26,18 +26,18 @@ export function pollingReducer(state, action) { enabled: Persistent.setEnabled(!state.enabled) }; - case 'games': return { + case 'setGames': return { ...state, games: action.value }; - case 'leaderboard': return { + case 'setLeaderboard': return { ...state, leaderboard: action.value }; default: - throw Error('Unknown action:' + action.type); + throw Error('Unknown action.type:' + action.type); } } diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js new file mode 100644 index 0000000..ed3fac6 --- /dev/null +++ b/webapp/src/util/Polling.js @@ -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 + ] +}