forked from external-repos/LifeTrinket
Clearer way to see if there is an update, more fun tracking
This commit is contained in:
2
.github/workflows/firebase-release.yml
vendored
2
.github/workflows/firebase-release.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
build_and_deploy:
|
build_and_deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }}
|
VITE_REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "life-trinket",
|
"name": "life-trinket",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.9.1",
|
"version": "0.9.2",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
"npm": "please use bun or yarn :) "
|
"yarn": "use pnpm",
|
||||||
|
"npm": "please use pnpm"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
9603
pnpm-lock.yaml
generated
Normal file
9603
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -133,10 +133,12 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
|||||||
trackMouse: true,
|
trackMouse: true,
|
||||||
onSwipedDown: (e) => {
|
onSwipedDown: (e) => {
|
||||||
e.event.stopPropagation();
|
e.event.stopPropagation();
|
||||||
|
analytics.trackEvent('open_player_menu_swipe');
|
||||||
setShowPlayerMenu(true);
|
setShowPlayerMenu(true);
|
||||||
},
|
},
|
||||||
onSwipedUp: (e) => {
|
onSwipedUp: (e) => {
|
||||||
e.event.stopPropagation();
|
e.event.stopPropagation();
|
||||||
|
analytics.trackEvent('close_player_menu_swipe');
|
||||||
setShowPlayerMenu(false);
|
setShowPlayerMenu(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -227,6 +229,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
|||||||
{settings.showPlayerMenuCog && (
|
{settings.showPlayerMenuCog && (
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
analytics.trackEvent('open_player_menu_button');
|
||||||
setShowPlayerMenu(!showPlayerMenu);
|
setShowPlayerMenu(!showPlayerMenu);
|
||||||
}}
|
}}
|
||||||
rotation={player.settings.rotation}
|
rotation={player.settings.rotation}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { twc } from 'react-twc';
|
|||||||
import { Separator } from './Separator';
|
import { Separator } from './Separator';
|
||||||
import { Paragraph } from './TextComponents';
|
import { Paragraph } from './TextComponents';
|
||||||
import { Cross } from '../../Icons/generated';
|
import { Cross } from '../../Icons/generated';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useAnalytics } from '../../Hooks/useAnalytics';
|
||||||
|
|
||||||
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]`;
|
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]`;
|
||||||
|
|
||||||
@@ -12,6 +14,17 @@ type InfoModalProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
|
export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
|
||||||
|
const analytics = useAnalytics();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
analytics.trackEvent('info_opened');
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Modal, Switch } from '@mui/material';
|
import { Modal, Switch } from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { twc } from 'react-twc';
|
import { twc } from 'react-twc';
|
||||||
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
||||||
import { Cross } from '../../Icons/generated';
|
import { Cross } from '../../Icons/generated';
|
||||||
@@ -7,76 +7,55 @@ import { PreStartMode } from '../../Types/Settings';
|
|||||||
import { ModalWrapper } from './InfoModal';
|
import { ModalWrapper } from './InfoModal';
|
||||||
import { Separator } from './Separator';
|
import { Separator } from './Separator';
|
||||||
import { Paragraph } from './TextComponents';
|
import { Paragraph } from './TextComponents';
|
||||||
|
import { useAnalytics } from '../../Hooks/useAnalytics';
|
||||||
|
|
||||||
const SettingContainer = twc.div`w-full flex flex-col mb-2`;
|
const SettingContainer = twc.div`w-full flex flex-col mb-2`;
|
||||||
|
|
||||||
const ToggleContainer = twc.div`flex flex-row justify-between items-center -mb-1`;
|
const ToggleContainer = twc.div`flex flex-row justify-between items-center -mb-1`;
|
||||||
|
|
||||||
const Container = twc.div`flex flex-col items-center w-full`;
|
const Container = twc.div`flex flex-col items-start w-full`;
|
||||||
|
|
||||||
const Description = twc.p`mr-16 text-xs text-left text-text-secondary`;
|
const Description = twc.p`mr-16 text-xs text-left text-text-secondary`;
|
||||||
|
|
||||||
|
const baseGithubReleasesUrl =
|
||||||
|
'https://github.com/Vikeo/LifeTrinket/releases/tag/';
|
||||||
|
|
||||||
type SettingsModalProps = {
|
type SettingsModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
||||||
const { settings, setSettings, isPWA } = useGlobalSettings();
|
const { settings, setSettings, isPWA, version } = useGlobalSettings();
|
||||||
const [isLatestVersion, setIsLatestVersion] = useState(false);
|
const analytics = useAnalytics();
|
||||||
const [newVersion, setNewVersion] = useState<string | undefined>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async function checkIfLatestVersion() {
|
|
||||||
try {
|
|
||||||
const result = await fetch(
|
|
||||||
'https://api.github.com/repos/Vikeo/LifeTrinket/releases/latest',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
/* @ts-expect-error is defined in vite.config.ts*/
|
|
||||||
Authorization: `Bearer ${REPO_READ_ACCESS_TOKEN}`,
|
|
||||||
Accept: 'application/vnd.github+json',
|
|
||||||
'X-GitHub-Api-Version': '2022-11-28',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const data = await result.json();
|
|
||||||
|
|
||||||
if (!data.name) {
|
analytics.trackEvent('settings_opened');
|
||||||
setNewVersion(undefined);
|
version.checkForNewVersion('settings');
|
||||||
setIsLatestVersion(false);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewVersion(data.name);
|
|
||||||
|
|
||||||
/* @ts-expect-error is defined in vite.config.ts*/
|
|
||||||
if (data.name === APP_VERSION) {
|
|
||||||
setIsLatestVersion(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLatestVersion(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('error getting latest version string', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkIfLatestVersion();
|
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={closeModal}
|
onClose={() => {
|
||||||
|
analytics.trackEvent('settings_outside_clicked');
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
className="w-full flex justify-center"
|
className="w-full flex justify-center"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-center items-center relative w-full max-w-[532px]">
|
<div className="flex justify-center items-center relative w-full max-w-[532px]">
|
||||||
<button
|
<button
|
||||||
onClick={closeModal}
|
onClick={() => {
|
||||||
|
analytics.trackEvent('settings_cross_clicked');
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
className="flex absolute top-12 right-0 z-10 w-10 h-10 bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
|
className="flex absolute top-12 right-0 z-10 w-10 h-10 bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
|
||||||
>
|
>
|
||||||
<Cross size="16px" className="text-text-primary " />
|
<Cross size="16px" className="text-text-primary " />
|
||||||
@@ -84,31 +63,71 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<ModalWrapper>
|
<ModalWrapper>
|
||||||
<Container>
|
<Container>
|
||||||
<h2 className="text-center text-2xl mb-2">⚙️ Settings ⚙️</h2>
|
<h2 className="text-center text-2xl mb-2 w-full">⚙️ Settings ⚙️</h2>
|
||||||
<SettingContainer>
|
<div className="flex flex-col mb-2 w-full">
|
||||||
<Paragraph>
|
<div className="text-text-primary flex items-center gap-2">
|
||||||
{/* @ts-expect-error is defined in vite.config.ts*/}
|
Current version: {version.installedVersion}{' '}
|
||||||
Current version: {APP_VERSION}{' '}
|
{version.isLatest && (
|
||||||
{isLatestVersion && (
|
|
||||||
<span className="text-sm text-text-secondary">(latest)</span>
|
<span className="text-sm text-text-secondary">(latest)</span>
|
||||||
)}
|
)}
|
||||||
</Paragraph>
|
<div className="text-xs text-text-primary opacity-75">
|
||||||
{!isLatestVersion && newVersion && (
|
(
|
||||||
<Paragraph className="text-text-secondary text-lg text-center">
|
<a
|
||||||
New version ({newVersion}) is available!{' '}
|
href={baseGithubReleasesUrl + version.installedVersion}
|
||||||
</Paragraph>
|
target="_blank"
|
||||||
|
className="underline"
|
||||||
|
onClick={() => {
|
||||||
|
analytics.trackEvent(
|
||||||
|
`current_change_log_clicked_v${version.installedVersion}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Release notes
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!version.isLatest && version.remoteVersion && (
|
||||||
|
<>
|
||||||
|
<div className="flex gap-2 items-center mt-2">
|
||||||
|
<Paragraph className="text-text-secondary">
|
||||||
|
{version.remoteVersion} available!
|
||||||
|
</Paragraph>
|
||||||
|
<div className="text-xs text-text-primary opacity-75">
|
||||||
|
(
|
||||||
|
<a
|
||||||
|
href={baseGithubReleasesUrl + version.remoteVersion}
|
||||||
|
target="_blank"
|
||||||
|
className="underline"
|
||||||
|
onClick={() => {
|
||||||
|
analytics.trackEvent(
|
||||||
|
`new_change_log_clicked_v${version.remoteVersion}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Release notes
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="flex justify-center items-center self-start mt-2 bg-primary-main px-3 py-1 rounded-md"
|
||||||
|
onClick={() => {
|
||||||
|
{
|
||||||
|
analytics.trackEvent(`pressed_update`, {
|
||||||
|
toVersion: version.remoteVersion,
|
||||||
|
fromVersion: version.installedVersion,
|
||||||
|
});
|
||||||
|
window?.location?.reload();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-sm">Update</span>
|
||||||
|
<span className="text-xs"> (reload app)</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingContainer>
|
</div>
|
||||||
{!isLatestVersion && newVersion && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
style={{ marginTop: '0.25rem', marginBottom: '0.25rem' }}
|
|
||||||
onClick={() => window?.location?.reload()}
|
|
||||||
>
|
|
||||||
<span>Update</span>
|
|
||||||
<span className="text-xs"> (reload app)</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Separator height="1px" />
|
<Separator height="1px" />
|
||||||
|
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
@@ -149,7 +168,7 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<div className="flex flex-row justify-between items-center mb-1">
|
<div className="flex flex-row justify-between items-center mb-1">
|
||||||
<label htmlFor="pre-start-modes">Pre-Start mode</label>
|
<label htmlFor="pre-start-modes">Player selection style</label>
|
||||||
<select
|
<select
|
||||||
name="pre-start-modes"
|
name="pre-start-modes"
|
||||||
id="pre-start-modes"
|
id="pre-start-modes"
|
||||||
@@ -163,9 +182,11 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
}}
|
}}
|
||||||
disabled={!settings.showStartingPlayer}
|
disabled={!settings.showStartingPlayer}
|
||||||
>
|
>
|
||||||
<option value={PreStartMode.None}>None</option>
|
<option value={PreStartMode.None}>Instant</option>
|
||||||
<option value={PreStartMode.RandomKing}>Random King</option>
|
<option value={PreStartMode.RandomKing}>Royal Shuffle</option>
|
||||||
<option value={PreStartMode.FingerGame}>Finger Game</option>
|
<option value={PreStartMode.FingerGame}>
|
||||||
|
Touch Roulette
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-left text-text-secondary">
|
<div className="text-xs text-left text-text-secondary">
|
||||||
@@ -175,13 +196,13 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
|
|
||||||
{settings.preStartMode === PreStartMode.None && (
|
{settings.preStartMode === PreStartMode.None && (
|
||||||
<div className="text-xs text-left text-text-secondary mt-1">
|
<div className="text-xs text-left text-text-secondary mt-1">
|
||||||
<span className="text-text-primary">None:</span> The starting
|
<span className="text-text-primary">Instant:</span> A random
|
||||||
player will simply be shown.
|
starting player will simply be shown on start.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{settings.preStartMode === PreStartMode.RandomKing && (
|
{settings.preStartMode === PreStartMode.RandomKing && (
|
||||||
<div className="text-xs text-left text-text-secondary mt-1">
|
<div className="text-xs text-left text-text-secondary mt-1">
|
||||||
<span className="text-text-primary">Random King:</span>{' '}
|
<span className="text-text-primary">Royal Shuffle:</span>{' '}
|
||||||
Randomly pass a crown between all players, press the screen to
|
Randomly pass a crown between all players, press the screen to
|
||||||
stop it. The player who has the crown when it stops gets to
|
stop it. The player who has the crown when it stops gets to
|
||||||
start.
|
start.
|
||||||
@@ -189,7 +210,7 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
)}
|
)}
|
||||||
{settings.preStartMode === PreStartMode.FingerGame && (
|
{settings.preStartMode === PreStartMode.FingerGame && (
|
||||||
<div className="text-xs text-left text-text-secondary mt-1">
|
<div className="text-xs text-left text-text-secondary mt-1">
|
||||||
<span className="text-text-primary">Finger Game:</span> All
|
<span className="text-text-primary">Touch Roulette:</span> All
|
||||||
players put a finger on the screen, one will be chosen at
|
players put a finger on the screen, one will be chosen at
|
||||||
random.
|
random.
|
||||||
</div>
|
</div>
|
||||||
@@ -234,6 +255,16 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
enabled.
|
enabled.
|
||||||
</Description>
|
</Description>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
<Separator height="1px" />
|
||||||
|
<button
|
||||||
|
className="flex justify-center self-center items-center mt-1 mb-1 bg-primary-main px-3 py-1 rounded-md"
|
||||||
|
onClick={() => {
|
||||||
|
analytics.trackEvent('settings_save_clicked');
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-sm">Save and Close</span>
|
||||||
|
</button>
|
||||||
{!isPWA && (
|
{!isPWA && (
|
||||||
<>
|
<>
|
||||||
<Separator height="1px" />
|
<Separator height="1px" />
|
||||||
@@ -254,14 +285,6 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Separator height="1px" />
|
<Separator height="1px" />
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={closeModal}
|
|
||||||
style={{ marginTop: '0.25rem' }}
|
|
||||||
>
|
|
||||||
Save and Close
|
|
||||||
</Button>
|
|
||||||
</Container>
|
</Container>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from '../../Icons/generated';
|
} from '../../Icons/generated';
|
||||||
import { Player, Rotation } from '../../Types/Player';
|
import { Player, Rotation } from '../../Types/Player';
|
||||||
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
||||||
|
import { useAnalytics } from '../../Hooks/useAnalytics';
|
||||||
|
|
||||||
const PlayerMenuWrapper = twc.div`
|
const PlayerMenuWrapper = twc.div`
|
||||||
flex
|
flex
|
||||||
@@ -111,6 +112,8 @@ const PlayerMenu = ({
|
|||||||
setRandomizingPlayer,
|
setRandomizingPlayer,
|
||||||
} = useGlobalSettings();
|
} = useGlobalSettings();
|
||||||
|
|
||||||
|
const analytics = useAnalytics();
|
||||||
|
|
||||||
const { updatePlayer, resetCurrentGame } = usePlayers();
|
const { updatePlayer, resetCurrentGame } = usePlayers();
|
||||||
|
|
||||||
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -130,6 +133,7 @@ const PlayerMenu = ({
|
|||||||
setShowPlayerMenu(false);
|
setShowPlayerMenu(false);
|
||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
setRandomizingPlayer(true);
|
setRandomizingPlayer(true);
|
||||||
|
analytics.trackEvent('reset_game');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoToStart = () => {
|
const handleGoToStart = () => {
|
||||||
@@ -173,7 +177,10 @@ const PlayerMenu = ({
|
|||||||
ref={settingsContainerRef}
|
ref={settingsContainerRef}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowPlayerMenu(false)}
|
onClick={() => {
|
||||||
|
analytics.trackEvent('close_player_menu_button');
|
||||||
|
setShowPlayerMenu(false);
|
||||||
|
}}
|
||||||
className="flex absolute top-0 right-2 z-10 bg-transparent items-center justify-center rounded-full border-solid border-primary-main border-2 p-[0.2rem]"
|
className="flex absolute top-0 right-2 z-10 bg-transparent items-center justify-center rounded-full border-solid border-primary-main border-2 p-[0.2rem]"
|
||||||
>
|
>
|
||||||
<Cross size={buttonFontSize} className="text-primary-main " />
|
<Cross size={buttonFontSize} className="text-primary-main " />
|
||||||
@@ -187,6 +194,11 @@ const PlayerMenu = ({
|
|||||||
type="color"
|
type="color"
|
||||||
className="size-[200%] absolute -left-2 -top-2"
|
className="size-[200%] absolute -left-2 -top-2"
|
||||||
value={player.color}
|
value={player.color}
|
||||||
|
onClick={() => {
|
||||||
|
analytics.trackEvent('color_picker_opened', {
|
||||||
|
player: player.index,
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ColorPickerButton>
|
</ColorPickerButton>
|
||||||
{player.settings.useCommanderDamage && (
|
{player.settings.useCommanderDamage && (
|
||||||
@@ -210,7 +222,12 @@ const PlayerMenu = ({
|
|||||||
strokeWidth="30"
|
strokeWidth="30"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onChange={handleSettingsChange}
|
onChange={(e) => {
|
||||||
|
analytics.trackEvent('toggle_partner', {
|
||||||
|
checked: e.target.checked,
|
||||||
|
});
|
||||||
|
handleSettingsChange(e);
|
||||||
|
}}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={player.settings.usePartner}
|
aria-checked={player.settings.usePartner}
|
||||||
aria-label="Partner"
|
aria-label="Partner"
|
||||||
@@ -237,7 +254,12 @@ const PlayerMenu = ({
|
|||||||
strokeWidth="30"
|
strokeWidth="30"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onChange={handleSettingsChange}
|
onChange={(e) => {
|
||||||
|
analytics.trackEvent('toggle_poison', {
|
||||||
|
checked: e.target.checked,
|
||||||
|
});
|
||||||
|
handleSettingsChange(e);
|
||||||
|
}}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={player.settings.usePoison}
|
aria-checked={player.settings.usePoison}
|
||||||
aria-label="Poison"
|
aria-label="Poison"
|
||||||
@@ -263,7 +285,12 @@ const PlayerMenu = ({
|
|||||||
strokeWidth="15"
|
strokeWidth="15"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onChange={handleSettingsChange}
|
onChange={(e) => {
|
||||||
|
analytics.trackEvent('toggle_energy', {
|
||||||
|
checked: e.target.checked,
|
||||||
|
});
|
||||||
|
handleSettingsChange(e);
|
||||||
|
}}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={player.settings.useEnergy}
|
aria-checked={player.settings.useEnergy}
|
||||||
aria-label="Energy"
|
aria-label="Energy"
|
||||||
@@ -289,7 +316,12 @@ const PlayerMenu = ({
|
|||||||
strokeWidth="15"
|
strokeWidth="15"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onChange={handleSettingsChange}
|
onChange={(e) => {
|
||||||
|
analytics.trackEvent('toggle_experience', {
|
||||||
|
checked: e.target.checked,
|
||||||
|
});
|
||||||
|
handleSettingsChange(e);
|
||||||
|
}}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={player.settings.useExperience}
|
aria-checked={player.settings.useExperience}
|
||||||
aria-label="Experience"
|
aria-label="Experience"
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ const Start = () => {
|
|||||||
settings,
|
settings,
|
||||||
isPWA,
|
isPWA,
|
||||||
setRandomizingPlayer,
|
setRandomizingPlayer,
|
||||||
|
version,
|
||||||
} = useGlobalSettings();
|
} = useGlobalSettings();
|
||||||
|
|
||||||
const [openInfoModal, setOpenInfoModal] = useState(false);
|
const [openInfoModal, setOpenInfoModal] = useState(false);
|
||||||
@@ -100,6 +101,29 @@ const Start = () => {
|
|||||||
initialGameSettings || defaultInitialGameSettings
|
initialGameSettings || defaultInitialGameSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let tracked = false;
|
||||||
|
// Check for new version on mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tracked) {
|
||||||
|
console.log('checking version');
|
||||||
|
version.checkForNewVersion('start_menu');
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
tracked = true;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInitialGameSettings(playerOptions);
|
||||||
|
}, [playerOptions, setInitialGameSettings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPlayerOptions({
|
||||||
|
...playerOptions,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [playerOptions.numberOfPlayers]);
|
||||||
|
|
||||||
const doStartGame = () => {
|
const doStartGame = () => {
|
||||||
if (!initialGameSettings) {
|
if (!initialGameSettings) {
|
||||||
return;
|
return;
|
||||||
@@ -127,21 +151,10 @@ const Start = () => {
|
|||||||
localStorage.setItem('showPlay', 'true');
|
localStorage.setItem('showPlay', 'true');
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const valueText = (value: number) => {
|
||||||
setInitialGameSettings(playerOptions);
|
|
||||||
}, [playerOptions, setInitialGameSettings]);
|
|
||||||
|
|
||||||
const valuetext = (value: number) => {
|
|
||||||
return `${value}`;
|
return `${value}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPlayerOptions({
|
|
||||||
...playerOptions,
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [playerOptions.numberOfPlayers]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainWrapper>
|
<MainWrapper>
|
||||||
<Info
|
<Info
|
||||||
@@ -169,8 +182,12 @@ const Start = () => {
|
|||||||
|
|
||||||
<SupportMe />
|
<SupportMe />
|
||||||
|
|
||||||
<h1 className="text-3xl block font-bold mt-6 mb-5 text-text-primary">
|
<h1 className="relative flex flex-col text-3xl font-bold mt-6 mb-6 text-text-primary justify-center items-center">
|
||||||
Life Trinket
|
Life Trinket
|
||||||
|
<div className="h-[1px] w-[120%] bg-common-white opacity-50" />
|
||||||
|
<div className="flex absolute text-xs font-medium -bottom-4">
|
||||||
|
v{version.installedVersion}
|
||||||
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="overflow-hidden items-center flex flex-col max-w-[548px] w-full mb-8 px-4">
|
<div className="overflow-hidden items-center flex flex-col max-w-[548px] w-full mb-8 px-4">
|
||||||
@@ -183,7 +200,7 @@ const Start = () => {
|
|||||||
min={1}
|
min={1}
|
||||||
aria-label="Custom marks"
|
aria-label="Custom marks"
|
||||||
value={playerOptions?.numberOfPlayers ?? 4}
|
value={playerOptions?.numberOfPlayers ?? 4}
|
||||||
getAriaValueText={valuetext}
|
getAriaValueText={valueText}
|
||||||
step={null}
|
step={null}
|
||||||
marks={playerMarks}
|
marks={playerMarks}
|
||||||
onChange={(_e, value) => {
|
onChange={(_e, value) => {
|
||||||
@@ -204,7 +221,7 @@ const Start = () => {
|
|||||||
min={20}
|
min={20}
|
||||||
aria-label="Custom marks"
|
aria-label="Custom marks"
|
||||||
value={playerOptions?.startingLifeTotal ?? 40}
|
value={playerOptions?.startingLifeTotal ?? 40}
|
||||||
getAriaValueText={valuetext}
|
getAriaValueText={valueText}
|
||||||
step={10}
|
step={10}
|
||||||
marks={healthMarks}
|
marks={healthMarks}
|
||||||
onChange={(_e, value) =>
|
onChange={(_e, value) =>
|
||||||
@@ -247,15 +264,32 @@ const Start = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ToggleContainer>
|
</ToggleContainer>
|
||||||
<Button
|
<div className="flex flex-nowrap text-nowrap relative justify-center items-start">
|
||||||
variant="contained"
|
<Button
|
||||||
style={{ height: '2rem' }}
|
variant="contained"
|
||||||
onClick={() => {
|
style={{ height: '2rem' }}
|
||||||
setOpenSettingsModal(true);
|
onClick={() => {
|
||||||
}}
|
setOpenSettingsModal(true);
|
||||||
>
|
}}
|
||||||
<Cog /> Other settings
|
>
|
||||||
</Button>
|
<Cog /> Game Settings
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-not-latest-version={
|
||||||
|
!version.isLatest && !!version.remoteVersion
|
||||||
|
}
|
||||||
|
className="absolute flex justify-center text-text-primary text-xxs -bottom-5 bg-primary-dark px-2 rounded-md
|
||||||
|
opacity-0 transition-all duration-200 delay-500
|
||||||
|
data-[not-latest-version=true]:opacity-100
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div className="absolute bg-primary-dark rotate-45 size-2 -top-[2px] z-0" />
|
||||||
|
<span className="z-10">
|
||||||
|
v{version.remoteVersion} available!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ToggleButtonsWrapper>
|
</ToggleButtonsWrapper>
|
||||||
|
|
||||||
<FormLabel>Layout</FormLabel>
|
<FormLabel>Layout</FormLabel>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { InitialGameSettings, Settings } from '../Types/Settings';
|
import { InitialGameSettings, Settings } from '../Types/Settings';
|
||||||
|
|
||||||
|
type Version = {
|
||||||
|
installedVersion: string;
|
||||||
|
isLatest: boolean;
|
||||||
|
checkForNewVersion: (source: 'settings' | 'start_menu') => Promise<void>;
|
||||||
|
remoteVersion?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type GlobalSettingsContextType = {
|
export type GlobalSettingsContextType = {
|
||||||
fullscreen: {
|
fullscreen: {
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
@@ -29,6 +36,8 @@ export type GlobalSettingsContextType = {
|
|||||||
isPWA: boolean;
|
isPWA: boolean;
|
||||||
preStartCompleted: boolean;
|
preStartCompleted: boolean;
|
||||||
setPreStartCompleted: (completed: boolean) => void;
|
setPreStartCompleted: (completed: boolean) => void;
|
||||||
|
|
||||||
|
version: Version;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GlobalSettingsContext =
|
export const GlobalSettingsContext =
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ export const GlobalSettingsProvider = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [isLatestVersion, setIsLatestVersion] = useState(false);
|
||||||
|
const [remoteVersion, setRemoteVersion] = useState<string | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
const { isSupported, release, released, request, type } = useWakeLock();
|
const { isSupported, release, released, request, type } = useWakeLock();
|
||||||
|
|
||||||
const active = settings.keepAwake;
|
const active = settings.keepAwake;
|
||||||
@@ -195,6 +200,46 @@ export const GlobalSettingsProvider = ({
|
|||||||
localStorage.setItem('playing', String(playing));
|
localStorage.setItem('playing', String(playing));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function checkForNewVersion(source: 'settings' | 'start_menu') {
|
||||||
|
try {
|
||||||
|
const result = await fetch(
|
||||||
|
'https://api.github.com/repos/Vikeo/LifeTrinket/releases/latest',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${
|
||||||
|
import.meta.env.VITE_REPO_READ_ACCESS_TOKEN
|
||||||
|
}`,
|
||||||
|
Accept: 'application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const data = await result.json();
|
||||||
|
|
||||||
|
if (!data.name) {
|
||||||
|
setRemoteVersion(undefined);
|
||||||
|
setIsLatestVersion(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRemoteVersion(data.name);
|
||||||
|
|
||||||
|
if (data.name === import.meta.env.VITE_APP_VERSION) {
|
||||||
|
setIsLatestVersion(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
analytics.trackEvent(`${source}_has_new_version`, {
|
||||||
|
remoteVersion: data.name,
|
||||||
|
installedVersion: import.meta.env.VITE_APP_VERSION,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLatestVersion(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error getting latest version string', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fullscreen: { isFullscreen, enableFullscreen, disableFullscreen },
|
fullscreen: { isFullscreen, enableFullscreen, disableFullscreen },
|
||||||
wakeLock: {
|
wakeLock: {
|
||||||
@@ -219,6 +264,13 @@ export const GlobalSettingsProvider = ({
|
|||||||
isPWA: window?.matchMedia('(display-mode: standalone)').matches,
|
isPWA: window?.matchMedia('(display-mode: standalone)').matches,
|
||||||
preStartCompleted,
|
preStartCompleted,
|
||||||
setPreStartCompleted: setPreStartCompletedAndLocalStorage,
|
setPreStartCompleted: setPreStartCompletedAndLocalStorage,
|
||||||
|
|
||||||
|
version: {
|
||||||
|
installedVersion: import.meta.env.VITE_APP_VERSION,
|
||||||
|
remoteVersion,
|
||||||
|
isLatest: isLatestVersion,
|
||||||
|
checkForNewVersion,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
@@ -233,6 +285,8 @@ export const GlobalSettingsProvider = ({
|
|||||||
settings,
|
settings,
|
||||||
randomizingPlayer,
|
randomizingPlayer,
|
||||||
preStartCompleted,
|
preStartCompleted,
|
||||||
|
remoteVersion,
|
||||||
|
isLatestVersion,
|
||||||
analytics,
|
analytics,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
9
src/vite-env.d.ts
vendored
9
src/vite-env.d.ts
vendored
@@ -1 +1,10 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_APP_VERSION: string;
|
||||||
|
readonly VITE_REPO_READ_ACCESS_TOKEN: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ export default {
|
|||||||
animation: {
|
animation: {
|
||||||
fadeOut: 'fadeOut 3s 1s ease-out forwards',
|
fadeOut: 'fadeOut 3s 1s ease-out forwards',
|
||||||
},
|
},
|
||||||
|
fontSize: {
|
||||||
|
xxs: ['0.625rem', '1rem'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [tailwindcssGridAreas],
|
plugins: [tailwindcssGridAreas],
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
APP_VERSION: JSON.stringify(process.env.npm_package_version),
|
'import.meta.env.VITE_APP_VERSION': JSON.stringify(
|
||||||
REPO_READ_ACCESS_TOKEN: JSON.stringify(process.env.REPO_READ_ACCESS_TOKEN),
|
process.env.npm_package_version
|
||||||
|
),
|
||||||
|
VITE_REPO_READ_ACCESS_TOKEN: JSON.stringify(
|
||||||
|
process.env.VITE_REPO_READ_ACCESS_TOKEN
|
||||||
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user