Implement battlesuit filters

This commit is contained in:
Sakura Knoll
2023-07-15 12:44:38 +09:00 Unverified
parent ac47f9499c
commit 5cb208b9a0
8 changed files with 1070 additions and 47 deletions
+7 -27
View File
@@ -1,26 +1,17 @@
/** @jsxImportSource theme-ui */
import { useRouter } from 'next/router'
import { Link, Text } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../lib/consts'
import SquareImageBox from './SquareImageBox'
import { ReactNode } from 'react'
import { Link } from 'theme-ui'
interface FilterButtonProps {
active?: boolean
icon?: string
label: string
href: string
m?: string | number
close?: () => void
children: ReactNode
}
const FilterButton = ({
active = false,
icon,
label,
href,
m,
close,
}: FilterButtonProps) => {
const FilterButton = ({ active = false, children, href, m }: FilterButtonProps) => {
const { push } = useRouter()
return (
<Link
@@ -30,24 +21,13 @@ const FilterButton = ({
px={2}
className={active ? 'active' : ''}
sx={{ display: 'flex' }}
variant='buttons.primary'
onClick={(event) => {
variant="buttons.primary"
onClick={event => {
event.preventDefault()
if (close != null) {
close()
}
push(href, href, { shallow: true })
}}
>
{icon != null && (
<SquareImageBox
src={`${assetsBucketBaseUrl}/honkai3rd/${icon}.png`}
alt={label}
size={30}
mr={1}
/>
)}
<Text sx={{ lineHeight: '30px' }}>{label}</Text>
{children}
</Link>
)
}
+144 -6
View File
@@ -84,7 +84,89 @@ export function getSkillTypeLabel(type: SkillType) {
}
}
export function getCharacterTypeLabel(type: CharacterType) {
export function getCharacterTypeLabel(type: CharacterType, locale: string = 'en-US') {
if (locale === 'en-US') {
switch (type) {
case 'kiana':
return 'Kiana'
case 'mei':
return 'Mei'
case 'bronya':
return 'Bronya'
case 'himeko':
return 'Himeko'
case 'theresa':
return 'Theresa'
case 'fuhua':
return 'Fu Hua'
case 'rita':
return 'Rita'
case 'sakura':
return 'Sakura'
case 'kallen':
return 'Kallen'
case 'olenyevas':
return 'Olenyevas'
case 'seele':
return 'Seele'
case 'durandal':
return 'Durandal'
case 'fischl':
return 'Fischl'
case 'elysia':
return 'Elysia'
case 'mobius':
return 'Mobius'
case 'raven':
return 'Raven'
case 'carole':
return 'Carole'
case 'pardofelis':
return 'Pardofelis'
case 'aponia':
return 'Aponia'
case 'eden':
return 'Eden'
case 'griseo':
return 'Griseo'
case 'vill-v':
return 'Vill V'
case 'sushang':
return 'Sushang'
case 'ai':
return 'AI'
case 'susannah':
return 'Susannah'
case 'hare':
return 'Hare'
case 'prometheus':
return 'Prometheus'
case 'kira':
return 'Kira'
case 'asuka':
return 'Asuka'
// Event Only
case 'keqing':
return 'Keqing'
// APHO
case 'apho-mei':
return '후서 메이'
case 'apho-adam':
return '후서 아담'
case 'apho-carol':
return '후서 캐롤'
case 'apho-bronya':
return '후서 브로냐'
case 'apho-timido':
return '후서 티미도'
default:
return `Unknown Character Type (${type})`
}
}
switch (type) {
case 'kiana':
return '키아나'
@@ -167,7 +249,63 @@ export function getCharacterTypeLabel(type: CharacterType) {
}
}
export function getTagTypeLabel(type: TagType) {
export function getTagTypeLabel(type: TagType, locale: string = 'en-US') {
if (type) {
switch (type) {
case 'branch':
return 'Branch'
case 'charge':
return 'Charge'
case 'physical-dmg':
return 'Physical DMG'
case 'fire-dmg':
return 'Fire DMG'
case 'ice-dmg':
return 'Ice DMG'
case 'lightning-dmg':
return 'Lightning DMG'
case 'freeze':
return 'Freeze'
case 'paralyze':
return 'Paralyze'
case 'stun':
return 'Stun'
case 'ignite':
return 'Ignite'
case 'bleed':
return 'Bleed'
case 'heavy-atk':
return 'Heavy ATK'
case 'weaken':
return 'Weaken'
case 'impair':
return 'Impair'
case 'float':
return 'Float'
case 'slow-down':
return 'Slow Down'
case 'time-mastery':
return 'Time Mastery'
case 'gather':
return 'Gather'
case 'heal':
return 'Heal'
case 'fast-atk':
return 'Fast ATK'
case 'aerial':
return 'Aerial'
case 'burst':
return 'Burst'
case 'shield':
return 'Shield'
case 'meele':
return 'Meele'
case 'ranged':
return 'Ranged'
default:
return `Unknown Tag Type (${type})`
}
}
switch (type) {
case 'branch':
return '분기'
@@ -210,11 +348,11 @@ export function getTagTypeLabel(type: TagType) {
case 'fast-atk':
return '높은 빈도'
case 'aerial':
return '버스트'
case 'burst':
return '실드'
case 'shield':
return '대공'
case 'burst':
return '버스트'
case 'shield':
return '실드'
case 'meele':
return '근접'
case 'ranged':
+1 -1
View File
@@ -103,7 +103,7 @@ export interface Battlesuit {
export type BattlesuitCatalogItem = Pick<
Battlesuit,
'id' | 'fullName' | 'attributeType' | 'weapon' | 'character' | 'initialStar'
'id' | 'fullName' | 'attributeType' | 'weapon' | 'character' | 'initialStar' | 'tags'
>
export interface SkillTagItem {
+55 -1
View File
@@ -1,7 +1,61 @@
import { BattlesuitSkill } from './types'
import { BattlesuitSkill, CharacterType, TagType } from './types'
export function sortBattlesuitSkill(a: BattlesuitSkill, b: BattlesuitSkill) {
return reversedSkillTypeOrder.indexOf(b.skillType) - reversedSkillTypeOrder.indexOf(a.skillType)
}
const reversedSkillTypeOrder = ['leader', 'passive', 'evasion', 'special', 'ultimate', 'basic', 'sp', 'none'].reverse()
export const characterFilterTypes: CharacterType[] = [
'kiana',
'mei',
'bronya',
'himeko',
'theresa',
'fuhua',
'rita',
'sakura',
'kallen',
'olenyevas',
'seele',
'durandal',
'fischl',
'elysia',
'mobius',
'raven',
'carole',
'pardofelis',
'aponia',
'eden',
'griseo',
'vill-v',
'sushang',
'ai',
'susannah',
'hare',
'prometheus',
'kira',
'asuka',
'keqing'
]
export const tagFilterTypes: TagType[] = [
'physical-dmg',
'fire-dmg',
'ice-dmg',
'lightning-dmg',
'freeze',
'paralyze',
'stun',
'ignite',
'bleed',
'heavy-atk',
'weaken',
'impair',
'time-mastery',
'gather',
'heal',
'fast-atk',
'aerial',
'burst'
]
+109 -11
View File
@@ -1,15 +1,23 @@
/** @jsxImportSource theme-ui */
import { Box } from '@theme-ui/components'
import { Box, Heading } from '@theme-ui/components'
import { NextPageContext } from 'next'
import { Flex, Link } from 'theme-ui'
import { Flex, Link, Text } from 'theme-ui'
import { loadBattlesuitCatalog } from '../../../lib/v2/server/loadData'
import { BattlesuitCatalogItem } from '../../../lib/v2/data/types'
import { BattlesuitCatalogItem, TagType } from '../../../lib/v2/data/types'
import BattlesuitCatalogItemCard from '../../../components/v2/BattlesuitCatalogItemCard'
import Head from '../../../components/atoms/Head'
import Honkai3rdLayout from '../../../components/layouts/Honkai3rdLayout'
import { useTranslation } from 'next-i18next'
import Breadcrumb from '../../../components/organisms/Breadcrumb'
import { getI18NProps } from '../../../server/i18n'
import FilterButton from '../../../components/atoms/FilterButton'
import { translate } from '../../../lib/i18n'
import { characterFilterTypes } from '../../../lib/v2/data/utils'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { getCharacterTypeLabel, getTagTypeLabel } from '../../../lib/v2/data/text'
import ChibiIcon from '../../../components/v2/ChibiIcon'
import TagIcon from '../../../components/v2/TagIcon'
interface BattlesuitListPageProps {
battlesuitCatalog: BattlesuitCatalogItem[]
@@ -17,6 +25,42 @@ interface BattlesuitListPageProps {
const BattlesuitListPage = ({ battlesuitCatalog }: BattlesuitListPageProps) => {
const { t } = useTranslation()
const { locale, query } = useRouter()
const valkyrieFilter = useMemo(() => {
if (query.valkyrie == null) {
return 'all'
}
return typeof query.valkyrie === 'string' ? query.valkyrie : query.valkyrie[0]
}, [query])
const tagFilter = useMemo(() => {
if (query.tag == null) {
return 'all'
}
return typeof query.tag === 'string' ? query.tag : query.tag[0]
}, [query])
const battlesuitsFilteredByValkyrie = useMemo(() => {
return battlesuitCatalog.filter(battlesuit => {
if (valkyrieFilter === 'all') {
return true
}
return valkyrieFilter === battlesuit.character
})
}, [battlesuitCatalog, valkyrieFilter])
const tagFilterTypes = useMemo(() => {
const tagSet = battlesuitsFilteredByValkyrie.reduce<Set<TagType>>((set, battlesuit) => {
battlesuit.tags.forEach(tag => set.add(tag))
return set
}, new Set())
return [...tagSet]
}, [battlesuitsFilteredByValkyrie])
return (
<Honkai3rdLayout>
<Head
@@ -34,18 +78,72 @@ const BattlesuitListPage = ({ battlesuitCatalog }: BattlesuitListPageProps) => {
}
]}
/>
<h1>{t('battlesuits-list.heading')}</h1>
<Flex sx={{ flexWrap: 'wrap' }}>
{battlesuitCatalog.map(battlesuit => {
<Heading as="h1">{t('battlesuits-list.heading')}</Heading>
<Heading as="h3">{t('battlesuits-list.filter-by-valkyries')}</Heading>
<Flex mb={2} sx={{ flexWrap: 'wrap' }}>
{characterFilterTypes.map(type => {
const active = type === valkyrieFilter
return (
<Box key={battlesuit.id}>
<Link href={`/honkai3rd/battlesuits/${battlesuit.id}`}>
<BattlesuitCatalogItemCard battlesuit={battlesuit} />
</Link>
</Box>
<FilterButton key={type} active={active} href={active ? `` : `?valkyrie=${type}`} m={1}>
<Flex sx={{ alignItems: 'center' }}>
<ChibiIcon id={type} />
<Text ml={1}>
{translate(locale, { 'ko-KR': getCharacterTypeLabel(type, 'ko-KR') }, getCharacterTypeLabel(type))}
</Text>
</Flex>
</FilterButton>
)
})}
</Flex>
{tagFilterTypes.length > 0 && (
<>
<Heading as="h3">{t('battlesuits-list.filter-by-features')}</Heading>
<Flex mb={2} sx={{ flexWrap: 'wrap' }}>
{tagFilterTypes.map(type => {
const active = type === tagFilter
return (
<FilterButton
key={type}
active={active}
href={`?${valkyrieFilter !== 'all' ? `valkyrie=${valkyrieFilter}&` : ''}${
active ? '' : `tag=${type}`
}`}
m={1}
>
<Flex sx={{ alignItems: 'center' }}>
<TagIcon type={type} />
<Text ml={1}>
{translate(locale, { 'ko-KR': getTagTypeLabel(type, 'ko-KR') }, getTagTypeLabel(type))}
</Text>
</Flex>
</FilterButton>
)
})}
</Flex>
</>
)}
<Flex sx={{ flexWrap: 'wrap' }}>
{battlesuitsFilteredByValkyrie
.filter(battlesuit => {
if (tagFilter === 'all') {
return true
}
return battlesuit.tags.findIndex(tag => tag === tagFilter) > -1
})
.map(battlesuit => {
return (
<Box key={battlesuit.id}>
<Link href={`/honkai3rd/battlesuits/${battlesuit.id}`}>
<BattlesuitCatalogItemCard battlesuit={battlesuit} />
</Link>
</Box>
)
})}
</Flex>
</Box>
</Honkai3rdLayout>
)
+2 -1
View File
@@ -58,7 +58,8 @@ function writeBattlesuitData(locale: string) {
attributeType: battlesuit.attributeType,
weapon: battlesuit.weapon,
character: battlesuit.character,
initialStar: battlesuit.initialStar
initialStar: battlesuit.initialStar,
tags: battlesuit.tags
}
})