From ddd5d160a4429c0cb3780024ab32703c71e8d8b8 Mon Sep 17 00:00:00 2001 From: djmil Date: Tue, 14 Nov 2023 10:03:08 +0100 Subject: [PATCH 1/2] initial implemention --- webapp/src/api/games.js | 8 ++- webapp/src/container/Games.jsx | 16 +++-- .../src/container/games/Message2Opponent.jsx | 62 +++++++++++++++++++ webapp/src/container/games/action/Create.jsx | 2 +- webapp/src/hook/Location.js | 46 ++++++++++++++ webapp/src/hook/Previous.js | 24 ------- webapp/src/reducer/games.js | 9 ++- 7 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 webapp/src/container/games/Message2Opponent.jsx create mode 100644 webapp/src/hook/Location.js delete mode 100644 webapp/src/hook/Previous.js diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index 58b800d..a6d907c 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -22,7 +22,13 @@ export default function useGamesApi(gamesReducer, config) { pushNewGame: (reqParams) => doPushing('/api/gameproposal', 'POST', reqParams, { onPushing: (isPushingNewGame) => dispatchGames({ type: 'next', isPushingNewGame }), - onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [...games.gamesList, game] }) + onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [...games.gamesList, game], newGame: emptyNewGame }) }), } +} + +const emptyNewGame = { + whitePlayer: '', + blackPlayer: '', + message2opponent: '' } \ No newline at end of file diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index e94d41b..d9da38c 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -16,6 +16,7 @@ import Backward from './games/action/Backward'; import Forward from './games/action/Forward'; import GameBoard from './games/GameBoard'; +import Message2Opponent from './games/Message2Opponent'; import './Games.css'; @@ -27,14 +28,15 @@ export default function Games({ context: { gamesReducer, gamesApi }, players })
- +
- + + {/* - */} + */}
@@ -54,15 +56,19 @@ function ViewSelector() { ) } -function ViewProvider({ players, dispatchGames }) { +function ViewProvider({ gamesReducer, players }) { + const [/*games*/, dispatchGames] = gamesReducer; + return (
{ + dispatchGames({ type: 'nextNewGame', whitePlayer, blackPlayer }) + }} players={players} - onSelectPlayer={(whitePlayer, blackPlayer) => dispatchGames({ type: "next", newGame: { whitePlayer, blackPlayer } })} /> } /> diff --git a/webapp/src/container/games/Message2Opponent.jsx b/webapp/src/container/games/Message2Opponent.jsx new file mode 100644 index 0000000..1105e75 --- /dev/null +++ b/webapp/src/container/games/Message2Opponent.jsx @@ -0,0 +1,62 @@ +import React, { useContext, /* useEffect,*/ useRef, useState } from 'react'; +import { useLocation, matchPath } from 'react-router-dom'; +import { GamesContext } from '../../context/games'; +//import { useLocationChange } from '../../hook/Location'; + +export default function Message2Opponent({ dispatchGames }) { + const games = useContext(GamesContext); + const [message2opponent, setMessage2opponent] = useState(''); + const { pathname } = useLocation(); + const timeoutIdRef = useRef(null); + + if (timeoutIdRef.current === null) { + // sync: internal <- external + // Sync external and internsl states only in abnsence of a delay timer + console.log("check message", message2opponent); + if (matchPath('/games/new', pathname)) { + if (message2opponent !== games.newGame.message2opponent) + setMessage2opponent(games.newGame.message2opponent); + } + else + if (message2opponent !== '') { + setMessage2opponent(''); + } + } + + console.log('timeout.current', timeoutIdRef.current) + /* --- */ + + + const delayedSync = (message2opponent) => { + timeoutIdRef.current = null; + + // sync: internl -> external + if (matchPath('/games/new', pathname)) + return dispatchGames({ type: 'nextNewGame', message2opponent }); + + console.warn('unknown path'); + } + + const onChange = (value) => { + setMessage2opponent(value); + + if (timeoutIdRef.current) + clearTimeout(timeoutIdRef.current); // cancel previous sync + + timeoutIdRef.current = setTimeout(() => delayedSync(value), 500); + } + + + //var gpathname; + //useLocationChange(({ pathname }) => { + + + return ( + onChange(e.target.value)} + /> + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/action/Create.jsx b/webapp/src/container/games/action/Create.jsx index cddb347..0416a97 100644 --- a/webapp/src/container/games/action/Create.jsx +++ b/webapp/src/container/games/action/Create.jsx @@ -29,7 +29,7 @@ export default function Create({ isCurrentUser, pushNewGame }) { opponentName, opponentColor, board: null, // default board configuration - message: 'default NewGame req message' + message: games.newGame.message2opponent } pushNewGame(reqParams); diff --git a/webapp/src/hook/Location.js b/webapp/src/hook/Location.js new file mode 100644 index 0000000..75dd13c --- /dev/null +++ b/webapp/src/hook/Location.js @@ -0,0 +1,46 @@ +import { useRef, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export default function usePrevious(value) { + const ref = useRef(); + + useEffect(() => { + ref.current = value; + }); + + return ref.current; +} + +/* Usage example + +const useLocationChange = (action) => { + const location = useLocation(); + const prevLocation = usePrevious(location); + + useEffect(() => { + action(location, prevLocation); + }, [location]); +} + +*/ + +// runs action(location) on location, i.e. route, change +export const useLocationChange = (action) => { + const location = useLocation(); + const prevLocation = usePrevious(location); + + useEffect(() => { + if (location !== prevLocation) + action(location, prevLocation); + }, [action, location, prevLocation]); +} + +/* + * Usage example + */ +// const MyComponent1 = () => { +// useLocationChange((location) => { +// console.log('handle route change here', location) +// }); +// // other code +// } \ No newline at end of file diff --git a/webapp/src/hook/Previous.js b/webapp/src/hook/Previous.js deleted file mode 100644 index d3e03ca..0000000 --- a/webapp/src/hook/Previous.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useRef, useEffect } from 'react'; - -export default function usePrevious(value) { - const ref = useRef(); - - useEffect(() => { - ref.current = value; - }); - - return ref.current; -} - -/* Usage example - -const useLocationChange = (action) => { - const location = useLocation(); - const prevLocation = usePrevious(location); - - useEffect(() => { - action(location, prevLocation); - }, [location]); -} - -*/ \ No newline at end of file diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index 073bd6a..74ea624 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -6,7 +6,8 @@ const initialState = { newGame: { whitePlayer: '', - blackPlayer: '' + blackPlayer: '', + message2opponent: '', }, // Network @@ -20,6 +21,12 @@ function reducer(state, action) { case 'next': return nextState(state, action); + case 'nextNewGame': + return { + ...state, + newGame: nextState(state.newGame, action) + }; + default: throw Error('GamesReducer: unknown action.type', action.type); } -- 2.45.2 From f87b1d5d109dff1979520729c394ba0e345a66ee Mon Sep 17 00:00:00 2001 From: djmil Date: Tue, 14 Nov 2023 11:53:12 +0100 Subject: [PATCH 2/2] cleanup --- .../src/container/games/Message2Opponent.jsx | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/webapp/src/container/games/Message2Opponent.jsx b/webapp/src/container/games/Message2Opponent.jsx index 1105e75..21dca32 100644 --- a/webapp/src/container/games/Message2Opponent.jsx +++ b/webapp/src/container/games/Message2Opponent.jsx @@ -1,62 +1,48 @@ -import React, { useContext, /* useEffect,*/ useRef, useState } from 'react'; +import React, { useContext, useRef, useState } from 'react'; import { useLocation, matchPath } from 'react-router-dom'; import { GamesContext } from '../../context/games'; -//import { useLocationChange } from '../../hook/Location'; export default function Message2Opponent({ dispatchGames }) { const games = useContext(GamesContext); - const [message2opponent, setMessage2opponent] = useState(''); const { pathname } = useLocation(); - const timeoutIdRef = useRef(null); + const [value, setValue] = useState(''); + const syncTimeoutRef = useRef(null); - if (timeoutIdRef.current === null) { - // sync: internal <- external - // Sync external and internsl states only in abnsence of a delay timer - console.log("check message", message2opponent); + if (syncTimeoutRef.current === null) { // <<--- Absorb external value if there is no scheduled sync if (matchPath('/games/new', pathname)) { - if (message2opponent !== games.newGame.message2opponent) - setMessage2opponent(games.newGame.message2opponent); - } - else - if (message2opponent !== '') { - setMessage2opponent(''); - } + if (value !== games.newGame.message2opponent) + setValue(games.newGame.message2opponent); + } else if (value !== '') { + setValue(''); + } } - console.log('timeout.current', timeoutIdRef.current) /* --- */ + const sync = (message2opponent) => { + syncTimeoutRef.current = null; - const delayedSync = (message2opponent) => { - timeoutIdRef.current = null; - - // sync: internl -> external if (matchPath('/games/new', pathname)) return dispatchGames({ type: 'nextNewGame', message2opponent }); console.warn('unknown path'); } - const onChange = (value) => { - setMessage2opponent(value); + const update = (value) => { + setValue(value); - if (timeoutIdRef.current) - clearTimeout(timeoutIdRef.current); // cancel previous sync + if (syncTimeoutRef.current) + clearTimeout(syncTimeoutRef.current); // <<--- Cancel previous sync - timeoutIdRef.current = setTimeout(() => delayedSync(value), 500); + syncTimeoutRef.current = setTimeout(() => sync(value), 500); } - - //var gpathname; - //useLocationChange(({ pathname }) => { - - return ( onChange(e.target.value)} + onChange={e => update(e.target.value)} /> ) } \ No newline at end of file -- 2.45.2