mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: next safe generalization
This commit is contained in:
1 parent
eb87463f08
commit
dae902fe03
19 files changed
+163
-155
No files matched your search
+40
-40
@@ -29,32 +29,32 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gltf-transform/core": "^3.9.0",
|
"@gltf-transform/core": "^3.10.1",
|
||||||
"@nivo/bar": "^0.87.0",
|
"@nivo/bar": "^0.87.0",
|
||||||
"@octokit/plugin-throttling": "^8.1.3",
|
"@octokit/plugin-throttling": "^8.2.0",
|
||||||
"@octokit/rest": "^20.0.1",
|
"@octokit/rest": "^20.1.1",
|
||||||
"@prisma/client": "^5.4.1",
|
"@prisma/client": "^5.16.1",
|
||||||
"@radix-ui/colors": "^3.0.0",
|
"@radix-ui/colors": "^3.0.0",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/themes": "^3.0.5",
|
"@radix-ui/themes": "^3.1.1",
|
||||||
"@react-three/drei": "^9.93.0",
|
"@react-three/drei": "^9.108.3",
|
||||||
"@react-three/fiber": "^8.15.14",
|
"@react-three/fiber": "^8.16.8",
|
||||||
"@resvg/resvg-js": "^2.4.1",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"@vanilla-extract/css": "^1.13.0",
|
"@vanilla-extract/css": "^1.15.3",
|
||||||
"@vanilla-extract/dynamic": "^2.1.1",
|
"@vanilla-extract/dynamic": "^2.1.1",
|
||||||
"adm-zip": "^0.5.12",
|
"adm-zip": "^0.5.14",
|
||||||
"bepaint": "^0.1.0-beta.3",
|
"bepaint": "^0.1.0-beta.3",
|
||||||
"csv-parse": "^5.5.6",
|
"csv-parse": "^5.5.6",
|
||||||
"decompress": "^4.2.1",
|
"decompress": "^4.2.1",
|
||||||
"discord.js": "^14.13.0",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.5",
|
||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.12",
|
||||||
"express": "^4.18.2",
|
"express": "^4.19.2",
|
||||||
"false": "^0.0.4",
|
"false": "^0.0.4",
|
||||||
"fuzzysort": "^2.0.4",
|
"fuzzysort": "^2.0.4",
|
||||||
"googleapis": "^134.0.0",
|
"googleapis": "^134.0.0",
|
||||||
"immer": "^10.0.2",
|
"immer": "^10.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lz4js": "^0.2.0",
|
"lz4js": "^0.2.0",
|
||||||
@@ -63,49 +63,49 @@
|
|||||||
"next": "^14.2.4",
|
"next": "^14.2.4",
|
||||||
"parse-dds": "^1.2.1",
|
"parse-dds": "^1.2.1",
|
||||||
"pickleparser": "^0.2.1",
|
"pickleparser": "^0.2.1",
|
||||||
"postprocessing": "^6.34.1",
|
"postprocessing": "^6.35.6",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"protobufjs": "^7.2.6",
|
"protobufjs": "^7.3.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-fuzzysort": "^1.0.1",
|
"react-fuzzysort": "^1.0.1",
|
||||||
"react-promise-suspense": "^0.3.4",
|
"react-promise-suspense": "^0.3.4",
|
||||||
"satori": "^0.10.13",
|
"satori": "^0.10.14",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"three": "0.159.0",
|
"three": "0.159.0",
|
||||||
"three-stdlib": "^2.29.4",
|
"three-stdlib": "^2.30.4",
|
||||||
"vite-node": "^0.34.5",
|
"vite-node": "^0.34.7",
|
||||||
"vitepress": "^1.0.0-rc.20",
|
"vitepress": "^1.3.0",
|
||||||
"vitepress-plugin-auto-sidebar": "^1.1.0",
|
"vitepress-plugin-auto-sidebar": "^1.2.0",
|
||||||
"yaml": "^2.3.4",
|
"yaml": "^2.4.5",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"@total-typescript/ts-reset": "^0.5.1",
|
||||||
"@types/adm-zip": "^0.5.5",
|
"@types/adm-zip": "^0.5.5",
|
||||||
"@types/bun": "^1.1.5",
|
"@types/bun": "^1.1.6",
|
||||||
"@types/decompress": "^4.2.7",
|
"@types/decompress": "^4.2.7",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.4",
|
||||||
"@types/express": "^4.17.18",
|
"@types/express": "^4.17.21",
|
||||||
"@types/js-yaml": "^4.0.8",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash": "^4.14.199",
|
"@types/lodash": "^4.17.6",
|
||||||
"@types/lz4": "^0.6.4",
|
"@types/lz4": "^0.6.4",
|
||||||
"@types/lz4js": "^0.2.1",
|
"@types/lz4js": "^0.2.1",
|
||||||
"@types/markdown-escape": "^1.1.0",
|
"@types/markdown-escape": "^1.1.3",
|
||||||
"@types/node": "^20.6.5",
|
"@types/node": "^20.14.10",
|
||||||
"@types/parse-dds": "^1.0.3",
|
"@types/parse-dds": "^1.0.3",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.5",
|
||||||
"@types/progress": "^2.0.7",
|
"@types/progress": "^2.0.7",
|
||||||
"@types/react": "^18.2.22",
|
"@types/react": "^18.3.3",
|
||||||
"@vanilla-extract/next-plugin": "^2.3.1",
|
"@vanilla-extract/next-plugin": "^2.4.3",
|
||||||
"compare-versions": "^6.1.0",
|
"compare-versions": "^6.1.0",
|
||||||
"crc": "^4.3.2",
|
"crc": "^4.3.2",
|
||||||
"fast-xml-parser": "^4.3.2",
|
"fast-xml-parser": "^4.4.0",
|
||||||
"knip": "^3.4.0",
|
"knip": "^3.13.2",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.3.2",
|
||||||
"prisma": "^5.4.1",
|
"prisma": "^5.16.1",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.5.3"
|
||||||
},
|
},
|
||||||
"packageManager": "[email protected]",
|
"packageManager": "[email protected]",
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export function Info() {
|
|||||||
const awaitedAverageDefinitions = use(averageDefinitions);
|
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||||
const numberFormat = Intl.NumberFormat(undefined, { notation: 'compact' });
|
const numberFormat = Intl.NumberFormat(undefined, { notation: 'compact' });
|
||||||
const ratio = useAveragesExclusionRatio();
|
const ratio = useAveragesExclusionRatio();
|
||||||
const samples = ratio * awaitedAverageDefinitions.sampled_players;
|
const samples = ratio * awaitedAverageDefinitions.samples.d_120;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center">
|
<Flex justify="center">
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{numberFormat.format(Math.round(ratio * averages.samples))}
|
{numberFormat.format(Math.round(ratio * averages.samples.d_30))}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{numberFormat.format(
|
{numberFormat.format(
|
||||||
Math.round(ratio * averages.mu.battles * averages.samples),
|
Math.round(ratio * averages.mu.battles * averages.samples.total),
|
||||||
)}
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Suspense, use, useEffect, useMemo, useState } from 'react';
|
|||||||
import { averageDefinitionsArray } from '../../../../core/blitzkit/averageDefinitions';
|
import { averageDefinitionsArray } from '../../../../core/blitzkit/averageDefinitions';
|
||||||
import { filterTank } from '../../../../core/blitzkit/filterTank';
|
import { filterTank } from '../../../../core/blitzkit/filterTank';
|
||||||
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import { useTankFilters } from '../../../../stores/tankFilters';
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
import { useTankPerformanceSort } from '../../../../stores/tankPerformanceSort';
|
import { useTankPerformanceSort } from '../../../../stores/tankPerformanceSort';
|
||||||
import { RowLoader } from './RowLoader';
|
import { RowLoader } from './RowLoader';
|
||||||
import { TankRow } from './TankRow';
|
import { TankRow } from './TankRow';
|
||||||
@@ -18,7 +18,7 @@ const DEFAULT_LOADED_ROWS = 25;
|
|||||||
export function Tanks() {
|
export function Tanks() {
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
||||||
const filters = useTankFilters();
|
const filters = TankFilters.use();
|
||||||
const sort = useTankPerformanceSort();
|
const sort = useTankPerformanceSort();
|
||||||
const tanksSorted = useMemo(() => {
|
const tanksSorted = useMemo(() => {
|
||||||
switch (sort.type) {
|
switch (sort.type) {
|
||||||
@@ -31,7 +31,7 @@ export function Tanks() {
|
|||||||
return awaitedAverageDefinitionsArray.sort(
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.samples * a.mu.battles - b.samples * b.mu.battles),
|
(a.samples.total * a.mu.battles - b.samples.total * b.mu.battles),
|
||||||
);
|
);
|
||||||
case 'capturePoints':
|
case 'capturePoints':
|
||||||
return awaitedAverageDefinitionsArray.sort(
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
@@ -81,7 +81,7 @@ export function Tanks() {
|
|||||||
);
|
);
|
||||||
case 'players':
|
case 'players':
|
||||||
return awaitedAverageDefinitionsArray.sort(
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
(a, b) => sort.direction * (a.samples - b.samples),
|
(a, b) => sort.direction * (a.samples.d_30 - b.samples.d_30),
|
||||||
);
|
);
|
||||||
case 'spots':
|
case 'spots':
|
||||||
return awaitedAverageDefinitionsArray.sort(
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export function TierBreakdown() {
|
|||||||
|
|
||||||
tierTanks.forEach((stats) => {
|
tierTanks.forEach((stats) => {
|
||||||
const tank = awaitedTankDefinitions[stats.id];
|
const tank = awaitedTankDefinitions[stats.id];
|
||||||
populations[tank.class] += stats.mu.battles * stats.samples;
|
populations[tank.class] +=
|
||||||
|
stats.mu.battles * stats.samples.d_30;
|
||||||
});
|
});
|
||||||
|
|
||||||
TANK_CLASSES.forEach((tankClass) => {
|
TANK_CLASSES.forEach((tankClass) => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const Total = memo<TotalProps>(
|
|||||||
const sum = useCallback(
|
const sum = useCallback(
|
||||||
(slice: (tank: AverageDefinitionsAllStats) => number) => {
|
(slice: (tank: AverageDefinitionsAllStats) => number) => {
|
||||||
return tanks.reduce(
|
return tanks.reduce(
|
||||||
(acc, tank) => acc + tank.samples * slice(tank.mu),
|
(acc, tank) => acc + tank.samples.total * slice(tank.mu),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { TankFiltersProvider } from '../../../stores/tankFilters';
|
import * as TankFilters from '../../../stores/tankFilters';
|
||||||
import { FilterControl } from '../tankopedia/components/FilterControl';
|
import { FilterControl } from '../tankopedia/components/FilterControl';
|
||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { TankTable } from './components/Table';
|
import { TankTable } from './components/Table';
|
||||||
@@ -7,13 +7,13 @@ import { TierBreakdown } from './components/TierBreakdown';
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<TankFiltersProvider>
|
<TankFilters.Provider>
|
||||||
<PageWrapper color="jade" noMaxWidth>
|
<PageWrapper color="jade" noMaxWidth>
|
||||||
<Info />
|
<Info />
|
||||||
<TierBreakdown />
|
<TierBreakdown />
|
||||||
<FilterControl />
|
<FilterControl />
|
||||||
<TankTable />
|
<TankTable />
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
</TankFiltersProvider>
|
</TankFilters.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -14,11 +14,7 @@ import { asset } from '../../../../core/blitzkit/asset';
|
|||||||
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
|
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
|
||||||
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
|
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
||||||
import {
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
useTankFilters,
|
|
||||||
useTankFiltersContext,
|
|
||||||
useTankFiltersMutation,
|
|
||||||
} from '../../../../stores/tankFilters';
|
|
||||||
|
|
||||||
interface FilterControlProps {
|
interface FilterControlProps {
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
@@ -26,9 +22,9 @@ interface FilterControlProps {
|
|||||||
|
|
||||||
export function FilterControl({ compact }: FilterControlProps) {
|
export function FilterControl({ compact }: FilterControlProps) {
|
||||||
const awaitedGameDefinitions = use(gameDefinitions);
|
const awaitedGameDefinitions = use(gameDefinitions);
|
||||||
const filters = useTankFilters();
|
const filters = TankFilters.use();
|
||||||
const mutateTankFilters = useTankFiltersMutation();
|
const mutateTankFilters = TankFilters.useMutation();
|
||||||
const tankFiltersContext = useTankFiltersContext();
|
const tankFiltersStore = TankFilters.useStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex height="fit-content" gap="2" align="start" justify="center">
|
<Flex height="fit-content" gap="2" align="start" justify="center">
|
||||||
@@ -325,10 +321,7 @@ export function FilterControl({ compact }: FilterControlProps) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
tankFiltersContext.setState(
|
tankFiltersStore.setState(tankFiltersStore.getInitialState(), true);
|
||||||
tankFiltersContext.getInitialState(),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReloadIcon />
|
<ReloadIcon />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Flex, Link, Text } from '@radix-ui/themes';
|
import { Flex, Link, Text } from '@radix-ui/themes';
|
||||||
import { useTankFiltersContext } from '../../../../stores/tankFilters';
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
|
|
||||||
interface NoResultsProps {
|
interface NoResultsProps {
|
||||||
type?: 'filters' | 'search';
|
type?: 'filters' | 'search';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NoResults({ type = 'filters' }: NoResultsProps) {
|
export function NoResults({ type = 'filters' }: NoResultsProps) {
|
||||||
const tankFiltersContext = useTankFiltersContext();
|
const tankFiltersStore = TankFilters.useStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexGrow="1" align="center" justify="center">
|
<Flex flexGrow="1" align="center" justify="center">
|
||||||
@@ -17,10 +17,7 @@ export function NoResults({ type = 'filters' }: NoResultsProps) {
|
|||||||
underline="always"
|
underline="always"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
tankFiltersContext.setState(
|
tankFiltersStore.setState(tankFiltersStore.getInitialState(), true)
|
||||||
tankFiltersContext.getInitialState(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{type === 'filters' ? 'Try clearing filters' : 'Try searching again'}
|
{type === 'filters' ? 'Try clearing filters' : 'Try searching again'}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||||
import { Button, Link, TextField } from '@radix-ui/themes';
|
import { Button, Link, TextField } from '@radix-ui/themes';
|
||||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import { useTankFilters } from '../../../../stores/tankFilters';
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
|
|
||||||
interface QuickLinkProps {
|
interface QuickLinkProps {
|
||||||
topResult?: TankDefinition;
|
topResult?: TankDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QuickLink({ topResult }: QuickLinkProps) {
|
export function QuickLink({ topResult }: QuickLinkProps) {
|
||||||
const search = useTankFilters((state) => state.search);
|
const search = TankFilters.use((state) => state.search);
|
||||||
const searching = useTankFilters((state) => state.searching);
|
const searching = TankFilters.use((state) => state.searching);
|
||||||
|
|
||||||
if (!search || !topResult || searching) return null;
|
if (!search || !topResult || searching) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { debounce } from 'lodash';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KeyboardEventHandler, useCallback, useRef } from 'react';
|
import { KeyboardEventHandler, useCallback, useRef } from 'react';
|
||||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import {
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
useTankFilters,
|
|
||||||
useTankFiltersContext,
|
|
||||||
} from '../../../../stores/tankFilters';
|
|
||||||
import { QuickLink } from './QuickLink';
|
import { QuickLink } from './QuickLink';
|
||||||
import { Sort } from './Sort';
|
import { Sort } from './Sort';
|
||||||
|
|
||||||
@@ -17,27 +14,27 @@ interface SearchBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SearchBar({ topResult, onSelect }: SearchBarProps) {
|
export function SearchBar({ topResult, onSelect }: SearchBarProps) {
|
||||||
const tankFiltersContext = useTankFiltersContext();
|
const tankFiltersStore = TankFilters.useStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const searching = useTankFilters((state) => state.searching);
|
const searching = TankFilters.use((state) => state.searching);
|
||||||
const performSearch = useCallback(
|
const performSearch = useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
tankFiltersContext.setState({ searching: false });
|
tankFiltersStore.setState({ searching: false });
|
||||||
|
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
|
|
||||||
const sanitized = input.current.value.trim();
|
const sanitized = input.current.value.trim();
|
||||||
|
|
||||||
tankFiltersContext.setState({
|
tankFiltersStore.setState({
|
||||||
search: sanitized.length === 0 ? undefined : sanitized,
|
search: sanitized.length === 0 ? undefined : sanitized,
|
||||||
});
|
});
|
||||||
}, 500),
|
}, 500),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const handleChange = useCallback(() => {
|
const handleChange = useCallback(() => {
|
||||||
if (!tankFiltersContext.getState().searching) {
|
if (!tankFiltersStore.getState().searching) {
|
||||||
tankFiltersContext.setState({ searching: true });
|
tankFiltersStore.setState({ searching: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
performSearch();
|
performSearch();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
} from '../../../../../core/blitzkit/tankDefinitions';
|
} from '../../../../../core/blitzkit/tankDefinitions';
|
||||||
import { unionBoundingBox } from '../../../../../core/blitzkit/unionBoundingBox';
|
import { unionBoundingBox } from '../../../../../core/blitzkit/unionBoundingBox';
|
||||||
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
||||||
import { useTankFilters } from '../../../../../stores/tankFilters';
|
import * as TankFilters from '../../../../../stores/tankFilters';
|
||||||
import { SORT_NAMES, SORT_UNITS } from '../../../../../stores/tankopedia';
|
import { SORT_NAMES, SORT_UNITS } from '../../../../../stores/tankopedia';
|
||||||
import { useTankopediaSort } from '../../../../../stores/tankopediaSort';
|
import { useTankopediaSort } from '../../../../../stores/tankopediaSort';
|
||||||
import { FilterControl } from '../FilterControl';
|
import { FilterControl } from '../FilterControl';
|
||||||
@@ -45,7 +45,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
||||||
const awaitedTankNames = use(tankNames);
|
const awaitedTankNames = use(tankNames);
|
||||||
const filters = useTankFilters();
|
const filters = TankFilters.use();
|
||||||
const sort = useTankopediaSort();
|
const sort = useTankopediaSort();
|
||||||
const tanks = useMemo(() => {
|
const tanks = useMemo(() => {
|
||||||
if (filters.search === undefined) {
|
if (filters.search === undefined) {
|
||||||
|
|||||||
@@ -3,17 +3,14 @@ import { times } from 'lodash';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
|
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
||||||
import {
|
import * as TankFilters from '../../../../stores/tankFilters';
|
||||||
useTankFilters,
|
|
||||||
useTankFiltersMutation,
|
|
||||||
} from '../../../../stores/tankFilters';
|
|
||||||
import { useTankopediaSort } from '../../../../stores/tankopediaSort';
|
import { useTankopediaSort } from '../../../../stores/tankopediaSort';
|
||||||
|
|
||||||
export const TierFilter = memo(() => {
|
export const TierFilter = memo(() => {
|
||||||
const tierFilter = useTankFilters((state) => state.tiers);
|
const tierFilter = TankFilters.use((state) => state.tiers);
|
||||||
const sort = useTankopediaSort();
|
const sort = useTankopediaSort();
|
||||||
const search = useTankFilters((state) => state.search);
|
const search = TankFilters.use((state) => state.search);
|
||||||
const mutateTankFilters = useTankFiltersMutation();
|
const mutateTankFilters = TankFilters.useMutation();
|
||||||
|
|
||||||
if (sort.by !== 'meta.none' || search) return null;
|
if (sort.by !== 'meta.none' || search) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
import { AdMidSectionResponsive } from '../../../components/AdMidSectionResponsive';
|
import { AdMidSectionResponsive } from '../../../components/AdMidSectionResponsive';
|
||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { useAdExempt } from '../../../hooks/useAdExempt';
|
import { useAdExempt } from '../../../hooks/useAdExempt';
|
||||||
import { TankFiltersProvider } from '../../../stores/tankFilters';
|
import * as TankFilters from '../../../stores/tankFilters';
|
||||||
import { TankSearch } from './components/TankSearch';
|
import { TankSearch } from './components/TankSearch';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const exempt = useAdExempt();
|
const exempt = useAdExempt();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TankFiltersProvider>
|
<TankFilters.Provider>
|
||||||
<PageWrapper color="purple" size={1028}>
|
<PageWrapper color="purple" size={1028}>
|
||||||
<TankSearch />
|
<TankSearch />
|
||||||
{!exempt && <AdMidSectionResponsive />}
|
{!exempt && <AdMidSectionResponsive />}
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
</TankFiltersProvider>
|
</TankFilters.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TankFilters } from '../../stores/tankFilters';
|
|
||||||
import { TankDefinition } from './tankDefinitions';
|
import { TankDefinition } from './tankDefinitions';
|
||||||
|
|
||||||
export function filterTank(filters: TankFilters, tank: TankDefinition) {
|
export function filterTank(filters: TankFilters, tank: TankDefinition) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Draft, produce } from 'immer';
|
||||||
|
import { Context, createContext, ReactNode, useContext, useRef } from 'react';
|
||||||
|
import { createStore, StoreApi, useStore as useStoreZustand } from 'zustand';
|
||||||
|
|
||||||
|
interface StoreProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStoreContext<Type>() {
|
||||||
|
return createContext<StoreApi<Type> | undefined>(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStoreProvider<Type>(
|
||||||
|
initialState: Type,
|
||||||
|
Context: Context<StoreApi<Type> | undefined>,
|
||||||
|
) {
|
||||||
|
return function Provider({ children }: StoreProviderProps) {
|
||||||
|
const storeRef = useRef<StoreApi<Type>>();
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = createStore<Type>()(() => ({ ...initialState }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={storeRef.current}>{children}</Context.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNextSafeStore<Type>(initialState: Type) {
|
||||||
|
const Context = createContext<StoreApi<Type> | undefined>(undefined);
|
||||||
|
|
||||||
|
function Provider({ children }: StoreProviderProps) {
|
||||||
|
const storeRef = useRef<StoreApi<Type>>();
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = createStore<Type>()(() => ({ ...initialState }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={storeRef.current}>{children}</Context.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useStore() {
|
||||||
|
const store = useContext(Context);
|
||||||
|
if (!store) {
|
||||||
|
throw new Error('useStoreContext must be used within Provider');
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function use<Slice = Type>(
|
||||||
|
selector: (store: Type) => Slice = (state) => state as unknown as Slice,
|
||||||
|
): Slice {
|
||||||
|
const store = useStore();
|
||||||
|
return useStoreZustand(store, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useMutation() {
|
||||||
|
const counterStoreContext = useStore();
|
||||||
|
|
||||||
|
return function (recipe: (draft: Draft<Type>) => void) {
|
||||||
|
counterStoreContext.setState(
|
||||||
|
produce<Type>((draft) => {
|
||||||
|
recipe(draft);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { Provider, use, useMutation, useStore };
|
||||||
|
}
|
||||||
@@ -8,6 +8,6 @@ export function useAveragesExclusionRatio() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
awaitedDiscoveredIdsDefinitions.count /
|
awaitedDiscoveredIdsDefinitions.count /
|
||||||
awaitedAverageDefinitions.scanned_players
|
awaitedAverageDefinitions.samples.total
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+11
-58
@@ -1,10 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { produce } from 'immer';
|
|
||||||
import { createContext, ReactNode, useContext, useRef } from 'react';
|
|
||||||
import { createStore, StoreApi, useStore } from 'zustand';
|
|
||||||
import { TankClass, TreeType } from '../components/Tanks';
|
import { TankClass, TreeType } from '../components/Tanks';
|
||||||
import { Tier } from '../core/blitzkit/tankDefinitions';
|
import { Tier } from '../core/blitzkit/tankDefinitions';
|
||||||
|
import { createNextSafeStore } from '../core/zustand/createNextSafeStore';
|
||||||
|
|
||||||
export interface TankFilters {
|
export interface TankFilters {
|
||||||
tiers: Tier[];
|
tiers: Tier[];
|
||||||
@@ -16,58 +14,13 @@ export interface TankFilters {
|
|||||||
searching: boolean;
|
searching: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tankFiltersInitialState: TankFilters = {
|
export const { Provider, use, useMutation, useStore } =
|
||||||
testing: 'include',
|
createNextSafeStore<TankFilters>({
|
||||||
searching: false,
|
tiers: [],
|
||||||
classes: [],
|
nations: [],
|
||||||
nations: [],
|
classes: [],
|
||||||
tiers: [],
|
types: [],
|
||||||
types: [],
|
testing: 'include',
|
||||||
};
|
search: undefined,
|
||||||
|
searching: false,
|
||||||
const createTankFiltersStore = (
|
});
|
||||||
initialState: TankFilters = tankFiltersInitialState,
|
|
||||||
) => createStore<TankFilters>()(() => ({ ...initialState }));
|
|
||||||
|
|
||||||
const TankFiltersContext = createContext<StoreApi<TankFilters> | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
interface TankFiltersProviderProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TankFiltersProvider({ children }: TankFiltersProviderProps) {
|
|
||||||
const storeRef = useRef<StoreApi<TankFilters>>();
|
|
||||||
if (!storeRef.current) storeRef.current = createTankFiltersStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TankFiltersContext.Provider value={storeRef.current}>
|
|
||||||
{children}
|
|
||||||
</TankFiltersContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTankFiltersContext() {
|
|
||||||
const context = useContext(TankFiltersContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useTankFilters must be used within TankFiltersContext');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTankFilters<Type = TankFilters>(
|
|
||||||
selector: (store: TankFilters) => Type = (store) => store as Type,
|
|
||||||
): Type {
|
|
||||||
const counterStoreContext = useTankFiltersContext();
|
|
||||||
|
|
||||||
return useStore(counterStoreContext, selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTankFiltersMutation() {
|
|
||||||
const counterStoreContext = useTankFiltersContext();
|
|
||||||
|
|
||||||
return function (recipe: (draft: TankFilters) => void) {
|
|
||||||
counterStoreContext.setState(produce(recipe));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user