Compare commits

...

29 Commits
0.5.3 ... 0.6.1

Author SHA1 Message Date
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
Viktor Rådberg
354c0dbbb2 bump 2024-01-20 11:11:03 +01:00
Viktor Rådberg
3770d13beb fix some styling 2024-01-20 10:56:53 +01:00
Viktor Rådberg
13733242a2 bump 2024-01-14 14:39:20 +01:00
Viktor Rådberg
81f3891b20 add better pwa support 2024-01-14 14:38:56 +01:00
Viktor Rådberg
e153de9093 Release 0.5.51 2024-01-14 13:42:11 +01:00
Viktor Rådberg
07775f85d2 fix start menu style 2024-01-14 13:41:51 +01:00
Viktor Rådberg
10039175a1 bump 2024-01-14 13:14:53 +01:00
Viktor Rådberg
bcf2a0a840 new colors 2024-01-14 13:14:33 +01:00
Viktor Rådberg
d25da5d97b fix styling 2024-01-14 12:31:57 +01:00
Viktor Rådberg
f5a80e573e cache 2024-01-14 10:41:14 +01:00
Viktor Rådberg
1f36264e39 update package json 2024-01-14 10:40:48 +01:00
Viktor Rådberg
d615cfd3ba reset game styling 2024-01-14 10:38:28 +01:00
Viktor Rådberg
4453b12ce6 bump 2024-01-13 20:32:10 +01:00
Viktor Rådberg
d601a820f8 remove log 2024-01-13 20:32:00 +01:00
Viktor Rådberg
0455f43794 test 2024-01-13 20:26:07 +01:00
Viktor Rådberg
f94103fe51 fix 2024-01-13 20:20:50 +01:00
Viktor Rådberg
c36668b933 fix 2024-01-13 20:20:20 +01:00
Viktor Rådberg
f9d0346300 bump 2024-01-13 20:18:50 +01:00
Viktor Rådberg
2f3ee74c74 test 2024-01-13 20:15:43 +01:00
Viktor Rådberg
f8f0788b97 bump 2024-01-13 19:53:02 +01:00
Viktor Rådberg
bfe25eacb7 fix lint 2024-01-13 19:44:27 +01:00
Viktor Rådberg
7b0965c0dd test release 2024-01-13 19:40:36 +01:00
Viktor Rådberg
e55ea6a83a final test 2024-01-13 19:32:16 +01:00
22 changed files with 2616 additions and 592 deletions

View File

@@ -1,8 +1,8 @@
robots.txt,1693082171694,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
manifest.json,1693082171694,91ce94afb71f33a477f5d8d48c3f98bd7de422279c74f17b6500eec72003ac1a
assets/index-5265c558.css,1693082171837,08c4451946bbdf520fe337edb365417a8bbf91914c018b83866723ef52d57b43
index.html,1693082171837,09e1919fbaaa3a0bf08f43eb46c29136d62a7747b41f8b5d0f4a7ed23337c344
logo192.png,1693082171693,4309255bccbdbb341b5ab88708677e3d43b9e171d2666528ff932295a8257e4e
favicon.ico,1693082171692,48d8c1b9714dbc9bcb012d9c9f04112d229f20e6c889bda588ac159f973e6a8d
logo512.png,1693082171694,92c7c05dc98170596d04f48e5e60eaae9535f409bcaeff129fd98fef8aba9f4e
assets/index-5023e89e.js,1693082171838,8a6177168e95e1ca90e5ad8774252a8a02a9a78765bd329b7deae729c01aedf3
index.html,1705225256081,6ef0d7e2de82bf64addbb9294fb28845fd06daaa544b010a47422c12ae3ad97f
robots.txt,1705225255906,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
manifest.json,1705225255906,91ce94afb71f33a477f5d8d48c3f98bd7de422279c74f17b6500eec72003ac1a
assets/index-08359bdb.css,1705225256081,d2766260d28230d960d75362810713efaddf40687205e697432b52869f162af7
logo192.png,1705225255905,3b0fcf91fe2128f493de0bce2f6e2d35520a4260a04e05b8d855181359b3d3fe
favicon.ico,1705225255905,75661e6187b524767554b4f28ec09a64bc72b0bb102a0b453aaead88519d9ed3
logo512.png,1705225255906,cf49739c9e6890bbfcd4157f299dde425df60759b7320ae9188d7ab9dc51e8ca
assets/index-20658f4b.js,1705225256081,742f2c10740beea3a23f269aa6266b3c288d1fd9c7e20b6829034e8a898bf1e1

