Compare commits

...

10 Commits

Author SHA1 Message Date
Viktor Rådberg
a18c253624 bump 2024-01-31 23:12:46 +01:00
Viktor Rådberg
3f319c4f3c add some blur to settings 2024-01-31 23:12:31 +01:00
Viktor Rådberg
db80e563f2 bump 2024-01-27 18:05:54 +01:00
Viktor Rådberg
573af42b75 fix taps and some settings stuff 2024-01-27 18:05:18 +01:00
Viktor Rådberg
89e1eaff4e bump 2024-01-27 16:25:40 +01:00
Viktor Rådberg
0f4e896342 Merge pull request #31 from Vikeo/swipable-settings
Swipable settings
2024-01-27 16:23:54 +01:00
Viktor Rådberg
dc1d5fe01d tsc 2024-01-27 16:20:09 +01:00
Viktor Rådberg
41e73d2c0c swipe 2024-01-27 11:05:54 +01:00
Viktor Rådberg
724dcf086c is side 2024-01-27 09:32:00 +01:00
Viktor Rådberg
51f9c4d20e initial test 2024-01-26 21:24:40 +01:00
16 changed files with 246 additions and 146 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "life-trinket", "name": "life-trinket",
"private": true, "private": true,
"version": "0.6.0", "version": "0.6.3",
"type": "commonjs", "type": "commonjs",
"engines": { "engines": {
"node": ">=18", "node": ">=18",
@@ -22,6 +22,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-screen-wake-lock": "^3.0.2", "react-screen-wake-lock": "^3.0.2",
"react-swipeable": "^7.0.1",
"react-twc": "^1.3.0", "react-twc": "^1.3.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },

View File

@@ -17,6 +17,8 @@ export type RotationButtonProps = TwcComponentProps<'button'> & {
$rotation?: number; $rotation?: number;
}; };
export const MAX_TAP_MOVE_DISTANCE = 20;
const CommanderDamageContainer = twc.div<RotationDivProps>((props) => [ const CommanderDamageContainer = twc.div<RotationDivProps>((props) => [
'flex flex-grow', 'flex flex-grow',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
@@ -38,7 +40,7 @@ const CommanderDamageTextContainer = twc.div<RotationDivProps>((props) => [
: '', : '',
]); ]);
const PartnerDamageSeperator = twc.div<RotationDivProps>((props) => [ const PartnerDamageSeparator = twc.div<RotationDivProps>((props) => [
'bg-black', 'bg-black',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? 'w-full h-px' ? 'w-full h-px'
@@ -54,6 +56,7 @@ type CommanderDamageButtonComponentProps = {
type InputProps = { type InputProps = {
opponentIndex: number; opponentIndex: number;
isPartner: boolean; isPartner: boolean;
event: React.PointerEvent<HTMLButtonElement>;
}; };
export const CommanderDamage = ({ export const CommanderDamage = ({
@@ -65,10 +68,7 @@ export const CommanderDamage = ({
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);
const downPositionRef = useRef({ x: 0, y: 0 });
const isSide =
player.settings.rotation === Rotation.Side ||
player.settings.rotation === Rotation.SideFlipped;
const handleCommanderDamageChange = ( const handleCommanderDamageChange = (
index: number, index: number,
@@ -107,7 +107,8 @@ export const CommanderDamage = ({
handleLifeChange(player.lifeTotal - increment); handleLifeChange(player.lifeTotal - increment);
}; };
const handleDownInput = ({ opponentIndex, isPartner }: InputProps) => { const handleDownInput = ({ opponentIndex, isPartner, event }: InputProps) => {
downPositionRef.current = { x: event.clientX, y: event.clientY };
setTimeoutFinished(false); setTimeoutFinished(false);
setHasPressedDown(true); setHasPressedDown(true);
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
@@ -116,11 +117,23 @@ export const CommanderDamage = ({
}, decrementTimeoutMs); }, decrementTimeoutMs);
}; };
const handleUpInput = ({ opponentIndex, isPartner }: InputProps) => { const handleUpInput = ({ opponentIndex, isPartner, event }: InputProps) => {
if (!(hasPressedDown && !timeoutFinished)) { if (!(hasPressedDown && !timeoutFinished)) {
return; return;
} }
clearTimeout(timeoutRef.current);
const upPosition = { x: event.clientX, y: event.clientY };
const hasMoved =
Math.abs(upPosition.x - downPositionRef.current.x) >
MAX_TAP_MOVE_DISTANCE ||
Math.abs(upPosition.y - downPositionRef.current.y) >
MAX_TAP_MOVE_DISTANCE;
if (hasMoved) {
return;
}
handleCommanderDamageChange(opponentIndex, 1, isPartner); handleCommanderDamageChange(opponentIndex, 1, isPartner);
setHasPressedDown(false); setHasPressedDown(false);
}; };
@@ -132,9 +145,9 @@ export const CommanderDamage = ({
}; };
const opponentIndex = opponent.index; const opponentIndex = opponent.index;
const fontSize = isSide ? '4vmax' : '7vmin'; const fontSize = player.isSide ? '4vmax' : '7vmin';
const fontWeight = 'bold'; const fontWeight = 'bold';
const strokeWidth = isSide ? '0.4vmax' : '0.7vmin'; const strokeWidth = player.isSide ? '0.4vmax' : '0.7vmin';
return ( return (
<CommanderDamageContainer <CommanderDamageContainer
@@ -145,10 +158,12 @@ export const CommanderDamage = ({
<CommanderDamageButton <CommanderDamageButton
key={opponentIndex} key={opponentIndex}
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
onPointerDown={() => onPointerDown={(e) =>
handleDownInput({ opponentIndex, isPartner: false }) handleDownInput({ opponentIndex, isPartner: false, event: e })
}
onPointerUp={(e) =>
handleUpInput({ opponentIndex, isPartner: false, event: e })
} }
onPointerUp={() => handleUpInput({ opponentIndex, isPartner: false })}
onPointerLeave={handleLeaveInput} onPointerLeave={handleLeaveInput}
onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { onContextMenu={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault(); e.preventDefault();
@@ -171,15 +186,15 @@ export const CommanderDamage = ({
{opponent.settings.usePartner && ( {opponent.settings.usePartner && (
<> <>
<PartnerDamageSeperator $rotation={player.settings.rotation} /> <PartnerDamageSeparator $rotation={player.settings.rotation} />
<CommanderDamageButton <CommanderDamageButton
key={opponentIndex} key={opponentIndex}
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
onPointerDown={() => onPointerDown={(e) =>
handleDownInput({ opponentIndex, isPartner: true }) handleDownInput({ opponentIndex, isPartner: true, event: e })
} }
onPointerUp={() => onPointerUp={(e) =>
handleUpInput({ opponentIndex, isPartner: true }) handleUpInput({ opponentIndex, isPartner: true, event: e })
} }
onPointerLeave={handleLeaveInput} onPointerLeave={handleLeaveInput}
onContextMenu={( onContextMenu={(

View File

@@ -3,7 +3,7 @@ import { twc } from 'react-twc';
import { decrementTimeoutMs } from '../../Data/constants'; import { decrementTimeoutMs } from '../../Data/constants';
import { CounterType, Rotation } from '../../Types/Player'; import { CounterType, Rotation } from '../../Types/Player';
import { OutlinedText } from '../Misc/OutlinedText'; import { OutlinedText } from '../Misc/OutlinedText';
import { RotationDivProps } from './CommanderDamage'; import { MAX_TAP_MOVE_DISTANCE, RotationDivProps } from './CommanderDamage';
const ExtraCounterContainer = twc.div` const ExtraCounterContainer = twc.div`
flex flex
@@ -47,6 +47,7 @@ type ExtraCounterProps = {
type: CounterType; type: CounterType;
setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void; setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void;
rotation: number; rotation: number;
isSide: boolean;
playerIndex: number; playerIndex: number;
}; };
@@ -56,14 +57,13 @@ const ExtraCounter = ({
setCounterTotal, setCounterTotal,
type, type,
rotation, rotation,
isSide,
playerIndex, playerIndex,
}: 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);
const [hasPressedDown, setHasPressedDown] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false);
const downPositionRef = useRef({ x: 0, y: 0 });
const isSide =
rotation === Rotation.Side || rotation === Rotation.SideFlipped;
const handleCountChange = (increment: number) => { const handleCountChange = (increment: number) => {
if (!counterTotal) { if (!counterTotal) {
@@ -73,7 +73,8 @@ const ExtraCounter = ({
setCounterTotal(counterTotal + increment, type); setCounterTotal(counterTotal + increment, type);
}; };
const handleDownInput = () => { const handleDownInput = (event: React.PointerEvent<HTMLButtonElement>) => {
downPositionRef.current = { x: event.clientX, y: event.clientY };
setTimeoutFinished(false); setTimeoutFinished(false);
setHasPressedDown(true); setHasPressedDown(true);
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
@@ -82,10 +83,23 @@ const ExtraCounter = ({
}, decrementTimeoutMs); }, decrementTimeoutMs);
}; };
const handleUpInput = () => { const handleUpInput = (event: React.PointerEvent<HTMLButtonElement>) => {
if (!(hasPressedDown && !timeoutFinished)) { if (!(hasPressedDown && !timeoutFinished)) {
return; return;
} }
const upPosition = { x: event.clientX, y: event.clientY };
const hasMoved =
Math.abs(upPosition.x - downPositionRef.current.x) >
MAX_TAP_MOVE_DISTANCE ||
Math.abs(upPosition.y - downPositionRef.current.y) >
MAX_TAP_MOVE_DISTANCE;
if (hasMoved) {
return;
}
clearTimeout(timeoutRef.current); clearTimeout(timeoutRef.current);
handleCountChange(1); handleCountChange(1);
setHasPressedDown(false); setHasPressedDown(false);

View File

@@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
import { TwcComponentProps, twc } from 'react-twc'; import { TwcComponentProps, twc } from 'react-twc';
import { lifeLongPressMultiplier } from '../../Data/constants'; import { lifeLongPressMultiplier } from '../../Data/constants';
import { Rotation } from '../../Types/Player'; import { Rotation } from '../../Types/Player';
import { MAX_TAP_MOVE_DISTANCE } from './CommanderDamage';
type RotationButtonProps = TwcComponentProps<'div'> & { type RotationButtonProps = TwcComponentProps<'div'> & {
$align?: string; $align?: string;
@@ -56,12 +57,14 @@ const LifeCounterButton = ({
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);
const downPositionRef = useRef({ x: 0, y: 0 });
const handleLifeChange = (increment: number) => { const handleLifeChange = (increment: number) => {
setLifeTotal(lifeTotal + increment); setLifeTotal(lifeTotal + increment);
}; };
const handleDownInput = () => { const handleDownInput = (event: React.PointerEvent<HTMLButtonElement>) => {
downPositionRef.current = { x: event.clientX, y: event.clientY };
setTimeoutFinished(false); setTimeoutFinished(false);
setHasPressedDown(true); setHasPressedDown(true);
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
@@ -70,10 +73,23 @@ const LifeCounterButton = ({
}, 500); }, 500);
}; };
const handleUpInput = () => { const handleUpInput = (event: React.PointerEvent<HTMLButtonElement>) => {
if (!(hasPressedDown && !timeoutFinished)) { if (!(hasPressedDown && !timeoutFinished)) {
return; return;
} }
const upPosition = { x: event.clientX, y: event.clientY };
const hasMoved =
Math.abs(upPosition.x - downPositionRef.current.x) >
MAX_TAP_MOVE_DISTANCE ||
Math.abs(upPosition.y - downPositionRef.current.y) >
MAX_TAP_MOVE_DISTANCE;
if (hasMoved) {
return;
}
clearTimeout(timeoutRef.current); clearTimeout(timeoutRef.current);
handleLifeChange(operation === 'add' ? 1 : -1); handleLifeChange(operation === 'add' ? 1 : -1);
setHasPressedDown(false); setHasPressedDown(false);

View File

@@ -4,13 +4,11 @@ import { Rotation } from '../../Types/Player';
import { RotationDivProps } from './CommanderDamage'; import { RotationDivProps } from './CommanderDamage';
const LoseButton = twc.div<RotationDivProps>((props) => [ const LoseButton = twc.div<RotationDivProps>((props) => [
'absolute flex-grow border-none outline-none cursor-pointer bg-interface-loseButton-background rounded-lg select-none z-[1] webkit-user-select-none', 'absolute flex-grow border-none outline-none cursor-pointer bg-interface-loseButton-background rounded-lg select-none z-[1] webkit-user-select-none py-2 px-4 ',
props.$rotation === Rotation.SideFlipped props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? `right-auto top-[15%] left-[27%]` ? `left-[19%]`
: props.$rotation === Rotation.Side : 'top-[21%]',
? `right-auto top-[15%] left-[27%]`
: 'right-[15%] top-1/4',
]); ]);
type LoseButtonProps = { type LoseButtonProps = {
@@ -24,6 +22,8 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
? rotation ? rotation
: rotation === Rotation.Side : rotation === Rotation.Side
? rotation - 180 ? rotation - 180
: rotation === Rotation.Flipped
? rotation - 180
: rotation; : rotation;
return ( return (
@@ -33,7 +33,7 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
aria-label={`Lose Game`} aria-label={`Lose Game`}
style={{ rotate: `${calcRotation}deg` }} style={{ rotate: `${calcRotation}deg` }}
> >
<Skull size="5vmin" color="black" opacity={0.5} /> <Skull size="8vmin" color="black" opacity={0.5} />
</LoseButton> </LoseButton>
); );
}; };

View File

@@ -1,30 +0,0 @@
import { twc } from 'react-twc';
import { Cog } from '../../Icons/generated';
import { Rotation } from '../../Types/Player';
import { RotationButtonProps } from './CommanderDamage';
const SettingsButtonTwc = twc.button<RotationButtonProps>((props) => [
'absolute flex-grow border-none outline-none cursor-pointer bg-transparent z-[1] select-none webkit-user-select-none',
props.$rotation === Rotation.Side || props.$rotation === Rotation.SideFlipped
? `right-auto top-[1vmax] left-[27%]`
: 'top-1/4 right-[1vmax]',
]);
type SettingsButtonProps = {
onClick: () => void;
rotation: Rotation;
};
const SettingsButton = ({ onClick, rotation }: SettingsButtonProps) => {
return (
<SettingsButtonTwc
onClick={onClick}
$rotation={rotation}
aria-label={`Settings`}
>
<Cog size="5vmin" color="black" opacity="0.3" />
</SettingsButtonTwc>
);
};
export default SettingsButton;

View File

@@ -100,6 +100,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'commanderTax' (counter) => counter.type === 'commanderTax'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -114,6 +115,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'partnerTax' (counter) => counter.type === 'partnerTax'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -127,6 +129,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
player.extraCounters?.find((counter) => counter.type === 'poison') player.extraCounters?.find((counter) => counter.type === 'poison')
?.value ?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -140,6 +143,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
player.extraCounters?.find((counter) => counter.type === 'energy') player.extraCounters?.find((counter) => counter.type === 'energy')
?.value ?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -154,6 +158,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'experience' (counter) => counter.type === 'experience'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />

View File

@@ -8,7 +8,7 @@ import {
import LifeCounterButton from '../Buttons/LifeCounterButton'; import LifeCounterButton from '../Buttons/LifeCounterButton';
import { OutlinedText } from '../Misc/OutlinedText'; import { OutlinedText } from '../Misc/OutlinedText';
const LifeCountainer = twc.div<RotationDivProps>((props) => [ const LifeContainer = twc.div<RotationDivProps>((props) => [
'flex flex-grow relative w-full h-full justify-between items-center', 'flex flex-grow relative w-full h-full justify-between items-center',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? 'flex-col-reverse' ? 'flex-col-reverse'
@@ -49,7 +49,6 @@ type HealthProps = {
const Health = ({ const Health = ({
player, player,
rotation,
handleLifeChange, handleLifeChange,
differenceKey, differenceKey,
recentDifference, recentDifference,
@@ -99,12 +98,13 @@ const Health = ({
}, [textContainerRef]); }, [textContainerRef]);
const calculateFontSize = (container: HTMLDivElement) => { const calculateFontSize = (container: HTMLDivElement) => {
const isSide = const widthRatio = player.isSide
rotation === Rotation.SideFlipped || rotation === Rotation.Side; ? container.clientHeight
: container.clientWidth;
const widthRatio = isSide ? container.clientHeight : container.clientWidth; const heightRatio = player.isSide
? container.clientWidth
const heightRatio = isSide ? container.clientWidth : container.clientHeight; : container.clientHeight;
const minRatio = Math.min(widthRatio, heightRatio); const minRatio = Math.min(widthRatio, heightRatio);
@@ -116,7 +116,7 @@ const Health = ({
}; };
return ( return (
<LifeCountainer $rotation={player.settings.rotation}> <LifeContainer $rotation={player.settings.rotation}>
<LifeCounterButton <LifeCounterButton
lifeTotal={player.lifeTotal} lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange} setLifeTotal={handleLifeChange}
@@ -154,7 +154,7 @@ const Health = ({
operation="add" operation="add"
increment={1} increment={1}
/> />
</LifeCountainer> </LifeContainer>
); );
}; };

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSwipeable } from 'react-swipeable';
import { twc } from 'react-twc'; import { twc } from 'react-twc';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings'; import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers'; import { usePlayers } from '../../Hooks/usePlayers';
import { Player, Rotation } from '../../Types/Player'; import { Player, Rotation } from '../../Types/Player';
import { RotationDivProps } from '../Buttons/CommanderDamage'; import { RotationDivProps } from '../Buttons/CommanderDamage';
import { LoseGameButton } from '../Buttons/LoseButton'; import { LoseGameButton } from '../Buttons/LoseButton';
import SettingsButton from '../Buttons/SettingsButton';
import CommanderDamageBar from '../Counters/CommanderDamageBar'; import CommanderDamageBar from '../Counters/CommanderDamageBar';
import ExtraCountersBar from '../Counters/ExtraCountersBar'; import ExtraCountersBar from '../Counters/ExtraCountersBar';
import PlayerMenu from '../Player/PlayerMenu'; import PlayerMenu from '../Player/PlayerMenu';
@@ -24,7 +24,7 @@ const LifeCounterWrapper = twc.div<RotationDivProps>((props) => [
const StartingPlayerNoticeWrapper = twc.div`z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-primary-main`; const StartingPlayerNoticeWrapper = twc.div`z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-primary-main`;
const PlayerLostWrapper = twc.div<RotationDivProps>((props) => [ const PlayerLostWrapper = twc.div<RotationDivProps>((props) => [
'z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-lifeCounter-lostWrapper', 'z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-lifeCounter-lostWrapper opacity-75',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? `rotate-[${props.$rotation - 90}deg]` ? `rotate-[${props.$rotation - 90}deg]`
: '', : '',
@@ -64,6 +64,8 @@ type LifeCounterProps = {
opponents: Player[]; opponents: Player[];
}; };
const RECENT_DIFFERENCE_TTL = 3_000;
const LifeCounter = ({ player, opponents }: LifeCounterProps) => { const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
const { updatePlayer, updateLifeTotal } = usePlayers(); const { updatePlayer, updateLifeTotal } = usePlayers();
const { settings } = useGlobalSettings(); const { settings } = useGlobalSettings();
@@ -71,14 +73,53 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
const [showPlayerMenu, setShowPlayerMenu] = useState(false); const [showPlayerMenu, setShowPlayerMenu] = useState(false);
const [recentDifference, setRecentDifference] = useState(0); const [recentDifference, setRecentDifference] = useState(0);
const [differenceKey, setDifferenceKey] = useState(Date.now()); const [differenceKey, setDifferenceKey] = useState(Date.now());
const [isLandscape, setIsLandscape] = useState(false);
const calcRot = player.isSide
? player.settings.rotation - 180
: player.settings.rotation;
const rotationAngle = isLandscape ? calcRot : calcRot + 90;
const handlers = useSwipeable({
trackMouse: true,
onSwipedDown: (e) => {
e.event.stopPropagation();
console.log(`User DOWN Swiped on player ${player.index}`);
setShowPlayerMenu(true);
},
onSwipedUp: (e) => {
e.event.stopPropagation();
console.log(`User UP Swiped on player ${player.index}`);
setShowPlayerMenu(false);
},
swipeDuration: 500,
onSwiping: (e) => e.event.stopPropagation(),
rotationAngle,
});
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setRecentDifference(0); setRecentDifference(0);
}, 3_000); }, RECENT_DIFFERENCE_TTL);
return () => clearTimeout(timer); const resizeObserver = new ResizeObserver(() => {
}, [recentDifference]); if (document.body.clientWidth > document.body.clientHeight)
setIsLandscape(true);
else setIsLandscape(false);
return;
});
resizeObserver.observe(document.body);
return () => {
clearTimeout(timer);
// Cleanup: disconnect the ResizeObserver when the component unmounts.
resizeObserver.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [recentDifference, document.body.clientHeight, document.body.clientWidth]);
useEffect(() => { useEffect(() => {
if (player.showStartingPlayer) { if (player.showStartingPlayer) {
@@ -124,6 +165,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
<LifeCounterWrapper <LifeCounterWrapper
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
style={{ rotate: `${calcRotation}deg` }} style={{ rotate: `${calcRotation}deg` }}
{...handlers}
> >
{settings.showStartingPlayer && {settings.showStartingPlayer &&
player.isStartingPlayer && player.isStartingPlayer &&
@@ -150,12 +192,6 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
key={player.index} key={player.index}
handleLifeChange={handleLifeChange} handleLifeChange={handleLifeChange}
/> />
<SettingsButton
onClick={() => {
setShowPlayerMenu(!showPlayerMenu);
}}
rotation={player.settings.rotation}
/>
{playerCanLose(player) && ( {playerCanLose(player) && (
<LoseGameButton <LoseGameButton
rotation={player.settings.rotation} rotation={player.settings.rotation}
@@ -170,9 +206,12 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
handleLifeChange={handleLifeChange} handleLifeChange={handleLifeChange}
/> />
<ExtraCountersBar player={player} /> <ExtraCountersBar player={player} />
{showPlayerMenu && (
<PlayerMenu player={player} setShowPlayerMenu={setShowPlayerMenu} /> <PlayerMenu
)} isShown={showPlayerMenu}
player={player}
setShowPlayerMenu={setShowPlayerMenu}
/>
</LifeCounterWrapper> </LifeCounterWrapper>
</LifeCounterContentWrapper> </LifeCounterContentWrapper>
); );

View File

@@ -6,7 +6,6 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers'; import { usePlayers } from '../../Hooks/usePlayers';
import { useSafeRotate } from '../../Hooks/useSafeRotate'; import { useSafeRotate } from '../../Hooks/useSafeRotate';
import { import {
Cross,
Energy, Energy,
Exit, Exit,
Experience, Experience,
@@ -17,10 +16,7 @@ import {
ResetGame, ResetGame,
} from '../../Icons/generated'; } from '../../Icons/generated';
import { Player, Rotation } from '../../Types/Player'; import { Player, Rotation } from '../../Types/Player';
import { import { RotationDivProps } from '../Buttons/CommanderDamage';
RotationButtonProps,
RotationDivProps,
} from '../Buttons/CommanderDamage';
const CheckboxContainer = twc.div``; const CheckboxContainer = twc.div``;
@@ -31,10 +27,12 @@ const PlayerMenuWrapper = twc.div`
w-full w-full
h-full h-full
bg-background-settings bg-background-settings
backdrop-blur-[10px]
items-center items-center
justify-center justify-center
z-[2] z-[2]
webkit-user-select-none webkit-user-select-none
transition-all
`; `;
const BetterRowContainer = twc.div` const BetterRowContainer = twc.div`
@@ -43,16 +41,19 @@ const BetterRowContainer = twc.div`
flex-grow flex-grow
w-full w-full
h-full h-full
justify-end justify-between
items-stretch items-stretch
`; `;
const TogglesSection = twc.div` const TogglesSection = twc.div`
flex flex
relative
flex-row flex-row
flex-wrap
relative
gap-2 gap-2
h-full
justify-evenly justify-evenly
items-center
`; `;
const ButtonsSections = twc.div` const ButtonsSections = twc.div`
@@ -62,14 +63,14 @@ const ButtonsSections = twc.div`
justify-between justify-between
p-[3%] p-[3%]
items-center items-center
flex-wrap
`; `;
const ColorPicker = twc.input` const ColorPicker = twc.input`
absolute
top-[5%]
left-[5%]
h-[8vmax] h-[8vmax]
w-[8vmax] w-[8vmax]
max-h-12
max-w-11
border-none border-none
outline-none outline-none
cursor-pointer cursor-pointer
@@ -85,21 +86,17 @@ const SettingsContainer = twc.div<RotationDivProps>((props) => [
: 'flex-row', : 'flex-row',
]); ]);
const CloseButton = twc.button<RotationButtonProps>((props) => [
'absolute border-none outline-none cursor-pointer bg-transparent z-[99]',
props.$rotation === Rotation.Side
? `top-[5%] right-auto left-[5%]`
: props.$rotation === Rotation.SideFlipped
? 'top-auto left-auto bottom-[5%] right-[5%]'
: 'top-[15%] right-[5%]',
]);
type PlayerMenuProps = { type PlayerMenuProps = {
player: Player; player: Player;
setShowPlayerMenu: (showPlayerMenu: boolean) => void; setShowPlayerMenu: (showPlayerMenu: boolean) => void;
isShown: boolean;
}; };
const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => { const PlayerMenu = ({
player,
setShowPlayerMenu,
isShown,
}: PlayerMenuProps) => {
const settingsContainerRef = useRef<HTMLDivElement | null>(null); const settingsContainerRef = useRef<HTMLDivElement | null>(null);
const dialogRef = useRef<HTMLDialogElement | null>(null); const dialogRef = useRef<HTMLDialogElement | null>(null);
@@ -108,9 +105,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
containerRef: settingsContainerRef, containerRef: settingsContainerRef,
}); });
const handleOnClick = () => {
setShowPlayerMenu(false);
};
const { fullscreen, wakeLock, goToStart } = useGlobalSettings(); const { fullscreen, wakeLock, goToStart } = useGlobalSettings();
const { updatePlayer, resetCurrentGame } = usePlayers(); const { updatePlayer, resetCurrentGame } = usePlayers();
@@ -142,7 +136,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
const buttonFontSize = isSide ? '1.5vmax' : '3vmin'; const buttonFontSize = isSide ? '1.5vmax' : '3vmin';
const iconSize = isSide ? '6vmin' : '3vmax'; const iconSize = isSide ? '6vmin' : '3vmax';
const extraCountersSize = isSide ? '8vmin' : '4vmax'; const extraCountersSize = isSide ? '8vmin' : '4vmax';
const closeButtonSize = isSide ? '6vmin' : '3vmax';
const calcRotation = const calcRotation =
player.settings.rotation === Rotation.Side player.settings.rotation === Rotation.Side
@@ -156,33 +149,10 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
//TODO: Fix hacky solution to rotation for SideFlipped //TODO: Fix hacky solution to rotation for SideFlipped
style={{ style={{
rotate: rotate:
player.settings.rotation === Rotation.SideFlipped ? '180deg' : '', player.settings.rotation === Rotation.SideFlipped ? `180deg` : '',
translate: isShown ? '' : player.isSide ? `-100%` : `0 -100%`,
}} }}
> >
<CloseButton
$rotation={player.settings.rotation}
style={{
rotate:
player.settings.rotation === Rotation.Side ||
player.settings.rotation === Rotation.SideFlipped
? `${player.settings.rotation - 180}deg`
: '',
}}
>
<Button
variant="text"
onClick={handleOnClick}
style={{
margin: 0,
padding: 0,
height: closeButtonSize,
width: closeButtonSize,
}}
>
<Cross size={closeButtonSize} />
</Button>
</CloseButton>
<SettingsContainer <SettingsContainer
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
style={{ style={{
@@ -190,15 +160,15 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
}} }}
ref={settingsContainerRef} ref={settingsContainerRef}
> >
<ColorPicker
type="color"
value={player.color}
onChange={handleColorChange}
role="button"
aria-label="Color picker"
/>
<BetterRowContainer> <BetterRowContainer>
<TogglesSection> <TogglesSection>
<ColorPicker
type="color"
value={player.color}
onChange={handleColorChange}
role="button"
aria-label="Color picker"
/>
{player.settings.useCommanderDamage && ( {player.settings.useCommanderDamage && (
<CheckboxContainer> <CheckboxContainer>
<Checkbox <Checkbox
@@ -373,9 +343,9 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
</BetterRowContainer> </BetterRowContainer>
<dialog <dialog
ref={dialogRef} ref={dialogRef}
className="z-[9999] min-h-2/4 bg-background-default text-text-primary rounded-2xl border-none absolute top-[10%]" className="z-[9999] min-h-2/4 bg-background-default text-text-primary rounded-2xl border-none absolute bottom-[20%]"
> >
<div className="h-full flex flex-col p-4 gap-2"> <div className="flex flex-col p-4 gap-2">
<h1 className="text-center">Reset Game?</h1> <h1 className="text-center">Reset Game?</h1>
<div className="flex justify-evenly gap-4"> <div className="flex justify-evenly gap-4">
<Button <Button

View File

@@ -229,6 +229,7 @@ export const createInitialPlayers = ({
extraCounters: [], extraCounters: [],
commanderDamage, commanderDamage,
hasLost: false, hasLost: false,
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
}; };
players.push(player); players.push(player);

View File

@@ -0,0 +1,53 @@
import { useEffect, useState } from 'react';
export interface OrientationState {
angle: number;
type: string;
}
const defaultState: OrientationState = {
angle: 0,
type: 'landscape-primary',
};
export default function useOrientation(
initialState: OrientationState = defaultState
) {
const [state, setState] = useState(initialState);
const [isLandscape, setIsLandscape] = useState(false);
useEffect(() => {
const screen = window.screen;
let mounted = true;
const onChange = () => {
if (mounted) {
const { orientation } = screen;
console.log(orientation);
if (orientation) {
const { angle, type } = orientation;
setState({ angle, type });
if (type.includes('landscape')) {
setIsLandscape(true);
} else if (type.includes('portrait')) {
setIsLandscape(false);
}
} else if (window.orientation !== undefined) {
setState({
angle:
typeof window.orientation === 'number' ? window.orientation : 0,
type: '',
});
}
}
};
onChange();
return () => {
mounted = false;
};
}, [isLandscape]);
return { state, isLandscape };
}

View File

@@ -8,6 +8,7 @@ export type Player = {
isStartingPlayer: boolean; isStartingPlayer: boolean;
showStartingPlayer: boolean; showStartingPlayer: boolean;
hasLost: boolean; hasLost: boolean;
isSide: boolean;
}; };
export type PlayerSettings = { export type PlayerSettings = {

View File

@@ -2,8 +2,18 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
html {
overflow: hidden;
}
body {
overflow: auto;
}
html, html,
body { body {
height: 100%;
position: relative;
background-color: theme('colors.background.default'); background-color: theme('colors.background.default');
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

View File

@@ -67,7 +67,7 @@ export default {
}, },
lifeCounter: { lifeCounter: {
text: 'rgba(0, 0, 0, 0.4)', text: 'rgba(0, 0, 0, 0.4)',
lostWrapper: '#00000070', lostWrapper: '#000000',
}, },
interface: { interface: {
loseButton: { loseButton: {

View File

@@ -7125,6 +7125,11 @@ react-screen-wake-lock@^3.0.2:
resolved "https://registry.yarnpkg.com/react-screen-wake-lock/-/react-screen-wake-lock-3.0.2.tgz#ce185ebfdb74a82c89d532726738f60466f00438" resolved "https://registry.yarnpkg.com/react-screen-wake-lock/-/react-screen-wake-lock-3.0.2.tgz#ce185ebfdb74a82c89d532726738f60466f00438"
integrity sha512-f88vcfMG1AWYRSIWQ5Qx5YVboH6TSL0F4ZlFLERZp6aKiZRGVRAAJ9wedJdO5jqTMcCDZ4OXJ8PjcSkDmvGSBg== integrity sha512-f88vcfMG1AWYRSIWQ5Qx5YVboH6TSL0F4ZlFLERZp6aKiZRGVRAAJ9wedJdO5jqTMcCDZ4OXJ8PjcSkDmvGSBg==
react-swipeable@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c"
integrity sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ==
react-transition-group@^4.4.5: react-transition-group@^4.4.5:
version "4.4.5" version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"