From 2482226e0e609937179a212242bdee09d366eea0 Mon Sep 17 00:00:00 2001 From: djmil Date: Sun, 12 Nov 2023 19:40:55 +0100 Subject: [PATCH] Better useXxxApi (#34) useXxxApi: use PollingReducer as a configuration provider provide pushAPIs, which update respective state with: pushing status push result PollingReducer: rename to configurationReducer move polling indication to it's respective state XxxState.polling = 'true/false' Reviewed-on: http://192.168.8.55:3000/HQLAx/CordaCheckers/pulls/34 --- webapp/src/App.js | 39 +++++++++------- webapp/src/api/games.js | 29 ++++++------ webapp/src/api/leaderboard.js | 24 +++++----- webapp/src/api/user.js | 15 +++---- webapp/src/container/Games.jsx | 3 +- webapp/src/container/Leaderboard.jsx | 9 ++-- webapp/src/container/games/action/Create.jsx | 7 +-- .../src/container/games/view/GameSelector.jsx | 8 ++-- webapp/src/container/games/view/NewGame.jsx | 4 +- webapp/src/reducer/config.js | 44 +++++++++++++++++++ webapp/src/reducer/games.js | 14 +++--- webapp/src/reducer/leaderboard.js | 24 ++++++++++ webapp/src/reducer/polling.js | 39 ---------------- webapp/src/reducer/user.js | 8 ++-- 14 files changed, 153 insertions(+), 114 deletions(-) create mode 100644 webapp/src/reducer/config.js create mode 100644 webapp/src/reducer/leaderboard.js delete mode 100644 webapp/src/reducer/polling.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 379665f..9730e57 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -9,7 +9,9 @@ import About from "./components/About" import Games from './container/Games'; import Leaderboard from './container/Leaderboard'; -import usePollingReducer from './reducer/polling'; +import useConfigReducer from './reducer/config'; +import useUserReducer from './reducer/user'; +import useLeaderboardReducer from './reducer/leaderboard'; import useGamesReducer from './reducer/games'; import useUserApi from './api/user'; @@ -17,35 +19,40 @@ import useLeaderboardApi from './api/leaderboard'; import useGamesApi from './api/games'; export default function App() { - const pollingReducer = usePollingReducer(); + const [config, dispatcConfig] = useConfigReducer(); - const leaderboard = useLeaderboardApi().poll(pollingReducer); - const user = useUserApi().get(); - - const [games, dispatchGames] = useGamesReducer(); - const gamesApi = useGamesApi(dispatchGames); - gamesApi.list(pollingReducer); + const user = useUserApi(useUserReducer()).getUser(); + const leaderboard = useLeaderboardApi(useLeaderboardReducer(), config).pollTable(); const players = { leaderboard, isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null }; + const gamesReducer = useGamesReducer(); + const gamesApi = useGamesApi(gamesReducer, config); + const games = gamesApi.pollGamesList(); + + const isPolling = { + games: games.isPollingGamesList, + leaderboard: leaderboard.isPollingTable + } + return ( -
+
} /> } /> - } /> + } /> } /> ) } -function Header({ pollingReducer }) { - const [polling, dispatchPolling] = pollingReducer; +function Header({ configReducer, isPolling }) { + const [config, dispatcConfig] = configReducer; return (
@@ -54,8 +61,8 @@ function Header({ pollingReducer }) { dispatchPolling({ type: 'toggleOnOff' })} + isOnline={config.online} + onClick={() => dispatcConfig({ type: 'toggleOnline' })} />
diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index 01d4501..8a4f3ed 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -1,26 +1,29 @@ import usePolling from '../hook/Polling'; -export default function useGamesApi(dispatchGames) { +export default function useGamesApi(gamesReducer, config) { + const [games, dispatchGames] = gamesReducer; - const useList = (pollingReducer) => { - const [polling, dispatchPolling] = pollingReducer; - - const onResponce = (json) => { - dispatchGames({ type: 'next', list: json }); + const usePollingGamesList = () => { + const onSuccess = (gamesList) => { + dispatchGames({ type: 'next', gamesList }); } - const mode = (polling.enabled === true) - ? { interval_sec: 30 } // fetch gamesList every half a minue - : { interval_stop: true } // user has fliped OfflineToggel + const isPollingGamesList = usePolling('/api/games', onSuccess, config.intervalMode(30)); + if (games.isPollingGamesList !== isPollingGamesList) { + dispatchGames({ type: 'next', isPollingGamesList }); + } - const isPolling = usePolling('/api/games', onResponce, mode); + return games; + } - if (isPolling !== polling.games) { - dispatchPolling({ type: 'next', games: isPolling }); + const usePushNewGame = () => { + const onSuccess = (game) => { + dispatchGames({ type: 'next', game }); } } return { - list: useList + pollGamesList: usePollingGamesList, + pushNewGame: usePushNewGame } } \ No newline at end of file diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index c4901b8..fe279a3 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -1,26 +1,22 @@ -import { useState } from "react"; import usePolling from "../hook/Polling"; -export default function useLeaderboardApi() { - const [leaderboard, setLeaderboard] = useState(null); +export default function useLeaderboardApi(leaderboardReducer, config) { + const [leaderboard, dispatchLeaderboard] = leaderboardReducer; - const usePoll = (pollingReducer) => { - const [polling, dispatchPolling] = pollingReducer; + const usePollingTable = () => { + const onSuccess = (table) => { + dispatchLeaderboard({ type: 'next', table }); + } - const mode = (polling.enabled === true) - ? { interval_sec: 300 } // update leaderbord stats every 5 min - : { interval_stop: true } // user has fliped OfflineToggel - - const isPolling = usePolling('/api/leaderboard', setLeaderboard, mode); - - if (isPolling !== polling.leaderboard) { - dispatchPolling({ type: 'next', leaderboard: isPolling }); + const isPollingTable = usePolling('/api/leaderboard', onSuccess, config.intervalMode(300)); + if (leaderboard.isPollingTable !== isPollingTable) { + dispatchLeaderboard({ type: 'next', isPollingTable }); } return leaderboard; } return { - poll: usePoll + pollTable: usePollingTable } } \ No newline at end of file diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js index 18f199c..39c3a68 100644 --- a/webapp/src/api/user.js +++ b/webapp/src/api/user.js @@ -1,20 +1,19 @@ import usePolling from "../hook/Polling"; -import useUserReducer from "../reducer/user"; -export default function useUserApi() { - const [user, dispatchUser] = useUserReducer(); +export default function useUserApi(userReducer) { + const [user, dispatchUser] = userReducer; - const useGet = () => { - const onResponce = (json) => { - dispatchUser({ type: "parse", json }); + const useGetUser = () => { + const onSuccess = (userJson) => { + dispatchUser({ type: "parse", userJson }); } - usePolling('/api/user', onResponce); // <<-- fetch once + usePolling('/api/user', onSuccess); // <<-- fetch once return user; } return { - get: useGet + getUser: useGetUser } } \ No newline at end of file diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index 595c98b..3ac2c34 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -19,7 +19,8 @@ import GameBoard from './games/GameBoard'; import './Games.css'; -export default function Games({ context: { games, dispatchGames, gamesApi }, players }) { +export default function Games({ context: { gamesReducer, gamesApi }, players }) { + const [games, dispatchGames] = gamesReducer; return ( diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index ca92490..9493dd7 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -4,13 +4,12 @@ import Loading from '../components/Loading'; export default function Leaderboard({ players }) { - const leaderboard = players.leaderboard; - - if (leaderboard == null) + const table = players.leaderboard.table; + if (table == null) return - const tableRows = Object.keys(leaderboard).map(playerName => { - var rank = leaderboard[playerName]; + const tableRows = Object.keys(table).map(playerName => { + var rank = table[playerName]; return {playerName} diff --git a/webapp/src/container/games/action/Create.jsx b/webapp/src/container/games/action/Create.jsx index 893258f..9fd59ca 100644 --- a/webapp/src/container/games/action/Create.jsx +++ b/webapp/src/container/games/action/Create.jsx @@ -4,13 +4,13 @@ import Wobler from '../../../components/Wobler'; -export default function Create({ isCurrentUser }) { +export default function Create({ isCurrentUser, onClick }) { const newGameCtx = useContext(GamesContext).newGame; const hasPlayers = checkPlayers(newGameCtx); const hasCurrentUser = checkCurrentUser(newGameCtx, isCurrentUser); - const pushNewGame = () => { + const prepareRequest = () => { if (!hasPlayers) return alert("Black and White players must be selected for the game"); @@ -20,6 +20,7 @@ export default function Create({ isCurrentUser }) { if (newGameCtx.pushing) return; // current request is still being processed + //onClick console.log("send request"); } @@ -27,7 +28,7 @@ export default function Create({ isCurrentUser }) { diff --git a/webapp/src/container/games/view/GameSelector.jsx b/webapp/src/container/games/view/GameSelector.jsx index 5578e7c..23c6d34 100644 --- a/webapp/src/container/games/view/GameSelector.jsx +++ b/webapp/src/container/games/view/GameSelector.jsx @@ -7,14 +7,14 @@ import Loading from '../../../components/Loading'; export default function GameSelector({ yours, opponents, onClick }) { - const games = useContext(GamesContext); - if (games.list === null) + const gamesList = useContext(GamesContext).gamesList; + if (gamesList === null) return - const yoursList = games.list.filter(game => game.status === yours) + const yoursList = gamesList.filter(game => game.status === yours) .map(game => ) - const opponentsList = games.list.filter(game => game.status === opponents) + const opponentsList = gamesList.filter(game => game.status === opponents) .map(game => ) return ( diff --git a/webapp/src/container/games/view/NewGame.jsx b/webapp/src/container/games/view/NewGame.jsx index 0ce7d3e..a729a70 100644 --- a/webapp/src/container/games/view/NewGame.jsx +++ b/webapp/src/container/games/view/NewGame.jsx @@ -11,9 +11,9 @@ export default function NewGame({ players, onSelectPlayer }) { /* * Name options */ - const nameOptions = !players.leaderboard + const nameOptions = !players.leaderboard.table ? [] - : Object.keys(players.leaderboard).map(playerName => + : Object.keys(players.leaderboard.table).map(playerName => ) diff --git a/webapp/src/reducer/config.js b/webapp/src/reducer/config.js new file mode 100644 index 0000000..1d2f3a4 --- /dev/null +++ b/webapp/src/reducer/config.js @@ -0,0 +1,44 @@ +import { useReducer } from 'react'; +import { namedLocalStorage } from '../util/PersistentStorage'; +import { nextState } from '../util/StateHelper'; + +const Persistent = (() => { + const [getOnline, setOnline] = namedLocalStorage('config.online', true); + + return { + getOnline, + setOnline + } +})(); // <<--- Execute + +const initialState = { + online: Persistent.getOnline() === 'true', + + intervalMode +}; + +function dispatch(state, action) { + switch (action.type) { + + case 'toggleOnline': return { + ...state, + online: Persistent.setOnline(!state.online) + }; + + case 'next': + return nextState(state, action); + + default: + throw Error('ConfigReducer: unknown action.type', action.type); + } +} + +export default function useConfigReducer() { + return useReducer(dispatch, initialState); +} + +function intervalMode(interval_sec) { + return (this.online === true) + ? { interval_sec } // fetch from API every interval_sec + : { interval_stop: true } // user has fliped OfflineToggel +} \ No newline at end of file diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index 854ef37..073bd6a 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -1,16 +1,20 @@ import { useReducer } from 'react'; import { nextState } from '../util/StateHelper'; -export const gamesInitialState = { - list: null, +const initialState = { + gamesList: null, newGame: { whitePlayer: '', blackPlayer: '' - } + }, + + // Network + isPollingGamesList: false, + isPushingNewGame: false, }; -export function gamesReducer(state, action) { +function reducer(state, action) { switch (action.type) { case 'next': @@ -22,5 +26,5 @@ export function gamesReducer(state, action) { } export default function useGamesReducer() { - return useReducer(gamesReducer, gamesInitialState); + return useReducer(reducer, initialState); } \ No newline at end of file diff --git a/webapp/src/reducer/leaderboard.js b/webapp/src/reducer/leaderboard.js new file mode 100644 index 0000000..f2b0141 --- /dev/null +++ b/webapp/src/reducer/leaderboard.js @@ -0,0 +1,24 @@ +import { useReducer } from 'react'; +import { nextState } from '../util/StateHelper'; + +const initialState = { + table: null, + + // Network + isPollingTable: false +}; + +function reducer(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(reducer, initialState); +} \ No newline at end of file diff --git a/webapp/src/reducer/polling.js b/webapp/src/reducer/polling.js deleted file mode 100644 index 750f2ca..0000000 --- a/webapp/src/reducer/polling.js +++ /dev/null @@ -1,39 +0,0 @@ -import { useReducer } from 'react'; -import { namedLocalStorage } from '../util/PersistentStorage'; -import { nextState } from '../util/StateHelper'; - -const Persistent = (() => { - const [getEnabled, setEnabled] = namedLocalStorage('polling.enabled', true); - - return { - getEnabled, - setEnabled - } -})(); // <<--- Execute - -export const pollingInitialState = { - enabled: Persistent.getEnabled() === 'true', - - games: false, - leaderboard: false -}; - -export function pollingReducer(curntState, action) { - switch (action.type) { - - case 'toggleOnOff': return { - ...curntState, - enabled: Persistent.setEnabled(!curntState.enabled) - }; - - case 'next': - return nextState(curntState, action); - - default: - throw Error('Unknown action.type:' + action.type); - } -} - -export default function usePollingReducer() { - return useReducer(pollingReducer, pollingInitialState); -} \ No newline at end of file diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js index 0351553..1cf1265 100644 --- a/webapp/src/reducer/user.js +++ b/webapp/src/reducer/user.js @@ -1,7 +1,7 @@ import { useReducer } from 'react'; import { localeCompare } from '../util/Locale'; -export const userInitialState = { +const initialState = { username: '', isCurrentUser: function (otherUsername) { @@ -9,13 +9,13 @@ export const userInitialState = { } }; -export function userReducer(state, action) { +function reducer(state, action) { switch (action.type) { case 'parse': return { ...state, - username: action.json.username + username: action.userJson.username }; default: @@ -24,5 +24,5 @@ export function userReducer(state, action) { } export default function useUserReducer() { - return useReducer(userReducer, userInitialState); + return useReducer(reducer, initialState); } \ No newline at end of file