#24-directory-structure #26
@ -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 <div className="App">
 | 
			
		||||
    <BrowserRouter>
 | 
			
		||||
      <Header polling={polling} dispatchPolling={dispatchPolling} />
 | 
			
		||||
      <Routes>
 | 
			
		||||
        {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */}
 | 
			
		||||
        <Route path="/game" element={<Game />} />
 | 
			
		||||
        <Route path="/game/new" element={<Game />} />
 | 
			
		||||
        <Route path="/game/proposal" element={<Game />} />
 | 
			
		||||
        <Route path="/game/active" element={<Game />} />
 | 
			
		||||
        <Route path="/game/archive" element={<Game />} />
 | 
			
		||||
        <Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} />} />
 | 
			
		||||
        <Route path="/about" element={<About />} />
 | 
			
		||||
      </Routes>
 | 
			
		||||
    </BrowserRouter>
 | 
			
		||||
  </div>
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="App" >
 | 
			
		||||
      <BrowserRouter>
 | 
			
		||||
        <Header pollingFlux={pollingFlux} />
 | 
			
		||||
        <Routes>
 | 
			
		||||
          {/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */}
 | 
			
		||||
          <Route path="/game" element={<Game />} />
 | 
			
		||||
          <Route path="/game/new" element={<Game />} />
 | 
			
		||||
          <Route path="/game/proposal" element={<Game />} />
 | 
			
		||||
          <Route path="/game/active" element={<Game />} />
 | 
			
		||||
          <Route path="/game/archive" element={<Game />} />
 | 
			
		||||
          <Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} user={user} />} />
 | 
			
		||||
          <Route path="/about" element={<About />} />
 | 
			
		||||
        </Routes>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								webapp/src/api/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								webapp/src/api/user.js
									
									
									
									
									
										Normal 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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 (
 | 
			
		||||
    <div className='Header'>
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,6 @@
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tr.username {
 | 
			
		||||
tr.currentuser {
 | 
			
		||||
  background-color:aliceblue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 <Loading />
 | 
			
		||||
 | 
			
		||||
  //  var listItems = Object.keys(data).map(playerName => {
 | 
			
		||||
  //    var rank = data[playerName];
 | 
			
		||||
  //
 | 
			
		||||
  //    return <li key={playerName}>
 | 
			
		||||
  //      {playerName}: played {rank.gamesPlayed}, won {rank.gamesWon}, draw {rank.gamesDraw}
 | 
			
		||||
  //      </li>
 | 
			
		||||
  //  });
 | 
			
		||||
  //  return <ul>{listItems}</ul>;
 | 
			
		||||
  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 <tr key={playerName} >
 | 
			
		||||
    return <tr key={playerName} className={isCurrentUser(playerName) && 'currentuser'}>
 | 
			
		||||
      <td>{playerName}</td>
 | 
			
		||||
      <td>{rank.gamesPlayed}</td>
 | 
			
		||||
      <td>{rank.gamesWon}</td>
 | 
			
		||||
@ -43,4 +36,4 @@ export default function Leaderboard({ leaderboard }) {
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
  </div>
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
@ -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
 | 
			
		||||
							
								
								
									
										28
									
								
								webapp/src/flux/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								webapp/src/flux/user.js
									
									
									
									
									
										Normal 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;
 | 
			
		||||
							
								
								
									
										6
									
								
								webapp/src/util/Locale.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								webapp/src/util/Locale.js
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								webapp/src/util/StateHelper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								webapp/src/util/StateHelper.js
									
									
									
									
									
										Normal 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;
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user