#24-directory-structure #26

Merged
djmil merged 15 commits from #24-directory-structure into main 2023-11-10 09:37:23 +01:00
19 changed files with 99 additions and 444 deletions
Showing only changes of commit 76eb556d09 - Show all commits

View File

@ -0,0 +1,15 @@
import React from 'react';
export default function DropdownList({ selected, onSelect, optionsList }) {
const handleSelect = (event) => {
onSelect(event.target.value)
}
return (
<form className='SelectPlayer'>
<select value={selected} onChange={handleSelect}>
{optionsList}
</select>
</form>
)
}

View File

@ -1,6 +0,0 @@
.game-message {
border-radius: 3px;
border-color: lightgray;
background-color:violet;
width: 70%;
}

View File

@ -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 (
<div className='game-message'>
TBD: Game Message
</div>
)
}

View File

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

View File

@ -1,44 +0,0 @@
import './GameSelector.css';
import React from 'react';
import { useLocation, matchPath } from "react-router";
import { AppData } from "../../context/data"
import { AppContext } from "../../context/app"
import Proposal from './GameSelector/GameProposal';
export default function GameSelector() {
const [data] = React.useContext(AppData)
const [/*ctx*/, dispatchCtx] = React.useContext(AppContext)
const { pathname } = useLocation();
const isProposalPath = matchPath("/game/proposal/*", pathname);
const isActivelPath = matchPath("/game/active/*", pathname);
const isArchivePath = matchPath("/game/archive/*", pathname);
// console.log("GameSelector appCtx", ctx)
const onClick_proposal = (selectedGame) => {
dispatchCtx({ component: "game-selector", selectedGameProposal: selectedGame })
}
// const onClick_active = (selectedGame) => {
// dispatchCtx({ component: "game-selector", selectedActiveGame: selectedGame })
// }
// const onClick_archive = (selectedGame) => {
// dispatchCtx({ component: "game-selector", selectedArchiveGame: selectedGame })
// }
if (!data.games)
return <div>Loading..</div>
console.log("Games", data.games)
return (
<div className='game-selector'>
{isProposalPath && <Proposal games={data.games} onClick={onClick_proposal} />}
{isActivelPath && <div>TBD #1</div>}
{isArchivePath && <div>TBD #2</div>}
</div>
)
}

View File

@ -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 => <Selectable game={game} />)
const WaitForOpponent = games
.filter(game => game.status === Status.WaitForOpponent)
.map(game => <Selectable game={game} />)
return <div className="Container">
<div className="Games">
{waitForYou}
{WaitForOpponent.length > 0 &&
<div className="separator">
waiting for opponent ({WaitForOpponent.length})
</div>
}
{WaitForOpponent}
</div>
</div>
};
const Status = {
WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU",
}

View File

@ -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 => <Selectable game={game} />)
const WaitForOpponent = games
.filter(game => game.status === Status.WaitForOpponent)
.map(game => <Selectable game={game} />)
return <div className="Container">
<div className="Games">
{waitForYou}
{WaitForOpponent.length > 0 &&
<div className="separator">
waiting for opponent ({WaitForOpponent.length})
</div>
}
{WaitForOpponent}
</div>
</div>
};
const Status = {
WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU",
}

View File

@ -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 => <Selectable game={game} key={game.uuid} onClick={onClick} />)
const WaitForOpponent = games
.filter(game => game.status === Status.WaitForOpponent)
.map(game => <Selectable game={game} key={game.uuid} onClick={onClick} />)
return (
<div className="Games">
{waitForYou}
{WaitForOpponent.length > 0 &&
<div className="separator">
waiting for opponent ({WaitForOpponent.length})
</div>
}
{WaitForOpponent}
</div>
)
}
const Status = {
WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
WaitForYou: "GAME_PROPOSAL_WAIT_FOR_YOU",
}

View File

@ -1,43 +0,0 @@
.selectable {
border: 1px solid black;
margin-bottom: 5px;
}
.selectable q {
color: gray;
}
.selectable i {
font-size: 70%;
margin-left: 5px;
margin-right: 5px;
}
.selectable:hover {
background-color: #d3d3d360;
}
/* .Games .li button.action {
display: none;
}
.Games .li:hover button.action {
display: initial;
} */
.separator {
/* width: 20%; */
/* height: 20px; */
border-bottom: 1px dotted black;
text-align: center;
font-size: 50%;
padding-left: 50%;
margin-bottom: 7px;
}
.selectable .title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}

View File

@ -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 (
<div className='selectable'>
<div className='title' onClick={() => onClick(game)}>
<Player color={myColor} />
<i>vs</i>
<Player color={opponentColor} name={opponentName} />
</div>
<q>{game.message}</q>
</div>
)
};

View File

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

View File

@ -1,15 +0,0 @@
import './GameView.css';
import React from 'react';
import { NavLink } from "react-router-dom";
export default function GameView() {
return (
<nav className='game-view'>
<NavLink to="/game/new">New</NavLink>
<NavLink to="/game/proposal">Proposal</NavLink>
<NavLink to="/game/active">Active</NavLink>
<NavLink to="/game/archive">Archive</NavLink>
</nav>
)
}

View File

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

View File

