-
+
{/*
@@ -80,11 +81,15 @@ function ViewProvider({ players, dispatchGames }) {
)
}
-function ActionPanel() {
+function ActionPanel({ players, gamesApi }) {
return (
- } />
+ gamesApi.pushNewGame(reqParams)}
+ />
+ } />
, , ]} />
, , ]} />
, ]} />
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 2cee6c6..cddb347 100644
--- a/webapp/src/container/games/action/Create.jsx
+++ b/webapp/src/container/games/action/Create.jsx
@@ -1,6 +1,66 @@
-import React from 'react';
+import React, { useContext } from 'react';
+import { GamesContext } from '../../../context/games';
+import Wobler from '../../../components/Wobler';
+import { Color } from '../../../components/Checkers';
-export default function Create() {
- return
+export default function Create({ isCurrentUser, pushNewGame }) {
+ const games = useContext(GamesContext);
+
+ const hasPlayers = checkPlayers(games.newGame);
+ const hasCurrentUser = checkCurrentUser(games.newGame, isCurrentUser);
+
+ const prepareNewGameRequest = () => {
+ if (!hasPlayers)
+ return alert("Black and White players must be selected for the game");
+
+ if (!hasCurrentUser)
+ return alert("You must be one of the selected players");
+
+ if (games.isPushingNewGame)
+ return; // current request is still being processed
+
+ /*
+ * Prepare & send NewGame request
+ */
+ const [opponentName, opponentColor] = getOpponent(games.newGame, isCurrentUser);
+
+ const reqParams = {
+ opponentName,
+ opponentColor,
+ board: null, // default board configuration
+ message: 'default NewGame req message'
+ }
+
+ pushNewGame(reqParams);
+ }
+
+ return (
+
+ )
}
+
+function checkPlayers({ whitePlayer, blackPlayer }) {
+ return whitePlayer && blackPlayer
+ && whitePlayer !== blackPlayer;
+}
+
+function checkCurrentUser({ whitePlayer, blackPlayer }, isCurrentUser) {
+ return isCurrentUser(whitePlayer) || isCurrentUser(blackPlayer);
+}
+
+function getOpponent({whitePlayer, blackPlayer}, isCurrentUser) {
+ if (isCurrentUser(whitePlayer)) {
+ return [blackPlayer, Color.black];
+ }
+
+ if (isCurrentUser(blackPlayer)) {
+ return [whitePlayer, Color.white];
+ }
+
+ return ['', ''];
+}
\ No newline at end of file
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/hook/Polling.js b/webapp/src/hook/api.js
similarity index 60%
rename from webapp/src/hook/Polling.js
rename to webapp/src/hook/api.js
index abfa3b7..28e759c 100644
--- a/webapp/src/hook/Polling.js
+++ b/webapp/src/hook/api.js
@@ -8,22 +8,21 @@ import { useState, useRef, useCallback, useEffect, } from "react"
- interval_stop
*/
-export default function usePolling(uri, onResponce, mode = null) {
+export function usePolling(uri, onSuccess, mode = null) {
const [isPolling, setPolling] = useState(false);
const initialPollRef = useRef(true);
const initialPoll = initialPollRef.current;
-
+
const intervalTimerIdRef = useRef(null);
const intervalTimerId = intervalTimerIdRef.current;
-
const pollData = useCallback(() => {
setPolling(true);
initialPollRef.current = false;
fetch(uri)
- .then((responce) => {
+ .then((response) => {
setPolling(false);
if (typeof mode?.interval_sec === 'number') {
@@ -31,15 +30,15 @@ export default function usePolling(uri, onResponce, mode = null) {
intervalTimerIdRef.current = setTimeout(pollData, mode.interval_sec * 1000);
}
- return responce.json();
+ return response.json();
})
.then((json) => {
- onResponce(json);
+ onSuccess(json);
})
.catch((err) => {
console.warn(err.message);
})
- }, [uri, mode, onResponce, initialPollRef, intervalTimerIdRef]);
+ }, [uri, mode, onSuccess, initialPollRef, intervalTimerIdRef]);
const stopPollInterval = useCallback(() => {
console.log("Cancel scheduled fetch for", uri);
@@ -51,11 +50,36 @@ export default function usePolling(uri, onResponce, mode = null) {
useEffect(() => {
if ((initialPoll || (typeof mode?.interval_sec === 'number' && intervalTimerId === null)) && !isPolling) {
pollData();
- } else
- if (mode?.interval_stop && intervalTimerId) {
- stopPollInterval();
- }
+ } else
+ if (mode?.interval_stop && intervalTimerId) {
+ stopPollInterval();
+ }
}, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]);
return isPolling;
+}
+
+export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
+ if (onPushing)
+ onPushing(true);
+
+ try {
+ const response = await fetch(uri, {
+ method,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data), // body data type must match "Content-Type" header
+ });
+
+ if (!response.ok)
+ throw new Error(`Error! status: ${response.status}`);
+
+ if (onSuccess)
+ onSuccess(await response.json());
+// } catch (err) {
+ } finally {
+ if (onPushing)
+ onPushing(false);
+ }
}
\ No newline at end of file
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