forked from external-repos/LifeTrinket
Compare commits
6 Commits
monarch
...
feature/ti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4130e069bb | ||
|
|
e7e5882859 | ||
|
|
c6039c2a53 | ||
|
|
6d6da2ad79 | ||
|
|
51acebb50e | ||
|
|
35c1cac691 |
51
package.json
51
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "life-trinket",
|
||||
"private": true,
|
||||
"version": "0.9.99",
|
||||
"version": "1.0.1",
|
||||
"type": "commonjs",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
@@ -17,39 +17,42 @@
|
||||
"deploy": "bun run build && firebase deploy --only hosting"
|
||||
},
|
||||
"dependencies": {
|
||||
"firebase": "^10.3.0",
|
||||
"@mui/icons-material": "^6.4.5",
|
||||
"@mui/material": "^6.4.5",
|
||||
"firebase": "^10.14.1",
|
||||
"ga-4-react": "^0.1.281",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-screen-wake-lock": "^3.0.2",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"react-twc": "^1.3.0",
|
||||
"semver": "^7.6.2",
|
||||
"zod": "^3.22.4"
|
||||
"react-swipeable": "^7.0.2",
|
||||
"react-timer-hook": "^3.0.8",
|
||||
"react-twc": "^1.4.2",
|
||||
"semver": "^7.7.1",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@savvywombat/tailwindcss-grid-areas": "^4.0.0",
|
||||
"@svgr/cli": "^8.1.0",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitejs/plugin-react-swc": "^3.8.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"firebase-tools": "^13.7.5",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"firebase-tools": "^13.31.2",
|
||||
"install": "^0.13.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "2.8.8",
|
||||
"prop-types": "^15.8.1",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.10",
|
||||
"vite-plugin-pwa": "^0.20.0"
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^5.4.14",
|
||||
"vite-plugin-pwa": "^0.20.5"
|
||||
}
|
||||
}
|
||||
|
||||
7619
pnpm-lock.yaml
generated
7619
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ const CommanderDamageButton = twc.button<RotationButtonProps>((props) => [
|
||||
]);
|
||||
|
||||
const CommanderDamageTextContainer = twc.div<RotationDivProps>((props) => [
|
||||
'relative top-1/2 left-1/2 tabular-nums pointer-events-none select-none webkit-user-select-none',
|
||||
'relative -translate-y-1/2 top-1/2 left-1/2 tabular-nums pointer-events-none select-none webkit-user-select-none',
|
||||
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
|
||||
? 'rotate-[270deg]'
|
||||
: '',
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CommanderTax,
|
||||
Energy,
|
||||
Experience,
|
||||
Monarch,
|
||||
PartnerTax,
|
||||
Poison,
|
||||
} from '../../Icons/generated';
|
||||
@@ -108,6 +109,9 @@ export const InfoDialog = ({
|
||||
<li className="flex items-center">
|
||||
<Experience className="size-6" /> - Experience
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<Monarch className="size-6" /> - Monarch
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-lg font-bold mb-2">Other functionality</h3>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { twc } from 'react-twc';
|
||||
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
||||
import LifeCounterButton from '../Buttons/LifeCounterButton';
|
||||
import { MonarchCrown } from '../Misc/MonarchCrown';
|
||||
import { OutlinedText } from '../Misc/OutlinedText';
|
||||
|
||||
const LifeContainer = twc.div<RotationDivProps>((props) => [
|
||||
@@ -33,7 +35,7 @@ const RecentDifference = twc.div`
|
||||
absolute min-w-[20vmin] drop-shadow-none text-center bg-interface-recentDifference-background tabular-nums rounded-full p-[6px 12px] text-[8vmin] text-interface-recentDifference-text animate-fadeOut
|
||||
|
||||
top-1/4 left-[50%] -translate-x-1/2
|
||||
data-[isSide=true]:top-1/3 data-[isSide=true]:translate-x-1/4 data-[isSide=true]:translate-y-1/2 data-[isSide=true]:rotate-[270deg] data-[isSide=true]:left-auto
|
||||
data-[is-side=true]:top-1/3 data-[is-side=true]:translate-x-1/4 data-[is-side=true]:translate-y-1/2 data-[is-side=true]:rotate-[270deg] data-[is-side=true]:left-auto
|
||||
`;
|
||||
|
||||
type HealthProps = {
|
||||
@@ -53,6 +55,8 @@ const Health = ({
|
||||
const [fontSize, setFontSize] = useState(16);
|
||||
const textContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { settings } = useGlobalSettings();
|
||||
|
||||
useEffect(() => {
|
||||
if (!textContainerRef.current) {
|
||||
return;
|
||||
@@ -104,6 +108,8 @@ const Health = ({
|
||||
|
||||
return (
|
||||
<LifeContainer $rotation={player.settings.rotation}>
|
||||
{settings.useMonarch && <MonarchCrown player={player} />}
|
||||
|
||||
<LifeCounterButton
|
||||
player={player}
|
||||
setLifeTotal={handleLifeChange}
|
||||
@@ -111,29 +117,32 @@ const Health = ({
|
||||
increment={-1}
|
||||
/>
|
||||
|
||||
{player.name && isSide ? (
|
||||
<div className="size-full relative flex items-center justify-start">
|
||||
<div className="fixed flex justify-center -rotate-90 left-[5.4vmax] ">
|
||||
<div
|
||||
data-is-side={isSide}
|
||||
className="size-full absolute flex items-start justify-center pointer-events-none webkit-user-select-none
|
||||
data-[is-side=true]:items-center data-[is-side=true]:justify-start
|
||||
"
|
||||
>
|
||||
{player.name && isSide ? (
|
||||
<div className="fixed flex justify-center -rotate-90 left-[5.4vmax]">
|
||||
<div
|
||||
data-contrast={player.iconTheme}
|
||||
className="absolute text-[4vmin] opacity-50 font-bold
|
||||
className="absolute text-[4vmin] opacity-50 font-bold text-center text-nowrap
|
||||
data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
>
|
||||
{player.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-full relative flex items-start justify-center">
|
||||
) : (
|
||||
<div
|
||||
data-contrast={player.iconTheme}
|
||||
className="absolute text-[4vmin] -top-[1.1vmin] opacity-50 font-bold
|
||||
className="absolute text-[4vmin] -top-[1.1vmin] opacity-50 font-bold text-center
|
||||
data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
>
|
||||
{player.name}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TextWrapper>
|
||||
<LifeCounterTextContainer
|
||||
@@ -148,7 +157,7 @@ const Health = ({
|
||||
{player.lifeTotal}
|
||||
</OutlinedText>
|
||||
{recentDifference !== 0 && (
|
||||
<RecentDifference data-isSide={isSide} key={differenceKey}>
|
||||
<RecentDifference data-is-side={isSide} key={differenceKey}>
|
||||
{recentDifference > 0 ? '+' : ''}
|
||||
{recentDifference}
|
||||
</RecentDifference>
|
||||
|
||||
@@ -238,6 +238,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
||||
recentDifference={recentDifference}
|
||||
handleLifeChange={handleLifeChange}
|
||||
/>
|
||||
|
||||
<ExtraCountersBar player={player} />
|
||||
<PlayerMenu
|
||||
isShown={showPlayerMenu}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const IconCheckbox = ({
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={className} >
|
||||
<label>
|
||||
<input
|
||||
name={name}
|
||||
|
||||
67
src/Components/Misc/MonarchCrown.tsx
Normal file
67
src/Components/Misc/MonarchCrown.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { usePlayers } from '../../Hooks/usePlayers';
|
||||
import { Monarch } from '../../Icons/generated';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import { IconCheckbox } from './IconCheckbox';
|
||||
|
||||
export const MonarchCrown = ({ player }: { player: Player }) => {
|
||||
const { players, setPlayers } = usePlayers();
|
||||
|
||||
const iconSize =
|
||||
player.settings.rotation === Rotation.SideFlipped ||
|
||||
player.settings.rotation === Rotation.Side
|
||||
? '5vmax'
|
||||
: '10vmin';
|
||||
|
||||
const rotationIsSide =
|
||||
player.settings.rotation === Rotation.SideFlipped ||
|
||||
player.settings.rotation === Rotation.Side;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-rotation-is-side={rotationIsSide}
|
||||
className="absolute w-full h-full flex items-start justify-center pointer-events-none z-[1]
|
||||
data-[rotation-is-side=true]:justify-start data-[rotation-is-side=true]:items-center
|
||||
"
|
||||
>
|
||||
<div
|
||||
data-rotation-is-side={rotationIsSide}
|
||||
className="data-[rotation-is-side=true]:-rotate-90"
|
||||
>
|
||||
<IconCheckbox
|
||||
className="pointer-events-all"
|
||||
name="useMonarch"
|
||||
checked={player.isMonarch}
|
||||
icon={<Monarch size={iconSize} color={player.color} stroke="white" />}
|
||||
checkedIcon={
|
||||
<div>
|
||||
<Monarch
|
||||
size={iconSize}
|
||||
stroke="white"
|
||||
className="absolute blur z-[-1] text-icons-gold"
|
||||
/>
|
||||
<Monarch
|
||||
size={iconSize}
|
||||
stroke="white"
|
||||
className="text-icons-gold"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onChange={(e) => {
|
||||
const updatedPlayer = { ...player, isMonarch: e.target.checked };
|
||||
|
||||
const updatedPlayers = players.map((p) => {
|
||||
if (p.index === player.index) {
|
||||
return updatedPlayer;
|
||||
}
|
||||
return { ...p, isMonarch: false };
|
||||
});
|
||||
|
||||
setPlayers(updatedPlayers);
|
||||
}}
|
||||
aria-checked={player.isMonarch}
|
||||
aria-label="Monarch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,5 +2,4 @@ import { twc } from 'react-twc';
|
||||
|
||||
export const Paragraph = twc.p`text-text-primary`;
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const H1 = twc.h1`text-text-primary;`;
|
||||
|
||||
37
src/Components/Misc/Timer/NegativeStopWatch.tsx
Normal file
37
src/Components/Misc/Timer/NegativeStopWatch.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useStopwatch } from 'react-timer-hook';
|
||||
import { TimerStyle, TimerWrapper } from './Timer';
|
||||
import { Time } from './Time';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import PauseIcon from '@mui/icons-material/Pause';
|
||||
|
||||
export const NegativeStopWatch = ({
|
||||
autoStart = false,
|
||||
}: {
|
||||
autoStart?: boolean;
|
||||
}) => {
|
||||
// Initialize the timer using the useTimer hook.
|
||||
const { seconds, minutes, hours, isRunning, start, pause } = useStopwatch({
|
||||
autoStart,
|
||||
});
|
||||
|
||||
return (
|
||||
<TimerWrapper>
|
||||
<TimerStyle>
|
||||
{'-'}
|
||||
<Time time={{ hours, minutes, seconds }} />
|
||||
<div>
|
||||
{!isRunning && (
|
||||
<button onClick={start}>
|
||||
<PlayArrowIcon />
|
||||
</button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<button onClick={pause}>
|
||||
<PauseIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</TimerStyle>
|
||||
</TimerWrapper>
|
||||
);
|
||||
};
|
||||
9
src/Components/Misc/Timer/Time.tsx
Normal file
9
src/Components/Misc/Timer/Time.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { formatTime } from '../../../Utils/formatTime';
|
||||
|
||||
export const Time = ({
|
||||
time,
|
||||
}: {
|
||||
time: { hours: number; minutes: number; seconds: number };
|
||||
}) => {
|
||||
return <div className="tabular-nums">{formatTime({ time })}</div>;
|
||||
};
|
||||
84
src/Components/Misc/Timer/Timer.tsx
Normal file
84
src/Components/Misc/Timer/Timer.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useState } from 'react';
|
||||
import { useTimer } from 'react-timer-hook';
|
||||
import { NegativeStopWatch } from './NegativeStopWatch';
|
||||
import { twc } from 'react-twc';
|
||||
import PauseIcon from '@mui/icons-material/Pause';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import { Time } from './Time';
|
||||
|
||||
export const TimerWrapper = twc.div`
|
||||
absolute z-50 top-0 w-screen flex items-center flex-col
|
||||
`;
|
||||
|
||||
export const TimerStyle = twc.div`
|
||||
flex gap-2 bg-interface-recentDifference-background py-1 px-4 rounded-full
|
||||
`;
|
||||
|
||||
const TIMER_MINUTES = 50;
|
||||
|
||||
export const Timer = () => {
|
||||
// Set the default expiry time to 50 minutes from now.
|
||||
const defaultExpiry = new Date();
|
||||
defaultExpiry.setMinutes(defaultExpiry.getMinutes() + TIMER_MINUTES);
|
||||
|
||||
const [hasStarted, setHasStarted] = useState(false);
|
||||
|
||||
// Initialize the timer using the useTimer hook.
|
||||
const { seconds, minutes, hours, isRunning, start, pause, restart } =
|
||||
useTimer({
|
||||
expiryTimestamp: defaultExpiry,
|
||||
onExpire: () => console.warn('Timer expired'),
|
||||
autoStart: false,
|
||||
});
|
||||
|
||||
// Function to restart the timer for 50 minutes.
|
||||
const handleRestart = () => {
|
||||
const time = new Date();
|
||||
time.setMinutes(time.getMinutes() + TIMER_MINUTES);
|
||||
restart(time);
|
||||
};
|
||||
|
||||
const handleStart = () => {
|
||||
setHasStarted(true);
|
||||
start();
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
setHasStarted(false);
|
||||
pause();
|
||||
};
|
||||
|
||||
if (hasStarted && !isRunning) {
|
||||
return <NegativeStopWatch autoStart />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TimerWrapper>
|
||||
<TimerStyle>
|
||||
<Time
|
||||
time={{
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
{!isRunning && (
|
||||
<button onClick={handleStart}>
|
||||
<PlayArrowIcon />
|
||||
</button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<button onClick={handlePause}>
|
||||
<PauseIcon />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={handleRestart}>
|
||||
<RestartAltIcon />
|
||||
</button>
|
||||
</div>
|
||||
</TimerStyle>
|
||||
</TimerWrapper>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Experience,
|
||||
FullscreenOff,
|
||||
FullscreenOn,
|
||||
Monarch,
|
||||
NameTag,
|
||||
PartnerTax,
|
||||
Poison,
|
||||
@@ -110,6 +111,7 @@ const PlayerMenu = ({
|
||||
wakeLock,
|
||||
goToStart,
|
||||
settings,
|
||||
setSettings,
|
||||
setPlaying,
|
||||
setRandomizingPlayer,
|
||||
saveCurrentGame,
|
||||
@@ -360,6 +362,36 @@ const PlayerMenu = ({
|
||||
aria-label="Experience"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<IconCheckbox
|
||||
name="useMonarch"
|
||||
checked={settings.useMonarch}
|
||||
icon={
|
||||
<Monarch
|
||||
size={extraCountersSize}
|
||||
color="black"
|
||||
stroke="white"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
}
|
||||
checkedIcon={
|
||||
<Monarch
|
||||
size={extraCountersSize}
|
||||
color={player.color}
|
||||
stroke="white"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
}
|
||||
onChange={(e) => {
|
||||
analytics.trackEvent('toggle_monarch', {
|
||||
checked: e.target.checked,
|
||||
});
|
||||
setSettings({ ...settings, useMonarch: e.target.checked });
|
||||
}}
|
||||
aria-checked={settings.useMonarch}
|
||||
aria-label="Monarch"
|
||||
/>
|
||||
</div>
|
||||
</TogglesSection>
|
||||
<ButtonsSections>
|
||||
<button
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PreStartMode } from '../../Types/Settings';
|
||||
import LifeCounter from '../LifeCounter/LifeCounter';
|
||||
import { RoulettePlayerCard } from '../PreStartGame/Games/RandomKing/RoulettePlayerCard';
|
||||
import { GridLayout } from '../Views/Play';
|
||||
import { Timer } from '../Misc/Timer/Timer';
|
||||
|
||||
const getGridArea = (player: PlayerType) => {
|
||||
switch (player.index) {
|
||||
@@ -36,6 +37,7 @@ export const Players = ({ gridLayout }: { gridLayout: GridLayout }) => {
|
||||
return (
|
||||
<PlayersWrapper>
|
||||
<div className={`grid w-full h-full gap-1 box-border ${gridLayout} `}>
|
||||
<Timer />
|
||||
{players.map((player) => {
|
||||
const gridArea = getGridArea(player);
|
||||
return (
|
||||
|
||||
@@ -19,6 +19,7 @@ export const Play = () => {
|
||||
useGlobalSettings();
|
||||
|
||||
let gridLayout: GridLayout;
|
||||
|
||||
switch (players.length) {
|
||||
case 1:
|
||||
if (initialGameSettings?.orientation === Orientation.Portrait) {
|
||||
@@ -101,7 +102,6 @@ export const Play = () => {
|
||||
settings.preStartMode !== PreStartMode.None &&
|
||||
!playing &&
|
||||
settings.showStartingPlayer && <PreStart />}
|
||||
|
||||
<Players gridLayout={gridLayout} />
|
||||
</MainWrapper>
|
||||
);
|
||||
|
||||
@@ -231,6 +231,7 @@ export const createInitialPlayers = ({
|
||||
isStartingPlayer: false,
|
||||
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
|
||||
name: '',
|
||||
isMonarch: false,
|
||||
};
|
||||
|
||||
players.push(player);
|
||||
|
||||
34
src/Icons/generated/Monarch.tsx
Normal file
34
src/Icons/generated/Monarch.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { SVGProps } from 'react';
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
size?: string;
|
||||
}
|
||||
const Monarch = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={props.size || 16}
|
||||
height={props.size || 16}
|
||||
fill="none"
|
||||
viewBox="0 0 52 52"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M46.163 38.82s-8.614 2.73-14.234 3.106c-2.508.167-3.918 0-6.429 0-2.51 0-3.921.167-6.429 0-5.62-.376-14.234-3.107-14.234-3.107s.637-3.944.459-6.471C5.053 28.888 3 24.038 3 24.038s2.897 2.25 4.592 1.294C9.78 24.098 10.5 20 10.5 20s3.006 6.024 7 5.332c2.386-.414 3.327-1.974 4.5-4.016.97-1.69 1.27-4.827 1.27-4.827l1.77-4.827L25.5 10l.46 1.662 1.77 4.827s.3 3.136 1.27 4.827c1.173 2.042 2.388 3.353 4.5 4.016 4.051 1.273 7-5.332 7-5.332s.72 4.098 2.908 5.332c1.695.956 4.592-1.294 4.592-1.294s-2.053 4.85-2.296 8.31c-.178 2.527.46 6.471.46 6.471"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
Monarch.propTypes = {
|
||||
title: PropTypes.string,
|
||||
};
|
||||
export default Monarch;
|
||||
@@ -10,6 +10,7 @@ export { default as FullscreenOn } from './FullscreenOn';
|
||||
export { default as Info } from './Info';
|
||||
export { default as LittleGuy } from './LittleGuy';
|
||||
export { default as Logo } from './Logo';
|
||||
export { default as Monarch } from './Monarch';
|
||||
export { default as NameTag } from './NameTag';
|
||||
export { default as PartnerTax } from './PartnerTax';
|
||||
export { default as Poison } from './Poison';
|
||||
|
||||
3
src/Icons/svgs/Monarch.svg
Normal file
3
src/Icons/svgs/Monarch.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M46.1633 38.8191C46.1633 38.8191 37.5494 41.5504 31.9286 41.9256C29.421 42.093 28.0105 41.9256 25.5 41.9256C22.9895 41.9256 21.579 42.093 19.0714 41.9256C13.4506 41.5504 4.83673 38.8191 4.83673 38.8191C4.83673 38.8191 5.47353 34.8751 5.29592 32.3476C5.05284 28.8883 3 24.0377 3 24.0377C3 24.0377 5.89664 26.2882 7.59184 25.332C9.77975 24.0978 10.5 20 10.5 20C10.5 20 13.5058 26.0243 17.5 25.332C19.886 24.9184 20.8269 23.3583 22 21.3158C22.9708 19.6255 23.2704 16.4887 23.2704 16.4887L25.0408 11.6616L25.5 10L25.9592 11.6616L27.7296 16.4887C27.7296 16.4887 28.0292 19.6255 29 21.3158C30.1731 23.3583 31.3881 24.6686 33.5 25.332C37.5515 26.6047 40.5 20 40.5 20C40.5 20 41.2203 24.0978 43.4082 25.332C45.1034 26.2882 48 24.0377 48 24.0377C48 24.0377 45.9472 28.8883 45.7041 32.3476C45.5265 34.8751 46.1633 38.8191 46.1633 38.8191Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 964 B |
@@ -94,18 +94,6 @@ export const GlobalSettingsProvider = ({
|
||||
);
|
||||
};
|
||||
|
||||
const removeLocalStorage = async () => {
|
||||
localStorage.removeItem('initialGameSettings');
|
||||
localStorage.removeItem('players');
|
||||
localStorage.removeItem('playing');
|
||||
localStorage.removeItem('showPlay');
|
||||
localStorage.removeItem('preStartComplete');
|
||||
|
||||
setPlaying(false);
|
||||
setShowPlay(false);
|
||||
setPreStartCompleted(false);
|
||||
};
|
||||
|
||||
// Set settings if they are not valid
|
||||
useEffect(() => {
|
||||
// If there are no saved settings, set default settings
|
||||
@@ -177,6 +165,19 @@ export const GlobalSettingsProvider = ({
|
||||
}
|
||||
|
||||
const ctxValue = useMemo((): GlobalSettingsContextType => {
|
||||
const removeLocalStorage = async () => {
|
||||
localStorage.removeItem('initialGameSettings');
|
||||
localStorage.removeItem('players');
|
||||
localStorage.removeItem('playing');
|
||||
localStorage.removeItem('showPlay');
|
||||
localStorage.removeItem('preStartComplete');
|
||||
|
||||
setPlaying(false);
|
||||
setShowPlay(false);
|
||||
setPreStartCompleted(false);
|
||||
setSettings({ ...settings, useMonarch: false });
|
||||
};
|
||||
|
||||
const goToStart = async () => {
|
||||
const currentPlayers = localStorage.getItem('players');
|
||||
|
||||
@@ -292,7 +293,6 @@ export const GlobalSettingsProvider = ({
|
||||
setPreStartCompleted: setPreStartCompletedAndLocalStorage,
|
||||
savedGame,
|
||||
saveCurrentGame: setCurrentGameAndLocalStorage,
|
||||
|
||||
version: {
|
||||
installedVersion: import.meta.env.VITE_APP_VERSION,
|
||||
remoteVersion,
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Player = {
|
||||
commanderDamage: CommanderDamage[];
|
||||
extraCounters: ExtraCounter[];
|
||||
isStartingPlayer: boolean;
|
||||
isMonarch: boolean;
|
||||
hasLost: boolean;
|
||||
isSide: boolean;
|
||||
name: string;
|
||||
|
||||
@@ -26,6 +26,7 @@ export type Settings = {
|
||||
goFullscreenOnStart: boolean;
|
||||
preStartMode: PreStartMode;
|
||||
showAnimations: boolean;
|
||||
useMonarch: boolean;
|
||||
};
|
||||
|
||||
export type InitialGameSettings = {
|
||||
@@ -59,6 +60,7 @@ export const settingsSchema = z.object({
|
||||
goFullscreenOnStart: z.boolean(),
|
||||
preStartMode: z.nativeEnum(PreStartMode),
|
||||
showAnimations: z.boolean(),
|
||||
useMonarch: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const defaultSettings: Settings = {
|
||||
@@ -68,4 +70,5 @@ export const defaultSettings: Settings = {
|
||||
showPlayerMenuCog: true,
|
||||
preStartMode: PreStartMode.None,
|
||||
showAnimations: true,
|
||||
useMonarch: false,
|
||||
};
|
||||
|
||||
17
src/Utils/formatTime.ts
Normal file
17
src/Utils/formatTime.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const formatTime = ({
|
||||
time,
|
||||
}: {
|
||||
time: { hours: number; minutes: number; seconds: number };
|
||||
}) => {
|
||||
const h = time.hours.toString().padStart(2, '0');
|
||||
const m = time.minutes.toString().padStart(2, '0');
|
||||
const s = time.seconds.toString().padStart(2, '0');
|
||||
|
||||
if (time.hours > 0) {
|
||||
return `${h}:${m}:${s}`;
|
||||
} else if (time.minutes > 0) {
|
||||
return `${m}:${s}`;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
@@ -20,6 +20,7 @@ export const baseColors = {
|
||||
icons: {
|
||||
dark: '#000000',
|
||||
light: '#F9FFE3',
|
||||
gold: '#FFD700',
|
||||
},
|
||||
text: {
|
||||
primary: '#F9FFE3',
|
||||
|
||||
Reference in New Issue
Block a user