{yoursList}
@@ -65,11 +77,10 @@ export function ActiveGameSelector({ onSelect }) {
}
export function GameArchiveSelector({ onSelect }) {
- const games = useContext(GamesContext);
- if (games.gamesList === null)
- return
+ const games = useContext(GamesStateContext);
+ const guide = useContext(GamesGuideContext);
- const isSelected = (uuid) => uuid === games.archive.selectedUUID;
+ const isSelected = (uuid) => uuid === guide.selectedUUID.archive;
const onClick = (uuid) => {
if (isSelected(uuid))
@@ -78,21 +89,28 @@ export function GameArchiveSelector({ onSelect }) {
onSelect(uuid);
}
- const rejectedList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_REJECTED')
+ const rejectedList = games.filter(game => game.status === 'GAME_PROPOSAL_REJECTED')
.map(game =>
)
- const canceledList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
+ const canceledList = games.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
.map(game =>
)
- const victoryList = games.gamesList.filter(game => game.status === 'GAME_RESULT_YOU_WON')
+ const victoryList = games.filter(game => game.status === 'GAME_RESULT_YOU_WON')
.map(game =>
)
- const defeatList = games.gamesList.filter(game => game.status === 'GAME_RESULT_YOU_LOOSE')
+ const defeatList = games.filter(game => game.status === 'GAME_RESULT_YOU_LOOSE')
.map(game =>
)
- const drawList = games.gamesList.filter(game => game.status === 'GAME_RESULT_DRAW')
+ const drawList = games.filter(game => game.status === 'GAME_RESULT_DRAW')
.map(game =>
)
+ if (rejectedList.length === 0 && canceledList.length === 0 && victoryList.length === 0 && defeatList.length === 0 && drawList.length === 0) {
+ if (guide.isPolling)
+ return
+ else
+ return <>Finished Games will be shown here..>
+ }
+
return (
{rejectedList.length > 0 &&
rejected proposals}
@@ -119,9 +137,15 @@ function Selectable({ game, selected, onClick }) {
onClick(game.uuid)}
>
-
+
diff --git a/webapp/src/container/games/view/NewGame.jsx b/webapp/src/container/games/view/NewGame.jsx
index f0040af..01c9ffd 100644
--- a/webapp/src/container/games/view/NewGame.jsx
+++ b/webapp/src/container/games/view/NewGame.jsx
@@ -1,19 +1,19 @@
import './NewGame.css'
import React, { useContext } from 'react';
-import { GamesContext } from '../../../context/games';
+import { GamesGuideContext } from '../../../context/games';
import DropdownList from '../../../components/DropdownList';
import { Color, WhiteStone, BlackStone } from '../../../components/Checkers';
export default function NewGame({ players, setPlayers }) {
- const games = useContext(GamesContext);
-
- const [whitePlayer, blackPlayer] = (() => {
- if (games.newGame.myColor === Color.white)
- return [players.currentUser, games.newGame.opponentName];
+ const newGame = useContext(GamesGuideContext).newGame;
- if (games.newGame.myColor === Color.black)
- return [games.newGame.opponentName, players.currentUser];
+ const [whitePlayer, blackPlayer] = (() => {
+ if (newGame.myColor === Color.white)
+ return [players.user.name, newGame.opponentName];
+
+ if (newGame.myColor === Color.black)
+ return [newGame.opponentName, players.user.name];
return ['', ''];
})(); // <<-- Execute!
@@ -21,25 +21,24 @@ export default function NewGame({ players, setPlayers }) {
/*
* Name options
*/
- const nameOptions = !players.leaderboard.table
- ? [
]
- : Object.keys(players.leaderboard.table).map(playerName =>
-
] :
+ Object.keys(players.leaderboard).map(name =>
+
)
- const whiteOptions = Array(nameOptions)
- whiteOptions.push(
)
+ const whiteOptions = Array(nameOptions);
+ whiteOptions.push(
);
- const blackOptions = Array(nameOptions)
- blackOptions.push(
)
+ const blackOptions = Array(nameOptions);
+ blackOptions.push(
);
/*
* The Component
*/
const onSelect = (name, myColor) => {
- if (players.isCurrentUser(name))
- setPlayers(games.newGame.opponentName, myColor);
+ if (players.user.isCurrentUser(name))
+ setPlayers(newGame.opponentName, myColor);
else
setPlayers(name, Color.opposite(myColor));
}
diff --git a/webapp/src/context/games.js b/webapp/src/context/games.js
index 537ab8a..1fea84d 100644
--- a/webapp/src/context/games.js
+++ b/webapp/src/context/games.js
@@ -1,6 +1,7 @@
import { createContext } from 'react';
-export const GamesContext = createContext(null);
+export const GamesStateContext = createContext(null);
+export const GamesGuideContext = createContext(null);
// export const Games = React.createContext({
// state: initialState,
diff --git a/webapp/src/hook/api.js b/webapp/src/hook/api.js
index 11e4b17..2e5b71b 100644
--- a/webapp/src/hook/api.js
+++ b/webapp/src/hook/api.js
@@ -8,7 +8,7 @@ import { useState, useRef, useCallback, useEffect, } from "react"
- interval_stop
*/
-export function usePolling(uri, onSuccess, mode = null) {
+export function usePolling(uri, { onSuccess, onPolling }, mode = null) {
const [isPolling, setPolling] = useState(false);
const initialPollRef = useRef(true);
@@ -19,11 +19,16 @@ export function usePolling(uri, onSuccess, mode = null) {
const pollData = useCallback(() => {
setPolling(true);
+ if (onPolling)
+ onPolling(true);
+
initialPollRef.current = false;
fetch(uri)
.then((response) => {
setPolling(false);
+ if (onPolling)
+ onPolling(false);
if (typeof mode?.interval_sec === 'number') {
console.log("Schedule", uri, "fetch in", mode.interval_sec, "sec");
@@ -38,7 +43,7 @@ export function usePolling(uri, onSuccess, mode = null) {
.catch((err) => {
console.warn(err.message);
})
- }, [uri, mode, onSuccess, initialPollRef, intervalTimerIdRef]);
+ }, [uri, mode, onSuccess, onPolling, initialPollRef, intervalTimerIdRef]);
const stopPollInterval = useCallback(() => {
console.log("Cancel scheduled fetch for", uri);
@@ -55,8 +60,6 @@ export function usePolling(uri, onSuccess, mode = null) {
stopPollInterval();
}
}, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]);
-
- return isPolling;
}
export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
@@ -77,13 +80,13 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
}
if (onSuccess) {
- var content = (response.headers.get('Content-Type') === "application/json")
- ? await response.json()
+ var content = (response.headers.get('Content-Type') === "application/json")
+ ? await response.json()
: null;
onSuccess(content);
}
-// } catch (err) {
+ // } catch (err) {
} finally {
if (onPushing)
onPushing(false);
diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js
index 6d79c0a..14aa963 100644
--- a/webapp/src/reducer/games.js
+++ b/webapp/src/reducer/games.js
@@ -2,91 +2,143 @@ import { useReducer } from 'react';
import { nextState } from '../util/StateHelper';
import { defaultBoard } from '../components/Checkers';
-export const gamesInitialState = {
- gamesList: null,
+/*
+ * State
+ */
+const gameTemplate = {
+ status: '',
+ myColor: '',
+ opponentName: '',
+ board: null,
+ moveNumber: 0,
+ previousMove: [],
+
+ message: '',
+ uuid: ''
+}
+
+const gamesStateTemplate = [/* gameTemplate */];
+
+function gamesStateReducer(state, action) {
+ switch (action.type) {
+
+ case 'next':
+ return action.games;
+
+ case 'add':
+ return [
+ ...state,
+ nextState(gameTemplate, action.game, 'Game.create')
+ ];
+
+ case 'update':
+ return state.map((game) =>
+ game.uuid !== action.game.uuid ? game :
+ nextState(gameTemplate, action.game, 'Game.update')
+ );
+
+ default:
+ throw Error('GamesState: unknown action.type', action.type);
+ }
+}
+
+export function useGamesStateReducer() {
+ return useReducer(gamesStateReducer, gamesStateTemplate);
+}
+
+/*
+ * Guide
+ */
+export const gamesGuideTemplate = {
newGame: {
opponentName: '',
myColor: '',
board: defaultBoard,
message: '',
+ isPushing: false
},
- proposal: {
- selectedUUID: null,
- message: '',
+ awaiting: {
+ proposal: 0,
+ active: 0
},
- active: {
- selectedUUID: null,
- message: '',
+ selectedUUID: {
+ proposal: null,
+ active: null,
+ archive: null,
},
- archive: {
- selectedUUID: null,
+ UUIDmessage: { // UUIDmessage[uuid]
},
- // Network
- isPollingGamesList: false,
- isPushingNewGame: false,
+ UUIDpushing: { // UUIDpushing[uuid]
+ },
- isPushingGameProposalCancel: false,
- isPushingGameProposalReject: false,
- isPushingGameProposalAccept: false,
-
- isPushingGameSurrender: false,
- isPushingGameDrawRequest: false,
- isPushingGameDrawAccept: false,
- isPushingGameDrawReject: false,
- isPushingGameMove: false,
-
- findGame,
- nextGame,
+ isPolling: false,
};
-function reducer(state, action) {
+function gamesGuideReducer(state, action) {
switch (action.type) {
case 'next':
- return nextState(state, action);
+ return nextState(state, action, 'GamesGuide');
+
+ case 'sync':
+ //console.log('sync');
+ return {
+ ...state,
+ awaiting: calcAwating(action.gamesState)
+ }
case 'nextNewGame':
return {
...state,
- newGame: nextState(state.newGame, action)
+ newGame: nextState(state.newGame, action, 'GamesGuide.newGame')
};
- case 'nextProposal':
+ case 'selectedUUID':
return {
...state,
- proposal: nextState(state.proposal, action)
+ selectedUUID: nextState(state.selectedUUID, action, 'GamesGuide.selectedUUID')
};
- case 'nextActive':
- return {
- ...state,
- active: nextState(state.active, action)
- };
+ case 'UUIDmessage': {
+ const next = { ...state };
+ next.UUIDmessage[action.uuid] = action.message;
+ return next;
+ }
- case 'nextArchive':
- return {
- ...state,
- archive: nextState(state.archive, action)
- };
+ case 'UUIDpushing': {
+ const next = { ...state };
+ next.UUIDpushing[action.uuid] = action.what
+ return next;
+ }
default:
- throw Error('GamesReducer: unknown action.type', action.type);
+ throw Error('GamesGuide: unknown action.type: ' + action.type);
}
}
-export default function useGamesReducer() {
- return useReducer(reducer, gamesInitialState);
+//const uuidRegex = new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}");
+
+export function useGamesGuideReducer() {
+ return useReducer(gamesGuideReducer, gamesGuideTemplate);
}
-function findGame({ uuid }) {
- return this.gamesList?.find((game) => game.uuid === uuid);
-}
-
-function nextGame(nextGame) {
- return this.gamesList?.map((game) => (game.uuid === nextGame?.uuid) ? nextGame : game);
+function calcAwating(games) {
+ return games.reduce((awaiting, game) => {
+ switch (game.status) {
+ case 'GAME_PROPOSAL_WAIT_FOR_YOU':
+ awaiting.proposal++;
+ return awaiting;
+ case 'GAME_BOARD_WAIT_FOR_YOU':
+ case 'DRAW_REQUEST_WAIT_FOR_YOU':
+ awaiting.active++;
+ return awaiting;
+ default:
+ return awaiting;
+ }
+ }, structuredClone(gamesGuideTemplate.awaiting));
}
\ No newline at end of file
diff --git a/webapp/src/reducer/leaderboard.js b/webapp/src/reducer/leaderboard.js
index f2b0141..feb86de 100644
--- a/webapp/src/reducer/leaderboard.js
+++ b/webapp/src/reducer/leaderboard.js
@@ -1,24 +1,48 @@
import { useReducer } from 'react';
import { nextState } from '../util/StateHelper';
-const initialState = {
- table: null,
-
- // Network
- isPollingTable: false
+/*
+ * State
+ */
+const stateTemplate = {
+ // name : { rank }
+ // Bobik: {total: 10, victory 5: draw: 1}
};
-function reducer(state, action) {
+function stateReducer(state, action) {
switch (action.type) {
case 'next':
- return nextState(state, action);
+ return action.table;
default:
- throw Error('LeaderboardReducer: unknown action.type', action.type);
+ throw Error('LeaderboardState: unknown action.type ' +action.type);
}
}
-export default function useLeaderboardReducer() {
- return useReducer(reducer, initialState);
+export function useLeaderboardStateReducer() {
+ return useReducer(stateReducer, stateTemplate);
+}
+
+/*
+ * Guide
+ */
+const guideTemplate = {
+
+ isPolling: false
+}
+
+function guideReducer(state, action) {
+ switch (action.type) {
+
+ case 'next':
+ return nextState(state, action, 'LeaderboardGuide');
+
+ default:
+ throw Error('LeaderboardGuide: unknown action.type ' +action.type);
+ }
+}
+
+export function useLeaderboardGuideReducer() {
+ return useReducer(guideReducer, guideTemplate);
}
\ No newline at end of file
diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js
index b019d0f..b42ca38 100644
--- a/webapp/src/reducer/user.js
+++ b/webapp/src/reducer/user.js
@@ -1,28 +1,31 @@
import { useReducer } from 'react';
import { localeCompare } from '../util/Locale';
-const initialState = {
- username: '',
+/*
+ * State
+ */
+const stateTemplate = {
+ name: '',
- isCurrentUser: function (otherUsername) {
- return localeCompare(this.username, otherUsername)
+ isCurrentUser: function (otherName) {
+ return localeCompare(this.name, otherName) || null; // true -or- null
}
};
-function reducer(state, action) {
+function stateReducer(state, action) {
switch (action.type) {
case 'parse':
return {
...state,
- username: action.userJson.holdingIdentity.name
+ name: action.userJson.holdingIdentity.name
};
default:
- throw Error('UserReducer: unknown action.type', action.type);
+ throw Error('UserState: unknown action.type ' +action.type);
}
}
-export default function useUserReducer() {
- return useReducer(reducer, initialState);
+export function useUserStateReducer() {
+ return useReducer(stateReducer, stateTemplate);
}
\ No newline at end of file
diff --git a/webapp/src/util/StateHelper.js b/webapp/src/util/StateHelper.js
index 4262349..e6608fb 100644
--- a/webapp/src/util/StateHelper.js
+++ b/webapp/src/util/StateHelper.js
@@ -1,16 +1,23 @@
-export function nextState(state, delta) {
+export function nextState(state, delta, coment) {
const nextState = { ...state };
- Object.keys(delta)
- .slice(1) // skip first property i.e. 'next'
- .forEach(key => {
- if (Object.hasOwn(nextState, key)) {
- console.log("next [", key, "] = ", delta[key]);
- nextState[key] = delta[key];
- } else {
- console.warn("nextState: bad action property\n", key + ":", delta[key]);
- }
- })
+ let logMsg = '';
+ Object.keys(delta).forEach(key => {
+ if (key === 'type')
+ return;
+
+ if (nextState.hasOwnProperty(key)) {
+ if (coment)
+ logMsg += '\n ' + key + ': ' + delta[key];
+
+ nextState[key] = delta[key];
+ } else {
+ console.warn("nextState: bad action property\n", key + ":", delta[key]);
+ }
+ })
+
+ if (coment)
+ console.log('Next', coment, logMsg);
return nextState;
}