View File

@@ -4,32 +4,32 @@ name: Deploy to Firebase Hosting
tags:
- '*'
jobs:
# build_and_deploy:
# runs-on: ubuntu-latest
# env:
# REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }}
# steps:
# - name: Checkout repository
# uses: actions/checkout@v3
build_and_deploy:
runs-on: ubuntu-latest
env:
REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
# - name: Setup bun
# uses: oven-sh/setup-bun@v1
- name: Setup bun
uses: oven-sh/setup-bun@v1
# - name: Build, lint, and deploy
# run: |
# bun install
# bun run build
# bun run lint
# - name: Deploy to Firebase Hosting
# uses: FirebaseExtended/action-hosting-deploy@v0
# with:
# repoToken: '${{ secrets.GITHUB_TOKEN }}'
# firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_LIFE_TRINKET }}'
# channelId: live
# projectId: life-trinket
- name: Build, lint, and deploy
run: |
bun install
bun run build
bun run lint
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_LIFE_TRINKET }}'
channelId: live
projectId: life-trinket
release:
# needs: build_and_deploy
needs: build_and_deploy
runs-on: ubuntu-latest
env:
working-directory: ${{ github.workspace }}
@@ -52,7 +52,7 @@ jobs:
- name: Create Release
uses: ncipollo/release-action@v1.13.0
with:
body: release_note.txt
bodyFile: release_note.txt
commit: ${{ github.sha }}
tag: '${{ steps.version.outputs.prop }}'
token: ${{ secrets.RELEASE_TOKEN }}

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{
"name": "life-trinket",
"private": true,
"version": "0.5.3",
"version": "0.6.1",
"type": "commonjs",
"engines": {
"node": ">=18",
@@ -13,7 +13,7 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"generate-icons": "npx @svgr/cli src/Icons/svgs",
"deploy": "bun build && firebase deploy --only hosting"
"deploy": "bun run build && firebase deploy --only hosting"
},
"dependencies": {
"@mui/material": "^5.13.6",
@@ -22,6 +22,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-screen-wake-lock": "^3.0.2",
"react-swipeable": "^7.0.1",
"react-twc": "^1.3.0",
"zod": "^3.22.4"
},
@@ -43,8 +44,9 @@
"install": "^0.13.0",
"postcss": "^8.4.32",
"prettier": "2.8.8",
"tailwindcss": "^3.4.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.0.12",
"vite-plugin-pwa": "^0.17.4"
}
}

View File

