feat: complete sort labels

This commit is contained in:
TrèsAbhi committed 2024-06-24 21:00:51 -05:00
1 parent 77105b514e
commit 8bcd74262c
10 files changed
+200 -177

No files matched your search

@@ -343,7 +343,7 @@ export function Characteristics() {
{stats.dispersion}
</InfoWithDelta>
<InfoWithDelta
prefix="+ "
prefix="+"
decimals={3}
indent
name="Moving"
@@ -353,7 +353,7 @@ export function Characteristics() {
</InfoWithDelta>
<InfoWithDelta
decimals={3}
prefix="+ "
prefix="+"
indent
name="Hull traversing"
deltaType="lowerIsBetter"
@@ -362,7 +362,7 @@ export function Characteristics() {
</InfoWithDelta>
<InfoWithDelta
decimals={3}
prefix="+ "
prefix="+"
indent
name="Turret traversing"
deltaType="lowerIsBetter"
@@ -371,7 +371,7 @@ export function Characteristics() {
</InfoWithDelta>
<InfoWithDelta
decimals={3}
prefix="+ "
prefix="+"
indent
name="After shooting"
deltaType="lowerIsBetter"
@@ -42,7 +42,7 @@ export function FilterControl() {
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = tier;
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
@@ -65,7 +65,7 @@ export function FilterControl() {
highContrast
onClick={() =>
mutateTankopediaFilters((draft) => {
draft.tier = tier;
draft.tier = draft.tier === tier ? undefined : tier;
})
}
>
+62 -51
View File
@@ -3,7 +3,9 @@ import { go } from 'fuzzysort';
import { times } from 'lodash';
import { memo, use, useMemo } from 'react';
import { ExperimentIcon } from '../../../../components/ExperimentIcon';
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
import { modelDefinitions } from '../../../../core/blitzkit/modelDefinitions';
import { normalizeBoundingBox } from '../../../../core/blitzkit/normalizeBoundingBox';
import { resolveDpm } from '../../../../core/blitzkit/resolveDpm';
@@ -14,6 +16,7 @@ import {
tankNames,
tanksDefinitionsArray,
} from '../../../../core/blitzkit/tankDefinitions';
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { FilterControl } from './FilterControl';
@@ -22,35 +25,41 @@ import { SearchBar } from './SearchBar';
import { SkeletonTankCard } from './SkeletonTankCard';
import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper';
import { TierCard } from './TierCard';
import { treeTypeOrder } from './TankSearch/constants';
export const Results = memo(() => {
const awaitedGameDefinitions = use(gameDefinitions);
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 tier = useTankopediaFilters((state) => state.tier);
const sort = useTankopediaFilters((state) => state.sort);
const searching = useTankopediaFilters((state) => state.searching);
const searchedTanks = useMemo(() => {
if (search !== undefined) {
const searchedRaw = go(search, awaitedTankNames, {
key: 'combined',
limit: 25,
});
const searchedTanks = searchedRaw.map(
(result) => awaitedTankDefinitions[result.obj.id],
const filters = useTankopediaFilters();
const tanks = useMemo(() => {
if (filters.search === undefined) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
tankopediaFilterTank(filters, tank),
);
return searchedTanks;
}
if (sort.by !== 'meta.tier') {
const filtered = awaitedTanksDefinitionsArray;
let sorted: TankDefinition[];
switch (sort.by) {
switch (filters.sort.by) {
case 'meta.none':
sorted = filtered
.sort(
(a, b) =>
treeTypeOrder.indexOf(b.treeType) -
treeTypeOrder.indexOf(a.treeType),
)
.sort(
(a, b) =>
TANK_CLASSES.indexOf(b.class) - TANK_CLASSES.indexOf(a.class),
)
.sort(
(a, b) =>
awaitedGameDefinitions.nations.indexOf(b.nation) -
awaitedGameDefinitions.nations.indexOf(a.nation),
);
break;
case 'survivability.health': {
sorted = filtered.sort((a, b) => {
const aHealth = a.health + a.turrets.at(-1)!.health;
@@ -82,8 +91,10 @@ export const Results = memo(() => {
case 'survivability.camouflageShooting':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.camouflageLoss -
b.turrets.at(-1)!.guns.at(-1)!.camouflageLoss,
a.camouflage.still *
a.turrets.at(-1)!.guns.at(-1)!.camouflageLoss -
b.camouflage.still *
b.turrets.at(-1)!.guns.at(-1)!.camouflageLoss,
);
break;
@@ -187,16 +198,10 @@ export const Results = memo(() => {
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,
a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration ?? 0,
) -
resolveNearPenetration(
(
b.turrets.at(-1)!.guns.at(-1)!.shells[1] ??
b.turrets.at(-1)!.guns.at(-1)!.shells[0]
).penetration,
b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration ?? 0,
),
);
break;
@@ -246,23 +251,26 @@ export const Results = memo(() => {
(a, b) =>
awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[
a.turrets.at(-1)!.guns.at(-1)!.id
].pitch.max -
].pitch.max +
(awaitedModelDefinitions[a.id].turretRotation?.pitch ?? 0) -
awaitedModelDefinitions[b.id].turrets[b.turrets.at(-1)!.id].guns[
b.turrets.at(-1)!.guns.at(-1)!.id
].pitch.max,
].pitch.max -
(awaitedModelDefinitions[b.id].turretRotation?.pitch ?? 0),
);
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 -
].pitch.min +
(awaitedModelDefinitions[b.id].turretRotation?.pitch ?? 0) -
awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[
a.turrets.at(-1)!.guns.at(-1)!.id
].pitch.min,
].pitch.min -
(awaitedModelDefinitions[a.id].turretRotation?.pitch ?? 0),
);
break;
@@ -324,19 +332,26 @@ export const Results = memo(() => {
break;
}
return sort.direction === 'ascending' ? sorted : sorted.reverse();
return filters.sort.direction === 'ascending' ? sorted : sorted.reverse();
} else {
const searchedRaw = go(filters.search, awaitedTankNames, {
key: 'combined',
limit: 25,
});
const searchedTanks = searchedRaw.map(
(result) => awaitedTankDefinitions[result.obj.id],
);
return searchedTanks;
}
return undefined;
}, [search, sort]);
}, [filters]);
return (
<Flex direction="column" gap="4" flexGrow="1">
<SearchBar topResult={searchedTanks?.[0]} />
<SearchBar topResult={tanks?.[0]} />
{!search && !searching && <FilterControl />}
{!filters.search && !filters.searching && <FilterControl />}
{testing === 'only' && (
{filters.testing === 'only' && (
<Flex justify="center" mt="4">
<Callout.Root color="amber">
<Callout.Icon>
@@ -350,25 +365,21 @@ export const Results = memo(() => {
</Flex>
)}
{!search && sort.by === 'meta.tier' && !searching && (
<TierCard tier={tier} />
)}
{(search || sort.by !== 'meta.tier') && !searching && searchedTanks && (
{!filters.searching && (
<>
{searchedTanks.length > 0 && (
{tanks.length > 0 && (
<TankCardWrapper>
{searchedTanks.map((tank) => (
{tanks.map((tank) => (
<TankCard key={tank.id} tank={tank} />
))}
</TankCardWrapper>
)}
{searchedTanks.length === 0 && <NoResults type="search" />}
{tanks.length === 0 && <NoResults type="search" />}
</>
)}
{searching && (
{filters.searching && (
<TankCardWrapper>
{times(Math.round(10 + 10 * Math.random()), (index) => (
<SkeletonTankCard key={index} />
+11 -18
View File
@@ -19,23 +19,18 @@ export function Sort() {
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Sort by</DropdownMenu.Label>
<DropdownMenu.CheckboxItem
checked={sort.by === 'meta.none'}
onClick={() => {
mutateTankopediaFilters((draft) => {
draft.sort.by = 'meta.none';
});
}}
>
No sort
</DropdownMenu.CheckboxItem>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Meta</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.CheckboxItem
checked={sort.by === 'meta.tier'}
onClick={() => {
mutateTankopediaFilters((draft) => {
draft.sort.by = 'meta.tier';
});
}}
>
Tier
</DropdownMenu.CheckboxItem>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Label>Sort by</DropdownMenu.Label>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Fire</DropdownMenu.SubTrigger>
@@ -305,8 +300,6 @@ export function Sort() {
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Label>Direction</DropdownMenu.Label>
<DropdownMenu.RadioGroup
value={sort.direction}
+114 -39
View File
@@ -1,10 +1,18 @@
import { Flex, Text } from '@radix-ui/themes';
import { Icon } from '@radix-ui/themes/dist/cjs/components/callout';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { use, useMemo } from 'react';
import { classIcons } from '../../../../components/ClassIcon';
import { Link } from '../../../../components/Link';
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
import { asset } from '../../../../core/blitzkit/asset';
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 } from '../../../../core/blitzkit/tankDefinitions';
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import * as styles from './TankCard.css';
interface TankCardProps {
@@ -12,47 +20,114 @@ interface TankCardProps {
}
export function TankCard({ tank }: TankCardProps) {
// const awaitedModelDefinitions = use(modelDefinitions);
// const Icon = classIcons[tank.class];
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 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];
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const shell0 = gun.shells[0];
const shell1 = gun.shells[1];
const tracks = tank.tracks.at(-1)!;
const engine = tank.engines.at(-1)!;
const tankModelDefinition = awaitedModelDefinitions[tank.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.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]);
switch (sortBy) {
case 'fire.aimTime':
return gun.aimTime.toFixed(2);
case 'fire.caliber':
return shell0.caliber.toFixed(0);
case 'fire.damage':
return shell0.damage.armor.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion.move.toFixed(3);
case 'fire.dispersionStill':
return gun.dispersion.base.toFixed(3);
case 'fire.dpm':
return resolveDpm(gun, shell0).toFixed(0);
case 'fire.reload':
return resolveReload(gun).toFixed(2);
case 'fire.standardPenetration':
return resolveNearPenetration(shell0.penetration).toFixed(0);
case 'fire.premiumPenetration':
return shell1
? resolveNearPenetration(shell1.penetration).toFixed(0)
: '--';
case 'fire.shellVelocity':
return shell0.speed.toFixed(0);
case 'fire.gunDepression':
return (
gunModelDefinition.pitch.max +
(tankModelDefinition.turretRotation?.pitch ?? 0)
).toFixed(1);
case 'fire.gunElevation':
return (
-gunModelDefinition.pitch.min -
(tankModelDefinition.turretRotation?.pitch ?? 0)
).toFixed(1);
case 'maneuverability.forwardsSpeed':
return tank.speed.forwards.toFixed(0);
case 'maneuverability.backwardsSpeed':
return tank.speed.backwards.toFixed(0);
case 'maneuverability.power':
return engine.power.toFixed(0);
case 'maneuverability.powerToWeight':
return (
(1000 * engine.power) /
(tank.weight +
engine.weight +
tracks.weight +
turret.weight +
gun.weight)
).toFixed(1);
case 'maneuverability.weight':
return (
(tank.weight +
engine.weight +
tracks.weight +
turret.weight +
gun.weight) /
1000
).toFixed(1);
case 'maneuverability.traverseSpeed':
return tracks.traverseSpeed.toFixed(1);
case 'survivability.health':
return (tank.health + turret.health).toFixed(0);
case 'survivability.viewRange':
return turret.viewRange.toFixed(0);
case 'survivability.camouflageStill':
return (tank.camouflage.still * 100).toFixed(0);
case 'survivability.camouflageMoving':
return (tank.camouflage.moving * 100).toFixed(0);
case 'survivability.camouflageShooting':
return (tank.camouflage.still * gun.camouflageLoss * 100).toFixed(0);
case 'survivability.volume': {
const dimensions = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.boundingBox,
turretModelDefinition.boundingBox,
),
);
// useEffect(() => {
// const unsubscribe = useTankopediaFilters.subscribe((filters) => {
// if (!link.current) return;
return (dimensions[0] * dimensions[1] * dimensions[2]).toFixed(0);
}
// const visible = tankopediaFilterTank(filters, tank);
// link.current.style.display = visible ? 'flex' : 'none';
// });
// return unsubscribe;
// }, []);
case 'survivability.length':
return Math.max(
...normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.boundingBox,
turretModelDefinition.boundingBox,
),
),
).toFixed(0);
}
}, [sortBy]);
return (
<Link
@@ -89,7 +164,7 @@ export function TankCard({ tank }: TankCardProps) {
</Text>
</Flex>
{/* {discriminator && (
{discriminator && (
<Text
mt="-2"
color="gray"
@@ -102,7 +177,7 @@ export function TankCard({ tank }: TankCardProps) {
>
{discriminator}
</Text>
)} */}
)}
</Link>
);
}
@@ -101,7 +101,7 @@ export function TankSearch({
let sorted: TankDefinition[] = [];
switch (sort.by) {
case 'meta.tier':
case 'meta.none':
sorted = filtered.sort((a, b) => a.tier - b.tier);
break;
@@ -475,7 +475,7 @@ export function TankSearch({
page: 0,
},
sort: {
by: 'meta.tier',
by: 'meta.none',
direction: 'descending',
},
})
@@ -1,56 +0,0 @@
import { memo, use, useMemo } from 'react';
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
import {
Tier,
tanksDefinitionsArray,
} from '../../../../core/blitzkit/tankDefinitions';
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
import { NoResults } from './NoResults';
import { TankCard } from './TankCard';
import { TankCardWrapper } from './TankCardWrapper';
import { treeTypeOrder } from './TankSearch/constants';
interface TierCardProps {
tier: Tier;
}
export const TierCard = memo(
({ tier }: TierCardProps) => {
const awaitedGameDefinitions = use(gameDefinitions);
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
const filters = useTankopediaFilters();
const tanks = useMemo(
() =>
awaitedTanksDefinitionsArray
.filter((tank) => tankopediaFilterTank(filters, tank))
.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, filters],
);
if (tanks.length === 0) return <NoResults />;
return (
<TankCardWrapper>
{tanks.map((tank) => (
<TankCard key={tank.id} tank={tank} />
))}
</TankCardWrapper>
);
},
(prev, next) => prev.tier === next.tier,
);
@@ -13,7 +13,7 @@ export const TierFilter = memo(() => {
const sort = useTankopediaFilters((state) => state.sort);
const search = useTankopediaFilters((state) => state.search);
if (sort.by !== 'meta.tier' || search) return null;
if (sort.by !== 'meta.none' || search) return null;
return (
<Flex justify="center">
+2 -2
View File
@@ -106,7 +106,7 @@ export type ArmorPiercingLayer =
};
export const SORT_NAMES = {
'meta.tier': 'tier',
'meta.none': 'none',
'survivability.health': 'health',
'survivability.viewRange': 'view range',
'survivability.camouflageStill': 'camouflage still',
@@ -150,7 +150,7 @@ export const useTankopediaPersistent = create<TankopediaPersistent>()(
},
},
sort: {
by: 'meta.tier',
by: 'meta.none',
direction: 'descending',
},
filters: {
+2 -2
View File
@@ -5,7 +5,7 @@ import { Tier } from '../core/blitzkit/tankDefinitions';
import { TankopediaSortBy, TankopediaSortDirection } from './tankopedia';
export interface TankopediaFilters {
tier: Tier;
tier?: Tier;
nation?: string;
class?: TankClass;
type?: TreeType;
@@ -21,7 +21,7 @@ export interface TankopediaFilters {
export const useTankopediaFilters = create<TankopediaFilters>()(() => ({
testing: 'include',
sort: {
by: 'meta.tier',
by: 'meta.none',
direction: 'descending',
},
tier: 10,