forked from external-repos/LifeTrinket
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89e1eaff4e | ||
|
|
0f4e896342 | ||
|
|
dc1d5fe01d | ||
|
|
41e73d2c0c | ||
|
|
724dcf086c | ||
|
|
51f9c4d20e | ||
|
|
354c0dbbb2 | ||
|
|
3770d13beb | ||
|
|
13733242a2 | ||
|
|
81f3891b20 | ||
|
|
e153de9093 | ||
|
|
07775f85d2 | ||
|
|
10039175a1 | ||
|
|
bcf2a0a840 | ||
|
|
d25da5d97b | ||
|
|
f5a80e573e | ||
|
|
1f36264e39 | ||
|
|
d615cfd3ba | ||
|
|
4453b12ce6 | ||
|
|
d601a820f8 | ||
|
|
0455f43794 | ||
|
|
f94103fe51 | ||
|
|
c36668b933 | ||
|
|
f9d0346300 | ||
|
|
2f3ee74c74 | ||
|
|
f8f0788b97 | ||
|
|
bfe25eacb7 | ||
|
|
7b0965c0dd | ||
|
|
e55ea6a83a |
@@ -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
|
||||
|
||||
46
.github/workflows/firebase-release.yml
vendored
46
.github/workflows/firebase-release.yml
vendored
@@ -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 }}
|
||||
|
||||
12
package.json
12
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -229,6 +229,7 @@ export const createInitialPlayers = ({
|
||||
extraCounters: [],
|
||||
commanderDamage,
|
||||
hasLost: false,
|
||||
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
|
||||
};
|
||||
|
||||
players.push(player);
|
||||
|
||||
53
src/Hooks/useOrientation.ts
Normal file
53
src/Hooks/useOrientation.ts
Normal 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 };
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export type Player = {
|
||||
isStartingPlayer: boolean;
|
||||
showStartingPlayer: boolean;
|
||||
hasLost: boolean;
|
||||
isSide: boolean;
|
||||
};
|
||||
|
||||
export type PlayerSettings = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user