feat: upgrade CompareEphemeral

This commit is contained in:
TrèsAbhi committed 2025-09-01 23:05:22 -05:00
1 parent ef2a47e756
commit fe4afdcbf0
8 files changed
+202 -212

No files matched your search

@@ -1,11 +1,11 @@
import { asset } from '@blitzkit/core';
import { Flex, Heading, IconButton, Table } from '@radix-ui/themes';
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { CompareRow } from './CompareRow';
import { CompareSectionTitle } from './CompareSectionTitle';
import { asset } from "@blitzkit/core";
import { Flex, Heading, IconButton, Table } from "@radix-ui/themes";
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
import { CompareRow } from "./CompareRow";
import { CompareSectionTitle } from "./CompareSectionTitle";
interface BodyProps {
stats: TankCharacteristics[];
@@ -14,10 +14,9 @@ interface BodyProps {
export function Body({ stats }: BodyProps) {
const members = CompareEphemeral.use((state) => state.members);
const hasNonRegularGun = members.some(
({ gun }) => gun.gun_type!.$case !== 'regular',
({ gun }) => gun.gun_type!.$case !== "regular"
);
const { unwrap, strings } = useLocale();
const mutateCompareEphemeral = CompareEphemeral.useMutation();
return (
<>
@@ -35,7 +34,7 @@ export function Body({ stats }: BodyProps) {
<Flex>
{gun.shells.map((thisShell, shellIndex) => (
<IconButton
color={thisShell.id === shell.id ? undefined : 'gray'}
color={thisShell.id === shell.id ? undefined : "gray"}
variant="soft"
key={thisShell.id}
style={{
@@ -49,7 +48,7 @@ export function Body({ stats }: BodyProps) {
marginLeft: shellIndex === 0 ? 0 : -1,
}}
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].shell = thisShell;
});
}}
@@ -88,7 +87,7 @@ export function Body({ stats }: BodyProps) {
}
display={(stats) =>
stats.shellReload?.toFixed(2) ??
stats.shellReloads!.map((reload) => reload.toFixed(2)).join(', ')
stats.shellReloads!.map((reload) => reload.toFixed(2)).join(", ")
}
deltaNominalDisplay={(delta) => delta.toFixed(2)}
/>
@@ -2,21 +2,21 @@ import {
CaretLeftIcon,
CaretRightIcon,
CaretSortIcon,
} from '@radix-ui/react-icons';
import { Flex, IconButton, Table, Text } from '@radix-ui/themes';
import { useRef } from 'react';
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import { ComparePersistent } from '../../stores/comparePersistent';
import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
} from "@radix-ui/react-icons";
import { Flex, IconButton, Table, Text } from "@radix-ui/themes";
import { useRef } from "react";
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
import { Var } from "../../core/radix/var";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { ComparePersistent } from "../../stores/comparePersistent";
import { StickyRowHeaderCell } from "../StickyRowHeaderCell";
type CompareRowProps = {
display?: (
member: Awaited<TankCharacteristics>,
member: Awaited<TankCharacteristics>
) => number | string | undefined;
deltaType?: 'higherIsBetter' | 'lowerIsBetter';
deltaType?: "higherIsBetter" | "lowerIsBetter";
decimals?: number;
deltaNominalDisplay?: (delta: number) => string | number;
indent?: boolean;
@@ -33,7 +33,7 @@ type CompareRowProps = {
export function CompareRow({
display,
deltaType = 'higherIsBetter',
deltaType = "higherIsBetter",
decimals,
deltaNominalDisplay,
indent,
@@ -41,17 +41,16 @@ export function CompareRow({
...props
}: CompareRowProps) {
const { strings } = useLocale();
const mutateCompareEphemeral = CompareEphemeral.useMutation();
const sorting = CompareEphemeral.use((state) => state.sorting);
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
const values = stats.map((stat) =>
typeof props.value === 'function'
typeof props.value === "function"
? props.value(stat)!
: (stat[props.value] as number),
: (stat[props.value] as number)
);
const id = useRef(Math.random());
const name =
typeof props.value === 'string'
typeof props.value === "string"
? strings.website.tools.tankopedia.characteristics.values[props.value]
: props.name;
@@ -60,21 +59,21 @@ export function CompareRow({
<StickyRowHeaderCell>
<Flex
align="center"
style={{ whiteSpace: 'nowrap' }}
gap={indent ? { initial: '4', sm: '6' } : { initial: '1', sm: '2' }}
style={{ whiteSpace: "nowrap" }}
gap={indent ? { initial: "4", sm: "6" } : { initial: "1", sm: "2" }}
>
<IconButton
color={sorting?.by === id.current ? undefined : 'gray'}
color={sorting?.by === id.current ? undefined : "gray"}
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members.sort((memberA, memberB) => {
const indexA = draft.members.indexOf(memberA);
const indexB = draft.members.indexOf(memberB);
const valueA = values[indexA];
const valueB = values[indexB];
return draft.sorting?.direction === 'ascending' &&
return draft.sorting?.direction === "ascending" &&
draft.sorting.by === id.current
? valueA - valueB
: valueB - valueA;
@@ -83,26 +82,26 @@ export function CompareRow({
draft.sorting = {
by: id.current,
direction:
draft.sorting?.direction === 'ascending' &&
draft.sorting?.direction === "ascending" &&
draft.sorting.by === id.current
? 'descending'
: 'ascending',
? "descending"
: "ascending",
};
});
}}
>
{(sorting === undefined || sorting.by !== id.current) && (
<CaretSortIcon style={{ transform: 'rotate(90deg)' }} />
<CaretSortIcon style={{ transform: "rotate(90deg)" }} />
)}
{sorting?.by === id.current && (
<>
{sorting.direction === 'ascending' && <CaretRightIcon />}
{sorting.direction === 'descending' && <CaretLeftIcon />}
{sorting.direction === "ascending" && <CaretRightIcon />}
{sorting.direction === "descending" && <CaretLeftIcon />}
</>
)}
</IconButton>
<Text size={{ initial: '1', sm: '2' }}>{name}</Text>
<Text size={{ initial: "1", sm: "2" }}>{name}</Text>
</Flex>
</StickyRowHeaderCell>
@@ -110,7 +109,7 @@ export function CompareRow({
const delta = value - values[0];
const deltaPercentage = value / values[0] - 1;
const normalizedDeltaPercentage = Math.round(
Math.min(100, Math.abs(deltaPercentage) * 2 * 100 + 25),
Math.min(100, Math.abs(deltaPercentage) * 2 * 100 + 25)
);
const resolvedDisplayValue = display
? display(stats[index])
@@ -129,12 +128,12 @@ export function CompareRow({
value === values[0]
? undefined
: (
deltaType === 'higherIsBetter'
deltaType === "higherIsBetter"
? value > values[0]
: value < values[0]
)
? `color-mix(in srgb, ${Var('green-7')} ${normalizedDeltaPercentage}%, ${Var('green-3')})`
: `color-mix(in srgb, ${Var('red-7')} ${normalizedDeltaPercentage}%, ${Var('red-3')})`,
? `color-mix(in srgb, ${Var("green-7")} ${normalizedDeltaPercentage}%, ${Var("green-3")})`
: `color-mix(in srgb, ${Var("red-7")} ${normalizedDeltaPercentage}%, ${Var("red-3")})`,
}}
>
<Flex
@@ -142,11 +141,11 @@ export function CompareRow({
justify="center"
gap="2"
style={{
width: '100%',
height: '100%',
width: "100%",
height: "100%",
}}
>
<Text style={{ textAlign: 'center' }} wrap="nowrap">
<Text style={{ textAlign: "center" }} wrap="nowrap">
{resolvedDisplayValue}
</Text>
@@ -154,10 +153,10 @@ export function CompareRow({
resolvedDisplayValue !== undefined &&
values[0] !== undefined && (
<>
{deltaMode === 'absolute' && (
{deltaMode === "absolute" && (
<Text color="gray">
(
{`${delta > 0 ? '+' : ''}${
{`${delta > 0 ? "+" : ""}${
deltaNominalDisplay
? deltaNominalDisplay(delta)
: decimals === undefined
@@ -168,11 +167,11 @@ export function CompareRow({
</Text>
)}
{deltaMode === 'percentage' && (
{deltaMode === "percentage" && (
<Text color="gray">
(
{`${deltaPercentage > 0 ? '+' : ''}${Math.round(
deltaPercentage * 100,
{`${deltaPercentage > 0 ? "+" : ""}${Math.round(
deltaPercentage * 100
)}%`}
)
</Text>
@@ -1,28 +1,28 @@
import { asset, availableProvisions, permanentSkills } from '@blitzkit/core';
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
import { ComponentPlaceholderIcon } from '@radix-ui/react-icons';
import { Button, Flex, IconButton, Popover, Table } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { awaitableSkillDefinitions } from '../../core/awaitables/skillDefinitions';
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import type { EquipmentMatrix } from '../../stores/duel';
import { BlitzkitButtonGrayIcon } from '../BlitzkitButtonGrayIcon';
import { ConsumablesManager } from '../ConsumablesManager';
import { CrewSkillManager } from '../CrewSkillManager';
import { EquipmentManager } from '../EquipmentManager';
import { ModuleManager } from '../ModuleManager';
import { ProvisionsManager } from '../ProvisionsManager';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { StickyTableRoot } from '../StickyTableRoot';
import { Body } from './Body';
import { InsertionMarker } from './IntersectionMarker';
import { TankCard } from './TankCard';
import { asset, availableProvisions, permanentSkills } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
import { Button, Flex, IconButton, Popover, Table } from "@radix-ui/themes";
import { times } from "lodash-es";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions";
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
import { awaitableSkillDefinitions } from "../../core/awaitables/skillDefinitions";
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
import { Var } from "../../core/radix/var";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import type { EquipmentMatrix } from "../../stores/duel";
import { BlitzkitButtonGrayIcon } from "../BlitzkitButtonGrayIcon";
import { ConsumablesManager } from "../ConsumablesManager";
import { CrewSkillManager } from "../CrewSkillManager";
import { EquipmentManager } from "../EquipmentManager";
import { ModuleManager } from "../ModuleManager";
import { ProvisionsManager } from "../ProvisionsManager";
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
import { StickyTableRoot } from "../StickyTableRoot";
import { Body } from "./Body";
import { InsertionMarker } from "./IntersectionMarker";
import { TankCard } from "./TankCard";
interface CompareTableProps {
stats: TankCharacteristics[];
@@ -43,7 +43,6 @@ const [
export function CompareTable({ stats }: CompareTableProps) {
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
const members = CompareEphemeral.use((state) => state.members);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
const { unwrap, strings } = useLocale();
return (
@@ -51,20 +50,20 @@ export function CompareTable({ stats }: CompareTableProps) {
size="1"
variant="surface"
style={{
position: 'absolute',
maxWidth: '100%',
maxHeight: '100%',
height: '100%',
position: "absolute",
maxWidth: "100%",
maxHeight: "100%",
height: "100%",
// height: 'calc(100vh - 128px)'
}}
>
<Table.Header>
<Table.Row>
<StickyColumnHeaderCell width="0" style={{ height: '100%' }}>
<StickyColumnHeaderCell width="0" style={{ height: "100%" }}>
<Flex
style={{
width: '100%',
height: '100%',
width: "100%",
height: "100%",
}}
align="center"
justify="center"
@@ -82,11 +81,11 @@ export function CompareTable({ stats }: CompareTableProps) {
<Table.Body>
<Table.Row>
<Table.Cell style={{ height: '100%' }}>
<Table.Cell style={{ height: "100%" }}>
<Flex
style={{
width: '100%',
height: '100%',
width: "100%",
height: "100%",
}}
align="center"
justify="center"
@@ -107,15 +106,15 @@ export function CompareTable({ stats }: CompareTableProps) {
borderRadius: 2,
backgroundColor:
crewSkills[skill] === 0
? Var('gray-11')
? Var("gray-11")
: permanentSkills.includes(skill)
? Var('crimson-11')
: Var('amber-11'),
? Var("crimson-11")
: Var("amber-11"),
}}
/>
))}
</Flex>
),
)
)}
</Flex>
</Button>
@@ -125,7 +124,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<CrewSkillManager
skillLevels={crewSkills}
onChange={(skills) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.crewSkills = skills;
});
}}
@@ -135,7 +134,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
Object.keys(draft.crewSkills).forEach((skill) => {
draft.crewSkills[skill] = 0;
});
@@ -147,7 +146,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
Object.keys(draft.crewSkills).forEach((skill) => {
draft.crewSkills[skill] = 7;
});
@@ -177,19 +176,19 @@ export function CompareTable({ stats }: CompareTableProps) {
consumables,
camouflage,
},
index,
index
) => {
const equipmentPreset =
equipmentDefinitions.presets[tank.equipment_preset];
const provisionsList = availableProvisions(
tank,
gun,
provisionDefinitions,
provisionDefinitions
);
const consumablesList = Object.values(
consumableDefinitions.consumables,
consumableDefinitions.consumables
).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, tank, gun),
checkConsumableProvisionInclusivity(consumable, tank, gun)
);
return (
@@ -205,7 +204,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button variant="ghost">
<Flex align="center">
{(
['turret', 'gun', 'engine', 'chassis'] as const
["turret", "gun", "engine", "chassis"] as const
).map((module, index) => (
<img
key={module}
@@ -215,7 +214,7 @@ export function CompareTable({ stats }: CompareTableProps) {
src={asset(`icons/modules/${module}.webp`)}
style={{
marginLeft: index > 0 ? -8 : 0,
objectFit: 'contain',
objectFit: "contain",
}}
/>
))}
@@ -232,7 +231,7 @@ export function CompareTable({ stats }: CompareTableProps) {
engine={engine}
track={track}
onChange={(modules) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index] = {
...draft.members[index],
...modules,
@@ -245,7 +244,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
const member = draft.members[index];
member.turret = member.tank.turrets[0];
@@ -261,7 +260,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
const member = draft.members[index];
member.turret = member.tank.turrets.at(-1)!;
@@ -288,9 +287,9 @@ export function CompareTable({ stats }: CompareTableProps) {
variant="ghost"
radius="large"
style={{
height: '100%',
height: "100%",
width: 12,
position: 'relative',
position: "relative",
}}
>
<Flex direction="column" justify="center">
@@ -298,14 +297,14 @@ export function CompareTable({ stats }: CompareTableProps) {
<img
alt={unwrap(
provisionDefinitions.provisions[provision]
.name,
.name
)}
key={provision}
src={asset(
`/icons/provisions/${provision}.webp`,
`/icons/provisions/${provision}.webp`
)}
style={{
left: '50%',
left: "50%",
/**
* max = 100% - 16px - 4px
* min = 4px
@@ -320,11 +319,11 @@ export function CompareTable({ stats }: CompareTableProps) {
? 0.5
: index / (provisions.length - 1)
} * (100% - 24px))`,
transform: 'translateX(-50%)',
position: 'absolute',
transform: "translateX(-50%)",
position: "absolute",
width: 16,
height: 16,
objectFit: 'contain',
objectFit: "contain",
}}
/>
))}
@@ -339,12 +338,12 @@ export function CompareTable({ stats }: CompareTableProps) {
<Popover.Content>
<ProvisionsManager
provisions={provisionsList.map(
(provision) => provision.id,
(provision) => provision.id
)}
selected={provisions}
disabled={tank.max_provisions === provisions.length}
onChange={(provisions) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].provisions = provisions;
});
}}
@@ -353,7 +352,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].provisions = [];
});
}}
@@ -394,8 +393,8 @@ export function CompareTable({ stats }: CompareTableProps) {
height: 8,
backgroundColor:
equipment === -1
? Var('crimson-11')
: Var('gray-11'),
? Var("crimson-11")
: Var("gray-11"),
borderRadius: 2,
}}
/>
@@ -405,8 +404,8 @@ export function CompareTable({ stats }: CompareTableProps) {
height: 8,
backgroundColor:
equipment === 1
? Var('crimson-11')
: Var('gray-11'),
? Var("crimson-11")
: Var("gray-11"),
borderRadius: 2,
}}
/>
@@ -424,7 +423,7 @@ export function CompareTable({ stats }: CompareTableProps) {
matrix={equipmentMatrix}
preset={equipmentPreset}
onChange={(matrix) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].equipmentMatrix = matrix;
draft.sorting = undefined;
});
@@ -436,10 +435,10 @@ export function CompareTable({ stats }: CompareTableProps) {
variant="ghost"
color="red"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].equipmentMatrix = times(
3,
() => times(3, () => 0),
() => times(3, () => 0)
) as EquipmentMatrix;
draft.sorting = undefined;
});
@@ -453,7 +452,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members.forEach((member) => {
member.equipmentMatrix =
draft.members[index].equipmentMatrix;
@@ -477,9 +476,9 @@ export function CompareTable({ stats }: CompareTableProps) {
variant="ghost"
radius="large"
style={{
height: '100%',
height: "100%",
width: 12,
position: 'relative',
position: "relative",
}}
>
<Flex direction="column" justify="center">
@@ -488,14 +487,14 @@ export function CompareTable({ stats }: CompareTableProps) {
alt={unwrap(
consumableDefinitions.consumables[
consumable
].name,
].name
)}
key={consumable}
src={asset(
`/icons/consumables/${consumable}.webp`,
`/icons/consumables/${consumable}.webp`
)}
style={{
left: '50%',
left: "50%",
/**
* max = 100% - 16px - 4px
* min = 4px
@@ -510,11 +509,11 @@ export function CompareTable({ stats }: CompareTableProps) {
? 0.5
: index / (consumables.length - 1)
} * (100% - 24px))`,
transform: 'translateX(-50%)',
position: 'absolute',
transform: "translateX(-50%)",
position: "absolute",
width: 16,
height: 16,
objectFit: 'contain',
objectFit: "contain",
}}
/>
))}
@@ -531,7 +530,7 @@ export function CompareTable({ stats }: CompareTableProps) {
consumables={consumablesList}
selected={consumables}
onConsumablesChange={(consumables) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].consumables = consumables;
});
}}
@@ -544,7 +543,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Button
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].consumables = [];
});
}}
@@ -562,18 +561,18 @@ export function CompareTable({ stats }: CompareTableProps) {
{!tank.fixed_camouflage && (
<IconButton
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index].camouflage = !camouflage;
});
}}
variant={camouflage ? 'solid' : 'outline'}
variant={camouflage ? "solid" : "outline"}
>
<img
src={asset('icons/camo.webp')}
src={asset("icons/camo.webp")}
style={{
width: '1em',
height: '1em',
transform: 'scale(2) translate(15%, 15%)',
width: "1em",
height: "1em",
transform: "scale(2) translate(15%, 15%)",
}}
/>
</IconButton>
@@ -581,7 +580,7 @@ export function CompareTable({ stats }: CompareTableProps) {
</Flex>
</Table.Cell>
);
},
}
)}
</Table.Row>
</Table.Body>
@@ -22,7 +22,6 @@ export function Controls({
onAddTankDialogOpenChange,
}: ControlsProps) {
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
const { strings } = useLocale();
return (
@@ -55,7 +54,7 @@ export function Controls({
<TankSearch
compact
onSelect={(tank) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members.push(
tankToCompareMember(tank, provisionDefinitions)
);
@@ -64,7 +63,7 @@ export function Controls({
onAddTankDialogOpenChange(false);
}}
onSelectAll={(tanks) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members.push(
...tanks.map((tank) => {
return tankToCompareMember(tank, provisionDefinitions);
@@ -84,7 +83,7 @@ export function Controls({
variant="soft"
color="red"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members = [];
draft.sorting = undefined;
});
@@ -1,13 +1,13 @@
import { type TankDefinition, tankIcon } from '@blitzkit/core';
import { Flex, Text } from '@radix-ui/themes';
import { useEffect, useRef } from 'react';
import { Vector2 } from 'three';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { InsertionMarker } from './IntersectionMarker';
import { insertionMarkers } from './IntersectionMarker/constants';
import { TankControl } from './TankControl';
import { type TankDefinition, tankIcon } from "@blitzkit/core";
import { Flex, Text } from "@radix-ui/themes";
import { useEffect, useRef } from "react";
import { Vector2 } from "three";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
import { InsertionMarker } from "./IntersectionMarker";
import { insertionMarkers } from "./IntersectionMarker/constants";
import { TankControl } from "./TankControl";
interface TankCardProps {
index: number;
@@ -16,7 +16,6 @@ interface TankCardProps {
export function TankCard({ index, tank }: TankCardProps) {
const draggable = useRef<HTMLDivElement>(null);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
const { unwrap } = useLocale();
useEffect(() => {
@@ -34,8 +33,8 @@ export function TankCard({ index, tank }: TankCardProps) {
delta.set(0, 0);
current.copy(initial.set(event.clientX, event.clientY));
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
@@ -43,7 +42,7 @@ export function TankCard({ index, tank }: TankCardProps) {
current.set(event.clientX, event.clientY);
delta.copy(current).sub(initial);
insertionMarkers.forEach(({ element }) => {
element.style.opacity = '0';
element.style.opacity = "0";
});
const distances = insertionMarkers.map((insertionMarker) => {
@@ -56,34 +55,34 @@ export function TankCard({ index, tank }: TankCardProps) {
};
});
const closest = distances.reduce((a, b) =>
a.distance < b.distance ? a : b,
a.distance < b.distance ? a : b
);
dropIndex = closest.index;
closest.element.style.opacity = '1';
draggable.current!.style.position = 'fixed';
draggable.current!.style.cursor = 'grabbing';
draggable.current!.style.zIndex = '1';
closest.element.style.opacity = "1";
draggable.current!.style.position = "fixed";
draggable.current!.style.cursor = "grabbing";
draggable.current!.style.zIndex = "1";
draggable.current!.style.left = `${initialRect.left + delta.x}px`;
draggable.current!.style.top = `${initialRect.top + delta.y}px`;
}
function handlePointerUp(event: PointerEvent) {
event.preventDefault();
draggable.current!.style.position = 'static';
draggable.current!.style.zIndex = 'unset';
draggable.current!.style.cursor = 'grab';
draggable.current!.style.position = "static";
draggable.current!.style.zIndex = "unset";
draggable.current!.style.cursor = "grab";
insertionMarkers.forEach(({ element }) => {
element.style.opacity = '0';
element.style.opacity = "0";
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
if (dropIndex === -1) return;
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
const fromIndex = index;
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
@@ -95,10 +94,10 @@ export function TankCard({ index, tank }: TankCardProps) {
dropIndex = -1;
}
draggable.current?.addEventListener('pointerdown', handlePointerDown);
draggable.current?.addEventListener("pointerdown", handlePointerDown);
return () => {
draggable.current?.removeEventListener('pointerdown', handlePointerDown);
draggable.current?.removeEventListener("pointerdown", handlePointerDown);
};
}, []);
@@ -114,9 +113,9 @@ export function TankCard({ index, tank }: TankCardProps) {
justify="between"
gap="2"
style={{
userSelect: 'none',
touchAction: 'none',
cursor: 'grab',
userSelect: "none",
touchAction: "none",
cursor: "grab",
}}
>
<img
@@ -126,16 +125,16 @@ export function TankCard({ index, tank }: TankCardProps) {
height={64}
draggable={false}
style={{
objectFit: 'contain',
objectFit: "contain",
}}
/>
<Text
style={{
whiteSpace: 'nowrap',
whiteSpace: "nowrap",
maxWidth: 128,
overflow: 'hidden',
textOverflow: 'ellipsis',
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{unwrap(tank.name)}
@@ -1,12 +1,12 @@
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
import { Dialog, Flex, IconButton } from '@radix-ui/themes';
import { useState } from 'react';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import { LinkI18n } from '../LinkI18n';
import { TankSearch } from '../TankSearch';
import { ExternalLinkIcon, LoopIcon, TrashIcon } from "@radix-ui/react-icons";
import { Dialog, Flex, IconButton } from "@radix-ui/themes";
import { useState } from "react";
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
import { tankToCompareMember } from "../../core/blitzkit/tankToCompareMember";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { LinkI18n } from "../LinkI18n";
import { TankSearch } from "../TankSearch";
interface TankControlProps {
index: number;
@@ -18,14 +18,13 @@ const provisionDefinitions = await awaitableProvisionDefinitions;
export function TankControl({ index, slug }: TankControlProps) {
const { locale } = useLocale();
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
return (
<Flex gap="3" justify="center" style={{ width: '100%' }}>
<Flex gap="3" justify="center" style={{ width: "100%" }}>
<IconButton
variant="ghost"
onClick={() => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members.splice(index, 1);
draft.sorting = undefined;
});
@@ -57,10 +56,10 @@ export function TankControl({ index, slug }: TankControlProps) {
<TankSearch
compact
onSelect={(tank) => {
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members[index] = tankToCompareMember(
tank,
provisionDefinitions,
provisionDefinitions
);
draft.sorting = undefined;
});
@@ -36,15 +36,13 @@ const [
]);
export function Page({ locale }: LocaleAcceptorProps) {
CompareEphemeral.useInitialization(createDefaultSkills(skillDefinitions));
return (
<LocaleProvider locale={locale}>
<App.Provider>
<TankopediaPersistent.Provider>
<CompareEphemeral.Provider
data={createDefaultSkills(skillDefinitions)}
>
<Content />
</CompareEphemeral.Provider>
<Content />
</TankopediaPersistent.Provider>
</App.Provider>
</LocaleProvider>
@@ -90,7 +88,6 @@ function Content() {
),
[members, crewSkills]
);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
useEffect(() => {
const search = new URLSearchParams(window.location.search);
@@ -100,7 +97,7 @@ function Content() {
const tanks = tanksQuery.split(",").map(Number);
mutateCompareEphemeral((draft) => {
CompareEphemeral.mutate((draft) => {
draft.members = [];
tanks.forEach((id) => {
+10 -11
View File
@@ -1,6 +1,5 @@
import { create } from 'zustand';
import { createContextualStore } from '../core/zustand/createContextualStore';
import type { DuelMember } from './duel';
import { Varuna } from "varuna";
import type { DuelMember } from "./duel";
export interface CompareMember extends DuelMember {
key: string;
@@ -10,15 +9,15 @@ export interface CompareEphemeral {
crewSkills: Record<string, number>;
members: CompareMember[];
sorting?: {
direction: 'ascending' | 'descending';
direction: "ascending" | "descending";
by: number;
};
}
export const CompareEphemeral = createContextualStore(
(crewSkills: Record<number, number>) =>
create<CompareEphemeral>()(() => ({
crewSkills,
members: [],
})),
);
export const CompareEphemeral = new Varuna<
CompareEphemeral,
Record<string, number>
>((crewSkills) => ({
crewSkills,
members: [],
}));