Skip to content

Commit 7660682

Browse files
committed
Add artifact pages
1 parent ee4f035 commit 7660682

File tree

7 files changed

+312
-27
lines changed

7 files changed

+312
-27
lines changed

components/Icon.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @next/next/no-img-element */
22
import Image from "next/image"
3-
import { SmallChar, SmallWeapon } from "../utils/types"
3+
import { SmallArtifact, SmallChar, SmallWeapon } from "../utils/types"
44
import { elements, getStarColor, image, urlify, weapons } from "../utils/utils"
55
import FormattedLink from "./FormattedLink"
66
import styles from "../pages/style.module.css"
@@ -24,23 +24,23 @@ export function IconName({ name, type, urltype, loading = "lazy" }: { name: stri
2424
</FormattedLink>
2525
}
2626

27-
28-
export function SmallIcon({ thing, location }: { thing: SmallChar | SmallWeapon, location: string }) {
27+
type SmallThing = SmallChar | SmallWeapon | SmallArtifact
28+
export function SmallIcon({ thing, location }: { thing: SmallThing, location: string }) {
2929
const color = getStarColor(thing.stars ?? 0)
3030

31-
return <FormattedLink key={thing.name} location={location} href={`/${isSmallChar(thing) ? "characters" : "weapons"}/${urlify(thing.name, false)}`} className="bg-slate-300 dark:bg-slate-800 w-24 sm:w-28 lg:w-32 m-1 relative rounded-xl transition-all duration-100 hover:outline outline-slate-800 dark:outline-slate-300 font-bold text-sm" >
31+
return <FormattedLink key={thing.name} location={location} href={`/${thing.urlpath}/${urlify(thing.name, false)}`} className="bg-slate-300 dark:bg-slate-800 w-24 sm:w-28 lg:w-32 m-1 relative rounded-xl transition-all duration-100 hover:outline outline-slate-800 dark:outline-slate-300 font-bold text-sm" >
3232
<div className={`${color} rounded-t-xl h-24 sm:h-28 lg:h-32`}>
3333
<Icon icon={thing} className="rounded-t-xl m-0 p-0" />
3434
<span className="absolute block p-0.5 top-0 w-full">
3535
<div className="flex flex-col">
36-
{isSmallChar(thing) && thing.element && thing.element.map(e => <div key={e} className="w-6 md:w-8">
36+
{hasElement(thing) && thing.element && thing.element.map(e => <div key={e} className="w-6 md:w-8">
3737
<Image src={elements[e]} alt={`${e} Element`} loading="eager" />
3838
</div>)}
3939
</div>
4040
</span>
4141
<span className="absolute block p-0.5 top-0 w-full">
4242
<div className="flex flex-col float-right">
43-
{thing.weapon && <div className="w-6 md:w-8">
43+
{hasWeapon(thing) && thing.weapon && <div className="w-6 md:w-8">
4444
<Image src={weapons[thing.weapon]} alt={thing.weapon} loading="eager" />
4545
</div>}
4646
</div>
@@ -52,6 +52,10 @@ export function SmallIcon({ thing, location }: { thing: SmallChar | SmallWeapon,
5252
</FormattedLink>
5353
}
5454

55-
function isSmallChar(char: SmallChar | SmallWeapon): char is SmallChar {
55+
function hasElement(char: SmallThing): char is SmallChar {
5656
return (char as SmallChar).element != undefined
5757
}
58+
59+
function hasWeapon(char: SmallThing): char is (SmallChar | SmallWeapon) {
60+
return (char as SmallChar).weapon != undefined
61+
}

components/NavBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState } from "react"
22
import FormattedLink from "./FormattedLink"
33

4-
const pages = ["Guides", "Characters", "Weapons", "Materials", "Reminders"]
4+
const pages = ["Guides", "Characters", "Weapons", "Materials", "Artifacts", "Reminders"]
55

66
export default function NavBar({ location }: {location: string}) {
77
const [menuOpen, setMenuOpen] = useState(false)

pages/artifacts/[artifact].tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { GetStaticPathsResult, GetStaticPropsContext, GetStaticPropsResult } from "next"
2+
import Head from "next/head"
3+
import ReactMarkdown from "react-markdown"
4+
import FormattedLink from "../../components/FormattedLink"
5+
import Guides from "../../components/Guides"
6+
import Icon from "../../components/Icon"
7+
import Main from "../../components/Main"
8+
import { getArtifacts } from "../../utils/data-cache"
9+
import { Arti, Artifact, Bonus } from "../../utils/types"
10+
import { getGuidesFor, getLinkToGuide, getStarColor, joinMulti, urlify } from "../../utils/utils"
11+
12+
interface Props {
13+
artifact: Artifact,
14+
guides?: string[][],
15+
}
16+
17+
export default function ArtifactWebpage({ artifact, location, guides }: Props & { location: string }) {
18+
const color = getStarColor(Math.max(...(artifact.levels ?? [1])))
19+
20+
return (
21+
<Main>
22+
<Head>
23+
<title>{artifact.name} | Hu Tao</title>
24+
<meta name="twitter:card" content="summary" />
25+
<meta property="og:title" content={`${artifact.name} | Hu Tao`} />
26+
<meta property="og:description" content={`View ${artifact.name} information`} />
27+
</Head>
28+
<h2 className="font-semibold">
29+
<FormattedLink href="/artifacts/" location={location} className="font-semibold text-lg">
30+
Artifacts
31+
</FormattedLink>
32+
</h2>
33+
34+
<h1 className="text-4xl font-bold pb-2 sm:sr-only not-sr-only">
35+
<Icon icon={{ name: artifact.name, icon: artifact.artis?.[0]?.icon }} className={`${color} rounded-xl sm:w-0 mr-2 w-12 inline-block`} />
36+
{artifact.name}
37+
</h1>
38+
39+
<div className="grid grid-flow-col justify-start">
40+
<div className="sm:w-36 mr-2 w-0 ">
41+
<Icon icon={{ name: artifact.name, icon: artifact.artis?.[0]?.icon }} className={`${color} rounded-xl`} />
42+
</div>
43+
44+
<div id="description" className="w-full">
45+
<h1 className="text-4xl font-bold pb-2 sm:not-sr-only sr-only">
46+
{artifact.name}
47+
</h1>
48+
49+
{artifact.levels && <div className="inline-block pr-2">
50+
Available in {joinMulti(artifact.levels.map(l => `${l}★`))}
51+
</div>}
52+
</div>
53+
</div>
54+
55+
<div id="details">
56+
{guides && guides.length > 0 && <Guides guides={guides} />}
57+
{artifact.bonuses && artifact.bonuses.length > 0 && <Bonuses bonuses={artifact.bonuses} />}
58+
{artifact.artis && artifact.artis.length > 0 && <Artis artis={artifact.artis} />}
59+
60+
</div>
61+
</Main>
62+
)
63+
}
64+
65+
function Bonuses({ bonuses }: { bonuses: Bonus[] }) {
66+
return <>
67+
<h3 className="text-lg font-bold pt-1" id="bonuses">Bonuses:</h3>
68+
{bonuses.map(bonus => <div key={bonus.count} className="py-2">
69+
<div className="font-semibold">{bonus.count}-Piece Set Bonus</div>
70+
{bonus.desc}
71+
</div>)}
72+
</>
73+
}
74+
75+
function Artis({ artis }: { artis: Arti[] }) {
76+
return <>
77+
<h3 className="text-lg font-bold pt-1" id="artis">Pieces:</h3>
78+
{artis.map(arti =>
79+
<div key={arti.type} className="border p-1 rounded-xl mb-2 border-opacity-75">
80+
<div className="flex flex-row items-center" id={urlify(arti.name, false)}>
81+
{arti.icon && <Icon icon={arti} className="rounded-full w-16 h-16 mr-2 " />}
82+
<div className="font-bold">{arti.name}</div>
83+
</div>
84+
<div className="flex flex-col pb-1 pl-1">
85+
<ReactMarkdown>{(arti.desc?.replace(/ ?\$\{.*?\}/g, "").replace(/\n/g, "\n\n") ?? "")}</ReactMarkdown>
86+
</div>
87+
</div>
88+
)}
89+
</>
90+
}
91+
92+
export async function getStaticProps(context: GetStaticPropsContext): Promise<GetStaticPropsResult<Props>> {
93+
const artifactName = context.params?.artifact
94+
const data = await getArtifacts()
95+
96+
const artifact = Object.values(data ?? {}).find(g => urlify(g.name, false) == artifactName)
97+
if (!data || !artifact) {
98+
return {
99+
notFound: true,
100+
revalidate: 5 * 60
101+
}
102+
}
103+
104+
const guides = (await getGuidesFor("artifact", artifact.name))?.map(({ guide, page }) => [page.name, getLinkToGuide(guide, page)])
105+
106+
return {
107+
props: {
108+
artifact,
109+
guides,
110+
},
111+
revalidate: 60 * 60
112+
}
113+
}
114+
115+
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
116+
const data = await getArtifacts()
117+
return {
118+
paths: Object.values(data ?? {}).map(a => ({
119+
params: { artifact: urlify(a.name, false) }
120+
})) ?? [],
121+
fallback: "blocking"
122+
}
123+
}

pages/artifacts/index.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { GetStaticPropsContext, GetStaticPropsResult } from "next"
2+
import Head from "next/head"
3+
import { useState } from "react"
4+
import { ExclusiveButton } from "../../components/Filters"
5+
import { SmallIcon } from "../../components/Icon"
6+
import Main from "../../components/Main"
7+
import { getArtifacts } from "../../utils/data-cache"
8+
import { SmallArtifact } from "../../utils/types"
9+
import { createSmallArtifact } from "../../utils/utils"
10+
11+
12+
interface Props {
13+
artifacts: SmallArtifact[]
14+
}
15+
16+
export default function Artifacts(props: Props & { location: string }) {
17+
const [filter, setFilter] = useState(false)
18+
19+
const [starFilter, setStarFilter] = useState(0)
20+
21+
return (
22+
<Main>
23+
<Head>
24+
<title>Artifacts | Hu Tao</title>
25+
<meta name="twitter:card" content="summary" />
26+
<meta property="og:title" content="Artifacts | Hu Tao" />
27+
<meta property="og:description" content="View information about different Genshin Impact artifacts!" />
28+
</Head>
29+
30+
<h1 className="text-5xl font-bold pb-2">
31+
Artifacts
32+
</h1>
33+
34+
{filter ? <div className="bg-slate-100 dark:bg-slate-600 flex flex-col p-2 rounded-2xl font-semibold gap-2">
35+
<div className="pb-2">
36+
<div className="flex flex-row font-semibold float-right">
37+
<ExclusiveButton type={filter} value={false} setter={setFilter}>
38+
Hide filters
39+
</ExclusiveButton>
40+
</div>
41+
<div>
42+
Maximum rarity filter
43+
</div>
44+
<div className="flex flex-row flex-wrap gap-2 pt-2">
45+
<ExclusiveButton type={starFilter} value={0} setter={setStarFilter}>
46+
All
47+
</ExclusiveButton>
48+
<ExclusiveButton type={starFilter} value={5} setter={setStarFilter}>
49+
Max 5★ Only
50+
</ExclusiveButton>
51+
<ExclusiveButton type={starFilter} value={4} setter={setStarFilter}>
52+
Max 4★ Only
53+
</ExclusiveButton>
54+
<ExclusiveButton type={starFilter} value={3} setter={setStarFilter}>
55+
Max 3★ Only
56+
</ExclusiveButton>
57+
</div>
58+
</div>
59+
</div> : <div className="flex flex-row font-semibold justify-end">
60+
<ExclusiveButton type={filter} value={true} setter={setFilter}>
61+
Show filters
62+
</ExclusiveButton>
63+
</div>
64+
}
65+
66+
<div className="flex flex-wrap justify-evenly text-center pt-2">
67+
{props.artifacts
68+
.filter(w => starFilter == 0 || starFilter == w.stars)
69+
.map(artifact => <SmallIcon key={artifact.name} thing={artifact} location={props.location} />)}
70+
</div>
71+
</Main>
72+
)
73+
}
74+
75+
76+
export async function getStaticProps(context: GetStaticPropsContext): Promise<GetStaticPropsResult<Props>> {
77+
const data = await getArtifacts()
78+
79+
if (!data) {
80+
return {
81+
notFound: true,
82+
revalidate: 5 * 60
83+
}
84+
}
85+
86+
return {
87+
props: {
88+
artifacts: Object
89+
.values(data)
90+
.sort((a, b) => Math.max(...b.levels) - Math.max(...a.levels) || Math.min(...a.levels) - Math.min(...b.levels) || a.name.localeCompare(b.name))
91+
.map(w => createSmallArtifact(w))
92+
},
93+
revalidate: 60 * 60
94+
}
95+
}

utils/data-cache.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
import { Character, Cost, CurveEnum, Guide, Material, Weapon, WeaponCurveName } from "./types"
1+
import { Artifact, Character, Cost, CurveEnum, Guide, Material, Weapon, WeaponCurveName } from "./types"
22

33
const baseURL = "https://raw.githubusercontent.com/Tibowl/HuTao/master/src/data"
44

5-
type Guides = Guide[]
5+
type Artifacts = Record<string, Artifact>
6+
67
type Characters = Record<string, Character>
78
export type CharacterCurves = Record<CurveEnum, number[]>
89
type CharacterLevels = number[]
10+
911
export type CostTemplates = Record<string, Cost[]>
12+
13+
type Guides = Guide[]
14+
1015
type Materials = Record<string, Material>
16+
1117
type Weapons = Record<string, Weapon>
1218
export type WeaponCurves = Record<WeaponCurveName, number[]>
1319

20+
1421
type Cache = {
15-
guides: Cacher<Guides>
22+
artifacts: Cacher<Artifacts>
1623
characters: Cacher<Characters>
1724
characterCurves: Cacher<CharacterCurves>
1825
characterLevels: Cacher<CharacterLevels>
1926
costTemplates: Cacher<CostTemplates>
27+
guides: Cacher<Guides>
2028
materials: Cacher<Materials>
2129
weapons: Cacher<Weapons>
2230
weaponCurves: Cacher<WeaponCurves>
@@ -28,6 +36,9 @@ interface Cacher<T> {
2836
}
2937

3038
const cached: Cache = {
39+
artifacts: {
40+
time: 0
41+
},
3142
guides: {
3243
time: 0
3344
},
@@ -57,6 +68,8 @@ const cached: Cache = {
5768
export const getGuides: (() => Promise<Guides | undefined>) = createGetCacheable("guides")
5869
export const getCostTemplates: (() => Promise<CostTemplates | undefined>) = createGetCacheable("costTemplates", "gamedata/cost_templates")
5970

71+
export const getArtifacts: (() => Promise<Artifacts | undefined>) = createGetCacheable("artifacts", "gamedata/artifacts")
72+
6073
export const getCharacters: (() => Promise<Characters | undefined>) = createGetCacheable("characters", "gamedata/characters")
6174
export const getCharacterCurves: (() => Promise<CharacterCurves | undefined>) = createGetCacheable("characterCurves", "gamedata/character_curves")
6275
export const getCharacterLevels: (() => Promise<CharacterLevels | undefined>) = createGetCacheable("characterLevels", "gamedata/character_levels")

utils/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ export interface Reminder {
2121
duration: number
2222
}
2323

24+
// Artifacts
25+
export interface Artifact {
26+
name: string
27+
levels: number[]
28+
bonuses: Bonus[]
29+
artis: Arti[]
30+
}
31+
32+
export interface Arti {
33+
type: ArtifactType
34+
name: string
35+
desc: string
36+
icon: string
37+
}
38+
39+
export enum ArtifactType {
40+
Flower = "Flower",
41+
Plume = "Plume",
42+
Sands = "Sands",
43+
Goblet = "Goblet",
44+
Circlet = "Circlet",
45+
}
46+
47+
export interface Bonus {
48+
count: number
49+
desc: string
50+
}
2451

2552
// Character
2653
export type Character = CharacterFull | CharacterPlaceholder
@@ -267,6 +294,7 @@ export interface GuidePage {
267294
links?: {
268295
material?: string[]
269296
weapon?: string[]
297+
artifact?: string[]
270298
enemy?: string[]
271299
character?: string[]
272300
}
@@ -278,11 +306,20 @@ export interface SmallChar {
278306
element?: ElementType[]
279307
weapon?: WeaponType
280308
icon?: string
309+
urlpath: "characters"
281310
}
282311

283312
export interface SmallWeapon {
284313
name: string
285314
stars?: number
286315
weapon?: WeaponType
287316
icon?: string
317+
urlpath: "weapons"
318+
}
319+
320+
export interface SmallArtifact {
321+
name: string
322+
stars?: number
323+
icon?: string
324+
urlpath: "artifacts"
288325
}

0 commit comments

Comments
 (0)