add support for more players

This commit is contained in:
Vikeo
2023-07-08 15:08:06 +02:00
parent eb2ab8e29e
commit 1dc679e2d3
15 changed files with 561 additions and 53 deletions

View File

@@ -15,17 +15,14 @@ export const initialPlayerOptions = {
const App = () => { const App = () => {
const savedPlayers = localStorage.getItem('players'); const savedPlayers = localStorage.getItem('players');
// const [players, setPlayers] = useState<Player[]>(
// savedPlayers
// ? JSON.parse(savedPlayers)
// : createInitialPlayers(initialPlayerOptions)
// );
const [gridAreas, setGridAreas] = useState(initialPlayerOptions.gridAreas);
const [players, setPlayers] = useState<Player[]>( const [players, setPlayers] = useState<Player[]>(
createInitialPlayers(initialPlayerOptions) savedPlayers
? JSON.parse(savedPlayers)
: createInitialPlayers(initialPlayerOptions)
); );
const [gridAreas, setGridAreas] = useState(initialPlayerOptions.gridAreas);
useEffect(() => { useEffect(() => {
localStorage.setItem('players', JSON.stringify(players)); localStorage.setItem('players', JSON.stringify(players));
}, [players]); }, [players]);

View File

@@ -1,9 +1,10 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import { Rotation } from '../../Types/Player';
export const StyledLifeCounterButton = styled.button<{ align?: string }>` export const StyledLifeCounterButton = styled.button`
width: 50%; width: 100%;
height: auto; height: 100%;
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
font-size: 4rem; font-size: 4rem;
font-weight: 600; font-weight: 600;
@@ -12,7 +13,6 @@ export const StyledLifeCounterButton = styled.button<{ align?: string }>`
outline: none; outline: none;
cursor: pointer; cursor: pointer;
padding: 0 28px; padding: 0 28px;
text-align: ${(props) => props.align || 'center'};
user-select: none; user-select: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
@@ -21,12 +21,37 @@ export const StyledLifeCounterButton = styled.button<{ align?: string }>`
-ms-user-select: none; -ms-user-select: none;
`; `;
const TextContainer = styled.div<{
align?: string;
rotation: number;
}>`
text-align: ${(props) => props.align || 'center'};
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
rotate: -90deg;
width: auto;
padding: 28px 0;
justify-content: space-between;
`;
}
}}
`;
type AddLifeButtonProps = { type AddLifeButtonProps = {
lifeTotal: number; lifeTotal: number;
setLifeTotal: (lifeTotal: number) => void; setLifeTotal: (lifeTotal: number) => void;
rotation: number;
}; };
const AddLifeButton = ({ lifeTotal, setLifeTotal }: AddLifeButtonProps) => { const AddLifeButton = ({
lifeTotal,
setLifeTotal,
rotation,
}: AddLifeButtonProps) => {
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [timeoutFinished, setTimeoutFinished] = useState(false); const [timeoutFinished, setTimeoutFinished] = useState(false);
const [hasPressedDown, setHasPressedDown] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false);
@@ -67,9 +92,10 @@ const AddLifeButton = ({ lifeTotal, setLifeTotal }: AddLifeButtonProps) => {
onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault(); e.preventDefault();
}} }}
align="right"
> >
&#43; <TextContainer rotation={rotation} align="right">
&#43;
</TextContainer>
</StyledLifeCounterButton> </StyledLifeCounterButton>
); );
}; };

View File

@@ -1,22 +1,51 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { Player } from '../../Types/Player'; import { Player, Rotation } from '../../Types/Player';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
const CommanderDamageGrid = styled.div` const CommanderDamageGrid = styled.div<{ rotation: number }>`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
flex-direction: column;
height: 100%;
width: auto;
`;
}
}}
`; `;
const CommanderDamageContainer = styled.div` const CommanderDamageContainer = styled.div<{
rotation: number;
}>`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
flex-direction: column;
`;
}
}}
`; `;
const CommanderDamageButton = styled.button<{ backgroundColor?: string }>` const CommanderDamageButton = styled.button<{
backgroundColor?: string;
rotation: number;
}>`
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
border: none; border: none;
@@ -31,13 +60,26 @@ const CommanderDamageButton = styled.button<{ backgroundColor?: string }>`
-moz-user-select: -moz-none; -moz-user-select: -moz-none;
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
padding: 0;
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
width: 10vmin;
height: auto;
`;
}
}}
`; `;
const CommanderDamageButtonText = styled.p` const CommanderDamageButtonText = styled.p<{
rotation: number;
}>`
position: relative; position: relative;
margin: auto; margin: auto;
font-size: 1.5rem; font-size: 1.5rem;
text-align: center;
text-size-adjust: auto; text-size-adjust: auto;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
pointer-events: none; pointer-events: none;
@@ -51,11 +93,40 @@ const CommanderDamageButtonText = styled.p`
-moz-user-select: -moz-none; -moz-user-select: -moz-none;
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
rotate: 180deg;
text-orientation: sideways;
writing-mode: vertical-lr;
height: 1rem;
width: auto;
`;
}
}}
`; `;
const VerticalSeperator = styled.div` const PartnerDamageSeperator = styled.div<{
rotation: number;
}>`
width: 1px; width: 1px;
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
width: auto;
height: 1px;
`;
}
}}
`; `;
type CommanderDamageBarProps = { type CommanderDamageBarProps = {
@@ -163,15 +234,19 @@ const CommanderDamageBar = ({
}; };
return ( return (
<CommanderDamageGrid> <CommanderDamageGrid rotation={player.settings.rotation}>
{opponents.map((opponent, index) => { {opponents.map((opponent, index) => {
if (!opponent.settings.useCommanderDamage) { if (!opponent.settings.useCommanderDamage) {
return null; return null;
} }
return ( return (
<CommanderDamageContainer key={index}> <CommanderDamageContainer
key={index}
rotation={player.settings.rotation}
>
<CommanderDamageButton <CommanderDamageButton
key={index} key={index}
rotation={player.settings.rotation}
onPointerDown={() => handleDownInput(index)} onPointerDown={() => handleDownInput(index)}
onPointerUp={() => handleUpInput(index)} onPointerUp={() => handleUpInput(index)}
onPointerLeave={handleLeaveInput} onPointerLeave={handleLeaveInput}
@@ -182,7 +257,7 @@ const CommanderDamageBar = ({
}} }}
backgroundColor={opponent.color} backgroundColor={opponent.color}
> >
<CommanderDamageButtonText> <CommanderDamageButtonText rotation={player.settings.rotation}>
{player.commanderDamage[index].damageTotal > 0 {player.commanderDamage[index].damageTotal > 0
? player.commanderDamage[index].damageTotal ? player.commanderDamage[index].damageTotal
: ''} : ''}
@@ -191,9 +266,10 @@ const CommanderDamageBar = ({
{opponent.settings.usePartner && ( {opponent.settings.usePartner && (
<> <>
<VerticalSeperator /> <PartnerDamageSeperator rotation={player.settings.rotation} />
<CommanderDamageButton <CommanderDamageButton
key={index} key={index}
rotation={player.settings.rotation}
onPointerDown={() => handlePartnerDownInput(index)} onPointerDown={() => handlePartnerDownInput(index)}
onPointerUp={() => handlePartnerUpInput(index)} onPointerUp={() => handlePartnerUpInput(index)}
onPointerLeave={handlePartnerLeaveInput} onPointerLeave={handlePartnerLeaveInput}
@@ -204,7 +280,9 @@ const CommanderDamageBar = ({
}} }}
backgroundColor={opponent.color} backgroundColor={opponent.color}
> >
<CommanderDamageButtonText> <CommanderDamageButtonText
rotation={player.settings.rotation}
>
{player.commanderDamage[index].partnerDamageTotal > 0 {player.commanderDamage[index].partnerDamageTotal > 0
? player.commanderDamage[index].partnerDamageTotal ? player.commanderDamage[index].partnerDamageTotal
: ''} : ''}

View File

@@ -1,6 +1,6 @@
import { ReactNode, useRef, useState } from 'react'; import { ReactNode, useRef, useState } from 'react';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import { CounterType } from '../../Types/Player'; import { CounterType, Rotation } from '../../Types/Player';
export const StyledExtraCounterButton = styled.button` export const StyledExtraCounterButton = styled.button`
position: relative; position: relative;
@@ -35,11 +35,27 @@ export const CenteredText = styled.div`
-ms-user-select: none; -ms-user-select: none;
`; `;
const IconContainer = styled.div<{
rotation: number;
}>`
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
rotate: -90deg;
`;
}
}}
`;
type ExtraCounterProps = { type ExtraCounterProps = {
Icon: ReactNode; Icon: ReactNode;
counterTotal?: number; counterTotal?: number;
type: CounterType; type: CounterType;
setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void; setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void;
rotation: number;
}; };
const ExtraCounter = ({ const ExtraCounter = ({
@@ -47,6 +63,7 @@ const ExtraCounter = ({
counterTotal, counterTotal,
setCounterTotal, setCounterTotal,
type, type,
rotation,
}: ExtraCounterProps) => { }: ExtraCounterProps) => {
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [timeoutFinished, setTimeoutFinished] = useState(false); const [timeoutFinished, setTimeoutFinished] = useState(false);
@@ -93,8 +110,10 @@ const ExtraCounter = ({
e.preventDefault(); e.preventDefault();
}} }}
> >
{Icon} <IconContainer rotation={rotation}>
<CenteredText>{counterTotal ? counterTotal : undefined}</CenteredText> {Icon}
<CenteredText>{counterTotal ? counterTotal : undefined}</CenteredText>
</IconContainer>
</StyledExtraCounterButton> </StyledExtraCounterButton>
); );
}; };

View File

@@ -1,7 +1,8 @@
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import SettingsIcon from '../../Icons/SettingsIcon'; import SettingsIcon from '../../Icons/SettingsIcon';
import { Rotation } from '../../Types/Player';
export const StyledExtraCounterButton = styled.button` export const StyledExtraCounterButton = styled.button<{ rotation: number }>`
position: relative; position: relative;
flex-grow: 1; flex-grow: 1;
border: none; border: none;
@@ -16,15 +17,28 @@ export const StyledExtraCounterButton = styled.button`
-moz-user-select: -moz-none; -moz-user-select: -moz-none;
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
${(props) => {
if (
props.rotation === Rotation.Side ||
props.rotation === Rotation.SideFlipped
) {
return css`
margin-bottom: 0;
top: 0
margin-right: -5vmin;
`;
}
}}
`; `;
type SettingsButtonProps = { type SettingsButtonProps = {
onClick: () => void; onClick: () => void;
rotation: number;
}; };
const SettingsButton = ({ onClick }: SettingsButtonProps) => { const SettingsButton = ({ onClick, rotation }: SettingsButtonProps) => {
return ( return (
<StyledExtraCounterButton onClick={onClick}> <StyledExtraCounterButton onClick={onClick} rotation={rotation}>
<SettingsIcon size="4vmin" /> <SettingsIcon size="4vmin" />
</StyledExtraCounterButton> </StyledExtraCounterButton>
); );

View File

@@ -1,9 +1,10 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import { Rotation } from '../../Types/Player';
export const StyledLifeCounterButton = styled.button<{ align?: string }>` export const StyledLifeCounterButton = styled.button`
width: 50%; width: 100%;
height: auto; height: 100%;
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
font-size: 4rem; font-size: 4rem;
font-weight: 600; font-weight: 600;
@@ -12,7 +13,6 @@ export const StyledLifeCounterButton = styled.button<{ align?: string }>`
outline: none; outline: none;
cursor: pointer; cursor: pointer;
padding: 0 28px; padding: 0 28px;
text-align: ${(props) => props.align || 'center'};
user-select: none; user-select: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
@@ -21,14 +21,36 @@ export const StyledLifeCounterButton = styled.button<{ align?: string }>`
-ms-user-select: none; -ms-user-select: none;
`; `;
const TextContainer = styled.div<{
align?: string;
rotation: number;
}>`
text-align: ${(props) => props.align || 'center'};
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
rotate: -90deg;
width: auto;
padding: 28px 0;
justify-content: space-between;
`;
}
}}
`;
type SubtractLifeButtonProps = { type SubtractLifeButtonProps = {
lifeTotal: number; lifeTotal: number;
setLifeTotal: (lifeTotal: number) => void; setLifeTotal: (lifeTotal: number) => void;
rotation: number;
}; };
const SubtractLifeButton = ({ const SubtractLifeButton = ({
lifeTotal, lifeTotal,
setLifeTotal, setLifeTotal,
rotation,
}: SubtractLifeButtonProps) => { }: SubtractLifeButtonProps) => {
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [timeoutFinished, setTimeoutFinished] = useState(false); const [timeoutFinished, setTimeoutFinished] = useState(false);
@@ -70,9 +92,10 @@ const SubtractLifeButton = ({
onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault(); e.preventDefault();
}} }}
align="left"
> >
&#8722; <TextContainer align="left" rotation={rotation}>
&#8722;
</TextContainer>
</StyledLifeCounterButton> </StyledLifeCounterButton>
); );
}; };

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import { Rotation } from '../../Types/Player';
export const CountersWrapper = styled.div` export const CountersWrapper = styled.div`
width: 100%; width: 100%;
@@ -22,14 +23,27 @@ export const GridItemContainer = styled.div<{
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; /* width: 100%; */
height: 100%; /* height: 100%; */
grid-area: ${(props) => props.gridArea}; grid-area: ${(props) => props.gridArea};
`; `;
export const ExtraCountersGrid = styled.div` export const ExtraCountersGrid = styled.div<{ rotation: number }>`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
${(props) => {
if (
props.rotation === Rotation.SideFlipped ||
props.rotation === Rotation.Side
) {
return css`
flex-direction: column-reverse;
height: 100%;
width: auto;
`;
}
}}
`; `;

View File

@@ -1,5 +1,6 @@
import { Player } from '../../Types/Player'; import { Player } from '../../Types/Player';
import LifeCounter from '../LifeCounter/LifeCounter'; import LifeCounter from '../LifeCounter/LifeCounter';
import SideLifeCounter from '../LifeCounter/SideLifeCounter';
import * as S from './Counters.style'; import * as S from './Counters.style';
type CountersProps = { type CountersProps = {
@@ -13,6 +14,26 @@ const Counters = ({ players, onPlayerChange, gridAreas }: CountersProps) => {
<S.CountersWrapper> <S.CountersWrapper>
<S.CountersGrid gridTemplateAreas={gridAreas}> <S.CountersGrid gridTemplateAreas={gridAreas}>
{players.map((player) => { {players.map((player) => {
if (
player.settings.rotation === 90 ||
player.settings.rotation === 270
) {
return (
<S.GridItemContainer
key={player.key}
gridArea={`player${player.key}`}
>
<SideLifeCounter
backgroundColor={player.color}
player={player}
opponents={players.filter(
(opponent) => opponent.key !== player.key
)}
onPlayerChange={onPlayerChange}
/>
</S.GridItemContainer>
);
}
return ( return (
<S.GridItemContainer <S.GridItemContainer
key={player.key} key={player.key}

View File

@@ -47,9 +47,10 @@ const ExtraCountersBar = ({
}; };
return ( return (
<S.ExtraCountersGrid> <S.ExtraCountersGrid rotation={player.settings.rotation}>
{player.settings.useCommanderDamage && ( {player.settings.useCommanderDamage && (
<ExtraCounter <ExtraCounter
rotation={player.settings.rotation}
Icon={<CommanderTaxIcon size="8vmin" />} Icon={<CommanderTaxIcon size="8vmin" />}
type={CounterType.CommanderTax} type={CounterType.CommanderTax}
counterTotal={ counterTotal={
@@ -64,6 +65,7 @@ const ExtraCountersBar = ({
player.settings.useCommanderDamage && player.settings.usePartner player.settings.useCommanderDamage && player.settings.usePartner
) && ( ) && (
<ExtraCounter <ExtraCounter
rotation={player.settings.rotation}
Icon={<PartnerTaxIcon size="8vmin" />} Icon={<PartnerTaxIcon size="8vmin" />}
type={CounterType.PartnerTax} type={CounterType.PartnerTax}
counterTotal={ counterTotal={
@@ -76,6 +78,7 @@ const ExtraCountersBar = ({
)} )}
{player.settings.usePoison && ( {player.settings.usePoison && (
<ExtraCounter <ExtraCounter
rotation={player.settings.rotation}
Icon={<PoisonIcon size="8vmin" />} Icon={<PoisonIcon size="8vmin" />}
type={CounterType.Poison} type={CounterType.Poison}
counterTotal={ counterTotal={
@@ -87,6 +90,7 @@ const ExtraCountersBar = ({
)} )}
{player.settings.useEnergy && ( {player.settings.useEnergy && (
<ExtraCounter <ExtraCounter
rotation={player.settings.rotation}
Icon={<EnergyIcon size="8vmin" />} Icon={<EnergyIcon size="8vmin" />}
type={CounterType.Energy} type={CounterType.Energy}
counterTotal={ counterTotal={
@@ -98,6 +102,7 @@ const ExtraCountersBar = ({
)} )}
{player.settings.useExperience && ( {player.settings.useExperience && (
<ExtraCounter <ExtraCounter
rotation={player.settings.rotation}
Icon={<ExperienceIcon size="8vmin" />} Icon={<ExperienceIcon size="8vmin" />}
type={CounterType.Experience} type={CounterType.Experience}
counterTotal={ counterTotal={

View File

@@ -38,6 +38,8 @@ export const LifeCountainer = styled.div`
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
height: 100%; height: 100%;
justify-content: space-between;
align-items: center;
`; `;
export const LifeCounterText = styled.p` export const LifeCounterText = styled.p`

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect } from 'react';
import * as S from './LifeCounter.style'; import * as S from './LifeCounter.style';
import { Player } from '../../Types/Player'; import { Player } from '../../Types/Player';
import { useSwipeable } from 'react-swipeable'; import { useSwipeable } from 'react-swipeable';
@@ -68,12 +68,13 @@ const LifeCounter = ({
onClick={() => { onClick={() => {
setShowPlayerMenu(!showPlayerMenu); setShowPlayerMenu(!showPlayerMenu);
}} }}
rotation={player.settings.rotation}
/> />
<div>{player.key}</div>
<S.LifeCountainer> <S.LifeCountainer>
<SubtractLifeButton <SubtractLifeButton
lifeTotal={player.lifeTotal} lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange} setLifeTotal={handleLifeChange}
rotation={player.settings.rotation}
/> />
<S.LifeCounterText> <S.LifeCounterText>
{player.lifeTotal} {player.lifeTotal}
@@ -87,6 +88,7 @@ const LifeCounter = ({
<AddLifeButton <AddLifeButton
lifeTotal={player.lifeTotal} lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange} setLifeTotal={handleLifeChange}
rotation={player.settings.rotation}
/> />
</S.LifeCountainer> </S.LifeCountainer>
<ExtraCountersBar player={player} onPlayerChange={onPlayerChange} /> <ExtraCountersBar player={player} onPlayerChange={onPlayerChange} />

View File

@@ -0,0 +1,96 @@
import styled, { css, keyframes } from 'styled-components';
import { Rotation } from '../../Types/Player';
export const SideLifeCounterWrapper = styled.div<{
backgroundColor: string;
}>`
position: relative;
display: flex;
flex-grow: 1;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
background-color: ${(props) => props.backgroundColor || 'antiquewhite'};
`;
export const SideLifeCounterContentContainer = styled.div<{
rotation: Rotation;
}>`
position: relative;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 100%;
z-index: 1;
${(props) => {
if (props.rotation === Rotation.SideFlipped) {
return css`
rotate: 180deg;
`;
}
}}
`;
export const SideLifeCountainer = styled.div`
display: flex;
flex-direction: column;
flex-grow: 1;
width: 100%;
height: 100%;
justify-content: space-between;
align-items: center;
`;
export const SideLifeCounterText = styled.p`
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
font-size: 30vmin;
text-align: center;
text-size-adjust: auto;
margin: 0;
padding: 0;
rotate: 270deg;
font-variant-numeric: tabular-nums;
pointer-events: none;
text-shadow: -1px -1px 0 #ffffff, 1px -1px 0 #ffffff, -1px 1px 0 #ffffff,
1px 1px 0 #ffffff;
color: #000000;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
-moz-user-select: -moz-none;
-webkit-user-select: none;
-ms-user-select: none;
`;
const fadeOut = keyframes`
0% {
opacity: 1;
}
33% {
opacity: 0.6;
}
100% {
opacity: 0;
}
`;
export const SideRecentDifference = styled.span`
position: absolute;
top: 40%;
left: 50%;
translate: -50%, -50%;
text-shadow: none;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 50%;
padding: 5px 10px;
font-size: 5vmin;
color: #333333;
animation: ${fadeOut} 3s 1s ease-out forwards;
`;

View File

@@ -0,0 +1,110 @@
import { useState, useEffect } from 'react';
import * as S from './SideLifeCounter.style';
import { Player } from '../../Types/Player';
import { useSwipeable } from 'react-swipeable';
import AddLifeButton from '../Buttons/AddLifeButton';
import SubtractLifeButton from '../Buttons/SubtractLifeButton';
import CommanderDamageBar from '../Buttons/CommanderDamageBar';
import PlayerMenu from '../PlayerMenu/PlayerMenu';
import SettingsButton from '../Buttons/SettingsButton';
import ExtraCountersBar from '../Counters/ExtraCountersBar';
type SideLifeCounterProps = {
player: Player;
backgroundColor: string;
opponents: Player[];
onPlayerChange: (updatedPlayer: Player) => void;
};
const SideLifeCounter = ({
backgroundColor,
player,
opponents,
onPlayerChange,
}: SideLifeCounterProps) => {
const handleLifeChange = (updatedLifeTotal: number) => {
const difference = updatedLifeTotal - player.lifeTotal;
const updatedPlayer = { ...player, lifeTotal: updatedLifeTotal };
setRecentDifference(recentDifference + difference);
onPlayerChange(updatedPlayer);
setKey(Date.now());
};
const [showPlayerMenu, setShowPlayerMenu] = useState(false);
const [recentDifference, setRecentDifference] = useState(0);
const [key, setKey] = useState(Date.now());
useEffect(() => {
const timer = setTimeout(() => {
setRecentDifference(0);
}, 3000);
return () => clearTimeout(timer);
}, [recentDifference]);
const swipeHandlers = useSwipeable({
onSwipedUp: () => {
// player.settings.flipped ? setShowPlayerMenu(true) : null;
},
onSwipedDown: () => {
// player.settings.flipped ? null : setShowPlayerMenu(true);
},
});
return (
<S.SideLifeCounterWrapper backgroundColor={backgroundColor}>
<S.SideLifeCounterContentContainer
{...swipeHandlers}
rotation={player.settings.rotation}
>
<CommanderDamageBar
lifeTotal={player.lifeTotal}
opponents={opponents}
player={player}
onPlayerChange={onPlayerChange}
setLifeTotal={handleLifeChange}
/>
<SettingsButton
onClick={() => {
setShowPlayerMenu(!showPlayerMenu);
}}
rotation={player.settings.rotation}
/>
<S.SideLifeCountainer>
<AddLifeButton
lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange}
rotation={player.settings.rotation}
/>
<S.SideLifeCounterText>
{player.lifeTotal}
{recentDifference !== 0 && (
<S.SideRecentDifference key={key}>
{recentDifference > 0 ? '+' : ''}
{recentDifference}
</S.SideRecentDifference>
)}
</S.SideLifeCounterText>
<SubtractLifeButton
lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange}
rotation={player.settings.rotation}
/>
</S.SideLifeCountainer>
<ExtraCountersBar player={player} onPlayerChange={onPlayerChange} />
</S.SideLifeCounterContentContainer>
{showPlayerMenu && (
<PlayerMenu
player={player}
opponents={opponents}
onPlayerChange={onPlayerChange}
setShowPlayerMenu={setShowPlayerMenu}
/>
)}
</S.SideLifeCounterWrapper>
);
};
export default SideLifeCounter;

View File

@@ -2,12 +2,13 @@ export enum GridTemplateAreas {
OnePlayerHorizontal = '"player1 player1"', OnePlayerHorizontal = '"player1 player1"',
OnePlayerVertical = '"player1" "player1"', OnePlayerVertical = '"player1" "player1"',
TwoPlayersOppositeHorizontal = '"player1" "player2"', TwoPlayersOppositeHorizontal = '"player1" "player2"',
TwoPlayersOppositeVertical = '"player1 player2" "player1 player2"',
TwoPlayersSameSide = '"player1 player2"', TwoPlayersSameSide = '"player1 player2"',
ThreePlayers = '"player1 player1" "player2 player3"', ThreePlayers = '"player1 player1" "player2 player3"',
ThreePlayersSide = '"player1 player1 player2" "player3 player3 player2"', ThreePlayersSide = '"player1 player1 player2" "player3 player3 player2"',
FourPlayers = '"player1 player2" "player3 player4"', FourPlayers = '"player1 player2" "player3 player4"',
FourPlayersSide = '"player1 player2 player2 player4" "player1 player3 player3 player4"', FourPlayersSide = '"player1 player2 player2 player4" "player1 player3 player3 player4"',
FivePlayers = '"player1 player1 player2 player2 player3 player3" "player4 player4 player4 player5 player5 player5"', FivePlayers = '"player1 player1 player1 player2 player2 player2" "player3 player3 player4 player4 player5 player5"',
FivePlayersSide = '"player1 player2 player3" "player4 player5 player3"', FivePlayersSide = '"player1 player2 player3" "player4 player5 player3"',
SixPlayers = '"player1 player2 player3" "player4 player5 player6"', SixPlayers = '"player1 player2 player3" "player4 player5 player6"',
SixPlayersSide = '"player1 player2 player2 player3 player3 player4" "player1 player5 player5 player6 player6 player4"', SixPlayersSide = '"player1 player2 player2 player3 player3 player4" "player1 player5 player5 player6 player6 player4"',

View File

@@ -28,6 +28,17 @@ const getRotation = (index: number, gridAreas: GridTemplateAreas): Rotation => {
return Rotation.Side; return Rotation.Side;
} }
if (gridAreas === GridTemplateAreas.TwoPlayersOppositeVertical) {
switch (index) {
case 1:
return Rotation.SideFlipped;
case 2:
return Rotation.Side;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.TwoPlayersOppositeHorizontal) { if (gridAreas === GridTemplateAreas.TwoPlayersOppositeHorizontal) {
switch (index) { switch (index) {
case 1: case 1:
@@ -68,7 +79,7 @@ const getRotation = (index: number, gridAreas: GridTemplateAreas): Rotation => {
case 1: case 1:
return Rotation.Flipped; return Rotation.Flipped;
case 2: case 2:
return Rotation.SideFlipped; return Rotation.Side;
case 3: case 3:
return Rotation.Normal; return Rotation.Normal;
default: default:
@@ -84,6 +95,95 @@ const getRotation = (index: number, gridAreas: GridTemplateAreas): Rotation => {
return Rotation.Flipped; return Rotation.Flipped;
case 3: case 3:
return Rotation.Normal; return Rotation.Normal;
case 4:
return Rotation.Normal;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.FourPlayersSide) {
switch (index) {
case 1:
return Rotation.SideFlipped;
case 2:
return Rotation.Flipped;
case 3:
return Rotation.Normal;
case 4:
return Rotation.Side;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.FivePlayers) {
switch (index) {
case 1:
return Rotation.Flipped;
case 2:
return Rotation.Flipped;
case 3:
return Rotation.Normal;
case 4:
return Rotation.Normal;
case 5:
return Rotation.Normal;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.FivePlayersSide) {
switch (index) {
case 1:
return Rotation.Flipped;
case 2:
return Rotation.Flipped;
case 3:
return Rotation.Side;
case 4:
return Rotation.Normal;
case 5:
return Rotation.Normal;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.SixPlayers) {
switch (index) {
case 1:
return Rotation.Flipped;
case 2:
return Rotation.Flipped;
case 3:
return Rotation.Flipped;
case 4:
return Rotation.Normal;
case 5:
return Rotation.Normal;
case 6:
return Rotation.Normal;
default:
return Rotation.Normal;
}
}
if (gridAreas === GridTemplateAreas.SixPlayersSide) {
switch (index) {
case 1:
return Rotation.SideFlipped;
case 2:
return Rotation.Flipped;
case 3:
return Rotation.Flipped;
case 4:
return Rotation.Side;
case 5:
return Rotation.Normal;
case 6:
return Rotation.Normal;
default: default:
return Rotation.Normal; return Rotation.Normal;
} }