forked from external-repos/LifeTrinket
add name tag
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "life-trinket",
|
||||
"private": true,
|
||||
"version": "0.9.98",
|
||||
"version": "0.9.99",
|
||||
"type": "commonjs",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
|
||||
@@ -9,10 +9,6 @@ export type RotationDivProps = TwcComponentProps<'div'> & {
|
||||
$rotation?: number;
|
||||
};
|
||||
|
||||
export type RotationSpanProps = TwcComponentProps<'span'> & {
|
||||
$rotation?: number;
|
||||
};
|
||||
|
||||
export type RotationButtonProps = TwcComponentProps<'button'> & {
|
||||
$rotation?: number;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { TwcComponentProps, twc } from 'react-twc';
|
||||
import { lifeLongPressMultiplier } from '../../Data/constants';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import { MAX_TAP_MOVE_DISTANCE } from './CommanderDamage';
|
||||
import { checkContrast } from '../../Utils/checkContrast';
|
||||
|
||||
type RotationButtonProps = TwcComponentProps<'div'> & {
|
||||
$align?: string;
|
||||
@@ -58,18 +57,6 @@ const LifeCounterButton = ({
|
||||
const [hasPressedDown, setHasPressedDown] = useState(false);
|
||||
const downPositionRef = useRef({ x: 0, y: 0 });
|
||||
|
||||
const [iconColor, setIconColor] = useState<'dark' | 'light'>('dark');
|
||||
|
||||
useEffect(() => {
|
||||
const contrast = checkContrast(player.color, '#00000080');
|
||||
|
||||
if (contrast === 'Fail') {
|
||||
setIconColor('light');
|
||||
} else {
|
||||
setIconColor('dark');
|
||||
}
|
||||
}, [player.color]);
|
||||
|
||||
const handleLifeChange = (increment: number) => {
|
||||
setLifeTotal(player.lifeTotal + increment);
|
||||
};
|
||||
@@ -132,7 +119,7 @@ const LifeCounterButton = ({
|
||||
<TextContainer
|
||||
$rotation={player.settings.rotation}
|
||||
$align={operation === 'add' ? 'right' : 'left'}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark
|
||||
data-[contrast=light]:text-icons-light"
|
||||
>
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
import { CounterType, Player, Rotation } from '../../Types/Player';
|
||||
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
||||
import ExtraCounter from '../Buttons/ExtraCounter';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { checkContrast } from '../../Utils/checkContrast';
|
||||
|
||||
const Container = twc.div<RotationDivProps>((props) => [
|
||||
'flex',
|
||||
@@ -33,17 +31,6 @@ type ExtraCountersBarProps = {
|
||||
|
||||
const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
const { updatePlayer } = usePlayers();
|
||||
const [iconColor, setIconColor] = useState<'dark' | 'light'>('dark');
|
||||
|
||||
useEffect(() => {
|
||||
const contrast = checkContrast(player.color, '#00000080');
|
||||
|
||||
if (contrast === 'Fail') {
|
||||
setIconColor('light');
|
||||
} else {
|
||||
setIconColor('dark');
|
||||
}
|
||||
}, [player.color]);
|
||||
|
||||
const handleCounterChange = (
|
||||
updatedCounterTotal: number,
|
||||
@@ -109,7 +96,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
Icon={
|
||||
<CommanderTax
|
||||
size={iconSize}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
strokeWidth={0}
|
||||
stroke="transparent"
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
@@ -132,7 +119,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
Icon={
|
||||
<PartnerTax
|
||||
size={iconSize}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
/>
|
||||
}
|
||||
@@ -153,7 +140,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
Icon={
|
||||
<Poison
|
||||
size={iconSize}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
/>
|
||||
}
|
||||
@@ -173,7 +160,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
Icon={
|
||||
<Energy
|
||||
size={iconSize}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
/>
|
||||
}
|
||||
@@ -193,7 +180,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
|
||||
Icon={
|
||||
<Experience
|
||||
size={iconSize}
|
||||
data-contrast={iconColor}
|
||||
data-contrast={player.iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { twc } from 'react-twc';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import {
|
||||
RotationDivProps,
|
||||
RotationSpanProps,
|
||||
} from '../Buttons/CommanderDamage';
|
||||
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
||||
import LifeCounterButton from '../Buttons/LifeCounterButton';
|
||||
import { OutlinedText } from '../Misc/OutlinedText';
|
||||
|
||||
@@ -32,12 +29,12 @@ const TextWrapper = twc.div`
|
||||
z-[-1]
|
||||
`;
|
||||
|
||||
const RecentDifference = twc.div<RotationSpanProps>((props) => [
|
||||
'absolute min-w-[20vmin] drop-shadow-none text-center bg-interface-recentDifference-background tabular-nums rounded-full p-[6px 12px] text-[8vmin] text-interface-recentDifference-text animate-fadeOut',
|
||||
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
|
||||
? 'top-1/3 translate-x-1/4 translate-y-1/2 rotate-[270deg]'
|
||||
: 'top-1/4 left-[50%] -translate-x-1/2',
|
||||
]);
|
||||
const RecentDifference = twc.div`
|
||||
absolute min-w-[20vmin] drop-shadow-none text-center bg-interface-recentDifference-background tabular-nums rounded-full p-[6px 12px] text-[8vmin] text-interface-recentDifference-text animate-fadeOut
|
||||
|
||||
top-1/4 left-[50%] -translate-x-1/2
|
||||
data-[isSide=true]:top-1/3 data-[isSide=true]:translate-x-1/4 data-[isSide=true]:translate-y-1/2 data-[isSide=true]:rotate-[270deg] data-[isSide=true]:left-auto
|
||||
`;
|
||||
|
||||
type HealthProps = {
|
||||
player: Player;
|
||||
@@ -101,6 +98,10 @@ const Health = ({
|
||||
return minRatio * scaleFactor * 1;
|
||||
};
|
||||
|
||||
const isSide =
|
||||
player.settings.rotation === Rotation.SideFlipped ||
|
||||
player.settings.rotation === Rotation.Side;
|
||||
|
||||
return (
|
||||
<LifeContainer $rotation={player.settings.rotation}>
|
||||
<LifeCounterButton
|
||||
@@ -109,6 +110,31 @@ const Health = ({
|
||||
operation="subtract"
|
||||
increment={-1}
|
||||
/>
|
||||
|
||||
{player.name && isSide ? (
|
||||
<div className="size-full relative flex items-center justify-start">
|
||||
<div className="fixed flex justify-center -rotate-90 left-[5.4vmax] ">
|
||||
<div
|
||||
data-contrast={player.iconTheme}
|
||||
className="absolute text-[4vmin] opacity-50 font-bold
|
||||
data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
>
|
||||
{player.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-full relative flex items-start justify-center">
|
||||
<div
|
||||
data-contrast={player.iconTheme}
|
||||
className="absolute text-[4vmin] -top-[1.1vmin] opacity-50 font-bold
|
||||
data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
>
|
||||
{player.name}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TextWrapper>
|
||||
<LifeCounterTextContainer
|
||||
$rotation={player.settings.rotation}
|
||||
@@ -122,10 +148,7 @@ const Health = ({
|
||||
{player.lifeTotal}
|
||||
</OutlinedText>
|
||||
{recentDifference !== 0 && (
|
||||
<RecentDifference
|
||||
key={differenceKey}
|
||||
$rotation={player.settings.rotation}
|
||||
>
|
||||
<RecentDifference data-isSide={isSide} key={differenceKey}>
|
||||
{recentDifference > 0 ? '+' : ''}
|
||||
{recentDifference}
|
||||
</RecentDifference>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
||||
import { usePlayers } from '../../Hooks/usePlayers';
|
||||
import { Cog } from '../../Icons/generated';
|
||||
import { Player, Rotation } from '../../Types/Player';
|
||||
import { checkContrast } from '../../Utils/checkContrast';
|
||||
import {
|
||||
RotationButtonProps,
|
||||
RotationDivProps,
|
||||
@@ -28,22 +27,14 @@ const SettingsButtonTwc = twc.button<RotationButtonProps>((props) => [
|
||||
type SettingsButtonProps = {
|
||||
onClick: () => void;
|
||||
rotation: Rotation;
|
||||
color: string;
|
||||
iconTheme: 'light' | 'dark';
|
||||
};
|
||||
|
||||
const SettingsButton = ({ onClick, rotation, color }: SettingsButtonProps) => {
|
||||
const [iconColor, setIconColor] = useState<'dark' | 'light'>('dark');
|
||||
|
||||
useEffect(() => {
|
||||
const contrast = checkContrast(color, '#00000080');
|
||||
|
||||
if (contrast === 'Fail') {
|
||||
setIconColor('light');
|
||||
} else {
|
||||
setIconColor('dark');
|
||||
}
|
||||
}, [color]);
|
||||
|
||||
const SettingsButton = ({
|
||||
onClick,
|
||||
rotation,
|
||||
iconTheme,
|
||||
}: SettingsButtonProps) => {
|
||||
return (
|
||||
<SettingsButtonTwc
|
||||
onClick={onClick}
|
||||
@@ -52,7 +43,7 @@ const SettingsButton = ({ onClick, rotation, color }: SettingsButtonProps) => {
|
||||
>
|
||||
<Cog
|
||||
size="5vmin"
|
||||
data-contrast={iconColor}
|
||||
data-contrast={iconTheme}
|
||||
className="data-[contrast=dark]:text-icons-dark data-[contrast=light]:text-icons-light"
|
||||
/>
|
||||
</SettingsButtonTwc>
|
||||
@@ -215,11 +206,9 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
||||
!playing &&
|
||||
settings.showStartingPlayer &&
|
||||
player.isStartingPlayer && <StartingPlayerCard player={player} />}
|
||||
|
||||
{player.hasLost && (
|
||||
<PlayerLostWrapper $rotation={player.settings.rotation} />
|
||||
)}
|
||||
|
||||
<CommanderDamageBar
|
||||
opponents={opponents}
|
||||
player={player}
|
||||
@@ -233,7 +222,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
||||
setShowPlayerMenu(!showPlayerMenu);
|
||||
}}
|
||||
rotation={player.settings.rotation}
|
||||
color={player.color}
|
||||
iconTheme={player.iconTheme}
|
||||
/>
|
||||
)}
|
||||
{playerCanLose(player) && (
|
||||
@@ -250,7 +239,6 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
|
||||
handleLifeChange={handleLifeChange}
|
||||
/>
|
||||
<ExtraCountersBar player={player} />
|
||||
|
||||
<PlayerMenu
|
||||
isShown={showPlayerMenu}
|
||||
player={player}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Experience,
|
||||
FullscreenOff,
|
||||
FullscreenOn,
|
||||
NameTag,
|
||||
PartnerTax,
|
||||
Poison,
|
||||
ResetGame,
|
||||
@@ -19,6 +20,7 @@ import { Player, Rotation } from '../../Types/Player';
|
||||
import { PreStartMode } from '../../Types/Settings';
|
||||
import { RotationDivProps } from '../Buttons/CommanderDamage';
|
||||
import { IconCheckbox } from '../Misc/IconCheckbox';
|
||||
import { checkContrast } from '../../Utils/checkContrast';
|
||||
|
||||
const PlayerMenuWrapper = twc.div`
|
||||
flex
|
||||
@@ -120,8 +122,16 @@ const PlayerMenu = ({
|
||||
const { updatePlayer, resetCurrentGame, players } = usePlayers();
|
||||
|
||||
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedPlayer = { ...player, color: event.target.value };
|
||||
updatePlayer(updatedPlayer);
|
||||
const iconTheme =
|
||||
checkContrast(event.target.value, '#00000080') === 'Fail'
|
||||
? 'light'
|
||||
: 'dark';
|
||||
|
||||
updatePlayer({
|
||||
...player,
|
||||
color: event.target.value,
|
||||
iconTheme,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSettingsChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -152,6 +162,18 @@ const PlayerMenu = ({
|
||||
setRandomizingPlayer(true);
|
||||
};
|
||||
|
||||
const handleUpdatePlayerName = () => {
|
||||
const newName = prompt('Enter your name', player.name);
|
||||
|
||||
const updatedPlayer: Player = { ...player, name: '' };
|
||||
if (!newName) {
|
||||
updatePlayer(updatedPlayer);
|
||||
return;
|
||||
}
|
||||
updatedPlayer.name = newName;
|
||||
updatePlayer(updatedPlayer);
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (fullscreen.isFullscreen) {
|
||||
fullscreen.disableFullscreen();
|
||||
@@ -396,6 +418,21 @@ const PlayerMenu = ({
|
||||
Keep Awake
|
||||
</button>
|
||||
|
||||
<button
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
fontSize: buttonFontSize,
|
||||
padding: '2px',
|
||||
}}
|
||||
className="text-primary-main"
|
||||
onClick={handleUpdatePlayerName}
|
||||
role="name_tag"
|
||||
aria-label="Name Tag"
|
||||
>
|
||||
<NameTag size={iconSize} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Player, Rotation } from '../Types/Player';
|
||||
import { InitialGameSettings, Orientation } from '../Types/Settings';
|
||||
import { checkContrast } from '../Utils/checkContrast';
|
||||
|
||||
export const presetColors = [
|
||||
'#D08182', // Muted Pink
|
||||
@@ -214,6 +215,8 @@ export const createInitialPlayers = ({
|
||||
lifeTotal: startingLifeTotal,
|
||||
index: i,
|
||||
color,
|
||||
iconTheme:
|
||||
checkContrast(color, '#00000080') === 'Fail' ? 'light' : 'dark',
|
||||
settings: {
|
||||
useCommanderDamage,
|
||||
usePartner: false,
|
||||
@@ -227,6 +230,7 @@ export const createInitialPlayers = ({
|
||||
hasLost: false,
|
||||
isStartingPlayer: false,
|
||||
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
|
||||
name: '',
|
||||
};
|
||||
|
||||
players.push(player);
|
||||
|
||||
36
src/Icons/generated/NameTag.tsx
Normal file
36
src/Icons/generated/NameTag.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { SVGProps } from 'react';
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
size?: string;
|
||||
}
|
||||
const NameTag = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={props.size || 16}
|
||||
height={props.size || 16}
|
||||
fill="none"
|
||||
viewBox="0 0 52 52"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M33.228 2H48a2 2 0 0 1 2 2v14.772a2 2 0 0 1-.586 1.414L21.721 47.879a3 3 0 0 1-4.242 0L4.12 34.52a3 3 0 0 1 0-4.242L31.814 2.586A2 2 0 0 1 33.228 2m14.105 6a3.333 3.333 0 1 1-6.666 0 3.333 3.333 0 0 1 6.666 0m-16.6 6.644 6.171 6.17 3.773-3.772-.663-.663-3.025 3.026-2.097-2.098 2.784-2.784-.663-.663-2.784 2.785-2.085-2.086 2.977-2.977-.663-.663zm-7.549 7.549.892-.892 7.22 3.025.072-.072-3.025-7.22.892-.892 6.171 6.171-.699.7-4.689-4.69-.06.06 2.76 6.618-.675.675-6.617-2.76-.06.06 4.689 4.689-.7.699zm.69 11.652-.783.783-3.905-8.437.771-.771 8.437 3.905-.783.783-2.368-1.127-2.496 2.496zm.47-5.291-2.024 2.024-1.796-3.772.048-.048zm-2.218 7.04-6.171-6.172-.735.735 4.857 4.858-.06.06-8.232-1.483-.724.724 6.172 6.17.747-.746-4.845-4.846.06-.06 8.208 1.482z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
NameTag.propTypes = {
|
||||
title: PropTypes.string,
|
||||
};
|
||||
export default NameTag;
|
||||
@@ -10,6 +10,7 @@ export { default as FullscreenOn } from './FullscreenOn';
|
||||
export { default as Info } from './Info';
|
||||
export { default as LittleGuy } from './LittleGuy';
|
||||
export { default as Logo } from './Logo';
|
||||
export { default as NameTag } from './NameTag';
|
||||
export { default as PartnerTax } from './PartnerTax';
|
||||
export { default as Poison } from './Poison';
|
||||
export { default as ResetGame } from './ResetGame';
|
||||
|
||||
3
src/Icons/svgs/NameTag.svg
Normal file
3
src/Icons/svgs/NameTag.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2284 2H48C49.1046 2 50 2.89543 50 4V18.7716C50 19.302 49.7893 19.8107 49.4142 20.1858L21.7213 47.8787C20.5497 49.0503 18.6502 49.0502 17.4787 47.8787L4.12132 34.5213C2.94974 33.3497 2.94974 31.4503 4.12132 30.2787L31.8142 2.58579C32.1893 2.21071 32.698 2 33.2284 2ZM47.3333 8C47.3333 9.84095 45.8409 11.3333 44 11.3333C42.159 11.3333 40.6667 9.84095 40.6667 8C40.6667 6.15905 42.159 4.66667 44 4.66667C45.8409 4.66667 47.3333 6.15905 47.3333 8ZM30.7334 14.6437L36.9045 20.8148L40.6771 17.0422L40.0142 16.3793L36.9889 19.4046L34.8916 17.3074L37.6759 14.5232L37.013 13.8602L34.2287 16.6445L32.1436 14.5593L35.1207 11.5822L34.4577 10.9193L30.7334 14.6437ZM23.1845 22.1926L24.0764 21.3007L31.2961 24.326L31.3684 24.2537L28.3431 17.0339L29.235 16.142L35.4062 22.3131L34.7071 23.0122L30.0185 18.3236L29.9582 18.3839L32.7183 25.0009L32.0434 25.6759L25.4263 22.9158L25.366 22.976L30.0546 27.6646L29.3556 28.3637L23.1845 22.1926ZM23.8745 33.8448L23.0911 34.6282L19.1859 26.1912L19.9573 25.4198L28.3944 29.3249L27.6109 30.1084L25.2428 28.9811L22.7472 31.4767L23.8745 33.8448ZM24.3446 28.5535L22.3196 30.5785L20.5238 26.8059L20.572 26.7577L24.3446 28.5535ZM22.1261 35.5932L15.9549 29.4221L15.2197 30.1574L20.0771 35.0147L20.0168 35.075L11.7846 33.5924L11.0614 34.3156L17.2326 40.4867L17.9798 39.7395L13.1346 34.8942L13.1948 34.8339L21.4029 36.3164L22.1261 35.5932Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -2,12 +2,14 @@ export type Player = {
|
||||
lifeTotal: number;
|
||||
index: number;
|
||||
color: string;
|
||||
iconTheme: 'light' | 'dark';
|
||||
settings: PlayerSettings;
|
||||
commanderDamage: CommanderDamage[];
|
||||
extraCounters: ExtraCounter[];
|
||||
isStartingPlayer: boolean;
|
||||
hasLost: boolean;
|
||||
isSide: boolean;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type PlayerSettings = {
|
||||
|
||||
Reference in New Issue
Block a user