mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: recently veiwed tanks
This commit is contained in:
1 parent
ea013c4df8
commit
387a67266f
9 files changed
+80
-14
No files matched your search
@@ -0,0 +1,30 @@
|
||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { Flex, Separator, Text } from '@radix-ui/themes';
|
||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||
import { TankCard } from './TankCard';
|
||||
import { TankCardWrapper } from './TankCardWrapper';
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
|
||||
export function RecentlyViewed() {
|
||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||
// non-reactive because it is a little weird that it updates instantly even before the page loads
|
||||
const recentlyViewed = tankopediaPersistentStore.getState().recentlyViewed;
|
||||
|
||||
if (recentlyViewed.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" mt="2" mb="6">
|
||||
<Text color="gray" align="center">
|
||||
Recently viewed
|
||||
</Text>
|
||||
<TankCardWrapper>
|
||||
{recentlyViewed.map((id) => (
|
||||
<TankCard tank={tankDefinitions.tanks[id]} key={id} />
|
||||
))}
|
||||
</TankCardWrapper>
|
||||
|
||||
<Separator size="4" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -10,10 +10,13 @@ import {
|
||||
} from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { resolveReload } from '../../../../core/blitzkit/resolveReload';
|
||||
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
|
||||
import { $tankopediaSort } from '../../../../stores/tankopediaSort';
|
||||
import { classIcons } from '../../../ClassIcon';
|
||||
import { MAX_RECENTLY_VIEWED } from '../../constants';
|
||||
import './index.css';
|
||||
|
||||
interface TankCardProps {
|
||||
@@ -24,6 +27,7 @@ interface TankCardProps {
|
||||
const modelDefinitions = await fetchModelDefinitions();
|
||||
|
||||
export function TankCard({ tank, onSelect }: TankCardProps) {
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const Icon = classIcons[tank.class];
|
||||
const tankopediaSort = useStore($tankopediaSort);
|
||||
const discriminator = useMemo(() => {
|
||||
@@ -155,7 +159,15 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
|
||||
: 'gray'
|
||||
}
|
||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
||||
onClick={onSelect ? () => onSelect(tank) : undefined}
|
||||
onClick={() => {
|
||||
onSelect?.(tank);
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed]).slice(
|
||||
0,
|
||||
MAX_RECENTLY_VIEWED,
|
||||
);
|
||||
});
|
||||
}}
|
||||
className="tank-search-card"
|
||||
style={{
|
||||
backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`,
|
||||
|
||||
@@ -5,3 +5,5 @@ export const treeTypeOrder = [
|
||||
TankType.PREMIUM,
|
||||
TankType.COLLECTOR,
|
||||
];
|
||||
|
||||
export const MAX_RECENTLY_VIEWED = 9;
|
||||
@@ -12,11 +12,12 @@ import {
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { times } from 'lodash-es';
|
||||
import { times, uniq } from 'lodash-es';
|
||||
import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { filterTank } from '../../core/blitzkit/filterTank';
|
||||
import { resolveReload } from '../../core/blitzkit/resolveReload';
|
||||
import { $tankFilters } from '../../stores/tankFilters';
|
||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
||||
import {
|
||||
SORT_NAMES,
|
||||
SORT_UNITS,
|
||||
@@ -25,11 +26,12 @@ import { $tankopediaSort } from '../../stores/tankopediaSort';
|
||||
import { ExperimentIcon } from '../ExperimentIcon';
|
||||
import { FilterControl } from './components/FilterControl';
|
||||
import { NoResults } from './components/NoResults';
|
||||
import { RecentlyViewed } from './components/RecentlyViewed';
|
||||
import { SearchBar } from './components/SearchBar';
|
||||
import { SkeletonTankCard } from './components/SkeletonTankCard';
|
||||
import { TankCard } from './components/TankCard';
|
||||
import { TankCardWrapper } from './components/TankCardWrapper';
|
||||
import { treeTypeOrder } from './constants';
|
||||
import { MAX_RECENTLY_VIEWED, treeTypeOrder } from './constants';
|
||||
|
||||
type TankSearchProps = Omit<FlexProps, 'onSelect'> & {
|
||||
compact?: boolean;
|
||||
@@ -47,10 +49,10 @@ const tankNames = await fetchTankNames();
|
||||
|
||||
export const TankSearch = memo<TankSearchProps>(
|
||||
({ compact, onSelect, onSelectAll, ...props }) => {
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
|
||||
const tankFilters = useStore($tankFilters);
|
||||
const tankopediaSort = useStore($tankopediaSort);
|
||||
|
||||
const tanksFiltered = useMemo(() => {
|
||||
if (tankFilters.search === undefined) {
|
||||
const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
|
||||
@@ -443,6 +445,8 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
<FilterControl compact={compact} />
|
||||
)}
|
||||
|
||||
<RecentlyViewed />
|
||||
|
||||
<Flex mt="2" gap="1" align="center" justify="center" direction="column">
|
||||
<Flex gap="2">
|
||||
<Text color="gray">
|
||||
@@ -457,6 +461,12 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
onSelectAll(tanksFiltered);
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.recentlyViewed = uniq([
|
||||
...tanksFiltered.map(({ id }) => id),
|
||||
...draft.recentlyViewed,
|
||||
]).slice(0, MAX_RECENTLY_VIEWED);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Select all
|
||||
|
||||
@@ -34,7 +34,7 @@ const testTanks = tanks
|
||||
const mostPlayedTanks = tankAverages
|
||||
.sort((a, b) => b.samples.d_1 - a.samples.d_1)
|
||||
.map(({ id }) => tankDefinitions.tanks[id])
|
||||
.filter(Boolean);
|
||||
.filter((tank) => tank !== undefined && !tank.testing);
|
||||
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8);
|
||||
---
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { tankToCompareMember } from '../../../core/blitzkit/tankToCompareMember'
|
||||
import { App } from '../../../stores/app';
|
||||
import { CompareEphemeral } from '../../../stores/compareEphemeral';
|
||||
import { ComparePersistent } from '../../../stores/comparePersistent';
|
||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||
|
||||
const modelDefinitions = await fetchModelDefinitions();
|
||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
||||
@@ -28,11 +29,15 @@ const tankDefinitions = await fetchTankDefinitions();
|
||||
export function Page() {
|
||||
return (
|
||||
<App.Provider>
|
||||
<ComparePersistent.Provider>
|
||||
<CompareEphemeral.Provider data={createDefaultSkills(skillDefinitions)}>
|
||||
<Content />
|
||||
</CompareEphemeral.Provider>
|
||||
</ComparePersistent.Provider>
|
||||
<TankopediaPersistent.Provider>
|
||||
<ComparePersistent.Provider>
|
||||
<CompareEphemeral.Provider
|
||||
data={createDefaultSkills(skillDefinitions)}
|
||||
>
|
||||
<Content />
|
||||
</CompareEphemeral.Provider>
|
||||
</ComparePersistent.Provider>
|
||||
</TankopediaPersistent.Provider>
|
||||
</App.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { PageWrapper } from '../../../components/PageWrapper';
|
||||
import { TankSearch } from '../../../components/TankSearch';
|
||||
import { App } from '../../../stores/app';
|
||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<PageWrapper color="purple" size={1028}>
|
||||
<App.Provider>
|
||||
<TankSearch />
|
||||
</App.Provider>
|
||||
<TankopediaPersistent.Provider>
|
||||
<App.Provider>
|
||||
<TankSearch />
|
||||
</App.Provider>
|
||||
</TankopediaPersistent.Provider>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
@@ -5,5 +5,7 @@ import { Page } from './_index';
|
||||
---
|
||||
|
||||
<ToolLayout tool={tankopediaTool}>
|
||||
<Page client:load />
|
||||
<Page client:only="react">
|
||||
<Page slot="fallback" />
|
||||
</Page>
|
||||
</ToolLayout>
|
||||
@@ -19,6 +19,7 @@ export interface TankopediaPersistentStore {
|
||||
showSpacedArmor: boolean;
|
||||
showExternalModules: boolean;
|
||||
showPrimaryArmor: boolean;
|
||||
recentlyViewed: number[];
|
||||
|
||||
sort: {
|
||||
by: TankopediaSortBy;
|
||||
@@ -41,6 +42,7 @@ export const TankopediaPersistent = createContextualStore(() =>
|
||||
showSpacedArmor: true,
|
||||
showExternalModules: true,
|
||||
showPrimaryArmor: true,
|
||||
recentlyViewed: [],
|
||||
sort: {
|
||||
by: 'meta.none',
|
||||
direction: 'ascending',
|
||||
|
||||
Reference in new issue
Block a user