Compare commits
No commits in common. "efd7127575cf6436bd78cf36fed1ab008a22557c" and "fdf227bf194569709bed094f8481a8869621d4cc" have entirely different histories.
efd7127575
...
fdf227bf19
@ -16,7 +16,3 @@
|
|||||||
.App-link {
|
.App-link {
|
||||||
color: #61dafb;
|
color: #61dafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Container {
|
|
||||||
margin-top: 25px;
|
|
||||||
}
|
|
@ -1,29 +1,45 @@
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import React from 'react'
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
BrowserRouter,
|
BrowserRouter,
|
||||||
Routes,
|
Routes,
|
||||||
Route,
|
Route,
|
||||||
} from "react-router-dom"
|
} from "react-router-dom";
|
||||||
|
|
||||||
import Header from "./components/Header"
|
import Header from "./Header"
|
||||||
import Leaderboard from "./components/Leaderboard"
|
import Leaderboard from "./Leaderboard";
|
||||||
import GameProposal from "./components/GameProposal"
|
import GameProposal from "./GameProposal";
|
||||||
import DataPolling from './components/DataPolling';
|
|
||||||
|
|
||||||
//import { UserProvider } from "../contexts/UserProvider"
|
|
||||||
//import { GameProposalProvider } from './context/GameProposal';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [games, setGames] = useState(null);
|
||||||
|
const [polling, setPolling] = useState(false);
|
||||||
|
|
||||||
|
const pollGames = useCallback(() => {
|
||||||
|
if (polling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setPolling(true);
|
||||||
|
fetch('/api/gamestate')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setGames(data);
|
||||||
|
setPolling(false);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err.message));
|
||||||
|
}, [polling]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(pollGames(), 35 * 1000); // <<-- poll new gamestates every 35 sec
|
||||||
|
return clearInterval(timer);
|
||||||
|
}, [pollGames])
|
||||||
|
|
||||||
return <div className="App">
|
return <div className="App">
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Header/>
|
<Header/>
|
||||||
<DataPolling/>
|
|
||||||
<div className="Container">
|
<div className="Container">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/leaderboard" element={<Leaderboard/>} />
|
<Route path="/leaderboard" element={<Leaderboard/>} />
|
||||||
<Route path="/gameproposal" element={<GameProposal/>} />
|
<Route path="/gameproposal" element={<GameProposal games={games}/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
@ -42,6 +42,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stone {
|
.stone {
|
||||||
font-size: 140%;
|
font-size: 160%;
|
||||||
vertical-align: -3px;
|
vertical-align: -3px;
|
||||||
}
|
}
|
@ -1,25 +1,24 @@
|
|||||||
import './index.css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Accept} from './GameProposalAction';
|
import {Accept, Reject, Cancel} from './GameProposalAction';
|
||||||
import Reject from './Reject'
|
import './GameProposal.css';
|
||||||
import Cancel from './GameProposalCancel'
|
|
||||||
|
|
||||||
import { AppData } from "../../context/data"
|
const State = {
|
||||||
|
WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
|
||||||
|
WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU",
|
||||||
|
}
|
||||||
|
|
||||||
|
const GameProposal = ({games}) => {
|
||||||
|
|
||||||
export default function GameProposal() {
|
if (games == null)
|
||||||
const [data] = React.useContext(AppData)
|
return <p>Loading..</p>
|
||||||
|
|
||||||
if (data.games == null)
|
// for (const [key, value] of Object.entries(games))
|
||||||
return <div>Loading..</div>
|
|
||||||
|
|
||||||
// for (const [key, value] of Object.entries(data.games))
|
|
||||||
// console.log(key, value);
|
// console.log(key, value);
|
||||||
|
|
||||||
const waitForYou = data.games
|
const waitForYou = games
|
||||||
.filter(game => game.status === Status.WaitForYou)
|
.filter(game => game.status === State.WaitForYou)
|
||||||
.map(game => {
|
.map(game => {
|
||||||
return <div className="li" key={game.uuid}>
|
return <div class="li" key={game.uuid}>
|
||||||
<p>
|
<p>
|
||||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||||
<br/>
|
<br/>
|
||||||
@ -31,10 +30,10 @@ export default function GameProposal() {
|
|||||||
</div>
|
</div>
|
||||||
});
|
});
|
||||||
|
|
||||||
const WaitForOpponent = data.games
|
const WaitForOpponent = games
|
||||||
.filter(game => game.status === Status.WaitForOpponent)
|
.filter(game => game.status === State.WaitForOpponent)
|
||||||
.map(game => {
|
.map(game => {
|
||||||
return <div className="li" key={game.uuid}>
|
return <div class="li" key={game.uuid}>
|
||||||
<p>
|
<p>
|
||||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||||
<br/>
|
<br/>
|
||||||
@ -48,7 +47,7 @@ export default function GameProposal() {
|
|||||||
return <div className="GameProposal">
|
return <div className="GameProposal">
|
||||||
{waitForYou}
|
{waitForYou}
|
||||||
{WaitForOpponent.length > 0 &&
|
{WaitForOpponent.length > 0 &&
|
||||||
<div className="separator">
|
<div class="separator">
|
||||||
waiting for opponent ({WaitForOpponent.length})
|
waiting for opponent ({WaitForOpponent.length})
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -56,21 +55,14 @@ export default function GameProposal() {
|
|||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Status = {
|
|
||||||
WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
|
|
||||||
WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU",
|
|
||||||
}
|
|
||||||
|
|
||||||
function Stone(color) {
|
function Stone(color) {
|
||||||
if (color === "WHITE")
|
if (color === "WHITE")
|
||||||
return <span className="stone">⛀</span>
|
return <span class="stone">⛀</span>
|
||||||
|
|
||||||
if (color === "BLACK")
|
if (color === "BLACK")
|
||||||
return <span className="stone">⛂</span>
|
return <span class="stone">⛂</span>
|
||||||
|
|
||||||
return <span className="stone">{color}</span>
|
return <span class="stone">{color}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
function oppositeColor(color) {
|
function oppositeColor(color) {
|
||||||
@ -82,3 +74,5 @@ function oppositeColor(color) {
|
|||||||
|
|
||||||
return color
|
return color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default GameProposal;
|
@ -6,11 +6,11 @@ export function Accept({uuid}) {
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function Reject({uuid}) {
|
export function Reject({uuid}) {
|
||||||
// return <button className="action" id={uuid} type="submit" >
|
return <button className="action" id={uuid} type="submit">
|
||||||
// Reject
|
Reject
|
||||||
// </button>
|
</button>
|
||||||
// }
|
}
|
||||||
|
|
||||||
export function Cancel({uuid}) {
|
export function Cancel({uuid}) {
|
||||||
return <button className="action" id={uuid} type="submit">
|
return <button className="action" id={uuid} type="submit">
|
17
webapp/src/Header.js
Normal file
17
webapp/src/Header.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import './Header.css';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
<h1>CordaCheckers</h1>
|
||||||
|
<nav>
|
||||||
|
<Link to="/leaderboard">Leaderboard</Link> {"| "}
|
||||||
|
<Link to="/gameproposal">Game Proposal</Link> {"| "}
|
||||||
|
<Link to="/game">Active Games</Link> {"| "}
|
||||||
|
<Link to="/archive">Archive</Link> {"| "}
|
||||||
|
<Link to="about">About</Link>
|
||||||
|
</nav>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,22 @@
|
|||||||
import React from "react"
|
import React, { useState, useEffect } from 'react';
|
||||||
import './index.css';
|
import './Leaderboard.css';
|
||||||
import { AppData } from "../../context/data"
|
|
||||||
|
|
||||||
export default function Leaderboard() {
|
const Leaderboard = () => {
|
||||||
const [data] = React.useContext(AppData)
|
|
||||||
|
|
||||||
if (data.leaderboard == null)
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/leaderboard')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setData(data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err.message);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
return <p>Loading...</p>
|
return <p>Loading...</p>
|
||||||
|
|
||||||
// var listItems = Object.keys(data).map(playerName => {
|
// var listItems = Object.keys(data).map(playerName => {
|
||||||
@ -17,8 +28,8 @@ export default function Leaderboard() {
|
|||||||
// });
|
// });
|
||||||
// return <ul>{listItems}</ul>;
|
// return <ul>{listItems}</ul>;
|
||||||
|
|
||||||
const tableRows = Object.keys(data.leaderboard).map(playerName => {
|
const tableRows = Object.keys(data).map(playerName => {
|
||||||
var rank = data.leaderboard[playerName];
|
var rank = data[playerName];
|
||||||
|
|
||||||
return <tr key={playerName}>
|
return <tr key={playerName}>
|
||||||
<td>{playerName}</td>
|
<td>{playerName}</td>
|
||||||
@ -44,3 +55,5 @@ export default function Leaderboard() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default Leaderboard;
|
@ -1,16 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { AppData } from "../../context/data"
|
|
||||||
import { AppContext } from "../../context/app"
|
|
||||||
|
|
||||||
export default function DataPolling() {
|
|
||||||
const [appData] = React.useContext(AppData)
|
|
||||||
const [appCtx, dispatchAppData] = React.useContext(AppContext)
|
|
||||||
|
|
||||||
return <div className={DataPolling.name}>
|
|
||||||
polling
|
|
||||||
<button onClick={() => dispatchAppData({type: "togglePolling"})}>
|
|
||||||
{ appCtx.disablePolling === true ? "off" : "on" }
|
|
||||||
</button>
|
|
||||||
{ appData.fetching }
|
|
||||||
</div>
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import React, {useState} from 'react';
|
|
||||||
|
|
||||||
export default function Cancel({uuid}) {
|
|
||||||
const [pending, setPending] = useState(new Map())
|
|
||||||
|
|
||||||
for (const [key, value] of pending)
|
|
||||||
console.log("cancel", key, value);
|
|
||||||
|
|
||||||
function sendRequest(uuid2reject) {
|
|
||||||
const nextPending = new Map(pending)
|
|
||||||
nextPending.set(uuid2reject, null)
|
|
||||||
|
|
||||||
setPending(nextPending)
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = pending.get(uuid)
|
|
||||||
const isPending = status !== undefined
|
|
||||||
|
|
||||||
return <button
|
|
||||||
className={isPending ? "visible" : "action"}
|
|
||||||
onClick={() => sendRequest(uuid) }>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import React, {useState} from 'react';
|
|
||||||
|
|
||||||
export default function Reject({uuid}) {
|
|
||||||
const [pending, setPending] = useState([])
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(pending))
|
|
||||||
console.log("pending ", key, value);
|
|
||||||
|
|
||||||
function sendRequest(reject_uuid) {
|
|
||||||
setPending( // Replace the old array
|
|
||||||
[ // with a new array consisting of:
|
|
||||||
...pending, // - all the old items
|
|
||||||
{ uuid: reject_uuid } // - and a new item at the end
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <button className="action" onClick={() => sendRequest(uuid) }>
|
|
||||||
Reject
|
|
||||||
</button>
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import './index.css';
|
|
||||||
import React from "react"
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export default function Header() {
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<h1>CordaCheckers</h1>
|
|
||||||
<nav>
|
|
||||||
<Link to="/leaderboard">Leaderboard</Link> {"| "}
|
|
||||||
<Link to="/gameproposal">Game Proposal</Link> {"| "}
|
|
||||||
<Link to="/game">Active Games</Link> {"| "}
|
|
||||||
<Link to="/archive">Archive</Link> {"| "}
|
|
||||||
<Link to="about">About</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
}
|
|
@ -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 (
|
|
||||||
<AppContext.Provider value={[ state, dispatch ]}>
|
|
||||||
{ children }
|
|
||||||
</AppContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
export const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case "togglePolling":
|
|
||||||
return { ...state,
|
|
||||||
disablePolling: !state.disablePolling // on/off
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialState = {
|
|
||||||
disablePolling: false,
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import { useState, useCallback, useEffect, } from "react"
|
|
||||||
|
|
||||||
export default function Poll(url, interval_sec, disabled) {
|
|
||||||
const [cache, setCache] = 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) => setCache(freshData))
|
|
||||||
.catch((err) => console.log(err.message))
|
|
||||||
}, [url])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (cache == null) {
|
|
||||||
fecthData() // <<-- run immediatly on startup
|
|
||||||
}
|
|
||||||
else if (disabled === true) {
|
|
||||||
clearTimeout(timeoutID) // cancel already scheduled fetch
|
|
||||||
setTimeoutID(null)
|
|
||||||
}
|
|
||||||
else if (timeoutID === null) {
|
|
||||||
const timeoutID = setTimeout(fecthData, interval_sec * 1000)
|
|
||||||
setTimeoutID(timeoutID)
|
|
||||||
console.log("Fetch '" +url +"' scheduled in " +interval_sec +" sec")
|
|
||||||
}
|
|
||||||
}, [url, cache, fecthData, timeoutID, disabled, interval_sec]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: cache,
|
|
||||||
fetching
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { reducer, initialState } from "./reducer"
|
|
||||||
import { AppContext } from "../app"
|
|
||||||
|
|
||||||
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 [appContext] = React.useContext(AppContext)
|
|
||||||
|
|
||||||
const games = Poll('api/gamestate', 30, appContext.disablePolling)
|
|
||||||
const leaderboard = Poll('api/leaderboard', 60, appContext.disablePolling)
|
|
||||||
|
|
||||||
data.games = games.data
|
|
||||||
data.leaderboard = leaderboard.data
|
|
||||||
|
|
||||||
var fetching = []
|
|
||||||
if (games.fetching === true)
|
|
||||||
fetching = [...fetching, "games"]
|
|
||||||
if (leaderboard.fetching === true)
|
|
||||||
fetching = [...fetching, "leaderboard"]
|
|
||||||
|
|
||||||
data.fetching = fetching
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppData.Provider value={[data, dispatchData]}>
|
|
||||||
{children}
|
|
||||||
</AppData.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
export const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.warn("Unknown action.type", action)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialState = {
|
|
||||||
games: null,
|
|
||||||
leaderboard: null,
|
|
||||||
fetching: []
|
|
||||||
}
|
|
@ -1,21 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import reportWebVitals from './reportWebVitals';
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { AppDataProvider } from "./context/data"
|
import reportWebVitals from './reportWebVitals';
|
||||||
import { AppContextProvider } from "./context/app"
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
//<React.StrictMode>
|
|
||||||
<AppContextProvider>
|
|
||||||
<AppDataProvider>
|
|
||||||
<App />
|
<App />
|
||||||
</AppDataProvider>
|
</React.StrictMode>
|
||||||
</AppContextProvider>
|
|
||||||
//</React.StrictMode>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
Loading…
Reference in New Issue
Block a user