11import { GetStaticPropsContext , GetStaticPropsResult } from "next"
22import Head from "next/head"
33import Image from "next/image"
4+ import { Dispatch , SetStateAction , useState } from "react"
45import FormattedLink from "../../components/FormattedLink"
56import Main from "../../components/Main"
6- import { getCharacters , urlify } from "../../utils/data-cache"
7- import { Character , CharacterFull } from "../../utils/types"
8-
9- import Cryo from "../../public/img/element/Cryo.png"
107import Anemo from "../../public/img/element/Anemo.png"
8+ import Cryo from "../../public/img/element/Cryo.png"
119import Dendro from "../../public/img/element/Dendro.png"
12- import Geo from "../../public/img/element/Geo.png"
1310import Electro from "../../public/img/element/Electro.png"
11+ import Geo from "../../public/img/element/Geo.png"
1412import Hydro from "../../public/img/element/Hydro.png"
1513import Pyro from "../../public/img/element/Pyro.png"
14+ import Bow from "../../public/img/weapon_types/Bow.png"
15+ import Catalyst from "../../public/img/weapon_types/Catalyst.png"
16+ import Claymore from "../../public/img/weapon_types/Claymore.png"
17+ import Polearm from "../../public/img/weapon_types/Polearm.png"
18+ import Sword from "../../public/img/weapon_types/Sword.png"
19+ import { getCharacters , urlify } from "../../utils/data-cache"
20+ import { Character , CharacterFull , WeaponType } from "../../utils/types"
21+
1622
1723const elements = {
18- Anemo, Cryo, Dendro, Geo, Electro, Hydro, Pyro
24+ Pyro, Electro, Cryo, Hydro, Anemo, Geo, Dendro
25+ }
26+ const weapons : Record < WeaponType , StaticImageData > = {
27+ Polearm, Sword, Claymore, Bow, Catalyst
1928}
2029
21- type Element = "Anemo"
30+ type ElementType = ( keyof ( typeof elements ) )
2231
2332interface SmallChar {
2433 name : string
2534 stars ?: number
26- element ?: ( keyof ( typeof elements ) ) [ ]
27- weapon ?: string
35+ element ?: ElementType [ ]
36+ weapon ?: WeaponType
2837 icon ?: string
2938}
3039
3140interface Props {
3241 characters : SmallChar [ ]
3342}
3443
44+ const defaultElements : ElementType [ ] = Object . keys ( elements ) as ElementType [ ]
45+ const defaultWeapons : WeaponType [ ] = Object . keys ( weapons ) as WeaponType [ ]
46+
3547export default function Characters ( props : Props & { location : string } ) {
48+ const [ filter , setFilter ] = useState ( false )
49+
50+ const [ starFilter , setStarFilter ] = useState ( 0 )
51+ const [ elementFilter , setElementFilter ] = useState ( defaultElements )
52+ const [ weaponFilter , setWeaponFilter ] = useState ( defaultWeapons )
53+
3654 return (
3755 < Main >
3856 < Head >
@@ -46,23 +64,150 @@ export default function Characters(props: Props & { location: string }) {
4664 Characters
4765 </ h1 >
4866
49- < div className = "flex flex-wrap justify-around text-center" >
50- { props . characters . map ( char => (
51- < FormattedLink key = { char . name } font = "semibold" size = "xl" location = { props . location } href = { `/characters/${ urlify ( char . name , false ) } ` } >
52- < div className = "bg-slate-600 w-24 h-24 m-2" >
53- < div className = "absolute w-6 m-1" >
54- { char . element && char . element . map ( e => < Image src = { elements [ e ] } key = { e } alt = { `${ e } Element` } /> ) }
55- < Image alt = { char . name } src = { char . icon ?? "/img/unknown.png" } className = "w-24" width = { 256 } height = { 256 } onError = { ( e ) => ( e . target as HTMLImageElement ) . src = "/img/unknown.png" } />
67+ { filter ? < div className = "bg-slate-100 dark:bg-slate-600 flex flex-col p-2 rounded-2xl font-semibold gap-2" >
68+ < div className = "pb-2" >
69+ < div className = "flex flex-row font-semibold float-right" >
70+ < ExclusiveButton type = { filter } value = { false } setter = { setFilter } >
71+ Hide filters
72+ </ ExclusiveButton >
73+ </ div >
74+ < div >
75+ Rarity filter
76+ </ div >
77+ < div className = "flex flex-row gap-2 pt-2" >
78+ < ExclusiveButton type = { starFilter } value = { 0 } setter = { setStarFilter } >
79+ All
80+ </ ExclusiveButton >
81+ < ExclusiveButton type = { starFilter } value = { 4 } setter = { setStarFilter } >
82+ 4★ Only
83+ </ ExclusiveButton >
84+ < ExclusiveButton type = { starFilter } value = { 5 } setter = { setStarFilter } >
85+ 5★ Only
86+ </ ExclusiveButton >
87+ </ div >
88+ </ div >
89+
90+ < div className = "py-1" >
91+ < div className = "flex flex-row gap-4" >
92+ Element filter
93+ < ToggleAllButton type = { elementFilter } value = { defaultElements } setter = { setElementFilter } >
94+ All
95+ </ ToggleAllButton >
96+ </ div >
97+ < div className = "flex flex-row gap-2 pt-2" >
98+ { defaultElements . map ( e => (
99+ < ToggleButton key = { e } type = { elementFilter } value = { e } setter = { setElementFilter } >
100+ { e }
101+ </ ToggleButton >
102+ ) ) }
103+ </ div >
104+ </ div >
105+
106+ < div className = "py-1" >
107+ < div className = "flex flex-row gap-4 pt-2" >
108+ Weapon filter
109+ < ToggleAllButton type = { weaponFilter } value = { defaultWeapons } setter = { setWeaponFilter } >
110+ All
111+ </ ToggleAllButton >
112+ </ div >
113+ < div className = "flex flex-row gap-2 pt-2" >
114+ { defaultWeapons . map ( e => (
115+ < ToggleButton key = { e } type = { weaponFilter } value = { e } setter = { setWeaponFilter } >
116+ { e }
117+ </ ToggleButton >
118+ ) ) }
119+ </ div >
120+ </ div >
121+ </ div > : < div className = "flex flex-row font-semibold justify-end" >
122+ < ExclusiveButton type = { filter } value = { true } setter = { setFilter } >
123+ Show filters
124+ </ ExclusiveButton >
125+ </ div >
126+ }
127+
128+ < div className = "flex flex-wrap justify-evenly text-center pt-2" >
129+ { props . characters
130+ . filter ( c => starFilter == 0 || starFilter == c . stars )
131+ . filter ( c => elementFilter . some ( e => c . element ?. includes ( e ) ) || c . element == undefined )
132+ . filter ( c => weaponFilter . some ( e => c . weapon ?. includes ( e ) ) || c . weapon == undefined )
133+ . map ( char => {
134+ let color = ""
135+ if ( char . stars == 5 ) color = "bg-amber-700"
136+ if ( char . stars == 4 ) color = "bg-purple-800"
137+
138+ return < FormattedLink key = { char . name } font = "semibold" size = "xl" location = { props . location } href = { `/characters/${ urlify ( char . name , false ) } ` } >
139+ < div className = "bg-slate-300 dark:bg-slate-800 w-24 m-1 relative text-sm font-bold rounded-xl transition-all duration-100 hover:outline outline-slate-800 dark:outline-slate-300" >
140+ < div className = { `${ color } rounded-t-xl w-24 h-24` } >
141+ < Icon char = { char } className = "rounded-t-xl w-24 m-0 p-0" />
142+ < span className = "absolute block p-0.5 top-0 w-full" >
143+ < div className = "flex flex-col" >
144+ { char . element && char . element . map ( e => < div key = { e } className = "w-6 h-6" >
145+ < Image src = { elements [ e ] } alt = { `${ e } Element` } />
146+ </ div > ) }
147+ </ div >
148+ </ span >
149+ < span className = "absolute block p-0.5 top-0 w-full" >
150+ < div className = "flex flex-col float-right" >
151+ { char . weapon && < div className = "w-6 h-6" >
152+ < Image src = { weapons [ char . weapon ] } alt = { `${ char . weapon } ` } />
153+ </ div > }
154+ </ div >
155+ </ span >
156+ </ div >
157+ < span className = "flex justify-center items-center h-10 m-0 p-0 duration-200" >
158+ { char . name }
159+ </ span >
56160 </ div >
57- { char . name }
58- </ div >
59- </ FormattedLink >
60- ) ) }
161+ </ FormattedLink >
162+ } ) }
61163 </ div >
62164 </ Main >
63165 )
64166}
65167
168+ function ExclusiveButton < T > ( { type, value, setter, children } : { type : T , value : T , setter : Dispatch < SetStateAction < T > > , children : any } ) {
169+ return < div
170+ onClick = { ( ) => setter ( value ) }
171+ className = { `${ type == value ? "bg-slate-400 dark:bg-slate-700 outline-slate-400 outline" : "bg-slate-300 dark:bg-slate-800" } px-2 py-0.5 rounded-lg cursor-pointer selection:bg-transparent` }
172+ >
173+ { children }
174+ </ div >
175+ }
176+
177+ function ToggleAllButton < T > ( { type, value, setter, children } : { type : T [ ] , value : T [ ] , setter : Dispatch < SetStateAction < T [ ] > > , children : any } ) {
178+ const equal = type . length == value . length && type . every ( e => value . includes ( e ) )
179+
180+ return < div
181+ onClick = { ( ) => equal ? setter ( [ ] ) : setter ( value ) }
182+ className = { `${ equal ? "bg-slate-400 dark:bg-slate-700 outline-slate-400 outline" : "bg-slate-300 dark:bg-slate-800" } px-2 py-0.5 rounded-lg cursor-pointer selection:bg-transparent` }
183+ >
184+ { children }
185+ </ div >
186+ }
187+
188+ function ToggleButton < T > ( { type, value, setter, children } : { type : T [ ] , value : T , setter : Dispatch < SetStateAction < T [ ] > > , children : any } ) {
189+ const has = type . includes ( value )
190+ return < div
191+ onClick = { ( ) => {
192+ if ( has ) setter ( type . filter ( x => x != value ) )
193+ else setter ( [ value , ...type ] )
194+ } }
195+ className = { `${ has ? "bg-slate-400 dark:bg-slate-700 outline-slate-400 outline" : "bg-slate-300 dark:bg-slate-800"
196+ } px-2 py-0.5 rounded-lg cursor-pointer selection:bg-transparent`}
197+ >
198+ { children }
199+ </ div >
200+ }
201+
202+
203+ function Icon ( { char, className } : { char : SmallChar , className ?: string } ) {
204+ let src = char . icon ?? "img/unknown.png"
205+
206+ if ( src . startsWith ( "img" ) ) src = "/" + src
207+
208+ return < Image alt = { char . name } src = { src } className = { className } width = { 256 } height = { 256 } onError = { ( e ) => ( e . target as HTMLImageElement ) . src = "/img/unknown.png" } />
209+ }
210+
66211function isFullCharacter ( char : Character ) : char is CharacterFull {
67212 return typeof ( char as CharacterFull ) . releasedOn == "string"
68213}
@@ -93,7 +238,7 @@ export async function getStaticProps(context: GetStaticPropsContext): Promise<Ge
93238 . map ( c => {
94239 const char : SmallChar = { name : c . name }
95240 if ( c . star ) char . stars = c . star
96- if ( c . skills ) char . element = c . skills . map ( skill => skill . ult ?. type ) . filter ( x => x ) as Element [ ]
241+ if ( c . skills ) char . element = c . skills . map ( skill => skill . ult ?. type ) . filter ( x => x ) as ElementType [ ]
97242 if ( c . weaponType ) char . weapon = c . weaponType
98243 if ( c . icon ) char . icon = c . icon
99244 return char
0 commit comments