Skip to content

Commit 2707820

Browse files
committed
Add Enemies
1 parent b52a5e4 commit 2707820

File tree

9 files changed

+344
-53
lines changed

9 files changed

+344
-53
lines changed

components/Icon.tsx

Lines changed: 6 additions & 3 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 { SmallArtifact, SmallChar, SmallWeapon } from "../utils/types"
3+
import { SmallArtifact, SmallChar, SmallEnemy, SmallThing, 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,9 +24,8 @@ export function IconName({ name, type, urltype, loading = "lazy" }: { name: stri
2424
</FormattedLink>
2525
}
2626

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

3130
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" >
3231
<div className={`${color} rounded-t-xl h-24 sm:h-28 lg:h-32`}>
@@ -59,3 +58,7 @@ function hasElement(char: SmallThing): char is SmallChar {
5958
function hasWeapon(char: SmallThing): char is (SmallChar | SmallWeapon) {
6059
return (char as SmallChar).weapon != undefined
6160
}
61+
62+
function hasStars(char: SmallThing): char is (SmallChar | SmallWeapon | SmallArtifact) {
63+
return (char as SmallChar).stars != undefined
64+
}

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", "Artifacts", "Tools"]
4+
const pages = ["Guides", "Characters", "Weapons", "Enemies", "Materials", "Artifacts", "Tools"]
55

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

pages/enemies/[enemy].tsx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { GetStaticPathsResult, GetStaticPropsContext, GetStaticPropsResult } from "next"
2+
import Head from "next/head"
3+
import Image from "next/image"
4+
import ReactMarkdown from "react-markdown"
5+
import FormattedLink from "../../components/FormattedLink"
6+
import Guides from "../../components/Guides"
7+
import Icon from "../../components/Icon"
8+
import Main from "../../components/Main"
9+
import { getEnemies } from "../../utils/data-cache"
10+
import { Enemy } from "../../utils/types"
11+
import { elements, getGuidesFor, getLinkToGuide, getStarColor, urlify } from "../../utils/utils"
12+
import styles from "../style.module.css"
13+
14+
interface Props {
15+
enemy: Enemy,
16+
guides?: string[][],
17+
}
18+
19+
export default function EnemyWebpage({ enemy, location, guides }: Props & { location: string }) {
20+
const color = getStarColor(1)
21+
22+
return (
23+
<Main>
24+
<Head>
25+
<title>{enemy.name} | Hu Tao</title>
26+
<meta name="twitter:card" content="summary" />
27+
<meta property="og:title" content={`${enemy.name} | Hu Tao`} />
28+
<meta property="og:description" content={`View ${enemy.name} information`} />
29+
</Head>
30+
<h2 className="font-semibold">
31+
<FormattedLink href="/enemies/" location={location} className="font-semibold text-lg">
32+
Enemies
33+
</FormattedLink>
34+
</h2>
35+
36+
<h1 className="text-4xl font-bold pb-2 sm:sr-only not-sr-only">
37+
<Icon icon={enemy} className={`${color} rounded-xl sm:w-0 mr-2 w-12 inline-block`} />
38+
{enemy.name}
39+
</h1>
40+
41+
<div className="grid grid-flow-col justify-start">
42+
<div className="sm:w-36 mr-2 w-0 ">
43+
<Icon icon={enemy} className={`${color} rounded-xl`} />
44+
</div>
45+
46+
<div id="description" className="w-full">
47+
<h1 className="text-4xl font-bold pb-2 sm:not-sr-only sr-only">
48+
{enemy.name}
49+
</h1>
50+
51+
{enemy.type && <div className="inline-block pr-2 font-semibold">
52+
{enemy.type}
53+
</div>}
54+
55+
{enemy.kind && <div className="inline-block pr-2 ">
56+
{enemy.kind}
57+
</div>}
58+
59+
<blockquote className="pl-5 mt-3 mb-2 border-l-2">
60+
<ReactMarkdown>{(enemy.desc?.replace(/ ?\$\{.*?\}/g, "").replace(/\n/g, "\n\n") ?? "")}</ReactMarkdown>
61+
</blockquote>
62+
</div>
63+
</div>
64+
65+
<div id="details">
66+
{guides && guides.length > 0 && <Guides guides={guides} />}
67+
{enemy.notes && <ReactMarkdown>{(enemy.notes?.replace(/ ?\$\{.*?\}/g, "").replace(/\n/g, "\n\n") ?? "")}</ReactMarkdown>}
68+
{enemy.resistance && <Resistances resistance={enemy.resistance} />}
69+
</div>
70+
</Main>
71+
)
72+
}
73+
74+
function Resistances({ resistance }: { resistance: string[][] }) {
75+
function getStyles(r: string) {
76+
77+
if (r.match(/\d+%/)) {
78+
const amount = parseInt(r.replace("%", ""))
79+
if (amount == 10) return "opacity-60"
80+
else if (amount > 50) return "font-bold"
81+
else if (amount > 100) return "font-bold text-red-400"
82+
else if (amount < -50) return "font-semibold text-green-400"
83+
else if (amount < 0) return "text-green-400"
84+
}
85+
86+
return ""
87+
}
88+
return <>
89+
<h3 className="text-lg font-bold pt-1" id="resistance">Resistances:</h3>
90+
<table className={`table-auto w-full ${styles.table} mb-2 sm:text-base text-sm`}>
91+
<thead className="font-semibold divide-x divide-gray-200 dark:divide-gray-500">
92+
<td>
93+
<div className="flex flex-row items-center content-center">
94+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Pyro} alt={"Pyro Element"} /></div>
95+
<div>Pyro</div>
96+
</div>
97+
</td>
98+
<td>
99+
<div className="flex flex-row items-center content-center">
100+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Electro} alt={"Electro Element"} /></div>
101+
<div>Electro</div>
102+
</div>
103+
</td>
104+
<td>
105+
<div className="flex flex-row items-center content-center">
106+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Cryo} alt={"Cryo Element"} /></div>
107+
<div>Cryo</div>
108+
</div>
109+
</td>
110+
<td>
111+
<div className="flex flex-row items-center content-center">
112+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Hydro} alt={"Hydro Element"} /></div>
113+
<div>Hydro</div>
114+
</div>
115+
</td>
116+
<td>
117+
<div className="flex flex-row items-center content-center">
118+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Anemo} alt={"Anemo Element"} /></div>
119+
<div>Anemo</div>
120+
</div>
121+
</td>
122+
<td>
123+
<div className="flex flex-row items-center content-center">
124+
<div className="absolute invisible inline-block w-5 h-5 sm:relative sm:pr-1 sm:visible"><Image src={elements.Geo} alt={"Geo Element"} /></div>
125+
<div>Geo</div>
126+
</div>
127+
</td>
128+
<td>Physical</td>
129+
{resistance[0].length > 7 && <td>Note</td>}
130+
</thead>
131+
<tbody className="divide-y divide-gray-200 dark:divide-gray-500">
132+
{resistance
133+
.map((row, i) => <tr className="pr-1 divide-x divide-gray-200 dark:divide-gray-500" key={i}>
134+
{row.map((r, j) => <td key={i + "," + j} className={getStyles(r)}><ReactMarkdown>{r}</ReactMarkdown></td>)}
135+
</tr>)}
136+
</tbody>
137+
</table>
138+
</>
139+
}
140+
141+
export async function getStaticProps(context: GetStaticPropsContext): Promise<GetStaticPropsResult<Props>> {
142+
const enemyName = context.params?.enemy
143+
const data = await getEnemies()
144+
145+
const enemy = Object.values(data ?? {}).find(e => urlify(e.name, false) == enemyName)
146+
if (!data || !enemy) {
147+
return {
148+
notFound: true,
149+
revalidate: 5 * 60
150+
}
151+
}
152+
153+
const guides = (await getGuidesFor("enemy", enemy.name))?.map(({ guide, page }) => [page.name, getLinkToGuide(guide, page)])
154+
155+
return {
156+
props: {
157+
enemy,
158+
guides,
159+
},
160+
revalidate: 60 * 60
161+
}
162+
}
163+
164+
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
165+
const data = await getEnemies()
166+
return {
167+
paths: Object.values(data ?? {}).map(e => ({
168+
params: { enemy: urlify(e.name, false) }
169+
})) ?? [],
170+
fallback: "blocking"
171+
}
172+
}

