feat: upgrade TankSort

This commit is contained in:
TrèsAbhi committed 2025-09-01 18:13:41 -05:00
1 parent 87b6a36520
commit 4efdaaf7c9
9 files changed
+39 -42

No files matched your search

+8 -2
View File
@@ -43,11 +43,17 @@ export class Varuna<Type> {
} }
mutate(mutator: (state: Type) => void) { mutate(mutator: (state: Type) => void) {
this.state = produce(this.state, mutator); this.set(produce(this.state, mutator));
}
set(state: Type) {
this.state = state;
this.dispatch(); this.dispatch();
} }
use<Slice>(slicer: (state: Type) => Slice) { use<Slice = Type>(
slicer: (state: Type) => Slice = (state) => state as unknown as Slice,
) {
const [slice, setSlice] = useState(slicer(this.state)); const [slice, setSlice] = useState(slicer(this.state));
useEffect(() => this.on(slicer, setSlice), []); useEffect(() => this.on(slicer, setSlice), []);
return slice; return slice;
@@ -4,11 +4,10 @@ import {
unionBoundingBox, unionBoundingBox,
type TankDefinition, type TankDefinition,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { useStore } from '@nanostores/react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions'; import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
import { resolveReload } from '../../../core/blitzkit/resolveReload'; import { resolveReload } from '../../../core/blitzkit/resolveReload';
import { $tankopediaSort } from '../../../stores/tankopediaSort'; import { TankSort } from '../../../stores/tankopediaSort';
import { TankCard } from '../../TankCard'; import { TankCard } from '../../TankCard';
interface TankSearchCardProps { interface TankSearchCardProps {
@@ -19,9 +18,9 @@ interface TankSearchCardProps {
const modelDefinitions = await awaitableModelDefinitions; const modelDefinitions = await awaitableModelDefinitions;
export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) { export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const tankopediaSort = useStore($tankopediaSort); const by = TankSort.use((state) => state.by);
const discriminator = useMemo(() => { const discriminator = useMemo(() => {
if (tankopediaSort.by.startsWith('meta')) return undefined; if (by.startsWith('meta')) return undefined;
const turret = tank.turrets.at(-1)!; const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!; const gun = turret.guns.at(-1)!;
@@ -34,7 +33,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id]; const gunModelDefinition = turretModelDefinition.guns[gun.id];
switch (tankopediaSort.by) { switch (by) {
case 'fire.aimTime': case 'fire.aimTime':
return gun.aim_time.toFixed(2); return gun.aim_time.toFixed(2);
case 'fire.caliber': case 'fire.caliber':
@@ -133,7 +132,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0); return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0);
} }
} }
}, [tankopediaSort.by]); }, [by]);
return ( return (
<TankCard <TankCard
@@ -24,10 +24,7 @@ import {
initialTankFilters, initialTankFilters,
type TankFilters, type TankFilters,
} from '../../../stores/tankFilters'; } from '../../../stores/tankFilters';
import { import { TankSort } from '../../../stores/tankopediaSort';
$tankopediaSort,
initialTankopediaSort,
} from '../../../stores/tankopediaSort';
import { classIcons } from '../../ClassIcon'; import { classIcons } from '../../ClassIcon';
import { ExperimentIcon } from '../../ExperimentIcon'; import { ExperimentIcon } from '../../ExperimentIcon';
import { GunAutoloaderIcon } from '../../GunAutoloaderIcon'; import { GunAutoloaderIcon } from '../../GunAutoloaderIcon';
@@ -620,7 +617,7 @@ export function FilterControl({ compact }: FilterControlProps) {
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
$tankFilters.set(initialTankFilters); $tankFilters.set(initialTankFilters);
$tankopediaSort.set(initialTankopediaSort); TankSort.set(TankSort.initial);
}} }}
> >
{strings.website.common.tank_search.clear_filters} {strings.website.common.tank_search.clear_filters}
@@ -6,7 +6,7 @@ import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitio
import { useLocale } from '../../../hooks/useLocale'; import { useLocale } from '../../../hooks/useLocale';
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters'; import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
import { $tankopediaSort } from '../../../stores/tankopediaSort'; import { TankSort } from '../../../stores/tankopediaSort';
import { TankCard } from '../../TankCard'; import { TankCard } from '../../TankCard';
import { TankCardWrapper } from './TankCardWrapper'; import { TankCardWrapper } from './TankCardWrapper';
@@ -19,7 +19,6 @@ export function RecentlyViewed() {
const recentlyViewed = tankopediaPersistentStore const recentlyViewed = tankopediaPersistentStore
.getState() .getState()
.recentlyViewed.filter((id) => id in tankDefinitions.tanks); .recentlyViewed.filter((id) => id in tankDefinitions.tanks);
const sort = useStore($tankopediaSort);
const hasFilters = useMemo( const hasFilters = useMemo(
() => () =>
Object.entries(filters).some(([key, value]) => { Object.entries(filters).some(([key, value]) => {
@@ -31,8 +30,9 @@ export function RecentlyViewed() {
[filters], [filters],
); );
const { strings } = useLocale(); const { strings } = useLocale();
const by = TankSort.use((state) => state.by);
if (recentlyViewed.length === 0 || sort.by !== 'meta.none' || hasFilters) { if (recentlyViewed.length === 0 || by !== 'meta.none' || hasFilters) {
return null; return null;
} }
@@ -1,4 +1,3 @@
import { useStore } from '@nanostores/react';
import { CaretDownIcon } from '@radix-ui/react-icons'; import { CaretDownIcon } from '@radix-ui/react-icons';
import { Button, DropdownMenu } from '@radix-ui/themes'; import { Button, DropdownMenu } from '@radix-ui/themes';
import { useLocale } from '../../../hooks/useLocale'; import { useLocale } from '../../../hooks/useLocale';
@@ -6,7 +5,7 @@ import type {
TankopediaSortBy, TankopediaSortBy,
TankopediaSortDirection, TankopediaSortDirection,
} from '../../../stores/tankopediaPersistent'; } from '../../../stores/tankopediaPersistent';
import { $tankopediaSort } from '../../../stores/tankopediaSort'; import { TankSort } from '../../../stores/tankopediaSort';
interface ItemProps { interface ItemProps {
by: TankopediaSortBy; by: TankopediaSortBy;
@@ -18,13 +17,15 @@ interface ItemProps {
function Item({ by }: ItemProps) { function Item({ by }: ItemProps) {
const { strings } = useLocale(); const { strings } = useLocale();
const tankopediaSort = useStore($tankopediaSort); const _by = TankSort.use((state) => state.by);
return ( return (
<DropdownMenu.CheckboxItem <DropdownMenu.CheckboxItem
checked={tankopediaSort.by === by} checked={_by === by}
onClick={() => { onClick={() => {
$tankopediaSort.setKey('by', by); TankSort.mutate((draft) => {
draft.by = by;
});
}} }}
> >
{strings.website.common.tank_search.sort[by]} {strings.website.common.tank_search.sort[by]}
@@ -33,7 +34,7 @@ function Item({ by }: ItemProps) {
} }
export function Sort() { export function Sort() {
const tankopediaSort = useStore($tankopediaSort); const direction = TankSort.use((state) => state.direction);
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
@@ -119,12 +120,11 @@ export function Sort() {
} }
</DropdownMenu.Label> </DropdownMenu.Label>
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={tankopediaSort.direction} value={direction}
onValueChange={(value) => { onValueChange={(value) => {
$tankopediaSort.setKey( TankSort.mutate((draft) => {
'direction', draft.direction = value as TankopediaSortDirection;
value as TankopediaSortDirection, });
);
}} }}
> >
<DropdownMenu.RadioItem value="ascending"> <DropdownMenu.RadioItem value="ascending">
@@ -4,13 +4,13 @@ import { Flex, IconButton, Text } from '@radix-ui/themes';
import { times } from 'lodash-es'; import { times } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
import { $tankFilters } from '../../../stores/tankFilters'; import { $tankFilters } from '../../../stores/tankFilters';
import { $tankopediaSort } from '../../../stores/tankopediaSort'; import { TankSort } from '../../../stores/tankopediaSort';
export const TierFilter = memo(() => { export const TierFilter = memo(() => {
const tankFilters = useStore($tankFilters); const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort); const by = TankSort.use((state) => state.by);
if (tankopediaSort.by !== 'meta.none' || tankFilters.search) return null; if (by !== 'meta.none' || tankFilters.search) return null;
return ( return (
<Flex justify="center"> <Flex justify="center">
@@ -22,7 +22,7 @@ import { useLocale } from '../../hooks/useLocale';
import { $tankFilters } from '../../stores/tankFilters'; import { $tankFilters } from '../../stores/tankFilters';
import { TankopediaPersistent } from '../../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
import { SORT_UNITS } from '../../stores/tankopediaPersistent/constants'; import { SORT_UNITS } from '../../stores/tankopediaPersistent/constants';
import { $tankopediaSort } from '../../stores/tankopediaSort'; import { TankSort } from '../../stores/tankopediaSort';
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
import { ExperimentIcon } from '../ExperimentIcon'; import { ExperimentIcon } from '../ExperimentIcon';
import { TankSearchCard } from './components/Card'; import { TankSearchCard } from './components/Card';
@@ -58,7 +58,7 @@ export const TankSearch = memo<TankSearchProps>(
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks); const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
const tankFilters = useStore($tankFilters); const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort); const tankopediaSort = TankSort.use();
const tanksFiltered = useMemo(() => { const tanksFiltered = useMemo(() => {
if (tankFilters.search === null) { if (tankFilters.search === null) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) => const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
@@ -7,7 +7,6 @@ import {
LocaleProvider, LocaleProvider,
type LocaleAcceptorProps, type LocaleAcceptorProps,
} from '../../../hooks/useLocale'; } from '../../../hooks/useLocale';
import { TankPerformanceEphemeral } from '../../../stores/tankPerformanceEphemeral';
import { TankPerformanceSort } from '../../../stores/tankPerformanceSort'; import { TankPerformanceSort } from '../../../stores/tankPerformanceSort';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
@@ -17,9 +16,7 @@ export function Page({ skeleton, locale }: PageProps) {
return ( return (
<LocaleProvider locale={locale}> <LocaleProvider locale={locale}>
<TankPerformanceSort.Provider> <TankPerformanceSort.Provider>
<TankPerformanceEphemeral.Provider> <Content skeleton={skeleton} />
<Content skeleton={skeleton} />
</TankPerformanceEphemeral.Provider>
</TankPerformanceSort.Provider> </TankPerformanceSort.Provider>
</LocaleProvider> </LocaleProvider>
); );
@@ -1,17 +1,15 @@
import { map } from 'nanostores'; import { Varuna } from 'varuna';
import { import {
type TankopediaSortBy, type TankopediaSortBy,
type TankopediaSortDirection, type TankopediaSortDirection,
} from './tankopediaPersistent'; } from './tankopediaPersistent';
export interface TankopediaSort { export interface TankSort {
by: TankopediaSortBy; by: TankopediaSortBy;
direction: TankopediaSortDirection; direction: TankopediaSortDirection;
} }
export const initialTankopediaSort: TankopediaSort = { export const TankSort = new Varuna<TankSort>({
by: 'meta.none', by: 'meta.none',
direction: 'descending', direction: 'descending',
}; });
export const $tankopediaSort = map<TankopediaSort>(initialTankopediaSort);