mirror of
https://github.com/Vikeo/LifeTrinket.git
synced 2025-11-16 07:47:59 +00:00
add "dead" button
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"ga-4-react": "^0.1.281",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-screen-wake-lock": "^3.0.2",
|
||||
"styled-components": "^6.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,6 +8,7 @@ import CommanderDamageBar from '../Counters/CommanderDamageBar';
|
||||
import ExtraCountersBar from '../Counters/ExtraCountersBar';
|
||||
import { OutlinedText } from '../Misc/OutlinedText';
|
||||
import PlayerMenu from '../PlayerMenu/PlayerMenu';
|
||||
import { Skull } from '../../Icons/generated';
|
||||
const LifeCounterContentWrapper = styled.div<{
|
||||
backgroundColor: string;
|
||||
}>`
|
||||
@@ -78,8 +79,9 @@ const LifeCountainer = styled.div<{
|
||||
}}
|
||||
`;
|
||||
|
||||
const StartingPlayer = styled.div<{
|
||||
const PlayerNoticeWrapper = styled.div<{
|
||||
rotation: Rotation;
|
||||
backgroundColor: string;
|
||||
}>`
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
@@ -88,7 +90,7 @@ const StartingPlayer = styled.div<{
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: ${theme.palette.primary.main};
|
||||
background: ${(props) => props.backgroundColor};
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@@ -109,7 +111,7 @@ const StartingPlayer = styled.div<{
|
||||
}}
|
||||
`;
|
||||
|
||||
const StartingPlayerText = styled.div<{ rotation: Rotation }>`
|
||||
const DynamicText = styled.div<{ rotation: Rotation }>`
|
||||
font-size: 8vmin;
|
||||
${(props) => {
|
||||
if (
|
||||
@@ -175,6 +177,36 @@ export const RecentDifference = styled.span`
|
||||
animation: ${fadeOut} 3s 1s ease-out forwards;
|
||||
`;
|
||||
|
||||
export const LoseGameButton = styled.button<{ rotation: Rotation }>`
|
||||
position: absolute;
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
top: 12vmin;
|
||||
right: 6vmax;
|
||||
background-color: #43434380;
|
||||
border-radius: 15px;
|
||||
|
||||
${(props) => {
|
||||
if (props.rotation === Rotation.SideFlipped) {
|
||||
return css`
|
||||
right: auto;
|
||||
top: 6vmax;
|
||||
left: 12vmin;
|
||||
rotate: ${props.rotation}deg;
|
||||
`;
|
||||
} else if (props.rotation === Rotation.Side) {
|
||||
return css`
|
||||
right: auto;
|
||||
top: 6vmax;
|
||||
left: 12vmin;
|
||||
rotate: ${props.rotation - 180}deg;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
`;
|
||||
|
||||
interface LifeCounterProps {
|
||||
backgroundColor: string;
|
||||
player: Player;
|
||||
@@ -183,6 +215,18 @@ interface LifeCounterProps {
|
||||
resetCurrentGame: () => void;
|
||||
}
|
||||
|
||||
const hasCommanderDamageReached21 = (player: Player) => {
|
||||
const commanderDamageTotal = player.commanderDamage.reduce(
|
||||
(totalDamage, commander) => totalDamage + commander.damageTotal,
|
||||
0
|
||||
);
|
||||
const partnerDamageTotal = player.commanderDamage.reduce(
|
||||
(totalDamage, commander) => totalDamage + commander.partnerDamageTotal,
|
||||
0
|
||||
);
|
||||
return commanderDamageTotal >= 21 || partnerDamageTotal >= 21;
|
||||
};
|
||||
|
||||
const LifeCounter = ({
|
||||
backgroundColor,
|
||||
player,
|
||||
@@ -192,12 +236,21 @@ const LifeCounter = ({
|
||||
}: LifeCounterProps) => {
|
||||
const handleLifeChange = (updatedLifeTotal: number) => {
|
||||
const difference = updatedLifeTotal - player.lifeTotal;
|
||||
const updatedPlayer = { ...player, lifeTotal: updatedLifeTotal };
|
||||
const updatedPlayer = {
|
||||
...player,
|
||||
lifeTotal: updatedLifeTotal,
|
||||
hasLost: false,
|
||||
};
|
||||
setRecentDifference(recentDifference + difference);
|
||||
onPlayerChange(updatedPlayer);
|
||||
setKey(Date.now());
|
||||
};
|
||||
|
||||
const setGameLost = () => {
|
||||
const updatedPlayer = { ...player, hasLost: true };
|
||||
onPlayerChange(updatedPlayer);
|
||||
};
|
||||
|
||||
const [showPlayerMenu, setShowPlayerMenu] = useState(false);
|
||||
const [recentDifference, setRecentDifference] = useState(0);
|
||||
const [showStartingPlayer, setShowStartingPlayer] = useState(
|
||||
@@ -249,11 +302,21 @@ const LifeCounter = ({
|
||||
<LifeCounterContentWrapper backgroundColor={backgroundColor}>
|
||||
<LifeCounterWrapper rotation={player.settings.rotation}>
|
||||
{player.isStartingPlayer && !showStartingPlayer && (
|
||||
<StartingPlayer rotation={player.settings.rotation}>
|
||||
<StartingPlayerText rotation={player.settings.rotation}>
|
||||
<PlayerNoticeWrapper
|
||||
rotation={player.settings.rotation}
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
>
|
||||
<DynamicText rotation={player.settings.rotation}>
|
||||
You start!
|
||||
</StartingPlayerText>
|
||||
</StartingPlayer>
|
||||
</DynamicText>
|
||||
</PlayerNoticeWrapper>
|
||||
)}
|
||||
|
||||
{player.hasLost && (
|
||||
<PlayerNoticeWrapper
|
||||
rotation={player.settings.rotation}
|
||||
backgroundColor={'#00000070'}
|
||||
/>
|
||||
)}
|
||||
<CommanderDamageBar
|
||||
opponents={opponents}
|
||||
@@ -267,6 +330,14 @@ const LifeCounter = ({
|
||||
}}
|
||||
rotation={player.settings.rotation}
|
||||
/>
|
||||
{(player.lifeTotal < 1 || hasCommanderDamageReached21(player)) && (
|
||||
<LoseGameButton
|
||||
rotation={player.settings.rotation}
|
||||
onClick={setGameLost}
|
||||
>
|
||||
<Skull size="5vmin" color="black" opacity={0.5} />
|
||||
</LoseGameButton>
|
||||
)}
|
||||
<LifeCountainer rotation={player.settings.rotation}>
|
||||
<LifeCounterButton
|
||||
lifeTotal={player.lifeTotal}
|
||||
@@ -296,6 +367,7 @@ const LifeCounter = ({
|
||||
</LifeCountainer>
|
||||
<ExtraCountersBar player={player} onPlayerChange={onPlayerChange} />
|
||||
</LifeCounterWrapper>
|
||||
|
||||
{showPlayerMenu && (
|
||||
<PlayerMenu
|
||||
player={player}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Checkbox } from '@mui/material';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { useWakeLock } from '../../Data/useWakeLock';
|
||||
import { Energy, Experience, PartnerTax, Poison } from '../../Icons/generated';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import { useWakeLock } from 'react-screen-wake-lock';
|
||||
|
||||
type SettingsProps = {
|
||||
player: Player;
|
||||
|
||||
@@ -236,6 +236,7 @@ export const createInitialPlayers = ({
|
||||
isStartingPlayer: isStartingPlayer,
|
||||
extraCounters: [],
|
||||
commanderDamage,
|
||||
hasLost: false,
|
||||
};
|
||||
|
||||
players.push(player);
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
// https://github.com/jorisre/react-screen-wake-lock
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
const warn = (content: string) =>
|
||||
console.warn('[react-screen-wake-lock]: ' + content);
|
||||
|
||||
export interface WakeLockOptions {
|
||||
onError?: (error: Error) => void;
|
||||
onRequest?: () => void;
|
||||
onRelease?: EventListener;
|
||||
}
|
||||
|
||||
export const useWakeLock = ({
|
||||
onError,
|
||||
onRequest,
|
||||
onRelease,
|
||||
}: WakeLockOptions | undefined = {}) => {
|
||||
const [released, setReleased] = React.useState<boolean | undefined>();
|
||||
const wakeLock = React.useRef<WakeLockSentinel | null>(null);
|
||||
|
||||
// https://caniuse.com/mdn-api_wakelock
|
||||
const isSupported = typeof window !== 'undefined' && 'wakeLock' in navigator;
|
||||
|
||||
const request = React.useCallback(
|
||||
async (type: WakeLockType = 'screen') => {
|
||||
const isWakeLockAlreadyDefined = wakeLock.current != null;
|
||||
if (!isSupported) {
|
||||
return warn(
|
||||
"Calling the `request` function has no effect, Wake Lock Screen API isn't supported"
|
||||
);
|
||||
}
|
||||
if (isWakeLockAlreadyDefined) {
|
||||
return warn(
|
||||
'Calling `request` multiple times without `release` has no effect'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
wakeLock.current = await navigator.wakeLock.request(type);
|
||||
|
||||
wakeLock.current.onrelease = (e: Event) => {
|
||||
// Default to `true` - `released` API is experimental: https://caniuse.com/mdn-api_wakelocksentinel_released
|
||||
setReleased((wakeLock.current && wakeLock.current.released) || true);
|
||||
onRelease && onRelease(e);
|
||||
wakeLock.current = null;
|
||||
};
|
||||
|
||||
onRequest && onRequest();
|
||||
setReleased((wakeLock.current && wakeLock.current.released) || false);
|
||||
} catch (error: unknown) {
|
||||
onError && onError(error as Error);
|
||||
}
|
||||
},
|
||||
[isSupported, onRequest, onError, onRelease]
|
||||
);
|
||||
|
||||
const release = React.useCallback(async () => {
|
||||
const isWakeLockUndefined = wakeLock.current == null;
|
||||
if (!isSupported) {
|
||||
return warn(
|
||||
"Calling the `release` function has no effect, Wake Lock Screen API isn't supported"
|
||||
);
|
||||
}
|
||||
|
||||
if (isWakeLockUndefined) {
|
||||
return warn('Calling `release` before `request` has no effect.');
|
||||
}
|
||||
|
||||
wakeLock.current && (await wakeLock.current.release());
|
||||
}, [isSupported]);
|
||||
|
||||
return {
|
||||
isSupported,
|
||||
request,
|
||||
released,
|
||||
release,
|
||||
type: (wakeLock.current && wakeLock.current.type) || undefined,
|
||||
};
|
||||
};
|
||||
33
src/Icons/generated/Skull.tsx
Normal file
33
src/Icons/generated/Skull.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { SVGProps } from 'react';
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
size?: string;
|
||||
}
|
||||
const Skull = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={props.size || 16}
|
||||
height={props.size || 16}
|
||||
viewBox="0 0 512 512"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<g fill="currentColor">
|
||||
<path d="M237.2 37.1c-30.6 2.9-64.8 15-89.4 31.6-12.2 8.2-31.7 23.4-38.3 30-15.2 14.9-24.6 33.8-27.5 55.1-4.5 32.9 10.8 64.5 38.7 79.8 16.3 8.9 22.3 12.7 28.1 18 15 13.5 23.5 31.4 24.9 52.3.8 11.5 3.2 17.9 8.9 23.1 8.6 7.9 4.4 7.5 73.4 7.5 60.6 0 61.6 0 66-2.1 10-4.8 14.7-12.3 16-25.7 1.3-12.5 1.8-15.2 4.2-22.7 3.9-12.1 11.9-24.4 21.2-32.7 5.5-4.9 8.1-6.5 25.3-16 16.4-9.1 26.1-19.1 33.9-34.8 5.6-11.4 7.4-18.5 8.1-30.8 1.5-26.6-9.6-54.2-29.4-72.7-8.8-8.3-27.3-22.7-38.3-29.8-35.8-23.3-82.1-34.3-125.8-30.1zm-46 72c15 3.2 26.2 13.8 28.7 27.3 1.7 9.2-2.6 18.2-14 29.7-14.1 14.1-28.2 21.9-40.9 22.6-6.6.4-7.9.2-11-1.9-7.5-5-10.9-17.2-11-39 0-14.7 1.9-20.6 8.9-27.6 10.1-10 24.7-14.2 39.3-11.1zm145 0c11.6 2.5 22.7 10.8 26.9 20.2 2.2 4.8 2.4 6.4 2.3 20.7-.1 22.2-3.1 32.2-11 37.1-2.9 1.8-4.6 2.1-10.8 1.7-6.2-.3-8.6-1-15.9-4.6-15.2-7.5-30.9-21.8-36.5-33.2-11.3-23.1 15.6-48.1 45-41.9zm-73.5 102.8c4.7 3.4 11.8 13.7 13.3 19.3 2 7.3-2 13.6-11.5 17.9-12.1 5.6-29.5-2.7-29.5-14.1 0-6.8 7.2-19 13.9-23.6 4.8-3.2 8.9-3.1 13.8.5z" />
|
||||
<path d="M56.8 304.7c-1.4 1-3.6 3.1-4.9 4.6-5.1 6.6-8.2 25.8-4.7 29.3 2.9 2.9 19.8 7.1 67.8 16.9 36.1 7.4 79 17.5 79 18.7 0 .4-.6.8-1.4.8-2.3 0-73.5 23.6-95.6 31.7-20.8 7.6-40.7 16.4-44.8 19.9-1.6 1.3-2.7 4.1-3.8 9.4-2.9 14.3.2 20.8 12.5 25.5 6.3 2.4 7.3 2.5 11.7 1.4 6.2-1.6 18.9-6.3 48.4-18.2 56.7-22.9 114.7-42.6 140.7-47.7l5.8-1.2 24.5 8.5c13.5 4.7 45.2 16 70.5 25.2 78.6 28.6 95.9 34 101.1 31.6 3.2-1.4 5.4-6.7 5.4-12.8 0-9.6-3.3-16.3-10.8-22-4.8-3.6-8.7-4.9-27.7-8.8-7.1-1.5-15.9-3.8-19.5-5.2-3.6-1.3-17.1-6.5-30-11.5-12.9-4.9-30.4-11.4-38.8-14.4-8.4-2.9-15.8-5.7-16.4-6-.6-.4 4.2-2.2 10.8-4.1 11.6-3.3 27-7.7 58.3-16.9 17.6-5.2 48.9-16.6 54.1-19.6 8.8-5.2 14.8-13.3 17.4-23.3 1.3-5.3 1.4-6.2.1-8.2-2-3-5.3-3.7-13.4-2.9-19.7 1.8-89.3 19.4-175.4 44l-29.8 8.6-18.2-5.1c-20.2-5.6-63.5-18.8-91.2-27.9-18.1-5.9-52.3-16.5-62.5-19.4-9.6-2.7-16.1-3-19.2-.9z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
Skull.propTypes = {
|
||||
title: PropTypes.string,
|
||||
};
|
||||
export default Skull;
|
||||
@@ -5,3 +5,4 @@ export { default as Experience } from './Experience';
|
||||
export { default as LittleGuy } from './LittleGuy';
|
||||
export { default as PartnerTax } from './PartnerTax';
|
||||
export { default as Poison } from './Poison';
|
||||
export { default as Skull } from './Skull';
|
||||
|
||||
35
src/Icons/svgs/Skull.svg
Normal file
35
src/Icons/svgs/Skull.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="currentColor" stroke="none">
|
||||
<path d="M2372 4749 c-306 -29 -648 -150 -894 -316 -122 -82 -317 -234 -383
|
||||
-300 -152 -149 -246 -338 -275 -551 -45 -329 108 -645 387 -798 163 -89 223
|
||||
-127 281 -180 150 -135 235 -314 249 -523 8 -115 32 -179 89 -231 86 -79 44
|
||||
-75 734 -75 606 0 616 0 660 21 100 48 147 123 160 257 13 125 18 152 42 227
|
||||
39 121 119 244 212 327 55 49 81 65 253 160 164 91 261 191 339 348 56 114 74
|
||||
185 81 308 15 266 -96 542 -294 727 -88 83 -273 227 -383 298 -358 233 -821
|
||||
343 -1258 301z m-460 -720 c150 -32 262 -138 287 -273 17 -92 -26 -182 -140
|
||||
-297 -141 -141 -282 -219 -409 -226 -66 -4 -79 -2 -110 19 -75 50 -109 172
|
||||
-110 390 0 147 19 206 89 276 101 100 247 142 393 111z m1450 0 c116 -25 227
|
||||
-108 269 -202 22 -48 24 -64 23 -207 -1 -222 -31 -322 -110 -371 -29 -18 -46
|
||||
-21 -108 -17 -62 3 -86 10 -159 46 -152 75 -309 218 -365 332 -113 231 156
|
||||
481 450 419z m-735 -1028 c47 -34 118 -137 133 -193 20 -73 -20 -136 -115
|
||||
-179 -121 -56 -295 27 -295 141 0 68 72 190 139 236 48 32 89 31 138 -5z"/>
|
||||
<path d="M568 2073 c-14 -10 -36 -31 -49 -46 -51 -66 -82 -258 -47 -293 29
|
||||
-29 198 -71 678 -169 361 -74 790 -175 790 -187 0 -4 -6 -8 -14 -8 -23 0 -735
|
||||
-236 -956 -317 -208 -76 -407 -164 -448 -199 -16 -13 -27 -41 -38 -94 -29
|
||||
-143 2 -208 125 -255 63 -24 73 -25 117 -14 62 16 189 63 484 182 567 229
|
||||
1147 426 1407 477 l58 12 245 -85 c135 -47 452 -160 705 -252 786 -286 959
|
||||
-340 1011 -316 32 14 54 67 54 128 0 96 -33 163 -108 220 -48 36 -87 49 -277
|
||||
88 -71 15 -159 38 -195 52 -36 13 -171 65 -300 115 -129 49 -304 114 -388 144
|
||||
-84 29 -158 57 -164 60 -6 4 42 22 108 41 116 33 270 77 583 169 176 52 489
|
||||
166 541 196 88 52 148 133 174 233 13 53 14 62 1 82 -20 30 -53 37 -134 29
|
||||
-197 -18 -893 -194 -1754 -440 l-298 -86 -182 51 c-202 56 -635 188 -912 279
|
||||
-181 59 -523 165 -625 194 -96 27 -161 30 -192 9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -7,6 +7,7 @@ export type Player = {
|
||||
commanderDamage: CommanderDamage[];
|
||||
extraCounters: ExtraCounter[];
|
||||
isStartingPlayer: boolean;
|
||||
hasLost: boolean;
|
||||
};
|
||||
|
||||
export type PlayerSettings = {
|
||||
|
||||
@@ -6475,6 +6475,11 @@ react-is@^18.2.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-screen-wake-lock@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-screen-wake-lock/-/react-screen-wake-lock-3.0.2.tgz#ce185ebfdb74a82c89d532726738f60466f00438"
|
||||
integrity sha512-f88vcfMG1AWYRSIWQ5Qx5YVboH6TSL0F4ZlFLERZp6aKiZRGVRAAJ9wedJdO5jqTMcCDZ4OXJ8PjcSkDmvGSBg==
|
||||
|
||||
react-transition-group@^4.4.5:
|
||||
version "4.4.5"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
|
||||
|
||||
Reference in New Issue
Block a user