Implement avatars/characters page

Handle edge-cases for avatar image rendering
This commit is contained in:
KingRainbow44 2023-04-06 23:54:15 -04:00
parent 1f27f83616
commit 757d682cd6
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
13 changed files with 137 additions and 19 deletions

View File

@ -40,6 +40,13 @@ export function getAvatars(): AvatarDump {
return map; 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. * Fetches and casts all items in the file.
*/ */

View File

@ -1,4 +1,4 @@
export type Page = "Home" | "Commands"; export type Page = "Home" | "Commands" | "Avatars";
export type Command = { export type Command = {
name: string[]; name: string[];

View File

@ -38,10 +38,6 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
div {
display: flex;
}
} }
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -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;
}

View File

@ -6,6 +6,10 @@
background-color: var(--background-color); background-color: var(--background-color);
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
div {
display: flex;
}
} }
.HomePage_Top { .HomePage_Top {

View File

@ -2,12 +2,12 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--legendary-color);
max-width: 100px; max-width: 100px;
max-height: 125px; max-height: 150px;
border-radius: 15px; border-radius: 15px;
height: 100%;
overflow: hidden; overflow: hidden;
} }
@ -20,15 +20,17 @@
} }
.Character_Label { .Character_Label {
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: var(--secondary-color); background-color: var(--secondary-color);
max-width: 100px; max-width: 100px;
height: 25px; height: 50px;
p { p {
color: var(--text-primary-color);
font-size: 18px; font-size: 18px;
text-align: center;
height: 100%;
} }
} }

View File

@ -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 (
<div className={"AvatarsPage"}>
<h1 className={"AvatarsPage_Title"}>Characters</h1>
<div className={"AvatarsPage_List"}>
{
listAvatars().map(avatar => (
avatar.id > 11000000 ? undefined :
<Character key={avatar.id} data={avatar} />
))
}
</div>
</div>
);
}
}
export default AvatarsPage;

View File

@ -16,6 +16,7 @@ class CommandsPage extends React.PureComponent {
{ {
listCommands().map(command => ( listCommands().map(command => (
<Card <Card
key={command.name[0]}
title={command.name[0]} title={command.name[0]}
alternate={command.name.length == 1 ? undefined : alternate={command.name.length == 1 ? undefined :
`(aka /${command.name.slice(1).join(", /")})`} `(aka /${command.name.slice(1).join(", /")})`}

View File

@ -19,7 +19,7 @@ class HomePage extends React.Component<any, any> {
<div className={"HomePage_Buttons"}> <div className={"HomePage_Buttons"}>
<HomeButton name={"Commands"} anchor={"Commands"} /> <HomeButton name={"Commands"} anchor={"Commands"} />
<HomeButton name={"Characters"} anchor={"Home"} /> <HomeButton name={"Characters"} anchor={"Avatars"} />
<HomeButton name={"Items"} anchor={"Home"} /> <HomeButton name={"Items"} anchor={"Home"} />
<HomeButton name={"Entities"} anchor={"Home"} /> <HomeButton name={"Entities"} anchor={"Home"} />
<HomeButton name={"Scenes"} anchor={"Home"} /> <HomeButton name={"Scenes"} anchor={"Home"} />

View File

@ -2,11 +2,12 @@ import React from "react";
import HomePage from "@pages/HomePage"; import HomePage from "@pages/HomePage";
import CommandsPage from "@pages/CommandsPage"; import CommandsPage from "@pages/CommandsPage";
import AvatarsPage from "@pages/AvatarsPage";
import type { Page } from "@backend/types"; import type { Page } from "@backend/types";
import { addNavListener, removeNavListener } from "@backend/events";
import "@css/views/Content.scss"; import "@css/views/Content.scss";
import { addNavListener, removeNavListener } from "@backend/events";
interface IProps { interface IProps {
initial?: Page | null; initial?: Page | null;
@ -48,6 +49,7 @@ class Content extends React.Component<IProps, IState> {
default: return undefined; default: return undefined;
case "Home": return <HomePage />; case "Home": return <HomePage />;
case "Commands": return <CommandsPage />; case "Commands": return <CommandsPage />;
case "Avatars": return <AvatarsPage />;
} }
} }
} }

View File

@ -23,7 +23,7 @@ class SideBar extends React.Component<any, any> {
<div className={"SideBar_Buttons"}> <div className={"SideBar_Buttons"}>
<SideBarButton name={"Commands"} anchor={"Commands"} /> <SideBarButton name={"Commands"} anchor={"Commands"} />
<SideBarButton name={"Characters"} anchor={"Home"} /> <SideBarButton name={"Characters"} anchor={"Avatars"} />
<SideBarButton name={"Items"} anchor={"Home"} /> <SideBarButton name={"Items"} anchor={"Home"} />
<SideBarButton name={"Entities"} anchor={"Home"} /> <SideBarButton name={"Entities"} anchor={"Home"} />
<SideBarButton name={"Scenes"} anchor={"Home"} /> <SideBarButton name={"Scenes"} anchor={"Home"} />

View File

@ -1,5 +1,8 @@
import React from "react"; import React from "react";
import type { Avatar } from "@backend/types";
import { colorFor } from "@app/utils";
import "@css/widgets/Character.scss"; import "@css/widgets/Character.scss";
// Image base URL: https://paimon.moe/images/characters/(name).png // Image base URL: https://paimon.moe/images/characters/(name).png
@ -9,13 +12,30 @@ import "@css/widgets/Character.scss";
* Example: Hu Tao -> hu_tao * Example: Hu Tao -> hu_tao
* *
* @param name The character's name. * @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(" ", "_"); 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 { interface IProps {
name: string; // paimon.moe reference name. data: Avatar;
} }
class Character extends React.PureComponent<IProps> { class Character extends React.PureComponent<IProps> {
@ -24,16 +44,26 @@ class Character extends React.PureComponent<IProps> {
} }
render() { 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 ( return (
<div className={"Character"}> <div
className={"Character"}
style={{ backgroundColor: `var(${qualityColor})` }}
>
<img <img
className={"Character_Icon"} className={"Character_Icon"}
alt={this.props.name} alt={name}
src={`https://paimon.moe/images/characters/${formatName(this.props.name)}.png`} src={`https://paimon.moe/images/characters/${formatName(name, id)}.png`}
/> />
<div className={"Character_Label"}> <div className={"Character_Label"}>
<p>{this.props.name}</p> <p>{nameSwitch[id] ?? name}</p>
</div> </div>
</div> </div>
); );

17
src/handbook/src/utils.ts Normal file
View File

@ -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";
}
}