pages/enemies/index.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { GetStaticPropsContext, GetStaticPropsResult } from "next"
2+
import Head from "next/head"
3+
import Image from "next/image"
4+
import { useState } from "react"
5+
import { ExclusiveButton, ToggleAllButton, ToggleButton } from "../../components/Filters"
6+
import { SmallIcon } from "../../components/Icon"
7+
import Main from "../../components/Main"
8+
import { getEnemies } from "../../utils/data-cache"
9+
import { SmallEnemy } from "../../utils/types"
10+
import { createSmallEnemy } from "../../utils/utils"
11+
12+
13+
interface Props {
14+
enemies: SmallEnemy[]
15+
}
16+
17+
export default function Enemies(props: Props & { location: string }) {
18+
const [filter, setFilter] = useState(false)
19+
20+
const defaultKinds = props.enemies.map(m => m.kind).filter((c, i, a) => c && a.indexOf(c) == i) as string[]
21+
const defaultTypes = props.enemies.map(m => m.type).filter((c, i, a) => c && a.indexOf(c) == i) as string[]
22+
23+
const [kindFilter, setKindFilter] = useState(defaultKinds)
24+
const [typeFilter, setTypeFilter] = useState(defaultTypes)
25+
26+
return (
27+
<Main>
28+
<Head>
29+
<title>Enemies | Hu Tao</title>
30+
<meta name="twitter:card" content="summary" />
31+
<meta property="og:title" content="Enemies | Hu Tao" />
32+
<meta property="og:description" content="View information about different Genshin Impact enemies!" />
33+
</Head>
34+
35+
<h1 className="text-5xl font-bold pb-2">
36+
Enemies
37+
</h1>
38+
39+
{filter ? <div className="bg-slate-100 dark:bg-slate-600 flex flex-col p-2 rounded-2xl font-semibold gap-2">
40+
<div className="pb-2">
41+
<div className="flex flex-row font-semibold float-right">
42+
<ExclusiveButton type={filter} value={false} setter={setFilter}>
43+
Hide filters
44+
</ExclusiveButton>
45+
</div>
46+
<div className="flex flex-row gap-4 pt-2">
47+
Enemy kind
48+
<ToggleAllButton type={kindFilter} value={defaultKinds} setter={setKindFilter}>
49+
All
50+
</ToggleAllButton>
51+
</div>
52+
<div className="flex flex-row flex-wrap gap-2 pt-2">
53+
{defaultKinds.map(e => (
54+
<ToggleButton key={e} type={kindFilter} value={e} setter={setKindFilter}>
55+
{e}
56+
</ToggleButton>
57+
))}
58+
</div>
59+
</div>
60+
<div className="py-1">
61+
<div className="flex flex-row gap-4 pt-2">
62+
Enemy type
63+
<ToggleAllButton type={typeFilter} value={defaultTypes} setter={setTypeFilter}>
64+
All
65+
</ToggleAllButton>
66+
</div>
67+
<div className="flex flex-row flex-wrap gap-2 pt-2">
68+
{defaultTypes.map(e => (
69+
<ToggleButton key={e} type={typeFilter} value={e} setter={setTypeFilter}>
70+
{e}
71+
</ToggleButton>
72+
))}
73+
</div>
74+
</div>
75+
</div> : <div className="flex flex-row font-semibold justify-end">
76+
<ExclusiveButton type={filter} value={true} setter={setFilter}>
77+
Show filters
78+
</ExclusiveButton>
79+
</div>
80+
}
81+
82+
<div className="flex flex-wrap justify-evenly text-center pt-2">
83+
{props.enemies
84+
.filter(m => typeFilter.some(e => m.type?.includes(e)) || m.type == undefined)
85+
.filter(m => kindFilter.some(e => m.kind?.includes(e)) || m.kind == undefined)
86+
.map(weapon => <SmallIcon key={weapon.name} thing={weapon} location={props.location} />)}
87+
</div>
88+
</Main>
89+
)
90+
}
91+
92+
93+
export async function getStaticProps(context: GetStaticPropsContext): Promise<GetStaticPropsResult<Props>> {
94+
const data = await getEnemies()
95+
96+
if (!data) {
97+
return {
98+
notFound: true,
99+
revalidate: 5 * 60
100+
}
101+
}
102+
103+
return {
104+
props: {
105+
enemies: Object
106+
.values(data)
107+
.map(w => createSmallEnemy(w))
108+
},
109+
revalidate: 60 * 60
110+
}
111+
}

pages/tools/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function Tools({ location }: {location: string}) {
1919
<ul>
2020
<li>
2121
-{" "}
22-
<FormattedLink className="font-semibold text-xl" location={location} href="/tools/reminders" >
22+
<FormattedLink className="font-semibold text-2xl" location={location} href="/tools/reminders" >
2323
Reminders
2424
</FormattedLink>
2525
<div className="ml-6">

public/img/enemy/The_Rock.png

75.3 KB
Loading

0 commit comments

Comments
 (0)