mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: translate tank search
This commit is contained in:
1 parent
c7856364ad
commit
5a65fee1d1
9 files changed
+231
-355
No files matched your search
@@ -716,6 +716,68 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"copy_button": {
|
"copy_button": {
|
||||||
"copied": "Copied!"
|
"copied": "Copied!"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tank_search": {
|
||||||
|
"search_bar_hint": "Search tanks...",
|
||||||
|
"recent": "Recently viewed",
|
||||||
|
"select_all": "Select all",
|
||||||
|
"count_singular": "1 tank",
|
||||||
|
"count_plural": "%s1 tanks",
|
||||||
|
"sorting_by": "Sorting by %s1",
|
||||||
|
"sort": {
|
||||||
|
"meta.none": "none",
|
||||||
|
"survivability.health": "health",
|
||||||
|
"survivability.viewRange": "view range",
|
||||||
|
"survivability.camouflageStill": "camouflage still",
|
||||||
|
"survivability.camouflageMoving": "camouflage moving",
|
||||||
|
"survivability.camouflageShooting": "camouflage shooting",
|
||||||
|
"survivability.volume": "volume",
|
||||||
|
"survivability.length": "length",
|
||||||
|
"fire.dpm": "standard DPM",
|
||||||
|
"fire.dpmPremium": "premium DPM",
|
||||||
|
"fire.reload": "reload",
|
||||||
|
"fire.caliber": "caliber",
|
||||||
|
"fire.standardPenetration": "standard penetration",
|
||||||
|
"fire.premiumPenetration": "premium penetration",
|
||||||
|
"fire.damage": "damage",
|
||||||
|
"fire.moduleDamage": "module damage",
|
||||||
|
"fire.shellVelocity": "shell velocity",
|
||||||
|
"fire.shellRange": "shell range",
|
||||||
|
"fire.shellCapacity": "shell capacity",
|
||||||
|
"fire.aimTime": "aim time",
|
||||||
|
"fire.dispersionStill": "dispersion still",
|
||||||
|
"fire.dispersionMoving": "dispersion moving",
|
||||||
|
"fire.gunDepression": "gun depression",
|
||||||
|
"fire.gunElevation": "gun elevation",
|
||||||
|
"maneuverability.forwardsSpeed": "forwards speed",
|
||||||
|
"maneuverability.backwardsSpeed": "backwards speed",
|
||||||
|
"maneuverability.power": "power",
|
||||||
|
"maneuverability.powerToWeight": "power to weight",
|
||||||
|
"maneuverability.weight": "weight",
|
||||||
|
"maneuverability.traverseSpeed": "traverse speed"
|
||||||
|
},
|
||||||
|
"sort_dropdown": {
|
||||||
|
"label": "Sort",
|
||||||
|
"groups": {
|
||||||
|
"sort": {
|
||||||
|
"no_sort": "No sort",
|
||||||
|
"label": "Sort by",
|
||||||
|
"fire": "Fire",
|
||||||
|
"maneuverability": "Maneuverability",
|
||||||
|
"survivability": "Survivability"
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"label": "Direction",
|
||||||
|
"ascending": "Ascending",
|
||||||
|
"descending": "Descending"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"warnings": {
|
||||||
|
"test": "Tanks in testing are subject to change and may not represent the final product."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -743,7 +805,24 @@
|
|||||||
"name": "Compare",
|
"name": "Compare",
|
||||||
"button": "Compare tanks",
|
"button": "Compare tanks",
|
||||||
"description": "Compare tank statistics",
|
"description": "Compare tank statistics",
|
||||||
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz"
|
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
|
||||||
|
"actions": {
|
||||||
|
"add": {
|
||||||
|
"button": "Add",
|
||||||
|
"title": "Add tanks",
|
||||||
|
"description": "Select tanks to add to comparison"
|
||||||
|
},
|
||||||
|
"clear": "Clear",
|
||||||
|
"deltas": {
|
||||||
|
"none": "No deltas",
|
||||||
|
"percentage": "Percentage",
|
||||||
|
"absolute": "Absolute"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no_tanks": {
|
||||||
|
"title": "No tanks selected",
|
||||||
|
"body": "Press the Add button to get started"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"performance": {
|
"performance": {
|
||||||
"name": "Performance",
|
"name": "Performance",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
|
|||||||
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
||||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||||
|
import { useLocale } from '../../hooks/useLocale';
|
||||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||||
import {
|
import {
|
||||||
ComparePersistent,
|
ComparePersistent,
|
||||||
@@ -23,6 +24,7 @@ export function Controls({
|
|||||||
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
|
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
|
||||||
const mutateCompareEphemeral = CompareEphemeral.useMutation();
|
const mutateCompareEphemeral = CompareEphemeral.useMutation();
|
||||||
const mutateComparePersistent = ComparePersistent.useMutation();
|
const mutateComparePersistent = ComparePersistent.useMutation();
|
||||||
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2" wrap="wrap" justify="center">
|
<Flex gap="2" wrap="wrap" justify="center">
|
||||||
@@ -32,14 +34,16 @@ export function Controls({
|
|||||||
>
|
>
|
||||||
<Dialog.Trigger>
|
<Dialog.Trigger>
|
||||||
<Button variant="soft">
|
<Button variant="soft">
|
||||||
<PlusIcon /> Add
|
<PlusIcon /> {strings.website.tools.compare.actions.add.button}
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|
||||||
<Dialog.Content>
|
<Dialog.Content>
|
||||||
<Dialog.Title align="center">Add tanks</Dialog.Title>
|
<Dialog.Title align="center">
|
||||||
|
{strings.website.tools.compare.actions.add.title}
|
||||||
|
</Dialog.Title>
|
||||||
<Dialog.Description align="center">
|
<Dialog.Description align="center">
|
||||||
Select tanks to add to comparison
|
{strings.website.tools.compare.actions.add.description}
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
|
|
||||||
<Flex gap="4" direction="column">
|
<Flex gap="4" direction="column">
|
||||||
@@ -87,7 +91,7 @@ export function Controls({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon /> Clear
|
<TrashIcon /> {strings.website.tools.compare.actions.clear}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<SegmentedControl.Root
|
<SegmentedControl.Root
|
||||||
@@ -99,13 +103,13 @@ export function Controls({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SegmentedControl.Item value={'none' satisfies DeltaMode}>
|
<SegmentedControl.Item value={'none' satisfies DeltaMode}>
|
||||||
No deltas
|
{strings.website.tools.compare.actions.deltas.none}
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={'percentage' satisfies DeltaMode}>
|
<SegmentedControl.Item value={'percentage' satisfies DeltaMode}>
|
||||||
Percentage
|
{strings.website.tools.compare.actions.deltas.percentage}
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={'absolute' satisfies DeltaMode}>
|
<SegmentedControl.Item value={'absolute' satisfies DeltaMode}>
|
||||||
Absolute
|
{strings.website.tools.compare.actions.deltas.absolute}
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
</SegmentedControl.Root>
|
</SegmentedControl.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
|
|||||||
import { Flex, Spinner, TextField } from '@radix-ui/themes';
|
import { Flex, Spinner, TextField } from '@radix-ui/themes';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { type KeyboardEventHandler, useCallback, useRef } from 'react';
|
import { type KeyboardEventHandler, useCallback, useRef } from 'react';
|
||||||
|
import { useLocale } from '../../../hooks/useLocale';
|
||||||
import { $tankFilters } from '../../../stores/tankFilters';
|
import { $tankFilters } from '../../../stores/tankFilters';
|
||||||
import { QuickLink } from './QuickLink';
|
import { QuickLink } from './QuickLink';
|
||||||
import { Sort } from './Sort';
|
import { Sort } from './Sort';
|
||||||
@@ -14,6 +15,7 @@ interface SearchBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SearchBar({ topResult, onSelect }: SearchBarProps) {
|
export function SearchBar({ topResult, onSelect }: SearchBarProps) {
|
||||||
|
const { strings } = useLocale();
|
||||||
const tankFilters = useStore($tankFilters);
|
const tankFilters = useStore($tankFilters);
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const performSearch = useCallback(
|
const performSearch = useCallback(
|
||||||
@@ -67,7 +69,7 @@ export function SearchBar({ topResult, onSelect }: SearchBarProps) {
|
|||||||
defaultValue={tankFilters.search}
|
defaultValue={tankFilters.search}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
ref={input}
|
ref={input}
|
||||||
placeholder="Search tanks..."
|
placeholder={strings.website.common.tank_search.search_bar_hint}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,308 +1,122 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
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 type { TankopediaSortDirection } from '../../../stores/tankopediaPersistent';
|
import { useLocale } from '../../../hooks/useLocale';
|
||||||
|
import type {
|
||||||
|
TankopediaSortBy,
|
||||||
|
TankopediaSortDirection,
|
||||||
|
} from '../../../stores/tankopediaPersistent';
|
||||||
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
||||||
|
|
||||||
|
interface ItemProps {
|
||||||
|
by: TankopediaSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemProps {
|
||||||
|
by: TankopediaSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item({ by }: ItemProps) {
|
||||||
|
const { strings } = useLocale();
|
||||||
|
const tankopediaSort = useStore($tankopediaSort);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.CheckboxItem
|
||||||
|
checked={tankopediaSort.by === by}
|
||||||
|
onClick={() => {
|
||||||
|
$tankopediaSort.setKey('by', by);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{strings.website.common.tank_search.sort[by]}
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Sort() {
|
export function Sort() {
|
||||||
const tankopediaSort = useStore($tankopediaSort);
|
const tankopediaSort = useStore($tankopediaSort);
|
||||||
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root modal={false}>
|
<DropdownMenu.Root modal={false}>
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<Button variant="surface" color="gray">
|
<Button variant="surface" color="gray">
|
||||||
Sort
|
{strings.website.common.tank_search.sort_dropdown.label}
|
||||||
<CaretDownIcon />
|
<CaretDownIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
<DropdownMenu.Content align="end">
|
<DropdownMenu.Content align="end">
|
||||||
<DropdownMenu.CheckboxItem
|
<DropdownMenu.Label>
|
||||||
checked={tankopediaSort.by === 'meta.none'}
|
{strings.website.common.tank_search.sort_dropdown.groups.sort.label}
|
||||||
onClick={() => {
|
</DropdownMenu.Label>
|
||||||
// mutateTankopediaSort((draft) => {
|
|
||||||
// draft.by = 'meta.none';
|
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'meta.none');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
No sort
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
|
|
||||||
<DropdownMenu.Label>Sort by</DropdownMenu.Label>
|
<Item by="meta.none" />
|
||||||
|
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
<DropdownMenu.SubTrigger>Fire</DropdownMenu.SubTrigger>
|
<DropdownMenu.SubTrigger>
|
||||||
|
{strings.website.common.tank_search.sort_dropdown.groups.sort.fire}
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
<DropdownMenu.SubContent>
|
<DropdownMenu.SubContent>
|
||||||
<DropdownMenu.CheckboxItem
|
<Item by="fire.dpm" />
|
||||||
checked={tankopediaSort.by === 'fire.dpm'}
|
<Item by="fire.dpmPremium" />
|
||||||
onClick={() => {
|
<Item by="fire.reload" />
|
||||||
// mutateTankopediaSort((draft) => {
|
<Item by="fire.caliber" />
|
||||||
// draft.by = 'fire.dpm';
|
<Item by="fire.standardPenetration" />
|
||||||
// });
|
<Item by="fire.premiumPenetration" />
|
||||||
$tankopediaSort.setKey('by', 'fire.dpm');
|
<Item by="fire.damage" />
|
||||||
}}
|
<Item by="fire.moduleDamage" />
|
||||||
>
|
<Item by="fire.shellVelocity" />
|
||||||
Standard DPM
|
<Item by="fire.shellRange" />
|
||||||
</DropdownMenu.CheckboxItem>
|
<Item by="fire.shellCapacity" />
|
||||||
<DropdownMenu.CheckboxItem
|
<Item by="fire.aimTime" />
|
||||||
checked={tankopediaSort.by === 'fire.dpmPremium'}
|
<Item by="fire.dispersionStill" />
|
||||||
onClick={() => {
|
<Item by="fire.dispersionMoving" />
|
||||||
// mutateTankopediaSort((draft) => {
|
<Item by="fire.gunDepression" />
|
||||||
// draft.by = 'fire.dpmPremium';
|
<Item by="fire.gunElevation" />
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'fire.dpmPremium');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Premium DPM
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.reload'}
|
|
||||||
onClick={() => {
|
|
||||||
// mutateTankopediaSort((draft) => {
|
|
||||||
// draft.by = 'fire.reload';
|
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'fire.reload');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reload
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.caliber'}
|
|
||||||
onClick={() => {
|
|
||||||
// mutateTankopediaSort((draft) => {
|
|
||||||
// draft.by = 'fire.caliber';
|
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'fire.caliber');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Caliber
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.standardPenetration'}
|
|
||||||
onClick={() => {
|
|
||||||
// mutateTankopediaSort((draft) => {
|
|
||||||
// draft.by = 'fire.standardPenetration';
|
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'fire.standardPenetration');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Standard penetration
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.premiumPenetration'}
|
|
||||||
onClick={() => {
|
|
||||||
// mutateTankopediaSort((draft) => {
|
|
||||||
// draft.by = 'fire.premiumPenetration';
|
|
||||||
// });
|
|
||||||
$tankopediaSort.setKey('by', 'fire.premiumPenetration');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Premium penetration
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.damage'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.damage');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Damage
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.moduleDamage'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.moduleDamage');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Module damage
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.shellVelocity'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.shellVelocity');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Shell velocity
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.shellRange'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.shellRange');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Shell range
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.shellCapacity'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.shellCapacity');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Shell capacity
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.aimTime'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.aimTime');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Aim time
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.dispersionStill'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.dispersionStill');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Dispersion still
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.dispersionMoving'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.dispersionMoving');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Dispersion moving
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.gunDepression'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.gunDepression');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Gun depression
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'fire.gunElevation'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'fire.gunElevation');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Gun elevation
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
|
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
<DropdownMenu.SubTrigger>Maneuverability</DropdownMenu.SubTrigger>
|
<DropdownMenu.SubTrigger>
|
||||||
|
{
|
||||||
|
strings.website.common.tank_search.sort_dropdown.groups.sort
|
||||||
|
.maneuverability
|
||||||
|
}
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
<DropdownMenu.SubContent>
|
<DropdownMenu.SubContent>
|
||||||
<DropdownMenu.CheckboxItem
|
<Item by="maneuverability.forwardsSpeed" />
|
||||||
checked={tankopediaSort.by === 'maneuverability.forwardsSpeed'}
|
<Item by="maneuverability.backwardsSpeed" />
|
||||||
onClick={() => {
|
<Item by="maneuverability.power" />
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.forwardsSpeed');
|
<Item by="maneuverability.powerToWeight" />
|
||||||
}}
|
<Item by="maneuverability.weight" />
|
||||||
>
|
<Item by="maneuverability.traverseSpeed" />
|
||||||
Forwards speed
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'maneuverability.backwardsSpeed'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.backwardsSpeed');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Backwards speed
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'maneuverability.power'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.power');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Power
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'maneuverability.powerToWeight'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.powerToWeight');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Power to weight
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'maneuverability.weight'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.weight');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Weight
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'maneuverability.traverseSpeed'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'maneuverability.traverseSpeed');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Traverse speed
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
|
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
<DropdownMenu.SubTrigger>Survivability</DropdownMenu.SubTrigger>
|
<DropdownMenu.SubTrigger>
|
||||||
|
{
|
||||||
|
strings.website.common.tank_search.sort_dropdown.groups.sort
|
||||||
|
.survivability
|
||||||
|
}
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
<DropdownMenu.SubContent>
|
<DropdownMenu.SubContent>
|
||||||
<DropdownMenu.CheckboxItem
|
<Item by="survivability.health" />
|
||||||
checked={tankopediaSort.by === 'survivability.health'}
|
<Item by="survivability.viewRange" />
|
||||||
onClick={() => {
|
<Item by="survivability.camouflageStill" />
|
||||||
$tankopediaSort.setKey('by', 'survivability.health');
|
<Item by="survivability.camouflageMoving" />
|
||||||
}}
|
<Item by="survivability.camouflageShooting" />
|
||||||
>
|
<Item by="survivability.volume" />
|
||||||
Health
|
<Item by="survivability.length" />
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.viewRange'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'survivability.viewRange');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View range
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.camouflageStill'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'survivability.camouflageStill');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Camouflage
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.camouflageMoving'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'survivability.camouflageMoving');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Camouflage moving
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.camouflageShooting'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey(
|
|
||||||
'by',
|
|
||||||
'survivability.camouflageShooting',
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Camouflage shooting
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.volume'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'survivability.volume');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Volume
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
checked={tankopediaSort.by === 'survivability.length'}
|
|
||||||
onClick={() => {
|
|
||||||
$tankopediaSort.setKey('by', 'survivability.length');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Length
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
|
|
||||||
<DropdownMenu.Label>Direction</DropdownMenu.Label>
|
<DropdownMenu.Label>
|
||||||
|
{
|
||||||
|
strings.website.common.tank_search.sort_dropdown.groups.direction
|
||||||
|
.label
|
||||||
|
}
|
||||||
|
</DropdownMenu.Label>
|
||||||
<DropdownMenu.RadioGroup
|
<DropdownMenu.RadioGroup
|
||||||
value={tankopediaSort.direction}
|
value={tankopediaSort.direction}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
@@ -313,10 +127,16 @@ export function Sort() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.RadioItem value="ascending">
|
<DropdownMenu.RadioItem value="ascending">
|
||||||
Ascending
|
{
|
||||||
|
strings.website.common.tank_search.sort_dropdown.groups.direction
|
||||||
|
.ascending
|
||||||
|
}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
<DropdownMenu.RadioItem value="descending">
|
<DropdownMenu.RadioItem value="descending">
|
||||||
Descending
|
{
|
||||||
|
strings.website.common.tank_search.sort_dropdown.groups.direction
|
||||||
|
.descending
|
||||||
|
}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
</DropdownMenu.RadioGroup>
|
</DropdownMenu.RadioGroup>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'
|
|||||||
import { awaitableTankNames } from '../../core/awaitables/tankNames';
|
import { awaitableTankNames } from '../../core/awaitables/tankNames';
|
||||||
import { filterTank } from '../../core/blitzkit/filterTank';
|
import { filterTank } from '../../core/blitzkit/filterTank';
|
||||||
import { resolveReload } from '../../core/blitzkit/resolveReload';
|
import { resolveReload } from '../../core/blitzkit/resolveReload';
|
||||||
|
import { literals } from '../../core/i18n/literals';
|
||||||
|
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 {
|
import { SORT_UNITS } from '../../stores/tankopediaPersistent/constants';
|
||||||
SORT_NAMES,
|
|
||||||
SORT_UNITS,
|
|
||||||
} from '../../stores/tankopediaPersistent/constants';
|
|
||||||
import { $tankopediaSort } from '../../stores/tankopediaSort';
|
import { $tankopediaSort } from '../../stores/tankopediaSort';
|
||||||
import { ExperimentIcon } from '../ExperimentIcon';
|
import { ExperimentIcon } from '../ExperimentIcon';
|
||||||
import { TankSearchCard } from './components/Card';
|
import { TankSearchCard } from './components/Card';
|
||||||
@@ -52,6 +51,7 @@ const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] =
|
|||||||
|
|
||||||
export const TankSearch = memo<TankSearchProps>(
|
export const TankSearch = memo<TankSearchProps>(
|
||||||
({ compact, onSelect, onSelectAll, ...props }) => {
|
({ compact, onSelect, onSelectAll, ...props }) => {
|
||||||
|
const { strings, locale } = useLocale();
|
||||||
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);
|
||||||
@@ -438,8 +438,11 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
<Flex mt="2" gap="1" align="center" justify="center" direction="column">
|
<Flex mt="2" gap="1" align="center" justify="center" direction="column">
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{tanksFiltered.length} tank
|
{tanksFiltered.length === 1
|
||||||
{tanksFiltered.length === 1 ? '' : 's'}
|
? strings.website.common.tank_search.count_singular
|
||||||
|
: literals(strings.website.common.tank_search.count_plural, [
|
||||||
|
`${tanksFiltered.length.toLocaleString(locale)}`,
|
||||||
|
])}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{onSelectAll && (
|
{onSelectAll && (
|
||||||
@@ -457,14 +460,16 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Select all
|
{strings.website.common.tank_search.select_all}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{tankopediaSort.by !== 'meta.none' && (
|
{tankopediaSort.by !== 'meta.none' && (
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
Sorting by {SORT_NAMES[tankopediaSort.by]}
|
{literals(strings.website.common.tank_search.sorting_by, [
|
||||||
|
strings.website.common.tank_search.sort[tankopediaSort.by],
|
||||||
|
])}
|
||||||
{SORT_UNITS[tankopediaSort.by] === undefined
|
{SORT_UNITS[tankopediaSort.by] === undefined
|
||||||
? ''
|
? ''
|
||||||
: ` (${SORT_UNITS[tankopediaSort.by]})`}
|
: ` (${SORT_UNITS[tankopediaSort.by]})`}
|
||||||
@@ -480,8 +485,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
||||||
</Callout.Icon>
|
</Callout.Icon>
|
||||||
<Callout.Text>
|
<Callout.Text>
|
||||||
Tanks in testing are subject to change and may not represent the
|
{strings.website.common.warnings.test}
|
||||||
final product.
|
|
||||||
</Callout.Text>
|
</Callout.Text>
|
||||||
</Callout.Root>
|
</Callout.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createDefaultSkills } from '@blitzkit/core';
|
import { createDefaultSkills } from '@blitzkit/core';
|
||||||
import { PlusIcon } from '@radix-ui/react-icons';
|
|
||||||
import { Flex, Heading, Text } from '@radix-ui/themes';
|
import { Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { CompareTable } from '../../../../components/Compare/CompareTable';
|
import { CompareTable } from '../../../../components/Compare/CompareTable';
|
||||||
@@ -13,6 +12,11 @@ import { awaitableSkillDefinitions } from '../../../../core/awaitables/skillDefi
|
|||||||
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
||||||
import { tankCharacteristics } from '../../../../core/blitzkit/tankCharacteristics';
|
import { tankCharacteristics } from '../../../../core/blitzkit/tankCharacteristics';
|
||||||
import { tankToCompareMember } from '../../../../core/blitzkit/tankToCompareMember';
|
import { tankToCompareMember } from '../../../../core/blitzkit/tankToCompareMember';
|
||||||
|
import {
|
||||||
|
LocaleProvider,
|
||||||
|
useLocale,
|
||||||
|
type LocaleAcceptorProps,
|
||||||
|
} from '../../../../hooks/useLocale';
|
||||||
import { App } from '../../../../stores/app';
|
import { App } from '../../../../stores/app';
|
||||||
import { CompareEphemeral } from '../../../../stores/compareEphemeral';
|
import { CompareEphemeral } from '../../../../stores/compareEphemeral';
|
||||||
import { ComparePersistent } from '../../../../stores/comparePersistent';
|
import { ComparePersistent } from '../../../../stores/comparePersistent';
|
||||||
@@ -32,19 +36,21 @@ const [
|
|||||||
awaitableTankDefinitions,
|
awaitableTankDefinitions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function Page() {
|
export function Page({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<App.Provider>
|
<LocaleProvider locale={locale}>
|
||||||
<TankopediaPersistent.Provider>
|
<App.Provider>
|
||||||
<ComparePersistent.Provider>
|
<TankopediaPersistent.Provider>
|
||||||
<CompareEphemeral.Provider
|
<ComparePersistent.Provider>
|
||||||
data={createDefaultSkills(skillDefinitions)}
|
<CompareEphemeral.Provider
|
||||||
>
|
data={createDefaultSkills(skillDefinitions)}
|
||||||
<Content />
|
>
|
||||||
</CompareEphemeral.Provider>
|
<Content />
|
||||||
</ComparePersistent.Provider>
|
</CompareEphemeral.Provider>
|
||||||
</TankopediaPersistent.Provider>
|
</ComparePersistent.Provider>
|
||||||
</App.Provider>
|
</TankopediaPersistent.Provider>
|
||||||
|
</App.Provider>
|
||||||
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +58,7 @@ function Content() {
|
|||||||
const members = CompareEphemeral.use((state) => state.members);
|
const members = CompareEphemeral.use((state) => state.members);
|
||||||
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
|
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
|
||||||
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
||||||
|
const { strings } = useLocale();
|
||||||
const stats = useMemo<ReturnType<typeof tankCharacteristics>[]>(
|
const stats = useMemo<ReturnType<typeof tankCharacteristics>[]>(
|
||||||
() =>
|
() =>
|
||||||
members.map((thisMember) =>
|
members.map((thisMember) =>
|
||||||
@@ -136,19 +143,6 @@ function Content() {
|
|||||||
{members.length > 0 && (
|
{members.length > 0 && (
|
||||||
<Flex justify="center" flexGrow="1" position="relative">
|
<Flex justify="center" flexGrow="1" position="relative">
|
||||||
<CompareTable stats={stats} />
|
<CompareTable stats={stats} />
|
||||||
|
|
||||||
{/* <IconButton
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
top: 73,
|
|
||||||
transform: 'translate(50%, -50%)',
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
onClick={() => setAddTankDialogOpen(true)}
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
</IconButton> */}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -159,9 +153,11 @@ function Content() {
|
|||||||
direction="column"
|
direction="column"
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
<Heading color="gray">No tanks selected</Heading>
|
<Heading color="gray">
|
||||||
|
{strings.website.tools.compare.no_tanks.title}
|
||||||
|
</Heading>
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
Press the <PlusIcon /> Add button to get started
|
{strings.website.tools.compare.no_tanks.body}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import BaseLayout from '../../../../layouts/BaseLayout.astro';
|
|||||||
import { Page } from './_index';
|
import { Page } from './_index';
|
||||||
|
|
||||||
export { getStaticPaths } from '../../index.astro';
|
export { getStaticPaths } from '../../index.astro';
|
||||||
|
|
||||||
|
const { locale } = Astro.params;
|
||||||
---
|
---
|
||||||
|
|
||||||
<ToolOpenGraph tool={compareTool} />
|
<ToolOpenGraph tool={compareTool} />
|
||||||
|
|
||||||
<BaseLayout noFooter>
|
<BaseLayout noFooter>
|
||||||
<Page client:load />
|
<Page {locale} client:load />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
@@ -32,38 +32,7 @@ export const SORT_UNITS: Record<TankopediaSortBy, string | undefined> = {
|
|||||||
'survivability.viewRange': 'm',
|
'survivability.viewRange': 'm',
|
||||||
'survivability.volume': 'm^3',
|
'survivability.volume': 'm^3',
|
||||||
};
|
};
|
||||||
export const SORT_NAMES = {
|
|
||||||
'meta.none': 'none',
|
|
||||||
'survivability.health': 'health',
|
|
||||||
'survivability.viewRange': 'view range',
|
|
||||||
'survivability.camouflageStill': 'camouflage still',
|
|
||||||
'survivability.camouflageMoving': 'camouflage moving',
|
|
||||||
'survivability.camouflageShooting': 'camouflage shooting',
|
|
||||||
'survivability.volume': 'volume',
|
|
||||||
'survivability.length': 'length',
|
|
||||||
'fire.dpm': 'standard DPM',
|
|
||||||
'fire.dpmPremium': 'premium DPM',
|
|
||||||
'fire.reload': 'reload',
|
|
||||||
'fire.caliber': 'caliber',
|
|
||||||
'fire.standardPenetration': 'standard penetration',
|
|
||||||
'fire.premiumPenetration': 'premium penetration',
|
|
||||||
'fire.damage': 'damage',
|
|
||||||
'fire.moduleDamage': 'module damage',
|
|
||||||
'fire.shellVelocity': 'shell velocity',
|
|
||||||
'fire.shellRange': 'shell range',
|
|
||||||
'fire.shellCapacity': 'shell capacity',
|
|
||||||
'fire.aimTime': 'aim time',
|
|
||||||
'fire.dispersionStill': 'dispersion still',
|
|
||||||
'fire.dispersionMoving': 'dispersion moving',
|
|
||||||
'fire.gunDepression': 'gun depression',
|
|
||||||
'fire.gunElevation': 'gun elevation',
|
|
||||||
'maneuverability.forwardsSpeed': 'forwards speed',
|
|
||||||
'maneuverability.backwardsSpeed': 'backwards speed',
|
|
||||||
'maneuverability.power': 'power',
|
|
||||||
'maneuverability.powerToWeight': 'power to weight',
|
|
||||||
'maneuverability.weight': 'weight',
|
|
||||||
'maneuverability.traverseSpeed': 'traverse speed',
|
|
||||||
} as const;
|
|
||||||
export enum TankopediaDisplay {
|
export enum TankopediaDisplay {
|
||||||
Model,
|
Model,
|
||||||
DynamicArmor,
|
DynamicArmor,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import type en from '@blitzkit/core/lang/en.json';
|
||||||
import * as lodash from 'lodash-es';
|
import * as lodash from 'lodash-es';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
||||||
import { SORT_NAMES } from './constants';
|
|
||||||
|
|
||||||
export type TankopediaSortBy = keyof typeof SORT_NAMES;
|
export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort;
|
||||||
export type TankopediaSortDirection = 'ascending' | 'descending';
|
export type TankopediaSortDirection = 'ascending' | 'descending';
|
||||||
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user