This commit is contained in:
djmil 2023-11-07 11:57:52 +01:00
parent df60508d45
commit d92a3df32b
10 changed files with 118 additions and 46 deletions

View File

@ -7,31 +7,38 @@ import Leaderboard from "./container/Leaderboard"
import Game from "./components/Game" import Game from "./components/Game"
import About from "./components/About" import About from "./components/About"
import Polling from './reducer/polling'; import Polling from './flux/polling';
import { LeaderboardApi } from './api/leaderboard'; import User from './flux/user';
// import { UserApi } from './api/user'; import useLeaderboardApi from './api/leaderboard';
import useUserApi from './api/user';
function App() { function App() {
const [polling, dispatchPolling] = useReducer(Polling.reducer, Polling.initialState) const pollingFlux = useReducer(Polling.reducer, Polling.restoreState);
const userFlux = useReducer(User.reducer, User.initialState);
const leaderboard = LeaderboardApi(polling, dispatchPolling).get();
// const user = UserApi(polling, dispatchPolling).get(); const leaderboardApi = useLeaderboardApi(pollingFlux);
const userApi = useUserApi(userFlux);
const leaderboard = leaderboardApi.get();
const user = userApi.get();
return <div className="App"> return (
<BrowserRouter> <div className="App" >
<Header polling={polling} dispatchPolling={dispatchPolling} /> <BrowserRouter>
<Routes> <Header pollingFlux={pollingFlux} />
{/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} <Routes>
<Route path="/game" element={<Game />} /> {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */}
<Route path="/game/new" element={<Game />} /> <Route path="/game" element={<Game />} />
<Route path="/game/proposal" element={<Game />} /> <Route path="/game/new" element={<Game />} />
<Route path="/game/active" element={<Game />} /> <Route path="/game/proposal" element={<Game />} />
<Route path="/game/archive" element={<Game />} /> <Route path="/game/active" element={<Game />} />
<Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} />} /> <Route path="/game/archive" element={<Game />} />
<Route path="/about" element={<About />} /> <Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} user={user} />} />
</Routes> <Route path="/about" element={<About />} />
</BrowserRouter> </Routes>
</div> </BrowserRouter>
</div>
)
} }
export default App; export default App;

View File

@ -2,7 +2,7 @@ import usePolling from "../util/Polling"
const uri = '/api/leaderboard'; const uri = '/api/leaderboard';
export function LeaderboardApi(polling, dispatchPolling) { export default function useLeaderboardApi([polling, dispatchPolling]) {
const useGet = () => { const useGet = () => {
const mode = (polling.enabled === true) const mode = (polling.enabled === true)

20
webapp/src/api/user.js Normal file
View File

@ -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
}
}

View File

@ -4,7 +4,8 @@ import { NavLink } from "react-router-dom";
import OnlineToggle from '../components/OnlineToggle'; import OnlineToggle from '../components/OnlineToggle';
import Wobler from '../components/Wobler'; import Wobler from '../components/Wobler';
export default function Header({ polling, dispatchPolling }) { export default function Header({ pollingFlux }) {
const [polling, dispatchPolling] = pollingFlux;
return ( return (
<div className='Header'> <div className='Header'>

View File

@ -4,6 +4,6 @@
align-items: center; align-items: center;
} }
tr.username { tr.currentuser {
background-color:aliceblue; background-color:aliceblue;
} }

View File

@ -2,25 +2,18 @@ import './Leaderboard.css';
import React from "react" import React from "react"
import Loading from '../components/Loading'; import Loading from '../components/Loading';
export default function Leaderboard({ leaderboard }) { export default function Leaderboard({ leaderboard, user }) {
if (leaderboard == null) if (leaderboard == null)
return <Loading /> return <Loading />
// var listItems = Object.keys(data).map(playerName => { const isCurrentUser = (playerName) =>
// var rank = data[playerName]; user.isCurrentUser(playerName) === true ? true : null;
//
// return <li key={playerName}>
// {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw}
// </li>
// });
// return <ul>{listItems}</ul>;
const tableRows = Object.keys(leaderboard).map(playerName => { const tableRows = Object.keys(leaderboard).map(playerName => {
var rank = leaderboard[playerName]; var rank = leaderboard[playerName];
// TODO tr: className={data.isCurrentUser(playerName) && 'username'} return <tr key={playerName} className={isCurrentUser(playerName) && 'currentuser'}>
return <tr key={playerName} >
<td>{playerName}</td> <td>{playerName}</td>
<td>{rank.gamesPlayed}</td> <td>{rank.gamesPlayed}</td>
<td>{rank.gamesWon}</td> <td>{rank.gamesWon}</td>
@ -43,4 +36,4 @@ export default function Leaderboard({ leaderboard }) {
</tbody> </tbody>
</table> </table>
</div> </div>
}; };

View File

@ -7,18 +7,16 @@ const Persistent = (() => {
getEnabled, getEnabled,
setEnabled setEnabled
} }
})(); // <<--- execute })(); // <<--- Execute
export const pollingGetInitialState = () => { const restoreState = {
return {
enabled: Persistent.getEnabled() === 'true', enabled: Persistent.getEnabled() === 'true',
games: false, games: false,
leaderboard: false leaderboard: false
}
}; };
export function pollingReducer(state, action) { function reducer(state, action) {
switch (action.type) { switch (action.type) {
case 'toggleOnOff': return { case 'toggleOnOff': return {
@ -41,9 +39,6 @@ export function pollingReducer(state, action) {
} }
} }
const Polling = { const Polling = { reducer, restoreState }
initialState: pollingGetInitialState(), // <<--- execute
reducer: pollingReducer
}
export default Polling export default Polling

28
webapp/src/flux/user.js Normal file
View File

@ -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;

View File

@ -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;
}

View File

@ -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;