@@ -66,10 +66,6 @@ export const CommanderDamage = ({
const [timeoutFinished, setTimeoutFinished] = useState(false);
const [hasPressedDown, setHasPressedDown] = useState(false);
const isSide =
player.settings.rotation === Rotation.Side ||
player.settings.rotation === Rotation.SideFlipped;
const handleCommanderDamageChange = (
index: number,
increment: number,
@@ -132,9 +128,9 @@ export const CommanderDamage = ({
};
const opponentIndex = opponent.index;
const fontSize = isSide ? '4vmax' : '7vmin';
const fontSize = player.isSide ? '4vmax' : '7vmin';
const fontWeight = 'bold';
const strokeWidth = isSide ? '0.4vmax' : '0.7vmin';
const strokeWidth = player.isSide ? '0.4vmax' : '0.7vmin';
return (
<CommanderDamageContainer

View File

@@ -47,6 +47,7 @@ type ExtraCounterProps = {
type: CounterType;
setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void;
rotation: number;
isSide: boolean;
playerIndex: number;
};
@@ -56,15 +57,13 @@ const ExtraCounter = ({
setCounterTotal,
type,
rotation,
isSide,
playerIndex,
}: ExtraCounterProps) => {
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [timeoutFinished, setTimeoutFinished] = useState(false);
const [hasPressedDown, setHasPressedDown] = useState(false);
const isSide =
rotation === Rotation.Side || rotation === Rotation.SideFlipped;
const handleCountChange = (increment: number) => {
if (!counterTotal) {
setCounterTotal(increment, type);

View File

@@ -4,13 +4,11 @@ import { Rotation } from '../../Types/Player';
import { RotationDivProps } from './CommanderDamage';
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
? `right-auto top-[15%] left-[27%]`
: props.$rotation === Rotation.Side
? `right-auto top-[15%] left-[27%]`
: 'right-[15%] top-1/4',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? `left-[19%]`
: 'top-[21%]',
]);
type LoseButtonProps = {
@@ -24,6 +22,8 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
? rotation
: rotation === Rotation.Side
? rotation - 180
: rotation === Rotation.Flipped
? rotation - 180
: rotation;
return (
@@ -33,7 +33,7 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
aria-label={`Lose Game`}
style={{ rotate: `${calcRotation}deg` }}
>
<Skull size="5vmin" color="black" opacity={0.5} />
<Skull size="8vmin" color="black" opacity={0.5} />
</LoseButton>
);
};

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react';
import { useSwipeable } from 'react-swipeable';
import { twc } from 'react-twc';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers';
import { Player, Rotation } from '../../Types/Player';
import { RotationDivProps } from '../Buttons/CommanderDamage';
import { LoseGameButton } from '../Buttons/LoseButton';
import SettingsButton from '../Buttons/SettingsButton';
import CommanderDamageBar from '../Counters/CommanderDamageBar';
import ExtraCountersBar from '../Counters/ExtraCountersBar';
import PlayerMenu from '../Player/PlayerMenu';
@@ -24,13 +24,13 @@ 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 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
? `rotate-[${props.$rotation - 90}deg]`
: '',
]);
const DynamicText = twc.div`text-[8vmin]`;
const DynamicText = twc.div`text-[8vmin] whitespace-nowrap`;
const hasCommanderDamageReached21 = (player: Player) => {
const commanderDamageTotals = player.commanderDamage.map(
@@ -71,14 +71,51 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
const [showPlayerMenu, setShowPlayerMenu] = useState(false);
const [recentDifference, setRecentDifference] = useState(0);
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: () => {
console.log(`User DOWN Swiped on player ${player.index}`);
setShowPlayerMenu(true);
},
onSwipedUp: () => {
console.log(`User UP Swiped on player ${player.index}`);
setShowPlayerMenu(false);
},
swipeDuration: 500,
onSwiping: (eventData) => console.log(eventData),
rotationAngle,
});
useEffect(() => {
const timer = setTimeout(() => {
setRecentDifference(0);
}, 3_000);
return () => clearTimeout(timer);
}, [recentDifference]);
const resizeObserver = new ResizeObserver(() => {
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(() => {
if (player.showStartingPlayer) {
@@ -124,6 +161,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
<LifeCounterWrapper
$rotation={player.settings.rotation}
style={{ rotate: `${calcRotation}deg` }}
{...handlers}
>
{settings.showStartingPlayer &&
player.isStartingPlayer &&
@@ -131,7 +169,11 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
<StartingPlayerNoticeWrapper
style={{ rotate: `${calcRotation}deg` }}
>
<DynamicText style={{ rotate: `${calcTextRotation}deg` }}>
<DynamicText
style={{
rotate: `${calcTextRotation}deg`,
}}
>
You start!
</DynamicText>
</StartingPlayerNoticeWrapper>
@@ -146,12 +188,6 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
key={player.index}
handleLifeChange={handleLifeChange}
/>
<SettingsButton
onClick={() => {
setShowPlayerMenu(!showPlayerMenu);
}}
rotation={player.settings.rotation}
/>
{playerCanLose(player) && (
<LoseGameButton
rotation={player.settings.rotation}
@@ -166,9 +202,12 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
handleLifeChange={handleLifeChange}
/>
<ExtraCountersBar player={player} />
{showPlayerMenu && (
<PlayerMenu player={player} setShowPlayerMenu={setShowPlayerMenu} />
)}
<PlayerMenu
isShown={showPlayerMenu}
player={player}
setShowPlayerMenu={setShowPlayerMenu}
/>
</LifeCounterWrapper>
</LifeCounterContentWrapper>
);

View File

@@ -1,8 +1,9 @@
import { Modal } from '@mui/material';
import { theme } from '../../Data/theme';
import { twc } from 'react-twc';
import { Separator } from './Separator';
import { Paragraph } from './TextComponents';
export const ModalWrapper = twc.div`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[80vw] h-[85vh] bg-background-default p-4 overflow-scroll rounded-2xl border-none text-text-primary`;
export const ModalWrapper = twc.div`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[85vh] bg-background-default p-4 overflow-scroll rounded-2xl border-none text-text-primary w-[95vw] max-w-[548px]`;
type InfoModalProps = {
isOpen: boolean;
@@ -11,29 +12,44 @@ type InfoModalProps = {
export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
return (
<Modal open={isOpen} onClose={closeModal}>
<Modal
open={isOpen}
onClose={closeModal}
style={{ display: 'flex', justifyContent: 'center' }}
>
<>
<div className="flex relative w-full max-w-[548px]">
<button
onClick={closeModal}
className="flex absolute top-10 right-0 z-10 w-10 h-10 text-common-white bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
>
X
</button>
</div>
<ModalWrapper>
<div>
<h2 style={{ textAlign: 'center' }}>📋 Usage Guide</h2>
<p>
<h2 className="text-2xl text-center mb-4">📋 Usage Guide</h2>
<Separator height="1px" />
<Paragraph className="my-4">
There are some controls that you might not know about, so here's a
short list of them.
</p>
<h3>Life counter</h3>
<ul>
</Paragraph>
<h3 className="text-lg font-bold mb-2">Life counter</h3>
<ul className="list-disc ml-6 mb-4">
<li>
<strong>Tap</strong> on a player's + or - button to add or
subtract <strong>1 life</strong>.
</li>
<li>
<strong>Long press</strong> on a player's + or - button to add or
subtract <strong>10 life</strong>.
<strong>Long press</strong> on a player's + or - button to add
or subtract <strong>10 life</strong>.
</li>
</ul>
<h3>Commander damage and other counters</h3>
<ul>
<h3 className="text-lg font-bold mb-2">
Commander damage and other counters
</h3>
<ul className="list-disc ml-6 mb-4">
<li>
<strong>Tap</strong> on the counter to add{' '}
<strong>1 counter</strong>.
@@ -44,33 +60,21 @@ export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
</li>
</ul>
<h3>Other</h3>
<p>
<h3 className="text-lg font-bold mb-2">Other</h3>
<Paragraph className="mb-4">
When a player is <strong>at or below 0 life</strong>, has taken{' '}
<strong>21 or more Commander Damage</strong> or has{' '}
<strong>10 or more poison counters</strong>, a button with a skull
will appear on that player's card.
</p>
<p>
Tap on the button to mark that player as lost, dimming their player
card.
</p>
will appear on that player's card. Tapping it will dim the
player's card.
</Paragraph>
</div>
<br />
<div
style={{
textAlign: 'center',
marginTop: '1rem',
}}
>
<div className="text-center mt-4">
Visit my
<a
href="https://github.com/Vikeo/LifeTrinket"
target="_blank"
style={{
textDecoration: 'none',
color: theme.palette.primary.light,
}}
className="text-text-secondary underline"
>
{' '}
GitHub{' '}
@@ -78,6 +82,7 @@ export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
for more info about this web app.
</div>
</ModalWrapper>
</>
</Modal>
);
};

View File

@@ -7,7 +7,7 @@ export const Separator = ({
}) => {
return (
<div
className={`bg-common-black bg-opacity-30 rounded-full mt-2 mb-2`}
className={`bg-common-white bg-opacity-30 rounded-full mt-2 mb-2`}
style={{ width, height }}
/>
);

View File

@@ -4,7 +4,7 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { ModalWrapper } from './InfoModal';
import { Separator } from './Separator';
import { Paragraph } from './TextComponents';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
const SettingContainer = twc.div`w-full flex flex-col`;
@@ -22,7 +22,7 @@ type SettingsModalProps = {
export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
const { settings, setSettings, isPWA } = useGlobalSettings();
const [isLatestVersion, setIsLatestVersion] = useState(false);
const newVersion = useRef<string | undefined>(undefined);
const [newVersion, setNewVersion] = useState<string | undefined>(undefined);
useEffect(() => {
if (!isOpen) {
@@ -44,17 +44,19 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
const data = await result.json();
if (!data.name) {
setNewVersion(undefined);
setIsLatestVersion(false);
newVersion.current = undefined;
return;
}
setNewVersion(data.name);
/* @ts-expect-error is defined in vite.config.ts*/
if (data.name === APP_VERSION) {
newVersion.current = data.name;
setIsLatestVersion(true);
return;
}
setIsLatestVersion(false);
} catch (error) {
console.error('error getting latest version string', error);
@@ -65,9 +67,19 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
return (
<Modal open={isOpen} onClose={closeModal}>
<>
<div className="flex relative w-full max-w-[548px]">
<button
onClick={closeModal}
className="flex absolute top-10 right-0 z-10 w-10 h-10 text-common-white bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
>
X
</button>
</div>
<ModalWrapper>
<Container>
<h2 style={{ textAlign: 'center' }}> Settings </h2>
<h2 className="text-center text-2xl mb-2"> Settings </h2>
<Separator height="1px" />
<SettingContainer>
<ToggleContainer>
<FormLabel>Show Start Player</FormLabel>
@@ -92,13 +104,16 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
<Switch
checked={settings.keepAwake}
onChange={() => {
setSettings({ ...settings, keepAwake: !settings.keepAwake });
setSettings({
...settings,
keepAwake: !settings.keepAwake,
});
}}
/>
</ToggleContainer>
<Description>
Will prevent device from going to sleep while this app is open if
this is enabled.
Will prevent device from going to sleep while this app is open
if this is enabled.
</Description>
</SettingContainer>
<SettingContainer>
@@ -132,8 +147,8 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
</Paragraph>
</ToggleContainer>
<Description className="mt-1">
If you do, this app will work offline and the toolbar will be
automatically hidden.
If you do, this app will work offline and the toolbar will
be automatically hidden.
</Description>
</SettingContainer>
</>
@@ -147,13 +162,13 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
<span className="text-sm text-text-secondary">(latest)</span>
)}
</Paragraph>
{!isLatestVersion && newVersion.current && (
{!isLatestVersion && newVersion && (
<Paragraph className="text-text-secondary text-lg text-center">
New version ({newVersion.current}) is available!{' '}
New version ({newVersion}) is available!{' '}
</Paragraph>
)}
</SettingContainer>
{!isLatestVersion && newVersion.current && (
{!isLatestVersion && newVersion && (
<Button
variant="contained"
style={{ marginTop: '0.25rem', marginBottom: '0.25rem' }}
@@ -174,6 +189,7 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
</Button>
</Container>
</ModalWrapper>
</>
</Modal>
);
};

View File

@@ -6,7 +6,6 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers';
import { useSafeRotate } from '../../Hooks/useSafeRotate';
import {
Cross,
Energy,
Exit,
Experience,
@@ -17,10 +16,7 @@ import {
ResetGame,
} from '../../Icons/generated';
import { Player, Rotation } from '../../Types/Player';
import {
RotationButtonProps,
RotationDivProps,
} from '../Buttons/CommanderDamage';
import { RotationDivProps } from '../Buttons/CommanderDamage';
const CheckboxContainer = twc.div``;
@@ -35,6 +31,7 @@ const PlayerMenuWrapper = twc.div`
justify-center
z-[2]
webkit-user-select-none
transition-all
`;
const BetterRowContainer = twc.div`
@@ -85,21 +82,17 @@ const SettingsContainer = twc.div<RotationDivProps>((props) => [
: '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 = {
player: Player;
setShowPlayerMenu: (showPlayerMenu: boolean) => void;
isShown: boolean;
};
const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
const PlayerMenu = ({
player,
setShowPlayerMenu,
isShown,
}: PlayerMenuProps) => {
const settingsContainerRef = useRef<HTMLDivElement | null>(null);
const dialogRef = useRef<HTMLDialogElement | null>(null);
@@ -108,9 +101,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
containerRef: settingsContainerRef,
});
const handleOnClick = () => {
setShowPlayerMenu(false);
};
const { fullscreen, wakeLock, goToStart } = useGlobalSettings();
const { updatePlayer, resetCurrentGame } = usePlayers();
@@ -142,7 +132,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
const buttonFontSize = isSide ? '1.5vmax' : '3vmin';
const iconSize = isSide ? '6vmin' : '3vmax';
const extraCountersSize = isSide ? '8vmin' : '4vmax';
const closeButtonSize = isSide ? '6vmin' : '3vmax';
const calcRotation =
player.settings.rotation === Rotation.Side
@@ -156,33 +145,10 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
//TODO: Fix hacky solution to rotation for SideFlipped
style={{
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
$rotation={player.settings.rotation}
style={{
@@ -373,10 +339,11 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
</BetterRowContainer>
<dialog
ref={dialogRef}
className="z-[9999] 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%]"
>
<h1>Reset Game?</h1>
<div style={{ display: 'flex', justifyContent: 'space-evenly' }}>
<div className="h-full flex flex-col p-4 gap-2">
<h1 className="text-center">Reset Game?</h1>
<div className="flex justify-evenly gap-4">
<Button
variant="contained"
onClick={() => dialogRef.current?.close()}
@@ -393,6 +360,7 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
Yes
</Button>
</div>
</div>
</dialog>
</SettingsContainer>
</PlayerMenuWrapper>

View File

@@ -18,7 +18,7 @@ import { twc } from 'react-twc';
import OnePlayerLandscape from '../../../Icons/generated/Layouts/OnePlayerLandscape';
import { Orientation } from '../../../Types/Settings';
const LayoutWrapper = twc.div`flex flex-row justify-between self-center`;
const LayoutWrapper = twc.div`flex flex-row justify-center items-center self-center w-full`;
type LayoutOptionsProps = {
numberOfPlayers: number;
@@ -31,14 +31,16 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
selectedOrientation,
onChange,
}) => {
const iconHeight = '30vmin';
const iconWidth = '20vmin';
const iconWidth = '21vmin';
const iconHeight = '40vmin';
const iconMaxWidth = '124px';
const iconMaxHeight = '196px';
const renderLayoutOptions = () => {
switch (numberOfPlayers) {
case 1:
return (
<>
<div>
<FormControlLabel
value={Orientation.Landscape}
control={
@@ -58,6 +60,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
/>
}
TouchRippleProps={{ style: { display: 'none' } }}
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
/>
}
label=""
@@ -81,11 +84,12 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
/>
}
TouchRippleProps={{ style: { display: 'none' } }}
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
/>
}
label=""
/>
</>
</div>
);
case 2:
return (
@@ -94,6 +98,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<TwoPlayersSameSide
height={iconHeight}
@@ -117,6 +122,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<TwoPlayersOppositePortrait
height={iconHeight}
@@ -140,6 +146,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.OppositeLandscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<TwoPlayersOppositeLandscape
height={iconHeight}
@@ -168,6 +175,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<ThreePlayers
height={iconHeight}
@@ -191,6 +199,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<ThreePlayersSide
height={iconHeight}
@@ -220,6 +229,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<FourPlayers
height={iconHeight}
@@ -243,6 +253,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<FourPlayersSide
height={iconHeight}
@@ -272,6 +283,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<FivePlayers
height={iconHeight}
@@ -324,6 +336,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape}
control={
<Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={
<SixPlayers
height={iconHeight}

View File

@@ -20,7 +20,9 @@ import { LayoutOptions } from './LayoutOptions';
const MainWrapper = twc.div`w-[100dvw] h-fit pb-14 overflow-hidden items-center flex flex-col`;
const StartButtonFooter = twc.div`fixed bottom-4 z-1`;
const StartButtonFooter = twc.div`w-full max-w-[548px] fixed bottom-4 z-1 items-center flex flex-col px-4`;
const SliderWrapper = twc.div`mx-8`;
const ToggleButtonsWrapper = twc.div`flex flex-row justify-between items-center`;
@@ -174,8 +176,10 @@ const Start = () => {
Life Trinket
</h1>
<FormControl focused={false} style={{ width: '80vw' }}>
<div className="overflow-hidden items-center flex flex-col max-w-[548px] w-full mb-8 px-4">
<FormControl focused={false} style={{ width: '100%' }}>
<FormLabel>Number of Players</FormLabel>
<SliderWrapper>
<Slider
title="Number of Players"
max={6}
@@ -193,7 +197,10 @@ const Start = () => {
});
}}
/>
</SliderWrapper>
<FormLabel className="mt-[0.7rem]">Starting Health</FormLabel>
<SliderWrapper>
<Slider
title="Starting Health"
max={60}
@@ -211,6 +218,7 @@ const Start = () => {
})
}
/>
</SliderWrapper>
<ToggleButtonsWrapper className="mt-4">
<ToggleContainer>
@@ -277,21 +285,21 @@ const Start = () => {
}}
/>
</FormControl>
{!isPWA && (
<p className="text-center, max-w-[75%] text-xs text-text-primary">
<p className="text-center text-xs text-text-primary w-11/12 mt-4">
If you're on iOS, this page works better if you{' '}
<strong>hide the toolbar</strong> or{' '}
<strong>add the app to your home screen</strong>.
</p>
)}
</div>
<StartButtonFooter>
<Button
size="large"
variant="contained"
onClick={doStartGame}
style={{ width: '90dvw' }}
fullWidth
>
START GAME
</Button>

View File

@@ -229,6 +229,7 @@ export const createInitialPlayers = ({
extraCounters: [],
commanderDamage,
hasLost: false,
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
};
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;
showStartingPlayer: boolean;
hasLost: boolean;
isSide: boolean;
};
export type PlayerSettings = {

View File

@@ -6,6 +6,9 @@ import type { Config } from 'tailwindcss';
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
screens: {
modalSm: '548px',
},
extend: {
gridTemplateAreas: {
onePlayerLandscape: ['player0 player0'],
@@ -39,23 +42,24 @@ export default {
},
colors: {
primary: {
main: '#7F9172',
dark: '#57654F',
main: '#3E7D78',
dark: '#2D5F5B',
},
secondary: {
main: '#5E714C',
main: '#284F4C',
dark: '#1B3B38',
},
background: {
default: '#495E35',
default: '#08253B',
backdrop: 'rgba(0, 0, 0, 0.3)',
settings: 'rgba(20, 20, 0, 0.9)',
},
text: {
primary: '#F5F5F5',
secondary: '#b3b39b',
secondary: '#76A6A5',
},
action: {
disabled: '#5E714C',
disabled: '#234A47',
},
common: {
white: '#F9FFE3',
@@ -63,7 +67,7 @@ export default {
},
lifeCounter: {
text: 'rgba(0, 0, 0, 0.4)',
lostWrapper: '#00000070',
lostWrapper: '#000000',
},
interface: {
loseButton: {
@@ -95,15 +99,4 @@ export default {
},
plugins: [tailwindcssGridAreas],
} satisfies Config;
// const fadeOut = keyframes`
// 0% {
// opacity: 1;
// }
// 33% {
// opacity: 0.6;
// }
// 100% {
// opacity: 0;
// }
// `;
// #98FF98

View File

@@ -1,9 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
clientsClaim: true,
skipWaiting: true,
},
}),
],
build: {
minify: 'esbuild',
rollupOptions: {

2231
yarn.lock

File diff suppressed because it is too large Load Diff