front: basic ui mockup
- more components - more navigation [bugfix] proxy routing
This commit is contained in:
parent
375af0798e
commit
8056c38ad5
5026
webapp/package-lock.json
generated
5026
webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,21 +2,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
.Container {
|
||||
margin-top: 25px;
|
||||
}
|
@ -9,6 +9,7 @@ import {
|
||||
import Header from "./components/Header"
|
||||
import Leaderboard from "./components/Leaderboard"
|
||||
import Game from "./components/Game"
|
||||
import GameProposal from './components/Game/Proposal'
|
||||
|
||||
function App() {
|
||||
|
||||
@ -19,6 +20,7 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/leaderboard" element={<Leaderboard/>} />
|
||||
<Route path="/game" element={<Game/>} />
|
||||
<Route path="/game/proposal" element={<GameProposal/>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
31
webapp/src/components/Board/index.css
Normal file
31
webapp/src/components/Board/index.css
Normal file
@ -0,0 +1,31 @@
|
||||
.tile {
|
||||
border: 1px solid #e4e4e4;
|
||||
float: left;
|
||||
font-size: 200%;
|
||||
font-weight: bold;
|
||||
line-height: 34px;
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
margin-right: -1px;
|
||||
margin-top: -1px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
.black {
|
||||
background: lightgray;
|
||||
}
|
||||
|
||||
.board {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* scale: 15%; */
|
||||
|
||||
}
|
||||
|
||||
.white:hover {
|
||||
background-color:azure;
|
||||
}
|
79
webapp/src/components/Board/index.jsx
Normal file
79
webapp/src/components/Board/index.jsx
Normal file
@ -0,0 +1,79 @@
|
||||
import './index.css';
|
||||
import React from 'react';
|
||||
|
||||
import { WhiteStone, BlackStone } from '../Stone'
|
||||
|
||||
export default function Board() {
|
||||
|
||||
return <div className='board'>
|
||||
<div className='row'>
|
||||
<BlackTile/> <WhiteTile id={0} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={1} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={2} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={4} stone={WhiteStone()} />
|
||||
</div>
|
||||
<div className='row'>
|
||||
<WhiteTile id={5} stone={WhiteStone()} /> <BlackTile/>
|
||||
<WhiteTile id={6} stone={WhiteStone()} /> <BlackTile/>
|
||||
<WhiteTile id={7} stone={WhiteStone()} /> <BlackTile/>
|
||||
<WhiteTile id={8} stone={WhiteStone()} /> <BlackTile/>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<BlackTile/> <WhiteTile id={ 9} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={10} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={11} stone={WhiteStone()} />
|
||||
<BlackTile/> <WhiteTile id={12} stone={WhiteStone()} />
|
||||
</div>
|
||||
<div className='row'>
|
||||
<WhiteTile id={13} stone={null} /> <BlackTile/>
|
||||
<WhiteTile id={14} stone={null} /> <BlackTile/>
|
||||
<WhiteTile id={15} stone={null} /> <BlackTile/>
|
||||
<WhiteTile id={16} stone={null} /> <BlackTile/>
|
||||
</div>
|
||||
|
||||
<div className='row'>
|
||||
<BlackTile/> <WhiteTile id={17} stone={null} />
|
||||
<BlackTile/> <WhiteTile id={18} stone={null} />
|
||||
<BlackTile/> <WhiteTile id={19} stone={null} />
|
||||
<BlackTile/> <WhiteTile id={20} stone={null} />
|
||||
</div>
|
||||
<div className='row'>
|
||||
<WhiteTile id={21} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={22} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={23} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={24} stone={BlackStone()} /> <BlackTile/>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<BlackTile/> <WhiteTile id={25} stone={BlackStone()} />
|
||||
<BlackTile/> <WhiteTile id={26} stone={BlackStone()} />
|
||||
<BlackTile/> <WhiteTile id={27} stone={BlackStone()} />
|
||||
<BlackTile/> <WhiteTile id={28} stone={BlackStone()} />
|
||||
</div>
|
||||
<div className='row'>
|
||||
<WhiteTile id={29} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={30} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={31} stone={BlackStone()} /> <BlackTile/>
|
||||
<WhiteTile id={32} stone={BlackStone()} /> <BlackTile/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function WhiteTile({ id, stone }) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='tile white'
|
||||
onClick={() => handleClick(id)}
|
||||
>
|
||||
{stone}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BlackTile() {
|
||||
return <div className='tile black'/>
|
||||
}
|
||||
|
||||
function handleClick(i) {
|
||||
console.log("click", i)
|
||||
}
|
0
webapp/src/components/Game/Proposal/index.css
Normal file
0
webapp/src/components/Game/Proposal/index.css
Normal file
22
webapp/src/components/Game/Proposal/index.jsx
Normal file
22
webapp/src/components/Game/Proposal/index.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import './index.css';
|
||||
import React from 'react';
|
||||
// import { NavLink } from "react-router-dom";
|
||||
// import { AppData } from "../../../context/data"
|
||||
import GameHeader from '../../GameHeader'
|
||||
import GameSelector from '../../GameSelector'
|
||||
import Board from '../../Board'
|
||||
|
||||
export default function Proposal() {
|
||||
// const [data] = React.useContext(AppData)
|
||||
|
||||
return <div className="split">
|
||||
<div className='split left'>
|
||||
<GameHeader/>
|
||||
<button>+ Create</button>
|
||||
<GameSelector/>
|
||||
</div>
|
||||
|
||||
<div className='split right'></div>
|
||||
<Board/>
|
||||
</div>
|
||||
};
|
@ -1,47 +1,21 @@
|
||||
.GameProposal {
|
||||
.split {
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.split .left {
|
||||
float: left;
|
||||
width: 45%;
|
||||
/* max-width: 400px; */
|
||||
|
||||
/* height: 100px; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.GameProposal .li {
|
||||
width: 50%;
|
||||
border: 1px solid black;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.GameProposal .li p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.GameProposal .li p q {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.GameProposal .li p i {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
.GameProposal .li button.action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.GameProposal .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;
|
||||
}
|
||||
|
||||
.stone {
|
||||
font-size: 140%;
|
||||
vertical-align: -3px;
|
||||
.split .right {
|
||||
float: left;
|
||||
width: 55%;
|
||||
}
|
@ -1,83 +1,22 @@
|
||||
import './index.css';
|
||||
import React from 'react';
|
||||
import {Accept} from './GameProposalAction';
|
||||
import Reject from './Reject'
|
||||
import Cancel from './GameProposalCancel'
|
||||
import GameHeader from '../GameHeader'
|
||||
import GameSelector from '../GameSelector'
|
||||
import Board from '../Board'
|
||||
|
||||
import { AppData } from "../../context/data"
|
||||
// import { AppData } from "../../context/data"
|
||||
|
||||
export default function Game() {
|
||||
const [data] = React.useContext(AppData)
|
||||
|
||||
if (data.games == null)
|
||||
return <div>Loading..</div>
|
||||
return <div className="split">
|
||||
|
||||
// for (const [key, value] of Object.entries(data.games))
|
||||
// console.log(key, value);
|
||||
|
||||
const waitForYou = data.games
|
||||
.filter(game => game.status === Status.WaitForYou)
|
||||
.map(game => {
|
||||
return <div className="li" key={game.uuid}>
|
||||
<p>
|
||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||
<br/>
|
||||
<q>{game.message}</q>
|
||||
<br/>
|
||||
<Accept uuid={game.uuid}/>
|
||||
<Reject uuid={game.uuid}/>
|
||||
</p>
|
||||
<div className='split left'>
|
||||
<GameHeader/>
|
||||
<GameSelector/>
|
||||
</div>
|
||||
});
|
||||
|
||||
const WaitForOpponent = data.games
|
||||
.filter(game => game.status === Status.WaitForOpponent)
|
||||
.map(game => {
|
||||
return <div className="li" key={game.uuid}>
|
||||
<p>
|
||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||
<br/>
|
||||
<q>{game.message}</q>
|
||||
<br/>
|
||||
<Cancel uuid={game.uuid}/>
|
||||
</p>
|
||||
<div className='split right'></div>
|
||||
<Board/>
|
||||
</div>
|
||||
});
|
||||
|
||||
return <div className="GameProposal">
|
||||
{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",
|
||||
}
|
||||
|
||||
function Stone(color) {
|
||||
if (color === "WHITE")
|
||||
return <span className="stone">⛀</span>
|
||||
|
||||
if (color === "BLACK")
|
||||
return <span className="stone">⛂</span>
|
||||
|
||||
return <span className="stone">{color}</span>
|
||||
}
|
||||
|
||||
function oppositeColor(color) {
|
||||
if (color === "WHITE")
|
||||
return "BLACK"
|
||||
|
||||
if (color === "BLACK")
|
||||
return "WHITE"
|
||||
|
||||
return color
|
||||
}
|
||||
|
31
webapp/src/components/GameHeader/index.css
Normal file
31
webapp/src/components/GameHeader/index.css
Normal file
@ -0,0 +1,31 @@
|
||||
.game-header {
|
||||
margin-bottom: 10px;
|
||||
background-color: lightgrey;
|
||||
width: 100%;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.game-header a {
|
||||
color:darkgrey;
|
||||
text-decoration: none;
|
||||
transition: .25s ease;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.game-header .active {
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
background-color: cadetblue;
|
||||
opacity: 80%;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.game-header a:hover:not(.active) {
|
||||
color: cadetblue;
|
||||
|
||||
/* box-shadow: 0 1.5px 0 0 currentColor; */
|
||||
}
|
15
webapp/src/components/GameHeader/index.jsx
Normal file
15
webapp/src/components/GameHeader/index.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import './index.css';
|
||||
import React from 'react';
|
||||
import { NavLink } from "react-router-dom";
|
||||
// import { AppData } from "../../context/data"
|
||||
|
||||
export default function GameHeader() {
|
||||
// const [data] = React.useContext(AppData)
|
||||
|
||||
|
||||
return <nav className="game-header">
|
||||
<NavLink to="/game/proposal">Proposal</NavLink>
|
||||
<NavLink to="/game/active">Active</NavLink>
|
||||
<NavLink to="/game/archive">Archive</NavLink>
|
||||
</nav>
|
||||
};
|
47
webapp/src/components/GameSelector/index.css
Normal file
47
webapp/src/components/GameSelector/index.css
Normal file
@ -0,0 +1,47 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.stone {
|
||||
font-size: 140%;
|
||||
vertical-align: -3px;
|
||||
}
|
82
webapp/src/components/GameSelector/index.jsx
Normal file
82
webapp/src/components/GameSelector/index.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import './index.css';
|
||||
import React from 'react';
|
||||
|
||||
import { AppData } from "../../context/data"
|
||||
|
||||
export default function Game() {
|
||||
const [data] = React.useContext(AppData)
|
||||
|
||||
if (!data?.games)
|
||||
return <div>Loading..</div>
|
||||
|
||||
// for (const [key, value] of Object.entries(data.games))
|
||||
// console.log(key, value);
|
||||
|
||||
console.log("data.games", data.games)
|
||||
const waitForYou = data.games
|
||||
.filter(game => game.status === Status.WaitForYou)
|
||||
.map(game => {
|
||||
return <div className="li" key={game.uuid}>
|
||||
<p>
|
||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||
<br/>
|
||||
<q>{game.message}</q>
|
||||
<br/>
|
||||
{/* <Accept uuid={game.uuid}/>
|
||||
<Reject uuid={game.uuid}/> */}
|
||||
</p>
|
||||
</div>
|
||||
});
|
||||
|
||||
const WaitForOpponent = data.games
|
||||
.filter(game => game.status === Status.WaitForOpponent)
|
||||
.map(game => {
|
||||
return <div className="li" key={game.uuid}>
|
||||
<p>
|
||||
You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
|
||||
<br/>
|
||||
<q>{game.message}</q>
|
||||
<br/>
|
||||
{/* <Cancel uuid={game.uuid}/> */}
|
||||
</p>
|
||||
</div>
|
||||
});
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
function Stone(color) {
|
||||
if (color === "WHITE")
|
||||
return <span className="stone">⛀</span>
|
||||
|
||||
if (color === "BLACK")
|
||||
return <span className="stone">⛂</span>
|
||||
|
||||
return <span className="stone">{color}</span>
|
||||
}
|
||||
|
||||
function oppositeColor(color) {
|
||||
if (color === "WHITE")
|
||||
return "BLACK"
|
||||
|
||||
if (color === "BLACK")
|
||||
return "WHITE"
|
||||
|
||||
return color
|
||||
}
|
@ -8,7 +8,7 @@ h1 {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
nav {
|
||||
.app-header {
|
||||
padding: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -16,18 +16,18 @@ nav {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav a {
|
||||
.app-header a {
|
||||
color: lightgray;
|
||||
text-decoration: none;
|
||||
transition: .25s ease;
|
||||
width: 100px;
|
||||
width: fit-content;
|
||||
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 0.25rem 1rem;
|
||||
}
|
||||
|
||||
nav .active {
|
||||
.app-header .active {
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
background-color: cadetblue;
|
||||
@ -35,18 +35,17 @@ nav .active {
|
||||
padding: 0.25rem 1rem;
|
||||
}
|
||||
|
||||
nav a:hover:not(.active) {
|
||||
.app-header a:hover:not(.active) {
|
||||
color: cadetblue;
|
||||
|
||||
box-shadow: 0 1.5px 0 0 currentcolor;
|
||||
}
|
||||
|
||||
|
||||
[data-darkreader-scheme="dark"] nav a {
|
||||
[data-darkreader-scheme="dark"] .app-header a {
|
||||
color: darkslategrey;
|
||||
}
|
||||
|
||||
[data-darkreader-scheme="dark"] nav .active {
|
||||
[data-darkreader-scheme="dark"] .app-header .active {
|
||||
color: white;
|
||||
box-shadow: 0 1.5px 0 0 currentcolor;
|
||||
}
|
@ -2,7 +2,7 @@ import './index.css';
|
||||
import './wave.css'
|
||||
import React from "react"
|
||||
import { NavLink } from "react-router-dom";
|
||||
import OfflineToggle from '../OnlineTgl';
|
||||
import OnlineToggle from '../OnlineTgl';
|
||||
import { AppData } from "../../context/data"
|
||||
|
||||
export default function Header() {
|
||||
@ -10,9 +10,9 @@ export default function Header() {
|
||||
|
||||
return <div>
|
||||
<h1>
|
||||
CordaCheckers<OfflineToggle />
|
||||
CordaCheckers<OnlineToggle />
|
||||
</h1>
|
||||
<nav>
|
||||
<nav className='app-header'>
|
||||
<NavLink to="/leaderboard" className={data.leaderboardFetching && "woble"}>
|
||||
<span>L</span>
|
||||
<span>e</span>
|
||||
|
7
webapp/src/components/Stone/index.css
Normal file
7
webapp/src/components/Stone/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
.white-stone {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.black-stone {
|
||||
cursor: default;
|
||||
}
|
41
webapp/src/components/Stone/index.jsx
Normal file
41
webapp/src/components/Stone/index.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import './index.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 <span className="white-stone">⛀</span>
|
||||
}
|
||||
|
||||
export function BlackStone() {
|
||||
return <span className="black-stone">⛂</span>
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
@ -12,8 +12,8 @@ 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 [games, gamesFetching ] = Poll('/api/gamestate' , 30, data.offlineMode)
|
||||
const [leaderboard, leaderboardFetching ] = Poll('/api/leaderboard', 60, data.offlineMode)
|
||||
|
||||
data.games = games
|
||||
data.gamesFetching = gamesFetching
|
||||
|
Loading…
Reference in New Issue
Block a user