feat: next safe generalization

This commit is contained in:
TrèsAbhi committed 2024-07-08 19:36:28 -05:00
1 parent eb87463f08
commit dae902fe03
19 files changed
+163 -155

No files matched your search

BIN
View File
Binary file not shown.
+40 -40
View File
@@ -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,
); );
}, },
+3 -3
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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) {
+71
View File
@@ -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 };
}
+1 -1
View File
@@ -8,6 +8,6 @@ export function useAveragesExclusionRatio() {
return ( return (
awaitedDiscoveredIdsDefinitions.count / awaitedDiscoveredIdsDefinitions.count /
awaitedAverageDefinitions.scanned_players awaitedAverageDefinitions.samples.total
); );
} }
+11 -58
View File
@@ -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));
};
}