From f5dc0f9c9423a4fa3cd80b7e2e29980ccfe3cf39 Mon Sep 17 00:00:00 2001 From: Daithi Hearn Date: Tue, 28 Feb 2023 22:46:57 +0100 Subject: [PATCH] Re ordering components on game page --- src/caches/GameSlice.ts | 2 + .../Game/Actions/ActionsWrapper.tsx | 32 ++++ src/components/Game/{ => Actions}/Buying.tsx | 65 +++---- src/components/Game/Actions/Calling.tsx | 125 +++++++++++++ src/components/Game/Actions/PlayCard.tsx | 68 +++++++ src/components/Game/Actions/SelectSuit.tsx | 156 ++++++++++++++++ .../{ => Actions}/ThrowCardsWarningModal.tsx | 14 +- src/components/Game/Calling.tsx | 101 ---------- src/components/Game/GameWrapper.tsx | 16 +- src/components/Game/MyCards.tsx | 61 +----- src/components/Game/SelectSuit.tsx | 176 ------------------ tsconfig.json | 3 +- 12 files changed, 430 insertions(+), 389 deletions(-) create mode 100644 src/components/Game/Actions/ActionsWrapper.tsx rename src/components/Game/{ => Actions}/Buying.tsx (58%) create mode 100644 src/components/Game/Actions/Calling.tsx create mode 100644 src/components/Game/Actions/PlayCard.tsx create mode 100644 src/components/Game/Actions/SelectSuit.tsx rename src/components/Game/{ => Actions}/ThrowCardsWarningModal.tsx (88%) delete mode 100644 src/components/Game/Calling.tsx delete mode 100644 src/components/Game/SelectSuit.tsx diff --git a/src/caches/GameSlice.ts b/src/caches/GameSlice.ts index 15d96ba..41fccbd 100644 --- a/src/caches/GameSlice.ts +++ b/src/caches/GameSlice.ts @@ -90,8 +90,10 @@ export const getIsDoublesGame = createSelector( players => players.length === 6, ) +export const getMaxCall = createSelector(getGame, game => game.maxCall || 0) export const getIsMyGo = createSelector(getGame, game => game.isMyGo) export const getIamGoer = createSelector(getGame, game => game.iamGoer) +export const getIamDealer = createSelector(getGame, game => game.iamDealer) export const getIHavePlayed = createSelector(getGame, game => { const myPosition = game.players.findIndex(p => p.id === game.me?.id) const currentPlayerPosition = game.players.findIndex( diff --git a/src/components/Game/Actions/ActionsWrapper.tsx b/src/components/Game/Actions/ActionsWrapper.tsx new file mode 100644 index 0000000..a97da1a --- /dev/null +++ b/src/components/Game/Actions/ActionsWrapper.tsx @@ -0,0 +1,32 @@ +import { ButtonGroup, CardBody } from "reactstrap" +import { + getIsRoundBuying, + getIsRoundCalled, + getIsRoundCalling, + getIsRoundPlaying, +} from "../../../caches/GameSlice" +import { useAppSelector } from "../../../caches/hooks" +import Buying from "./Buying" +import Calling from "./Calling" +import PlayCard from "./PlayCard" +import SelectSuit from "./SelectSuit" + +const ActionsWrapper = () => { + const isBuying = useAppSelector(getIsRoundBuying) + const isCalling = useAppSelector(getIsRoundCalling) + const isPlaying = useAppSelector(getIsRoundPlaying) + const isCalled = useAppSelector(getIsRoundCalled) + + return ( + + + {isCalling && } + {isBuying && } + {isCalled && } + {isPlaying && } + + + ) +} + +export default ActionsWrapper diff --git a/src/components/Game/Buying.tsx b/src/components/Game/Actions/Buying.tsx similarity index 58% rename from src/components/Game/Buying.tsx rename to src/components/Game/Actions/Buying.tsx index 47b4cc8..0bb94d5 100644 --- a/src/components/Game/Buying.tsx +++ b/src/components/Game/Actions/Buying.tsx @@ -1,26 +1,32 @@ -import { Button, ButtonGroup, CardBody } from "reactstrap" +import { Button } from "reactstrap" import { useCallback, useEffect, useState } from "react" -import GameService from "../../services/GameService" -import { useAppDispatch, useAppSelector } from "../../caches/hooks" +import GameService from "../../..//services/GameService" +import { useAppDispatch, useAppSelector } from "../../..//caches/hooks" import { useSnackbar } from "notistack" import { getMyCardsWithoutBlanks, getSelectedCards, selectAll, -} from "../../caches/MyCardsSlice" +} from "../../..//caches/MyCardsSlice" import { getGameId, getIamGoer, getIHavePlayed, getIsMyGo, getSuit, -} from "../../caches/GameSlice" -import { riskOfMistakeBuyingCards } from "../../utils/GameUtils" +} from "../../..//caches/GameSlice" +import { riskOfMistakeBuyingCards } from "../../../utils/GameUtils" import ThrowCardsWarningModal from "./ThrowCardsWarningModal" -import { SelectableCard } from "../../model/Cards" -import parseError from "../../utils/ErrorUtils" +import { SelectableCard } from "../../../model/Cards" +import parseError from "../../../utils/ErrorUtils" + +const WaitingForRoundToStart = () => ( + +) const Buying = () => { const dispatch = useAppDispatch() @@ -69,32 +75,27 @@ const Buying = () => { } }, [gameId, suit, selectedCards, myCards, isMyGo, readyToBuy]) + if (iHavePlayed) return return ( -
- - - {!iHavePlayed ? ( - - ) : null} - + <> + - - -
+ + ) } diff --git a/src/components/Game/Actions/Calling.tsx b/src/components/Game/Actions/Calling.tsx new file mode 100644 index 0000000..5cae98f --- /dev/null +++ b/src/components/Game/Actions/Calling.tsx @@ -0,0 +1,125 @@ +import { Button } from "reactstrap" + +import GameService from "../../../services/GameService" +import { useAppDispatch, useAppSelector } from "../../../caches/hooks" +import { + getCards, + getGameId, + getGamePlayers, + getIamDealer, + getIsMyGo, + getMaxCall, + getRound, +} from "../../../caches/GameSlice" +import { useCallback, useMemo } from "react" +import { useSnackbar } from "notistack" +import parseError from "../../../utils/ErrorUtils" + +const Calling = () => { + const dispatch = useAppDispatch() + const { enqueueSnackbar } = useSnackbar() + const gameId = useAppSelector(getGameId) + const cards = useAppSelector(getCards) + const round = useAppSelector(getRound) + const players = useAppSelector(getGamePlayers) + const isMyGo = useAppSelector(getIsMyGo) + const iamDealer = useAppSelector(getIamDealer) + const maxCall = useAppSelector(getMaxCall) + + const call = useCallback( + (callAmount: number) => { + if (gameId) + dispatch(GameService.call(gameId, callAmount)).catch( + (e: Error) => + enqueueSnackbar(parseError(e), { variant: "error" }), + ) + }, + [gameId], + ) + + const buttonsEnabled = useMemo( + () => round && round.currentHand && cards.length > 0 && isMyGo, + [round, cards, isMyGo], + ) + + const canCall10 = useMemo( + () => + players.length === 6 && + ((iamDealer && maxCall <= 10) || maxCall < 10), + [players, iamDealer, maxCall], + ) + + const canCall15 = useMemo( + () => (iamDealer && maxCall <= 15) || maxCall < 15, + [iamDealer, maxCall], + ) + + const canCall20 = useMemo( + () => (iamDealer && maxCall <= 20) || maxCall < 20, + [iamDealer, maxCall], + ) + + const canCall25 = useMemo( + () => (iamDealer && maxCall <= 25) || maxCall < 25, + [iamDealer, maxCall], + ) + + const canCallJink = useMemo(() => players.length > 2, [players]) + + return ( + <> + + {canCall10 ? ( + + ) : null} + {canCall15 ? ( + + ) : null} + {canCall20 ? ( + + ) : null} + {canCall25 ? ( + + ) : null} + + + ) +} + +export default Calling diff --git a/src/components/Game/Actions/PlayCard.tsx b/src/components/Game/Actions/PlayCard.tsx new file mode 100644 index 0000000..300f481 --- /dev/null +++ b/src/components/Game/Actions/PlayCard.tsx @@ -0,0 +1,68 @@ +import { Button } from "reactstrap" + +import { useCallback, useMemo } from "react" + +import GameService from "../../../services/GameService" +import { useAppDispatch, useAppSelector } from "../../../caches/hooks" +import { useSnackbar } from "notistack" +import { + getMyCardsWithoutBlanks, + getSelectedCards, +} from "../../../caches/MyCardsSlice" +import { getGameId, getIsMyGo, getRound } from "../../../caches/GameSlice" +import { BLANK_CARD } from "../../../model/Cards" +import parseError from "../../../utils/ErrorUtils" +import { RoundStatus } from "../../../model/Round" + +const WaitingForYourGo = () => ( + +) + +const PlayCard = () => { + const dispatch = useAppDispatch() + const round = useAppSelector(getRound) + const { enqueueSnackbar } = useSnackbar() + const gameId = useAppSelector(getGameId) + const myCards = useAppSelector(getMyCardsWithoutBlanks) + const isMyGo = useAppSelector(getIsMyGo) + const selectedCards = useAppSelector(getSelectedCards) + + const playButtonEnabled = useMemo( + () => + isMyGo && + round && + round.status === RoundStatus.PLAYING && + round.completedHands.length + + myCards.filter(c => c.name !== BLANK_CARD.name).length === + 5, + + [isMyGo, round, myCards], + ) + + const playCard = useCallback(() => { + if (selectedCards.length !== 1) { + enqueueSnackbar("Please select exactly one card to play", { + variant: "warning", + }) + } else { + dispatch( + GameService.playCard(gameId!, selectedCards[0].name), + ).catch(e => enqueueSnackbar(parseError(e), { variant: "error" })) + } + }, [gameId, selectedCards]) + + if (!playButtonEnabled) return + return ( + + ) +} + +export default PlayCard diff --git a/src/components/Game/Actions/SelectSuit.tsx b/src/components/Game/Actions/SelectSuit.tsx new file mode 100644 index 0000000..c7ee0be --- /dev/null +++ b/src/components/Game/Actions/SelectSuit.tsx @@ -0,0 +1,156 @@ +import { Button } from "reactstrap" + +import { useCallback, useState } from "react" + +import GameService from "../../../services/GameService" +import { useAppDispatch, useAppSelector } from "../../../caches/hooks" +import { getGameId, getIamGoer } from "../../../caches/GameSlice" +import { Suit } from "../../../model/Suit" +import { useSnackbar } from "notistack" +import { + getMyCardsWithoutBlanks, + getSelectedCards, +} from "../../../caches/MyCardsSlice" +import { removeAllFromHand } from "../../../utils/GameUtils" +import ThrowCardsWarningModal from "./ThrowCardsWarningModal" +import { SelectableCard } from "../../../model/Cards" +import parseError from "../../../utils/ErrorUtils" + +const WaitingForSuit = () => ( + +) + +const SelectSuit = () => { + const dispatch = useAppDispatch() + const { enqueueSnackbar } = useSnackbar() + const gameId = useAppSelector(getGameId) + const myCards = useAppSelector(getMyCardsWithoutBlanks) + const iamGoer = useAppSelector(getIamGoer) + + const [selectedSuit, setSelectedSuit] = useState() + const [possibleIssue, setPossibleIssues] = useState(false) + + const selectedCards = useAppSelector(getSelectedCards) + + const selectFromDummy = useCallback( + (suit: Suit) => { + // Make sure a suit was selected + if ( + suit !== Suit.HEARTS && + suit !== Suit.DIAMONDS && + suit !== Suit.CLUBS && + suit !== Suit.SPADES + ) { + enqueueSnackbar("Please select a suit!") + return + } + + // Check if there is a risk that they made a mistake when selecting the cards + if (riskOfMistakeBuyingCards(suit)) { + setSelectedSuit(suit) + setPossibleIssues(true) + } else { + dispatch( + GameService.chooseFromDummy(gameId!, selectedCards, suit), + ).catch((e: Error) => + enqueueSnackbar(parseError(e), { variant: "error" }), + ) + } + }, + [gameId, selectedCards], + ) + + const selectFromDummyCallback = ( + id: string, + sel: SelectableCard[], + suit?: Suit, + ) => { + if (!suit) throw Error("Must provide a suit") + dispatch(GameService.chooseFromDummy(id, sel, suit)).catch((e: Error) => + enqueueSnackbar(parseError(e), { variant: "error" }), + ) + } + + const hideCancelSelectFromDummyDialog = useCallback(() => { + setSelectedSuit(undefined) + setPossibleIssues(false) + }, []) + + const riskOfMistakeBuyingCards = useCallback( + (suit: Suit) => { + let deletingCards = removeAllFromHand(selectedCards, myCards) + + for (const element of deletingCards) { + if ( + element.name === "JOKER" || + element.name === "ACE_HEARTS" || + element.suit === suit + ) { + return true + } + } + + return false + }, + [myCards, selectedCards], + ) + + if (!iamGoer) return + return ( + <> + + + + + + {selectedSuit && ( + + )} + + ) +} + +export default SelectSuit diff --git a/src/components/Game/ThrowCardsWarningModal.tsx b/src/components/Game/Actions/ThrowCardsWarningModal.tsx similarity index 88% rename from src/components/Game/ThrowCardsWarningModal.tsx rename to src/components/Game/Actions/ThrowCardsWarningModal.tsx index da92040..790063a 100644 --- a/src/components/Game/ThrowCardsWarningModal.tsx +++ b/src/components/Game/Actions/ThrowCardsWarningModal.tsx @@ -1,4 +1,3 @@ -import { useSnackbar } from "notistack" import React, { useCallback, useMemo } from "react" import { Modal, @@ -11,16 +10,15 @@ import { CardGroup, Card as CardComponent, } from "reactstrap" -import { getGameId } from "../../caches/GameSlice" -import { useAppDispatch, useAppSelector } from "../../caches/hooks" +import { getGameId } from "../../../caches/GameSlice" +import { useAppSelector } from "../../../caches/hooks" import { getMyCardsWithoutBlanks, getSelectedCards, -} from "../../caches/MyCardsSlice" -import { Card, SelectableCard } from "../../model/Cards" -import { Suit } from "../../model/Suit" -import GameService from "../../services/GameService" -import { removeAllFromHand } from "../../utils/GameUtils" +} from "../../../caches/MyCardsSlice" +import { SelectableCard } from "../../../model/Cards" +import { Suit } from "../../../model/Suit" +import { removeAllFromHand } from "../../../utils/GameUtils" interface ModalOpts { modalVisible: boolean diff --git a/src/components/Game/Calling.tsx b/src/components/Game/Calling.tsx deleted file mode 100644 index bec11f2..0000000 --- a/src/components/Game/Calling.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Button, ButtonGroup, CardBody } from "reactstrap" - -import GameService from "../../services/GameService" -import { useAppDispatch, useAppSelector } from "../../caches/hooks" -import { getGame, getGameId } from "../../caches/GameSlice" -import { useCallback } from "react" -import { useSnackbar } from "notistack" -import parseError from "../../utils/ErrorUtils" - -const Calling = () => { - const dispatch = useAppDispatch() - const { enqueueSnackbar } = useSnackbar() - const gameId = useAppSelector(getGameId) - const game = useAppSelector(getGame) - - const buttonsEnabled = - game.round && - game.round.currentHand && - game.cards.length > 0 && - game.isMyGo - - const call = useCallback( - (callAmount: number) => { - if (gameId) - dispatch(GameService.call(gameId, callAmount)).catch( - (e: Error) => - enqueueSnackbar(parseError(e), { variant: "error" }), - ) - }, - [gameId], - ) - - if (!game) { - return null - } - return ( -
- - - - {game.players.length === 6 && - ((game.iamDealer && game.maxCall! <= 10) || - game.maxCall! < 10) ? ( - - ) : null} - {(game.iamDealer && game.maxCall! <= 15) || - game.maxCall! < 15 ? ( - - ) : null} - {(game.iamDealer && game.maxCall! <= 20) || - game.maxCall! < 20 ? ( - - ) : null} - {(game.iamDealer && game.maxCall! <= 25) || - game.maxCall! < 25 ? ( - - ) : null} - - - -
- ) -} - -export default Calling diff --git a/src/components/Game/GameWrapper.tsx b/src/components/Game/GameWrapper.tsx index 1a99822..65925ce 100644 --- a/src/components/Game/GameWrapper.tsx +++ b/src/components/Game/GameWrapper.tsx @@ -2,22 +2,14 @@ import { Card, CardGroup } from "reactstrap" import MyCards from "./MyCards" import PlayersAndCards from "./PlayersAndCards" -import Calling from "./Calling" -import Buying from "./Buying" -import SelectSuit from "./SelectSuit" import WebsocketManager from "./WebsocketManager" import { useAppSelector } from "../../caches/hooks" -import { - getIamSpectator, - getIsRoundBuying, - getIsRoundCalling, -} from "../../caches/GameSlice" +import { getIamSpectator } from "../../caches/GameSlice" +import ActionsWrapper from "./Actions/ActionsWrapper" const GameWrapper = () => { const iamSpectator = useAppSelector(getIamSpectator) - const isBuying = useAppSelector(getIsRoundBuying) - const isCalling = useAppSelector(getIsRoundCalling) return ( @@ -26,9 +18,7 @@ const GameWrapper = () => { - {!iamSpectator && isCalling ? : null} - {!iamSpectator && isBuying ? : null} - {!iamSpectator ? : null} + {!iamSpectator ? : null} {!iamSpectator ? : null} diff --git a/src/components/Game/MyCards.tsx b/src/components/Game/MyCards.tsx index 3da7a5b..364fe4c 100644 --- a/src/components/Game/MyCards.tsx +++ b/src/components/Game/MyCards.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useMemo, useState } from "react" -import { Button, ButtonGroup, CardImg, CardBody } from "reactstrap" +import React, { useCallback, useMemo } from "react" +import { CardImg, CardBody } from "reactstrap" import { DragDropContext, Draggable, @@ -8,21 +8,12 @@ import { } from "react-beautiful-dnd" import { BLANK_CARD, SelectableCard } from "../../model/Cards" -import GameService from "../../services/GameService" import { RoundStatus } from "../../model/Round" -import { - getGameId, - getIamGoer, - getIsMyGo, - getIsRoundCalled, - getRound, -} from "../../caches/GameSlice" +import { getIamGoer, getIsRoundCalled, getRound } from "../../caches/GameSlice" import { useAppDispatch, useAppSelector } from "../../caches/hooks" -import { useSnackbar } from "notistack" import { clearSelectedCards, getMyCards, - getSelectedCards, replaceMyCards, toggleSelect, toggleUniqueSelect, @@ -32,7 +23,6 @@ import { toggleAutoPlay, clearAutoPlay, } from "../../caches/AutoPlaySlice" -import parseError from "../../utils/ErrorUtils" const EMPTY_HAND = [ { ...BLANK_CARD, selected: false }, @@ -44,29 +34,11 @@ const EMPTY_HAND = [ const MyCards: React.FC = () => { const dispatch = useAppDispatch() - const gameId = useAppSelector(getGameId) const round = useAppSelector(getRound) const isRoundCalled = useAppSelector(getIsRoundCalled) const myCards = useAppSelector(getMyCards) const autoPlayCard = useAppSelector(getAutoPlayCard) const iamGoer = useAppSelector(getIamGoer) - const isMyGo = useAppSelector(getIsMyGo) - - const { enqueueSnackbar } = useSnackbar() - - const selectedCards = useAppSelector(getSelectedCards) - - const playButtonEnabled = useMemo( - () => - isMyGo && - round && - round.status === RoundStatus.PLAYING && - round.completedHands.length + - myCards.filter(c => c.name !== BLANK_CARD.name).length === - 5, - - [isMyGo, round, myCards], - ) const cardsSelectable = useMemo( () => @@ -161,18 +133,6 @@ const MyCards: React.FC = () => { [cardsSelectable, autoPlayCard], ) - const playCard = useCallback(() => { - if (selectedCards.length !== 1) { - enqueueSnackbar("Please select exactly one card to play", { - variant: "warning", - }) - } else { - dispatch( - GameService.playCard(gameId!, selectedCards[0].name), - ).catch(e => enqueueSnackbar(parseError(e), { variant: "error" })) - } - }, [gameId, selectedCards]) - return (
@@ -283,21 +243,6 @@ const MyCards: React.FC = () => { - - {round?.status === RoundStatus.PLAYING ? ( - - - - - - ) : null}
) } diff --git a/src/components/Game/SelectSuit.tsx b/src/components/Game/SelectSuit.tsx deleted file mode 100644 index 9c19958..0000000 --- a/src/components/Game/SelectSuit.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { Button, ButtonGroup, CardBody } from "reactstrap" - -import { useCallback, useState } from "react" - -import GameService from "../../services/GameService" -import { useAppDispatch, useAppSelector } from "../../caches/hooks" -import { getGameId, getIamGoer, getIsRoundCalled } from "../../caches/GameSlice" -import { Suit } from "../../model/Suit" -import { useSnackbar } from "notistack" -import { - getMyCardsWithoutBlanks, - getSelectedCards, -} from "../../caches/MyCardsSlice" -import { removeAllFromHand } from "../../utils/GameUtils" -import ThrowCardsWarningModal from "./ThrowCardsWarningModal" -import { SelectableCard } from "../../model/Cards" -import parseError from "../../utils/ErrorUtils" - -const SelectSuit = () => { - const dispatch = useAppDispatch() - const { enqueueSnackbar } = useSnackbar() - const gameId = useAppSelector(getGameId) - const myCards = useAppSelector(getMyCardsWithoutBlanks) - const iamGoer = useAppSelector(getIamGoer) - const isCalled = useAppSelector(getIsRoundCalled) - - const [selectedSuit, setSelectedSuit] = useState() - const [possibleIssue, setPossibleIssues] = useState(false) - - const selectedCards = useAppSelector(getSelectedCards) - - const selectFromDummy = useCallback( - (suit: Suit) => { - // Make sure a suit was selected - if ( - suit !== Suit.HEARTS && - suit !== Suit.DIAMONDS && - suit !== Suit.CLUBS && - suit !== Suit.SPADES - ) { - enqueueSnackbar("Please select a suit!") - return - } - - // Check if there is a risk that they made a mistake when selecting the cards - if (riskOfMistakeBuyingCards(suit)) { - setSelectedSuit(suit) - setPossibleIssues(true) - } else { - dispatch( - GameService.chooseFromDummy(gameId!, selectedCards, suit), - ).catch((e: Error) => - enqueueSnackbar(parseError(e), { variant: "error" }), - ) - } - }, - [gameId, selectedCards], - ) - - const selectFromDummyCallback = ( - id: string, - sel: SelectableCard[], - suit?: Suit, - ) => { - if (!suit) throw Error("Must provide a suit") - dispatch(GameService.chooseFromDummy(id, sel, suit)).catch((e: Error) => - enqueueSnackbar(parseError(e), { variant: "error" }), - ) - } - - const hideCancelSelectFromDummyDialog = useCallback(() => { - setSelectedSuit(undefined) - setPossibleIssues(false) - }, []) - - const riskOfMistakeBuyingCards = useCallback( - (suit: Suit) => { - let deletingCards = removeAllFromHand(selectedCards, myCards) - - for (const element of deletingCards) { - if ( - element.name === "JOKER" || - element.name === "ACE_HEARTS" || - element.suit === suit - ) { - return true - } - } - - return false - }, - [myCards, selectedCards], - ) - - return ( -
- {isCalled ? ( - - {iamGoer ? ( -
- - - - - - - - {selectedSuit && ( - - )} -
- ) : ( - - - - )} -
- ) : null} -
- ) -} - -export default SelectSuit diff --git a/tsconfig.json b/tsconfig.json index e49c3a8..58ff4dd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": "src" }, "include": ["src"], "exclude": [