diff --git a/webapp/src/App.js b/webapp/src/App.js
index 16d5a50..84e5661 100644
--- a/webapp/src/App.js
+++ b/webapp/src/App.js
@@ -9,40 +9,36 @@ import About from "./components/About"
import Games from './container/Games';
import Leaderboard from './container/Leaderboard';
-import useUserReducer from './reducer/user';
import usePollingReducer from './reducer/polling';
-import useLeaderboardReducer from './reducer/leaderboard';
-import useGamesReducer from './reducer/games';
+//import useGamesReducer from './reducer/games';
import useUserApi from './api/user';
import useLeaderboardApi from './api/leaderboard';
-import useGamesApi from './api/games';
+//import useGamesApi from './api/games';
export default function App() {
- const userReducer = useUserReducer();
- const pollingFlux = usePollingReducer();
- const leaderboardReducer = useLeaderboardReducer();
- const gamesReducer = useGamesReducer();
+ const pollingReducer = usePollingReducer();
+ //const gamesReducer = useGamesReducer();
- const user = useUserApi(userReducer).get();
- const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingFlux);
- /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingFlux);
+ //const games = useGamesApi(gamesReducer).list(pollingReducer);
+ const leaderboard = useLeaderboardApi().poll(pollingReducer);
+ const user = useUserApi().get();
return (
-
+
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
)
}
-function Header({ pollingFlux }) {
- const [polling, dispatchPolling] = pollingFlux;
+function Header({ pollingReducer }) {
+ const [polling, dispatchPolling] = pollingReducer;
return (
@@ -52,20 +48,20 @@ function Header({ pollingFlux }) {
dispatchPolling({ type: "toggleOnOff" })}
+ onClick={() => dispatchPolling({ type: 'toggleOnOff' })}
/>
diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js
index aefbdbb..df2a168 100644
--- a/webapp/src/api/games.js
+++ b/webapp/src/api/games.js
@@ -1,25 +1,19 @@
import usePolling from "../util/Polling"
-const uri = '/api/games';
-
-export default function useGamesApi(gamesReducer) {
- const [games, dispatchGames] = gamesReducer;
+export default function useGamesApi(gamesState) {
+ const [games, setGames] = gamesState;
const useList = (pollingReducer) => {
const [polling, dispatchPolling] = pollingReducer;
const mode = (polling.enabled === true)
- ? { interval_sec: 30 } // update games list half a minue
+ ? { interval_sec: 30 } // update games list every half a minue
: { interval_stop: true } // user has fliped OfflineToggel
- const [list, isFetching] = usePolling(uri, mode);
+ const isPolling = usePolling('/api/games', setGames, mode);
- if (polling.games !== isFetching) {
- dispatchPolling({ type: 'next', games: isFetching });
- }
-
- if (games.list !== list) {
- dispatchGames({ type: 'next', list });
+ if (isPolling !== polling.games) {
+ dispatchPolling({ type: 'next', games: isPolling });
}
return games;
diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js
index f5d01f9..a653e35 100644
--- a/webapp/src/api/leaderboard.js
+++ b/webapp/src/api/leaderboard.js
@@ -1,9 +1,8 @@
-import usePolling from "../util/Polling"
+import { useState } from "react";
+import usePolling from "../util/Polling";
-const uri = '/api/leaderboard';
-
-export default function useLeaderboardApi(leaderboardReducer) {
- const [leaderboard, dispatchLeaderboaed] = leaderboardReducer;
+export default function useLeaderboardApi() {
+ const [leaderboard, setLeaderboard] = useState(null);
const usePoll = (pollingReducer) => {
const [polling, dispatchPolling] = pollingReducer;
@@ -12,14 +11,10 @@ export default function useLeaderboardApi(leaderboardReducer) {
? { interval_sec: 300 } // update leaderbord stats every 5 min
: { interval_stop: true } // user has fliped OfflineToggel
- const [table, isFetching] = usePolling(uri, mode);
+ const isPolling = usePolling('/api/leaderboard', setLeaderboard, mode);
- if (polling.leaderboard !== isFetching) {
- dispatchPolling({ type: 'next', leaderboard: isFetching });
- }
-
- if (leaderboard.table !== table) {
- dispatchLeaderboaed({ type: 'next', table });
+ if (isPolling !== polling.leaderboard) {
+ dispatchPolling({ type: 'next', leaderboard: isPolling });
}
return leaderboard;
diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js
index 7f8606a..83106b3 100644
--- a/webapp/src/api/user.js
+++ b/webapp/src/api/user.js
@@ -1,16 +1,15 @@
-import usePolling from "../util/Polling"
+import usePolling from "../util/Polling";
+import useUserReducer from "../reducer/user";
-const uri = '/api/user';
-
-export default function useUserApi([user, dispatchUser]) {
+export default function useUserApi() {
+ const [user, dispatchUser] = useUserReducer();
const useGet = () => {
- const [nextUser] = usePolling(uri);
-
- if (typeof nextUser?.username === 'string' && nextUser.username !== user.username) {
- dispatchUser({ type: "next", username: nextUser.username });
+ const onResponce = (json) => {
+ dispatchUser({ type: "parse", json });
}
+ usePolling('/api/user', onResponce); // <<-- fetch once
return user;
}
diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx
index 6224487..6d8863e 100644
--- a/webapp/src/container/Leaderboard.jsx
+++ b/webapp/src/container/Leaderboard.jsx
@@ -4,15 +4,14 @@ import Loading from '../components/Loading';
export default function Leaderboard({ leaderboard, user }) {
- const table = leaderboard?.table;
- if (!table)
+ if (leaderboard == null)
return
const isCurrentUser = (playerName) =>
user?.isCurrentUser(playerName) === true ? true : null;
- const tableRows = Object.keys(table).map(playerName => {
- var rank = table[playerName];
+ const tableRows = Object.keys(leaderboard).map(playerName => {
+ var rank = leaderboard[playerName];
return
{playerName} |
diff --git a/webapp/src/context/app/reducer.js b/webapp/src/context/app/reducer.js
deleted file mode 100644
index bb53b1d..0000000
--- a/webapp/src/context/app/reducer.js
+++ /dev/null
@@ -1,84 +0,0 @@
-export const reducer = (state, action) => {
-
- switch (action.update) {
-
- case "game-selector":
- return GameSelector_update(state, action)
-
- case "newGame":
- return updateNewGame(state, action)
-
- default:
- console.warn("Unknown action.component", action.component)
- return state
- }
-}
-
-export const initialState = {
- gameSelector: {
- selectedGameProposal: null,
- selectedActiveGame: null,
- selectedArchiveGame: null,
- },
-
- newGame: {
- whitePlayer: '',
- blackPlayer: '',
- message: '',
- fetching: false,
- },
-
-}
-
-function GameSelector_update(state, action) {
- if (Object.hasOwn(action, 'selectedGameProposal')) {
- return {
- ...state,
- gameSelector: {
- ...state.gameSelector,
- selectedGameProposal: action.selectedGameProposal
- }
- }
- }
-
- if (Object.hasOwn(action, 'selectedActiveGame')) {
- return {
- ...state,
- gameSelector: {
- ...state.gameSelector,
- selectedActiveGame: action.selectedActiveGame
- }
- }
- }
-
- if (Object.hasOwn(action, 'selectedArchiveGame')) {
- return {
- ...state,
- gameSelector: {
- ...state.gameSelector,
- selectedArchiveGame: action.selectedArchiveGame
- }
- }
- }
-
- console.warn(action.component, "- bad property")
-}
-
-function updateNewGame(state, action) {
- const newGame = {...state.newGame}
-
- Object.keys(action)
- .slice(1) // skip 'update' property
- .forEach(actionKey => {
- if (Object.hasOwn(newGame, actionKey)) {
- newGame[actionKey] = action[actionKey]
- } else {
- console.warn("NewGame update: bad action property\n", actionKey + ":", action[actionKey])
- }
- })
-
- return {
- ...state,
- newGame
- }
-}
diff --git a/webapp/src/context/data/Poll.js b/webapp/src/context/data/Poll.js
deleted file mode 100644
index 9d6d209..0000000
--- a/webapp/src/context/data/Poll.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useState, useCallback, useEffect, } from "react"
-
-/*
- TODO: Poll(uri, flavour)
- - uri: string
- - execution_flvour:
- - once (i.e. now)
- - interval (sec)
- - stop
-*/
-
-export default function Poll(url, interval_sec, offlineMode) {
- const [dataCache, setDataCache] = useState(null)
- const [fetching , setFetching ] = useState(false)
- const [timeoutID, setTimeoutID] = useState(null)
-
- const fecthData = useCallback(() => {
- setTimeoutID(null)
- setFetching(true)
-
- fetch(url)
- .then((response) => {
- setFetching(false)
- return response.json()
- })
- .then((freshData) => setDataCache(freshData))
- .catch((err) => console.log(err.message))
- }, [url])
-
- useEffect(() => {
- if (dataCache == null) {
- fecthData() // <<-- run immediatly on startup
- }
- else if (offlineMode === true) {
- clearTimeout(timeoutID) // cancel already scheduled fetch
- setTimeoutID(null) // & stop interval fetching
- }
- else if (timeoutID === null && typeof interval_sec === 'number') {
- const timeoutID = setTimeout(fecthData, interval_sec * 1000)
- setTimeoutID(timeoutID)
- console.log("Fetch '" +url +"' scheduled in " +interval_sec +" sec")
- }
- }, [url, dataCache, fecthData, timeoutID, offlineMode, interval_sec]);
-
- return [ dataCache, fetching ]
-}
diff --git a/webapp/src/context/data/index.jsx b/webapp/src/context/data/index.jsx
deleted file mode 100644
index e7fa95e..0000000
--- a/webapp/src/context/data/index.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from "react"
-import { reducer, initialState } from "./reducer"
-
-import Poll from "./Poll"
-
-export const AppData = React.createContext({
- state: initialState,
- dispatch: () => null
-})
-
-export const AppDataProvider = ({ children }) => {
-
- const [data, dispatchData] = React.useReducer(reducer, initialState)
-
- const [games, gamesFetching ] = Poll('/api/gamestate' , 30, data.offlineMode)
- const [leaderboard, leaderboardFetching ] = Poll('/api/leaderboard', 60, data.offlineMode)
- const [user] = Poll('/api/user') // once
-
- data.games = games
- data.gamesFetching = gamesFetching
-
- data.leaderboard = leaderboard
- data.leaderboardFetching = leaderboardFetching
-
- data.isCurrentUser = (otherUsername) => {
- return user?.username && ciEquals(user.username, otherUsername) ? true : null
- }
-
- return (
-
- {children}
-
- )
-}
-
-function ciEquals(a, b) {
- return typeof a === 'string' && typeof b === 'string'
- ? a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0
- : a === b;
-}
\ No newline at end of file
diff --git a/webapp/src/context/data/reducer.js b/webapp/src/context/data/reducer.js
deleted file mode 100644
index 6e9e065..0000000
--- a/webapp/src/context/data/reducer.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export const reducer = (state, action) => {
- switch (action.type) {
-
- case "toggleOfflineMode":
- return { ...state,
- offlineMode: !state.offlineMode // on/off
- }
-
- default:
- console.warn("Unknown action.type", action)
- return state
- }
-}
-
-export const initialState = {
- games: null,
- gamesFetching: false,
-
- leaderboard: null,
- leaderboardFetching: false,
-
- isCurrentUser: () => null,
-
- offlineMode: false
-}
diff --git a/webapp/src/context/app/index.jsx b/webapp/src/context/games.jsx
similarity index 55%
rename from webapp/src/context/app/index.jsx
rename to webapp/src/context/games.jsx
index f4be27c..d747333 100644
--- a/webapp/src/context/app/index.jsx
+++ b/webapp/src/context/games.jsx
@@ -1,17 +1,17 @@
import React from "react"
import { reducer, initialState } from "./reducer"
-export const AppContext = React.createContext({
+export const Games = React.createContext({
state: initialState,
dispatch: () => null
})
-export const AppContextProvider = ({ children }) => {
+export const GamesProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState)
return (
-
+
{ children }
-
+
)
}
diff --git a/webapp/src/index.js b/webapp/src/index.js
index d65c14e..71bb868 100644
--- a/webapp/src/index.js
+++ b/webapp/src/index.js
@@ -3,18 +3,11 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import reportWebVitals from './reportWebVitals';
import App from './App';
-import { AppDataProvider } from "./context/data"
-import { AppContextProvider } from "./context/app"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
-
-
-
-
+
);
diff --git a/webapp/src/reducer/leaderboard.js b/webapp/src/reducer/leaderboard.js
deleted file mode 100644
index 06352a4..0000000
--- a/webapp/src/reducer/leaderboard.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useReducer } from 'react';
-import { nextState } from '../util/StateHelper';
-
-export const leaderboardInitialState = {
- table: null,
-};
-
-export function leaderboardReducer(state, action) {
- switch (action.type) {
-
- case 'next':
- return nextState(state, action);
-
- default:
- throw Error('LeaderboardReducer: Unknown action.type', action.type);
- }
-}
-
-export default function useLeaderboardReducer() {
- return useReducer(leaderboardReducer, leaderboardInitialState);
-}
\ No newline at end of file
diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js
index 09b8f86..151c220 100644
--- a/webapp/src/reducer/user.js
+++ b/webapp/src/reducer/user.js
@@ -1,6 +1,6 @@
import { useReducer } from 'react';
import { localeCompare } from '../util/Locale';
-import { nextState } from '../util/StateHelper';
+//import { nextState } from '../util/StateHelper';
export const userInitialState = {
username: '',
@@ -13,8 +13,13 @@ export const userInitialState = {
export function userReducer(state, action) {
switch (action.type) {
- case 'next':
- return nextState(state, action);
+ case 'parse':
+ const apiData = parse(action.json);
+
+ return {
+ ...state,
+ ...apiData
+ };
default:
throw Error('UserReducer: unknown action.type', action.type);
@@ -23,4 +28,11 @@ export function userReducer(state, action) {
export default function useUserReducer() {
return useReducer(userReducer, userInitialState);
+}
+
+function parse(json) {
+ console.log("userreducer.parse", json);
+ return {
+ username: json.username
+ }
}
\ No newline at end of file
diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js
index 49a1f9a..4446656 100644
--- a/webapp/src/util/Polling.js
+++ b/webapp/src/util/Polling.js
@@ -3,51 +3,53 @@ import { useState, useCallback, useEffect, } from "react"
/*
- uri: string
- mode:
- - null - default, return cashed value while polling fresh one from server)
+ - null - default, fetch data ONCE
- 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);
+export default function usePolling(uri, onResponce, mode = null) {
+ const [initialPoll, setInitialPoll] = useState(true);
+ const [isPolling, setPolling] = useState(false);
+ const [intervalTimer, setIntervalTimer] = useState(null);
- const fetchData = useCallback(() => {
- setDelayID(null);
- setFetching(true);
+ const pollData = useCallback(() => {
+ setPolling(true);
+ setInitialPoll(false);
- fetch(url)
- .then((response) => response.json())
- .then((freshData) => {
- setCache(freshData);
- setFetching(false);
+ fetch(uri)
+ .then((responce) => {
+ setPolling(false);
+
+ if (typeof mode?.interval_sec === 'number') {
+ console.log("Schedule", uri, "fetch in", mode.interval_sec, "sec");
+ const intervalTimer = setTimeout(pollData, mode.interval_sec * 1000);
+ setIntervalTimer(intervalTimer);
+ }
+
+ return responce.json();
+ })
+ .then((json) => {
+ onResponce(json);
})
.catch((err) => {
console.warn(err.message);
- setFetching(false);
})
- }, [url])
+ }, [uri, mode, onResponce]);
useEffect(() => {
- if (cache === null && isFetching === false) {
- fetchData();
+ if ((initialPoll || (typeof mode?.interval_sec === 'number' && intervalTimer === null)) && !isPolling) {
+ pollData();
}
+ }, [initialPoll, mode, intervalTimer, isPolling, pollData]);
- 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);
- }
+ if (mode?.interval_stop && intervalTimer) {
+ console.log("Cancel scheduled fetch for", uri);
+ clearTimeout(intervalTimer);
+ setIntervalTimer(null);
+ setInitialPoll(true);
+ }
- }, [url, mode, isFetching, cache, fetchData, delayID]);
- return [
- cache, // API responce
- isFetching // true / false
- ]
-}
+ return isPolling;
+}
\ No newline at end of file