diff --git a/src/handbook/src/backend/data.ts b/src/handbook/src/backend/data.ts
index 0d4597e53..90d3a8bfb 100644
--- a/src/handbook/src/backend/data.ts
+++ b/src/handbook/src/backend/data.ts
@@ -40,6 +40,13 @@ export function getAvatars(): AvatarDump {
return map;
}
+/**
+ * Fetches and lists all the avatars in the file.
+ */
+export function listAvatars(): Avatar[] {
+ return Object.values(getAvatars());
+}
+
/**
* Fetches and casts all items in the file.
*/
diff --git a/src/handbook/src/backend/types.ts b/src/handbook/src/backend/types.ts
index 35b9bc9c0..4ded0f43e 100644
--- a/src/handbook/src/backend/types.ts
+++ b/src/handbook/src/backend/types.ts
@@ -1,4 +1,4 @@
-export type Page = "Home" | "Commands";
+export type Page = "Home" | "Commands" | "Avatars";
export type Command = {
name: string[];
diff --git a/src/handbook/src/css/App.scss b/src/handbook/src/css/App.scss
index 9256bf764..668df9cef 100644
--- a/src/handbook/src/css/App.scss
+++ b/src/handbook/src/css/App.scss
@@ -38,10 +38,6 @@ body {
width: 100%;
height: 100%;
-
- div {
- display: flex;
- }
}
::-webkit-scrollbar {
diff --git a/src/handbook/src/css/pages/AvatarsPage.scss b/src/handbook/src/css/pages/AvatarsPage.scss
new file mode 100644
index 000000000..a172e1cdf
--- /dev/null
+++ b/src/handbook/src/css/pages/AvatarsPage.scss
@@ -0,0 +1,31 @@
+.AvatarsPage {
+ display: flex;
+ height: 100%;
+ width: 100%;
+
+ background-color: var(--background-color);
+ flex-direction: column;
+
+ padding: 24px;
+}
+
+.AvatarsPage_Title {
+ max-width: 275px;
+ max-height: 60px;
+
+ font-size: 48px;
+ font-weight: bold;
+ text-align: center;
+
+ margin-bottom: 30px;
+}
+
+.AvatarsPage_List {
+ display: grid;
+ gap: 15px 15px;
+
+ grid-template-columns: repeat(15, 100px);
+
+ margin-bottom: 28px;
+ overflow-y: scroll;
+}
diff --git a/src/handbook/src/css/pages/HomePage.scss b/src/handbook/src/css/pages/HomePage.scss
index 0edc9ce17..ee2afacdc 100644
--- a/src/handbook/src/css/pages/HomePage.scss
+++ b/src/handbook/src/css/pages/HomePage.scss
@@ -6,6 +6,10 @@
background-color: var(--background-color);
flex-direction: column;
justify-content: space-between;
+
+ div {
+ display: flex;
+ }
}
.HomePage_Top {
diff --git a/src/handbook/src/css/widgets/Character.scss b/src/handbook/src/css/widgets/Character.scss
index de1e75337..1a30cb049 100644
--- a/src/handbook/src/css/widgets/Character.scss
+++ b/src/handbook/src/css/widgets/Character.scss
@@ -2,12 +2,12 @@
display: flex;
flex-direction: column;
- background-color: var(--legendary-color);
-
max-width: 100px;
- max-height: 125px;
+ max-height: 150px;
border-radius: 15px;
+ height: 100%;
+
overflow: hidden;
}
@@ -20,15 +20,17 @@
}
.Character_Label {
+ display: flex;
align-items: center;
justify-content: center;
background-color: var(--secondary-color);
max-width: 100px;
- height: 25px;
+ height: 50px;
p {
- color: var(--text-primary-color);
font-size: 18px;
+ text-align: center;
+ height: 100%;
}
}
diff --git a/src/handbook/src/ui/pages/AvatarsPage.tsx b/src/handbook/src/ui/pages/AvatarsPage.tsx
new file mode 100644
index 000000000..19445f951
--- /dev/null
+++ b/src/handbook/src/ui/pages/AvatarsPage.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+import Character from "@app/ui/widgets/Character";
+
+import { listAvatars } from "@backend/data";
+
+import "@css/pages/AvatarsPage.scss";
+
+class AvatarsPage extends React.PureComponent {
+ render() {
+ return (
+
+
Characters
+
+
+ {
+ listAvatars().map(avatar => (
+ avatar.id > 11000000 ? undefined :
+
+ ))
+ }
+
+
+ );
+ }
+}
+
+export default AvatarsPage;
diff --git a/src/handbook/src/ui/pages/CommandsPage.tsx b/src/handbook/src/ui/pages/CommandsPage.tsx
index 78fcc9191..7901cd1dc 100644
--- a/src/handbook/src/ui/pages/CommandsPage.tsx
+++ b/src/handbook/src/ui/pages/CommandsPage.tsx
@@ -16,6 +16,7 @@ class CommandsPage extends React.PureComponent {
{
listCommands().map(command => (
{
-
+
diff --git a/src/handbook/src/ui/views/Content.tsx b/src/handbook/src/ui/views/Content.tsx
index 3bf00b3a8..866ffa558 100644
--- a/src/handbook/src/ui/views/Content.tsx
+++ b/src/handbook/src/ui/views/Content.tsx
@@ -2,11 +2,12 @@ import React from "react";
import HomePage from "@pages/HomePage";
import CommandsPage from "@pages/CommandsPage";
+import AvatarsPage from "@pages/AvatarsPage";
import type { Page } from "@backend/types";
+import { addNavListener, removeNavListener } from "@backend/events";
import "@css/views/Content.scss";
-import { addNavListener, removeNavListener } from "@backend/events";
interface IProps {
initial?: Page | null;
@@ -48,6 +49,7 @@ class Content extends React.Component
{
default: return undefined;
case "Home": return ;
case "Commands": return ;
+ case "Avatars": return ;
}
}
}
diff --git a/src/handbook/src/ui/views/SideBar.tsx b/src/handbook/src/ui/views/SideBar.tsx
index b1db7df7b..3b69f53f9 100644
--- a/src/handbook/src/ui/views/SideBar.tsx
+++ b/src/handbook/src/ui/views/SideBar.tsx
@@ -23,7 +23,7 @@ class SideBar extends React.Component {
-
+
diff --git a/src/handbook/src/ui/widgets/Character.tsx b/src/handbook/src/ui/widgets/Character.tsx
index ae4da9a6b..92dc7500d 100644
--- a/src/handbook/src/ui/widgets/Character.tsx
+++ b/src/handbook/src/ui/widgets/Character.tsx
@@ -1,5 +1,8 @@
import React from "react";
+import type { Avatar } from "@backend/types";
+import { colorFor } from "@app/utils";
+
import "@css/widgets/Character.scss";
// Image base URL: https://paimon.moe/images/characters/(name).png
@@ -9,13 +12,30 @@ import "@css/widgets/Character.scss";
* Example: Hu Tao -> hu_tao
*
* @param name The character's name.
+ * @param id The character's ID.
*/
-function formatName(name: string): string {
+function formatName(name: string, id: number): string {
+ // Check if a different name is used for the character.
+ if (refSwitch[id]) name = refSwitch[id];
return name.toLowerCase().replace(" ", "_");
}
+const ignored = [
+ 10000001 // Kate
+];
+
+const refSwitch: { [key: number]: string } = {
+ 10000005: "traveler_anemo",
+ 10000007: "traveler_geo",
+};
+
+const nameSwitch: { [key: number]: string } = {
+ 10000005: "Lumine",
+ 10000007: "Aether",
+};
+
interface IProps {
- name: string; // paimon.moe reference name.
+ data: Avatar;
}
class Character extends React.PureComponent
{
@@ -24,16 +44,26 @@ class Character extends React.PureComponent {
}
render() {
+ const { name, quality, id } = this.props.data;
+ const qualityColor = colorFor(quality);
+
+ // Check if the avatar is blacklisted.
+ if (ignored.includes(id))
+ return undefined;
+
return (
-
+
-
{this.props.name}
+
{nameSwitch[id] ?? name}
);
diff --git a/src/handbook/src/utils.ts b/src/handbook/src/utils.ts
new file mode 100644
index 000000000..821538017
--- /dev/null
+++ b/src/handbook/src/utils.ts
@@ -0,0 +1,17 @@
+import { Quality } from "@backend/types";
+
+/**
+ * Fetches the name of the CSS variable for the quality.
+ *
+ * @param quality The quality of the item.
+ */
+export function colorFor(quality: Quality): string {
+ switch (quality) {
+ default: return "--legendary-color";
+ case "EPIC": return "--epic-color";
+ case "RARE": return "--rare-color";
+ case "UNCOMMON": return "--uncommon-color";
+ case "COMMON": return "--common-color";
+ case "UNKNOWN": return "--unknown-color";
+ }
+}