mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: better layout
This commit is contained in:
1 parent
0c74cb90e9
commit
fe945cced1
11 files changed
+156
-137
No files matched your search
@@ -1,10 +1,9 @@
|
||||
import { TrashIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Flex, IconButton, Text } from '@radix-ui/themes';
|
||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash';
|
||||
import { use } from 'react';
|
||||
import { classIcons } from '../../../../components/ClassIcon';
|
||||
import { ExperimentIcon } from '../../../../components/ExperimentIcon';
|
||||
import { NAVBAR_HEIGHT } from '../../../../components/Navbar';
|
||||
import { ResearchedIcon } from '../../../../components/ResearchedIcon';
|
||||
import { ScienceIcon } from '../../../../components/ScienceIcon';
|
||||
import { ScienceOffIcon } from '../../../../components/ScienceOffIcon';
|
||||
@@ -23,19 +22,11 @@ export function FilterControl() {
|
||||
const filters = useTankopediaFilters();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={{ initial: 'row', sm: 'column' }}
|
||||
height="fit-content"
|
||||
gap="2"
|
||||
align={{ initial: 'start', sm: 'start' }}
|
||||
justify={{ initial: 'center', sm: 'start' }}
|
||||
position={{ initial: 'static', sm: 'sticky' }}
|
||||
style={{ top: `calc(${NAVBAR_HEIGHT}px + var(--space-3))` }}
|
||||
>
|
||||
<Flex height="fit-content" gap="2" align="start" justify="center">
|
||||
<Flex
|
||||
direction={{ initial: 'row', sm: 'column' }}
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-4)' }}
|
||||
style={{ borderRadius: 'var(--radius-5)' }}
|
||||
>
|
||||
<Flex direction={{ sm: 'row', initial: 'column' }}>
|
||||
{times(5, (index) => {
|
||||
@@ -51,7 +42,7 @@ export function FilterControl() {
|
||||
highContrast
|
||||
onClick={() =>
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.tier = draft.tier === tier ? undefined : tier;
|
||||
draft.tier = tier;
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -74,7 +65,7 @@ export function FilterControl() {
|
||||
highContrast
|
||||
onClick={() =>
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.tier = draft.tier === tier ? undefined : tier;
|
||||
draft.tier = tier;
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -88,7 +79,7 @@ export function FilterControl() {
|
||||
<Flex
|
||||
direction={{ initial: 'row', sm: 'column' }}
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-4)' }}
|
||||
style={{ borderRadius: 'var(--radius-5)' }}
|
||||
>
|
||||
<Flex direction={{ sm: 'row', initial: 'column' }}>
|
||||
{awaitedGameDefinitions.nations
|
||||
@@ -227,7 +218,7 @@ export function FilterControl() {
|
||||
}
|
||||
>
|
||||
<Text
|
||||
color={filters.type === 'premium' ? undefined : 'blue'}
|
||||
color={filters.type === 'collector' ? undefined : 'blue'}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
@@ -287,10 +278,9 @@ export function FilterControl() {
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Box display={{ initial: 'block', sm: 'none' }}>
|
||||
<Box>
|
||||
<IconButton
|
||||
color="red"
|
||||
ml="2"
|
||||
onClick={() =>
|
||||
useTankopediaFilters.setState(
|
||||
useTankopediaFilters.getInitialState(),
|
||||
@@ -298,23 +288,9 @@ export function FilterControl() {
|
||||
)
|
||||
}
|
||||
>
|
||||
<TrashIcon />
|
||||
<ReloadIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box display={{ initial: 'none', sm: 'block' }}>
|
||||
<Button
|
||||
color="red"
|
||||
mt="2"
|
||||
onClick={() =>
|
||||
useTankopediaFilters.setState(
|
||||
useTankopediaFilters.getInitialState(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
>
|
||||
<TrashIcon /> Clear
|
||||
</Button>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
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';
|
||||
@@ -11,13 +9,13 @@ 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 { FilterControl } from './FilterControl';
|
||||
import { SearchBar } from './SearchBar';
|
||||
import { TankCard } from './TankCard';
|
||||
import { TankCardWrapper } from './TankCardWrapper';
|
||||
@@ -30,10 +28,14 @@ export const Results = memo(() => {
|
||||
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 searchedTanks = useMemo(() => {
|
||||
if (search !== undefined) {
|
||||
const searchedRaw = go(search, awaitedTankNames, { key: 'combined' });
|
||||
const searchedRaw = go(search, awaitedTankNames, {
|
||||
key: 'combined',
|
||||
limit: 25,
|
||||
});
|
||||
const searchedTanks = searchedRaw.map(
|
||||
(result) => awaitedTankDefinitions[result.obj.id],
|
||||
);
|
||||
@@ -45,10 +47,6 @@ export const Results = memo(() => {
|
||||
let sorted: TankDefinition[];
|
||||
|
||||
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;
|
||||
@@ -332,25 +330,23 @@ export const Results = memo(() => {
|
||||
<Flex direction="column" gap="4" flexGrow="1">
|
||||
<SearchBar topResult={searchedTanks?.[0]} />
|
||||
|
||||
{!search && <FilterControl />}
|
||||
|
||||
{testing === 'only' && (
|
||||
<Callout.Root color="amber">
|
||||
<Callout.Icon>
|
||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>
|
||||
Tanks in testing are subject to change and many not represent the
|
||||
final product.
|
||||
</Callout.Text>
|
||||
</Callout.Root>
|
||||
<Flex justify="center" mt="4">
|
||||
<Callout.Root color="amber">
|
||||
<Callout.Icon>
|
||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>
|
||||
Tanks in testing are subject to change and many not represent the
|
||||
final product.
|
||||
</Callout.Text>
|
||||
</Callout.Root>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{!search &&
|
||||
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 && sort.by === 'meta.tier' && <TierCard tier={tier} />}
|
||||
|
||||
{(search || sort.by !== 'meta.tier') && (
|
||||
<TankCardWrapper py="4">
|
||||
|
||||
@@ -45,31 +45,32 @@ export function SearchBar({ topResult }: SearchBarProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap="2">
|
||||
<TextField.Root
|
||||
style={{ flex: 1 }}
|
||||
ref={input}
|
||||
placeholder="Search tanks..."
|
||||
size="3"
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<TextField.Slot>
|
||||
{searching ? <Spinner /> : <MagnifyingGlassIcon />}
|
||||
</TextField.Slot>
|
||||
|
||||
{topResult && (
|
||||
<Flex justify="center">
|
||||
<Flex gap="2" flexGrow="1">
|
||||
<TextField.Root
|
||||
style={{ flex: 1 }}
|
||||
ref={input}
|
||||
placeholder="Search tanks..."
|
||||
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>
|
||||
|
||||
<Sort />
|
||||
{topResult && (
|
||||
<TextField.Slot>
|
||||
<Link href={`/tools/tankopedia/${topResult.id}`}>
|
||||
<Button variant="ghost">
|
||||
{topResult.name} <CaretRightIcon />
|
||||
</Button>
|
||||
</Link>
|
||||
</TextField.Slot>
|
||||
)}
|
||||
</TextField.Root>
|
||||
|
||||
<Sort />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export function Sort() {
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="surface" size="3" color="gray">
|
||||
<Button variant="surface" color="gray">
|
||||
Sort
|
||||
<CaretDownIcon />
|
||||
</Button>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { Icon } from '@radix-ui/themes/dist/cjs/components/callout';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Link } from '../../../../components/Link';
|
||||
import { asset } from '../../../../core/blitzkit/asset';
|
||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
|
||||
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
|
||||
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
|
||||
import * as styles from './TankCard.css';
|
||||
|
||||
interface TankCardProps {
|
||||
@@ -17,7 +14,7 @@ interface TankCardProps {
|
||||
export function TankCard({ tank }: TankCardProps) {
|
||||
// const awaitedModelDefinitions = use(modelDefinitions);
|
||||
// const Icon = classIcons[tank.class];
|
||||
const link = useRef<HTMLAnchorElement>(null);
|
||||
// const link = useRef<HTMLAnchorElement>(null);
|
||||
// const sortBy = useTankopediaFilters((state) => state.sort.by);
|
||||
// const discriminator = useMemo(() => {
|
||||
// if (sortBy.startsWith('meta')) return undefined;
|
||||
@@ -46,20 +43,20 @@ export function TankCard({ tank }: TankCardProps) {
|
||||
// }
|
||||
// }, [sortBy]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = useTankopediaFilters.subscribe((filters) => {
|
||||
if (!link.current) return;
|
||||
// useEffect(() => {
|
||||
// const unsubscribe = useTankopediaFilters.subscribe((filters) => {
|
||||
// if (!link.current) return;
|
||||
|
||||
const visible = tankopediaFilterTank(filters, tank);
|
||||
link.current.style.display = visible ? 'flex' : 'none';
|
||||
});
|
||||
// const visible = tankopediaFilterTank(filters, tank);
|
||||
// link.current.style.display = visible ? 'flex' : 'none';
|
||||
// });
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
// return unsubscribe;
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<Link
|
||||
ref={link}
|
||||
// ref={link}
|
||||
size="1"
|
||||
color={
|
||||
tank.treeType === 'collector'
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Grid, GridProps } from '@radix-ui/themes';
|
||||
export function TankCardWrapper(props: GridProps) {
|
||||
return (
|
||||
<Grid
|
||||
flexGrow="1"
|
||||
columns="repeat(auto-fill, minmax(100px, 1fr))"
|
||||
gap="2"
|
||||
gapY="6"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Flex, Heading, Separator } from '@radix-ui/themes';
|
||||
import { memo, use, useEffect, useMemo, useRef } from 'react';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { memo, use, useMemo } from 'react';
|
||||
import { Link } from '../../../../components/Link';
|
||||
import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants';
|
||||
import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions';
|
||||
import {
|
||||
Tier,
|
||||
tanksDefinitionsArray,
|
||||
} from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
||||
import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank';
|
||||
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
|
||||
import { TankCard } from './TankCard';
|
||||
@@ -21,11 +21,11 @@ export const TierCard = memo(
|
||||
({ tier }: TierCardProps) => {
|
||||
const awaitedGameDefinitions = use(gameDefinitions);
|
||||
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const filters = useTankopediaFilters();
|
||||
const tanks = useMemo(
|
||||
() =>
|
||||
awaitedTanksDefinitionsArray
|
||||
.filter((tank) => tank.tier === tier)
|
||||
.filter((tank) => tankopediaFilterTank(filters, tank))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
treeTypeOrder.indexOf(a.treeType) -
|
||||
@@ -40,41 +40,38 @@ export const TierCard = memo(
|
||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||
awaitedGameDefinitions.nations.indexOf(b.nation),
|
||||
),
|
||||
[tier],
|
||||
[tier, filters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = useTankopediaFilters.subscribe((filters) => {
|
||||
if (!container.current) return;
|
||||
|
||||
const visible = tanks.some((tank) =>
|
||||
tankopediaFilterTank(filters, tank),
|
||||
);
|
||||
container.current.style.display = visible ? 'flex' : 'none';
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
if (tanks.length === 0)
|
||||
return (
|
||||
<Flex flexGrow="1" align="center" justify="center">
|
||||
<Text color="gray">
|
||||
No tanks found.{' '}
|
||||
<Link
|
||||
href="#"
|
||||
underline="always"
|
||||
color="red"
|
||||
onClick={() =>
|
||||
useTankopediaFilters.setState(
|
||||
useTankopediaFilters.getInitialState(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
>
|
||||
Try clearing filters
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={container}
|
||||
direction="column"
|
||||
gap="4"
|
||||
display={tanks.length === 0 ? 'none' : 'flex'}
|
||||
py="4"
|
||||
>
|
||||
<Flex align="center" mb="4" gap="4">
|
||||
<Heading weight="regular">Tier {TIER_ROMAN_NUMERALS[tier]}</Heading>
|
||||
<Separator style={{ flex: 1 }} />
|
||||
</Flex>
|
||||
|
||||
<TankCardWrapper>
|
||||
{tanks.map((tank) => (
|
||||
<TankCard key={tank.id} tank={tank} />
|
||||
))}
|
||||
</TankCardWrapper>
|
||||
</Flex>
|
||||
<TankCardWrapper mt="6">
|
||||
{tanks.map((tank) => (
|
||||
<TankCard key={tank.id} tank={tank} />
|
||||
))}
|
||||
</TankCardWrapper>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.tier === next.tier,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Flex, IconButton, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { Tier } from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
|
||||
import {
|
||||
mutateTankopediaFilters,
|
||||
useTankopediaFilters,
|
||||
} from '../../../../stores/tankopediaFilters';
|
||||
|
||||
export const TierFilter = memo(() => {
|
||||
const tierFilter = useTankopediaFilters((state) => state.tier);
|
||||
const sort = useTankopediaFilters((state) => state.sort);
|
||||
const search = useTankopediaFilters((state) => state.search);
|
||||
|
||||
if (sort.by !== 'meta.tier' || search) return null;
|
||||
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<Flex
|
||||
direction="row"
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
>
|
||||
{times(10, (index) => {
|
||||
const tier = (10 - index) as Tier;
|
||||
const selected = tierFilter === tier;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
key={tier}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={selected ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() =>
|
||||
mutateTankopediaFilters((draft) => {
|
||||
draft.tier = tier;
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text size="2">{TIER_ROMAN_NUMERALS[tier]}</Text>
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
@@ -2,14 +2,18 @@
|
||||
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import PageWrapper from '../../../components/PageWrapper';
|
||||
import { FilterControl } from './components/FilterControl';
|
||||
import { Results } from './components/Results';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<PageWrapper color="purple" size={1028 + 256}>
|
||||
<Flex gap="8" direction={{ initial: 'column', sm: 'row' }} pb="6">
|
||||
<FilterControl />
|
||||
<PageWrapper color="purple" size={1028}>
|
||||
<Flex
|
||||
gap="8"
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
py="4"
|
||||
flexGrow="1"
|
||||
>
|
||||
{/* <FilterControl /> */}
|
||||
<Results />
|
||||
</Flex>
|
||||
</PageWrapper>
|
||||
|
||||
@@ -107,7 +107,6 @@ export type ArmorPiercingLayer =
|
||||
|
||||
export const SORT_NAMES = {
|
||||
'meta.tier': 'tier',
|
||||
'meta.name': 'name',
|
||||
'survivability.health': 'health',
|
||||
'survivability.viewRange': 'view range',
|
||||
'survivability.camouflageStill': 'camouflage still',
|
||||
|
||||
@@ -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;
|
||||
@@ -23,6 +23,7 @@ export const useTankopediaFilters = create<TankopediaFilters>()(() => ({
|
||||
by: 'meta.tier',
|
||||
direction: 'descending',
|
||||
},
|
||||
tier: 10,
|
||||
}));
|
||||
|
||||
export function mutateTankopediaFilters(
|
||||
|
||||
Reference in new issue
Block a user