Implement navigation and the page system

This commit is contained in:
KingRainbow44 2023-04-04 22:30:49 -04:00
parent 30c8d01c9e
commit e0b1f275dd
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
9 changed files with 357 additions and 198 deletions

View File

@ -13,12 +13,15 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"events": "^3.3.0"
},
"devDependencies": {
"typescript": "^4.9.3",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/events": "^3.0.0",
"vite": "^4.2.0",
"vite-plugin-svgr": "^2.4.0",

View File

@ -0,0 +1,92 @@
import { EventEmitter } from "events";
import { Page } from "@backend/types";
const emitter = new EventEmitter();
const navigation = new EventEmitter();
let navStack: Page[] = [];
let currentPage: number | null = -1;
/**
* The initial setup function for this file.
*/
export function setup(): void {
// Check if the window's href is a page.
const page = window.location.href.split("/").pop();
if (page == undefined) return;
// Convert the page to a Page type.
const pageName = page.charAt(0).toUpperCase() + page.slice(1);
const pageType = pageName as Page;
// Navigate to the page.
navigate(pageType, false);
}
/**
* Adds a navigation listener.
*
* @param listener The listener to add.
*/
export function addNavListener(listener: (page: Page) => void) {
navigation.on("navigate", listener);
}
/**
* Removes a navigation listener.
*
* @param listener The listener to remove.
*/
export function removeNavListener(listener: (page: Page) => void) {
navigation.off("navigate", listener);
}
/**
* Navigates to a page.
* Returns the last page.
*
* @param page The page to navigate to.
* @param update Whether to update the state or not.
*/
export function navigate(page: Page, update: boolean = true): Page | null {
// Navigate to the new page.
const lastPage = currentPage;
navigation.emit("navigate", page);
if (update) {
// Set the current page.
navStack.push(page);
currentPage = navStack.length - 1;
// Add the page to the window history.
window.history.pushState(page, page, "/" + page.toLowerCase());
}
return lastPage ? navStack[lastPage] : null;
}
/**
* Goes back or forward in the navigation stack.
*
* @param forward Whether to go forward or not.
*/
export function go(forward: boolean): void {
if (currentPage == undefined) return;
// Get the new page.
const newPage = forward ? currentPage + 1 : currentPage - 1;
if (newPage < 0 || newPage >= navStack.length) return;
// Navigate to the new page.
currentPage = newPage;
navigation.emit("navigate", navStack[newPage]);
// Update the window history.
window.history.pushState(navStack[newPage], navStack[newPage], "/" + navStack[newPage].toLowerCase());
}
// This is the global event system.
export default emitter;
// @ts-ignore
window["emitter"] = emitter;
// @ts-ignore
window["navigate"] = navigate;

View File

@ -0,0 +1 @@
export type Page = "Home";

View File

@ -0,0 +1,147 @@
.HomePage {
display: flex;
height: 100%;
width: 100%;
background-color: var(--background-color);
flex-direction: column;
justify-content: space-between;
}
.HomePage_Top {
display: flex;
width: 100%;
height: 80%;
flex-direction: column;
align-items: center;
gap: 24px;
}
.HomePage_Title {
margin-top: 31px;
margin-bottom: 15px;
}
.HomePage_Buttons {
width: 100%;
height: 40%;
max-width: 1376px;
max-height: 256px;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
}
.HomePage_Bottom {
display: flex;
height: 50%;
max-height: 125px;
flex-direction: row;
justify-content: space-between;
}
.HomePage_Box {
display: flex;
background-color: var(--secondary-color);
}
.HomePage_Disclaimer {
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: var(--secondary-color);
width: 50%;
height: 100%;
max-width: 630px;
max-height: 93px;
margin: 0 0 0 60px;
border-radius: 10px;
box-sizing: border-box;
padding: 11px;
:nth-child(1) {
font-size: 24px;
max-height: 30px;
display: flex;
flex-direction: column;
}
p {
font-size: 18px;
max-height: 40px;
}
}
.HomePage_Discord {
max-height: 40px;
max-width: 150px;
gap: 8px;
align-self: center;
align-items: center;
svg {
width: 100%;
height: 100%;
max-width: 44px;
max-height: 30px;
}
p {
font-size: 16px;
}
}
.HomePage_Text {
display: flex;
flex-direction: column;
background-color: var(--secondary-color);
max-width: 300px;
max-height: 80px;
margin: 13px 60px 0 0;
border-radius: 10px;
box-sizing: border-box;
padding: 11px;
}
.HomePage_Credits {
display: flex;
flex-direction: row;
gap: 5px;
max-height: 18px;
padding-bottom: 5px;
:nth-child(1) {
font-size: 18px;
font-weight: bold;
}
:nth-child(2) {
font-size: 10px;
align-self: center;
}
}
.HomePage_Links {
display: flex;
flex-wrap: wrap;
a {
color: var(--text-primary-color);
text-decoration: none;
padding-right: 10px;
}
}

View File

@ -1,147 +1,4 @@
.Content {
display: flex;
width: 100%;
height: 100%;
width: 100%;
background-color: var(--background-color);
flex-direction: column;
justify-content: space-between;
}
.Content_Top {
display: flex;
width: 100%;
height: 80%;
flex-direction: column;
align-items: center;
gap: 24px;
}
.Content_Title {
margin-top: 31px;
margin-bottom: 15px;
}
.Content_Buttons {
width: 100%;
height: 40%;
max-width: 1376px;
max-height: 256px;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
}
.Content_Bottom {
display: flex;
height: 50%;
max-height: 125px;
flex-direction: row;
justify-content: space-between;
}
.Content_Box {
display: flex;
background-color: var(--secondary-color);
}
.Content_Disclaimer {
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: var(--secondary-color);
width: 50%;
height: 100%;
max-width: 630px;
max-height: 93px;
margin: 0 0 0 60px;
border-radius: 10px;
box-sizing: border-box;
padding: 11px;
:nth-child(1) {
font-size: 24px;
max-height: 30px;
display: flex;
flex-direction: column;
}
p {
font-size: 18px;
max-height: 40px;
}
}
.Content_Discord {
max-height: 40px;
max-width: 150px;
gap: 8px;
align-self: center;
align-items: center;
svg {
width: 100%;
height: 100%;
max-width: 44px;
max-height: 30px;
}
p {
font-size: 16px;
}
}
.Content_Text {
display: flex;
flex-direction: column;
background-color: var(--secondary-color);
max-width: 300px;
max-height: 80px;
margin: 13px 60px 0 0;
border-radius: 10px;
box-sizing: border-box;
padding: 11px;
}
.Content_Credits {
display: flex;
flex-direction: row;
gap: 5px;
max-height: 18px;
padding-bottom: 5px;
:nth-child(1) {
font-size: 18px;
font-weight: bold;
}
:nth-child(2) {
font-size: 10px;
align-self: center;
}
}
.Content_Links {
display: flex;
flex-wrap: wrap;
a {
color: var(--text-primary-color);
text-decoration: none;
padding-right: 10px;
}
}

View File

@ -1,8 +1,13 @@
import React from "react";
import { createRoot } from "react-dom/client";
import * as events from "@backend/events";
import App from "@components/App";
// Call initial setup functions.
events.setup();
// Render the application.
createRoot(document.getElementById(
"root") as HTMLElement).render(

View File

@ -0,0 +1,66 @@
import React from "react";
import HomeButton from "@app/ui/widgets/HomeButton";
import { ReactComponent as DiscordLogo } from "@icons/discord.svg";
import "@css/pages/HomePage.scss";
class HomePage extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
return (
<div className={"HomePage"}>
<div className={"HomePage_Top"}>
<h1 className={"HomePage_Title"}>Welcome back, Traveler~</h1>
<div className={"HomePage_Buttons"}>
<HomeButton name={"Commands"} anchor={"commands"} />
<HomeButton name={"Characters"} anchor={"avatars"} />
<HomeButton name={"Items"} anchor={"items"} />
<HomeButton name={"Entities"} anchor={"monsters"} />
<HomeButton name={"Scenes"} anchor={"scenes"} />
</div>
<div className={"HomePage_Buttons"}>
<HomeButton name={"Quests"} anchor={"quests"} />
<HomeButton name={"Achievements"} anchor={"achievements"} />
</div>
</div>
<div className={"HomePage_Bottom"}>
<div className={"HomePage_Box HomePage_Disclaimer"}>
<div>
<p>This tool is not affiliated with HoYoverse.</p>
<p>Genshin Impact, game HomePage and materials are</p>
<p>trademarks and copyrights of HoYoverse.</p>
</div>
<div className={"HomePage_Discord"}>
<DiscordLogo />
<p>Join the Community!</p>
</div>
</div>
<div className={"HomePage_Text"}>
<div className={"HomePage_Credits"}>
<p>Credits</p>
<p>(hover to see info)</p>
</div>
<div className={"HomePage_Links"}>
<a href={"https://paimon.moe"}>paimon.moe</a>
<a href={"https://gitlab.com/Dimbreath/AnimeGameData"}>Anime Game Data</a>
<a href={"https://genshin-impact.fandom.com"}>Genshin Impact Wiki</a>
</div>
</div>
</div>
</div>
);
}
}
export default HomePage;

