diff --git a/package.json b/package.json index a70eadf..4331f3a 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/Components/LifeCounter/LifeCounter.tsx b/src/Components/LifeCounter/LifeCounter.tsx index 8823233..9d8ab34 100644 --- a/src/Components/LifeCounter/LifeCounter.tsx +++ b/src/Components/LifeCounter/LifeCounter.tsx @@ -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 = ({ {player.isStartingPlayer && !showStartingPlayer && ( - - + + You start! - - + + + )} + + {player.hasLost && ( + )} + {(player.lifeTotal < 1 || hasCommanderDamageReached21(player)) && ( + + + + )} + {showPlayerMenu && ( - 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(); - const wakeLock = React.useRef(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, - }; -}; diff --git a/src/Icons/generated/Skull.tsx b/src/Icons/generated/Skull.tsx new file mode 100644 index 0000000..0dda217 --- /dev/null +++ b/src/Icons/generated/Skull.tsx @@ -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 & SVGRProps) => { + return ( + + {title ? {title} : null} + + + + + + ); +}; +Skull.propTypes = { + title: PropTypes.string, +}; +export default Skull; diff --git a/src/Icons/generated/index.ts b/src/Icons/generated/index.ts index 450a849..5e6cbe0 100644 --- a/src/Icons/generated/index.ts +++ b/src/Icons/generated/index.ts @@ -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'; diff --git a/src/Icons/svgs/Skull.svg b/src/Icons/svgs/Skull.svg new file mode 100644 index 0000000..bfb68ec --- /dev/null +++ b/src/Icons/svgs/Skull.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/src/Types/Player.ts b/src/Types/Player.ts index a1f044a..4b5cb56 100644 --- a/src/Types/Player.ts +++ b/src/Types/Player.ts @@ -7,6 +7,7 @@ export type Player = { commanderDamage: CommanderDamage[]; extraCounters: ExtraCounter[]; isStartingPlayer: boolean; + hasLost: boolean; }; export type PlayerSettings = { diff --git a/yarn.lock b/yarn.lock index 41c9254..b76696b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"