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