@ -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 =>
<option key={playerName} value={playerName}>
{data.isCurrentUser(playerName) ? 'You' : playerName}
</option>)
: [<option key='loading' value='…'>loading</option>]
const whiteOptions = Array(nameOptions)
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>)
const blackOptions = Array(nameOptions)
blackOptions.push(<option key='default' value=''>{'black player …'}</option>)
/*
* 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 (
<div className='new-game'>
<div>
<WhiteStone />
<SelectPlayer name={ctx.newGame.whitePlayer} setName={setWhitePlayer} nameOptions={whiteOptions} />
</div>
<div>- vs -</div>
<div>
<SelectPlayer name={ctx.newGame.blackPlayer} setName={setBlackPlayer} nameOptions={blackOptions} />
<BlackStone />
</div>
</div>
)
}

View File

@ -17,29 +17,25 @@ import Forward from './games/action/Forward';
import GameBoard from './games/GameBoard';
import { GamesContext, GamesDispatchContext, GamesApiContext } from '../context/games';
import { GamesContext } from '../context/games';
export default function Games({ context }) {
export default function Games({ context, players }) {
return (
<GamesContext.Provider value={context.games} >
<GamesDispatchContext.Provider value={context.dispatchGames} >
<GamesApiContext.Provider value={context.gamesApi} >
<div className='Games'>
<div className='left-side'>
<ViewSelector />
<ViewProvider />
</div>
<div className='right-side'>
<ActionPanel />
<GameBoard />
{/*
<div className='Games'>
<div className='left-side'>
<ViewSelector />
<ViewProvider players={players} dispatchGames={context.dispatchGames} />
</div>
<div className='right-side'>
<ActionPanel />
<GameBoard />
{/*
<GameMessage />
<Message2Opponent /> */}
</div>
</div >
</GamesApiContext.Provider>
</GamesDispatchContext.Provider>
</div>
</div >
</GamesContext.Provider>
)
};
@ -57,11 +53,17 @@ function ViewSelector() {
)
}
function ViewProvider(/*todo: dispatchGame*/) {
function ViewProvider({ players, dispatchGames }) {
return (
<div className='ViewProvider'>
<Routes>
<Route path='new' element={<NewGame />} />
<Route path='new' element={
<NewGame
players={players}
onSelectPlayer={(whitePlayer, blackPlayer) => dispatchGames({ type: "next", newGame: { whitePlayer, blackPlayer } })}
/>
} />
<Route path='proposal' element={
<GameSelector

View File

@ -1,8 +1,13 @@
.SelectPlayer {
border-radius: 5px;
border: 0.5px solid darkgrey;
.NewGame * {
/* all childs */
width: 200px;
display: flex;
flex-flow: column;
align-items: center;
}
.SelectPlayer select:hover {
background: lightgray;
.NewGame>i {
/* first level childs only*/
margin-top: 25px;
margin-bottom: 25px;
}

View File

@ -1,30 +1,62 @@
import './NewGame.css'
import React, { useContext } from 'react';
import { GamesContext } from '../../../context/games';
export default function NewGame() {
import DropdownList from '../../../components/DropdownList';
import { WhiteStone, BlackStone } from '../../../components/Checkers';
export default function NewGame({ players, onSelectPlayer }) {
const games = useContext(GamesContext);
console.log("NewGame", games);
/*
* Name options
*/
const nameOptions = !players.leaderboard
? [<option key='loading' value='…'>loading</option>]
: Object.keys(players.leaderboard).map(playerName =>
<option key={playerName} value={playerName}>
{players.isCurrentUser(playerName) ? 'You' : playerName}
</option>)
return (
<div>View: NewGame</div>
)
}
const whiteOptions = Array(nameOptions)
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>)
const blackOptions = Array(nameOptions)
blackOptions.push(<option key='default' value=''>{'black player …'}</option>)
// Move to components as DropSelector
function SelectPlayer({ name, setName, nameOptions }) {
const handleSelectChange = (event) => {
setName(event.target.value)
/*
* Radiobutton
*/
const radioButton = (whitePlayer, blackPlayer) => {
if (whitePlayer !== '' && whitePlayer === games.newGame.blackPlayer) {
blackPlayer = '';
}
if (blackPlayer !== '' && blackPlayer === games.newGame.whitePlayer) {
whitePlayer = '';
}
console.log("WhitePlayer", whitePlayer, "BlackPlayer", blackPlayer);
onSelectPlayer(whitePlayer, blackPlayer);
}
const setWhitePlayer = (name) => {
radioButton(name, games.newGame.blackPlayer);
}
const setBlackPlayer = (name) => {
radioButton(games.newGame.whitePlayer, name);
}
/*
* The Component
*/
return (
<div className='SelectPlayer'>
<form>
<select value={name} onChange={handleSelectChange}>
{nameOptions}
</select>
</form>
<div className='NewGame'>
<WhiteStone />
<DropdownList selected={games.newGame.whitePlayer} onSelect={setWhitePlayer} optionsList={whiteOptions} />
<i>- vs -</i>
<DropdownList selected={games.newGame.blackPlayer} onSelect={setBlackPlayer} optionsList={blackOptions} />
<BlackStone />
</div>
)
}

View File

@ -1,8 +1,6 @@
import { createContext } from 'react';
export const GamesContext = createContext(null);
export const GamesDispatchContext = createContext(null);
export const GamesApiContext = createContext(null);
// export const Games = React.createContext({
// state: initialState,

View File

@ -3,6 +3,11 @@ import { nextState } from '../util/StateHelper';
export const gamesInitialState = {
list: null,
newGame: {
whitePlayer: '',
blackPlayer: ''
}
};
export function gamesReducer(state, action) {