Merge pull request 'developmaunt -> mauster' (#3) from developmaunt into mauster
Reviewed-on: https://git.mau-mau.nl/MauMau/MauMau-Client/pulls/3
This commit was merged in pull request #3.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
.env.example
|
.env.example
|
||||||
|
.env
|
||||||
.idea
|
.idea
|
||||||
README.md
|
README.md
|
||||||
|
.gitea
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
.env
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
import React, {FunctionComponent} from "react";
|
import React, {ButtonHTMLAttributes, DetailedHTMLProps, FunctionComponent} from "react";
|
||||||
|
|
||||||
|
interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
|
|
||||||
interface Props {
|
|
||||||
className?: string
|
|
||||||
onClick?: () => void
|
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GHButton: FunctionComponent<Props> = ({className, onClick, children}) => {
|
export const GHButton: FunctionComponent<ButtonProps> = (props) => {
|
||||||
|
|
||||||
|
const {className, ...rest} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`gh-button ${className}`} onClick={onClick}>{children}</span>
|
<button className={`gh-button ${className}`} {...rest}></button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoButton: FunctionComponent<ButtonProps> = (props) => {
|
||||||
|
|
||||||
|
const {className, ...rest} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={`no-button ${className}`} {...rest}></button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,9 +11,17 @@ const Card: FunctionComponent<Props> = ({cardString, handleClick, isHidden, isCl
|
|||||||
const cardType = cardString.split(' ')[0].toLowerCase();
|
const cardType = cardString.split(' ')[0].toLowerCase();
|
||||||
const cardValue = cardString.split(' ')[1].toLowerCase();
|
const cardValue = cardString.split(' ')[1].toLowerCase();
|
||||||
|
|
||||||
const cardSource = isHidden ?
|
const cardSource = (): string => {
|
||||||
require(`../../assets/cards/back.png`) :
|
if (isHidden) return require("../../assets/cards/back.png");
|
||||||
require(`../../assets/cards/${cardType}_${cardValue}.png`);
|
if (cardType === "JOKER") {
|
||||||
|
if (cardValue === "RED") {
|
||||||
|
return require("../../assets/cards/joker_red.png")
|
||||||
|
}
|
||||||
|
return require("../../assets/cards/joker_black.png")
|
||||||
|
}
|
||||||
|
return require(`../../assets/cards/${cardType}_${cardValue}.png`);
|
||||||
|
}
|
||||||
|
|
||||||
const cardName = isHidden ? 'back' : `${cardType} ${cardValue}`;
|
const cardName = isHidden ? 'back' : `${cardType} ${cardValue}`;
|
||||||
|
|
||||||
const handleCardClick = () => {
|
const handleCardClick = () => {
|
||||||
@@ -24,7 +32,7 @@ const Card: FunctionComponent<Props> = ({cardString, handleClick, isHidden, isCl
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`card ${isClickable && 'card-clickable'}`}>
|
<div className={`card ${isClickable && 'card-clickable'}`}>
|
||||||
<img className="card__texture" src={cardSource} alt={cardName} onClick={handleCardClick}/>
|
<img className="card__texture" src={cardSource()} alt={cardName} onClick={handleCardClick}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/layout/components/Chat.tsx
Normal file
31
src/layout/components/Chat.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {GHButton} from "./Button";
|
||||||
|
import React, {FormEvent, FunctionComponent, RefObject} from "react";
|
||||||
|
|
||||||
|
interface Props{
|
||||||
|
chatRef: RefObject<HTMLOListElement>;
|
||||||
|
handleSend: (message: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chat: FunctionComponent<Props> = ({chatRef, handleSend}) => {
|
||||||
|
|
||||||
|
const handleChat = (form: FormEvent<HTMLFormElement>) => {
|
||||||
|
form.preventDefault();
|
||||||
|
const data = new FormData(form.currentTarget);
|
||||||
|
const chatInput = data.get("chat-input") as string;
|
||||||
|
if(!chatInput) return;
|
||||||
|
handleSend(chatInput);
|
||||||
|
form.currentTarget.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ol className={"chat__list"} ref={chatRef}/>
|
||||||
|
<form className={"chat-form"} onSubmit={handleChat}>
|
||||||
|
<input className={"chat-form__input"} type="text" placeholder={"Chat here..."} id={"chat-input"} name={"chat-input"}/>
|
||||||
|
<GHButton type={"submit"}>Send</GHButton>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Chat;
|
||||||
@@ -20,7 +20,7 @@ const Deck: FunctionComponent<Props> = ({currentCard, actionOnClick}) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="deck">
|
<div className="deck">
|
||||||
<p></p>
|
<p></p>
|
||||||
<Card cardString={"AA BB"} handleClick={handleClick} isHidden={true}/>
|
<Card cardString={"AA BB"} handleClick={handleClick} isHidden isClickable/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,66 @@
|
|||||||
import React, {FunctionComponent} from "react";
|
import React, {FunctionComponent} from "react";
|
||||||
import Deck from "./Deck";
|
import Deck from "./Deck";
|
||||||
import Hand from "./Hand";
|
import Hand from "./Hand";
|
||||||
|
import {GHButton} from "./Button";
|
||||||
|
import {GameAction, Player} from "../pages/Room";
|
||||||
|
|
||||||
interface GameState {
|
export interface GameState {
|
||||||
PlayerName: string;
|
Me: Player;
|
||||||
|
CurrentState: string;
|
||||||
Hand: string[];
|
Hand: string[];
|
||||||
CurrentCard: string;
|
CurrentCard: string;
|
||||||
CurrentPlayer: string;
|
CurrentPlayer: Player;
|
||||||
Players: string[];
|
Players: Player[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gameState: GameState
|
gameState: GameState
|
||||||
handleCardSend: (cardString: string) => void;
|
handleGameAction: (action: GameAction) => void;
|
||||||
handleDraw: () => void;
|
}
|
||||||
|
|
||||||
|
const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS'];
|
||||||
|
|
||||||
|
const Game: FunctionComponent<Props> = ({gameState, handleGameAction}) => {
|
||||||
|
|
||||||
|
const handleChoice = (choice: string) => {
|
||||||
|
handleGameAction({Action: 'CHOOSE', Data: choice});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDraw = () => {
|
||||||
|
handleGameAction({Action: 'DRAW', Data: ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCardSend = (cardString: string) => {
|
||||||
|
handleGameAction({Action: "PLAYCARD", Data: JSON.stringify({
|
||||||
|
CardType: cardString.split(' ')[0],
|
||||||
|
CardValue: cardString.split(' ')[1]
|
||||||
|
})})
|
||||||
}
|
}
|
||||||
|
|
||||||
const Game:FunctionComponent<Props> = ({gameState, handleCardSend, handleDraw}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="game">
|
<div className="game">
|
||||||
|
<ol className={"game__players"}>
|
||||||
|
{
|
||||||
|
gameState?.Me?.Id &&
|
||||||
|
gameState.Players.map((player) => {
|
||||||
|
const isCurrentPlayer = player.Id === gameState.CurrentPlayer.Id;
|
||||||
|
const isMe = player.Id === gameState.Me.Id;
|
||||||
|
|
||||||
|
const style =
|
||||||
|
{
|
||||||
|
fontWeight: isMe ? 'bold' : 'normal',
|
||||||
|
outline: isCurrentPlayer ? '2px solid red' : 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
return <li key={player.Id} style={style}>
|
||||||
|
<div className={"game__players__info"}>
|
||||||
|
<h3>{player.Name} {isMe && '(You)'}</h3>
|
||||||
|
<p>{player.CardsLeft} left</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
{
|
{
|
||||||
gameState.CurrentCard &&
|
gameState.CurrentCard &&
|
||||||
<Deck currentCard={gameState.CurrentCard} actionOnClick={handleDraw}/>
|
<Deck currentCard={gameState.CurrentCard} actionOnClick={handleDraw}/>
|
||||||
@@ -27,18 +69,10 @@ const Game:FunctionComponent<Props> = ({gameState, handleCardSend, handleDraw})
|
|||||||
gameState.Hand &&
|
gameState.Hand &&
|
||||||
<Hand hand={gameState.Hand} actionOnClick={handleCardSend}/>
|
<Hand hand={gameState.Hand} actionOnClick={handleCardSend}/>
|
||||||
}
|
}
|
||||||
<ul>
|
|
||||||
{
|
{
|
||||||
gameState.Players &&
|
gameState.CurrentState === 'CHOOSE' &&
|
||||||
gameState.Players.map((player, index) => {
|
CHOICES.map(choice => <GHButton key={choice} onClick={() => handleChoice(choice)}>{choice}</GHButton>)
|
||||||
const isCurrentPlayer = player === gameState.CurrentPlayer;
|
|
||||||
const isMe = player === gameState.PlayerName;
|
|
||||||
return <li key={index} style={{fontWeight: isCurrentPlayer ? 'bold' : 'normal'}}>
|
|
||||||
{player} {isMe && '(You)'}
|
|
||||||
</li>
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/layout/components/Lobby.tsx
Normal file
29
src/layout/components/Lobby.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {FunctionComponent} from "react";
|
||||||
|
import {GHButton} from "./Button";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
winner: string | undefined;
|
||||||
|
handleLobbyAction: (action: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Lobby: FunctionComponent<Props> = ({winner, handleLobbyAction}) => {
|
||||||
|
|
||||||
|
const onStartClick = () => {
|
||||||
|
console.log('Start Game');
|
||||||
|
handleLobbyAction("START");
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Lobby</h1>
|
||||||
|
{
|
||||||
|
winner &&
|
||||||
|
<h2>{winner} has won the game!</h2>
|
||||||
|
}
|
||||||
|
<GHButton onClick={onStartClick}>Start Game</GHButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Lobby;
|
||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import useTitle from "../../utils/hooks/TitleHook";
|
import useTitle from "../../utils/hooks/TitleHook";
|
||||||
import Card from "../components/Card";
|
import Card from "../components/Card";
|
||||||
import {useNavigate} from "react-router";
|
import {useNavigate} from "react-router";
|
||||||
|
import {NoButton} from "../components/Button";
|
||||||
|
|
||||||
const ROOM_URL = `${process.env.REACT_APP_API_URL}/room`;
|
const ROOM_URL = `${process.env.REACT_APP_API_URL}/room`;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ const MainLobby = () => {
|
|||||||
useTitle('Mau-Mau Lobby');
|
useTitle('Mau-Mau Lobby');
|
||||||
|
|
||||||
const navigateTo = useNavigate();
|
const navigateTo = useNavigate();
|
||||||
|
const playerName = localStorage.getItem('playerName') ?? "";
|
||||||
|
|
||||||
const handleCreateRoom = () => {
|
const handleCreateRoom = () => {
|
||||||
fetch(ROOM_URL, {
|
fetch(ROOM_URL, {
|
||||||
@@ -19,18 +21,28 @@ const MainLobby = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changePlayerName = (name: string) => {
|
||||||
|
if (!name) return;
|
||||||
|
localStorage.setItem('playerName', name);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"main-lobby"}>
|
<div className={"main-lobby"}>
|
||||||
<h1 className={"main-lobby__title mau"}>Mau-Mau</h1>
|
<h1 className={"main-lobby__title mau"}>Mau-Mau</h1>
|
||||||
|
<div className={"main-lobby__name mau"}>
|
||||||
|
<label htmlFor={"name-input"}>Enter your name</label>
|
||||||
|
<input id={"name-input"} type="text" placeholder="Enter your name"
|
||||||
|
defaultValue={playerName} onBlur={(e) => changePlayerName(e.target.value)}/>
|
||||||
|
</div>
|
||||||
<div className={"main-lobby__container"}>
|
<div className={"main-lobby__container"}>
|
||||||
<div className={"main-lobby__container-button clickable"} onClick={handleCreateRoom}>
|
<NoButton className={"main-lobby__container-button"} onClick={handleCreateRoom}>
|
||||||
<h2 className={"mau"}>Host Game</h2>
|
<h2 className={"mau"}>Host Game</h2>
|
||||||
<Card cardString={'SPADES ACE'} isClickable/>
|
<Card cardString={'SPADES ACE'} isClickable/>
|
||||||
</div>
|
</NoButton>
|
||||||
<div className={"main-lobby__container-button clickable"} onClick={() => navigateTo('/rooms')}>
|
<NoButton className={"main-lobby__container-button"} onClick={() => navigateTo('/rooms')}>
|
||||||
<h2 className={"mau"}>Join Game</h2>
|
<h2 className={"mau"}>Join Game</h2>
|
||||||
<Card cardString={'SPADES ACE'} isHidden isClickable/>
|
<Card cardString={'SPADES ACE'} isHidden isClickable/>
|
||||||
</div>
|
</NoButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import useWebSocket from "react-use-websocket";
|
import useWebSocket from "react-use-websocket";
|
||||||
import React from "react";
|
import React, {useEffect} from "react";
|
||||||
import {useNavigate, useParams} from "react-router";
|
import {useNavigate, useParams} from "react-router";
|
||||||
import {GHButton} from "../components/Button";
|
import {GHButton} from "../components/Button";
|
||||||
import useTitle from "../../utils/hooks/TitleHook";
|
import useTitle from "../../utils/hooks/TitleHook";
|
||||||
import Game from "../components/Game";
|
import Game, {GameState} from "../components/Game";
|
||||||
|
import Lobby from "../components/Lobby";
|
||||||
|
import Chat from "../components/Chat";
|
||||||
|
|
||||||
interface GameState {
|
export interface Player {
|
||||||
PlayerName: string;
|
Name: string;
|
||||||
Hand: string[];
|
Id: string;
|
||||||
CurrentCard: string;
|
CardsLeft: number;
|
||||||
CurrentPlayer: string;
|
|
||||||
Players: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChatMessage {
|
|
||||||
PlayerName: string;
|
|
||||||
Message: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SocketMessage {
|
interface SocketMessage {
|
||||||
@@ -23,6 +18,11 @@ interface SocketMessage {
|
|||||||
Payload: any;
|
Payload: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GameAction {
|
||||||
|
Action: string;
|
||||||
|
Data: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Room = () => {
|
const Room = () => {
|
||||||
|
|
||||||
useTitle('Mau!');
|
useTitle('Mau!');
|
||||||
@@ -31,17 +31,29 @@ const Room = () => {
|
|||||||
|
|
||||||
const {roomId} = useParams();
|
const {roomId} = useParams();
|
||||||
|
|
||||||
const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}`;
|
const playerName = localStorage.getItem('playerName') ?? Math.random().toString(36).substring(7);
|
||||||
|
const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`;
|
||||||
|
|
||||||
const [gameState, setGameState] = React.useState<GameState>({
|
const [gameState, setGameState] = React.useState<GameState | undefined>(undefined);
|
||||||
PlayerName: '',
|
const [lobbyState, setLobbyState] = React.useState<string>("");
|
||||||
Hand: [],
|
const [winner, setWinner] = React.useState<string | undefined>(undefined);
|
||||||
CurrentCard: '',
|
|
||||||
CurrentPlayer: '',
|
const chatRef = React.useRef<HTMLOListElement>(null);
|
||||||
Players: []
|
|
||||||
});
|
const addChatMessage = (message: { Sender: string, Message: string }) => {
|
||||||
const [chatMessages, setChatMessages] = React.useState<ChatMessage[]>([]);
|
if (!chatRef.current) return;
|
||||||
const [chatInput, setChatInput] = React.useState<string>('');
|
|
||||||
|
const {scrollTop, scrollHeight, clientHeight} = chatRef.current;
|
||||||
|
|
||||||
|
const newChatElement = document.createElement('li');
|
||||||
|
newChatElement.innerHTML = `<strong>${message.Sender}:</strong> ${message.Message}`;
|
||||||
|
chatRef.current.appendChild(newChatElement);
|
||||||
|
|
||||||
|
const isNearBottom = scrollTop + clientHeight + newChatElement.scrollHeight * 2 >= scrollHeight;
|
||||||
|
if (isNearBottom) {
|
||||||
|
newChatElement.scrollIntoView({behavior: 'smooth'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const websocket = useWebSocket(WS_URL, {
|
const websocket = useWebSocket(WS_URL, {
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
@@ -50,14 +62,28 @@ const Room = () => {
|
|||||||
onMessage: (event) => {
|
onMessage: (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
const payload = JSON.parse(data.Payload);
|
const payload = JSON.parse(data.Payload);
|
||||||
if (data.Type === 'GAME') setGameState(payload);
|
switch (data.Type) {
|
||||||
if (data.Type === 'CHAT') setChatMessages(prev => [...prev, payload]);
|
case 'LOBBY':
|
||||||
|
setLobbyState(data.Type);
|
||||||
|
break;
|
||||||
|
case 'GAME':
|
||||||
|
setLobbyState(data.Type);
|
||||||
|
setGameState(payload);
|
||||||
|
break;
|
||||||
|
case 'CHAT':
|
||||||
|
addChatMessage(payload);
|
||||||
|
break;
|
||||||
|
case "END":
|
||||||
|
setWinner(payload.Name)
|
||||||
|
setGameState(undefined);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Unknown message type:', data.Type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLeaveRoom = () => {
|
const handleLeaveRoom = () => {
|
||||||
const socket = websocket.getWebSocket();
|
|
||||||
if (socket) socket.close();
|
|
||||||
navigateTo('/');
|
navigateTo('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,48 +91,50 @@ const Room = () => {
|
|||||||
websocket.sendMessage(JSON.stringify(message));
|
websocket.sendMessage(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCardSend = (card: string) => {
|
const handleGameAction = (action: GameAction) => {
|
||||||
handleSend({
|
handleSend({Type: "GAME", Payload: JSON.stringify(action)});
|
||||||
Type: "GAME",
|
|
||||||
Payload: JSON.stringify({
|
|
||||||
Action: "PLAYCARD",
|
|
||||||
Data: JSON.stringify({
|
|
||||||
CardType: card.split(' ')[0],
|
|
||||||
CardValue: card.split(' ')[1]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDraw = () => {
|
const handleLobbyAction = (action: string) => {
|
||||||
handleSend({
|
handleSend({Type: "LOBBY", Payload: JSON.stringify(action)});
|
||||||
Type: "GAME",
|
|
||||||
Payload: JSON.stringify({
|
|
||||||
Action: "DRAW",
|
|
||||||
Data: ""
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChat = (message: string) => {
|
const handleChatMessage = (chatMessage: string) => {
|
||||||
handleSend({
|
handleSend({
|
||||||
Type: "CHAT",
|
Type: "CHAT",
|
||||||
Payload: message
|
Payload: chatMessage
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const socket = websocket.getWebSocket();
|
||||||
|
if (socket) socket.close();
|
||||||
|
}
|
||||||
|
// this is a cleanup function and should not include any dependencies that would cause it to run more than once
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={"room"}>
|
||||||
<h1>Room {roomId}</h1>
|
<aside className={"room-aside"}>
|
||||||
|
<header>
|
||||||
|
<hgroup>
|
||||||
|
<h1>Mau-Mau</h1>
|
||||||
|
<h2>Room {roomId?.split("-")[0]}</h2>
|
||||||
|
</hgroup>
|
||||||
<GHButton onClick={handleLeaveRoom}>Leave Room</GHButton>
|
<GHButton onClick={handleLeaveRoom}>Leave Room</GHButton>
|
||||||
<Game gameState={gameState} handleCardSend={handleCardSend} handleDraw={handleDraw}/>
|
</header>
|
||||||
<input type="text" placeholder={"Chat"} value={chatInput} onChange={(e) => setChatInput(e.target.value)} />
|
<Chat chatRef={chatRef} handleSend={handleChatMessage}/>
|
||||||
<button onClick={() => handleChat(chatInput)}>Send</button>
|
</aside>
|
||||||
<ul>
|
<main className={"room-main"}>
|
||||||
{chatMessages.map((message, index) => (
|
{
|
||||||
<li key={index}>{message.PlayerName}: {message.Message}</li>
|
gameState ?
|
||||||
))}
|
<Game gameState={gameState} handleGameAction={handleGameAction}/>
|
||||||
</ul>
|
: lobbyState === 'LOBBY' &&
|
||||||
|
<Lobby winner={winner} handleLobbyAction={handleLobbyAction}/>
|
||||||
|
}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ const Rooms = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Rooms</h1>
|
<h1 className={"mau"}>Rooms</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
rooms.map((room, index) => {
|
rooms.map((room, index) => {
|
||||||
return <li key={index} className={"clickable"}
|
return <li key={index} className={"mouse-pointer mau"}
|
||||||
onClick={() => navigateTo(`/room/${room}`)}>{room}</li>
|
onClick={() => navigateTo(`/room/${room}`)}>{room}</li>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,22 @@
|
|||||||
src: local('Mau'), url(../assets/fonts/OrientalCatsLight.otf) format('opentype');
|
src: local('Mau'), url(../assets/fonts/OrientalCatsLight.otf) format('opentype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
* {
|
||||||
width: 100vw;
|
box-sizing: border-box;
|
||||||
height: 100vh;
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100dvw;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root, .app {
|
||||||
|
background-color: #131313;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mau {
|
.mau {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@import "button";
|
@import "button";
|
||||||
@import "card";
|
@import "card";
|
||||||
|
@import "chat";
|
||||||
@import "hand";
|
@import "hand";
|
||||||
|
@import "game";
|
||||||
@import "deck";
|
@import "deck";
|
||||||
@@ -54,3 +54,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
28
src/styles/layout/components/chat.scss
Normal file
28
src/styles/layout/components/chat.scss
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
.chat {
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
overflow-y: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 1rem 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
background-color: #2e2e2e;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/styles/layout/components/game.scss
Normal file
25
src/styles/layout/components/game.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.game {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
&__players {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
.hand {
|
.hand {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
max-width: 100%;
|
||||||
//justify-content: center;
|
flex-flow: row nowrap !important;
|
||||||
flex-flow: nowrap !important;
|
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
@import "mainlobby";
|
@import "mainlobby";
|
||||||
|
@import "room";
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
.main-lobby {
|
.main-lobby {
|
||||||
width: 100vw;
|
|
||||||
height: 75vh;
|
height: 75vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
&__container {
|
&__container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
@@ -19,6 +24,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 5rem;
|
font-size: 7.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
src/styles/layout/pages/room.scss
Normal file
12
src/styles/layout/pages/room.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.room {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&-aside {
|
||||||
|
display: grid;
|
||||||
|
min-width: 20%;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||||
|
overflow: hidden auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1 @@
|
|||||||
@import "pointer";
|
@import "pointer";
|
||||||
@import "theme";
|
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
.clickable {
|
.mouse-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mouse-default {
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
.dark {
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.light {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user