@@ -5,16 +5,18 @@ import { useState } from "react"
55import FormattedLink from "../../components/FormattedLink"
66import Main from "../../components/Main"
77import YouTube from "../../components/YouTube"
8- import { getCharacterCurves , getCharacters , urlify } from "../../utils/data-cache"
9- import { elements , ElementType , getCharStatsAt , isFullCharacter , stat , weapons } from "../../utils/utils"
10- import { Character , CharacterFull , CostTemplate , CurveEnum , Skills } from "../../utils/types"
8+ import { CharacterCurves , CostTemplates , getCharacterCurves , getCharacters , getCostTemplates , urlify } from "../../utils/data-cache"
9+ import { elements , ElementType , getCharStatsAt , getCostsFromTemplate , image , isFullCharacter , stat , weapons } from "../../utils/utils"
10+ import { Character , CharacterFull , CostTemplate , CurveEnum , Meta , Skills } from "../../utils/types"
11+ import styles from "../style.module.css"
1112
1213interface Props {
1314 char : Character ,
14- characterCurves : Record < CurveEnum , number [ ] > | null
15+ characterCurves : CharacterCurves | null
16+ costTemplates : CostTemplates | null
1517}
1618
17- export default function CharacterWebpage ( { char, location, characterCurves } : Props & { location : string } ) {
19+ export default function CharacterWebpage ( { char, location, characterCurves, costTemplates } : Props & { location : string } ) {
1820 const charElems = char . skills ?. map ( skill => skill . ult ?. type ) . filter ( x => x ) as ElementType [ ] ?? [ char . meta . element ]
1921 return (
2022 < Main >
@@ -34,12 +36,15 @@ export default function CharacterWebpage({ char, location, characterCurves }: Pr
3436 { char . name }
3537 </ h1 >
3638
37- < div className = "float-right border-2 border-slate-500 dark:bg-slate-600 bg-slate-200 m-2" >
38- Table of Content
39+ < div className = "float-right border-2 border-slate-500 dark:bg-slate-600 bg-slate-200 m-2 px-2" >
40+ Table of Contents
41+ { isFullCharacter ( char ) && characterCurves && < TOC href = "#stats" title = "Stats / Ascensions" /> }
42+ { char . meta && < TOC href = "#meta" title = "Meta" /> }
43+ { char . media . videos && < TOC href = "#videos" title = { Object . keys ( char . media . videos ) . length > 1 ? "Videos" : "Video" } /> }
3944 </ div >
4045
41- < div >
42- < blockquote className = "pl-5 border-l-2" >
46+ < div id = "description" >
47+ < blockquote className = "pl-5 mb-2 border-l-2" >
4348 { char . desc }
4449 </ blockquote >
4550
@@ -61,29 +66,69 @@ export default function CharacterWebpage({ char, location, characterCurves }: Pr
6166
6267 { char . ascensionCosts && < AscensionCosts costs = { char . ascensionCosts } /> }
6368 { char . skills && < TalentCosts skills = { char . skills } /> }
64- { isFullCharacter ( char ) && characterCurves && < QuickStats char = { char } curves = { characterCurves } /> }
69+ { isFullCharacter ( char ) && characterCurves && < Stats char = { char } curves = { characterCurves } /> }
70+ { char . ascensionCosts && costTemplates && < FullAscensionCosts template = { char . ascensionCosts } costTemplates = { costTemplates } /> }
71+ { char . meta && < Meta meta = { char . meta } /> }
6572 { char . media . videos && < Videos videos = { char . media . videos } /> }
6673 </ div >
67-
6874 </ Main >
6975 )
7076}
7177
78+ function TOC ( { href, title } : { href : string , title : string } ) {
79+ return < div >
80+ < FormattedLink href = { href } size = "base" > { title } </ FormattedLink >
81+ </ div >
82+ }
83+
7284function AscensionCosts ( { costs } : { costs : CostTemplate } ) {
7385 const ascensionCosts = [
7486 costs . mapping . Gem4 ,
7587 costs . mapping . BossMat ,
7688 costs . mapping . Specialty ,
7789 costs . mapping . EnemyDropTier3 ,
7890 ] . filter ( x => x )
79- return < div >
80- < h4 className = "text-base font-semibold pt-1 inline-block pr-1" > Ascension materials:</ h4 >
81- { ascensionCosts . map ( e => < div className = "inline-block pr-1" key = { e } >
82- { e }
91+ return < div className = "flex flex-row items-center" >
92+ < div className = "text-base font-semibold pt-1 inline-block pr-1 h-9 " > Ascension materials:</ div >
93+ { ascensionCosts . map ( e => < div className = "inline-block pr-1 w-6 h-6 md:h-8 md:w-8 " key = { e } >
94+ < Image src = { image ( "material" , e ) } alt = { e } width = { 256 } height = { 256 } />
8395 </ div > ) }
8496 </ div >
8597}
8698
99+ function FullAscensionCosts ( { template, costTemplates } : { template : CostTemplate , costTemplates : CostTemplates } ) {
100+ const costs = getCostsFromTemplate ( template , costTemplates )
101+
102+ return < >
103+ < table className = { `table-auto w-full ${ styles . table } mb-2` } >
104+ < thead className = "font-semibold divide-x divide-gray-200 dark:divide-gray-500" >
105+ < td > Asc.</ td >
106+ < td > Mora</ td >
107+ < td > Items</ td >
108+ </ thead >
109+ < tbody className = "divide-y divide-gray-200 dark:divide-gray-500" >
110+ { costs . slice ( 1 ) . map ( ( { mora, items } , ind ) => {
111+ let newItems = items
112+ if ( ind == 0 && template . mapping . BossMat )
113+ newItems = [ items [ 0 ] , { name : "" , count : 0 } , ...items . slice ( 1 ) ]
114+ return < tr className = "pr-1 divide-x divide-gray-200 dark:divide-gray-500" key = { ind } >
115+ < td > A{ ind + 1 } </ td >
116+ < td className = "text-right" > { mora } </ td >
117+ { newItems . map ( ( { count, name } ) => < td key = { name } >
118+ { count > 0 &&
119+ < div className = "flex flex-row align-middle items-center" >
120+ < div > { count } ×</ div >
121+ < div className = "pr-1 w-6 h-6 md:h-8 md:w-8" >
122+ < Image src = { image ( "material" , name ) } alt = { name } width = { 256 } height = { 256 } />
123+ </ div >
124+ < div > { name } </ div > </ div > }
125+ </ td > ) }
126+ </ tr > } ) }
127+ </ tbody >
128+ </ table >
129+ </ >
130+ }
131+
87132function TalentCosts ( { skills } : { skills : Skills [ ] } ) {
88133 const talents = skills
89134 . flatMap ( s => [ ...( s . talents ?? [ ] ) , s . ult ] )
@@ -107,48 +152,84 @@ function TalentCosts({ skills }: { skills: Skills[] }) {
107152 . map ( s => s ?. costs ?. mapping ?. EnemyDropTier3 )
108153 . filter ( ( x , i , a ) => x && a . indexOf ( x ) == i )
109154
110- const all = [ ...books , ...mats , ...drops ]
111- return < div >
112- < h4 className = "text-base font-semibold pt-1 inline-block pr-1" > Talent materials:</ h4 >
113- { all . map ( e => < div className = "inline-block pr-1" key = { e } >
114- { e }
155+ const all = [ ...books , ...mats , ...drops ] as string [ ]
156+ return < div className = "flex flex-row items-center" >
157+ < div className = "text-base font-semibold pt-1 inline-block pr-1 h-9 " > Talent materials:</ div >
158+ { all . map ( e => < div className = "inline-block pr-1 w-6 h-6 md:h-8 md:w-8 " key = { e } >
159+ < Image src = { image ( "material" , e ) } alt = { e } width = { 256 } height = { 256 } />
115160 </ div > ) }
116161 </ div >
117162}
118163
119- function QuickStats ( { char, curves } : { char : CharacterFull , curves : Record < CurveEnum , number [ ] > } ) {
164+ function Stats ( { char, curves } : { char : CharacterFull , curves : Record < CurveEnum , number [ ] > } ) {
120165 const maxAscension = char . ascensions [ char . ascensions . length - 1 ]
121166
122- const base = getCharStatsAt ( char , 1 , 0 , curves )
167+ const levels : { a : number , lv : number } [ ] = [ ]
168+
169+ let prev = 1
170+ for ( const asc of char . ascensions ) {
171+ levels . push ( { a : asc . level , lv : prev } )
172+ levels . push ( { a : asc . level , lv : asc . maxLevel } )
173+ prev = asc . maxLevel
174+ }
123175 const max = getCharStatsAt ( char , maxAscension . maxLevel , maxAscension . level , curves )
124176
125- // TODO
177+ return < >
178+ < h3 className = "text-lg font-bold pt-1" id = "stats" > Stats / Ascensions:</ h3 >
179+ < table className = { `table-auto w-full ${ styles . table } ${ styles . stattable } mb-2` } >
180+ < thead className = "font-semibold divide-x divide-gray-200 dark:divide-gray-500" >
181+ < td > Asc.</ td >
182+ < td > Lv.</ td >
183+ { Object . keys ( max ) . map ( ( name ) => < td key = { name } > { name } </ td > ) }
184+ </ thead >
185+ < tbody className = "divide-y divide-gray-200 dark:divide-gray-500" >
186+ { levels . map ( ( { a, lv } ) => < tr className = "pr-1 divide-x divide-gray-200 dark:divide-gray-500" key = { a + "," + lv } >
187+ < td > A{ a } </ td >
188+ < td > { lv } </ td >
189+ { Object . entries ( getCharStatsAt ( char , lv , a , curves ) ) . map ( ( [ name , value ] ) => < td key = { name } > { stat ( name , value ) } </ td > ) }
190+ </ tr > ) }
191+ </ tbody >
192+ </ table >
193+ </ >
194+ }
126195
127- return < div className = "flex flex-row justify-evenly" >
128- < div >
129- < h4 className = "text-base font-semibold pt-1 inline-block pr-1" > Base stats:</ h4 >
130- { Object . entries ( base )
131- . map ( ( [ name , value ] ) => < div className = "pr-1" key = { name } >
132- { name } : { stat ( name , value ) }
133- </ div > ) }
134- </ div >
135- < div >
136- < h4 className = "text-base font-semibold pt-1 inline-block pr-1" > Max stats:</ h4 >
137- { Object . entries ( max )
138- . map ( ( [ name , value ] ) => < div className = "pr-1" key = { name } >
139- { name } : { stat ( name , value ) }
140- </ div > ) }
141- </ div >
142- </ div >
196+ function Meta ( { meta } : { meta : Meta } ) {
197+ return < >
198+ < h3 className = "text-lg font-bold pt-1" id = "meta" > Meta:</ h3 >
199+ < table className = { `table-auto ${ styles . table } mb-2` } >
200+ < tbody className = "divide-y divide-gray-200 dark:divide-gray-500" >
201+ { meta . title && < tr > < td > Title</ td > < td > { meta . title } </ td > </ tr > }
202+ { meta . birthDay && meta . birthMonth && < tr > < td > Title</ td > < td > {
203+ new Date ( Date . UTC ( 2020 , meta . birthMonth - 1 , meta . birthDay ) )
204+ . toLocaleString ( "en-UK" , {
205+ timeZone : "UTC" ,
206+ month : "long" ,
207+ day : "numeric" ,
208+ } ) } </ td > </ tr > }
209+ { meta . association && < tr > < td > Association</ td > < td > { meta . association } </ td > </ tr > }
210+ { meta . affiliation && < tr > < td > Affiliation</ td > < td > { meta . affiliation } </ td > </ tr > }
211+ { meta . constellation && < tr > < td > Constellation</ td > < td > { meta . constellation } </ td > </ tr > }
212+ { meta . element && < tr > < td > Element</ td > < td > { Object . keys ( elements ) . includes ( meta . element ) ? < >
213+ < div className = "w-5 inline-block pr-1" >
214+ < Image src = { elements [ meta . element as ElementType ] } alt = { `${ meta . element } Element` } />
215+ </ div >
216+ { meta . element } </ > : meta . element } </ td > </ tr > }
217+ { meta . cvChinese && < tr > < td > Chinese voice actor</ td > < td > { meta . cvChinese } </ td > </ tr > }
218+ { meta . cvJapanese && < tr > < td > Japanese voice actor</ td > < td > { meta . cvJapanese } </ td > </ tr > }
219+ { meta . cvEnglish && < tr > < td > English voice actor</ td > < td > { meta . cvEnglish } </ td > </ tr > }
220+ { meta . cvKorean && < tr > < td > Korean voice actor</ td > < td > { meta . cvKorean } </ td > </ tr > }
221+ </ tbody >
222+ </ table >
223+ </ >
143224}
144225
145226function Videos ( { videos } : { videos : Record < string , string > } ) {
146227 const multiple = Object . keys ( videos ) . length > 1
147228
148- return < div >
149- < h3 className = "text-lg font-bold pt-1" > { multiple ? "Videos" : "Video" } :</ h3 >
229+ return < >
230+ < h3 className = "text-lg font-bold pt-1" id = "videos" > { multiple ? "Videos" : "Video" } :</ h3 >
150231 { Object . entries ( videos ) . map ( ( [ name , link ] ) => < Video key = { name } name = { name } link = { link } /> ) }
151- </ div >
232+ </ >
152233}
153234
154235function Video ( { name, link } : { name : string , link : string } ) {
@@ -176,13 +257,22 @@ export async function getStaticProps(context: GetStaticPropsContext): Promise<Ge
176257 if ( isFullCharacter ( char ) ) {
177258 const curves = await getCharacterCurves ( )
178259 if ( curves )
179- characterCurves = Object . fromEntries ( char . curves . map ( c => c . curve ) . filter ( ( v , i , arr ) => arr . indexOf ( v ) == i ) . map ( c => [ c , curves [ c ] ] ) ) as Record < CurveEnum , number [ ] >
260+ characterCurves = Object . fromEntries ( char . curves . map ( c => c . curve ) . filter ( ( v , i , arr ) => arr . indexOf ( v ) == i ) . map ( c => [ c , curves [ c ] ] ) ) as CharacterCurves
261+ }
262+
263+ let costTemplates = null
264+ if ( char . ascensionCosts ) {
265+ const templates = await getCostTemplates ( )
266+ if ( templates )
267+ costTemplates = Object . fromEntries ( [ char . ascensionCosts . template ] . filter ( ( v , i , arr ) => arr . indexOf ( v ) == i ) . map ( c => [ c , templates [ c ] ] ) ) as CostTemplates
268+
180269 }
181270
182271 return {
183272 props : {
184273 char,
185- characterCurves
274+ characterCurves,
275+ costTemplates
186276 } ,
187277 revalidate : 60 * 60
188278 }
0 commit comments