#24-directory-structure #26
@ -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 <div className="App">
 | 
			
		||||
    <BrowserRouter>
 | 
			
		||||
@ -22,7 +27,7 @@ function App() {
 | 
			
		||||
        <Route path="/game/proposal" element={<Game />} />
 | 
			
		||||
        <Route path="/game/active" 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 />} />
 | 
			
		||||
      </Routes>
 | 
			
		||||
    </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 './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 <p>Loading...</p>
 | 
			
		||||
 | 
			
		||||
//  var listItems = Object.keys(data).map(playerName => {
 | 
			
		||||
//    var rank = data[playerName];
 | 
			
		||||
//
 | 
			
		||||
//    return <li key={playerName}>
 | 
			
		||||
//      {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw}
 | 
			
		||||
//      </li>
 | 
			
		||||
//  });
 | 
			
		||||
//  return <ul>{listItems}</ul>;
 | 
			
		||||
  //  var listItems = Object.keys(data).map(playerName => {
 | 
			
		||||
  //    var rank = data[playerName];
 | 
			
		||||
  //
 | 
			
		||||
  //    return <li key={playerName}>
 | 
			
		||||
  //      {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw}
 | 
			
		||||
  //      </li>
 | 
			
		||||
  //  });
 | 
			
		||||
  //  return <ul>{listItems}</ul>;
 | 
			
		||||
 | 
			
		||||
  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 <tr key={playerName} className={data.isCurrentUser(playerName) && 'username'}>
 | 
			
		||||
    // TODO tr:  className={data.isCurrentUser(playerName) && 'username'}
 | 
			
		||||
    return <tr key={playerName} >
 | 
			
		||||
      <td>{playerName}</td>
 | 
			
		||||
      <td>{rank.gamesPlayed}</td>
 | 
			
		||||
      <td>{rank.gamesWon}</td>
 | 
			
		||||
      <td>{rank.gamesDraw}</td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tr>
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return <div className="Leaderboard">
 | 
			
		||||
@ -39,8 +38,8 @@ export default function Leaderboard() {
 | 
			
		||||
        </tr>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        { tableRows }
 | 
			
		||||
        {tableRows}
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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