mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-27 02:43:15 +00:00
Implement avatars/characters page
Handle edge-cases for avatar image rendering
This commit is contained in:
parent
1f27f83616
commit
757d682cd6
@ -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.
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type Page = "Home" | "Commands";
|
||||
export type Page = "Home" | "Commands" | "Avatars";
|
||||
|
||||
export type Command = {
|
||||
name: string[];
|
||||
|
@ -38,10 +38,6 @@ body {
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
31
src/handbook/src/css/pages/AvatarsPage.scss
Normal file
31
src/handbook/src/css/pages/AvatarsPage.scss
Normal 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;
|
||||
}
|
@ -6,6 +6,10 @@
|
||||
background-color: var(--background-color);
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.HomePage_Top {
|
||||
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
28
src/handbook/src/ui/pages/AvatarsPage.tsx
Normal file
28
src/handbook/src/ui/pages/AvatarsPage.tsx
Normal 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;
|
@ -16,6 +16,7 @@ class CommandsPage extends React.PureComponent {
|
||||
{
|
||||
listCommands().map(command => (
|
||||
<Card
|
||||
key={command.name[0]}
|
||||
title={command.name[0]}
|
||||
alternate={command.name.length == 1 ? undefined :
|
||||
`(aka /${command.name.slice(1).join(", /")})`}
|
||||
|
@ -19,7 +19,7 @@ class HomePage extends React.Component<any, any> {
|
||||
|
||||
<div className={"HomePage_Buttons"}>
|
||||
<HomeButton name={"Commands"} anchor={"Commands"} />
|
||||
<HomeButton name={"Characters"} anchor={"Home"} />
|
||||
<HomeButton name={"Characters"} anchor={"Avatars"} />
|
||||
<HomeButton name={"Items"} anchor={"Home"} />
|
||||
<HomeButton name={"Entities"} anchor={"Home"} />
|
||||
<HomeButton name={"Scenes"} anchor={"Home"} />
|
||||
|
@ -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<IProps, IState> {
|
||||
default: return undefined;
|
||||
case "Home": return <HomePage />;
|
||||
case "Commands": return <CommandsPage />;
|
||||
case "Avatars": return <AvatarsPage />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class SideBar extends React.Component<any, any> {
|
||||
|
||||
<div className={"SideBar_Buttons"}>
|
||||
<SideBarButton name={"Commands"} anchor={"Commands"} />
|
||||
<SideBarButton name={"Characters"} anchor={"Home"} />
|
||||
<SideBarButton name={"Characters"} anchor={"Avatars"} />
|
||||
<SideBarButton name={"Items"} anchor={"Home"} />
|
||||
<SideBarButton name={"Entities"} anchor={"Home"} />
|
||||
<SideBarButton name={"Scenes"} anchor={"Home"} />
|
||||
|
@ -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<IProps> {
|
||||
@ -24,16 +44,26 @@ class Character extends React.PureComponent<IProps> {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={"Character"}>
|
||||
<div
|
||||
className={"Character"}
|
||||
style={{ backgroundColor: `var(${qualityColor})` }}
|
||||
>
|
||||
<img
|
||||
className={"Character_Icon"}
|
||||
alt={this.props.name}
|
||||
src={`https://paimon.moe/images/characters/${formatName(this.props.name)}.png`}
|
||||
alt={name}
|
||||
src={`https://paimon.moe/images/characters/${formatName(name, id)}.png`}
|
||||
/>
|
||||
|
||||
<div className={"Character_Label"}>
|
||||
<p>{this.props.name}</p>
|
||||
<p>{nameSwitch[id] ?? name}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
17
src/handbook/src/utils.ts
Normal file
17
src/handbook/src/utils.ts
Normal 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";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user