mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
feat: nominal deltas
This commit is contained in:
1 parent
cf419a9ebb
commit
1b88773e28
4 files changed
+90
-17
No files matched your search
@@ -8,7 +8,10 @@ import { Dialog, Flex, IconButton } from '@radix-ui/themes';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TankDefinition } from '../../../../core/blitzkrieg/tankDefinitions';
|
import { TankDefinition } from '../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import { tankToDuelMember } from '../../../../core/blitzkrieg/tankToDuelMember';
|
import { tankToDuelMember } from '../../../../core/blitzkrieg/tankToDuelMember';
|
||||||
import mutateCompare, { CompareMember } from '../../../../stores/compare';
|
import {
|
||||||
|
CompareMember,
|
||||||
|
mutateCompareTemporary,
|
||||||
|
} from '../../../../stores/compare';
|
||||||
import { TankSearch } from '../../tankopedia/components/TankSearch';
|
import { TankSearch } from '../../tankopedia/components/TankSearch';
|
||||||
|
|
||||||
interface TankControlProps {
|
interface TankControlProps {
|
||||||
@@ -26,7 +29,7 @@ export function TankControl({ index, tank, members }: TankControlProps) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
const item = draft.members[index];
|
const item = draft.members[index];
|
||||||
draft.members.splice(index, 1);
|
draft.members.splice(index, 1);
|
||||||
draft.members.splice(index - 1, 0, item);
|
draft.members.splice(index - 1, 0, item);
|
||||||
@@ -39,7 +42,7 @@ export function TankControl({ index, tank, members }: TankControlProps) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
draft.members.splice(index, 1);
|
draft.members.splice(index, 1);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -67,7 +70,7 @@ export function TankControl({ index, tank, members }: TankControlProps) {
|
|||||||
<TankSearch
|
<TankSearch
|
||||||
compact
|
compact
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
draft.members[index] = {
|
draft.members[index] = {
|
||||||
...tankToDuelMember(tank),
|
...tankToDuelMember(tank),
|
||||||
crewSkills: {},
|
crewSkills: {},
|
||||||
@@ -84,7 +87,7 @@ export function TankControl({ index, tank, members }: TankControlProps) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
const item = draft.members[index];
|
const item = draft.members[index];
|
||||||
draft.members.splice(index, 1);
|
draft.members.splice(index, 1);
|
||||||
draft.members.splice(index + 1, 0, item);
|
draft.members.splice(index + 1, 0, item);
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ import { tankDefinitions } from '../../../core/blitzkrieg/tankDefinitions';
|
|||||||
import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
|
import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
|
||||||
import { tankToCompareMember } from '../../../core/blitzkrieg/tankToCompareMember';
|
import { tankToCompareMember } from '../../../core/blitzkrieg/tankToCompareMember';
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
import mutateCompare, { useCompare } from '../../../stores/compare';
|
import {
|
||||||
|
DeltaMode,
|
||||||
|
mutateComparePersistent,
|
||||||
|
mutateCompareTemporary,
|
||||||
|
useComparePersistent,
|
||||||
|
useCompareTemporary,
|
||||||
|
} from '../../../stores/compare';
|
||||||
import { TankSearch } from '../tankopedia/components/TankSearch';
|
import { TankSearch } from '../tankopedia/components/TankSearch';
|
||||||
import { TankControl } from './components/TankControl';
|
import { TankControl } from './components/TankControl';
|
||||||
|
|
||||||
@@ -32,11 +38,12 @@ export default function Page() {
|
|||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const awaitedSkillDefinitions = use(skillDefinitions);
|
const awaitedSkillDefinitions = use(skillDefinitions);
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const members = useCompare((state) => state.members);
|
const members = useCompareTemporary((state) => state.members);
|
||||||
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
|
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||||
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||||
|
const deltaMode = useComparePersistent((state) => state.deltaMode);
|
||||||
const stats = useMemo(
|
const stats = useMemo(
|
||||||
() =>
|
() =>
|
||||||
members.map((item) =>
|
members.map((item) =>
|
||||||
@@ -84,7 +91,7 @@ export default function Page() {
|
|||||||
const tanksParam = searchParams.get('tanks');
|
const tanksParam = searchParams.get('tanks');
|
||||||
|
|
||||||
if (tanksParam) {
|
if (tanksParam) {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
draft.members = tanksParam
|
draft.members = tanksParam
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(Number)
|
.map(Number)
|
||||||
@@ -104,6 +111,7 @@ export default function Page() {
|
|||||||
display,
|
display,
|
||||||
deltaType = 'higherIsBetter',
|
deltaType = 'higherIsBetter',
|
||||||
decimals,
|
decimals,
|
||||||
|
deltaNominalDisplay,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
value:
|
value:
|
||||||
@@ -116,6 +124,7 @@ export default function Page() {
|
|||||||
) => number | string | undefined;
|
) => number | string | undefined;
|
||||||
deltaType?: 'higherIsBetter' | 'lowerIsBetter';
|
deltaType?: 'higherIsBetter' | 'lowerIsBetter';
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
|
deltaNominalDisplay?: (delta: number) => string | number;
|
||||||
}) {
|
}) {
|
||||||
const values = stats.map((stat) =>
|
const values = stats.map((stat) =>
|
||||||
typeof value === 'function' ? value(stat) : (stat[value] as number),
|
typeof value === 'function' ? value(stat) : (stat[value] as number),
|
||||||
@@ -136,6 +145,7 @@ export default function Page() {
|
|||||||
</Table.RowHeaderCell>
|
</Table.RowHeaderCell>
|
||||||
|
|
||||||
{values.map((value, index) => {
|
{values.map((value, index) => {
|
||||||
|
const delta = (value ?? 0) - (values[0] ?? 0);
|
||||||
const deltaPercentage = (value ?? 1) / (values[0] ?? 1) - 1;
|
const deltaPercentage = (value ?? 1) / (values[0] ?? 1) - 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),
|
||||||
@@ -163,6 +173,7 @@ export default function Page() {
|
|||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
|
gap="2"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -179,6 +190,24 @@ export default function Page() {
|
|||||||
? value
|
? value
|
||||||
: value?.toFixed(decimals)}
|
: value?.toFixed(decimals)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{delta !== 0 && (
|
||||||
|
<>
|
||||||
|
{deltaMode === 'nominal' && (
|
||||||
|
<Text color="gray">
|
||||||
|
(
|
||||||
|
{`${delta > 0 ? '+' : ''}${
|
||||||
|
deltaNominalDisplay
|
||||||
|
? deltaNominalDisplay(delta)
|
||||||
|
: decimals === undefined
|
||||||
|
? delta
|
||||||
|
: delta.toFixed(decimals)
|
||||||
|
}`}
|
||||||
|
)
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
);
|
);
|
||||||
@@ -208,12 +237,21 @@ export default function Page() {
|
|||||||
<Flex justify="center" gap="6" align="center">
|
<Flex justify="center" gap="6" align="center">
|
||||||
<Flex justify="center" gap="2" align="center">
|
<Flex justify="center" gap="2" align="center">
|
||||||
Delta:{' '}
|
Delta:{' '}
|
||||||
<SegmentedControl.Root defaultValue="none">
|
<SegmentedControl.Root
|
||||||
<SegmentedControl.Item value="none">None</SegmentedControl.Item>
|
value={deltaMode}
|
||||||
<SegmentedControl.Item value="percentage">
|
onValueChange={(value) => {
|
||||||
|
mutateComparePersistent((draft) => {
|
||||||
|
draft.deltaMode = value as DeltaMode;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SegmentedControl.Item value={'none' satisfies DeltaMode}>
|
||||||
|
None
|
||||||
|
</SegmentedControl.Item>
|
||||||
|
<SegmentedControl.Item value={'percentage' satisfies DeltaMode}>
|
||||||
Percentage
|
Percentage
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value="nominal">
|
<SegmentedControl.Item value={'nominal' satisfies DeltaMode}>
|
||||||
Nominal
|
Nominal
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
</SegmentedControl.Root>
|
</SegmentedControl.Root>
|
||||||
@@ -240,7 +278,7 @@ export default function Page() {
|
|||||||
<TankSearch
|
<TankSearch
|
||||||
compact
|
compact
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
mutateCompare((draft) => {
|
mutateCompareTemporary((draft) => {
|
||||||
draft.members.push(
|
draft.members.push(
|
||||||
tankToCompareMember(tank, awaitedSkillDefinitions),
|
tankToCompareMember(tank, awaitedSkillDefinitions),
|
||||||
);
|
);
|
||||||
@@ -311,6 +349,7 @@ export default function Page() {
|
|||||||
.shellReloads!.map((reload) => reload.toFixed(2))
|
.shellReloads!.map((reload) => reload.toFixed(2))
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
|
deltaNominalDisplay={(delta) => delta.toFixed(2)}
|
||||||
/>
|
/>
|
||||||
{hasNonRegularGun && (
|
{hasNonRegularGun && (
|
||||||
<Row
|
<Row
|
||||||
@@ -401,17 +440,20 @@ export default function Page() {
|
|||||||
value="fireChance"
|
value="fireChance"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
display={(stats) => (stats.fireChance * 100).toFixed(0)}
|
display={(stats) => (stats.fireChance * 100).toFixed(0)}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row name="View range" value="viewRange" decimals={0} />
|
<Row name="View range" value="viewRange" decimals={0} />
|
||||||
<Row
|
<Row
|
||||||
name="Camouflage still"
|
name="Camouflage still"
|
||||||
value="camouflageStill"
|
value="camouflageStill"
|
||||||
display={(stats) => (stats.camouflageStill * 100).toFixed(0)}
|
display={(stats) => (stats.camouflageStill * 100).toFixed(0)}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
name="Camouflage moving"
|
name="Camouflage moving"
|
||||||
value="camouflageMoving"
|
value="camouflageMoving"
|
||||||
display={(stats) => (stats.camouflageMoving * 100).toFixed(0)}
|
display={(stats) => (stats.camouflageMoving * 100).toFixed(0)}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
name="Camouflage shooting still"
|
name="Camouflage shooting still"
|
||||||
@@ -419,6 +461,7 @@ export default function Page() {
|
|||||||
display={(stats) =>
|
display={(stats) =>
|
||||||
(stats.camouflageShootingStill * 100).toFixed(0)
|
(stats.camouflageShootingStill * 100).toFixed(0)
|
||||||
}
|
}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
name="Camouflage shooting moving"
|
name="Camouflage shooting moving"
|
||||||
@@ -426,6 +469,7 @@ export default function Page() {
|
|||||||
display={(stats) =>
|
display={(stats) =>
|
||||||
(stats.camouflageShootingMoving * 100).toFixed(0)
|
(stats.camouflageShootingMoving * 100).toFixed(0)
|
||||||
}
|
}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
name="Camouflage caught on fire"
|
name="Camouflage caught on fire"
|
||||||
@@ -433,6 +477,7 @@ export default function Page() {
|
|||||||
display={(stats) =>
|
display={(stats) =>
|
||||||
(stats.camouflageCaughtOnFire * 100).toFixed(0)
|
(stats.camouflageCaughtOnFire * 100).toFixed(0)
|
||||||
}
|
}
|
||||||
|
deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
name="Width"
|
name="Width"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export function EquipmentMatrixCompact() {}
|
||||||
+28
-4
@@ -1,19 +1,43 @@
|
|||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
|
import { merge } from 'lodash';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { DuelMember } from './duel';
|
import { DuelMember } from './duel';
|
||||||
|
|
||||||
export interface CompareMember extends DuelMember {
|
export interface CompareMember extends DuelMember {
|
||||||
crewSkills: Record<string, number>;
|
crewSkills: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Compare {
|
export interface CompareTemporary {
|
||||||
members: CompareMember[];
|
members: CompareMember[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCompare = create<Compare>()(() => ({
|
export type DeltaMode = 'none' | 'percentage' | 'nominal';
|
||||||
|
export interface ComparePersistent {
|
||||||
|
deltaMode: DeltaMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCompareTemporary = create<CompareTemporary>()(() => ({
|
||||||
members: [],
|
members: [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function mutateCompare(recipe: (draft: Compare) => void) {
|
export const useComparePersistent = create<ComparePersistent>()(
|
||||||
useCompare.setState(produce(recipe));
|
persist(
|
||||||
|
subscribeWithSelector<ComparePersistent>(() => ({
|
||||||
|
deltaMode: 'none',
|
||||||
|
})),
|
||||||
|
{ name: 'compare', merge: (a, b) => merge(b, a) },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function mutateCompareTemporary(
|
||||||
|
recipe: (draft: CompareTemporary) => void,
|
||||||
|
) {
|
||||||
|
useCompareTemporary.setState(produce(recipe));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mutateComparePersistent(
|
||||||
|
recipe: (draft: ComparePersistent) => void,
|
||||||
|
) {
|
||||||
|
useComparePersistent.setState(produce(recipe));
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user