feat: recently veiwed tanks

This commit is contained in:
tresabhi committed 2024-11-11 10:50:15 -06:00
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'; } from '@blitzkit/core';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { Flex, Link, Text } from '@radix-ui/themes'; import { Flex, Link, Text } from '@radix-ui/themes';
import { uniq } from 'lodash-es';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { resolveReload } from '../../../../core/blitzkit/resolveReload'; import { resolveReload } from '../../../../core/blitzkit/resolveReload';
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
import { $tankopediaSort } from '../../../../stores/tankopediaSort'; import { $tankopediaSort } from '../../../../stores/tankopediaSort';
import { classIcons } from '../../../ClassIcon'; import { classIcons } from '../../../ClassIcon';
import { MAX_RECENTLY_VIEWED } from '../../constants';
import './index.css'; import './index.css';
interface TankCardProps { interface TankCardProps {
@@ -24,6 +27,7 @@ interface TankCardProps {
const modelDefinitions = await fetchModelDefinitions(); const modelDefinitions = await fetchModelDefinitions();
export function TankCard({ tank, onSelect }: TankCardProps) { export function TankCard({ tank, onSelect }: TankCardProps) {
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const Icon = classIcons[tank.class]; const Icon = classIcons[tank.class];
const tankopediaSort = useStore($tankopediaSort); const tankopediaSort = useStore($tankopediaSort);
const discriminator = useMemo(() => { const discriminator = useMemo(() => {
@@ -155,7 +159,15 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
: 'gray' : 'gray'
} }
highContrast={tank.type === TankType.RESEARCHABLE} 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" className="tank-search-card"
style={{ style={{
backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`, backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`,
@@ -5,3 +5,5 @@ export const treeTypeOrder = [
TankType.PREMIUM, TankType.PREMIUM,
TankType.COLLECTOR, TankType.COLLECTOR,
]; ];
export const MAX_RECENTLY_VIEWED = 9;
@@ -12,11 +12,12 @@ import {
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes'; import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes';
import fuzzysort from 'fuzzysort'; import fuzzysort from 'fuzzysort';
import { times } from 'lodash-es'; import { times, uniq } from 'lodash-es';
import { memo, useEffect, useMemo, useState } from 'react'; import { memo, useEffect, useMemo, useState } from 'react';
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 { $tankFilters } from '../../stores/tankFilters'; import { $tankFilters } from '../../stores/tankFilters';
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
import { import {
SORT_NAMES, SORT_NAMES,
SORT_UNITS, SORT_UNITS,
@@ -25,11 +26,12 @@ import { $tankopediaSort } from '../../stores/tankopediaSort';
import { ExperimentIcon } from '../ExperimentIcon'; import { ExperimentIcon } from '../ExperimentIcon';
import { FilterControl } from './components/FilterControl'; import { FilterControl } from './components/FilterControl';
import { NoResults } from './components/NoResults'; import { NoResults } from './components/NoResults';
import { RecentlyViewed } from './components/RecentlyViewed';
import { SearchBar } from './components/SearchBar'; import { SearchBar } from './components/SearchBar';
import { SkeletonTankCard } from './components/SkeletonTankCard'; import { SkeletonTankCard } from './components/SkeletonTankCard';
import { TankCard } from './components/TankCard'; import { TankCard } from './components/TankCard';
import { TankCardWrapper } from './components/TankCardWrapper'; import { TankCardWrapper } from './components/TankCardWrapper';
import { treeTypeOrder } from './constants'; import { MAX_RECENTLY_VIEWED, treeTypeOrder } from './constants';
type TankSearchProps = Omit<FlexProps, 'onSelect'> & { type TankSearchProps = Omit<FlexProps, 'onSelect'> & {
compact?: boolean; compact?: boolean;
@@ -47,10 +49,10 @@ const tankNames = await fetchTankNames();
export const TankSearch = memo<TankSearchProps>( export const TankSearch = memo<TankSearchProps>(
({ compact, onSelect, onSelectAll, ...props }) => { ({ compact, onSelect, onSelectAll, ...props }) => {
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks); const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
const tankFilters = useStore($tankFilters); const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort); const tankopediaSort = useStore($tankopediaSort);
const tanksFiltered = useMemo(() => { const tanksFiltered = useMemo(() => {
if (tankFilters.search === undefined) { if (tankFilters.search === undefined) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) => const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
@@ -443,6 +445,8 @@ export const TankSearch = memo<TankSearchProps>(
<FilterControl compact={compact} /> <FilterControl compact={compact} />
)} )}
<RecentlyViewed />
<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">
@@ -457,6 +461,12 @@ export const TankSearch = memo<TankSearchProps>(
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
onSelectAll(tanksFiltered); onSelectAll(tanksFiltered);
mutateTankopediaPersistent((draft) => {
draft.recentlyViewed = uniq([
...tanksFiltered.map(({ id }) => id),
...draft.recentlyViewed,
]).slice(0, MAX_RECENTLY_VIEWED);
});
}} }}
> >
Select all Select all
+1 -1
View File
@@ -34,7 +34,7 @@ const testTanks = tanks
const mostPlayedTanks = tankAverages const mostPlayedTanks = tankAverages
.sort((a, b) => b.samples.d_1 - a.samples.d_1) .sort((a, b) => b.samples.d_1 - a.samples.d_1)
.map(({ id }) => tankDefinitions.tanks[id]) .map(({ id }) => tankDefinitions.tanks[id])
.filter(Boolean); .filter((tank) => tank !== undefined && !tank.testing);
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8); const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8);
--- ---
@@ -18,6 +18,7 @@ import { tankToCompareMember } from '../../../core/blitzkit/tankToCompareMember'
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';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
const modelDefinitions = await fetchModelDefinitions(); const modelDefinitions = await fetchModelDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions(); const equipmentDefinitions = await fetchEquipmentDefinitions();
@@ -28,11 +29,15 @@ const tankDefinitions = await fetchTankDefinitions();
export function Page() { export function Page() {
return ( return (
<App.Provider> <App.Provider>
<ComparePersistent.Provider> <TankopediaPersistent.Provider>
<CompareEphemeral.Provider data={createDefaultSkills(skillDefinitions)}> <ComparePersistent.Provider>
<Content /> <CompareEphemeral.Provider
</CompareEphemeral.Provider> data={createDefaultSkills(skillDefinitions)}
</ComparePersistent.Provider> >
<Content />
</CompareEphemeral.Provider>
</ComparePersistent.Provider>
</TankopediaPersistent.Provider>
</App.Provider> </App.Provider>
); );
} }
@@ -1,13 +1,16 @@
import { PageWrapper } from '../../../components/PageWrapper'; import { PageWrapper } from '../../../components/PageWrapper';
import { TankSearch } from '../../../components/TankSearch'; import { TankSearch } from '../../../components/TankSearch';
import { App } from '../../../stores/app'; import { App } from '../../../stores/app';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
export function Page() { export function Page() {
return ( return (
<PageWrapper color="purple" size={1028}> <PageWrapper color="purple" size={1028}>
<App.Provider> <TankopediaPersistent.Provider>
<TankSearch /> <App.Provider>
</App.Provider> <TankSearch />
</App.Provider>
</TankopediaPersistent.Provider>
</PageWrapper> </PageWrapper>
); );
} }
@@ -5,5 +5,7 @@ import { Page } from './_index';
--- ---
<ToolLayout tool={tankopediaTool}> <ToolLayout tool={tankopediaTool}>
<Page client:load /> <Page client:only="react">
<Page slot="fallback" />
</Page>
</ToolLayout> </ToolLayout>
@@ -19,6 +19,7 @@ export interface TankopediaPersistentStore {
showSpacedArmor: boolean; showSpacedArmor: boolean;
showExternalModules: boolean; showExternalModules: boolean;
showPrimaryArmor: boolean; showPrimaryArmor: boolean;
recentlyViewed: number[];
sort: { sort: {
by: TankopediaSortBy; by: TankopediaSortBy;
@@ -41,6 +42,7 @@ export const TankopediaPersistent = createContextualStore(() =>
showSpacedArmor: true, showSpacedArmor: true,
showExternalModules: true, showExternalModules: true,
showPrimaryArmor: true, showPrimaryArmor: true,
recentlyViewed: [],
sort: { sort: {
by: 'meta.none', by: 'meta.none',
direction: 'ascending', direction: 'ascending',