From 2e86ad881833b61411a2f401d1eeb40a3a2efd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20R=C3=A5dberg?= Date: Sun, 2 Jul 2023 23:09:11 +0200 Subject: [PATCH] stable state --- my-app/src/App.test.tsx | 9 - my-app/src/App.tsx | 57 +++++- .../src/Components/Buttons/AddLifeButton.tsx | 61 ++++++ .../Components/Buttons/CommanderDamageBar.tsx | 174 ++++++++++++++++++ .../Components/Buttons/CommanderTaxButton.tsx | 56 ++++++ .../PartnerCommanderTaxButton copy.tsx | 56 ++++++ .../Components/Buttons/SubtractLifeButton.tsx | 61 ++++++ .../src/Components/Counters/Counters.style.ts | 23 ++- my-app/src/Components/Counters/Counters.tsx | 48 +++-- .../LifeCounter/LifeCounter.style.ts | 38 ++-- .../Components/LifeCounter/LifeCounter.tsx | 31 +++- my-app/src/Icons/CommanderTaxIcon.tsx | 39 ++++ my-app/src/Types/Player.ts | 15 ++ 13 files changed, 610 insertions(+), 58 deletions(-) delete mode 100644 my-app/src/App.test.tsx create mode 100644 my-app/src/Components/Buttons/AddLifeButton.tsx create mode 100644 my-app/src/Components/Buttons/CommanderDamageBar.tsx create mode 100644 my-app/src/Components/Buttons/CommanderTaxButton.tsx create mode 100644 my-app/src/Components/Buttons/PartnerCommanderTaxButton copy.tsx create mode 100644 my-app/src/Components/Buttons/SubtractLifeButton.tsx create mode 100644 my-app/src/Icons/CommanderTaxIcon.tsx create mode 100644 my-app/src/Types/Player.ts diff --git a/my-app/src/App.test.tsx b/my-app/src/App.test.tsx deleted file mode 100644 index 2a68616..0000000 --- a/my-app/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/my-app/src/App.tsx b/my-app/src/App.tsx index 0de0e9b..2f8246a 100644 --- a/my-app/src/App.tsx +++ b/my-app/src/App.tsx @@ -1,15 +1,70 @@ import './App.css'; import Counters from './Components/Counters/Counters'; import styled from 'styled-components'; +import { Player } from './Types/Player'; const MainWrapper = styled.div` + width: 100vw; + height: 100vh; + overflow: hidden; `; +const players: Player[] = [ + { + key: 1, + color: "grey", + settings: { + useCommanderDamage: true, + usePartner: true, + useEnergy: true, + useExperience: true, + usePoison: true, + flipped: true, + } + }, + { + key: 2, + color: "mintcream", + settings: { + useCommanderDamage: true, + usePartner: false, + useEnergy: true, + useExperience: true, + usePoison: true, + flipped: true, + } + }, + { + key: 3, + color: "gold", + settings: { + useCommanderDamage: true, + usePartner: false, + useEnergy: true, + useExperience: true, + usePoison: true, + flipped: false, + } + }, + { + key: 4, + color: "aquamarine", + settings: { + useCommanderDamage: true, + usePartner: true, + useEnergy: true, + useExperience: true, + usePoison: true, + flipped: false, + } + }, +]; + function App() { return ( - + ); } diff --git a/my-app/src/Components/Buttons/AddLifeButton.tsx b/my-app/src/Components/Buttons/AddLifeButton.tsx new file mode 100644 index 0000000..eb2c5dc --- /dev/null +++ b/my-app/src/Components/Buttons/AddLifeButton.tsx @@ -0,0 +1,61 @@ +import { useRef, useState } from "react"; +import styled from "styled-components"; + +export const StyledLifeCounterButton = styled.button<{ align?: string }>` + width: 50%; + height: auto; + color: rgba(0, 0, 0, 0.4); + font-size: 4rem; + font-weight: 600; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + padding: 0 28px; + text-align: ${props => props.align || "center"}; + user-select: none; +`; + +type AddLifeButtonProps = { + lifeTotal: number; + setLifeTotal: (lifeTotal: number) => void; +}; + +const AddLifeButton = ({ lifeTotal, setLifeTotal }: AddLifeButtonProps) => { + const timeoutRef = useRef(undefined); + const [timeoutFinished, setTimeoutFinished] = useState(false); + + const handleLifeChange = (increment: number) => { + setLifeTotal(lifeTotal + increment); + }; + + const handleDownInput = () => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + handleLifeChange(10); + setTimeoutFinished(true); + }, 500) + } + + const handleUpInput = () => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handleLifeChange(1); + } + } + + return ( + ) => { + e.preventDefault(); + }} + align="right" + > + + + + ); +}; + +export default AddLifeButton; diff --git a/my-app/src/Components/Buttons/CommanderDamageBar.tsx b/my-app/src/Components/Buttons/CommanderDamageBar.tsx new file mode 100644 index 0000000..2e50f1f --- /dev/null +++ b/my-app/src/Components/Buttons/CommanderDamageBar.tsx @@ -0,0 +1,174 @@ +import { useRef, useState } from "react"; +import { Player } from "../../Types/Player"; +import styled from "styled-components"; + +const CommanderDamageGrid = styled.div` + display: flex; + flex-direction: row; + flex-grow: 1; + width: 100%; +`; + +const CommanderDamageContainer = styled.div` + display: flex; + flex-direction: row; + flex-grow: 1; + width: 100%; +`; + +const CommanderDamageButton = styled.button<{ backgroundColor?: string }>` + display: flex; + flex-grow: 1; + border: none; + height: 10vh; + outline: none; + cursor: pointer; + background-color: ${props => props.backgroundColor || "antiquewhite"}; +`; + +const CommanderDamageButtonText = styled.p` + position: relative; + margin: auto; + font-size: 1.5rem; + text-align: center; + text-size-adjust: auto; + font-variant-numeric: tabular-nums; + pointer-events: none; + width: 2rem; + user-select: none; +`; + +const VerticalSeperator = styled.div` + width: 1px; + background-color: rgba(0, 0, 0, 1); +`; + + + +type CommanderDamageBarProps = { + lifeTotal: number; + setLifeTotal: (lifeTotal: number) => void; + opponents: Player[]; +}; + +const CommanderDamageBar = ({ opponents, lifeTotal, setLifeTotal }: CommanderDamageBarProps) => { + const [commanderDamage, setCommanderDamage] = useState( + Array(opponents.length).fill(0) + ); + const [partnerCommanderDamage, setPartnerCommanderDamage] = useState( + Array(opponents.length).fill(0) + ); + + + const timeoutRef = useRef(undefined); + const [timeoutFinished, setTimeoutFinished] = useState(false); + + const handleCommanderDamageChange = (index: number, increment: number) => { + const currentCommanderDamage = commanderDamage[index]; + if (currentCommanderDamage === 0 && increment === -1) { + return; + } + + if (currentCommanderDamage === 21 && increment === 1) { + return; + } + + const updatedCommanderDamage = [...commanderDamage]; + updatedCommanderDamage[index] += increment; + setCommanderDamage(updatedCommanderDamage); + setLifeTotal(lifeTotal - increment); + }; + + const handlePartnerCommanderDamageChange = (index: number, increment: number) => { + const currentPartnerCommanderDamage = partnerCommanderDamage[index]; + if (currentPartnerCommanderDamage === 0 && increment === -1) { + return; + } + + const updatedPartnerCommanderDamage = [...partnerCommanderDamage]; + updatedPartnerCommanderDamage[index] += increment; + + setPartnerCommanderDamage(updatedPartnerCommanderDamage); + setLifeTotal(lifeTotal - increment); + }; + + const handleDownInput = (index: number) => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + setTimeoutFinished(true); + handleCommanderDamageChange(index, -1); + }, 500) + } + + const handleUpInput = (index: number) => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handleCommanderDamageChange(index, 1); + } + clearTimeout(timeoutRef.current); + } + + const handlePartnerDownInput = (index: number) => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + setTimeoutFinished(true); + handlePartnerCommanderDamageChange(index, -1); + }, 500) + } + + const handlePartnerUpInput = (index: number) => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handlePartnerCommanderDamageChange(index, 1); + } + } + + return ( + + {opponents.map((opponent, index) => { + return ( + + handleDownInput(index)} + onPointerUp={() => handleUpInput(index)} + onContextMenu={(e: React.MouseEvent) => { + e.preventDefault(); + } + } + backgroundColor={opponent.color} + > + + {commanderDamage[index] > 0 ? commanderDamage[index] : ""} + + + + {opponent.settings.usePartner && ( + <> + + handlePartnerDownInput(index)} + onPointerUp={() => handlePartnerUpInput(index)} + onContextMenu={(e: React.MouseEvent) => { + e.preventDefault(); + } + } + backgroundColor={opponent.color} + > + + {partnerCommanderDamage[index] > 0 ? partnerCommanderDamage[index] : ""} + + + + + ) + } + + ) + })} + + ); +}; + +export default CommanderDamageBar; diff --git a/my-app/src/Components/Buttons/CommanderTaxButton.tsx b/my-app/src/Components/Buttons/CommanderTaxButton.tsx new file mode 100644 index 0000000..bab5826 --- /dev/null +++ b/my-app/src/Components/Buttons/CommanderTaxButton.tsx @@ -0,0 +1,56 @@ +import { useRef, useState } from "react"; +import CommanderTaxIcon from "../../Icons/CommanderTaxIcon"; +import styled from "styled-components"; + +export const StyledCommanderTaxButton = styled.button` + flex-grow: 1; + border: none; + outline: none; + cursor: pointer; + background-color: transparent; + user-select: none; +`; + +const CommanderTaxButton = () => { + const [commanderTax, setCommanderTax] = useState(0); + + const timeoutRef = useRef(undefined); + const [timeoutFinished, setTimeoutFinished] = useState(false); + + const handleCommanderTaxChange = (increment: number) => { + setCommanderTax(commanderTax + increment); + }; + + const handleDownInput = () => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + setTimeoutFinished(true); + handleCommanderTaxChange(-1); + }, 500) + } + + const handleUpInput = () => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handleCommanderTaxChange(1); + } + } + + return ( + ) => { + e.preventDefault(); + } + } + > + + + ); +}; + +export default CommanderTaxButton; diff --git a/my-app/src/Components/Buttons/PartnerCommanderTaxButton copy.tsx b/my-app/src/Components/Buttons/PartnerCommanderTaxButton copy.tsx new file mode 100644 index 0000000..dfb2b54 --- /dev/null +++ b/my-app/src/Components/Buttons/PartnerCommanderTaxButton copy.tsx @@ -0,0 +1,56 @@ +import { useRef, useState } from "react"; +import CommanderTaxIcon from "../../Icons/CommanderTaxIcon"; +import styled from "styled-components"; + +export const StyledCommanderTaxButton = styled.button` + flex-grow: 1; + border: none; + outline: none; + cursor: pointer; + background-color: transparent; + user-select: none; +`; + +const PartnerCommanderTaxButton = () => { + const [partnerCommanderTax, setPartnerCommanderTax] = useState(0); + const timeoutRef = useRef(undefined); + const [timeoutFinished, setTimeoutFinished] = useState(false); + + const handlePartnerCommanderTaxChange = (increment: number) => { + setPartnerCommanderTax(partnerCommanderTax + increment); + }; + + + const handleDownInput = () => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + setTimeoutFinished(true); + handlePartnerCommanderTaxChange(-1); + }, 500) + } + + const handleUpInput = () => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handlePartnerCommanderTaxChange(1); + } + } + + return ( + ) => { + e.preventDefault(); + } + } + > + 2 + + ); +}; + +export default PartnerCommanderTaxButton; diff --git a/my-app/src/Components/Buttons/SubtractLifeButton.tsx b/my-app/src/Components/Buttons/SubtractLifeButton.tsx new file mode 100644 index 0000000..9df7f40 --- /dev/null +++ b/my-app/src/Components/Buttons/SubtractLifeButton.tsx @@ -0,0 +1,61 @@ +import { useRef, useState } from "react"; +import styled from "styled-components"; + +export const StyledLifeCounterButton = styled.button<{ align?: string }>` + width: 50%; + height: auto; + color: rgba(0, 0, 0, 0.4); + font-size: 4rem; + font-weight: 600; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + padding: 0 28px; + text-align: ${props => props.align || "center"}; + user-select: none; +`; + +type SubtractLifeButtonProps = { + lifeTotal: number; + setLifeTotal: (lifeTotal: number) => void; +}; + +const SubtractLifeButton = ({ lifeTotal, setLifeTotal }: SubtractLifeButtonProps) => { + const timeoutRef = useRef(undefined); + const [timeoutFinished, setTimeoutFinished] = useState(false); + + const handleLifeChange = (increment: number) => { + setLifeTotal(lifeTotal + increment); + }; + + const handleDownInput = () => { + setTimeoutFinished(false); + timeoutRef.current = setTimeout(() => { + handleLifeChange(-10); + setTimeoutFinished(true); + }, 500) + } + + const handleUpInput = () => { + if (!timeoutFinished) { + clearTimeout(timeoutRef.current); + handleLifeChange(-1); + } + } + + return ( + ) => { + e.preventDefault(); + }} + align="left" + > + − + + ); +}; + +export default SubtractLifeButton; diff --git a/my-app/src/Components/Counters/Counters.style.ts b/my-app/src/Components/Counters/Counters.style.ts index 5cfa738..4d45dd1 100644 --- a/my-app/src/Components/Counters/Counters.style.ts +++ b/my-app/src/Components/Counters/Counters.style.ts @@ -1,9 +1,9 @@ import styled from "styled-components"; export const CountersWrapper = styled.div` - width: 100vw; - height: 100vh; - background-color: #4f4f4f; + width: 100%; + max-height: 100%; + background-color: black; `; export const CountersGrid = styled.div` @@ -11,16 +11,27 @@ export const CountersGrid = styled.div` flex-wrap: wrap; justify-content: center; align-items: center; - border-radius: 10px; -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ -moz-box-sizing: border-box; /* Firefox, other Gecko */ box-sizing: border-box; /* Opera/IE 8+ */ + row-gap: 4px; + column-gap: 4px; `; export const GridItemContainer = styled.div` display: flex; - width: 50%; - height: 50vh; + width: calc(50vw - 2px); + height: calc(50vh - 2px); justify-content: center; align-items: center; `; + +export const GridItemContainerFlipped = styled.div` + display: flex; + width: calc(50vw - 2px); + height: calc(50vh - 2px); + justify-content: center; + align-items: center; + transform: rotate(180deg); +`; + diff --git a/my-app/src/Components/Counters/Counters.tsx b/my-app/src/Components/Counters/Counters.tsx index bffffda..f7a57d6 100644 --- a/my-app/src/Components/Counters/Counters.tsx +++ b/my-app/src/Components/Counters/Counters.tsx @@ -1,26 +1,36 @@ import * as S from "./Counters.style"; import LifeCounter from "../LifeCounter/LifeCounter"; +import { Player } from "../../Types/Player"; +type CountersProps = { + players: Player[]; +}; -const Counters = () => { - return ( - - - - - - - - - - - - - - - - - ); +const Counters = ({ players }: CountersProps) => { + return ( + + + {players.map((player) => { + if (player.settings.flipped) { + return ( + + opponent.key !== player.key) + } /> + + ) + } + return ( + + opponent.key !== player.key) + } /> + + ) + })} + + + ); } export default Counters; diff --git a/my-app/src/Components/LifeCounter/LifeCounter.style.ts b/my-app/src/Components/LifeCounter/LifeCounter.style.ts index 8177a5f..c92a469 100644 --- a/my-app/src/Components/LifeCounter/LifeCounter.style.ts +++ b/my-app/src/Components/LifeCounter/LifeCounter.style.ts @@ -1,36 +1,44 @@ import styled from "styled-components"; - -//LifeCounterWrapper with a background color variable: export const LifeCounterWrapper = styled.div<{ backgroundColor?: string }>` + position: relative; display: flex; - justify-content: center; + flex-direction: column; align-items: center; width: 100%; height: 100%; - background-color: ${props => props.backgroundColor || "antiquewhite"}; - border-radius: 10px; - + background-color: ${props => props.backgroundColor || "antiquewhite"}; `; -export const LifeCounterButton = styled.button` +export const LifeCountainer = styled.div` + display: flex; + flex-direction: row; + flex-grow: 1; width: 100%; height: 100%; - font-size: 5rem; - font-weight: bold; - background-color: transparent; - border: none; - outline: none; - cursor: pointer; `; export const LifeCounterText = styled.p` - font-size: 5rem; - font-weight: bold; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 30vh; text-align: center; + text-size-adjust: auto; margin: 0; padding: 0; + width: 100%; + font-variant-numeric: tabular-nums; + pointer-events: none; + user-select: none; +`; +export const ExtraCountersGrid = styled.div` + display: flex; + flex-direction: row; + flex-grow: 1; + width: 100%; `; diff --git a/my-app/src/Components/LifeCounter/LifeCounter.tsx b/my-app/src/Components/LifeCounter/LifeCounter.tsx index b2e55e8..b4885c6 100644 --- a/my-app/src/Components/LifeCounter/LifeCounter.tsx +++ b/my-app/src/Components/LifeCounter/LifeCounter.tsx @@ -1,20 +1,35 @@ import { useState } from "react"; import * as S from "./LifeCounter.style"; +import { Player } from "../../Types/Player"; +import CommanderTaxButton from "../Buttons/CommanderTaxButton"; +import PartnerCommanderTaxButton from "../Buttons/PartnerCommanderTaxButton copy"; +import AddLifeButton from "../Buttons/AddLifeButton"; +import SubtractLifeButton from "../Buttons/SubtractLifeButton"; +import CommanderDamageBar from "../Buttons/CommanderDamageBar"; type LifeCounterProps = { + player: Player; backgroundColor: string; -} + opponents: Player[]; +}; -const LifeCounter = ({backgroundColor}: LifeCounterProps) => { - const [life, setLife] = useState(40); +const LifeCounter = ({ backgroundColor, player, opponents }: LifeCounterProps) => { + const [lifeTotal, setLifeTotal] = useState(40); return ( - setLife(life - 1)}>- - {life} - setLife(life + 1)}>+ + + + + {lifeTotal} + + + + + {player.settings.usePartner && } + ); -} +}; -export default LifeCounter; \ No newline at end of file +export default LifeCounter; diff --git a/my-app/src/Icons/CommanderTaxIcon.tsx b/my-app/src/Icons/CommanderTaxIcon.tsx new file mode 100644 index 0000000..e7ff0b0 --- /dev/null +++ b/my-app/src/Icons/CommanderTaxIcon.tsx @@ -0,0 +1,39 @@ +type CommanderTaxIconProps = { + size?: string; + text?: number; +}; + +const CommanderTaxIcon = ({ size, text }: CommanderTaxIconProps) => { + return ( +
+ + CommanderTaxIcon + + + + +
+ {text} +
+
+ ); +}; + +export default CommanderTaxIcon; diff --git a/my-app/src/Types/Player.ts b/my-app/src/Types/Player.ts new file mode 100644 index 0000000..e6edc7d --- /dev/null +++ b/my-app/src/Types/Player.ts @@ -0,0 +1,15 @@ +export type Player = { + key: number; + color: string; + settings: PlayerSettings; +} + +type PlayerSettings = { + useCommanderDamage: boolean; + flipped?: boolean; + usePartner?: boolean; + usePoison?: boolean; + useEnergy?: boolean; + useExperience?: boolean; + +}