more consistent theming
This commit is contained in:
parent
af2d6a1f16
commit
f7e20fc608
4
tank-frontend/src/App.css
Normal file
4
tank-frontend/src/App.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.GadgetRows {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
47
tank-frontend/src/App.tsx
Normal file
47
tank-frontend/src/App.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import {useCallback, useState} from 'react';
|
||||||
|
import ClientScreen from './ClientScreen';
|
||||||
|
import Controls from './Controls.tsx';
|
||||||
|
import JoinForm from './JoinForm.tsx';
|
||||||
|
import PlayerInfo from './PlayerInfo.tsx';
|
||||||
|
import {useStoredObjectState} from './useStoredState.ts';
|
||||||
|
import {NameId, postPlayer} from './serverCalls.tsx';
|
||||||
|
import Column from "./components/Column.tsx";
|
||||||
|
import Row from "./components/Row.tsx";
|
||||||
|
import Scoreboard from "./Scoreboard.tsx";
|
||||||
|
import Button from "./components/Button.tsx";
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const getNewNameId = () => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
name: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [nameId, setNameId] = useStoredObjectState<NameId>('access', getNewNameId);
|
||||||
|
|
||||||
|
const [isLoggedIn, setLoggedIn] = useState<boolean>(false);
|
||||||
|
const logout = () => setLoggedIn(false);
|
||||||
|
|
||||||
|
useCallback(async () => {
|
||||||
|
if (isLoggedIn)
|
||||||
|
return;
|
||||||
|
const result = await postPlayer(nameId);
|
||||||
|
setLoggedIn(result !== null);
|
||||||
|
}, [nameId, isLoggedIn])();
|
||||||
|
|
||||||
|
return <Column className='grow'>
|
||||||
|
<Row>
|
||||||
|
<h1 className='grow'>Tanks!</h1>
|
||||||
|
{nameId.name !== '' &&
|
||||||
|
<Button className='PlayerInfo-Reset' onClick={() => setNameId(getNewNameId)} text='logout'/>}
|
||||||
|
</Row>
|
||||||
|
<ClientScreen logout={logout}/>
|
||||||
|
{nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>}
|
||||||
|
{isLoggedIn && <Row className='GadgetRows'>
|
||||||
|
<Controls playerId={nameId.id} logout={logout}/>
|
||||||
|
<PlayerInfo playerId={nameId.id} logout={logout}/>
|
||||||
|
<Scoreboard/>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
</Column>;
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ kbd {
|
||||||
background: hsl(0, 0%, 96%);
|
background: hsl(0, 0%, 96%);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 5px;
|
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
width: 1.6em;
|
width: 1.6em;
|
||||||
height: 1.3em;
|
height: 1.3em;
|
||||||
|
|
|
@ -62,12 +62,12 @@ export default function Controls({playerId, logout}: {
|
||||||
return <Column className="Controls">
|
return <Column className="Controls">
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<kbd className="up">↑</kbd>
|
<kbd className="up">▲</kbd>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<kbd>←</kbd>
|
<kbd>◄</kbd>
|
||||||
<kbd>↓</kbd>
|
<kbd>▼</kbd>
|
||||||
<kbd>→</kbd>
|
<kbd>►</kbd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>Move</h3>
|
<h3>Move</h3>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
.JoinForm {
|
.JoinForm {
|
||||||
border: 2px solid rgb(76, 76, 76);
|
border: 4px solid rgb(76, 76, 76);
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.JoinElems {
|
position: absolute;
|
||||||
padding: 8px 8px;
|
top: 50%;
|
||||||
margin: 8px 8px;
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
background: black;
|
||||||
|
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import './JoinForm.css';
|
||||||
import {NameId, PlayerResponse, postPlayer} from './serverCalls';
|
import {NameId, PlayerResponse, postPlayer} from './serverCalls';
|
||||||
import {Guid} from './Guid.ts';
|
import {Guid} from './Guid.ts';
|
||||||
import Column from "./components/Column.tsx";
|
import Column from "./components/Column.tsx";
|
||||||
|
import Button from "./components/Button.tsx";
|
||||||
|
import TextInput from "./components/TextInput.tsx";
|
||||||
|
|
||||||
export default function JoinForm({setNameId, clientId}: {
|
export default function JoinForm({setNameId, clientId}: {
|
||||||
setNameId: (mutator: (oldState: NameId) => NameId) => void,
|
setNameId: (mutator: (oldState: NameId) => NameId) => void,
|
||||||
|
@ -31,25 +33,19 @@ export default function JoinForm({setNameId, clientId}: {
|
||||||
}, [clicked, setData, data, clientId, setClicked, setNameId]);
|
}, [clicked, setData, data, clientId, setClicked, setNameId]);
|
||||||
|
|
||||||
const disableButtons = clicked || name.trim() === '';
|
const disableButtons = clicked || name.trim() === '';
|
||||||
|
const setClickedTrue = () => setClicked(true);
|
||||||
|
|
||||||
return <Column className='JoinForm'>
|
return <Column className='JoinForm'>
|
||||||
<p className="JoinElems"> Enter your name to join the game! </p>
|
<h3> Enter your name to play </h3>
|
||||||
<input
|
<TextInput
|
||||||
className="JoinElems"
|
|
||||||
type="text"
|
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="player name"
|
placeholder="player name"
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
onKeyUp={event => {
|
onEnter={setClickedTrue}
|
||||||
if (event.key === 'Enter')
|
|
||||||
setClicked(true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<Button
|
||||||
className="JoinElems"
|
onClick={setClickedTrue}
|
||||||
onClick={() => setClicked(true)}
|
|
||||||
disabled={disableButtons}
|
disabled={disableButtons}
|
||||||
>
|
text='INSERT COIN'/>
|
||||||
join
|
|
||||||
</button>
|
|
||||||
</Column>;
|
</Column>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,3 @@
|
||||||
margin: 8px 8px;
|
margin: 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PlayerInfo-Reset {
|
|
||||||
height: 4em;
|
|
||||||
width: 4em;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
.Button {
|
.Button {
|
||||||
border: solid 1px green;
|
border: solid 4px green;
|
||||||
border-radius: 12px;
|
padding: 8px;
|
||||||
|
|
||||||
color: green;
|
color: green;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Button:hover, .Button:active {
|
||||||
|
background-color: green;
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import './Button.css';
|
import './Button.css';
|
||||||
import {MouseEventHandler} from "react";
|
import {MouseEventHandler} from "react";
|
||||||
|
|
||||||
export default function Button({text, onClick, className}: {
|
export default function Button({text, onClick, className, disabled}: {
|
||||||
text: string,
|
text: string,
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>,
|
onClick?: MouseEventHandler<HTMLButtonElement>,
|
||||||
className?: string
|
className?: string,
|
||||||
|
disabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
return <button
|
return <button
|
||||||
className={'Button ' + (className ?? '')}
|
className={'Button ' + (className ?? '')}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
disabled={disabled ?? false}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
|
|
3
tank-frontend/src/components/TextInput.css
Normal file
3
tank-frontend/src/components/TextInput.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.TextInput {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
21
tank-frontend/src/components/TextInput.tsx
Normal file
21
tank-frontend/src/components/TextInput.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import {ChangeEventHandler} from "react";
|
||||||
|
import './TextInput.css';
|
||||||
|
|
||||||
|
export default function TextInput(props: {
|
||||||
|
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
||||||
|
className?: string;
|
||||||
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
|
onEnter?: () => void;
|
||||||
|
}) {
|
||||||
|
return <input
|
||||||
|
{...props}
|
||||||
|
type="text"
|
||||||
|
className={'TextInput ' + (props.className?? '')}
|
||||||
|
|
||||||
|
onKeyUp={event => {
|
||||||
|
if (props.onEnter && event.key === 'Enter')
|
||||||
|
props.onEnter();
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
|
@ -1,7 +1,3 @@
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'CCCBFont';
|
font-family: 'CCCBFont';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -10,10 +6,15 @@
|
||||||
src: url('/CCCBFont.ttf') format('ttf'), url('/CCCBFont.woff') format('woff');
|
src: url('/CCCBFont.ttf') format('ttf'), url('/CCCBFont.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: CCCBFont, monospace;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
font-family: CCCBFont, monospace;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -34,8 +35,3 @@ html, body {
|
||||||
.grow {
|
.grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.GadgetRows {
|
|
||||||
justify-content: space-evenly;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,51 +1,8 @@
|
||||||
import React, {useCallback, useState} from 'react';
|
import React from 'react';
|
||||||
import './index.css';
|
|
||||||
import ClientScreen from './ClientScreen';
|
|
||||||
import Controls from './Controls.tsx';
|
|
||||||
import JoinForm from './JoinForm.tsx';
|
|
||||||
import {createRoot} from 'react-dom/client';
|
import {createRoot} from 'react-dom/client';
|
||||||
import PlayerInfo from './PlayerInfo.tsx';
|
import './index.css';
|
||||||
import {useStoredObjectState} from './useStoredState.ts';
|
import App from "./App.tsx";
|
||||||
import {NameId, postPlayer} from './serverCalls.tsx';
|
|
||||||
import Column from "./components/Column.tsx";
|
|
||||||
import Row from "./components/Row.tsx";
|
|
||||||
import Scoreboard from "./Scoreboard.tsx";
|
|
||||||
import Button from "./components/Button.tsx";
|
|
||||||
|
|
||||||
const getNewNameId = () => ({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
name: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [nameId, setNameId] = useStoredObjectState<NameId>('access', getNewNameId);
|
|
||||||
|
|
||||||
const [isLoggedIn, setLoggedIn] = useState<boolean>(false);
|
|
||||||
const logout = () => setLoggedIn(false);
|
|
||||||
|
|
||||||
useCallback(async () => {
|
|
||||||
if (isLoggedIn)
|
|
||||||
return;
|
|
||||||
const result = await postPlayer(nameId);
|
|
||||||
setLoggedIn(result !== null);
|
|
||||||
}, [nameId, isLoggedIn])();
|
|
||||||
|
|
||||||
return <Column className='grow'>
|
|
||||||
<Row>
|
|
||||||
<h1 className='grow'>Tanks!</h1>
|
|
||||||
{nameId.name !== '' &&
|
|
||||||
<Button className='PlayerInfo-Reset' onClick={() => setNameId(getNewNameId)} text='x'/>}
|
|
||||||
</Row>
|
|
||||||
<ClientScreen logout={logout}/>
|
|
||||||
{nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>}
|
|
||||||
{isLoggedIn && <Row className='GadgetRows'>
|
|
||||||
<Controls playerId={nameId.id} logout={logout}/>
|
|
||||||
<PlayerInfo playerId={nameId.id} logout={logout}/>
|
|
||||||
<Scoreboard/>
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
</Column>;
|
|
||||||
}
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function useStoredObjectState<T>(
|
||||||
const getInitialState = () => {
|
const getInitialState = () => {
|
||||||
const localStorageJson = localStorage.getItem(storageKey);
|
const localStorageJson = localStorage.getItem(storageKey);
|
||||||
if (localStorageJson !== null && localStorageJson !== '') {
|
if (localStorageJson !== null && localStorageJson !== '') {
|
||||||
return JSON.parse(localStorageJson);
|
return JSON.parse(localStorageJson) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return initialState();
|
return initialState();
|
||||||
|
@ -27,8 +27,9 @@ export function useStoredObjectState<T>(
|
||||||
const [state, setState] = useState<T>(getInitialState);
|
const [state, setState] = useState<T>(getInitialState);
|
||||||
|
|
||||||
const setSavedState = (mut: (oldState: T) => T) => {
|
const setSavedState = (mut: (oldState: T) => T) => {
|
||||||
localStorage.setItem(storageKey, JSON.stringify(mut(state)));
|
const newState = mut(state);
|
||||||
setState(mut);
|
localStorage.setItem(storageKey, JSON.stringify(newState));
|
||||||
|
setState(newState);
|
||||||
};
|
};
|
||||||
|
|
||||||
return [state, setSavedState];
|
return [state, setSavedState];
|
||||||
|
|
Loading…
Reference in a new issue