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 91ba01d..4178dc1 100644
--- a/webapp/src/App.css
+++ b/webapp/src/App.css
@@ -1,7 +1,51 @@
-.App {
- text-align: center;
+.Header .OnlineToggle {
+ transform: scale(.5);
+ margin-left: -19px;
}
-.Container {
- margin-top: 25px;
+.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;
+}
+
+[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 aa7b6e4..379665f 100644
--- a/webapp/src/App.js
+++ b/webapp/src/App.js
@@ -1,30 +1,76 @@
import './App.css';
-import React from 'react'
-import { BrowserRouter, Routes, Route } from "react-router-dom"
+import React from 'react';
+import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';
+
+import OnlineToggle from './components/OnlineToggle';
+import Wobler from './components/Wobler';
-import Header from "./components/Header"
-import Leaderboard from "./components/Leaderboard"
-import Game from "./components/Game"
import About from "./components/About"
+import Games from './container/Games';
+import Leaderboard from './container/Leaderboard';
-function App() {
+import usePollingReducer from './reducer/polling';
+import useGamesReducer from './reducer/games';
- return
+import useUserApi from './api/user';
+import useLeaderboardApi from './api/leaderboard';
+import useGamesApi from './api/games';
+
+export default function App() {
+ const pollingReducer = usePollingReducer();
+
+ const leaderboard = useLeaderboardApi().poll(pollingReducer);
+ const user = useUserApi().get();
+
+ const [games, dispatchGames] = useGamesReducer();
+ const gamesApi = useGamesApi(dispatchGames);
+ gamesApi.list(pollingReducer);
+
+ const players = {
+ leaderboard,
+ isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null
+ };
+
+ return (
-
+
- {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */}
- } />
- } />
- } />
- } />
- } />
-
- } />
- } />
+ } />
+ } />
+ } />
+ } />
-
+ )
}
-export default App;
+function Header({ pollingReducer }) {
+ const [polling, dispatchPolling] = pollingReducer;
+
+ return (
+
+
+ CordaCheckers
+
+
+ dispatchPolling({ type: 'toggleOnOff' })}
+ />
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js
new file mode 100644
index 0000000..f0d2e25
--- /dev/null
+++ b/webapp/src/api/games.js
@@ -0,0 +1,26 @@
+import usePolling from '../util/Polling';
+
+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 } // fetch gamesList every half a minue
+ : { interval_stop: true } // user has fliped OfflineToggel
+
+ const isPolling = usePolling('/api/games', onResponce, mode);
+
+ if (isPolling !== polling.games) {
+ dispatchPolling({ type: 'next', games: isPolling });
+ }
+ }
+
+ return {
+ list: useList
+ }
+}
\ No newline at end of file
diff --git a/webapp/src/api/leaderboard.js b/webapp/src/api/leaderboard.js
new file mode 100644
index 0000000..a653e35
--- /dev/null
+++ b/webapp/src/api/leaderboard.js
@@ -0,0 +1,26 @@
+import { useState } from "react";
+import usePolling from "../util/Polling";
+
+export default function useLeaderboardApi() {
+ const [leaderboard, setLeaderboard] = useState(null);
+
+ const usePoll = (pollingReducer) => {
+ const [polling, dispatchPolling] = pollingReducer;
+
+ const mode = (polling.enabled === true)
+ ? { interval_sec: 300 } // update leaderbord stats every 5 min
+ : { interval_stop: true } // user has fliped OfflineToggel
+
+ const isPolling = usePolling('/api/leaderboard', setLeaderboard, mode);
+
+ if (isPolling !== polling.leaderboard) {
+ dispatchPolling({ type: 'next', leaderboard: isPolling });
+ }
+
+ return leaderboard;
+ }
+
+ return {
+ poll: usePoll
+ }
+}
\ No newline at end of file
diff --git a/webapp/src/api/user.js b/webapp/src/api/user.js
new file mode 100644
index 0000000..1b863a0
--- /dev/null
+++ b/webapp/src/api/user.js
@@ -0,0 +1,20 @@
+import usePolling from "../util/Polling";
+import useUserReducer from "../reducer/user";
+
+export default function useUserApi() {
+ const [user, dispatchUser] = useUserReducer();
+
+ const useGet = () => {
+ const onResponce = (json) => {
+ dispatchUser({ type: "parse", json });
+ }
+
+ usePolling('/api/user', onResponce); // <<-- fetch once
+
+ return user;
+ }
+
+ return {
+ get: useGet
+ }
+}
\ 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..7d0817c
--- /dev/null
+++ b/webapp/src/components/Checkers.jsx
@@ -0,0 +1,124 @@
+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 (
+ console.log('click', id)}
+ >
+ {stone}
+
+ );
+}
+
+function BlackTile() {
+ return
+}
\ No newline at end of file
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.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/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 73c08f8..0000000
--- a/webapp/src/components/Game/GameSelector.jsx
+++ /dev/null
@@ -1,42 +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..
-
- 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.jsx b/webapp/src/components/Game/GameSelector/Selectable.jsx
deleted file mode 100644
index 7e7a53e..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 4a13705..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/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/components/Header.css b/webapp/src/components/Header.css
deleted file mode 100644
index 299bc64..0000000
--- a/webapp/src/components/Header.css
+++ /dev/null
@@ -1,51 +0,0 @@
-.OnlineTgl {
- transform: scale(.5);
- margin-left: -19px;
-}
-
-.app-header {
- display: flex;
-}
-
-.app-header nav {
- align-items: center;
- justify-content: center;
- display: flex;
- flex-wrap: wrap;
-
- padding-top: 10px;
-}
-
-.app-header a {
- color: lightgray;
- text-decoration: none;
- transition: .25s ease;
- width: fit-content;
-
- margin-left: 5px;
- margin-right: 5px;
- padding: 0.25rem 1rem;
-}
-
-.app-header .active {
- color: white;
- border-radius: 2px;
- background-color: cadetblue;
- opacity: 80%;
- padding: 0.25rem 1rem;
-}
-
-.app-header a:hover:not(.active) {
- color: cadetblue;
-
- box-shadow: 0 1.5px 0 0 currentcolor;
-}
-
-[data-darkreader-scheme="dark"] .app-header a {
- color: darkslategrey;
-}
-
-[data-darkreader-scheme="dark"] .app-header .active {
- color: white;
- box-shadow: 0 1.5px 0 0 currentcolor;
-}
\ No newline at end of file
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/Leaderboard/index.jsx b/webapp/src/components/Leaderboard/index.jsx
deleted file mode 100644
index 7d893fb..0000000
--- a/webapp/src/components/Leaderboard/index.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from "react"
-import './index.css';
-import { AppData } from "../../context/data"
-
-export default function Leaderboard() {
- const [data] = React.useContext(AppData)
-
- if (data.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 ;
-
- const tableRows = Object.keys(data.leaderboard).map(playerName => {
- var rank = data.leaderboard[playerName];
-
- return
- {playerName} |
- {rank.gamesPlayed} |
- {rank.gamesWon} |
- {rank.gamesDraw} |
-
- });
-
- return
-
-
-
- |
- Played |
- Won |
- Draw |
-
-
-
- { tableRows }
-
-
-
-};
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/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/container/Games.css b/webapp/src/container/Games.css
new file mode 100644
index 0000000..3d8680a
--- /dev/null
+++ b/webapp/src/container/Games.css
@@ -0,0 +1,126 @@
+.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-flow: column;
+ height: 340px;
+ 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..31d3584
--- /dev/null
+++ b/webapp/src/container/Games.jsx
@@ -0,0 +1,94 @@
+import './Games.css';
+import React from 'react';
+import { NavLink, Routes, Route } from 'react-router-dom';
+
+import NewGame from './games/view/NewGame';
+import GameSelector from './games/view/GameSelector';
+
+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';
+
+import { GamesContext } from '../context/games';
+
+export default function Games({ context, players }) {
+
+ return (
+
+
+
+ )
+};
+
+function ViewSelector() {
+ // TODO: counter Wating for YOU
+
+ return (
+
+ )
+}
+
+function ViewProvider({ players, dispatchGames }) {
+ return (
+
+
+
+ dispatchGames({ type: "next", newGame: { whitePlayer, blackPlayer } })}
+ />
+ } />
+
+ console.log("GameProposal", uuid)}
+ />
+ } />
+
+ } />
+ } />
+
+
+ )
+}
+
+function ActionPanel() {
+ return (
+
+
+ } />
+ , , ]} />
+ , , ]} />
+ , ]} />
+
+
+ )
+}
\ No newline at end of file
diff --git a/webapp/src/components/Leaderboard/index.css b/webapp/src/container/Leaderboard.css
similarity index 87%
rename from webapp/src/components/Leaderboard/index.css
rename to webapp/src/container/Leaderboard.css
index 89964b1..4eeafb6 100644
--- a/webapp/src/components/Leaderboard/index.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
new file mode 100644
index 0000000..5e447eb
--- /dev/null
+++ b/webapp/src/container/Leaderboard.jsx
@@ -0,0 +1,40 @@
+import './Leaderboard.css';
+import React from "react"
+import Loading from '../components/Loading';
+
+export default function Leaderboard({ players }) {
+
+ const leaderboard = players.leaderboard;
+
+ if (leaderboard == null)
+ return
+
+ const tableRows = Object.keys(leaderboard).map(playerName => {
+ var rank = leaderboard[playerName];
+
+ return
+ {playerName} |
+ {rank.gamesPlayed} |
+ {rank.gamesWon} |
+ {rank.gamesDraw} |
+
+ });
+
+ return (
+
+
+
+
+ |
+ Played |
+ Won |
+ Draw |
+
+
+
+ {tableRows}
+
+
+
+ )
+};
\ 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/components/Game/GameSelector/Selectable.css b/webapp/src/container/games/view/GameSelector.css
similarity index 77%
rename from webapp/src/components/Game/GameSelector/Selectable.css
rename to webapp/src/container/games/view/GameSelector.css
index faff0ad..1722aa6 100644
--- a/webapp/src/components/Game/GameSelector/Selectable.css
+++ b/webapp/src/container/games/view/GameSelector.css
@@ -1,19 +1,24 @@
-.selectable {
+.GameSelector {
+ flex: 1 1 auto;
+ overflow-y: scroll;
+}
+
+.Selectable {
border: 1px solid black;
margin-bottom: 5px;
}
-.selectable q {
+.Selectable q {
color: gray;
}
-.selectable i {
+.Selectable i {
font-size: 70%;
margin-left: 5px;
margin-right: 5px;
}
-.selectable:hover {
+.Selectable:hover {
background-color: #d3d3d360;
}
@@ -25,7 +30,7 @@
display: initial;
} */
-.separator {
+.Separator {
/* width: 20%; */
/* height: 20px; */
border-bottom: 1px dotted black;
@@ -35,7 +40,7 @@
margin-bottom: 7px;
}
-.selectable .title {
+.Selectable .Title {
display: flex;
flex-direction: row;
align-items: 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)}>
+
+
{game.message}
+
+ )
+};
+
+function Separator({ counter }) {
+ return (
+
+ waiting for opponent ({counter})
+
+ )
+}
\ 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..1dac814
--- /dev/null
+++ b/webapp/src/container/games/view/NewGame.css
@@ -0,0 +1,13 @@
+.NewGame * {
+ /* all childs */
+ width: 200px;
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+}
+
+.NewGame>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
new file mode 100644
index 0000000..0069967
--- /dev/null
+++ b/webapp/src/container/games/view/NewGame.jsx
@@ -0,0 +1,62 @@
+import './NewGame.css'
+import React, { useContext } from 'react';
+import { GamesContext } from '../../../context/games';
+
+import DropdownList from '../../../components/DropdownList';
+import { WhiteStone, BlackStone } from '../../../components/Checkers';
+
+export default function NewGame({ players, onSelectPlayer }) {
+ const games = useContext(GamesContext);
+
+ /*
+ * Name options
+ */
+ const nameOptions = !players.leaderboard
+ ? []
+ : Object.keys(players.leaderboard).map(playerName =>
+ )
+
+ const whiteOptions = Array(nameOptions)
+ whiteOptions.push()
+
+ const blackOptions = Array(nameOptions)
+ blackOptions.push()
+
+ /*
+ * 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/app/index.jsx b/webapp/src/context/app/index.jsx
deleted file mode 100644
index f4be27c..0000000
--- a/webapp/src/context/app/index.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from "react"
-import { reducer, initialState } from "./reducer"
-
-export const AppContext = React.createContext({
- state: initialState,
- dispatch: () => null
-})
-
-export const AppContextProvider = ({ children }) => {
- const [state, dispatch] = React.useReducer(reducer, initialState)
-
- return (
-
- { children }
-
- )
-}
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/games.jsx b/webapp/src/context/games.jsx
new file mode 100644
index 0000000..537ab8a
--- /dev/null
+++ b/webapp/src/context/games.jsx
@@ -0,0 +1,18 @@
+import { createContext } from 'react';
+
+export const GamesContext = createContext(null);
+
+// export const Games = React.createContext({
+// state: initialState,
+// dispatch: () => null
+// })
+
+// export const GamesProvider = ({ children }) => {
+// const [state, dispatch] = React.useReducer(reducer, initialState)
+
+// return (
+//
+// { children }
+//
+// )
+// }
\ No newline at end of file
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/games.js b/webapp/src/reducer/games.js
new file mode 100644
index 0000000..40c4954
--- /dev/null
+++ b/webapp/src/reducer/games.js
@@ -0,0 +1,26 @@
+import { useReducer } from 'react';
+import { nextState } from '../util/StateHelper';
+
+export const gamesInitialState = {
+ list: null,
+
+ newGame: {
+ whitePlayer: '',
+ blackPlayer: ''
+ }
+};
+
+export function gamesReducer(state, action) {
+ switch (action.type) {
+
+ case 'next':
+ return nextState(state, action);
+
+ default:
+ throw Error('GamesReducer: 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/polling.js b/webapp/src/reducer/polling.js
new file mode 100644
index 0000000..521f5c7
--- /dev/null
+++ b/webapp/src/reducer/polling.js
@@ -0,0 +1,39 @@
+import { useReducer } from 'react';
+import { useLocalStorage } from '../util/PersistentStorage';
+import { nextState } from '../util/StateHelper';
+
+const Persistent = (() => {
+ const [getEnabled, setEnabled] = useLocalStorage('polling.enabled', true);
+
+ return {
+ getEnabled,
+ setEnabled
+ }
+})(); // <<--- Execute
+
+export const pollingInitialState = {
+ enabled: Persistent.getEnabled() === 'true',
+
+ games: false,
+ leaderboard: false
+};
+
+export function pollingReducer(curntState, action) {
+ switch (action.type) {
+
+ case 'toggleOnOff': return {
+ ...curntState,
+ enabled: Persistent.setEnabled(!curntState.enabled)
+ };
+
+ case 'next':
+ return nextState(curntState, action);
+
+ default:
+ throw Error('Unknown action.type:' + action.type);
+ }
+}
+
+export default function usePollingReducer() {
+ return useReducer(pollingReducer, pollingInitialState);
+}
\ No newline at end of file
diff --git a/webapp/src/reducer/user.js b/webapp/src/reducer/user.js
new file mode 100644
index 0000000..0351553
--- /dev/null
+++ b/webapp/src/reducer/user.js
@@ -0,0 +1,28 @@
+import { useReducer } from 'react';
+import { localeCompare } from '../util/Locale';
+
+export const userInitialState = {
+ username: '',
+
+ isCurrentUser: function (otherUsername) {
+ return localeCompare(this.username, otherUsername)
+ }
+};
+
+export function userReducer(state, action) {
+ switch (action.type) {
+
+ case 'parse':
+ return {
+ ...state,
+ username: action.json.username
+ };
+
+ default:
+ throw Error('UserReducer: 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/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/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
diff --git a/webapp/src/util/Polling.js b/webapp/src/util/Polling.js
new file mode 100644
index 0000000..4446656
--- /dev/null
+++ b/webapp/src/util/Polling.js
@@ -0,0 +1,55 @@
+import { useState, useCallback, useEffect, } from "react"
+
+/*
+ - uri: string
+ - mode:
+ - null - default, fetch data ONCE
+ - interval_sec
+ - interval_stop
+*/
+
+export default function usePolling(uri, onResponce, mode = null) {
+ const [initialPoll, setInitialPoll] = useState(true);
+ const [isPolling, setPolling] = useState(false);
+ const [intervalTimer, setIntervalTimer] = useState(null);
+
+ const pollData = useCallback(() => {
+ setPolling(true);
+ setInitialPoll(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);
+ })
+ }, [uri, mode, onResponce]);
+
+ useEffect(() => {
+ if ((initialPoll || (typeof mode?.interval_sec === 'number' && intervalTimer === null)) && !isPolling) {
+ pollData();
+ }
+ }, [initialPoll, mode, intervalTimer, isPolling, pollData]);
+
+ if (mode?.interval_stop && intervalTimer) {
+ console.log("Cancel scheduled fetch for", uri);
+ clearTimeout(intervalTimer);
+ setIntervalTimer(null);
+ setInitialPoll(true);
+ }
+
+
+ return isPolling;
+}
\ 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