From b237722e82ca3334e0ff07034795e5ec08b57fec Mon Sep 17 00:00:00 2001 From: djmil Date: Fri, 3 Nov 2023 08:53:27 +0100 Subject: [PATCH 01/15] Header + OnlineToggle --- webapp/src/App.css | 4 --- webapp/src/App.js | 13 ++++--- webapp/src/components/Game/GameSelector.jsx | 2 ++ webapp/src/components/Header.jsx | 30 ---------------- webapp/src/components/OnlineTgl/index.jsx | 12 ------- .../{OnlineTgl/index.css => OnlineToggle.css} | 0 webapp/src/components/OnlineToggle.jsx | 11 ++++++ .../src/{components => container}/Header.css | 16 ++++----- webapp/src/container/Header.jsx | 35 +++++++++++++++++++ webapp/src/reducer/polling.js | 32 +++++++++++++++++ 10 files changed, 96 insertions(+), 59 deletions(-) delete mode 100644 webapp/src/components/Header.jsx delete mode 100644 webapp/src/components/OnlineTgl/index.jsx rename webapp/src/components/{OnlineTgl/index.css => OnlineToggle.css} (100%) create mode 100644 webapp/src/components/OnlineToggle.jsx rename webapp/src/{components => container}/Header.css (74%) create mode 100644 webapp/src/container/Header.jsx create mode 100644 webapp/src/reducer/polling.js diff --git a/webapp/src/App.css b/webapp/src/App.css index 91ba01d..4931613 100644 --- a/webapp/src/App.css +++ b/webapp/src/App.css @@ -1,7 +1,3 @@ .App { text-align: center; } - -.Container { - margin-top: 25px; -} \ No newline at end of file diff --git a/webapp/src/App.js b/webapp/src/App.js index aa7b6e4..7f1f6ad 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,17 +1,21 @@ import './App.css'; -import React from 'react' +import React, { useReducer } from 'react' import { BrowserRouter, Routes, Route } from "react-router-dom" -import Header from "./components/Header" +import Header from "./container/Header" import Leaderboard from "./components/Leaderboard" import Game from "./components/Game" import About from "./components/About" -function App() { +import { pollingReducer, pollingDefaults } from './reducer/polling'; +function App() { + const [polling, dispatchPolling] = useReducer(pollingReducer, pollingDefaults) + + console.log('polling', polling) return
-
+
{/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} } /> @@ -19,7 +23,6 @@ function App() { } /> } /> } /> - } /> } /> diff --git a/webapp/src/components/Game/GameSelector.jsx b/webapp/src/components/Game/GameSelector.jsx index 73c08f8..404e434 100644 --- a/webapp/src/components/Game/GameSelector.jsx +++ b/webapp/src/components/Game/GameSelector.jsx @@ -32,6 +32,8 @@ export default function GameSelector() { if (!data.games) return
Loading..
+ console.log("Games", data.games) + return (
{isProposalPath && } diff --git a/webapp/src/components/Header.jsx b/webapp/src/components/Header.jsx deleted file mode 100644 index 71d4b9f..0000000 --- a/webapp/src/components/Header.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import './Header.css'; -import React from "react" -import { NavLink } from "react-router-dom"; -import OnlineToggle from './OnlineTgl'; -import { AppData } from "../context/data" -import Wobler from './Wobler'; - -export default function Header() { - const [data] = React.useContext(AppData) - - return ( -
-

- CordaCheckers -

- - -
- ) -} diff --git a/webapp/src/components/OnlineTgl/index.jsx b/webapp/src/components/OnlineTgl/index.jsx deleted file mode 100644 index 52ef048..0000000 --- a/webapp/src/components/OnlineTgl/index.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import "./index.css" -import React from "react" -import { AppData } from "../../context/data" - -export default function OnlineTgl() { - const [/*appData*/, dispatchData] = React.useContext(AppData) - - return
- dispatchData({type: "toggleOfflineMode"})}/> -
-} diff --git a/webapp/src/components/OnlineTgl/index.css b/webapp/src/components/OnlineToggle.css similarity index 100% rename from webapp/src/components/OnlineTgl/index.css rename to webapp/src/components/OnlineToggle.css diff --git a/webapp/src/components/OnlineToggle.jsx b/webapp/src/components/OnlineToggle.jsx new file mode 100644 index 0000000..7f80f59 --- /dev/null +++ b/webapp/src/components/OnlineToggle.jsx @@ -0,0 +1,11 @@ +import "./OnlineToggle.css" +import React from "react" + +export default function OnlineToggle({ isOnline, onClick }) { + return ( +
+ +
+ ) +} diff --git a/webapp/src/components/Header.css b/webapp/src/container/Header.css similarity index 74% rename from webapp/src/components/Header.css rename to webapp/src/container/Header.css index 299bc64..651fa5e 100644 --- a/webapp/src/components/Header.css +++ b/webapp/src/container/Header.css @@ -1,13 +1,13 @@ -.OnlineTgl { +.OnlineToggle { transform: scale(.5); margin-left: -19px; } -.app-header { +.Header { display: flex; } -.app-header nav { +.Header nav { align-items: center; justify-content: center; display: flex; @@ -16,7 +16,7 @@ padding-top: 10px; } -.app-header a { +.Header a { color: lightgray; text-decoration: none; transition: .25s ease; @@ -27,7 +27,7 @@ padding: 0.25rem 1rem; } -.app-header .active { +.Header .active { color: white; border-radius: 2px; background-color: cadetblue; @@ -35,17 +35,17 @@ padding: 0.25rem 1rem; } -.app-header a:hover:not(.active) { +.Header a:hover:not(.active) { color: cadetblue; box-shadow: 0 1.5px 0 0 currentcolor; } -[data-darkreader-scheme="dark"] .app-header a { +[data-darkreader-scheme="dark"] .Header a { color: darkslategrey; } -[data-darkreader-scheme="dark"] .app-header .active { +[data-darkreader-scheme="dark"] .Header .active { color: white; box-shadow: 0 1.5px 0 0 currentcolor; } \ No newline at end of file diff --git a/webapp/src/container/Header.jsx b/webapp/src/container/Header.jsx new file mode 100644 index 0000000..b6a446a --- /dev/null +++ b/webapp/src/container/Header.jsx @@ -0,0 +1,35 @@ +import './Header.css'; +import React from "react" +import { NavLink } from "react-router-dom"; +import OnlineToggle from '../components/OnlineToggle'; +import Wobler from '../components/Wobler'; + +export default function Header({ polling, dispatchPolling }) { + + return ( +
+

+ CordaCheckers +

+ + dispatchPolling({ type: "toggleOnOff" })} + /> + + +
+ ) +} diff --git a/webapp/src/reducer/polling.js b/webapp/src/reducer/polling.js new file mode 100644 index 0000000..a5f343c --- /dev/null +++ b/webapp/src/reducer/polling.js @@ -0,0 +1,32 @@ +export function pollingReducer(polling, action) { + switch (action.type) { + + case "toggleOnOff": + return { + ...polling, + enabled: !polling.enabled + }; + + case "games": + return { + ...polling, + games: action.value + }; + + case "leaderboard": + return { + ...polling, + leaderboard: action.value + }; + + default: + throw Error("Unknown action: " + action.type); + } +} + +export const pollingDefaults = { + enabled: true, + + games: false, + leaderboard: false, +}; -- 2.45.2 From ba7f9ce7d165f98b7f0068a95039c6861a79b903 Mon Sep 17 00:00:00 2001 From: djmil Date: Fri, 3 Nov 2023 13:32:59 +0100 Subject: [PATCH 02/15] useLocalStorage for persistant values from Reducer --- webapp/src/App.js | 5 +-- webapp/src/reducer/polling.js | 61 ++++++++++++++++++---------- webapp/src/util/PersistentStorage.js | 31 ++++++++++++++ 3 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 webapp/src/util/PersistentStorage.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 7f1f6ad..04ad760 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -7,12 +7,11 @@ import Leaderboard from "./components/Leaderboard" import Game from "./components/Game" import About from "./components/About" -import { pollingReducer, pollingDefaults } from './reducer/polling'; +import Polling from './reducer/polling'; function App() { - const [polling, dispatchPolling] = useReducer(pollingReducer, pollingDefaults) + const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) - console.log('polling', polling) return
diff --git a/webapp/src/reducer/polling.js b/webapp/src/reducer/polling.js index a5f343c..89ebf9a 100644 --- a/webapp/src/reducer/polling.js +++ b/webapp/src/reducer/polling.js @@ -1,32 +1,49 @@ -export function pollingReducer(polling, action) { +import { useLocalStorage } from './util/PersistentStorage' + +const Persistent = (() => { + const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); + + return { + getEnabled, + setEnabled + } +})(); // <<--- execute + +export const pollingGetInitialState = () => { + return { + enabled: Persistent.getEnabled() === 'true', + + games: false, + leaderboard: false + } +}; + +export function pollingReducer(state, action) { switch (action.type) { - case "toggleOnOff": - return { - ...polling, - enabled: !polling.enabled - }; + case 'toggleOnOff': return { + ...state, + enabled: Persistent.setEnabled(!state.enabled) + }; - case "games": - return { - ...polling, - games: action.value - }; + case 'games': return { + ...state, + games: action.value + }; - case "leaderboard": - return { - ...polling, - leaderboard: action.value - }; + case 'leaderboard': return { + ...state, + leaderboard: action.value + }; default: - throw Error("Unknown action: " + action.type); + throw Error('Unknown action:' + action.type); } } -export const pollingDefaults = { - enabled: true, +const Polling = { + initialState: pollingGetInitialState(), // <<--- execute + reducer: pollingReducer +} - games: false, - leaderboard: false, -}; +export default Polling \ No newline at end of file diff --git a/webapp/src/util/PersistentStorage.js b/webapp/src/util/PersistentStorage.js new file mode 100644 index 0000000..ea44750 --- /dev/null +++ b/webapp/src/util/PersistentStorage.js @@ -0,0 +1,31 @@ +export function useLocalStorage(name, initialValue) { + + const get = () => localStorage.getItem(name); + const del = () => localStorage.removeItem(name); + const set = (value) => { + localStorage.setItem(name, value); + return value; + } + + if (get() === null) { + set(initialValue); + } + + return [get, set, del] +} + +export function useSessionStorage(name, initialValue) { + + const get = () => sessionStorage.getItem(name); + const del = () => sessionStorage.removeItem(name); + const set = (value) => { + sessionStorage.setItem(name, value); + return value; + } + + if (get() === null) { + set(initialValue); + } + + return [get, set, del] +} \ No newline at end of file -- 2.45.2 From 472f5de9283bdce47b9e80c2691665e95dc8ca0e Mon Sep 17 00:00:00 2001 From: djmil Date: Fri, 3 Nov 2023 22:25:56 +0100 Subject: [PATCH 03/15] leaderboard api polling --- webapp/src/App.js | 7 ++- webapp/src/api/leaderboard.js | 24 ++++++++++ webapp/src/components/Leaderboard/index.jsx | 35 +++++++------- webapp/src/reducer/polling.js | 8 ++-- webapp/src/util/Polling.js | 53 +++++++++++++++++++++ 5 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 webapp/src/api/leaderboard.js create mode 100644 webapp/src/util/Polling.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 04ad760..dd7a425 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -8,9 +8,14 @@ import Game from "./components/Game" import About from "./components/About" import Polling from './reducer/polling'; +import { LeaderboardApi } from './api/leaderboard'; +// import { UserApi } from './api/user'; function App() { const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) + + const leaderboard = LeaderboardApi(polling, dispatchPolling).get(); + // const user = UserApi(polling, dispatchPolling).get(); return
@@ -22,7 +27,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js new file mode 100644 index 0000000..993932c --- /dev/null +++ b/webapp/src/api/leaderboard.js @@ -0,0 +1,24 @@ +import usePolling from "../util/Polling" + +const uri = '/api/leaderboard'; + +export function LeaderboardApi(polling, dispatchPolling) { + + const useGet = () => { + const mode = (polling.enabled === true) + ? { interval_sec: 300 } // update leaderbord stats every 5 min + : { interval_stop: true } // user has fliped OfflineToggel + + const [leaderboard, isFetching] = usePolling(uri, mode); + + if (polling.leaderboard !== isFetching) { + dispatchPolling({ type: 'setLeaderboard', value: isFetching }); + } + + return leaderboard; + } + + return { + get: useGet + } +} \ No newline at end of file diff --git a/webapp/src/components/Leaderboard/index.jsx b/webapp/src/components/Leaderboard/index.jsx index 7d893fb..2dd542d 100644 --- a/webapp/src/components/Leaderboard/index.jsx +++ b/webapp/src/components/Leaderboard/index.jsx @@ -1,31 +1,30 @@ import React from "react" import './index.css'; -import { AppData } from "../../context/data" -export default function Leaderboard() { - const [data] = React.useContext(AppData) +export default function Leaderboard({ leaderboard }) { - if (data.leaderboard == null) + if (leaderboard == null) return

Loading...

-// var listItems = Object.keys(data).map(playerName => { -// var rank = data[playerName]; -// -// return
  • -// {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} -//
  • -// }); -// return
      {listItems}
    ; + // var listItems = Object.keys(data).map(playerName => { + // var rank = data[playerName]; + // + // return
  • + // {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} + //
  • + // }); + // return
      {listItems}
    ; - const tableRows = Object.keys(data.leaderboard).map(playerName => { - var rank = data.leaderboard[playerName]; + const tableRows = Object.keys(leaderboard).map(playerName => { + var rank = leaderboard[playerName]; - return + // TODO tr: className={data.isCurrentUser(playerName) && 'username'} + return {playerName} {rank.gamesPlayed} {rank.gamesWon} {rank.gamesDraw} - + }); return
    @@ -39,8 +38,8 @@ export default function Leaderboard() { - { tableRows } + {tableRows} -
    +
    }; diff --git a/webapp/src/reducer/polling.js b/webapp/src/reducer/polling.js index 89ebf9a..88573a3 100644 --- a/webapp/src/reducer/polling.js +++ b/webapp/src/reducer/polling.js @@ -1,4 +1,4 @@ -import { useLocalStorage } from './util/PersistentStorage' +import { useLocalStorage } from '../util/PersistentStorage' const Persistent = (() => { const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); @@ -26,18 +26,18 @@ export function pollingReducer(state, action) { enabled: Persistent.setEnabled(!state.enabled) }; - case 'games': return { + case 'setGames': return { ...state, games: action.value }; - case 'leaderboard': return { + case 'setLeaderboard': return { ...state, leaderboard: action.value }; default: - throw Error('Unknown action:' + action.type); + throw Error('Unknown action.type:' + action.type); } } diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js new file mode 100644 index 0000000..ed3fac6 --- /dev/null +++ b/webapp/src/util/Polling.js @@ -0,0 +1,53 @@ +import { useState, useCallback, useEffect, } from "react" + +/* + - uri: string + - mode: + - null - default, return cashed value while polling fresh one from server) + - interval_sec + - interval_stop +*/ + +export default function usePolling(url, mode) { + const [cache, setCache] = useState(null); + const [isFetching, setFetching] = useState(false); + const [delayID, setDelayID] = useState(null); + + const fetchData = useCallback(() => { + setDelayID(null); + setFetching(true); + + fetch(url) + .then((response) => response.json()) + .then((freshData) => { + setCache(freshData); + setFetching(false); + }) + .catch((err) => { + console.warn(err.message); + setFetching(false); + }) + }, [url]) + + useEffect(() => { + if (cache === null && isFetching === false) { + fetchData(); + } + + if (mode?.interval_sec && delayID === null) { + const timeoutID = setTimeout(fetchData, mode.interval_sec * 1000) + setDelayID(timeoutID) + console.log("Fetch '" + url + "' scheduled in " + mode.interval_sec + " sec") + } + else if (mode?.interval_stop) { + clearTimeout(delayID); // cancel already scheduled fetch + setDelayID(null); + } + + }, [url, mode, isFetching, cache, fetchData, delayID]); + + return [ + cache, // API response + isFetching // true / false + ] +} -- 2.45.2 From df60508d45303a4b41d5dddff2fdcf4e923b0d62 Mon Sep 17 00:00:00 2001 From: djmil Date: Mon, 6 Nov 2023 09:29:13 +0100 Subject: [PATCH 04/15] Loading component --- webapp/src/App.js | 2 +- webapp/src/components/Loading.jsx | 5 +++++ .../Leaderboard/index.css => container/Leaderboard.css} | 0 .../Leaderboard/index.jsx => container/Leaderboard.jsx} | 5 +++-- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 webapp/src/components/Loading.jsx rename webapp/src/{components/Leaderboard/index.css => container/Leaderboard.css} (100%) rename webapp/src/{components/Leaderboard/index.jsx => container/Leaderboard.jsx} (91%) diff --git a/webapp/src/App.js b/webapp/src/App.js index dd7a425..d19d707 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -3,7 +3,7 @@ import React, { useReducer } from 'react' import { BrowserRouter, Routes, Route } from "react-router-dom" import Header from "./container/Header" -import Leaderboard from "./components/Leaderboard" +import Leaderboard from "./container/Leaderboard" import Game from "./components/Game" import About from "./components/About" diff --git a/webapp/src/components/Loading.jsx b/webapp/src/components/Loading.jsx new file mode 100644 index 0000000..66acbef --- /dev/null +++ b/webapp/src/components/Loading.jsx @@ -0,0 +1,5 @@ +import React from "react" + +export default function Loading() { + return
    Loading...
    +} \ No newline at end of file diff --git a/webapp/src/components/Leaderboard/index.css b/webapp/src/container/Leaderboard.css similarity index 100% rename from webapp/src/components/Leaderboard/index.css rename to webapp/src/container/Leaderboard.css diff --git a/webapp/src/components/Leaderboard/index.jsx b/webapp/src/container/Leaderboard.jsx similarity index 91% rename from webapp/src/components/Leaderboard/index.jsx rename to webapp/src/container/Leaderboard.jsx index 2dd542d..4194048 100644 --- a/webapp/src/components/Leaderboard/index.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -1,10 +1,11 @@ +import './Leaderboard.css'; import React from "react" -import './index.css'; +import Loading from '../components/Loading'; export default function Leaderboard({ leaderboard }) { if (leaderboard == null) - return

    Loading...

    + return // var listItems = Object.keys(data).map(playerName => { // var rank = data[playerName]; -- 2.45.2 From d92a3df32b002919a5a890b680e64bcc67cbe01b Mon Sep 17 00:00:00 2001 From: djmil Date: Tue, 7 Nov 2023 11:57:52 +0100 Subject: [PATCH 05/15] userFlux --- webapp/src/App.js | 51 ++++++++++++++----------- webapp/src/api/leaderboard.js | 2 +- webapp/src/api/user.js | 20 ++++++++++ webapp/src/container/Header.jsx | 3 +- webapp/src/container/Leaderboard.css | 2 +- webapp/src/container/Leaderboard.jsx | 17 +++------ webapp/src/{reducer => flux}/polling.js | 13 ++----- webapp/src/flux/user.js | 28 ++++++++++++++ webapp/src/util/Locale.js | 6 +++ webapp/src/util/StateHelper.js | 22 +++++++++++ 10 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 webapp/src/api/user.js rename webapp/src/{reducer => flux}/polling.js (75%) create mode 100644 webapp/src/flux/user.js create mode 100644 webapp/src/util/Locale.js create mode 100644 webapp/src/util/StateHelper.js diff --git a/webapp/src/App.js b/webapp/src/App.js index d19d707..0b8f5a3 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -7,31 +7,38 @@ import Leaderboard from "./container/Leaderboard" import Game from "./components/Game" import About from "./components/About" -import Polling from './reducer/polling'; -import { LeaderboardApi } from './api/leaderboard'; -// import { UserApi } from './api/user'; +import Polling from './flux/polling'; +import User from './flux/user'; +import useLeaderboardApi from './api/leaderboard'; +import useUserApi from './api/user'; function App() { - const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) - - const leaderboard = LeaderboardApi(polling, dispatchPolling).get(); - // const user = UserApi(polling, dispatchPolling).get(); + const pollingFlux = useReducer(Polling.reducer, Polling.restoreState); + const userFlux = useReducer(User.reducer, User.initialState); + + const leaderboardApi = useLeaderboardApi(pollingFlux); + const userApi = useUserApi(userFlux); + + const leaderboard = leaderboardApi.get(); + const user = userApi.get(); - return
    - -
    - - {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - -
    + return ( +
    + +
    + + {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + +
    + ) } export default App; diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index 993932c..5b2e565 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -2,7 +2,7 @@ import usePolling from "../util/Polling" const uri = '/api/leaderboard'; -export function LeaderboardApi(polling, dispatchPolling) { +export default function useLeaderboardApi([polling, dispatchPolling]) { const useGet = () => { const mode = (polling.enabled === true) diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js new file mode 100644 index 0000000..7f8606a --- /dev/null +++ b/webapp/src/api/user.js @@ -0,0 +1,20 @@ +import usePolling from "../util/Polling" + +const uri = '/api/user'; + +export default function useUserApi([user, dispatchUser]) { + + const useGet = () => { + const [nextUser] = usePolling(uri); + + if (typeof nextUser?.username === 'string' && nextUser.username !== user.username) { + dispatchUser({ type: "next", username: nextUser.username }); + } + + return user; + } + + return { + get: useGet + } +} \ No newline at end of file diff --git a/webapp/src/container/Header.jsx b/webapp/src/container/Header.jsx index b6a446a..72f52bf 100644 --- a/webapp/src/container/Header.jsx +++ b/webapp/src/container/Header.jsx @@ -4,7 +4,8 @@ import { NavLink } from "react-router-dom"; import OnlineToggle from '../components/OnlineToggle'; import Wobler from '../components/Wobler'; -export default function Header({ polling, dispatchPolling }) { +export default function Header({ pollingFlux }) { + const [polling, dispatchPolling] = pollingFlux; return (
    diff --git a/webapp/src/container/Leaderboard.css b/webapp/src/container/Leaderboard.css index 89964b1..4eeafb6 100644 --- a/webapp/src/container/Leaderboard.css +++ b/webapp/src/container/Leaderboard.css @@ -4,6 +4,6 @@ align-items: center; } -tr.username { +tr.currentuser { background-color:aliceblue; } diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index 4194048..44e338b 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -2,25 +2,18 @@ import './Leaderboard.css'; import React from "react" import Loading from '../components/Loading'; -export default function Leaderboard({ leaderboard }) { +export default function Leaderboard({ leaderboard, user }) { if (leaderboard == null) return - // var listItems = Object.keys(data).map(playerName => { - // var rank = data[playerName]; - // - // return
  • - // {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw} - //
  • - // }); - // return
      {listItems}
    ; + const isCurrentUser = (playerName) => + user.isCurrentUser(playerName) === true ? true : null; const tableRows = Object.keys(leaderboard).map(playerName => { var rank = leaderboard[playerName]; - // TODO tr: className={data.isCurrentUser(playerName) && 'username'} - return + return {playerName} {rank.gamesPlayed} {rank.gamesWon} @@ -43,4 +36,4 @@ export default function Leaderboard({ leaderboard }) {
    -}; +}; \ No newline at end of file diff --git a/webapp/src/reducer/polling.js b/webapp/src/flux/polling.js similarity index 75% rename from webapp/src/reducer/polling.js rename to webapp/src/flux/polling.js index 88573a3..2f8cbd8 100644 --- a/webapp/src/reducer/polling.js +++ b/webapp/src/flux/polling.js @@ -7,18 +7,16 @@ const Persistent = (() => { getEnabled, setEnabled } -})(); // <<--- execute +})(); // <<--- Execute -export const pollingGetInitialState = () => { - return { +const restoreState = { enabled: Persistent.getEnabled() === 'true', games: false, leaderboard: false - } }; -export function pollingReducer(state, action) { +function reducer(state, action) { switch (action.type) { case 'toggleOnOff': return { @@ -41,9 +39,6 @@ export function pollingReducer(state, action) { } } -const Polling = { - initialState: pollingGetInitialState(), // <<--- execute - reducer: pollingReducer -} +const Polling = { reducer, restoreState } export default Polling \ No newline at end of file diff --git a/webapp/src/flux/user.js b/webapp/src/flux/user.js new file mode 100644 index 0000000..0d286cd --- /dev/null +++ b/webapp/src/flux/user.js @@ -0,0 +1,28 @@ +import { localeCompare } from '../util/Locale' +import StateHelper from '../util/StateHelper'; + +export const userInitialState = { + username: '', + isCurrentUser: function (otherUsername) { + return localeCompare(this.username, otherUsername) + }, + +}; + +export function userReducer(state, action) { + switch (action.type) { + + case 'next': + return StateHelper.next(state, action); + + default: + throw Error('Unknown action.type: ' + action.type); + } +} + +const User = { + reducer: userReducer, + initialState: userInitialState +}; + +export default User; \ No newline at end of file diff --git a/webapp/src/util/Locale.js b/webapp/src/util/Locale.js new file mode 100644 index 0000000..42e0b83 --- /dev/null +++ b/webapp/src/util/Locale.js @@ -0,0 +1,6 @@ +export function localeCompare(a, b) { + // console.log(localeCompare, a, b); + return typeof a === 'string' && typeof b === 'string' + ? a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0 + : a === b; +} \ No newline at end of file diff --git a/webapp/src/util/StateHelper.js b/webapp/src/util/StateHelper.js new file mode 100644 index 0000000..1cda2df --- /dev/null +++ b/webapp/src/util/StateHelper.js @@ -0,0 +1,22 @@ +export function nextState(state, action) { + const nextState = { ...state }; + + Object.keys(action) + .slice(1) // skip first property i.e. 'next' + .forEach(key => { + if (Object.hasOwn(nextState, key)) { + console.log("next [", key, "] = ", action[key]); + nextState[key] = action[key]; + } else { + console.warn("nextState: bad action property\n", key + ":", action[key]); + } + }) + + return nextState; +} + +const StateHelper = { + next: nextState +}; + +export default StateHelper; \ No newline at end of file -- 2.45.2 From 9ec2059c4a9e27c024f1c4c71d675d072d3e567f Mon Sep 17 00:00:00 2001 From: djmil Date: Tue, 7 Nov 2023 12:58:09 +0100 Subject: [PATCH 06/15] pollingFlux --- webapp/src/App.js | 2 +- webapp/src/api/leaderboard.js | 2 +- webapp/src/flux/polling.js | 31 ++++++++++++++----------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index 0b8f5a3..3aa63ee 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -13,7 +13,7 @@ import useLeaderboardApi from './api/leaderboard'; import useUserApi from './api/user'; function App() { - const pollingFlux = useReducer(Polling.reducer, Polling.restoreState); + const pollingFlux = useReducer(Polling.reducer, Polling.initialState); const userFlux = useReducer(User.reducer, User.initialState); const leaderboardApi = useLeaderboardApi(pollingFlux); diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index 5b2e565..d32b4b6 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -12,7 +12,7 @@ export default function useLeaderboardApi([polling, dispatchPolling]) { const [leaderboard, isFetching] = usePolling(uri, mode); if (polling.leaderboard !== isFetching) { - dispatchPolling({ type: 'setLeaderboard', value: isFetching }); + dispatchPolling({ type: 'next', leaderboard: isFetching }); } return leaderboard; diff --git a/webapp/src/flux/polling.js b/webapp/src/flux/polling.js index 2f8cbd8..c4ce599 100644 --- a/webapp/src/flux/polling.js +++ b/webapp/src/flux/polling.js @@ -1,4 +1,5 @@ import { useLocalStorage } from '../util/PersistentStorage' +import { nextState } from '../util/StateHelper'; const Persistent = (() => { const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true); @@ -9,36 +10,32 @@ const Persistent = (() => { } })(); // <<--- Execute -const restoreState = { - enabled: Persistent.getEnabled() === 'true', +export const pollingInitialState = { + enabled: Persistent.getEnabled() === 'true', - games: false, - leaderboard: false + games: false, + leaderboard: false }; -function reducer(state, action) { +export function pollingReducer(curntState, action) { switch (action.type) { case 'toggleOnOff': return { - ...state, - enabled: Persistent.setEnabled(!state.enabled) + ...curntState, + enabled: Persistent.setEnabled(!curntState.enabled) }; - case 'setGames': return { - ...state, - games: action.value - }; - - case 'setLeaderboard': return { - ...state, - leaderboard: action.value - }; + case 'next': + return nextState(curntState, action); default: throw Error('Unknown action.type:' + action.type); } } -const Polling = { reducer, restoreState } +const Polling = { + reducer: pollingReducer, + initialState: pollingInitialState +}; export default Polling \ No newline at end of file -- 2.45.2 From b58c71c87650845e9fac31c5b0797266e4f43e7c Mon Sep 17 00:00:00 2001 From: djmil Date: Wed, 8 Nov 2023 09:23:20 +0100 Subject: [PATCH 07/15] useXxxReducer --- webapp/src/App.js | 23 +++++++++++--------- webapp/src/api/leaderboard.js | 13 +++++++++--- webapp/src/container/Header.jsx | 6 +++--- webapp/src/container/Leaderboard.jsx | 9 ++++---- webapp/src/flux/user.js | 28 ------------------------- webapp/src/reducer/leaderboard.js | 21 +++++++++++++++++++ webapp/src/{flux => reducer}/polling.js | 12 +++++------ webapp/src/reducer/user.js | 26 +++++++++++++++++++++++ webapp/src/util/Polling.js | 2 +- 9 files changed, 84 insertions(+), 56 deletions(-) delete mode 100644 webapp/src/flux/user.js create mode 100644 webapp/src/reducer/leaderboard.js rename webapp/src/{flux => reducer}/polling.js (77%) create mode 100644 webapp/src/reducer/user.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 3aa63ee..80f2778 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,31 +1,34 @@ import './App.css'; -import React, { useReducer } from 'react' -import { BrowserRouter, Routes, Route } from "react-router-dom" +import React from 'react'; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Header from "./container/Header" import Leaderboard from "./container/Leaderboard" import Game from "./components/Game" import About from "./components/About" -import Polling from './flux/polling'; -import User from './flux/user'; +import usePollingReducer from './reducer/polling'; +import useUserReducer from './reducer/user'; +import useLeaderboardReducer from './reducer/leaderboard'; + import useLeaderboardApi from './api/leaderboard'; import useUserApi from './api/user'; function App() { - const pollingFlux = useReducer(Polling.reducer, Polling.initialState); - const userFlux = useReducer(User.reducer, User.initialState); + const pollingReducer = usePollingReducer(); + const userReducer = useUserReducer(); + const leaderboardReducer = useLeaderboardReducer(); - const leaderboardApi = useLeaderboardApi(pollingFlux); - const userApi = useUserApi(userFlux); + const leaderboardApi = useLeaderboardApi(leaderboardReducer); + const userApi = useUserApi(userReducer); - const leaderboard = leaderboardApi.get(); + const leaderboard = leaderboardApi.get(pollingReducer); const user = userApi.get(); return (
    -
    +
    {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} } /> diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index d32b4b6..1b7ca18 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -2,19 +2,26 @@ import usePolling from "../util/Polling" const uri = '/api/leaderboard'; -export default function useLeaderboardApi([polling, dispatchPolling]) { +export default function useLeaderboardApi(leaderboardReducer) { + const [leaderboard, dispatchLeaderboaed] = leaderboardReducer; + + const useGet = (pollingReducer) => { + const [polling, dispatchPolling] = pollingReducer; - const useGet = () => { const mode = (polling.enabled === true) ? { interval_sec: 300 } // update leaderbord stats every 5 min : { interval_stop: true } // user has fliped OfflineToggel - const [leaderboard, isFetching] = usePolling(uri, mode); + const [table, isFetching] = usePolling(uri, mode); if (polling.leaderboard !== isFetching) { dispatchPolling({ type: 'next', leaderboard: isFetching }); } + if (leaderboard.table !== table) { + dispatchLeaderboaed({ type: 'next', table }); + } + return leaderboard; } diff --git a/webapp/src/container/Header.jsx b/webapp/src/container/Header.jsx index 72f52bf..dc4d2a0 100644 --- a/webapp/src/container/Header.jsx +++ b/webapp/src/container/Header.jsx @@ -4,8 +4,8 @@ import { NavLink } from "react-router-dom"; import OnlineToggle from '../components/OnlineToggle'; import Wobler from '../components/Wobler'; -export default function Header({ pollingFlux }) { - const [polling, dispatchPolling] = pollingFlux; +export default function Header({ pollingReducer }) { + const [polling, dispatchPolling] = pollingReducer; return (
    @@ -33,4 +33,4 @@ export default function Header({ pollingFlux }) {
    ) -} +} \ No newline at end of file diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index 44e338b..54457c7 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -4,14 +4,15 @@ import Loading from '../components/Loading'; export default function Leaderboard({ leaderboard, user }) { - if (leaderboard == null) + const table = leaderboard?.table; + if (!table) return const isCurrentUser = (playerName) => - user.isCurrentUser(playerName) === true ? true : null; + user?.isCurrentUser(playerName) === true ? true : null; - 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/flux/user.js b/webapp/src/flux/user.js deleted file mode 100644 index 0d286cd..0000000 --- a/webapp/src/flux/user.js +++ /dev/null @@ -1,28 +0,0 @@ -import { localeCompare } from '../util/Locale' -import StateHelper from '../util/StateHelper'; - -export const userInitialState = { - username: '', - isCurrentUser: function (otherUsername) { - return localeCompare(this.username, otherUsername) - }, - -}; - -export function userReducer(state, action) { - switch (action.type) { - - case 'next': - return StateHelper.next(state, action); - - default: - throw Error('Unknown action.type: ' + action.type); - } -} - -const User = { - reducer: userReducer, - initialState: userInitialState -}; - -export default User; \ 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..06352a4 --- /dev/null +++ b/webapp/src/reducer/leaderboard.js @@ -0,0 +1,21 @@ +import { useReducer } from 'react'; +import { nextState } from '../util/StateHelper'; + +export const leaderboardInitialState = { + table: null, +}; + +export function leaderboardReducer(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(leaderboardReducer, leaderboardInitialState); +} \ No newline at end of file diff --git a/webapp/src/flux/polling.js b/webapp/src/reducer/polling.js similarity index 77% rename from webapp/src/flux/polling.js rename to webapp/src/reducer/polling.js index c4ce599..521f5c7 100644 --- a/webapp/src/flux/polling.js +++ b/webapp/src/reducer/polling.js @@ -1,4 +1,5 @@ -import { useLocalStorage } from '../util/PersistentStorage' +import { useReducer } from 'react'; +import { useLocalStorage } from '../util/PersistentStorage'; import { nextState } from '../util/StateHelper'; const Persistent = (() => { @@ -33,9 +34,6 @@ export function pollingReducer(curntState, action) { } } -const Polling = { - reducer: pollingReducer, - initialState: pollingInitialState -}; - -export default Polling \ No newline at end of file +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 new file mode 100644 index 0000000..4128100 --- /dev/null +++ b/webapp/src/reducer/user.js @@ -0,0 +1,26 @@ +import { useReducer } from 'react'; +import { localeCompare } from '../util/Locale'; +import { nextState } from '../util/StateHelper'; + +export const userInitialState = { + username: '', + + isCurrentUser: function (otherUsername) { + return localeCompare(this.username, otherUsername) + } +}; + +export function userReducer(state, action) { + switch (action.type) { + + case 'next': + return nextState(state, action); + + default: + throw Error('Unknown action.type', action.type); + } +} + +export default function useUserReducer() { + return useReducer(userReducer, userInitialState); +} \ No newline at end of file diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js index ed3fac6..49a1f9a 100644 --- a/webapp/src/util/Polling.js +++ b/webapp/src/util/Polling.js @@ -47,7 +47,7 @@ export default function usePolling(url, mode) { }, [url, mode, isFetching, cache, fetchData, delayID]); return [ - cache, // API response + cache, // API responce isFetching // true / false ] } -- 2.45.2 From aa2a2500850286311ee33d6aa8c85cfe715b4b7f Mon Sep 17 00:00:00 2001 From: djmil Date: Wed, 8 Nov 2023 18:22:05 +0100 Subject: [PATCH 08/15] Games container sceleton - use Route for conditional rendering - useGamesAPI - Checkers component --- .../api/GameStateController.java | 2 +- webapp/src/App.css | 9 +- webapp/src/App.js | 51 ++++--- webapp/src/api/games.js | 31 +++++ webapp/src/api/leaderboard.js | 4 +- .../GameBoard/Board.css => Checkers.css} | 21 ++- webapp/src/components/Checkers.jsx | 130 ++++++++++++++++++ webapp/src/components/Game.css | 26 ---- webapp/src/components/Game.jsx | 29 ---- webapp/src/components/Game/GameAction.css | 55 -------- webapp/src/components/Game/GameAction.jsx | 45 ------ .../components/Game/GameAction/Backward.jsx | 6 - .../components/Game/GameAction/DrawAcq.jsx | 6 - .../components/Game/GameAction/DrawReq.jsx | 6 - .../components/Game/GameAction/Forward.jsx | 6 - .../components/Game/GameAction/Surrender.jsx | 6 - webapp/src/components/Game/GameBoard.css | 3 - webapp/src/components/Game/GameBoard.jsx | 20 --- .../src/components/Game/GameBoard/Board.jsx | 79 ----------- .../Game/GameSelector/Selectable.jsx | 2 +- webapp/src/components/Game/NewGame.jsx | 2 +- webapp/src/components/Game/Player.css | 15 -- webapp/src/components/Game/Player.jsx | 29 ---- webapp/src/components/Game/Stone.css | 3 - webapp/src/components/Game/Stone.jsx | 41 ------ webapp/src/container/Games.css | 125 +++++++++++++++++ webapp/src/container/Games.jsx | 77 +++++++++++ webapp/src/container/Header.css | 11 +- webapp/src/container/Header.jsx | 4 +- webapp/src/container/Leaderboard.jsx | 32 +++-- webapp/src/container/games/GameBoard.css | 3 + webapp/src/container/games/GameBoard.jsx | 20 +++ .../games/action}/Accept.jsx | 2 +- .../src/container/games/action/Backward.jsx | 6 + .../games/action}/Cancel.jsx | 2 +- webapp/src/container/games/action/Create.jsx | 6 + webapp/src/container/games/action/DrawAcq.jsx | 6 + webapp/src/container/games/action/DrawReq.jsx | 6 + webapp/src/container/games/action/Forward.jsx | 6 + .../games/action}/Reject.jsx | 2 +- .../src/container/games/action/Surrender.jsx | 6 + .../src/container/games/view/ActiveGames.jsx | 7 + .../container/games/view/GameProposals.jsx | 7 + .../src/container/games/view/GamesArchive.jsx | 7 + webapp/src/container/games/view/NewGame.css | 8 ++ webapp/src/container/games/view/NewGame.jsx | 25 ++++ webapp/src/reducer/games.js | 21 +++ webapp/src/reducer/user.js | 2 +- 48 files changed, 572 insertions(+), 446 deletions(-) create mode 100644 webapp/src/api/games.js rename webapp/src/components/{Game/GameBoard/Board.css => Checkers.css} (64%) create mode 100644 webapp/src/components/Checkers.jsx delete mode 100644 webapp/src/components/Game.css delete mode 100644 webapp/src/components/Game.jsx delete mode 100644 webapp/src/components/Game/GameAction.css delete mode 100644 webapp/src/components/Game/GameAction.jsx delete mode 100644 webapp/src/components/Game/GameAction/Backward.jsx delete mode 100644 webapp/src/components/Game/GameAction/DrawAcq.jsx delete mode 100644 webapp/src/components/Game/GameAction/DrawReq.jsx delete mode 100644 webapp/src/components/Game/GameAction/Forward.jsx delete mode 100644 webapp/src/components/Game/GameAction/Surrender.jsx delete mode 100644 webapp/src/components/Game/GameBoard.css delete mode 100644 webapp/src/components/Game/GameBoard.jsx delete mode 100644 webapp/src/components/Game/GameBoard/Board.jsx delete mode 100644 webapp/src/components/Game/Player.css delete mode 100644 webapp/src/components/Game/Player.jsx delete mode 100644 webapp/src/components/Game/Stone.css delete mode 100644 webapp/src/components/Game/Stone.jsx create mode 100644 webapp/src/container/Games.css create mode 100644 webapp/src/container/Games.jsx create mode 100644 webapp/src/container/games/GameBoard.css create mode 100644 webapp/src/container/games/GameBoard.jsx rename webapp/src/{components/Game/GameAction => container/games/action}/Accept.jsx (50%) create mode 100644 webapp/src/container/games/action/Backward.jsx rename webapp/src/{components/Game/GameAction => container/games/action}/Cancel.jsx (50%) create mode 100644 webapp/src/container/games/action/Create.jsx create mode 100644 webapp/src/container/games/action/DrawAcq.jsx create mode 100644 webapp/src/container/games/action/DrawReq.jsx create mode 100644 webapp/src/container/games/action/Forward.jsx rename webapp/src/{components/Game/GameAction => container/games/action}/Reject.jsx (50%) create mode 100644 webapp/src/container/games/action/Surrender.jsx create mode 100644 webapp/src/container/games/view/ActiveGames.jsx create mode 100644 webapp/src/container/games/view/GameProposals.jsx create mode 100644 webapp/src/container/games/view/GamesArchive.jsx create mode 100644 webapp/src/container/games/view/NewGame.css create mode 100644 webapp/src/container/games/view/NewGame.jsx create mode 100644 webapp/src/reducer/games.js diff --git a/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java b/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java index a128272..4b2432b 100644 --- a/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java +++ b/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java @@ -16,7 +16,7 @@ import djmil.cordacheckers.user.User; @RestController -@RequestMapping("api/gamestate") +@RequestMapping("api/games") public class GameStateController { @Autowired diff --git a/webapp/src/App.css b/webapp/src/App.css index 4931613..88346b6 100644 --- a/webapp/src/App.css +++ b/webapp/src/App.css @@ -1,3 +1,8 @@ -.App { - text-align: center; +[data-darkreader-scheme="dark"] .Header a { + color: darkslategrey; } + +[data-darkreader-scheme="dark"] .Header .active { + color: white; + box-shadow: 0 1.5px 0 0 currentcolor; +} \ No newline at end of file diff --git a/webapp/src/App.js b/webapp/src/App.js index 80f2778..a96c5c1 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,46 +1,41 @@ import './App.css'; import React from 'react'; -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Header from "./container/Header" -import Leaderboard from "./container/Leaderboard" -import Game from "./components/Game" +import Header from './container/Header'; import About from "./components/About" +import Games from './container/Games'; +import Leaderboard from './container/Leaderboard'; -import usePollingReducer from './reducer/polling'; import useUserReducer from './reducer/user'; +import usePollingReducer from './reducer/polling'; import useLeaderboardReducer from './reducer/leaderboard'; +import useGamesReducer from './reducer/games'; -import useLeaderboardApi from './api/leaderboard'; import useUserApi from './api/user'; +import useLeaderboardApi from './api/leaderboard'; +import useGamesApi from './api/games'; function App() { - const pollingReducer = usePollingReducer(); const userReducer = useUserReducer(); + const pollingReducer = usePollingReducer(); const leaderboardReducer = useLeaderboardReducer(); - - const leaderboardApi = useLeaderboardApi(leaderboardReducer); - const userApi = useUserApi(userReducer); - - const leaderboard = leaderboardApi.get(pollingReducer); - const user = userApi.get(); + const gamesReducer = useGamesReducer(); + + const user = useUserApi(userReducer).get(); + const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingReducer); + /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingReducer); return ( -
    - -
    - - {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - -
    + +
    + + } /> + } /> + } /> + } /> + + ) } diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js new file mode 100644 index 0000000..aefbdbb --- /dev/null +++ b/webapp/src/api/games.js @@ -0,0 +1,31 @@ +import usePolling from "../util/Polling" + +const uri = '/api/games'; + +export default function useGamesApi(gamesReducer) { + const [games, dispatchGames] = gamesReducer; + + const useList = (pollingReducer) => { + const [polling, dispatchPolling] = pollingReducer; + + const mode = (polling.enabled === true) + ? { interval_sec: 30 } // update games list half a minue + : { interval_stop: true } // user has fliped OfflineToggel + + const [list, isFetching] = usePolling(uri, mode); + + if (polling.games !== isFetching) { + dispatchPolling({ type: 'next', games: isFetching }); + } + + if (games.list !== list) { + dispatchGames({ type: 'next', list }); + } + + return games; + } + + return { + list: useList + } +} \ No newline at end of file diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index 1b7ca18..f5d01f9 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -5,7 +5,7 @@ const uri = '/api/leaderboard'; export default function useLeaderboardApi(leaderboardReducer) { const [leaderboard, dispatchLeaderboaed] = leaderboardReducer; - const useGet = (pollingReducer) => { + const usePoll = (pollingReducer) => { const [polling, dispatchPolling] = pollingReducer; const mode = (polling.enabled === true) @@ -26,6 +26,6 @@ export default function useLeaderboardApi(leaderboardReducer) { } return { - get: useGet + poll: usePoll } } \ No newline at end of file diff --git a/webapp/src/components/Game/GameBoard/Board.css b/webapp/src/components/Checkers.css similarity index 64% rename from webapp/src/components/Game/GameBoard/Board.css rename to webapp/src/components/Checkers.css index 6e9cc05..069b28e 100644 --- a/webapp/src/components/Game/GameBoard/Board.css +++ b/webapp/src/components/Checkers.css @@ -1,4 +1,15 @@ -.board { +.Stone { + cursor: default; /* disable 'I beam' cursor change */ +} + +.Player { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.Board { display: flex; flex-direction: column; justify-content: center; @@ -6,7 +17,7 @@ /* scale: 15%; */ } -.tile { +.Tile { border: 1px solid #e4e4e4; float: left; font-size: 200%; @@ -20,14 +31,14 @@ text-align: center; } -.tile.black { +.Tile.black { background: lightgray; } -.tile.white:hover { +.Tile.white:hover { background-color:azure; } -.stone { +.Tile .Stone { font-size: 120%; } \ No newline at end of file diff --git a/webapp/src/components/Checkers.jsx b/webapp/src/components/Checkers.jsx new file mode 100644 index 0000000..7f7f220 --- /dev/null +++ b/webapp/src/components/Checkers.jsx @@ -0,0 +1,130 @@ +import './Checkers.css' +import React from 'react' + +export const Color = { + white: "WHITE", + black: "BLACK", + + opposite: (color) => { + if (color === Color.white) + return Color.black; + if (color === Color.black) + return Color.white; + + return color; + } +}; + +/* + * Stone + */ +export function Stone({ color }) { + switch (color) { + case Color.white: + return WhiteStone(); + + case Color.black: + return BlackStone(); + + default: + console.warn("Unknown color: ", color) + } +} + +export function WhiteStone() { + return +} + +export function BlackStone() { + return +} + +/* + * Player + */ +export function Player({ color, name }) { + return ( +
    + + {name} +
    + ) +} + +/* + * Board + */ +export function Board() { + + return
    +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    + +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    +
    +} + +function WhiteTile({ id, stone }) { + + return ( +
    handleClick(id)} + > + {stone} +
    + ); +} + +function BlackTile() { + return
    +} + +function handleClick(i) { + console.log("click", i) +} \ No newline at end of file diff --git a/webapp/src/components/Game.css b/webapp/src/components/Game.css deleted file mode 100644 index e15501b..0000000 --- a/webapp/src/components/Game.css +++ /dev/null @@ -1,26 +0,0 @@ -.game { - width: 100%; - float: left; -} - -.game .left-side { - float: left; - width: 45%; - /* max-width: 400px; */ - - /* height: 100px; */ - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.game .right-side { - float: left; - width: 55%; - - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} \ No newline at end of file diff --git a/webapp/src/components/Game.jsx b/webapp/src/components/Game.jsx deleted file mode 100644 index e4d49c1..0000000 --- a/webapp/src/components/Game.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import './Game.css'; -import React from 'react'; -import GameView from './Game/GameView' -import GameSelector from './Game/GameSelector' -import GameAction from './Game/GameAction' -import GameBoard from './Game/GameBoard' -import NewGame from './Game/NewGame' -import GameMessage from './Game/GameMessage' -import Message2Opponent from './Game/Message2Opponent' - -export default function Game() { - - return ( -
    -
    - - - -
    -
    - - - - -
    -
    - ) - -} diff --git a/webapp/src/components/Game/GameAction.css b/webapp/src/components/Game/GameAction.css deleted file mode 100644 index 69f5a28..0000000 --- a/webapp/src/components/Game/GameAction.css +++ /dev/null @@ -1,55 +0,0 @@ -.action-panel { - margin-bottom: 10px; - /* background-color: lightgrey; */ - width: 100%; - /* padding-top: 8px; - padding-bottom: 8px; */ - color: black; - padding-left: -10px; - /* */ - - margin-left: 10px; - border: 0.5px dotted lightslategray; -} - -.game-action { - width:fit-content; - padding: 8px; - padding-left: 15px; - padding-right: 15px; - border-radius: 5px; - border: 0.5px solid darkgrey; - margin: 2px; -} - -.game-action.create:hover, /* OR */ -.game-action.busy -{ - background-color:#00b0ff60; -} - -.game-action.create.enabled:active { - background-color:#00b0ffa0; -} - -.game-action.cancel:hover, -.game-action.reject:hover { - background-color:#ff000030 -} - -.game-action.cancel:active, -.game-action.reject:active { - background-color:#ff000080 -} - -.game-action.accept:hover { - background-color: #00af0030; -} - -.game-action.accept:active { - background-color:#00af0080; -} - -.game-action.disabled { - color: gray; -} diff --git a/webapp/src/components/Game/GameAction.jsx b/webapp/src/components/Game/GameAction.jsx deleted file mode 100644 index 8cdbcf9..0000000 --- a/webapp/src/components/Game/GameAction.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import './GameAction.css'; -import React from 'react'; -import { useLocation, matchPath } from "react-router"; - -import Create from './GameAction/Create'; - -import Reject from './GameAction/Reject'; -import Cancel from './GameAction/Cancel'; -import Accept from './GameAction/Accept'; - -import DrawReq from './GameAction/DrawReq'; -import DrawAcq from './GameAction/DrawAcq'; -import Surrender from './GameAction/Surrender'; - -import Backward from './GameAction/Backward'; -import Forward from './GameAction/Forward'; - -// import { AppContext } from '../../context/app' - -export default function GameAction() { - // const [ctx, dispatchCtx] = React.useContext(AppContext) - - const { pathname } = useLocation(); - const isNewGamePath = matchPath("/game/new", pathname); - const isProposalPath = matchPath("/game/proposal/*", pathname); - const isActivelPath = matchPath("/game/active/*", pathname); - const isArchivePath = matchPath("/game/archive/*", pathname); - - return ( -
    - {isNewGamePath && } - - {isProposalPath && } - {isProposalPath && } - {isProposalPath && } - - {isActivelPath && } - {isActivelPath && } - {isActivelPath && } - - {isArchivePath && } - {isArchivePath && } -
    - ) -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameAction/Backward.jsx b/webapp/src/components/Game/GameAction/Backward.jsx deleted file mode 100644 index 2cebb00..0000000 --- a/webapp/src/components/Game/GameAction/Backward.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export default function Backward() { - - return -} diff --git a/webapp/src/components/Game/GameAction/DrawAcq.jsx b/webapp/src/components/Game/GameAction/DrawAcq.jsx deleted file mode 100644 index 5d1a5af..0000000 --- a/webapp/src/components/Game/GameAction/DrawAcq.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export default function DrawAcq() { - - return -} diff --git a/webapp/src/components/Game/GameAction/DrawReq.jsx b/webapp/src/components/Game/GameAction/DrawReq.jsx deleted file mode 100644 index 33f147e..0000000 --- a/webapp/src/components/Game/GameAction/DrawReq.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export default function DrawReq() { - - return -} diff --git a/webapp/src/components/Game/GameAction/Forward.jsx b/webapp/src/components/Game/GameAction/Forward.jsx deleted file mode 100644 index a9f5380..0000000 --- a/webapp/src/components/Game/GameAction/Forward.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export default function Forward() { - - return -} diff --git a/webapp/src/components/Game/GameAction/Surrender.jsx b/webapp/src/components/Game/GameAction/Surrender.jsx deleted file mode 100644 index fb53cb4..0000000 --- a/webapp/src/components/Game/GameAction/Surrender.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export default function Surrender() { - - return -} diff --git a/webapp/src/components/Game/GameBoard.css b/webapp/src/components/Game/GameBoard.css deleted file mode 100644 index 8a8d0f5..0000000 --- a/webapp/src/components/Game/GameBoard.css +++ /dev/null @@ -1,3 +0,0 @@ -.game-board .board { - padding: 5px; -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameBoard.jsx b/webapp/src/components/Game/GameBoard.jsx deleted file mode 100644 index 9af7ef8..0000000 --- a/webapp/src/components/Game/GameBoard.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import './GameBoard.css' -import React from 'react' -import Board from './GameBoard/Board' -import { WHITE, BLACK } from './Stone' -import { Player } from './Player' - -import { AppContext } from '../../context/app' - -export default function GameBoard() { - - const [ctx] = React.useContext(AppContext) - - return ( -
    - - - -
    - ) -} diff --git a/webapp/src/components/Game/GameBoard/Board.jsx b/webapp/src/components/Game/GameBoard/Board.jsx deleted file mode 100644 index 3d1644a..0000000 --- a/webapp/src/components/Game/GameBoard/Board.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import './Board.css'; -import React from 'react'; - -import { WhiteStone, BlackStone } from '../Stone' - -export default function Board() { - - return
    -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    - -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    -
    -} - -function WhiteTile({ id, stone }) { - - return ( -
    handleClick(id)} - > - {stone} -
    - ); -} - -function BlackTile() { - return
    -} - -function handleClick(i) { - console.log("click", i) -} diff --git a/webapp/src/components/Game/GameSelector/Selectable.jsx b/webapp/src/components/Game/GameSelector/Selectable.jsx index 7e7a53e..838ed3e 100644 --- a/webapp/src/components/Game/GameSelector/Selectable.jsx +++ b/webapp/src/components/Game/GameSelector/Selectable.jsx @@ -1,7 +1,7 @@ import './Selectable.css' import React from 'react'; import { oppositeColor } from '../Stone'; -import { Player } from '../Player'; +import { Player } from '../../Player'; export default function Selectable({ game, onClick }) { diff --git a/webapp/src/components/Game/NewGame.jsx b/webapp/src/components/Game/NewGame.jsx index 4a13705..4a42b71 100644 --- a/webapp/src/components/Game/NewGame.jsx +++ b/webapp/src/components/Game/NewGame.jsx @@ -3,7 +3,7 @@ import React from 'react'; import { AppData } from '../../context/data' import { AppContext } from '../../context/app' import { useLocation, matchPath } from "react-router"; -import { SelectPlayer } from './Player'; +import { SelectPlayer } from '../Player'; import { WhiteStone, BlackStone } from './Stone'; diff --git a/webapp/src/components/Game/Player.css b/webapp/src/components/Game/Player.css deleted file mode 100644 index 549182a..0000000 --- a/webapp/src/components/Game/Player.css +++ /dev/null @@ -1,15 +0,0 @@ -.player { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - -.player select { - border-radius: 5px; - border: 0.5px solid darkgrey; -} - -.player select:hover { - background: lightgray; -} \ No newline at end of file diff --git a/webapp/src/components/Game/Player.jsx b/webapp/src/components/Game/Player.jsx deleted file mode 100644 index ab55ad5..0000000 --- a/webapp/src/components/Game/Player.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import './Player.css' -import React from 'react' -import { Stone } from './Stone' - -export function Player({ color, name }) { - - return ( -
    - - {name} -
    - ) -} - -export function SelectPlayer({ name, setName, nameOptions }) { - const handleSelectChange = (event) => { - setName(event.target.value) - } - - return ( -
    -
    - -
    -
    - ) -} diff --git a/webapp/src/components/Game/Stone.css b/webapp/src/components/Game/Stone.css deleted file mode 100644 index debebf0..0000000 --- a/webapp/src/components/Game/Stone.css +++ /dev/null @@ -1,3 +0,0 @@ -.stone { - cursor: default; /* disable 'I beam' cursor change */ -} \ No newline at end of file diff --git a/webapp/src/components/Game/Stone.jsx b/webapp/src/components/Game/Stone.jsx deleted file mode 100644 index 60995fc..0000000 --- a/webapp/src/components/Game/Stone.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import './Stone.css' -import React from 'react' - -export function Stone({ color }) { - switch (color) { - case WHITE(): - return WhiteStone() - - case BLACK(): - return BlackStone() - - default: - console.warn("Unknown color: ", color) - } -} - -export function WhiteStone() { - return -} - -export function BlackStone() { - return -} - -export function oppositeColor(color) { - if (color === WHITE()) - return BLACK() - - if (color === BLACK()) - return WHITE() - - return color -} - -export function WHITE() { - return "WHITE" -} - -export function BLACK() { - return "BLACK" -} diff --git a/webapp/src/container/Games.css b/webapp/src/container/Games.css new file mode 100644 index 0000000..54be4a0 --- /dev/null +++ b/webapp/src/container/Games.css @@ -0,0 +1,125 @@ +.Games { + width: 100%; + float: left; +} + +.Games .left-side { + float: left; + width: 45%; + /* max-width: 400px; */ + + /* height: 100px; */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.Games .right-side { + float: left; + width: 55%; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + + + +.ViewSelector { + text-align: center; + margin-bottom: 10px; + background-color: lightgrey; + width: 100%; + padding-top: 8px; + padding-bottom: 8px; + color: black; +} + +.ViewSelector a { + color: black; + text-decoration: none; + transition: .25s ease; + margin-left: 5px; + margin-right: 5px; +} + +.ViewSelector .active { + color: white; + border-radius: 2px; + background-color: cadetblue; + opacity: 80%; + padding-top: 8px; + padding-bottom: 8px; +} + +.ViewSelector a:hover:not(.active) { + color: cadetblue; + box-shadow: 0 1.5px 0 0 currentColor; +} + + + +.ViewProvider { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + + +.ActionPanel { + text-align: center; + margin-bottom: 10px; + height: 34.5px; + + /* background-color: lightgrey; */ + width: 100%; + /* padding-top: 8px; + padding-bottom: 8px; */ + color: black; + padding-left: -10px; + /* */ + + margin-left: 10px; + border: 0.5px dotted lightslategray; +} + +.ActionPanel button { + width:fit-content; + padding: 6px; + padding-left: 15px; + padding-right: 15px; + border-radius: 5px; + border: 0.5px solid darkgrey; + margin: 2px; +} + +.ActionPanel .Create:hover, /* OR */ +.game-action.busy +{ + background-color:#00b0ff60; +} + +.ActionPanel .Create.enabled:active { + background-color:#00b0ffa0; +} + +.ActionPanel .Cancel:hover, +.ActionPanel .Reject:hover { + background-color:#ff000030 +} + +.ActionPanel .Cancel:active, +.ActionPanel .Reject:active { + background-color:#ff000080 +} + +.ActionPanel .Accept:hover { + background-color: #00af0030; +} + +.ActionPanel .Accept:active { + background-color:#00af0080; +} \ No newline at end of file diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx new file mode 100644 index 0000000..c045551 --- /dev/null +++ b/webapp/src/container/Games.jsx @@ -0,0 +1,77 @@ +import './Games.css'; +import React from "react" +import { NavLink, Routes, Route } from "react-router-dom"; + +import NewGame from './games/view/NewGame'; +import GameProposals from './games/view/GameProposals'; +import ActiveGames from './games/view/ActiveGames'; +import GamesArchive from './games/view/GamesArchive'; + +import Create from './games/action/Create'; +import Reject from './games/action/Reject'; +import Cancel from './games/action/Cancel'; +import Accept from './games/action/Accept'; +import DrawReq from './games/action/DrawReq'; +import DrawAcq from './games/action/DrawAcq'; +import Surrender from './games/action/Surrender'; +import Backward from './games/action/Backward'; +import Forward from './games/action/Forward'; + +import GameBoard from './games/GameBoard'; + +export default function Games({ gamesReducer, user }) { + + return ( +
    +
    + + +
    +
    + + + {/* + + + */} +
    +
    + ) +}; + +function ViewSelector() { + return ( + + ) +} + +function ViewProvider() { + return ( +
    + + } /> + } /> + } /> + } /> + +
    + ) +} + +function ActionPanel() { + return ( +
    + + } /> + , , ]} /> + , , ]} /> + , ]} /> + +
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/Header.css b/webapp/src/container/Header.css index 651fa5e..0f4286a 100644 --- a/webapp/src/container/Header.css +++ b/webapp/src/container/Header.css @@ -17,7 +17,7 @@ } .Header a { - color: lightgray; + color: black; text-decoration: none; transition: .25s ease; width: fit-content; @@ -38,14 +38,5 @@ .Header a:hover:not(.active) { color: cadetblue; - box-shadow: 0 1.5px 0 0 currentcolor; -} - -[data-darkreader-scheme="dark"] .Header a { - color: darkslategrey; -} - -[data-darkreader-scheme="dark"] .Header .active { - color: white; box-shadow: 0 1.5px 0 0 currentcolor; } \ No newline at end of file diff --git a/webapp/src/container/Header.jsx b/webapp/src/container/Header.jsx index dc4d2a0..553e540 100644 --- a/webapp/src/container/Header.jsx +++ b/webapp/src/container/Header.jsx @@ -23,8 +23,8 @@ export default function Header({ pollingReducer }) { About - - + + diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index 54457c7..6224487 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -22,19 +22,21 @@ export default function Leaderboard({ leaderboard, user }) { }); - return
    - - - - - - - - - - - {tableRows} - -
    PlayedWonDraw
    -
    + return ( +
    + + + + + + + + + + + {tableRows} + +
    PlayedWonDraw
    +
    + ) }; \ No newline at end of file diff --git a/webapp/src/container/games/GameBoard.css b/webapp/src/container/games/GameBoard.css new file mode 100644 index 0000000..9bc2ca5 --- /dev/null +++ b/webapp/src/container/games/GameBoard.css @@ -0,0 +1,3 @@ +.GameBoard .Board { + padding: 5px; +} \ No newline at end of file diff --git a/webapp/src/container/games/GameBoard.jsx b/webapp/src/container/games/GameBoard.jsx new file mode 100644 index 0000000..b5b93fe --- /dev/null +++ b/webapp/src/container/games/GameBoard.jsx @@ -0,0 +1,20 @@ +import './GameBoard.css' +import React from 'react' + +import { Color, Player, Board } from '../../components/Checkers'; + +//import { AppContext } from '../../context/app' + +export default function GameBoard() { + + + //const [ctx] = React.useContext(AppContext) + + return ( +
    + + + +
    + ) +} \ No newline at end of file diff --git a/webapp/src/components/Game/GameAction/Accept.jsx b/webapp/src/container/games/action/Accept.jsx similarity index 50% rename from webapp/src/components/Game/GameAction/Accept.jsx rename to webapp/src/container/games/action/Accept.jsx index 17c7249..5fba232 100644 --- a/webapp/src/components/Game/GameAction/Accept.jsx +++ b/webapp/src/container/games/action/Accept.jsx @@ -2,5 +2,5 @@ import React from 'react'; export default function Accept() { - return + return } diff --git a/webapp/src/container/games/action/Backward.jsx b/webapp/src/container/games/action/Backward.jsx new file mode 100644 index 0000000..4ac5747 --- /dev/null +++ b/webapp/src/container/games/action/Backward.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function Backward() { + + return +} diff --git a/webapp/src/components/Game/GameAction/Cancel.jsx b/webapp/src/container/games/action/Cancel.jsx similarity index 50% rename from webapp/src/components/Game/GameAction/Cancel.jsx rename to webapp/src/container/games/action/Cancel.jsx index 9470172..3d6642e 100644 --- a/webapp/src/components/Game/GameAction/Cancel.jsx +++ b/webapp/src/container/games/action/Cancel.jsx @@ -2,5 +2,5 @@ import React from 'react'; export default function Cancel() { - return + return } diff --git a/webapp/src/container/games/action/Create.jsx b/webapp/src/container/games/action/Create.jsx new file mode 100644 index 0000000..2cee6c6 --- /dev/null +++ b/webapp/src/container/games/action/Create.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function Create() { + + return +} diff --git a/webapp/src/container/games/action/DrawAcq.jsx b/webapp/src/container/games/action/DrawAcq.jsx new file mode 100644 index 0000000..26b9a99 --- /dev/null +++ b/webapp/src/container/games/action/DrawAcq.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function DrawAcq() { + + return +} diff --git a/webapp/src/container/games/action/DrawReq.jsx b/webapp/src/container/games/action/DrawReq.jsx new file mode 100644 index 0000000..11cac07 --- /dev/null +++ b/webapp/src/container/games/action/DrawReq.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function DrawReq() { + + return +} diff --git a/webapp/src/container/games/action/Forward.jsx b/webapp/src/container/games/action/Forward.jsx new file mode 100644 index 0000000..0365bfa --- /dev/null +++ b/webapp/src/container/games/action/Forward.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function Forward() { + + return +} diff --git a/webapp/src/components/Game/GameAction/Reject.jsx b/webapp/src/container/games/action/Reject.jsx similarity index 50% rename from webapp/src/components/Game/GameAction/Reject.jsx rename to webapp/src/container/games/action/Reject.jsx index 9797197..4cc20ba 100644 --- a/webapp/src/components/Game/GameAction/Reject.jsx +++ b/webapp/src/container/games/action/Reject.jsx @@ -2,5 +2,5 @@ import React from 'react'; export default function Reject() { - return + return } diff --git a/webapp/src/container/games/action/Surrender.jsx b/webapp/src/container/games/action/Surrender.jsx new file mode 100644 index 0000000..3a9303d --- /dev/null +++ b/webapp/src/container/games/action/Surrender.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function Surrender() { + + return +} diff --git a/webapp/src/container/games/view/ActiveGames.jsx b/webapp/src/container/games/view/ActiveGames.jsx new file mode 100644 index 0000000..c91ff68 --- /dev/null +++ b/webapp/src/container/games/view/ActiveGames.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function ActiveGames() { + return ( +
    View: ActiveGame
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/view/GameProposals.jsx b/webapp/src/container/games/view/GameProposals.jsx new file mode 100644 index 0000000..f487eb1 --- /dev/null +++ b/webapp/src/container/games/view/GameProposals.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function GameProposals() { + return ( +
    View: GameProposals
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/view/GamesArchive.jsx b/webapp/src/container/games/view/GamesArchive.jsx new file mode 100644 index 0000000..302ae09 --- /dev/null +++ b/webapp/src/container/games/view/GamesArchive.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function GamesArchive() { + return ( +
    View: GameArchive
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/view/NewGame.css b/webapp/src/container/games/view/NewGame.css new file mode 100644 index 0000000..8b828cf --- /dev/null +++ b/webapp/src/container/games/view/NewGame.css @@ -0,0 +1,8 @@ +.SelectPlayer { + border-radius: 5px; + border: 0.5px solid darkgrey; +} + +.SelectPlayer select:hover { + background: lightgray; +} \ No newline at end of file diff --git a/webapp/src/container/games/view/NewGame.jsx b/webapp/src/container/games/view/NewGame.jsx new file mode 100644 index 0000000..ba41be0 --- /dev/null +++ b/webapp/src/container/games/view/NewGame.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +export default function NewGame() { + return ( +
    View: NewGame
    + ) +} + + +// Move to components as DropSelector +function SelectPlayer({ name, setName, nameOptions }) { + const handleSelectChange = (event) => { + setName(event.target.value) + } + + return ( +
    +
    + +
    +
    + ) +} \ No newline at end of file diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js new file mode 100644 index 0000000..ebe8eac --- /dev/null +++ b/webapp/src/reducer/games.js @@ -0,0 +1,21 @@ +import { useReducer } from 'react'; +import { nextState } from '../util/StateHelper'; + +export const gamesInitialState = { + list: null, +}; + +export function gamesReducer(state, action) { + switch (action.type) { + + case 'next': + return nextState(state, action); + + default: + throw Error('GameReducer: unknown action.type', action.type); + } +} + +export default function useGamesReducer() { + return useReducer(gamesReducer, gamesInitialState); +} \ No newline at end of file diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js index 4128100..09b8f86 100644 --- a/webapp/src/reducer/user.js +++ b/webapp/src/reducer/user.js @@ -17,7 +17,7 @@ export function userReducer(state, action) { return nextState(state, action); default: - throw Error('Unknown action.type', action.type); + throw Error('UserReducer: unknown action.type', action.type); } } -- 2.45.2 From 7314b8c328e9569caac7a1cb5d029fd5dbdbd94e Mon Sep 17 00:00:00 2001 From: djmil Date: Wed, 8 Nov 2023 19:30:06 +0100 Subject: [PATCH 09/15] move Header into App --- webapp/src/App.css | 43 ++++++++++++++++++++++++++++++ webapp/src/App.js | 47 +++++++++++++++++++++++++++------ webapp/src/container/Header.css | 42 ----------------------------- webapp/src/container/Header.jsx | 36 ------------------------- 4 files changed, 82 insertions(+), 86 deletions(-) delete mode 100644 webapp/src/container/Header.css delete mode 100644 webapp/src/container/Header.jsx diff --git a/webapp/src/App.css b/webapp/src/App.css index 88346b6..4178dc1 100644 --- a/webapp/src/App.css +++ b/webapp/src/App.css @@ -1,3 +1,46 @@ +.Header .OnlineToggle { + transform: scale(.5); + margin-left: -19px; +} + +.Header { + display: flex; +} + +.Header nav { + align-items: center; + justify-content: center; + display: flex; + flex-wrap: wrap; + + padding-top: 10px; +} + +.Header a { + color: black; + text-decoration: none; + transition: .25s ease; + width: fit-content; + + margin-left: 5px; + margin-right: 5px; + padding: 0.25rem 1rem; +} + +.Header .active { + color: white; + border-radius: 2px; + background-color: cadetblue; + opacity: 80%; + padding: 0.25rem 1rem; +} + +.Header a:hover:not(.active) { + color: cadetblue; + + box-shadow: 0 1.5px 0 0 currentcolor; +} + [data-darkreader-scheme="dark"] .Header a { color: darkslategrey; } diff --git a/webapp/src/App.js b/webapp/src/App.js index a96c5c1..16d5a50 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,8 +1,10 @@ import './App.css'; import React from 'react'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom'; + +import OnlineToggle from './components/OnlineToggle'; +import Wobler from './components/Wobler'; -import Header from './container/Header'; import About from "./components/About" import Games from './container/Games'; import Leaderboard from './container/Leaderboard'; @@ -16,19 +18,19 @@ import useUserApi from './api/user'; import useLeaderboardApi from './api/leaderboard'; import useGamesApi from './api/games'; -function App() { +export default function App() { const userReducer = useUserReducer(); - const pollingReducer = usePollingReducer(); + const pollingFlux = usePollingReducer(); const leaderboardReducer = useLeaderboardReducer(); const gamesReducer = useGamesReducer(); const user = useUserApi(userReducer).get(); - const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingReducer); - /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingReducer); + const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingFlux); + /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingFlux); return ( -
    +
    } /> } /> @@ -39,4 +41,33 @@ function App() { ) } -export default App; +function Header({ pollingFlux }) { + const [polling, dispatchPolling] = pollingFlux; + + return ( +
    +

    + CordaCheckers +

    + + dispatchPolling({ type: "toggleOnOff" })} + /> + + +
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/Header.css b/webapp/src/container/Header.css deleted file mode 100644 index 0f4286a..0000000 --- a/webapp/src/container/Header.css +++ /dev/null @@ -1,42 +0,0 @@ -.OnlineToggle { - transform: scale(.5); - margin-left: -19px; -} - -.Header { - display: flex; -} - -.Header nav { - align-items: center; - justify-content: center; - display: flex; - flex-wrap: wrap; - - padding-top: 10px; -} - -.Header a { - color: black; - text-decoration: none; - transition: .25s ease; - width: fit-content; - - margin-left: 5px; - margin-right: 5px; - padding: 0.25rem 1rem; -} - -.Header .active { - color: white; - border-radius: 2px; - background-color: cadetblue; - opacity: 80%; - padding: 0.25rem 1rem; -} - -.Header a:hover:not(.active) { - color: cadetblue; - - box-shadow: 0 1.5px 0 0 currentcolor; -} \ No newline at end of file diff --git a/webapp/src/container/Header.jsx b/webapp/src/container/Header.jsx deleted file mode 100644 index 553e540..0000000 --- a/webapp/src/container/Header.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import './Header.css'; -import React from "react" -import { NavLink } from "react-router-dom"; -import OnlineToggle from '../components/OnlineToggle'; -import Wobler from '../components/Wobler'; - -export default function Header({ pollingReducer }) { - const [polling, dispatchPolling] = pollingReducer; - - return ( -
    -

    - CordaCheckers -

    - - dispatchPolling({ type: "toggleOnOff" })} - /> - - -
    - ) -} \ No newline at end of file -- 2.45.2 From 3f47654cf2719d8b25be24d1344291afa37aee7f Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 9 Nov 2023 12:29:47 +0100 Subject: [PATCH 10/15] react: state -> reducer -> context - Leaderboard: useState - User: useReducer - Games: useContext [...in progress] - usePolling giveup on internal cache in favour of onResponce() callback --- webapp/src/App.js | 42 +++++----- webapp/src/api/games.js | 18 ++-- webapp/src/api/leaderboard.js | 19 ++--- webapp/src/api/user.js | 15 ++-- webapp/src/container/Leaderboard.jsx | 7 +- webapp/src/context/app/reducer.js | 84 ------------------- webapp/src/context/data/Poll.js | 46 ---------- webapp/src/context/data/index.jsx | 40 --------- webapp/src/context/data/reducer.js | 25 ------ .../src/context/{app/index.jsx => games.jsx} | 8 +- webapp/src/index.js | 9 +- webapp/src/reducer/leaderboard.js | 21 ----- webapp/src/reducer/user.js | 18 +++- webapp/src/util/Polling.js | 66 ++++++++------- 14 files changed, 96 insertions(+), 322 deletions(-) delete mode 100644 webapp/src/context/app/reducer.js delete mode 100644 webapp/src/context/data/Poll.js delete mode 100644 webapp/src/context/data/index.jsx delete mode 100644 webapp/src/context/data/reducer.js rename webapp/src/context/{app/index.jsx => games.jsx} (55%) delete mode 100644 webapp/src/reducer/leaderboard.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 16d5a50..84e5661 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -9,40 +9,36 @@ import About from "./components/About" import Games from './container/Games'; import Leaderboard from './container/Leaderboard'; -import useUserReducer from './reducer/user'; import usePollingReducer from './reducer/polling'; -import useLeaderboardReducer from './reducer/leaderboard'; -import useGamesReducer from './reducer/games'; +//import useGamesReducer from './reducer/games'; import useUserApi from './api/user'; import useLeaderboardApi from './api/leaderboard'; -import useGamesApi from './api/games'; +//import useGamesApi from './api/games'; export default function App() { - const userReducer = useUserReducer(); - const pollingFlux = usePollingReducer(); - const leaderboardReducer = useLeaderboardReducer(); - const gamesReducer = useGamesReducer(); + const pollingReducer = usePollingReducer(); + //const gamesReducer = useGamesReducer(); - const user = useUserApi(userReducer).get(); - const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingFlux); - /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingFlux); + //const games = useGamesApi(gamesReducer).list(pollingReducer); + const leaderboard = useLeaderboardApi().poll(pollingReducer); + const user = useUserApi().get(); return ( -
    +
    - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> ) } -function Header({ pollingFlux }) { - const [polling, dispatchPolling] = pollingFlux; +function Header({ pollingReducer }) { + const [polling, dispatchPolling] = pollingReducer; return (
    @@ -52,20 +48,20 @@ function Header({ pollingFlux }) { dispatchPolling({ type: "toggleOnOff" })} + onClick={() => dispatchPolling({ type: 'toggleOnOff' })} />
    diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index aefbdbb..df2a168 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -1,25 +1,19 @@ import usePolling from "../util/Polling" -const uri = '/api/games'; - -export default function useGamesApi(gamesReducer) { - const [games, dispatchGames] = gamesReducer; +export default function useGamesApi(gamesState) { + const [games, setGames] = gamesState; const useList = (pollingReducer) => { const [polling, dispatchPolling] = pollingReducer; const mode = (polling.enabled === true) - ? { interval_sec: 30 } // update games list half a minue + ? { interval_sec: 30 } // update games list every half a minue : { interval_stop: true } // user has fliped OfflineToggel - const [list, isFetching] = usePolling(uri, mode); + const isPolling = usePolling('/api/games', setGames, mode); - if (polling.games !== isFetching) { - dispatchPolling({ type: 'next', games: isFetching }); - } - - if (games.list !== list) { - dispatchGames({ type: 'next', list }); + if (isPolling !== polling.games) { + dispatchPolling({ type: 'next', games: isPolling }); } return games; diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js index f5d01f9..a653e35 100644 --- a/webapp/src/api/leaderboard.js +++ b/webapp/src/api/leaderboard.js @@ -1,9 +1,8 @@ -import usePolling from "../util/Polling" +import { useState } from "react"; +import usePolling from "../util/Polling"; -const uri = '/api/leaderboard'; - -export default function useLeaderboardApi(leaderboardReducer) { - const [leaderboard, dispatchLeaderboaed] = leaderboardReducer; +export default function useLeaderboardApi() { + const [leaderboard, setLeaderboard] = useState(null); const usePoll = (pollingReducer) => { const [polling, dispatchPolling] = pollingReducer; @@ -12,14 +11,10 @@ export default function useLeaderboardApi(leaderboardReducer) { ? { interval_sec: 300 } // update leaderbord stats every 5 min : { interval_stop: true } // user has fliped OfflineToggel - const [table, isFetching] = usePolling(uri, mode); + const isPolling = usePolling('/api/leaderboard', setLeaderboard, mode); - if (polling.leaderboard !== isFetching) { - dispatchPolling({ type: 'next', leaderboard: isFetching }); - } - - if (leaderboard.table !== table) { - dispatchLeaderboaed({ type: 'next', table }); + if (isPolling !== polling.leaderboard) { + dispatchPolling({ type: 'next', leaderboard: isPolling }); } return leaderboard; diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js index 7f8606a..83106b3 100644 --- a/webapp/src/api/user.js +++ b/webapp/src/api/user.js @@ -1,16 +1,15 @@ -import usePolling from "../util/Polling" +import usePolling from "../util/Polling"; +import useUserReducer from "../reducer/user"; -const uri = '/api/user'; - -export default function useUserApi([user, dispatchUser]) { +export default function useUserApi() { + const [user, dispatchUser] = useUserReducer(); const useGet = () => { - const [nextUser] = usePolling(uri); - - if (typeof nextUser?.username === 'string' && nextUser.username !== user.username) { - dispatchUser({ type: "next", username: nextUser.username }); + const onResponce = (json) => { + dispatchUser({ type: "parse", json }); } + usePolling('/api/user', onResponce); // <<-- fetch once return user; } diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index 6224487..6d8863e 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -4,15 +4,14 @@ import Loading from '../components/Loading'; export default function Leaderboard({ leaderboard, user }) { - const table = leaderboard?.table; - if (!table) + if (leaderboard == null) return const isCurrentUser = (playerName) => user?.isCurrentUser(playerName) === true ? true : null; - const tableRows = Object.keys(table).map(playerName => { - var rank = table[playerName]; + const tableRows = Object.keys(leaderboard).map(playerName => { + var rank = leaderboard[playerName]; return {playerName} diff --git a/webapp/src/context/app/reducer.js b/webapp/src/context/app/reducer.js deleted file mode 100644 index bb53b1d..0000000 --- a/webapp/src/context/app/reducer.js +++ /dev/null @@ -1,84 +0,0 @@ -export const reducer = (state, action) => { - - switch (action.update) { - - case "game-selector": - return GameSelector_update(state, action) - - case "newGame": - return updateNewGame(state, action) - - default: - console.warn("Unknown action.component", action.component) - return state - } -} - -export const initialState = { - gameSelector: { - selectedGameProposal: null, - selectedActiveGame: null, - selectedArchiveGame: null, - }, - - newGame: { - whitePlayer: '', - blackPlayer: '', - message: '', - fetching: false, - }, - -} - -function GameSelector_update(state, action) { - if (Object.hasOwn(action, 'selectedGameProposal')) { - return { - ...state, - gameSelector: { - ...state.gameSelector, - selectedGameProposal: action.selectedGameProposal - } - } - } - - if (Object.hasOwn(action, 'selectedActiveGame')) { - return { - ...state, - gameSelector: { - ...state.gameSelector, - selectedActiveGame: action.selectedActiveGame - } - } - } - - if (Object.hasOwn(action, 'selectedArchiveGame')) { - return { - ...state, - gameSelector: { - ...state.gameSelector, - selectedArchiveGame: action.selectedArchiveGame - } - } - } - - console.warn(action.component, "- bad property") -} - -function updateNewGame(state, action) { - const newGame = {...state.newGame} - - Object.keys(action) - .slice(1) // skip 'update' property - .forEach(actionKey => { - if (Object.hasOwn(newGame, actionKey)) { - newGame[actionKey] = action[actionKey] - } else { - console.warn("NewGame update: bad action property\n", actionKey + ":", action[actionKey]) - } - }) - - return { - ...state, - newGame - } -} diff --git a/webapp/src/context/data/Poll.js b/webapp/src/context/data/Poll.js deleted file mode 100644 index 9d6d209..0000000 --- a/webapp/src/context/data/Poll.js +++ /dev/null @@ -1,46 +0,0 @@ -import { useState, useCallback, useEffect, } from "react" - -/* - TODO: Poll(uri, flavour) - - uri: string - - execution_flvour: - - once (i.e. now) - - interval (sec) - - stop -*/ - -export default function Poll(url, interval_sec, offlineMode) { - const [dataCache, setDataCache] = useState(null) - const [fetching , setFetching ] = useState(false) - const [timeoutID, setTimeoutID] = useState(null) - - const fecthData = useCallback(() => { - setTimeoutID(null) - setFetching(true) - - fetch(url) - .then((response) => { - setFetching(false) - return response.json() - }) - .then((freshData) => setDataCache(freshData)) - .catch((err) => console.log(err.message)) - }, [url]) - - useEffect(() => { - if (dataCache == null) { - fecthData() // <<-- run immediatly on startup - } - else if (offlineMode === true) { - clearTimeout(timeoutID) // cancel already scheduled fetch - setTimeoutID(null) // & stop interval fetching - } - else if (timeoutID === null && typeof interval_sec === 'number') { - const timeoutID = setTimeout(fecthData, interval_sec * 1000) - setTimeoutID(timeoutID) - console.log("Fetch '" +url +"' scheduled in " +interval_sec +" sec") - } - }, [url, dataCache, fecthData, timeoutID, offlineMode, interval_sec]); - - return [ dataCache, fetching ] -} diff --git a/webapp/src/context/data/index.jsx b/webapp/src/context/data/index.jsx deleted file mode 100644 index e7fa95e..0000000 --- a/webapp/src/context/data/index.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react" -import { reducer, initialState } from "./reducer" - -import Poll from "./Poll" - -export const AppData = React.createContext({ - state: initialState, - dispatch: () => null -}) - -export const AppDataProvider = ({ children }) => { - - const [data, dispatchData] = React.useReducer(reducer, initialState) - - const [games, gamesFetching ] = Poll('/api/gamestate' , 30, data.offlineMode) - const [leaderboard, leaderboardFetching ] = Poll('/api/leaderboard', 60, data.offlineMode) - const [user] = Poll('/api/user') // once - - data.games = games - data.gamesFetching = gamesFetching - - data.leaderboard = leaderboard - data.leaderboardFetching = leaderboardFetching - - data.isCurrentUser = (otherUsername) => { - return user?.username && ciEquals(user.username, otherUsername) ? true : null - } - - return ( - - {children} - - ) -} - -function ciEquals(a, b) { - return typeof a === 'string' && typeof b === 'string' - ? a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0 - : a === b; -} \ No newline at end of file diff --git a/webapp/src/context/data/reducer.js b/webapp/src/context/data/reducer.js deleted file mode 100644 index 6e9e065..0000000 --- a/webapp/src/context/data/reducer.js +++ /dev/null @@ -1,25 +0,0 @@ -export const reducer = (state, action) => { - switch (action.type) { - - case "toggleOfflineMode": - return { ...state, - offlineMode: !state.offlineMode // on/off - } - - default: - console.warn("Unknown action.type", action) - return state - } -} - -export const initialState = { - games: null, - gamesFetching: false, - - leaderboard: null, - leaderboardFetching: false, - - isCurrentUser: () => null, - - offlineMode: false -} diff --git a/webapp/src/context/app/index.jsx b/webapp/src/context/games.jsx similarity index 55% rename from webapp/src/context/app/index.jsx rename to webapp/src/context/games.jsx index f4be27c..d747333 100644 --- a/webapp/src/context/app/index.jsx +++ b/webapp/src/context/games.jsx @@ -1,17 +1,17 @@ import React from "react" import { reducer, initialState } from "./reducer" -export const AppContext = React.createContext({ +export const Games = React.createContext({ state: initialState, dispatch: () => null }) -export const AppContextProvider = ({ children }) => { +export const GamesProvider = ({ children }) => { const [state, dispatch] = React.useReducer(reducer, initialState) return ( - + { children } - + ) } diff --git a/webapp/src/index.js b/webapp/src/index.js index d65c14e..71bb868 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -3,18 +3,11 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import reportWebVitals from './reportWebVitals'; import App from './App'; -import { AppDataProvider } from "./context/data" -import { AppContextProvider } from "./context/app" const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - - - - + ); diff --git a/webapp/src/reducer/leaderboard.js b/webapp/src/reducer/leaderboard.js deleted file mode 100644 index 06352a4..0000000 --- a/webapp/src/reducer/leaderboard.js +++ /dev/null @@ -1,21 +0,0 @@ -import { useReducer } from 'react'; -import { nextState } from '../util/StateHelper'; - -export const leaderboardInitialState = { - table: null, -}; - -export function leaderboardReducer(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(leaderboardReducer, leaderboardInitialState); -} \ No newline at end of file diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js index 09b8f86..151c220 100644 --- a/webapp/src/reducer/user.js +++ b/webapp/src/reducer/user.js @@ -1,6 +1,6 @@ import { useReducer } from 'react'; import { localeCompare } from '../util/Locale'; -import { nextState } from '../util/StateHelper'; +//import { nextState } from '../util/StateHelper'; export const userInitialState = { username: '', @@ -13,8 +13,13 @@ export const userInitialState = { export function userReducer(state, action) { switch (action.type) { - case 'next': - return nextState(state, action); + case 'parse': + const apiData = parse(action.json); + + return { + ...state, + ...apiData + }; default: throw Error('UserReducer: unknown action.type', action.type); @@ -23,4 +28,11 @@ export function userReducer(state, action) { export default function useUserReducer() { return useReducer(userReducer, userInitialState); +} + +function parse(json) { + console.log("userreducer.parse", json); + return { + username: json.username + } } \ No newline at end of file diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js index 49a1f9a..4446656 100644 --- a/webapp/src/util/Polling.js +++ b/webapp/src/util/Polling.js @@ -3,51 +3,53 @@ import { useState, useCallback, useEffect, } from "react" /* - uri: string - mode: - - null - default, return cashed value while polling fresh one from server) + - null - default, fetch data ONCE - interval_sec - interval_stop */ -export default function usePolling(url, mode) { - const [cache, setCache] = useState(null); - const [isFetching, setFetching] = useState(false); - const [delayID, setDelayID] = useState(null); +export default function usePolling(uri, onResponce, mode = null) { + const [initialPoll, setInitialPoll] = useState(true); + const [isPolling, setPolling] = useState(false); + const [intervalTimer, setIntervalTimer] = useState(null); - const fetchData = useCallback(() => { - setDelayID(null); - setFetching(true); + const pollData = useCallback(() => { + setPolling(true); + setInitialPoll(false); - fetch(url) - .then((response) => response.json()) - .then((freshData) => { - setCache(freshData); - setFetching(false); + fetch(uri) + .then((responce) => { + setPolling(false); + + if (typeof mode?.interval_sec === 'number') { + console.log("Schedule", uri, "fetch in", mode.interval_sec, "sec"); + const intervalTimer = setTimeout(pollData, mode.interval_sec * 1000); + setIntervalTimer(intervalTimer); + } + + return responce.json(); + }) + .then((json) => { + onResponce(json); }) .catch((err) => { console.warn(err.message); - setFetching(false); }) - }, [url]) + }, [uri, mode, onResponce]); useEffect(() => { - if (cache === null && isFetching === false) { - fetchData(); + if ((initialPoll || (typeof mode?.interval_sec === 'number' && intervalTimer === null)) && !isPolling) { + pollData(); } + }, [initialPoll, mode, intervalTimer, isPolling, pollData]); - if (mode?.interval_sec && delayID === null) { - const timeoutID = setTimeout(fetchData, mode.interval_sec * 1000) - setDelayID(timeoutID) - console.log("Fetch '" + url + "' scheduled in " + mode.interval_sec + " sec") - } - else if (mode?.interval_stop) { - clearTimeout(delayID); // cancel already scheduled fetch - setDelayID(null); - } + if (mode?.interval_stop && intervalTimer) { + console.log("Cancel scheduled fetch for", uri); + clearTimeout(intervalTimer); + setIntervalTimer(null); + setInitialPoll(true); + } - }, [url, mode, isFetching, cache, fetchData, delayID]); - return [ - cache, // API responce - isFetching // true / false - ] -} + return isPolling; +} \ No newline at end of file -- 2.45.2 From ac50d92c1a7d99b575e2c19984d21d8bd86bdb6f Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 9 Nov 2023 13:59:14 +0100 Subject: [PATCH 11/15] GamesContext: initial implementation --- webapp/src/App.js | 12 ++++--- webapp/src/api/games.js | 15 ++++---- webapp/src/api/user.js | 1 + webapp/src/container/Games.jsx | 39 ++++++++++++--------- webapp/src/container/games/view/NewGame.jsx | 7 +++- webapp/src/context/games.jsx | 31 ++++++++-------- webapp/src/reducer/games.js | 2 +- webapp/src/reducer/user.js | 12 +------ 8 files changed, 64 insertions(+), 55 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index 84e5661..549c55d 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -10,27 +10,29 @@ import Games from './container/Games'; import Leaderboard from './container/Leaderboard'; import usePollingReducer from './reducer/polling'; -//import useGamesReducer from './reducer/games'; +import useGamesReducer from './reducer/games'; import useUserApi from './api/user'; import useLeaderboardApi from './api/leaderboard'; -//import useGamesApi from './api/games'; +import useGamesApi from './api/games'; export default function App() { const pollingReducer = usePollingReducer(); - //const gamesReducer = useGamesReducer(); - //const games = useGamesApi(gamesReducer).list(pollingReducer); const leaderboard = useLeaderboardApi().poll(pollingReducer); const user = useUserApi().get(); + const [games, dispatchGames] = useGamesReducer(); + const gamesApi = useGamesApi(dispatchGames); + gamesApi.list(pollingReducer); + return (
    } /> } /> - } /> + } /> } /> diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index df2a168..f0d2e25 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -1,22 +1,23 @@ -import usePolling from "../util/Polling" +import usePolling from '../util/Polling'; -export default function useGamesApi(gamesState) { - const [games, setGames] = gamesState; +export default function useGamesApi(dispatchGames) { const useList = (pollingReducer) => { const [polling, dispatchPolling] = pollingReducer; + const onResponce = (json) => { + dispatchGames({ type: 'next', list: json }); + } + const mode = (polling.enabled === true) - ? { interval_sec: 30 } // update games list every half a minue + ? { interval_sec: 30 } // fetch gamesList every half a minue : { interval_stop: true } // user has fliped OfflineToggel - const isPolling = usePolling('/api/games', setGames, mode); + const isPolling = usePolling('/api/games', onResponce, mode); if (isPolling !== polling.games) { dispatchPolling({ type: 'next', games: isPolling }); } - - return games; } return { diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js index 83106b3..1b863a0 100644 --- a/webapp/src/api/user.js +++ b/webapp/src/api/user.js @@ -10,6 +10,7 @@ export default function useUserApi() { } usePolling('/api/user', onResponce); // <<-- fetch once + return user; } diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index c045551..fe5f91d 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -1,6 +1,6 @@ import './Games.css'; -import React from "react" -import { NavLink, Routes, Route } from "react-router-dom"; +import React from 'react'; +import { NavLink, Routes, Route } from 'react-router-dom'; import NewGame from './games/view/NewGame'; import GameProposals from './games/view/GameProposals'; @@ -19,23 +19,30 @@ import Forward from './games/action/Forward'; import GameBoard from './games/GameBoard'; -export default function Games({ gamesReducer, user }) { +import { GamesContext, GamesDispatchContext, GamesApiContext } from '../context/games'; + +export default function Games({ games, dispatchGames, gamesApi }) { return (
    -
    - - -
    -
    - - - {/* - - - */} -
    -
    + + + +
    + + +
    +
    + + + {/* + + */} +
    +
    +
    +
    +
    ) }; diff --git a/webapp/src/container/games/view/NewGame.jsx b/webapp/src/container/games/view/NewGame.jsx index ba41be0..02e58df 100644 --- a/webapp/src/container/games/view/NewGame.jsx +++ b/webapp/src/container/games/view/NewGame.jsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React, { useContext } from 'react'; +import { GamesContext } from '../../../context/games'; export default function NewGame() { + const games = useContext(GamesContext); + + console.log("NewGame", games); + return (
    View: NewGame
    ) diff --git a/webapp/src/context/games.jsx b/webapp/src/context/games.jsx index d747333..69d370c 100644 --- a/webapp/src/context/games.jsx +++ b/webapp/src/context/games.jsx @@ -1,17 +1,20 @@ -import React from "react" -import { reducer, initialState } from "./reducer" +import { createContext } from 'react'; -export const Games = React.createContext({ - state: initialState, - dispatch: () => null -}) +export const GamesContext = createContext(null); +export const GamesDispatchContext = createContext(null); +export const GamesApiContext = createContext(null); -export const GamesProvider = ({ children }) => { - const [state, dispatch] = React.useReducer(reducer, initialState) +// export const Games = React.createContext({ +// state: initialState, +// dispatch: () => null +// }) - return ( - - { children } - - ) -} +// export const GamesProvider = ({ children }) => { +// const [state, dispatch] = React.useReducer(reducer, initialState) + +// return ( +// +// { children } +// +// ) +// } \ No newline at end of file diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index ebe8eac..95e940e 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -12,7 +12,7 @@ export function gamesReducer(state, action) { return nextState(state, action); default: - throw Error('GameReducer: unknown action.type', action.type); + throw Error('GamesReducer: unknown action.type', action.type); } } diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js index 151c220..0351553 100644 --- a/webapp/src/reducer/user.js +++ b/webapp/src/reducer/user.js @@ -1,6 +1,5 @@ import { useReducer } from 'react'; import { localeCompare } from '../util/Locale'; -//import { nextState } from '../util/StateHelper'; export const userInitialState = { username: '', @@ -14,11 +13,9 @@ export function userReducer(state, action) { switch (action.type) { case 'parse': - const apiData = parse(action.json); - return { ...state, - ...apiData + username: action.json.username }; default: @@ -28,11 +25,4 @@ export function userReducer(state, action) { export default function useUserReducer() { return useReducer(userReducer, userInitialState); -} - -function parse(json) { - console.log("userreducer.parse", json); - return { - username: json.username - } } \ No newline at end of file -- 2.45.2 From 6b8b75ba7ff0a512228d3ed33c148991fc564744 Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 9 Nov 2023 15:59:38 +0100 Subject: [PATCH 12/15] component params grouping --- webapp/src/App.js | 9 +++++++-- webapp/src/container/Games.jsx | 18 +++++++++--------- webapp/src/container/Leaderboard.jsx | 9 ++++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index 549c55d..379665f 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -26,14 +26,19 @@ export default function App() { const gamesApi = useGamesApi(dispatchGames); gamesApi.list(pollingReducer); + const players = { + leaderboard, + isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null + }; + return (
    } /> } /> - } /> - } /> + } /> + } /> ) diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index fe5f91d..fcdeed3 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -21,13 +21,13 @@ import GameBoard from './games/GameBoard'; import { GamesContext, GamesDispatchContext, GamesApiContext } from '../context/games'; -export default function Games({ games, dispatchGames, gamesApi }) { +export default function Games({ context }) { return ( -
    - - - + + + +
    @@ -39,10 +39,10 @@ export default function Games({ games, dispatchGames, gamesApi }) { */}
    - - - -
    +
    + + + ) }; diff --git a/webapp/src/container/Leaderboard.jsx b/webapp/src/container/Leaderboard.jsx index 6d8863e..5e447eb 100644 --- a/webapp/src/container/Leaderboard.jsx +++ b/webapp/src/container/Leaderboard.jsx @@ -2,18 +2,17 @@ import './Leaderboard.css'; import React from "react" import Loading from '../components/Loading'; -export default function Leaderboard({ leaderboard, user }) { +export default function Leaderboard({ players }) { + + const leaderboard = players.leaderboard; if (leaderboard == null) return - const isCurrentUser = (playerName) => - user?.isCurrentUser(playerName) === true ? true : null; - const tableRows = Object.keys(leaderboard).map(playerName => { var rank = leaderboard[playerName]; - return + return {playerName} {rank.gamesPlayed} {rank.gamesWon} -- 2.45.2 From b632aa7dc35c11408d1151e053b1d90417202773 Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 9 Nov 2023 18:19:31 +0100 Subject: [PATCH 13/15] GameSelector - single component for GameProposals, ActiveGames and Archive - clickabple and scrollable --- webapp/src/components/Checkers.jsx | 10 +--- .../src/container/games/view/ActiveGames.jsx | 7 --- .../container/games/view/GameProposals.jsx | 7 --- .../src/container/games/view/GameSelector.css | 48 +++++++++++++++++ .../src/container/games/view/GameSelector.jsx | 52 +++++++++++++++++++ .../src/container/games/view/GamesArchive.jsx | 7 --- 6 files changed, 102 insertions(+), 29 deletions(-) delete mode 100644 webapp/src/container/games/view/ActiveGames.jsx delete mode 100644 webapp/src/container/games/view/GameProposals.jsx create mode 100644 webapp/src/container/games/view/GameSelector.css create mode 100644 webapp/src/container/games/view/GameSelector.jsx delete mode 100644 webapp/src/container/games/view/GamesArchive.jsx diff --git a/webapp/src/components/Checkers.jsx b/webapp/src/components/Checkers.jsx index 7f7f220..7d0817c 100644 --- a/webapp/src/components/Checkers.jsx +++ b/webapp/src/components/Checkers.jsx @@ -110,11 +110,9 @@ export function Board() { } function WhiteTile({ id, stone }) { - return ( -
    handleClick(id)} +
    console.log('click', id)} > {stone}
    @@ -123,8 +121,4 @@ function WhiteTile({ id, stone }) { function BlackTile() { return
    -} - -function handleClick(i) { - console.log("click", i) } \ No newline at end of file diff --git a/webapp/src/container/games/view/ActiveGames.jsx b/webapp/src/container/games/view/ActiveGames.jsx deleted file mode 100644 index c91ff68..0000000 --- a/webapp/src/container/games/view/ActiveGames.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -export default function ActiveGames() { - return ( -
    View: ActiveGame
    - ) -} \ No newline at end of file diff --git a/webapp/src/container/games/view/GameProposals.jsx b/webapp/src/container/games/view/GameProposals.jsx deleted file mode 100644 index f487eb1..0000000 --- a/webapp/src/container/games/view/GameProposals.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -export default function GameProposals() { - return ( -
    View: GameProposals
    - ) -} \ No newline at end of file diff --git a/webapp/src/container/games/view/GameSelector.css b/webapp/src/container/games/view/GameSelector.css new file mode 100644 index 0000000..1722aa6 --- /dev/null +++ b/webapp/src/container/games/view/GameSelector.css @@ -0,0 +1,48 @@ +.GameSelector { + flex: 1 1 auto; + overflow-y: scroll; +} + +.Selectable { + border: 1px solid black; + margin-bottom: 5px; +} + +.Selectable q { + color: gray; +} + +.Selectable i { + font-size: 70%; + margin-left: 5px; + margin-right: 5px; +} + +.Selectable:hover { + background-color: #d3d3d360; +} + +/* .Games .li button.action { + display: none; +} + +.Games .li:hover button.action { + display: initial; +} */ + +.Separator { + /* width: 20%; */ + /* height: 20px; */ + border-bottom: 1px dotted black; + text-align: center; + font-size: 50%; + padding-left: 50%; + margin-bottom: 7px; +} + +.Selectable .Title { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} diff --git a/webapp/src/container/games/view/GameSelector.jsx b/webapp/src/container/games/view/GameSelector.jsx new file mode 100644 index 0000000..5578e7c --- /dev/null +++ b/webapp/src/container/games/view/GameSelector.jsx @@ -0,0 +1,52 @@ +import './GameSelector.css'; +import React, { useContext } from 'react'; +import { GamesContext } from '../../../context/games'; + +import { Color, Player } from '../../../components/Checkers'; +import Loading from '../../../components/Loading'; + +export default function GameSelector({ yours, opponents, onClick }) { + + const games = useContext(GamesContext); + if (games.list === null) + return + + const yoursList = games.list.filter(game => game.status === yours) + .map(game => ) + + const opponentsList = games.list.filter(game => game.status === opponents) + .map(game => ) + + return ( +
    + {yoursList} + {opponentsList.length > 0 && } + {opponentsList} +
    + ) +} + +function Selectable({ game, onClick }) { + const myColor = game.myColor; + const opponentColor = Color.opposite(myColor); + const opponentName = game.opponentName; + + return ( +
    onClick(game.uuid)}> +
    + + vs + +
    + {game.message} +
    + ) +}; + +function Separator({ counter }) { + return ( +
    + waiting for opponent ({counter}) +
    + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/view/GamesArchive.jsx b/webapp/src/container/games/view/GamesArchive.jsx deleted file mode 100644 index 302ae09..0000000 --- a/webapp/src/container/games/view/GamesArchive.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -export default function GamesArchive() { - return ( -
    View: GameArchive
    - ) -} \ No newline at end of file -- 2.45.2 From 3171a97827e8356e29d09dd70700b06ad11bb305 Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 9 Nov 2023 18:20:19 +0100 Subject: [PATCH 14/15] ammend --- webapp/src/container/Games.css | 3 ++- webapp/src/container/Games.jsx | 42 ++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/webapp/src/container/Games.css b/webapp/src/container/Games.css index 54be4a0..3d8680a 100644 --- a/webapp/src/container/Games.css +++ b/webapp/src/container/Games.css @@ -63,7 +63,8 @@ .ViewProvider { display: flex; - flex-direction: column; + flex-flow: column; + height: 340px; justify-content: center; align-items: center; } diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index fcdeed3..0ec98b8 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -3,9 +3,7 @@ import React from 'react'; import { NavLink, Routes, Route } from 'react-router-dom'; import NewGame from './games/view/NewGame'; -import GameProposals from './games/view/GameProposals'; -import ActiveGames from './games/view/ActiveGames'; -import GamesArchive from './games/view/GamesArchive'; +import GameSelector from './games/view/GameSelector'; import Create from './games/action/Create'; import Reject from './games/action/Reject'; @@ -27,7 +25,7 @@ export default function Games({ context }) { -
    +
    @@ -47,24 +45,34 @@ export default function Games({ context }) { }; function ViewSelector() { + // TODO: counter Wating for YOU + return ( ) } -function ViewProvider() { +function ViewProvider(/*todo: dispatchGame*/) { return (
    - } /> - } /> - } /> - } /> + } /> + + console.log("GameProposal", uuid)} + /> + } /> + + } /> + } />
    ) @@ -74,10 +82,10 @@ function ActionPanel() { return (
    - } /> - , , ]} /> - , , ]} /> - , ]} /> + } /> + , , ]} /> + , , ]} /> + , ]} />
    ) -- 2.45.2 From 76eb556d096cf4faf7ee138e5552a9ff16fb660a Mon Sep 17 00:00:00 2001 From: djmil Date: Fri, 10 Nov 2023 09:34:46 +0100 Subject: [PATCH 15/15] NewGame to use dispatchGames - remove DispatchGames and GamesAPI Context transfer it as props instead - DropdownList component - delete obsoleete files --- webapp/src/components/DropdownList.jsx | 15 ++++ webapp/src/components/Game/GameMessage.css | 6 -- webapp/src/components/Game/GameMessage.jsx | 15 ---- webapp/src/components/Game/GameSelector.css | 42 ----------- webapp/src/components/Game/GameSelector.jsx | 44 ------------ .../Game/GameSelector/ActiveGames.jsx | 32 --------- .../Game/GameSelector/GameArchive.jsx | 32 --------- .../Game/GameSelector/GameProposal.jsx | 30 -------- .../Game/GameSelector/Selectable.css | 43 ----------- .../Game/GameSelector/Selectable.jsx | 24 ------- webapp/src/components/Game/GameView.css | 30 -------- webapp/src/components/Game/GameView.jsx | 15 ---- webapp/src/components/Game/NewGame.css | 17 ----- webapp/src/components/Game/NewGame.jsx | 72 ------------------- webapp/src/container/Games.jsx | 40 ++++++----- webapp/src/container/games/view/NewGame.css | 15 ++-- webapp/src/container/games/view/NewGame.jsx | 64 ++++++++++++----- webapp/src/context/games.jsx | 2 - webapp/src/reducer/games.js | 5 ++ 19 files changed, 99 insertions(+), 444 deletions(-) create mode 100644 webapp/src/components/DropdownList.jsx delete mode 100644 webapp/src/components/Game/GameMessage.css delete mode 100644 webapp/src/components/Game/GameMessage.jsx delete mode 100644 webapp/src/components/Game/GameSelector.css delete mode 100644 webapp/src/components/Game/GameSelector.jsx delete mode 100644 webapp/src/components/Game/GameSelector/ActiveGames.jsx delete mode 100644 webapp/src/components/Game/GameSelector/GameArchive.jsx delete mode 100644 webapp/src/components/Game/GameSelector/GameProposal.jsx delete mode 100644 webapp/src/components/Game/GameSelector/Selectable.css delete mode 100644 webapp/src/components/Game/GameSelector/Selectable.jsx delete mode 100644 webapp/src/components/Game/GameView.css delete mode 100644 webapp/src/components/Game/GameView.jsx delete mode 100644 webapp/src/components/Game/NewGame.css delete mode 100644 webapp/src/components/Game/NewGame.jsx diff --git a/webapp/src/components/DropdownList.jsx b/webapp/src/components/DropdownList.jsx new file mode 100644 index 0000000..42824b7 --- /dev/null +++ b/webapp/src/components/DropdownList.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export default function DropdownList({ selected, onSelect, optionsList }) { + const handleSelect = (event) => { + onSelect(event.target.value) + } + + return ( +
    + +
    + ) +} \ No newline at end of file diff --git a/webapp/src/components/Game/GameMessage.css b/webapp/src/components/Game/GameMessage.css deleted file mode 100644 index b8871b8..0000000 --- a/webapp/src/components/Game/GameMessage.css +++ /dev/null @@ -1,6 +0,0 @@ -.game-message { - border-radius: 3px; - border-color: lightgray; - background-color:violet; - width: 70%; -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameMessage.jsx b/webapp/src/components/Game/GameMessage.jsx deleted file mode 100644 index 328b2ba..0000000 --- a/webapp/src/components/Game/GameMessage.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import './GameMessage.css' -import React from 'react' - -// import { AppContext } from '../../context/app' - -export default function GameMessage() { - - // const [ctx] = React.useContext(AppContext) - - return ( -
    - TBD: Game Message -
    - ) -} diff --git a/webapp/src/components/Game/GameSelector.css b/webapp/src/components/Game/GameSelector.css deleted file mode 100644 index 96d7c1a..0000000 --- a/webapp/src/components/Game/GameSelector.css +++ /dev/null @@ -1,42 +0,0 @@ -.Games { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - -} - -.Games .li { - border: 1px solid black; - margin-bottom: 5px; -} - -.Games .li p { - margin: 5px; -} - -.Games .li p q { - color: gray; -} - -.Games .li p i { - font-size: 70%; -} - -.Games .li button.action { - display: none; -} - -.Games .li:hover button.action { - display: initial; -} - -.separator { - /* width: 20%; */ - /* height: 20px; */ - border-bottom: 1px dotted black; - text-align: center; - font-size: 50%; - padding-left: 50%; - margin-bottom: 7px; -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameSelector.jsx b/webapp/src/components/Game/GameSelector.jsx deleted file mode 100644 index 404e434..0000000 --- a/webapp/src/components/Game/GameSelector.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import './GameSelector.css'; -import React from 'react'; -import { useLocation, matchPath } from "react-router"; - -import { AppData } from "../../context/data" -import { AppContext } from "../../context/app" -import Proposal from './GameSelector/GameProposal'; - -export default function GameSelector() { - const [data] = React.useContext(AppData) - const [/*ctx*/, dispatchCtx] = React.useContext(AppContext) - - const { pathname } = useLocation(); - const isProposalPath = matchPath("/game/proposal/*", pathname); - const isActivelPath = matchPath("/game/active/*", pathname); - const isArchivePath = matchPath("/game/archive/*", pathname); - - // console.log("GameSelector appCtx", ctx) - - const onClick_proposal = (selectedGame) => { - dispatchCtx({ component: "game-selector", selectedGameProposal: selectedGame }) - } - - // const onClick_active = (selectedGame) => { - // dispatchCtx({ component: "game-selector", selectedActiveGame: selectedGame }) - // } - - // const onClick_archive = (selectedGame) => { - // dispatchCtx({ component: "game-selector", selectedArchiveGame: selectedGame }) - // } - - if (!data.games) - return
    Loading..
    - - console.log("Games", data.games) - - return ( -
    - {isProposalPath && } - {isActivelPath &&
    TBD #1
    } - {isArchivePath &&
    TBD #2
    } -
    - ) -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameSelector/ActiveGames.jsx b/webapp/src/components/Game/GameSelector/ActiveGames.jsx deleted file mode 100644 index ed68149..0000000 --- a/webapp/src/components/Game/GameSelector/ActiveGames.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import './ProposalSelector.css' -import React from 'react'; -import Selectable from './Selectable'; - -export default function ProposalSelector({ games }) { - - const waitForYou = games - .filter(game => game.status === Status.WaitForYou) - .map(game => ) - - const WaitForOpponent = games - .filter(game => game.status === Status.WaitForOpponent) - .map(game => ) - - return
    -
    - {waitForYou} - {WaitForOpponent.length > 0 && -
    - waiting for opponent ({WaitForOpponent.length}) -
    - } - {WaitForOpponent} -
    -
    -}; - - -const Status = { - WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT", - WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU", -} diff --git a/webapp/src/components/Game/GameSelector/GameArchive.jsx b/webapp/src/components/Game/GameSelector/GameArchive.jsx deleted file mode 100644 index ed68149..0000000 --- a/webapp/src/components/Game/GameSelector/GameArchive.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import './ProposalSelector.css' -import React from 'react'; -import Selectable from './Selectable'; - -export default function ProposalSelector({ games }) { - - const waitForYou = games - .filter(game => game.status === Status.WaitForYou) - .map(game => ) - - const WaitForOpponent = games - .filter(game => game.status === Status.WaitForOpponent) - .map(game => ) - - return
    -
    - {waitForYou} - {WaitForOpponent.length > 0 && -
    - waiting for opponent ({WaitForOpponent.length}) -
    - } - {WaitForOpponent} -
    -
    -}; - - -const Status = { - WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT", - WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU", -} diff --git a/webapp/src/components/Game/GameSelector/GameProposal.jsx b/webapp/src/components/Game/GameSelector/GameProposal.jsx deleted file mode 100644 index eb5858a..0000000 --- a/webapp/src/components/Game/GameSelector/GameProposal.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import Selectable from './Selectable'; - -export default function ProposalSelector({ games, onClick }) { - - const waitForYou = games - .filter(game => game.status === Status.WaitForYou) - .map(game => ) - - const WaitForOpponent = games - .filter(game => game.status === Status.WaitForOpponent) - .map(game => ) - - return ( -
    - {waitForYou} - {WaitForOpponent.length > 0 && -
    - waiting for opponent ({WaitForOpponent.length}) -
    - } - {WaitForOpponent} -
    - ) -} - -const Status = { - WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT", - WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU", -} diff --git a/webapp/src/components/Game/GameSelector/Selectable.css b/webapp/src/components/Game/GameSelector/Selectable.css deleted file mode 100644 index faff0ad..0000000 --- a/webapp/src/components/Game/GameSelector/Selectable.css +++ /dev/null @@ -1,43 +0,0 @@ -.selectable { - border: 1px solid black; - margin-bottom: 5px; -} - -.selectable q { - color: gray; -} - -.selectable i { - font-size: 70%; - margin-left: 5px; - margin-right: 5px; -} - -.selectable:hover { - background-color: #d3d3d360; -} - -/* .Games .li button.action { - display: none; -} - -.Games .li:hover button.action { - display: initial; -} */ - -.separator { - /* width: 20%; */ - /* height: 20px; */ - border-bottom: 1px dotted black; - text-align: center; - font-size: 50%; - padding-left: 50%; - margin-bottom: 7px; -} - -.selectable .title { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} diff --git a/webapp/src/components/Game/GameSelector/Selectable.jsx b/webapp/src/components/Game/GameSelector/Selectable.jsx deleted file mode 100644 index 838ed3e..0000000 --- a/webapp/src/components/Game/GameSelector/Selectable.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import './Selectable.css' -import React from 'react'; -import { oppositeColor } from '../Stone'; -import { Player } from '../../Player'; - -export default function Selectable({ game, onClick }) { - - const myColor = game.myColor - - const opponentColor = oppositeColor(myColor) - const opponentName = game.opponentName - - return ( -
    -
    onClick(game)}> - - vs - -
    - {game.message} -
    - ) -}; - diff --git a/webapp/src/components/Game/GameView.css b/webapp/src/components/Game/GameView.css deleted file mode 100644 index 29f4839..0000000 --- a/webapp/src/components/Game/GameView.css +++ /dev/null @@ -1,30 +0,0 @@ -.game-view { - margin-bottom: 10px; - background-color: lightgrey; - width: 100%; - padding-top: 8px; - padding-bottom: 8px; - color: black; -} - -.game-view a { - color:darkgrey; - text-decoration: none; - transition: .25s ease; - margin-left: 5px; - margin-right: 5px; -} - -.game-view .active { - color: white; - border-radius: 2px; - background-color: cadetblue; - opacity: 80%; - padding-top: 8px; - padding-bottom: 8px; -} - -.game-view a:hover:not(.active) { - color: cadetblue; - box-shadow: 0 1.5px 0 0 currentColor; -} \ No newline at end of file diff --git a/webapp/src/components/Game/GameView.jsx b/webapp/src/components/Game/GameView.jsx deleted file mode 100644 index 7930edf..0000000 --- a/webapp/src/components/Game/GameView.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import './GameView.css'; -import React from 'react'; -import { NavLink } from "react-router-dom"; - -export default function GameView() { - - return ( - - ) -} diff --git a/webapp/src/components/Game/NewGame.css b/webapp/src/components/Game/NewGame.css deleted file mode 100644 index 3661eb4..0000000 --- a/webapp/src/components/Game/NewGame.css +++ /dev/null @@ -1,17 +0,0 @@ -.new-game { - margin-top: 60px; -} - -.new-game * { - width: 230px; -} - -.new-game>div { /* first level childs only*/ - margin-top: 25px; - margin-bottom: 25px; -} - -.new-game .stone { - font-size: 150%; - vertical-align: -3px; -} \ No newline at end of file diff --git a/webapp/src/components/Game/NewGame.jsx b/webapp/src/components/Game/NewGame.jsx deleted file mode 100644 index 4a42b71..0000000 --- a/webapp/src/components/Game/NewGame.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import './NewGame.css'; -import React from 'react'; -import { AppData } from '../../context/data' -import { AppContext } from '../../context/app' -import { useLocation, matchPath } from "react-router"; -import { SelectPlayer } from '../Player'; -import { WhiteStone, BlackStone } from './Stone'; - - -export default function NewGame() { - const [ctx, dispatchCtx] = React.useContext(AppContext) - const [data] = React.useContext(AppData) - const { pathname } = useLocation(); - const isMyPath = matchPath("/game/new", pathname); - - if (!isMyPath) - return - - /* - * Name options - */ - const nameOptions = data.leaderboard ? Object.keys(data.leaderboard).map(playerName => - ) - : [] - - const whiteOptions = Array(nameOptions) - whiteOptions.push() - - const blackOptions = Array(nameOptions) - blackOptions.push() - - /* - * Radiobutton - */ - const radioButton = (whitePlayer, blackPlayer) => { - if (whitePlayer !== '' && whitePlayer === ctx.newGame.blackPlayer) { - blackPlayer = '' - } - if (blackPlayer !== '' && blackPlayer === ctx.newGame.whitePlayer) { - whitePlayer = '' - } - - dispatchCtx({ update: "newGame", whitePlayer, blackPlayer }) - } - - const setWhitePlayer = (name) => { - radioButton(name, ctx.newGame.blackPlayer) - } - - const setBlackPlayer = (name) => { - radioButton(ctx.newGame.whitePlayer, name) - } - - /* - * Component - */ - return ( -
    -
    - - -
    -
    - vs -
    -
    - - -
    -
    - ) -} diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index 0ec98b8..31d3584 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -17,29 +17,25 @@ import Forward from './games/action/Forward'; import GameBoard from './games/GameBoard'; -import { GamesContext, GamesDispatchContext, GamesApiContext } from '../context/games'; +import { GamesContext } from '../context/games'; -export default function Games({ context }) { +export default function Games({ context, players }) { return ( - - -
    -
    - - -
    -
    - - - {/* +
    +
    + + +
    +
    + + + {/* */} -
    -
    - - +
    +
    ) }; @@ -57,11 +53,17 @@ function ViewSelector() { ) } -function ViewProvider(/*todo: dispatchGame*/) { +function ViewProvider({ players, dispatchGames }) { return (
    - } /> + + dispatchGames({ type: "next", newGame: { whitePlayer, blackPlayer } })} + /> + } /> i { + /* first level childs only*/ + margin-top: 25px; + margin-bottom: 25px; } \ No newline at end of file diff --git a/webapp/src/container/games/view/NewGame.jsx b/webapp/src/container/games/view/NewGame.jsx index 02e58df..0069967 100644 --- a/webapp/src/container/games/view/NewGame.jsx +++ b/webapp/src/container/games/view/NewGame.jsx @@ -1,30 +1,62 @@ +import './NewGame.css' import React, { useContext } from 'react'; import { GamesContext } from '../../../context/games'; -export default function NewGame() { +import DropdownList from '../../../components/DropdownList'; +import { WhiteStone, BlackStone } from '../../../components/Checkers'; + +export default function NewGame({ players, onSelectPlayer }) { const games = useContext(GamesContext); - console.log("NewGame", games); + /* + * Name options + */ + const nameOptions = !players.leaderboard + ? [] + : Object.keys(players.leaderboard).map(playerName => + ) - return ( -
    View: NewGame
    - ) -} + const whiteOptions = Array(nameOptions) + whiteOptions.push() + const blackOptions = Array(nameOptions) + blackOptions.push() -// Move to components as DropSelector -function SelectPlayer({ name, setName, nameOptions }) { - const handleSelectChange = (event) => { - setName(event.target.value) + /* + * Radiobutton + */ + const radioButton = (whitePlayer, blackPlayer) => { + if (whitePlayer !== '' && whitePlayer === games.newGame.blackPlayer) { + blackPlayer = ''; + } + if (blackPlayer !== '' && blackPlayer === games.newGame.whitePlayer) { + whitePlayer = ''; + } + + console.log("WhitePlayer", whitePlayer, "BlackPlayer", blackPlayer); + onSelectPlayer(whitePlayer, blackPlayer); } + const setWhitePlayer = (name) => { + radioButton(name, games.newGame.blackPlayer); + } + + const setBlackPlayer = (name) => { + radioButton(games.newGame.whitePlayer, name); + } + + /* + * The Component + */ return ( -
    -
    - -
    +
    + + + - vs - + +
    ) } \ No newline at end of file diff --git a/webapp/src/context/games.jsx b/webapp/src/context/games.jsx index 69d370c..537ab8a 100644 --- a/webapp/src/context/games.jsx +++ b/webapp/src/context/games.jsx @@ -1,8 +1,6 @@ import { createContext } from 'react'; export const GamesContext = createContext(null); -export const GamesDispatchContext = createContext(null); -export const GamesApiContext = createContext(null); // export const Games = React.createContext({ // state: initialState, diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index 95e940e..40c4954 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -3,6 +3,11 @@ import { nextState } from '../util/StateHelper'; export const gamesInitialState = { list: null, + + newGame: { + whitePlayer: '', + blackPlayer: '' + } }; export function gamesReducer(state, action) { -- 2.45.2