UUIDprops
- guide.selectedUUID is used to determina current uuid - all the game related midifications are stored independantly per UUID - no global indicators
This commit is contained in:
parent
e017787441
commit
df431eb4f1
@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import djmil.cordacheckers.cordaclient.CordaClient;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameView;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
import djmil.cordacheckers.user.User;
|
||||
|
||||
|
@ -10,9 +10,6 @@ import Games from './container/Games';
|
||||
import Leaderboard from './container/Leaderboard';
|
||||
|
||||
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';
|
||||
import useLeaderboardApi from './api/leaderboard';
|
||||
@ -21,22 +18,23 @@ import useGamesApi from './api/games';
|
||||
export default function App() {
|
||||
const [config, dispatcConfig] = useConfigReducer();
|
||||
|
||||
const user = useUserApi(useUserReducer()).getUser();
|
||||
const leaderboard = useLeaderboardApi(useLeaderboardReducer(), config).pollTable();
|
||||
const user = useUserApi();
|
||||
const leaderboard = useLeaderboardApi();
|
||||
const games = useGamesApi();
|
||||
|
||||
user.api.useGetUser();
|
||||
leaderboard.api.useTablePolling(config);
|
||||
games.api.useGamesPolling(config);
|
||||
|
||||
|
||||
const players = {
|
||||
leaderboard,
|
||||
currentUser: user.username,
|
||||
isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null
|
||||
user: user.state,
|
||||
leaderboard: leaderboard.state,
|
||||
};
|
||||
|
||||
const gamesReducer = useGamesReducer();
|
||||
const gamesApi = useGamesApi(gamesReducer, config);
|
||||
const games = gamesApi.pollGamesList();
|
||||
|
||||
const isPolling = {
|
||||
games: games.isPollingGamesList,
|
||||
leaderboard: leaderboard.isPollingTable
|
||||
games: games.guide.isPolling,
|
||||
leaderboard: leaderboard.guide.isPolling
|
||||
}
|
||||
|
||||
return (
|
||||
@ -45,7 +43,7 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path='/' element={<About />} />
|
||||
<Route path='/about' element={<About />} />
|
||||
<Route path='/games/*' element={<Games context={{ gamesReducer, gamesApi }} players={players} />} />
|
||||
<Route path='/games/*' element={<Games games={games} players={players} />} />
|
||||
<Route path='/leaderboard' element={<Leaderboard players={players} />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
@ -1,82 +1,101 @@
|
||||
import { usePolling, doPushing } from '../hook/api';
|
||||
import { gamesInitialState } from '../reducer/games';
|
||||
import { Color } from '../components/Checkers';
|
||||
import { useGamesGuideReducer, useGamesStateReducer, gamesGuideTemplate } from '../reducer/games';
|
||||
|
||||
export default function useGamesApi(gamesReducer, config) {
|
||||
const [games, dispatchGames] = gamesReducer;
|
||||
export default function useGamesApi() {
|
||||
const [state, dispatchState] = useGamesStateReducer();
|
||||
const [guide, dispatchGuide] = useGamesGuideReducer();
|
||||
|
||||
const usePollingGamesList = () => {
|
||||
const onSuccess = (gamesList) => {
|
||||
dispatchGames({ type: 'next', gamesList });
|
||||
const useGamesPolling = (config) => {
|
||||
const onPolling = (isPolling) => dispatchGuide({ type: 'next', isPolling });
|
||||
const onSuccess = (games) => dispatchState({ type: 'next', games });
|
||||
|
||||
usePolling('/api/game', { onPolling, onSuccess }, config.intervalMode(30));
|
||||
}
|
||||
|
||||
const isPollingGamesList = usePolling('/api/game', onSuccess, config.intervalMode(30));
|
||||
if (games.isPollingGamesList !== isPollingGamesList) {
|
||||
dispatchGames({ type: 'next', isPollingGamesList });
|
||||
const pushNewGame = ({ opponentName, opponentColor, board, message }) => {
|
||||
doPushing('/api/gameproposal', 'POST', { opponentName, opponentColor, board, message }, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'nextNewGame', isPushing }),
|
||||
onSuccess: (game) => {
|
||||
dispatchState({ type: 'add', game });
|
||||
dispatchGuide({ type: 'nextNewGame', ...gamesGuideTemplate.newGame });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return games;
|
||||
const pushGameProposalAccept = ({ uuid }) => {
|
||||
doPushing(`/api/gameproposal/${uuid}/accept`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'accept' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameProposalReject = ({ uuid }) => {
|
||||
doPushing(`/api/gameproposal/${uuid}/reject`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'reject' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameProposalCancel = ({ uuid }) => {
|
||||
doPushing(`/api/gameproposal/${uuid}/cancel`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'cancel' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameSurrender = ({ uuid }) => {
|
||||
doPushing(`/api/game/${uuid}/surrender`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'surrender' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameDrawRequest = ({ uuid }) => {
|
||||
doPushing(`/api/game/${uuid}/drawreq`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_request' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameDrawAccept = ({ uuid }) => {
|
||||
doPushing(`/api/game/${uuid}/drawacc`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_accept' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameDrawReject = ({ uuid }) => {
|
||||
doPushing(`/api/game/${uuid}/drawrej`, 'PUT', null, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_reject' }),
|
||||
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||
})
|
||||
}
|
||||
|
||||
const pushGameMove = ({ uuid, move, message }) => {
|
||||
doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, {
|
||||
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'move' }),
|
||||
onSuccess: (game) => {
|
||||
dispatchState({ type: 'update', game });
|
||||
dispatchGuide({ type: 'UUIDmessage', uuid, message: ''});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
pollGamesList: usePollingGamesList,
|
||||
|
||||
pushNewGame: ({ opponentName, myColor, board, message }) => ifNot(games.isPushingNewGame) &&
|
||||
doPushing('/api/gameproposal', 'POST', { opponentName, opponentColor: Color.opposite(myColor), board, message }, {
|
||||
onPushing: (isPushingNewGame) => dispatchGames({ type: 'next', isPushingNewGame }),
|
||||
onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [game, ...games.gamesList], newGame: gamesInitialState.newGame })
|
||||
}),
|
||||
|
||||
pushGameProposalCancel: ({ uuid }) => ifNot(games.isPushingGameProposalCancel) &&
|
||||
doPushing(`/api/gameproposal/${uuid}/cancel`, 'PUT', null, {
|
||||
onPushing: (isPushingGameProposalCancel) => dispatchGames({ type: 'next', isPushingGameProposalCancel }),
|
||||
onSuccess: (canceledGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(canceledGame), proposal: gamesInitialState.proposal })
|
||||
}),
|
||||
|
||||
pushGameProposalReject: ({ uuid }) => ifNot(games.isPushingGameProposalReject) &&
|
||||
doPushing(`/api/gameproposal/${uuid}/reject`, 'PUT', null, {
|
||||
onPushing: (isPushingGameProposalReject) => dispatchGames({ type: 'next', isPushingGameProposalReject }),
|
||||
onSuccess: (rejectedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(rejectedGame), proposal: gamesInitialState.proposal })
|
||||
}),
|
||||
|
||||
pushGameProposalAccept: ({ uuid }) => ifNot(games.isPushingGameProposalAccept) &&
|
||||
doPushing(`/api/gameproposal/${uuid}/accept`, 'PUT', null, {
|
||||
onPushing: (isPushingGameProposalAccept) => dispatchGames({ type: 'next', isPushingGameProposalAccept }),
|
||||
onSuccess: (acceptedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(acceptedGame), proposal: gamesInitialState.proposal })
|
||||
}),
|
||||
|
||||
pushGameSurrender: ({ uuid }) => ifNot(games.isPushingGameSurrender) &&
|
||||
doPushing(`/api/game/${uuid}/surrender`, 'PUT', null, {
|
||||
onPushing: (isPushingGameSurrender) => dispatchGames({ type: 'next', isPushingGameSurrender }),
|
||||
onSuccess: (finishedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(finishedGame), active: gamesInitialState.active })
|
||||
}),
|
||||
|
||||
pushGameDrawRequest: ({ uuid }) => ifNot(games.isPushingGameDrawRequest) &&
|
||||
doPushing(`/api/game/${uuid}/drawreq`, 'PUT', null, {
|
||||
onPushing: (isPushingGameDrawRequest) => dispatchGames({ type: 'next', isPushingGameDrawRequest }),
|
||||
onSuccess: (drawReqGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawReqGame), active: gamesInitialState.active })
|
||||
}),
|
||||
|
||||
pushGameDrawAccept: ({ uuid }) => ifNot(games.isPushingGameDrawAccept) &&
|
||||
doPushing(`/api/game/${uuid}/drawacc`, 'PUT', null, {
|
||||
onPushing: (isPushingGameDrawAccept) => dispatchGames({ type: 'next', isPushingGameDrawAccept }),
|
||||
onSuccess: (drawAccGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawAccGame), active: gamesInitialState.active })
|
||||
}),
|
||||
|
||||
pushGameDrawReject: ({ uuid }) => ifNot(games.isPushingGameDrawReject) &&
|
||||
doPushing(`/api/game/${uuid}/drawrej`, 'PUT', null, {
|
||||
onPushing: (isPushingGameDrawReject) => dispatchGames({ type: 'next', isPushingGameDrawReject }),
|
||||
onSuccess: (drawRejGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawRejGame), active: gamesInitialState.active })
|
||||
}),
|
||||
|
||||
pushGameMove: ({ uuid, move, message }) => ifNot(games.isPushingGameMove) &&
|
||||
doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, {
|
||||
onPushing: (isPushingGameMove) => dispatchGames({ type: 'next', isPushingGameMove }),
|
||||
onSuccess: (game) => dispatchGames({ type: 'next', gamesList: games.nextGame(game), active: gamesInitialState.active })
|
||||
}),
|
||||
state,
|
||||
guide,
|
||||
dispatchGuide,
|
||||
api: {
|
||||
useGamesPolling,
|
||||
pushNewGame,
|
||||
pushGameProposalAccept,
|
||||
pushGameProposalReject,
|
||||
pushGameProposalCancel,
|
||||
pushGameSurrender,
|
||||
pushGameDrawRequest,
|
||||
pushGameDrawAccept,
|
||||
pushGameDrawReject,
|
||||
pushGameMove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ifNot(expression) {
|
||||
return !expression;
|
||||
}
|
@ -1,22 +1,23 @@
|
||||
import { usePolling } from "../hook/api";
|
||||
import { useLeaderboardStateReducer, useLeaderboardGuideReducer } from '../reducer/leaderboard';
|
||||
|
||||
export default function useLeaderboardApi(leaderboardReducer, config) {
|
||||
const [leaderboard, dispatchLeaderboard] = leaderboardReducer;
|
||||
export default function useLeaderboardApi() {
|
||||
const [state, dispatchState] = useLeaderboardStateReducer();
|
||||
const [guide, dispatchGuide] = useLeaderboardGuideReducer();
|
||||
|
||||
const usePollingTable = () => {
|
||||
const onSuccess = (table) => {
|
||||
dispatchLeaderboard({ type: 'next', table });
|
||||
}
|
||||
const useTablePolling = (config) => {
|
||||
const onPolling = (isPolling) => dispatchGuide({ type: 'next', isPolling });
|
||||
const onSuccess = (table) => dispatchState({type: 'next', table });
|
||||
|
||||
const isPollingTable = usePolling('/api/leaderboard', onSuccess, config.intervalMode(300));
|
||||
if (leaderboard.isPollingTable !== isPollingTable) {
|
||||
dispatchLeaderboard({ type: 'next', isPollingTable });
|
||||
}
|
||||
|
||||
return leaderboard;
|
||||
usePolling('/api/leaderboard', { onSuccess, onPolling }, config.intervalMode(300));
|
||||
}
|
||||
|
||||
return {
|
||||
pollTable: usePollingTable
|
||||
state,
|
||||
guide,
|
||||
dispatchGuide,
|
||||
api: {
|
||||
useTablePolling
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
import { usePolling } from "../hook/api";
|
||||
import { useUserStateReducer } from "../reducer/user";
|
||||
|
||||
export default function useUserApi(userReducer) {
|
||||
const [user, dispatchUser] = userReducer;
|
||||
export default function useUserApi() {
|
||||
const [state, dispatchState] = useUserStateReducer();
|
||||
|
||||
const useGetUser = () => {
|
||||
const onSuccess = (userJson) => {
|
||||
dispatchUser({ type: "parse", userJson });
|
||||
dispatchState({ type: "parse", userJson });
|
||||
}
|
||||
|
||||
usePolling('/api/user', onSuccess); // <<-- fetch once
|
||||
|
||||
return user;
|
||||
usePolling('/api/user', { onSuccess }); // <<-- fetch once
|
||||
}
|
||||
|
||||
return {
|
||||
getUser: useGetUser
|
||||
state,
|
||||
api : {
|
||||
useGetUser
|
||||
}
|
||||
}
|
||||
}
|
@ -74,10 +74,7 @@ export function Player({ color, name }) {
|
||||
export function Board({ board, flip, onStoneClick, onStoneMove }) {
|
||||
const [[moveId, moveX, moveY], setMove] = useState([0, 0, 0]);
|
||||
|
||||
if (!board)
|
||||
board = [];
|
||||
|
||||
const isInteractive = (typeof onStoneClick === 'function' || typeof onStoneMove === 'function') ? ' interactive' : '';
|
||||
const isInteractive = (board && (typeof onStoneClick === 'function' || typeof onStoneMove === 'function')) ? ' interactive' : '';
|
||||
|
||||
const WhiteTile = ({ id }) => {
|
||||
const stone = board[id];
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { GamesContext } from '../context/games';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { GamesStateContext, GamesGuideContext } from '../context/games';
|
||||
import { NavLink, Routes, Route } from 'react-router-dom';
|
||||
|
||||
import NewGame from './games/view/NewGame';
|
||||
@ -12,67 +12,75 @@ import Counter from '../components/Counter';
|
||||
|
||||
import './Games.css';
|
||||
|
||||
export default function Games({ context: { gamesReducer, gamesApi }, players }) {
|
||||
const [games, dispatchGames] = gamesReducer;
|
||||
export default function Games({ games, players }) {
|
||||
const gamesState = games.state;
|
||||
const gamesDispatchGuide = games.dispatchGuide;
|
||||
|
||||
useEffect(() => {
|
||||
gamesDispatchGuide({ type: 'sync', gamesState });
|
||||
}, [gamesState, gamesDispatchGuide]);
|
||||
|
||||
return (
|
||||
<GamesContext.Provider value={games} >
|
||||
<GamesStateContext.Provider value={gamesState} >
|
||||
<GamesGuideContext.Provider value={games.guide} >
|
||||
<div className='Games'>
|
||||
|
||||
<div className='left-side'>
|
||||
<ViewSelector games={games} />
|
||||
<ViewProvider dispatchGames={dispatchGames} players={players} />
|
||||
<ViewSelector />
|
||||
<ViewProvider dispatchGuide={gamesDispatchGuide} players={players} />
|
||||
</div>
|
||||
|
||||
<div className='right-side'>
|
||||
<ActionPanel gamesApi={gamesApi} />
|
||||
<ActionPanel gamesApi={games.api} />
|
||||
<GameBoardRoutes dispatchGuide={gamesDispatchGuide} gamesApi={games.api} username={players.user.name} />
|
||||
<Message2OpponentRoutes dispatchGuide={gamesDispatchGuide} />
|
||||
{/* <GameMessage /> */}
|
||||
<GameBoardRoutes gamesReducer={gamesReducer} gamesApi={gamesApi} username={players.currentUser} />
|
||||
<Message2Opponent dispatchGames={dispatchGames} />
|
||||
</div>
|
||||
|
||||
</div >
|
||||
</GamesContext.Provider>
|
||||
</GamesGuideContext.Provider>
|
||||
</GamesStateContext.Provider>
|
||||
)
|
||||
};
|
||||
|
||||
function ViewSelector({ games }) {
|
||||
const awaiting = countGames(games.gamesList);
|
||||
function ViewSelector() {
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
return (
|
||||
<nav className='ViewSelector' >
|
||||
<div className='Container' >
|
||||
<NavLink to='new'>New</NavLink>
|
||||
<NavLink to='proposal'>Proposal<Counter number={awaiting.proposals} /></NavLink>
|
||||
<NavLink to='active' >Active<Counter number={awaiting.active} /></NavLink>
|
||||
<NavLink to='proposal'>Proposal<Counter number={guide.awaiting.proposal} /></NavLink>
|
||||
<NavLink to='active' >Active<Counter number={guide.awaiting.active} /></NavLink>
|
||||
<NavLink to='archive' >Archive</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function ViewProvider({ dispatchGames, players }) {
|
||||
function ViewProvider({ dispatchGuide, players }) {
|
||||
|
||||
return (
|
||||
<div className='ViewProvider'>
|
||||
<Routes>
|
||||
|
||||
<Route path='new' element={
|
||||
<NewGame setPlayers={(opponentName, myColor) => dispatchGames({ type: 'nextNewGame', opponentName, myColor })}
|
||||
<NewGame
|
||||
players={players}
|
||||
setPlayers={(opponentName, myColor) => dispatchGuide({ type: 'nextNewGame', opponentName, myColor })}
|
||||
/>
|
||||
} />
|
||||
|
||||
<Route path='proposal' element={
|
||||
<GameProposalSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextProposal', selectedUUID })} />
|
||||
<GameProposalSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', proposal: uuid })} />
|
||||
} />
|
||||
|
||||
<Route path='active' element={
|
||||
<ActiveGameSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextActive', selectedUUID })} />
|
||||
<ActiveGameSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', active: uuid })} />
|
||||
} />
|
||||
|
||||
<Route path='archive' element={
|
||||
<GameArchiveSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextArchive', selectedUUID })} />
|
||||
<GameArchiveSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', archive: uuid })} />
|
||||
} />
|
||||
|
||||
</Routes>
|
||||
@ -81,25 +89,57 @@ function ViewProvider({ dispatchGames, players }) {
|
||||
}
|
||||
|
||||
function ActionPanel({ gamesApi }) {
|
||||
const games = useContext(GamesStateContext);
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
const fromUUID = (uuid) => (!uuid) ? [{}, null] :
|
||||
[
|
||||
games.find((game) => game.uuid === uuid) || {}, // game
|
||||
guide.UUIDpushing[uuid] // pushing
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='ActionPanel'>
|
||||
<Routes>
|
||||
|
||||
<Route path='new' element={
|
||||
<Create onClick={(reqParams) => gamesApi.pushNewGame(reqParams)} />
|
||||
<Create
|
||||
getGame={() => [guide.newGame, guide.newGame.isPushing]}
|
||||
onClick={(req) => gamesApi.pushNewGame(req)}
|
||||
/>
|
||||
} />
|
||||
|
||||
<Route path='proposal' element={[
|
||||
<Accept key={1} onClick={(uuid) => gamesApi.pushGameProposalAccept({ uuid })} />,
|
||||
<Reject key={2} onClick={(uuid) => gamesApi.pushGameProposalReject({ uuid })} />,
|
||||
<Cancel key={3} onClick={(uuid) => gamesApi.pushGameProposalCancel({ uuid })} />
|
||||
<Accept key={1}
|
||||
getGame={() => fromUUID(guide.selectedUUID.proposal)}
|
||||
onClick={(req) => gamesApi.pushGameProposalAccept(req)}
|
||||
/>,
|
||||
<Reject key={2}
|
||||
getGame={() => fromUUID(guide.selectedUUID.proposal)}
|
||||
onClick={(req) => gamesApi.pushGameProposalReject(req)}
|
||||
/>,
|
||||
<Cancel key={3}
|
||||
getGame={() => fromUUID(guide.selectedUUID.proposal)}
|
||||
onClick={(req) => gamesApi.pushGameProposalCancel(req)}
|
||||
/>
|
||||
]} />
|
||||
|
||||
<Route path='active' element={[
|
||||
<DrawRequest key={1} onClick={(uuid) => gamesApi.pushGameDrawRequest({ uuid })} />,
|
||||
<DrawAccept key={2} onClick={(uuid) => gamesApi.pushGameDrawAccept({ uuid })} />,
|
||||
<DrawReject key={3} onClick={(uuid) => gamesApi.pushGameDrawReject({ uuid })} />,
|
||||
<Surrender key={4} onClick={(uuid) => gamesApi.pushGameSurrender({ uuid })} />
|
||||
<DrawRequest key={1}
|
||||
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||
onClick={(req) => gamesApi.pushGameDrawRequest(req)}
|
||||
/>,
|
||||
<DrawAccept key={2}
|
||||
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||
onClick={(req) => gamesApi.pushGameDrawAccept(req)}
|
||||
/>,
|
||||
<DrawReject key={3}
|
||||
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||
onClick={(req) => gamesApi.pushGameDrawReject(req)}
|
||||
/>,
|
||||
<Surrender key={4}
|
||||
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||
onClick={(req) => gamesApi.pushGameSurrender(req)} />
|
||||
]} />
|
||||
|
||||
<Route path='archive' element={[
|
||||
@ -112,50 +152,80 @@ function ActionPanel({ gamesApi }) {
|
||||
)
|
||||
}
|
||||
|
||||
function GameBoardRoutes({ gamesReducer, gamesApi, username }) {
|
||||
const [games, dispatchGames] = gamesReducer;
|
||||
function GameBoardRoutes({ dispatchGuide, gamesApi, username }) {
|
||||
const games = useContext(GamesStateContext);
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
const fromUUID = (uuid) => (!uuid) ? [{}, null] :
|
||||
[
|
||||
games.find((game) => game.uuid === uuid) || {}, // game
|
||||
guide.UUIDpushing[uuid] // pushing
|
||||
];
|
||||
|
||||
const onStoneClick = (uuid, cellId) => {
|
||||
let board = { ...games.newGame.board };
|
||||
let board = { ...guide.newGame.board };
|
||||
board[cellId] = nextStone(board[cellId]);
|
||||
dispatchGames({ type: 'nextNewGame', board });
|
||||
dispatchGuide({ type: 'nextNewGame', board });
|
||||
}
|
||||
|
||||
const onStoneMove = (uuid, move) => gamesApi.pushGameMove({ uuid, move, message: games.active.message });
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path='new' element={<GameBoard username={username} onStoneClick={onStoneClick} />} />
|
||||
<Route path='proposal' element={<GameBoard username={username} />} />
|
||||
<Route path='active' element={<GameBoard username={username} onStoneMove={onStoneMove} />} />
|
||||
<Route path='archive' element={<GameBoard username={username} />} />
|
||||
|
||||
<Route path='new' element={
|
||||
<GameBoard
|
||||
username={username}
|
||||
getGame={() => [guide.newGame, null]}
|
||||
onStoneClick={onStoneClick}
|
||||
/>
|
||||
} />
|
||||
|
||||
<Route path='proposal' element={
|
||||
<GameBoard
|
||||
username={username}
|
||||
getGame={() => fromUUID(guide.selectedUUID.proposal)}
|
||||
/>
|
||||
} />
|
||||
|
||||
<Route path='active' element={
|
||||
<GameBoard
|
||||
username={username}
|
||||
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||
onStoneMove={(uuid, move) => gamesApi.pushGameMove({ uuid, move, message: guide.UUIDmessage[uuid] })}
|
||||
/>
|
||||
} />
|
||||
|
||||
<Route path='archive' element={
|
||||
<GameBoard
|
||||
username={username}
|
||||
getGame={() => fromUUID(guide.selectedUUID.archive)}
|
||||
/>
|
||||
} />
|
||||
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
function countGames(gamesList) {
|
||||
function Message2OpponentRoutes({ dispatchGuide }) {
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
var awaiting = {
|
||||
proposals: 0,
|
||||
active: 0
|
||||
};
|
||||
const getMessage = (uuid) => !uuid ? undefined : // <<-- appears as inactive message field
|
||||
guide.UUIDmessage[uuid] || '';
|
||||
|
||||
if (!gamesList)
|
||||
return awaiting;
|
||||
return (
|
||||
<Routes>
|
||||
|
||||
for (const game of gamesList) {
|
||||
switch (game.status) {
|
||||
case 'GAME_PROPOSAL_WAIT_FOR_YOU':
|
||||
awaiting.proposals++;
|
||||
break;
|
||||
case 'GAME_BOARD_WAIT_FOR_YOU':
|
||||
case 'DRAW_REQUEST_WAIT_FOR_YOU':
|
||||
awaiting.active++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
<Route path='new' element={
|
||||
<Message2Opponent
|
||||
getMessage={() => guide.newGame.message}
|
||||
setMessage={(message) => dispatchGuide({ type: 'nextNewGame', message })} />
|
||||
} />
|
||||
|
||||
return awaiting;
|
||||
<Route path='active' element={
|
||||
<Message2Opponent
|
||||
getMessage={() => getMessage(guide.selectedUUID.active)}
|
||||
setMessage={(message) => dispatchGuide({ type: 'UUIDmessage', message, uuid: guide.selectedUUID.active })} />
|
||||
} />
|
||||
|
||||
</Routes>
|
||||
)
|
||||
}
|
@ -1,26 +1,22 @@
|
||||
import './Leaderboard.css';
|
||||
import React from "react"
|
||||
import Loading from '../components/Loading';
|
||||
import React from 'react';
|
||||
|
||||
export default function Leaderboard({ players }) {
|
||||
const tableRows = Object.keys(players.leaderboard).map(name => {
|
||||
var rank = players.leaderboard[name];
|
||||
|
||||
const table = players.leaderboard.table;
|
||||
if (table == null)
|
||||
return <Loading />
|
||||
|
||||
const tableRows = Object.keys(table).map(playerName => {
|
||||
var rank = table[playerName];
|
||||
|
||||
return <tr key={playerName} className={players.isCurrentUser(playerName) && 'currentuser'}>
|
||||
<td>{playerName}</td>
|
||||
return (
|
||||
<tr key={name} className={players.user.isCurrentUser(name) && 'currentuser'}>
|
||||
<td>{name}</td>
|
||||
<td>{rank.total}</td>
|
||||
<td>{rank.victory}</td>
|
||||
<td>{rank.draw}</td>
|
||||
</tr>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="Leaderboard">
|
||||
<div className='Leaderboard'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -1,28 +1,33 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { GamesContext } from '../../context/games';
|
||||
import React from 'react';
|
||||
import { Color } from '../../components/Checkers';
|
||||
import Wobler from '../../components/Wobler';
|
||||
import './ActionPanel.css'
|
||||
|
||||
/*
|
||||
* NewGame actoins
|
||||
*/
|
||||
export function Create({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
export function Create({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
const hasPlayers = (game.opponentName && game.myColor) ? true : '';
|
||||
|
||||
const hasPlayers = games.newGame.opponentName && games.newGame.myColor;
|
||||
|
||||
const validateNewGame = () => {
|
||||
const validate = () => {
|
||||
if (!hasPlayers)
|
||||
return alert("You have to select an opponent");
|
||||
|
||||
onClick(games.newGame);
|
||||
if (isPushing)
|
||||
return; // busy
|
||||
|
||||
onClick({
|
||||
opponentName: game.opponentName,
|
||||
opponentColor: Color.opposite(game.myColor), // TODO: by fixing this i can simply return GAME
|
||||
board: game.board,
|
||||
message: game.message
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={'Create' + (hasPlayers ? ' ready' : '')}
|
||||
onClick={validateNewGame}
|
||||
>
|
||||
<Wobler text="Create" dance={games.isPushingNewGame} />
|
||||
<button className={'Create' + (hasPlayers && ' ready')} onClick={() => validate()} >
|
||||
<Wobler text="Create" dance={isPushing} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -30,51 +35,71 @@ export function Create({ onClick }) {
|
||||
/*
|
||||
* GameProposal actions
|
||||
*/
|
||||
export function Accept({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU') ? true : '';
|
||||
|
||||
export function Accept({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
const validate = () => {
|
||||
if (!hasGame)
|
||||
return alert('You have to select pending GameProposal');
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
|
||||
if (isPushing)
|
||||
return; // busy
|
||||
|
||||
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
onClick({ uuid: game.uuid });
|
||||
}
|
||||
|
||||
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
return (
|
||||
<button className={'Accept' + (isReady && ' ready')}
|
||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
|
||||
>
|
||||
<Wobler text="Accept" dance={games.isPushingGameProposalAccept} />
|
||||
<button className={'Accept' + (hasGame && ' ready')} onClick={() => validate()}>
|
||||
<Wobler text="Accept" dance={isPushing === 'accept'} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function Reject({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
export function Reject({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU') ? true : '';
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
|
||||
const validate = () => {
|
||||
if (!hasGame)
|
||||
return alert('You have to select some GameProposal');
|
||||
|
||||
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
if (isPushing)
|
||||
return; // busy
|
||||
|
||||
onClick({ uuid: game.uuid });
|
||||
}
|
||||
|
||||
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
return (
|
||||
<button className={'Reject' + (isReady && ' ready')}
|
||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
|
||||
>
|
||||
<Wobler text="Reject" dance={games.isPushingGameProposalReject} />
|
||||
<button className={'Reject' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||
<Wobler text="Reject" dance={isPushing === 'reject'} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function Cancel({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
export function Cancel({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT' ? true : '';
|
||||
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
return;
|
||||
|
||||
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT') ? true : '';
|
||||
|
||||
const validate = () => {
|
||||
if (!hasGame)
|
||||
return alert('You have to select pending GameProposal');
|
||||
|
||||
if (isPushing)
|
||||
return; // busy
|
||||
|
||||
onClick({ uuid: game.uuid });
|
||||
}
|
||||
|
||||
if (selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
return (
|
||||
<button className={'Cancel' + (isReady && ' ready')}
|
||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select pending GameProposal')}
|
||||
>
|
||||
<Wobler text="Cancel" dance={games.isPushingGameProposalCancel} />
|
||||
<button className={'Cancel' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||
<Wobler text="Cancel" dance={isPushing === 'cancel'} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -82,83 +107,82 @@ export function Cancel({ onClick }) {
|
||||
/*
|
||||
* Game actions
|
||||
*/
|
||||
export function Surrender({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
export function Surrender({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
||||
const gameStatus = selectedGame?.status;
|
||||
const isReady = (gameStatus === 'GAME_BOARD_WAIT_FOR_OPPONENT' || gameStatus === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
|
||||
|
||||
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU' || gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||
if (game.status === 'DRAW_REQUEST_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||
return; // You shall not surrender if there is an active tie negotiations
|
||||
|
||||
const hasGame = (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
|
||||
|
||||
const validate = () => {
|
||||
if (!hasGame)
|
||||
return alert('You have to select a Game');
|
||||
|
||||
if (isPushing)
|
||||
return; // busy
|
||||
|
||||
onClick({ uuid: game.uuid });
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={'Surrender' + (isReady && ' ready')}
|
||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select a game')}
|
||||
>
|
||||
<Wobler text="Surrender" dance={games.isPushingGameSurrender} />
|
||||
</button>
|
||||
<button className={'Surrender' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||
<Wobler text="Surrender" dance={isPushing === 'surrender'} />
|
||||
</ button>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Game actions: Draw
|
||||
*/
|
||||
export function DrawRequest({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
export function DrawRequest({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
||||
const gameStatus = selectedGame?.status;
|
||||
const isReady = gameStatus === 'GAME_BOARD_WAIT_FOR_YOU' ? true : '';
|
||||
|
||||
const checkStatus = () => {
|
||||
if (!selectedGame)
|
||||
return alert('You have to select a game');
|
||||
|
||||
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||
return alert('A draw was alredy offered to the opponent');
|
||||
|
||||
if (!isReady)
|
||||
return alert('You can ask for a draw only during your turn');
|
||||
|
||||
onClick(selectedGame.uuid);
|
||||
}
|
||||
|
||||
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
if (game.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
return; // You can not send counter draw request
|
||||
|
||||
const hasGame = (game.status === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
|
||||
|
||||
const validate = () => {
|
||||
if (!hasGame)
|
||||
return alert('You can ask for a draw only during your turn in a Game');
|
||||
|
||||
if (game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||
return alert('A draw was alredy offered to the opponent');
|
||||
|
||||
onClick({ uuid: game.uuid });
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<button className={'Draw' + (isReady && ' ready')} onClick={() => checkStatus()} >
|
||||
<Wobler text="Draw?" dance={games.isPushingGameDrawRequest} />
|
||||
<button className={'Draw' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||
<Wobler text="Draw?" dance={isPushing === 'draw_request'} />
|
||||
</button >
|
||||
)
|
||||
}
|
||||
|
||||
export function DrawAccept({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
export function DrawAccept({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
||||
const gameStatus = selectedGame?.status;
|
||||
if (game.status !== 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
return;
|
||||
|
||||
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
return (
|
||||
<button className='Draw accept' onClick={() => onClick(selectedGame.uuid)} >
|
||||
<Wobler text="Draw accept" dance={games.isPushingGameDrawAccept} />
|
||||
<button className='Draw accept' onClick={() => onClick({ uuid: game.uuid })} >
|
||||
<Wobler text="Draw accept" dance={isPushing === 'draw_accept'} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function DrawReject({ onClick }) {
|
||||
const games = useContext(GamesContext);
|
||||
export function DrawReject({ getGame, onClick }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
||||
if (game.status !== 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
return;
|
||||
|
||||
if (selectedGame?.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||
return (
|
||||
<button className='Draw reject' onClick={() => onClick(selectedGame.uuid)} >
|
||||
<Wobler text="Reject" dance={games.isPushingGameDrawReject} />
|
||||
<button className='Draw reject' onClick={() => onClick({ uuid: game.uuid })}>
|
||||
<Wobler text="Reject" dance={isPushing === 'draw_reject'} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -166,13 +190,10 @@ export function DrawReject({ onClick }) {
|
||||
/*
|
||||
* GameArchive actions
|
||||
*/
|
||||
|
||||
export function Backward() {
|
||||
|
||||
return <button className='Backward' disabled>Backward</button>
|
||||
}
|
||||
|
||||
export function Forward() {
|
||||
|
||||
return <button className='Forward' disabled>Forward</button>
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useLocation, matchPath } from 'react-router-dom';
|
||||
import { GamesContext } from '../../context/games';
|
||||
import React from 'react';
|
||||
|
||||
import { Color, Player, Board } from '../../components/Checkers';
|
||||
import './GameBoard.css';
|
||||
|
||||
export default function GameBoard({ username, onStoneClick, onStoneMove }) {
|
||||
const game = useSelectedGame() || {};
|
||||
export default function GameBoard({ username, getGame, onStoneClick, onStoneMove }) {
|
||||
const [game, isPushing] = getGame();
|
||||
|
||||
const myName = game.opponentName ? username : '';
|
||||
const opponentColor = Color.opposite(game.myColor);
|
||||
@ -15,37 +13,26 @@ export default function GameBoard({ username, onStoneClick, onStoneMove }) {
|
||||
const optionalOnStoneClick = (typeof onStoneClick !== 'function') ? null :
|
||||
(cellId) => onStoneClick(game.uuid, cellId);
|
||||
|
||||
const optionalOnStoneMove = (typeof onStoneMove !== 'function') ? null :
|
||||
const optionalOnStoneMove = (typeof onStoneMove !== 'function' || isPushing) ? null :
|
||||
(move) => onStoneMove(game.uuid, move);
|
||||
|
||||
return (
|
||||
<div className='GameBoard'>
|
||||
<Player color={opponentColor || Color.black} name={game.opponentName} />
|
||||
<Board board={game.board} flip={flipBoard}
|
||||
<Player
|
||||
color={opponentColor || Color.black}
|
||||
name={game.opponentName}
|
||||
/>
|
||||
<Board
|
||||
board={game.board || []}
|
||||
flip={flipBoard}
|
||||
onStoneClick={optionalOnStoneClick}
|
||||
onStoneMove={optionalOnStoneMove}
|
||||
/>
|
||||
<Player color={game.myColor || Color.white} name={myName} />
|
||||
{game.isPushingGameMove ? <span>Moving...</span> : null /* TODO: isPushing shall be stored per game. curernty it is global indicator */}
|
||||
<Player
|
||||
color={game.myColor || Color.white}
|
||||
name={myName}
|
||||
/>
|
||||
{(isPushing === 'move') ? <span>Moving...</span> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSelectedGame() {
|
||||
const games = useContext(GamesContext);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
if (matchPath('/games/new', pathname))
|
||||
return games.newGame;
|
||||
|
||||
if (matchPath('/games/proposal', pathname))
|
||||
return games.findGame({ uuid: games.proposal.selectedUUID });
|
||||
|
||||
if (matchPath('/games/active', pathname))
|
||||
return games.findGame({ uuid: games.active.selectedUUID });
|
||||
|
||||
if (matchPath('/games/archive', pathname))
|
||||
return games.findGame({ uuid: games.archive.selectedUUID });
|
||||
|
||||
return undefined;
|
||||
}
|
@ -1,65 +1,38 @@
|
||||
import React, { useContext, useRef, useState } from 'react';
|
||||
import { useLocation, matchPath } from 'react-router-dom';
|
||||
import { GamesContext } from '../../context/games';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
export default function Message2Opponent({ dispatchGames }) {
|
||||
const games = useContext(GamesContext);
|
||||
const { pathname } = useLocation();
|
||||
export default function Message2Opponent({ getMessage, setMessage }) {
|
||||
const [value, setValue] = useState('');
|
||||
const syncTimeoutRef = useRef(null);
|
||||
|
||||
if (matchPath('/games/archive', pathname))
|
||||
return; // Shhh.. no chatting in the archives!
|
||||
|
||||
if (matchPath('/games/proposal', pathname))
|
||||
return; // TODO: Enable GameProposal messages, as soon as it has been supported by the server side
|
||||
|
||||
if (syncTimeoutRef.current === null) { // <<--- Absorb external value ONLY if there is no scheduled sync
|
||||
var externalValue = '';
|
||||
|
||||
if (matchPath('/games/new', pathname))
|
||||
externalValue = games.newGame.message;
|
||||
else if (matchPath('/games/proposal', pathname))
|
||||
externalValue = games.proposal.message;
|
||||
else if (matchPath('/games/active', pathname))
|
||||
externalValue = games.active.message;
|
||||
|
||||
if (value !== externalValue)
|
||||
setValue(externalValue);
|
||||
const message = getMessage();
|
||||
if (value !== message && syncTimeoutRef.current === null) {
|
||||
// Absorb external value ONLY if there is no scheduled sync
|
||||
setValue(message);
|
||||
}
|
||||
|
||||
const disabled = message === undefined;
|
||||
|
||||
/* --- */
|
||||
|
||||
const sync = (message) => {
|
||||
const sync = (nextMessage) => {
|
||||
syncTimeoutRef.current = null;
|
||||
|
||||
if (matchPath('/games/new', pathname))
|
||||
return dispatchGames({ type: 'nextNewGame', message });
|
||||
|
||||
if (matchPath('/games/proposal', pathname))
|
||||
return dispatchGames({ type: 'nextProposal', message });
|
||||
|
||||
if (matchPath('/games/active', pathname))
|
||||
return dispatchGames({ type: 'nextActive', message });
|
||||
|
||||
console.warn('unknown path');
|
||||
setMessage(nextMessage);
|
||||
}
|
||||
|
||||
const update = (value) => {
|
||||
setValue(value);
|
||||
|
||||
if (syncTimeoutRef.current)
|
||||
clearTimeout(syncTimeoutRef.current); // <<--- Cancel previous sync
|
||||
|
||||
syncTimeoutRef.current = setTimeout(() => sync(value), 500);
|
||||
}
|
||||
|
||||
return (
|
||||
<input className='Message2Opponent'
|
||||
placeholder='Message'
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
value={value || ''}
|
||||
maxLength={150}
|
||||
onChange={e => update(e.target.value)}
|
||||
onChange={(e) => update(e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
import './GameSelector.css';
|
||||
import React, { useContext } from 'react';
|
||||
import { GamesContext } from '../../../context/games';
|
||||
import { GamesStateContext, GamesGuideContext } from '../../../context/games';
|
||||
|
||||
import { Board, Color, Player } from '../../../components/Checkers';
|
||||
import Loading from '../../../components/Loading';
|
||||
import Counter from '../../../components/Counter';
|
||||
|
||||
export function GameProposalSelector({ onSelect }) {
|
||||
const games = useContext(GamesContext);
|
||||
if (games.gamesList === null)
|
||||
return <Loading />
|
||||
const games = useContext(GamesStateContext);
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
const isSelected = (uuid) => uuid === games.proposal.selectedUUID;
|
||||
const isSelected = (uuid) => uuid === guide.selectedUUID.proposal;
|
||||
|
||||
const onClick = (uuid) => {
|
||||
if (isSelected(uuid))
|
||||
@ -20,12 +19,19 @@ export function GameProposalSelector({ onSelect }) {
|
||||
onSelect(uuid);
|
||||
}
|
||||
|
||||
const yoursList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU')
|
||||
const yoursList = games.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU')
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
const opponentsList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
const opponentsList = games.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
if (yoursList.length === 0 && opponentsList.length === 0) {
|
||||
if (guide.isPolling)
|
||||
return <Loading />
|
||||
else
|
||||
return <>There are no pending GameProposals..</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='GameSelector'>
|
||||
{yoursList}
|
||||
@ -36,11 +42,10 @@ export function GameProposalSelector({ onSelect }) {
|
||||
}
|
||||
|
||||
export function ActiveGameSelector({ onSelect }) {
|
||||
const games = useContext(GamesContext);
|
||||
if (games.gamesList === null)
|
||||
return <Loading />
|
||||
const games = useContext(GamesStateContext);
|
||||
const guide = useContext(GamesGuideContext);
|
||||
|
||||
const isSelected = (uuid) => uuid === games.active.selectedUUID;
|
||||
const isSelected = (uuid) => uuid === guide.selectedUUID.active;
|
||||
|
||||
const onClick = (uuid) => {
|
||||
if (isSelected(uuid))
|
||||
@ -49,12 +54,19 @@ export function ActiveGameSelector({ onSelect }) {
|
||||
onSelect(uuid);
|
||||
}
|
||||
|
||||
const yoursList = games.gamesList.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_YOU'))
|
||||
const yoursList = games.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_YOU'))
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
const opponentsList = games.gamesList.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT'))
|
||||
const opponentsList = games.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT'))
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
if (yoursList.length === 0 && opponentsList.length === 0) {
|
||||
if (guide.isPolling)
|
||||
return <Loading />
|
||||
else
|
||||
return <>There are no pending Games..</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='GameSelector'>
|
||||
{yoursList}
|
||||
@ -65,11 +77,10 @@ export function ActiveGameSelector({ onSelect }) {
|
||||
}
|
||||
|
||||
export function GameArchiveSelector({ onSelect }) {
|
||||
const games = useContext(GamesContext);
|
||||
if (games.gamesList === null)
|
||||
return <Loading />
|
||||
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 => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
const canceledList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
|
||||
const canceledList = games.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
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 => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
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 => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
const drawList = games.gamesList.filter(game => game.status === 'GAME_RESULT_DRAW')
|
||||
const drawList = games.filter(game => game.status === 'GAME_RESULT_DRAW')
|
||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||
|
||||
if (rejectedList.length === 0 && canceledList.length === 0 && victoryList.length === 0 && defeatList.length === 0 && drawList.length === 0) {
|
||||
if (guide.isPolling)
|
||||
return <Loading />
|
||||
else
|
||||
return <>Finished Games will be shown here..</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='GameSelector'>
|
||||
{rejectedList.length > 0 && <Separator counter={rejectedList.length}>rejected proposals</Separator>}
|
||||
@ -119,9 +137,15 @@ function Selectable({ game, selected, onClick }) {
|
||||
<div className={'Selectable' + (selected ? ' selected' : '')}
|
||||
onClick={() => onClick(game.uuid)}
|
||||
>
|
||||
<Board board={game.board} flip={flipBoard} />
|
||||
<Board
|
||||
board={game.board || []}
|
||||
flip={flipBoard}
|
||||
/>
|
||||
<div className='Message'>
|
||||
<Player color={opponentColor} name={opponentName} />
|
||||
<Player
|
||||
color={opponentColor}
|
||||
name={opponentName}
|
||||
/>
|
||||
<q>{game.message}</q>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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 newGame = useContext(GamesGuideContext).newGame;
|
||||
|
||||
const [whitePlayer, blackPlayer] = (() => {
|
||||
if (games.newGame.myColor === Color.white)
|
||||
return [players.currentUser, games.newGame.opponentName];
|
||||
if (newGame.myColor === Color.white)
|
||||
return [players.user.name, newGame.opponentName];
|
||||
|
||||
if (games.newGame.myColor === Color.black)
|
||||
return [games.newGame.opponentName, players.currentUser];
|
||||
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
|
||||
? [<option key='loading' value='…'>…loading</option>]
|
||||
: Object.keys(players.leaderboard.table).map(playerName =>
|
||||
<option key={playerName} value={playerName}>
|
||||
{players.isCurrentUser(playerName) ? 'You' : playerName}
|
||||
const nameOptions = !players.leaderboard ? [<option key='loading' value='…'>…loading</option>] :
|
||||
Object.keys(players.leaderboard).map(name =>
|
||||
<option key={name} value={name}>
|
||||
{players.user.isCurrentUser(name) ? 'You' : name}
|
||||
</option>)
|
||||
|
||||
const whiteOptions = Array(nameOptions)
|
||||
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>)
|
||||
const whiteOptions = Array(nameOptions);
|
||||
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>);
|
||||
|
||||
const blackOptions = Array(nameOptions)
|
||||
blackOptions.push(<option key='default' value=''>{'black player …'}</option>)
|
||||
const blackOptions = Array(nameOptions);
|
||||
blackOptions.push(<option key='default' value=''>{'black player …'}</option>);
|
||||
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 }) {
|
||||
@ -83,7 +86,7 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
|
||||
|
||||
onSuccess(content);
|
||||
}
|
||||
// } catch (err) {
|
||||
// } catch (err) {
|
||||
} finally {
|
||||
if (onPushing)
|
||||
onPushing(false);
|
||||
|
@ -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));
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -1,17 +1,24 @@
|
||||
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]);
|
||||
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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user