View File

@ -1,65 +1,52 @@
import React from "react";
import HomeButton from "@app/ui/widgets/HomeButton";
import HomePage from "@pages/HomePage";
import { ReactComponent as DiscordLogo } from "@icons/discord.svg";
import type { Page } from "@backend/types";
import "@css/views/Content.scss";
import { addNavListener, removeNavListener } from "@backend/events";
class Content extends React.Component<any, any> {
constructor(props: any) {
interface IProps {
initial?: Page;
}
interface IState {
current: Page;
}
class Content extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
current: props.initial ?? "Home"
};
}
/**
* Navigates to the specified page.
*
* @param page The page to navigate to.
* @private
*/
private navigate(page: Page): void {
this.setState({ current: page });
}
componentDidMount() {
addNavListener(this.navigate.bind(this));
}
componentWillUnmount() {
removeNavListener(this.navigate.bind(this));
}
render() {
return (
<div className={"Content"}>
<div className={"Content_Top"}>
<h1 className={"Content_Title"}>Welcome back, Traveler~</h1>
<div className={"Content_Buttons"}>
<HomeButton name={"Commands"} anchor={"commands"} />
<HomeButton name={"Characters"} anchor={"avatars"} />
<HomeButton name={"Items"} anchor={"items"} />
<HomeButton name={"Entities"} anchor={"monsters"} />
<HomeButton name={"Scenes"} anchor={"scenes"} />
</div>
<div className={"Content_Buttons"}>
<HomeButton name={"Quests"} anchor={"quests"} />
<HomeButton name={"Achievements"} anchor={"achievements"} />
</div>
</div>
<div className={"Content_Bottom"}>
<div className={"Content_Box Content_Disclaimer"}>
<div>
<p>This tool is not affiliated with HoYoverse.</p>
<p>Genshin Impact, game content and materials are</p>
<p>trademarks and copyrights of HoYoverse.</p>
</div>
<div className={"Content_Discord"}>
<DiscordLogo />
<p>Join the Community!</p>
</div>
</div>
<div className={"Content_Text"}>
<div className={"Content_Credits"}>
<p>Credits</p>
<p>(hover to see info)</p>
</div>
<div className={"Content_Links"}>
<a href={"https://paimon.moe"}>paimon.moe</a>
<a href={"https://gitlab.com/Dimbreath/AnimeGameData"}>Anime Game Data</a>
<a href={"https://genshin-impact.fandom.com"}>Genshin Impact Wiki</a>
</div>
</div>
</div>
</div>
);
switch (this.state.current) {
default: return undefined;
case "Home": return <HomePage />;
}
}
}

View File

@ -23,7 +23,8 @@
"@css/*": ["src/css/*"],
"@components/*": ["src/ui/*"],
"@icons/*": ["src/icons/*"],
"@views/*": ["src/ui/views/*"]
"@views/*": ["src/ui/views/*"],
"@pages/*": ["src/ui/pages/*"]
}
},
"include": ["src"],