mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: start of sorting
This commit is contained in:
1 parent
479a165149
commit
38b2b6025f
8 files changed
+455
-75
No files matched your search
@@ -2,12 +2,21 @@ import { Callout, Flex } from '@radix-ui/themes';
|
||||
import { go } from 'fuzzysort';
|
||||
import { range } from 'lodash';
|
||||
import { memo, use, useMemo } from 'react';
|
||||
import { Vector2Tuple } from 'three';
|
||||
import { ExperimentIcon } from '../../../../components/ExperimentIcon';
|
||||
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
|
||||
import { modelDefinitions } from '../../../../core/blitzkit/modelDefinitions';
|
||||
import { normalizeBoundingBox } from '../../../../core/blitzkit/normalizeBoundingBox';
|
||||
import { resolveDpm } from '../../../../core/blitzkit/resolveDpm';
|
||||
import { resolveReload } from '../../../../core/blitzkit/resolveReload';
|
||||
import {
|
||||
TankDefinition,
|
||||
Tier,
|
||||
tankDefinitions,
|
||||
tankNames,
|
||||
tanksDefinitionsArray,
|
||||
} from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox';
|
||||
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
|
||||
import { SearchBar } from './SearchBar';
|
||||
import { TankCard } from './TankCard';
|
||||
@@ -15,20 +24,309 @@ import { TankCardWrapper } from './TankCardWrapper';
|
||||
import { TierCard } from './TierCard';
|
||||
|
||||
export const Results = memo(() => {
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const awaitedTankDefinitions = use(tankDefinitions);
|
||||
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
||||
const awaitedTankNames = use(tankNames);
|
||||
const testing = useTankopediaFilters((state) => state.testing);
|
||||
const search = useTankopediaFilters((state) => state.search);
|
||||
const sort = useTankopediaFilters((state) => state.sort);
|
||||
const searchedTanks = useMemo(() => {
|
||||
if (search === undefined) return undefined;
|
||||
if (search !== undefined) {
|
||||
const searchedRaw = go(search, awaitedTankNames, { key: 'combined' });
|
||||
const searchedTanks = searchedRaw.map(
|
||||
(result) => awaitedTankDefinitions[result.obj.id],
|
||||
);
|
||||
return searchedTanks;
|
||||
}
|
||||
|
||||
const searchedRaw = go(search, awaitedTankNames, { key: 'combined' });
|
||||
const searchedTanks = searchedRaw.map(
|
||||
(result) => awaitedTankDefinitions[result.obj.id],
|
||||
);
|
||||
if (sort.by !== 'meta.tier') {
|
||||
const filtered = awaitedTanksDefinitionsArray;
|
||||
let sorted: TankDefinition[];
|
||||
|
||||
return searchedTanks;
|
||||
}, [search]);
|
||||
switch (sort.by) {
|
||||
case 'meta.name':
|
||||
sorted = filtered.sort((a, b) => a.name?.localeCompare(b.name));
|
||||
break;
|
||||
|
||||
case 'survivability.health': {
|
||||
sorted = filtered.sort((a, b) => {
|
||||
const aHealth = a.health + a.turrets.at(-1)!.health;
|
||||
const bHealth = b.health + b.turrets.at(-1)!.health;
|
||||
|
||||
return aHealth - bHealth;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'survivability.viewRange':
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.turrets.at(-1)!.viewRange - b.turrets.at(-1)!.viewRange,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'survivability.camouflageStill':
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.camouflage.still - b.camouflage.still,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'survivability.camouflageMoving':
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.camouflage.moving - b.camouflage.moving,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'survivability.camouflageShooting':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.camouflageLoss -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.camouflageLoss,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'survivability.volume':
|
||||
sorted = filtered.sort((a, b) => {
|
||||
const aTankModelDefinition = awaitedModelDefinitions[a.id];
|
||||
const bTankModelDefinition = awaitedModelDefinitions[b.id];
|
||||
const aTurretModelDefinition =
|
||||
aTankModelDefinition.turrets[a.turrets.at(-1)!.id];
|
||||
const bTurretModelDefinition =
|
||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||
const aSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
aTankModelDefinition.boundingBox,
|
||||
aTurretModelDefinition.boundingBox,
|
||||
),
|
||||
);
|
||||
const bSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
bTankModelDefinition.boundingBox,
|
||||
bTurretModelDefinition.boundingBox,
|
||||
),
|
||||
);
|
||||
const aVolume = aSize[0] * aSize[1] * aSize[2];
|
||||
const bVolume = bSize[0] * bSize[1] * bSize[2];
|
||||
|
||||
return aVolume - bVolume;
|
||||
});
|
||||
break;
|
||||
|
||||
case 'survivability.length':
|
||||
sorted = filtered.sort((a, b) => {
|
||||
const aTankModelDefinition = awaitedModelDefinitions[a.id];
|
||||
const bTankModelDefinition = awaitedModelDefinitions[b.id];
|
||||
const aTurretModelDefinition =
|
||||
aTankModelDefinition.turrets[a.turrets.at(-1)!.id];
|
||||
const bTurretModelDefinition =
|
||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||
const aSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
aTankModelDefinition.boundingBox,
|
||||
aTurretModelDefinition.boundingBox,
|
||||
),
|
||||
);
|
||||
const bSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
bTankModelDefinition.boundingBox,
|
||||
bTurretModelDefinition.boundingBox,
|
||||
),
|
||||
);
|
||||
const aLength = Math.max(aSize[0], aSize[1], aSize[2]);
|
||||
const bLength = Math.max(bSize[0], bSize[1], bSize[2]);
|
||||
|
||||
return aLength - bLength;
|
||||
});
|
||||
break;
|
||||
|
||||
case 'fire.dpm':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
resolveDpm(
|
||||
a.turrets.at(-1)!.guns.at(-1)!,
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||
) -
|
||||
resolveDpm(
|
||||
b.turrets.at(-1)!.guns.at(-1)!,
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.reload':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
resolveReload(a.turrets.at(-1)!.guns.at(-1)!) -
|
||||
resolveReload(b.turrets.at(-1)!.guns.at(-1)!),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.caliber':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.standardPenetration':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
resolveNearPenetration(
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration,
|
||||
) -
|
||||
resolveNearPenetration(
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.premiumPenetration':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
resolveNearPenetration(
|
||||
(
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[1] ??
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
||||
).penetration,
|
||||
) -
|
||||
resolveNearPenetration(
|
||||
(
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[1] ??
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
||||
).penetration,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.damage':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].damage.armor -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].damage.armor,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.shellVelocity':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].speed -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].speed,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.aimTime':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.aimTime -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.aimTime,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.dispersionStill':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.dispersion.base -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.dispersion.base,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.dispersionMoving':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.tracks.at(-1)!.dispersion.move -
|
||||
b.tracks.at(-1)!.dispersion.move,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.gunDepression':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[
|
||||
a.turrets.at(-1)!.guns.at(-1)!.id
|
||||
].pitch.max -
|
||||
awaitedModelDefinitions[b.id].turrets[b.turrets.at(-1)!.id].guns[
|
||||
b.turrets.at(-1)!.guns.at(-1)!.id
|
||||
].pitch.max,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'fire.gunElevation':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
// reversed because of negative values
|
||||
awaitedModelDefinitions[b.id].turrets[b.turrets.at(-1)!.id].guns[
|
||||
b.turrets.at(-1)!.guns.at(-1)!.id
|
||||
].pitch.min -
|
||||
awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[
|
||||
a.turrets.at(-1)!.guns.at(-1)!.id
|
||||
].pitch.min,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'maneuverability.forwardsSpeed':
|
||||
sorted = filtered.sort((a, b) => a.speed.forwards - b.speed.forwards);
|
||||
break;
|
||||
|
||||
case 'maneuverability.backwardsSpeed':
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.speed.backwards - b.speed.backwards,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'maneuverability.power':
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'maneuverability.powerToWeight':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.engines.at(-1)!.power /
|
||||
(a.weight +
|
||||
a.engines.at(-1)!.weight +
|
||||
a.tracks.at(-1)!.weight +
|
||||
a.turrets.at(-1)!.weight +
|
||||
a.turrets.at(-1)!.guns.at(-1)!.weight) -
|
||||
b.engines.at(-1)!.power /
|
||||
(b.weight +
|
||||
b.engines.at(-1)!.weight +
|
||||
b.tracks.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'maneuverability.weight':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.weight +
|
||||
a.engines.at(-1)!.weight +
|
||||
a.tracks.at(-1)!.weight +
|
||||
a.turrets.at(-1)!.weight +
|
||||
a.turrets.at(-1)!.guns.at(-1)!.weight -
|
||||
(b.weight +
|
||||
b.engines.at(-1)!.weight +
|
||||
b.tracks.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'maneuverability.traverseSpeed':
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.tracks.at(-1)!.traverseSpeed - b.tracks.at(-1)!.traverseSpeed,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return sort.direction === 'ascending' ? sorted : sorted.reverse();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [search, sort]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4" flexGrow="1">
|
||||
@@ -47,9 +345,14 @@ export const Results = memo(() => {
|
||||
)}
|
||||
|
||||
{!search &&
|
||||
range(10, 0).map((tier) => <TierCard key={tier} tier={tier as Tier} />)}
|
||||
sort.by === 'meta.tier' &&
|
||||
range(
|
||||
...(sort.direction === 'descending'
|
||||
? ([10, 0] as Vector2Tuple)
|
||||
: ([1, 11] as Vector2Tuple)),
|
||||
).map((tier) => <TierCard key={tier} tier={tier as Tier} />)}
|
||||
|
||||
{search && (
|
||||
{(search || sort.by !== 'meta.tier') && (
|
||||
<TankCardWrapper py="4">
|
||||
{searchedTanks?.map((tank) => <TankCard key={tank.id} tank={tank} />)}
|
||||
</TankCardWrapper>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CaretRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Spinner, TextField } from '@radix-ui/themes';
|
||||
import { Button, Flex, Spinner, TextField } from '@radix-ui/themes';
|
||||
import { debounce } from 'lodash';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { KeyboardEventHandler, useCallback, useRef, useState } from 'react';
|
||||
import { Link } from '../../../../components/Link';
|
||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
|
||||
import { Sort } from './Sort';
|
||||
|
||||
interface SearchBarProps {
|
||||
topResult?: TankDefinition;
|
||||
@@ -44,26 +45,31 @@ export function SearchBar({ topResult }: SearchBarProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<TextField.Root
|
||||
ref={input}
|
||||
placeholder="Search tanks..."
|
||||
size="3"
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<TextField.Slot>
|
||||
{searching ? <Spinner /> : <MagnifyingGlassIcon />}
|
||||
</TextField.Slot>
|
||||
|
||||
{topResult && (
|
||||
<Flex gap="2">
|
||||
<TextField.Root
|
||||
style={{ flex: 1 }}
|
||||
ref={input}
|
||||
placeholder="Search tanks..."
|
||||
size="3"
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<TextField.Slot>
|
||||
<Link href={`/tools/tankopedia/${topResult.id}`}>
|
||||
<Button variant="ghost" size="3">
|
||||
{topResult.name} <CaretRightIcon />
|
||||
</Button>
|
||||
</Link>
|
||||
{searching ? <Spinner /> : <MagnifyingGlassIcon />}
|
||||
</TextField.Slot>
|
||||
)}
|
||||
</TextField.Root>
|
||||
|
||||
{topResult && (
|
||||
<TextField.Slot>
|
||||
<Link href={`/tools/tankopedia/${topResult.id}`}>
|
||||
<Button variant="ghost" size="3">
|
||||
{topResult.name} <CaretRightIcon />
|
||||
</Button>
|
||||
</Link>
|
||||
</TextField.Slot>
|
||||
)}
|
||||
</TextField.Root>
|
||||
|
||||
<Sort />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
+39
-37
@@ -1,23 +1,25 @@
|
||||
import { CaretDownIcon } from '@radix-ui/react-icons';
|
||||
import { Button, DropdownMenu } from '@radix-ui/themes';
|
||||
import mutateTankopediaPersistent, {
|
||||
TankopediaSortDirection,
|
||||
useTankopediaPersistent,
|
||||
} from '../../../../../../stores/tankopedia';
|
||||
import { TankopediaSortDirection } from '../../../../stores/tankopedia';
|
||||
import {
|
||||
mutateTankopediaFilters,
|
||||
useTankopediaFilters,
|
||||
} from '../../../../stores/tankopediaFilters';
|
||||
|
||||
export function Sort() {
|
||||
const sort = useTankopediaPersistent((state) => state.sort);
|
||||
const sort = useTankopediaFilters((state) => state.sort);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Root modal={false}>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="soft" color="gray">
|
||||
<Button variant="surface" size="3" color="gray">
|
||||
Sort
|
||||
<CaretDownIcon />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Label>By</DropdownMenu.Label>
|
||||
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Sort by</DropdownMenu.Label>
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>Meta</DropdownMenu.SubTrigger>
|
||||
@@ -25,7 +27,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'meta.tier'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'meta.tier';
|
||||
});
|
||||
}}
|
||||
@@ -35,7 +37,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'meta.name'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'meta.name';
|
||||
});
|
||||
}}
|
||||
@@ -51,7 +53,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.dpm'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.dpm';
|
||||
});
|
||||
}}
|
||||
@@ -61,7 +63,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.reload'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.reload';
|
||||
});
|
||||
}}
|
||||
@@ -71,7 +73,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.caliber'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.caliber';
|
||||
});
|
||||
}}
|
||||
@@ -81,7 +83,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.standardPenetration'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.standardPenetration';
|
||||
});
|
||||
}}
|
||||
@@ -91,7 +93,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.premiumPenetration'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.premiumPenetration';
|
||||
});
|
||||
}}
|
||||
@@ -101,7 +103,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.damage'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.damage';
|
||||
});
|
||||
}}
|
||||
@@ -111,7 +113,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.shellVelocity'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.shellVelocity';
|
||||
});
|
||||
}}
|
||||
@@ -121,7 +123,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.aimTime'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.aimTime';
|
||||
});
|
||||
}}
|
||||
@@ -131,7 +133,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.dispersionStill'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.dispersionStill';
|
||||
});
|
||||
}}
|
||||
@@ -141,7 +143,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.dispersionMoving'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.dispersionMoving';
|
||||
});
|
||||
}}
|
||||
@@ -151,7 +153,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.gunDepression'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.gunDepression';
|
||||
});
|
||||
}}
|
||||
@@ -161,7 +163,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'fire.gunElevation'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'fire.gunElevation';
|
||||
});
|
||||
}}
|
||||
@@ -177,7 +179,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.forwardsSpeed'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.forwardsSpeed';
|
||||
});
|
||||
}}
|
||||
@@ -187,7 +189,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.backwardsSpeed'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.backwardsSpeed';
|
||||
});
|
||||
}}
|
||||
@@ -197,7 +199,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.power'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.power';
|
||||
});
|
||||
}}
|
||||
@@ -207,7 +209,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.powerToWeight'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.powerToWeight';
|
||||
});
|
||||
}}
|
||||
@@ -217,7 +219,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.weight'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.weight';
|
||||
});
|
||||
}}
|
||||
@@ -227,7 +229,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'maneuverability.traverseSpeed'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'maneuverability.traverseSpeed';
|
||||
});
|
||||
}}
|
||||
@@ -243,7 +245,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.health'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.health';
|
||||
});
|
||||
}}
|
||||
@@ -253,7 +255,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.viewRange'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.viewRange';
|
||||
});
|
||||
}}
|
||||
@@ -263,7 +265,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.camouflageStill'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.camouflageStill';
|
||||
});
|
||||
}}
|
||||
@@ -273,7 +275,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.camouflageMoving'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.camouflageMoving';
|
||||
});
|
||||
}}
|
||||
@@ -283,7 +285,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.camouflageShooting'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.camouflageShooting';
|
||||
});
|
||||
}}
|
||||
@@ -293,7 +295,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.volume'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.volume';
|
||||
});
|
||||
}}
|
||||
@@ -303,7 +305,7 @@ export function Sort() {
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={sort.by === 'survivability.length'}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.by = 'survivability.length';
|
||||
});
|
||||
}}
|
||||
@@ -319,7 +321,7 @@ export function Sort() {
|
||||
<DropdownMenu.RadioGroup
|
||||
value={sort.direction}
|
||||
onValueChange={(value) =>
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.sort.direction = value as TankopediaSortDirection;
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { use, useEffect, useMemo, useRef } from 'react';
|
||||
import { classIcons } from '../../../../components/ClassIcon';
|
||||
import { asset } from '../../../../core/blitzkit/asset';
|
||||
import { modelDefinitions } from '../../../../core/blitzkit/modelDefinitions';
|
||||
import { resolveDpm } from '../../../../core/blitzkit/resolveDpm';
|
||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
|
||||
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
|
||||
@@ -12,8 +14,36 @@ interface TankCardProps {
|
||||
}
|
||||
|
||||
export function TankCard({ tank }: TankCardProps) {
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const Icon = classIcons[tank.class];
|
||||
const link = useRef<HTMLAnchorElement>(null);
|
||||
const sortBy = useTankopediaFilters((state) => state.sort.by);
|
||||
const discriminator = useMemo(() => {
|
||||
if (sortBy.startsWith('meta')) return undefined;
|
||||
|
||||
const turret = tank.turrets.at(-1)!;
|
||||
const gun = turret.guns.at(-1)!;
|
||||
const shell = gun.shells[0];
|
||||
const tracks = tank.tracks.at(-1)!;
|
||||
const tankModelDefinition = awaitedModelDefinitions[tank.id];
|
||||
|
||||
switch (sortBy) {
|
||||
case 'fire.aimTime':
|
||||
return `${gun.aimTime.toFixed(2)}s`;
|
||||
case 'fire.caliber':
|
||||
return `${shell.caliber.toFixed(0)}mm`;
|
||||
case 'fire.damage':
|
||||
return `${shell.damage.armor.toFixed(0)}hp`;
|
||||
case 'fire.dispersionMoving':
|
||||
return `+ ${tracks.dispersion.move.toFixed(3)}`;
|
||||
case 'fire.dispersionStill':
|
||||
return gun.dispersion.base.toFixed(3);
|
||||
case 'fire.dpm':
|
||||
return resolveDpm(gun, shell).toFixed(0);
|
||||
case 'fire.gunDepression':
|
||||
return awaitedModelDefinitions;
|
||||
}
|
||||
}, [sortBy]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = useTankopediaFilters.subscribe((filters) => {
|
||||
@@ -89,6 +119,21 @@ export function TankCard({ tank }: TankCardProps) {
|
||||
{tank.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{discriminator && (
|
||||
<Text
|
||||
mt="-2"
|
||||
color="gray"
|
||||
align="center"
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{discriminator}
|
||||
</Text>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -31,9 +31,9 @@ import mutateTankopediaPersistent, {
|
||||
useTankopediaPersistent,
|
||||
} from '../../../../../stores/tankopedia';
|
||||
import { PageTurner } from '../PageTurner';
|
||||
import { Sort } from '../Sort';
|
||||
import { Options } from './components/Options';
|
||||
import { Results } from './components/Results';
|
||||
import { Sort } from './components/Sort';
|
||||
import { treeTypeOrder } from './constants';
|
||||
|
||||
interface TankSearchProps {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Flex, Heading, Separator } from '@radix-ui/themes';
|
||||
import { memo, use, useEffect, useMemo, useRef } from 'react';
|
||||
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
|
||||
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
|
||||
import {
|
||||
Tier,
|
||||
tanksDefinitionsArray,
|
||||
@@ -9,6 +11,7 @@ import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilter
|
||||
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
|
||||
import { TankCard } from './TankCard';
|
||||
import { TankCardWrapper } from './TankCardWrapper';
|
||||
import { treeTypeOrder } from './TankSearch/constants';
|
||||
|
||||
interface TierCardProps {
|
||||
tier: Tier;
|
||||
@@ -16,10 +19,27 @@ interface TierCardProps {
|
||||
|
||||
export const TierCard = memo(
|
||||
({ tier }: TierCardProps) => {
|
||||
const awaitedGameDefinitions = use(gameDefinitions);
|
||||
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const tanks = useMemo(
|
||||
() => awaitedTanksDefinitionsArray.filter((tank) => tank.tier === tier),
|
||||
() =>
|
||||
awaitedTanksDefinitionsArray
|
||||
.filter((tank) => tank.tier === tier)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
treeTypeOrder.indexOf(a.treeType) -
|
||||
treeTypeOrder.indexOf(b.treeType),
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class),
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||
awaitedGameDefinitions.nations.indexOf(b.nation),
|
||||
),
|
||||
[tier],
|
||||
);
|
||||
|
||||
|
||||
@@ -8,12 +8,7 @@ import { Results } from './components/Results';
|
||||
export default function Page() {
|
||||
return (
|
||||
<PageWrapper color="purple" size={1028 + 256}>
|
||||
<Flex
|
||||
gap="8"
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
pt={{ initial: '0', sm: '6' }}
|
||||
pb="6"
|
||||
>
|
||||
<Flex gap="8" direction={{ initial: 'column', sm: 'row' }} pb="6">
|
||||
<FilterControl />
|
||||
<Results />
|
||||
</Flex>
|
||||
|
||||
Reference in new issue
Block a user