mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-24 04:48:05 +00:00
misc: Remove the handbook from the main repository
sorry guys, i just wanted to do something cool...
This commit is contained in:
parent
ab0ec0a0e0
commit
70bb5ca5b5
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -36,13 +36,8 @@ jobs:
|
|||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle', 'gradle.properties', '**/*.accesswidener') }}
|
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle', 'gradle.properties', '**/*.accesswidener') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Download Handbook
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/handbook.html
|
|
||||||
target: src/main/resources/html/
|
|
||||||
- name: Run Gradle
|
- name: Run Gradle
|
||||||
run: ./gradlew -PskipHandbook=1 && ./gradlew jar -PskipHandbook=1
|
run: ./gradlew && ./gradlew jar
|
||||||
- name: Upload build
|
- name: Upload build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/check_code.yml
vendored
2
.github/workflows/check_code.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Format Code
|
- name: Format Code
|
||||||
run: ./gradlew -PskipHandbook=1 && ./gradlew spotlessApply -PskipHandbook=1
|
run: ./gradlew && ./gradlew spotlessApply
|
||||||
|
|
||||||
- run: git config --global user.name "github-actions"
|
- run: git config --global user.name "github-actions"
|
||||||
- run: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
- run: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
126
.github/workflows/handbook.yml
vendored
126
.github/workflows/handbook.yml
vendored
@ -1,126 +0,0 @@
|
|||||||
name: "Handbook"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch: ~
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- "src/handbook/**.tsx"
|
|
||||||
branches:
|
|
||||||
- "development"
|
|
||||||
- "unstable"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "src/handbook/**.tsx"
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Lint-Code:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Extract branch name
|
|
||||||
shell: bash
|
|
||||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
|
||||||
id: extract_branch
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '17'
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.npm
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: src/handbook
|
|
||||||
run: npm install --force
|
|
||||||
- name: Run linter
|
|
||||||
working-directory: src/handbook
|
|
||||||
run: npm run lint
|
|
||||||
|
|
||||||
- run: git config --global user.name "github-actions"
|
|
||||||
- run: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
- run: git stash
|
|
||||||
- run: git checkout ${{ steps.extract_branch.outputs.branch }} && git pull
|
|
||||||
- run: git stash pop || true
|
|
||||||
- name: Commit changes
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
run: git add -u && git commit -m "Lint Code [skip actions]" || true
|
|
||||||
- name: Push changes
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
run: git push --set-upstream --force origin ${{ steps.extract_branch.outputs.branch }}
|
|
||||||
|
|
||||||
Build-Handbook:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '17'
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.npm
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: src/handbook
|
|
||||||
run: npm install --force
|
|
||||||
|
|
||||||
- name: Download Avatar Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/avatars.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Command Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/commands.json
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Entity Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/entities.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Item Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/items.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Scene Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/scenes.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Quest Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/quests.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
- name: Download Main Quest Data
|
|
||||||
uses: suisei-cn/actions-download-file@v1.4.0
|
|
||||||
with:
|
|
||||||
url: https://api.grasscutter.io/static/mainquests.csv
|
|
||||||
target: src/handbook/data/
|
|
||||||
|
|
||||||
- name: Build handbook
|
|
||||||
working-directory: src/handbook
|
|
||||||
run: npm run build
|
|
||||||
- name: Upload build
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: Handbook
|
|
||||||
path: src/handbook/dist/*.html
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -67,7 +67,6 @@ tmp/
|
|||||||
!entrypoint.sh
|
!entrypoint.sh
|
||||||
|
|
||||||
GM Handbook*.txt
|
GM Handbook*.txt
|
||||||
handbook.html
|
|
||||||
|
|
||||||
config.json
|
config.json
|
||||||
mitmdump.exe
|
mitmdump.exe
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
|||||||
[submodule "docs/wiki"]
|
[submodule "docs/wiki"]
|
||||||
path = docs/wiki
|
path = docs/wiki
|
||||||
url = https://github.com/Grasscutters/Grasscutter.wiki.git
|
url = https://github.com/Grasscutters/Grasscutter.wiki.git
|
||||||
[submodule "src/handbook/data/assets"]
|
|
||||||
path = src/handbook/data/assets
|
|
||||||
url = https://github.com/genshitters/gm-handbook-assets.git
|
|
||||||
|
21
README.md
21
README.md
@ -50,7 +50,6 @@ Grasscutter uses Gradle to handle dependencies & building.
|
|||||||
|
|
||||||
- [Java Development Kit 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or higher
|
- [Java Development Kit 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or higher
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
- [NodeJS](https://nodejs.org/en/download) (Optional, for building the handbook)
|
|
||||||
|
|
||||||
##### Clone
|
##### Clone
|
||||||
|
|
||||||
@ -61,8 +60,6 @@ cd Grasscutter
|
|||||||
|
|
||||||
##### Compile
|
##### Compile
|
||||||
|
|
||||||
**Note**: Handbook generation may fail on some systems. To disable the handbook generation, append `-PskipHandbook=1` to the `gradlew jar` command.
|
|
||||||
|
|
||||||
Windows:
|
Windows:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -77,24 +74,6 @@ chmod +x gradlew
|
|||||||
./gradlew jar
|
./gradlew jar
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Compiling the Handbook (Manually)
|
|
||||||
|
|
||||||
With Gradle:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
./gradlew generateHandbook
|
|
||||||
```
|
|
||||||
|
|
||||||
With NPM:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cd src/handbook
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can find the output jar in the root of the project folder.
|
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
For a list of common issues and solutions and to ask for help, please join [our Discord server](https://discord.gg/T5vZU6UyeG) and go to the support channel.
|
For a list of common issues and solutions and to ask for help, please join [our Discord server](https://discord.gg/T5vZU6UyeG) and go to the support channel.
|
||||||
|
77
build.gradle
77
build.gradle
@ -1,4 +1,3 @@
|
|||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
|
||||||
import org.gradle.plugins.ide.eclipse.model.SourceFolder
|
import org.gradle.plugins.ide.eclipse.model.SourceFolder
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -311,7 +310,6 @@ javadoc {
|
|||||||
// Add this to avoid warning caused by lack of comments in proto generated java files
|
// Add this to avoid warning caused by lack of comments in proto generated java files
|
||||||
options.addStringOption('Xdoclint:none', '-quiet')
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
exclude '**/*.md'
|
exclude '**/*.md'
|
||||||
exclude 'src/handbook/**/*.*'
|
|
||||||
exclude 'src/generated/**/*.*'
|
exclude 'src/generated/**/*.*'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,81 +330,6 @@ public final class BuildConfig {
|
|||||||
}"""
|
}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('generateHandbook') {
|
|
||||||
if (project.hasProperty('skipHandbook')) {
|
|
||||||
println('Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the NPM command.
|
|
||||||
var npm = 'npm'
|
|
||||||
if (Os.isFamily(Os.FAMILY_WINDOWS))
|
|
||||||
npm = 'npm.cmd'
|
|
||||||
|
|
||||||
def npmVersion = {
|
|
||||||
try {
|
|
||||||
return "${npm} --version".execute()
|
|
||||||
} catch (ignored) {
|
|
||||||
ignored.printStackTrace()
|
|
||||||
return 'NPM_NOT_FOUND'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Node is installed.
|
|
||||||
if (npmVersion() == 'NPM_NOT_FOUND') {
|
|
||||||
println('NPM is not installed. Skipping handbook generation.')
|
|
||||||
} else {
|
|
||||||
// Check if the handbook resources are present.
|
|
||||||
if (!file('src/handbook/data/commands.json').exists()) {
|
|
||||||
println('Command data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/avatars.csv').exists()) {
|
|
||||||
println('Avatar data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/entities.csv').exists()) {
|
|
||||||
println('Entity data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/items.csv').exists()) {
|
|
||||||
println('Item data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/mainquests.csv').exists()) {
|
|
||||||
println('Main quest data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/quests.csv').exists()) {
|
|
||||||
println('Quest data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!file('src/handbook/data/scenes.csv').exists()) {
|
|
||||||
println('Scene data was not found. Skipping handbook generation.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install dependencies before building.
|
|
||||||
exec {
|
|
||||||
workingDir 'src/handbook'
|
|
||||||
commandLine npm, 'install'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the handbook.
|
|
||||||
exec {
|
|
||||||
workingDir 'src/handbook'
|
|
||||||
commandLine npm, 'run', 'build'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the handbook from /dist to /src/main/resources.
|
|
||||||
copy {
|
|
||||||
from 'src/handbook/dist/index.html'
|
|
||||||
into 'src/main/resources/html'
|
|
||||||
rename 'index.html', 'handbook.html'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
dependsOn 'generateProto'
|
dependsOn 'generateProto'
|
||||||
}
|
}
|
||||||
|
27
src/handbook/.gitignore
vendored
27
src/handbook/.gitignore
vendored
@ -1,27 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
# Handbook data
|
|
||||||
data/
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"arrowParens": "always",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"jsxBracketSameLine": false,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": false,
|
|
||||||
"tabWidth": 4,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"useTabs": false
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import tailwind from "tailwindcss";
|
|
||||||
import autoprefixer from "autoprefixer";
|
|
||||||
import cssnanoPlugin from "cssnano";
|
|
||||||
|
|
||||||
import tailwindConfig from "./tailwind.config.js";
|
|
||||||
const mode = process.env.NODE_ENV;
|
|
||||||
const dev = mode === "development";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
plugins: (() => {
|
|
||||||
let plugins = [
|
|
||||||
// Some plugins, like TailwindCSS/Nesting, need to run before Tailwind.
|
|
||||||
tailwind(tailwindConfig),
|
|
||||||
|
|
||||||
// But others, like autoprefixer, need to run after.
|
|
||||||
autoprefixer()
|
|
||||||
];
|
|
||||||
|
|
||||||
!dev && cssnanoPlugin({
|
|
||||||
preset: "default"
|
|
||||||
});
|
|
||||||
|
|
||||||
return plugins;
|
|
||||||
})()
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
export default {
|
|
||||||
content: ["./src/**/*.{html,js,tsx,ts}"],
|
|
||||||
mode: "jit",
|
|
||||||
theme: {
|
|
||||||
extend: {}
|
|
||||||
},
|
|
||||||
darkMode: "class",
|
|
||||||
plugins: []
|
|
||||||
};
|
|
@ -1,58 +0,0 @@
|
|||||||
# Handbook Data
|
|
||||||
Use Grasscutter's dumpers to generate the data to put here.
|
|
||||||
|
|
||||||
# Generating Data
|
|
||||||
|
|
||||||
When you have Grasscutter set up, you can use the following commands to generate the data:
|
|
||||||
- Commands - `grasscutter.jar -dump=commands,en-us`
|
|
||||||
- Items - `grasscutter.jar -dump=items,EN`
|
|
||||||
- Avatars - `grasscutter.jar -dump=avatars,EN`
|
|
||||||
- Quests - `grasscutter.jar -dump=quests,EN`
|
|
||||||
- Entities - `grasscutter.jar -dump=entities,en-us`
|
|
||||||
- Areas - `grasscutter.jar -dump=areas,EN`
|
|
||||||
- Scenes - `grasscutter.jar -dump=scenes,en-us`
|
|
||||||
|
|
||||||
Grasscutter being "set up" means:
|
|
||||||
- A Java runtime is installed
|
|
||||||
- Resources are provided in the working directory
|
|
||||||
|
|
||||||
## Language Locales
|
|
||||||
|
|
||||||
You can replace `en-us` or `EN` using the language locale which matches the format.
|
|
||||||
|
|
||||||
| Grasscutter Language Locale | Handbook Language Locale |
|
|
||||||
|-----------------------------|--------------------------|
|
|
||||||
| en-us | EN |
|
|
||||||
|
|
||||||
|
|
||||||
## Files Required
|
|
||||||
- `mainquests.csv`
|
|
||||||
- `commands.json`
|
|
||||||
- `entities.csv`
|
|
||||||
- `avatars.csv`
|
|
||||||
- `scenes.csv`
|
|
||||||
- `quests.csv`
|
|
||||||
- `items.csv`
|
|
||||||
|
|
||||||
# Item Icon Notes
|
|
||||||
- Artifacts: `https://bbs.hoyolab.com/hoyowiki/picture/reliquary/(name)/(piece)_icon.png`
|
|
||||||
- Alternate source: `https://api.ambr.top/assets/UI/reliquary/UI_RelicIcon_(set)_(piece).png`
|
|
||||||
- `xxxx4` - `flower_of_life`
|
|
||||||
- `xxxx5` - `sands_of_eon`
|
|
||||||
- `xxxx3` - `circlet_of_logos`/`plume_of_death`
|
|
||||||
- Use `circlet_of_logos` with a complete set
|
|
||||||
- Use `plume_of_death` with part of a set.
|
|
||||||
- `xxxx2` - `plume_of_death`
|
|
||||||
- `xxxx1` - `goblet_of_eonothem`
|
|
||||||
- Miscellaneous Items: `https://bbs.hoyolab.com/hoyowiki/picture/object/(name)_icon.png`
|
|
||||||
- Includes: materials, quest items, food, etc.
|
|
||||||
- Alternate source: `https://api.ambr.top/assets/UI/UI_ItemIcon_(id).png`
|
|
||||||
- Avatars/Avatar Items: `https://bbs.hoyolab.com/hoyowiki/picture/character/(name)_icon.png`
|
|
||||||
- Avatar Items are between ranges `1001` and `1099`.
|
|
||||||
- Weapons: `https://api.ambr.top/assets/UI/UI_EquipIcon_(type)_(name).png`
|
|
||||||
- Furniture: `https://api.ambr.top/assets/UI/furniture/UI_Homeworld_(location)_(name).png`
|
|
||||||
- Monsters: `https://api.ambr.top/assets/UI/monster/UI_MonsterIcon_(type)_(variant).png`
|
|
||||||
|
|
||||||
# Credits
|
|
||||||
- [`...List.json` files](https://raw.githubusercontent.com/Dituon/grasscutter-command-helper/main/data/en-US) - Grasscutter Command Helper
|
|
||||||
- [Internal Asset API](https://ambr.top) - Project Amber
|
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 1b9f8b2c0d60a9c06a6eb6ad5fc21c55afd926fd
|
|
@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>GM Handbook</title>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window["hide"] = ["quests", "achievements"];
|
|
||||||
window["details"] = {
|
|
||||||
address: "{{DETAILS_ADDRESS}}",
|
|
||||||
port: "{{DETAILS_PORT}}",
|
|
||||||
disable: "{{DETAILS_DISABLE}}"
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
6236
src/handbook/package-lock.json
generated
6236
src/handbook/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "handbook",
|
|
||||||
"description": "The ultimate anime game handbook!",
|
|
||||||
"version": "0.1.0",
|
|
||||||
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
|
|
||||||
"postinstall": "npx patch-package",
|
|
||||||
"lint": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\""
|
|
||||||
},
|
|
||||||
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-icons": "^4.8.0",
|
|
||||||
"react-d3-tree": "^3.6.1",
|
|
||||||
"react-collapsible": "^2.10.0",
|
|
||||||
"react-virtualized": "^9.22.3",
|
|
||||||
|
|
||||||
"events": "^3.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^4.9.3",
|
|
||||||
"@types/react": "^18.0.28",
|
|
||||||
"@types/react-dom": "^18.0.11",
|
|
||||||
"@types/react-virtualized": "^9.21.21",
|
|
||||||
"@types/events": "^3.0.0",
|
|
||||||
|
|
||||||
"vite": "^4.2.0",
|
|
||||||
"vite-plugin-svgr": "^2.4.0",
|
|
||||||
"vite-tsconfig-paths": "^4.0.7",
|
|
||||||
"vite-plugin-singlefile": "^0.13.5",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
|
||||||
"@rollup/plugin-dsv": "^3.0.2",
|
|
||||||
|
|
||||||
"sass": "^1.58.3",
|
|
||||||
"cssnano": "^5.1.15",
|
|
||||||
"tailwindcss": "^3.2.7",
|
|
||||||
"autoprefixer": "^10.4.13",
|
|
||||||
|
|
||||||
"postcss": "^8.4.21",
|
|
||||||
"postcss-load-config": "^4.0.1",
|
|
||||||
"postcss-font-magician": "^3.0.0",
|
|
||||||
|
|
||||||
"prettier": "^2.8.7",
|
|
||||||
"patch-package": "^6.5.1"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
diff --git a/node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js b/node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js
|
|
||||||
index d00f0f1..42456dc 100644
|
|
||||||
--- a/node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js
|
|
||||||
+++ b/node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js
|
|
||||||
@@ -71,4 +71,3 @@ export function unregisterScrollListener(component, element) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";
|
|
||||||
\ No newline at end of file
|
|
@ -1,55 +0,0 @@
|
|||||||
/**
|
|
||||||
* Validates a number.
|
|
||||||
*
|
|
||||||
* @param value The number to validate.
|
|
||||||
*/
|
|
||||||
function invalid(value: number): boolean {
|
|
||||||
return isNaN(value) || value < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a basic give command.
|
|
||||||
*/
|
|
||||||
function basicGive(item: number, amount = 1): string {
|
|
||||||
// Validate the numbers.
|
|
||||||
if (invalid(item) || invalid(amount)) return "Invalid arguments.";
|
|
||||||
|
|
||||||
return `/give ${item} x${amount}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a basic teleport command.
|
|
||||||
* This creates a relative teleport command.
|
|
||||||
*/
|
|
||||||
function teleport(scene: number): string {
|
|
||||||
// Validate the number.
|
|
||||||
if (invalid(scene)) return "Invalid arguments.";
|
|
||||||
|
|
||||||
return `/teleport ~ ~ ~ ${scene}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a basic spawn monster command.
|
|
||||||
*
|
|
||||||
* @param monster The monster's ID.
|
|
||||||
* @param amount The amount of the monster to spawn.
|
|
||||||
* @param level The level of the monster to spawn.
|
|
||||||
*/
|
|
||||||
function spawnMonster(monster: number, amount = 1, level = 1): string {
|
|
||||||
// Validate the numbers.
|
|
||||||
if (invalid(monster) || invalid(amount)) return "Invalid arguments.";
|
|
||||||
|
|
||||||
return `/spawn ${monster} x${amount} lv${level}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const give = {
|
|
||||||
basic: basicGive
|
|
||||||
};
|
|
||||||
|
|
||||||
export const spawn = {
|
|
||||||
monster: spawnMonster
|
|
||||||
};
|
|
||||||
|
|
||||||
export const action = {
|
|
||||||
teleport: teleport
|
|
||||||
};
|
|
@ -1,245 +0,0 @@
|
|||||||
import mainQuests from "@data/mainquests.csv";
|
|
||||||
import commands from "@data/commands.json";
|
|
||||||
import entities from "@data/entities.csv";
|
|
||||||
import avatars from "@data/avatars.csv";
|
|
||||||
import scenes from "@data/scenes.csv";
|
|
||||||
import quests from "@data/quests.csv";
|
|
||||||
import items from "@data/items.csv";
|
|
||||||
|
|
||||||
import type { RawNodeDatum } from "react-d3-tree";
|
|
||||||
|
|
||||||
import { Quality, ItemType, ItemCategory, SceneType } from "@backend/types";
|
|
||||||
import type { MainQuest, Command, Avatar, Item, Scene, Entity, Quest } from "@backend/types";
|
|
||||||
|
|
||||||
import { inRange } from "@app/utils";
|
|
||||||
|
|
||||||
type AvatarDump = { [key: number]: Avatar };
|
|
||||||
type CommandDump = { [key: string]: Command };
|
|
||||||
type TaggedItems = { [key: number]: Item[] };
|
|
||||||
type QuestDump = { [key: number]: Quest };
|
|
||||||
type MainQuestDump = { [key: number]: MainQuest };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see {@file src/handbook/data/README.md}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const sortedItems: TaggedItems = {
|
|
||||||
[ItemCategory.Constellation]: [], // Range: 1102 - 11xx
|
|
||||||
[ItemCategory.Avatar]: [], // Range: 1002 - 10xx
|
|
||||||
[ItemCategory.Weapon]: [],
|
|
||||||
[ItemCategory.Artifact]: [],
|
|
||||||
[ItemCategory.Furniture]: [],
|
|
||||||
[ItemCategory.Material]: [],
|
|
||||||
[ItemCategory.Miscellaneous]: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export let allMainQuests: MainQuestDump = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup function for this file.
|
|
||||||
* Sorts all items into their respective categories.
|
|
||||||
*/
|
|
||||||
export function setup(): void {
|
|
||||||
getItems().forEach((item) => {
|
|
||||||
switch (item.type) {
|
|
||||||
case ItemType.Weapon:
|
|
||||||
sortedItems[ItemCategory.Weapon].push(item);
|
|
||||||
break;
|
|
||||||
case ItemType.Material:
|
|
||||||
sortedItems[ItemCategory.Material].push(item);
|
|
||||||
break;
|
|
||||||
case ItemType.Furniture:
|
|
||||||
sortedItems[ItemCategory.Furniture].push(item);
|
|
||||||
break;
|
|
||||||
case ItemType.Reliquary:
|
|
||||||
sortedItems[ItemCategory.Artifact].push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort constellations.
|
|
||||||
if (inRange(item.id, 1102, 1199)) {
|
|
||||||
sortedItems[ItemCategory.Constellation].push(item);
|
|
||||||
}
|
|
||||||
// Sort avatars.
|
|
||||||
if (inRange(item.id, 1002, 1099)) {
|
|
||||||
sortedItems[ItemCategory.Avatar].push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
allMainQuests = getMainQuests();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all commands in the file.
|
|
||||||
*/
|
|
||||||
export function getCommands(): CommandDump {
|
|
||||||
return commands as CommandDump;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and lists all the commands in the file.
|
|
||||||
*/
|
|
||||||
export function listCommands(): Command[] {
|
|
||||||
return Object.values(getCommands()).sort((a, b) => a.name[0].localeCompare(b.name[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all entities in the file.
|
|
||||||
*/
|
|
||||||
export function getEntities(): Entity[] {
|
|
||||||
return entities
|
|
||||||
.map((entry) => {
|
|
||||||
const values = Object.values(entry) as string[];
|
|
||||||
const id = parseInt(values[0]);
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: values[1],
|
|
||||||
internal: values[2]
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((entity) => !entity.name.includes("Mechanicus"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all avatars in the file.
|
|
||||||
*/
|
|
||||||
export function getAvatars(): AvatarDump {
|
|
||||||
const map: AvatarDump = {};
|
|
||||||
avatars.forEach((avatar) => {
|
|
||||||
const values = Object.values(avatar) as [string, string, string];
|
|
||||||
const id = parseInt(values[0]);
|
|
||||||
map[id] = {
|
|
||||||
id,
|
|
||||||
name: values[1],
|
|
||||||
quality: values[2] as Quality
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and lists all the avatars in the file.
|
|
||||||
*/
|
|
||||||
export function listAvatars(): Avatar[] {
|
|
||||||
return Object.values(getAvatars()).sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all scenes in the file.
|
|
||||||
*/
|
|
||||||
export function getScenes(): Scene[] {
|
|
||||||
return scenes
|
|
||||||
.map((entry) => {
|
|
||||||
const values = Object.values(entry) as string[];
|
|
||||||
const id = parseInt(values[0]);
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
identifier: values[1],
|
|
||||||
type: values[2] as SceneType
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => a.id - b.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all items in the file.
|
|
||||||
*/
|
|
||||||
export function getItems(): Item[] {
|
|
||||||
return items.map((entry) => {
|
|
||||||
const values = Object.values(entry) as string[];
|
|
||||||
const id = parseInt(values[0]);
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: values[1],
|
|
||||||
type: values[3] as ItemType,
|
|
||||||
quality: values[2] as Quality,
|
|
||||||
icon: values[4]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all quests in the file.
|
|
||||||
*/
|
|
||||||
export function getQuests(): QuestDump {
|
|
||||||
const map: QuestDump = {};
|
|
||||||
quests.forEach((quest: Quest) => {
|
|
||||||
quest.description = quest.description.replaceAll("\\", ",");
|
|
||||||
map[quest.id] = quest;
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and lists all the quests in the file.
|
|
||||||
*/
|
|
||||||
export function listQuests(): Quest[] {
|
|
||||||
return Object.values(getQuests()).sort((a, b) => a.id - b.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and casts all quests in the file.
|
|
||||||
*/
|
|
||||||
export function getMainQuests(): MainQuestDump {
|
|
||||||
const map: MainQuestDump = {};
|
|
||||||
mainQuests.forEach((quest: MainQuest) => {
|
|
||||||
quest.title = quest.title.replaceAll("\\", ",");
|
|
||||||
map[quest.id] = quest;
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and lists all the quests in the file.
|
|
||||||
*/
|
|
||||||
export function listMainQuests(): MainQuestDump[] {
|
|
||||||
return Object.values(allMainQuests).sort((a, b) => a.id - b.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a quest by its ID.
|
|
||||||
*
|
|
||||||
* @param quest The quest ID.
|
|
||||||
*/
|
|
||||||
export function getMainQuestFor(quest: Quest): MainQuest {
|
|
||||||
return allMainQuests[quest.mainId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all quests for a main quest.
|
|
||||||
*
|
|
||||||
* @param mainQuest The main quest to fetch quests for.
|
|
||||||
*/
|
|
||||||
export function listSubQuestsFor(mainQuest: MainQuest): Quest[] {
|
|
||||||
return listQuests().filter((quest) => quest.mainId == mainQuest.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tree conversion methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a quest to a tree.
|
|
||||||
*
|
|
||||||
* @param mainQuest The main quest to convert.
|
|
||||||
*/
|
|
||||||
export function questToTree(mainQuest: MainQuest): RawNodeDatum {
|
|
||||||
return {
|
|
||||||
name: mainQuest.title,
|
|
||||||
attributes: {
|
|
||||||
id: mainQuest.id
|
|
||||||
},
|
|
||||||
children: listSubQuestsFor(mainQuest).map((quest) => {
|
|
||||||
return {
|
|
||||||
name: quest.id.toString(),
|
|
||||||
attributes: {
|
|
||||||
description: quest.description
|
|
||||||
},
|
|
||||||
children: []
|
|
||||||
} as RawNodeDatum;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
import { EventEmitter } from "events";
|
|
||||||
|
|
||||||
import type { Page } from "@backend/types";
|
|
||||||
import { isPage } from "@backend/types";
|
|
||||||
|
|
||||||
const emitter = new EventEmitter();
|
|
||||||
const navigation = new EventEmitter();
|
|
||||||
|
|
||||||
let navStack: Page[] = [];
|
|
||||||
let currentPage: number | null = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the event system.
|
|
||||||
*/
|
|
||||||
export function setup(): void {
|
|
||||||
window.onpopstate = (event) => {
|
|
||||||
navigate(event.state, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// Check if the window's href is a page.
|
|
||||||
const page = window.location.href.split("/").pop();
|
|
||||||
if (page == undefined || page == "") 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.
|
|
||||||
isPage(page) && navigate(pageType, false);
|
|
||||||
}, 3e2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
// Check the page.
|
|
||||||
if (page == undefined) page = "Home";
|
|
||||||
|
|
||||||
// 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;
|
|
13
src/handbook/src/backend/files.d.ts
vendored
13
src/handbook/src/backend/files.d.ts
vendored
@ -1,13 +0,0 @@
|
|||||||
declare module "*.svg" {
|
|
||||||
export const ReactComponent: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "*.webp" {
|
|
||||||
const ref: string;
|
|
||||||
export default ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "*.csv" {
|
|
||||||
const content: any[];
|
|
||||||
export default content;
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
import type { CommandResponse } from "@backend/types";
|
|
||||||
import emitter from "@backend/events";
|
|
||||||
|
|
||||||
import { getWindowDetails } from "@app/utils";
|
|
||||||
|
|
||||||
let playerToken: string | null = null; // The session token for the player.
|
|
||||||
export let targetPlayer = 0; // The UID of the target player.
|
|
||||||
|
|
||||||
// The server's address and port.
|
|
||||||
export let address: string = getWindowDetails().address,
|
|
||||||
port: string = getWindowDetails().port.toString();
|
|
||||||
export let encrypted: boolean = true;
|
|
||||||
|
|
||||||
export let lockedPlayer = false; // Whether the UID field is locked.
|
|
||||||
export let connected = false; // Whether the server is connected.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the server details from local storage.
|
|
||||||
*/
|
|
||||||
export function setup(): void {
|
|
||||||
// Check if the server is disabled.
|
|
||||||
if (getWindowDetails().disable) return;
|
|
||||||
|
|
||||||
// Load the server details from local storage.
|
|
||||||
const storedAddress = localStorage.getItem("address");
|
|
||||||
const storedPort = localStorage.getItem("port");
|
|
||||||
|
|
||||||
// Set the server details.
|
|
||||||
if (storedAddress) address = storedAddress;
|
|
||||||
if (storedPort) port = storedPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the formed URL.
|
|
||||||
* This assumes that the server upgrades to HTTPS.
|
|
||||||
*/
|
|
||||||
export function url(): string {
|
|
||||||
// noinspection HttpUrlsUsage
|
|
||||||
return `http${window.isSecureContext || encrypted ? "s" : ""}://${address}:${port}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the target player.
|
|
||||||
*
|
|
||||||
* @param player The UID of the target player.
|
|
||||||
* @param token The session token for the player.
|
|
||||||
*/
|
|
||||||
export function setTargetPlayer(player: number, token: string | null = null): void {
|
|
||||||
playerToken = token;
|
|
||||||
targetPlayer = player;
|
|
||||||
|
|
||||||
// Determine connected status.
|
|
||||||
connected = !isNaN(player) && player > 0;
|
|
||||||
// Determine locked status.
|
|
||||||
lockedPlayer = connected && token != null;
|
|
||||||
|
|
||||||
// Emit the connected event.
|
|
||||||
emitter.emit("connected", connected);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the server details.
|
|
||||||
*
|
|
||||||
* @param newAddress The server's address.
|
|
||||||
* @param newPort The server's port.
|
|
||||||
*/
|
|
||||||
export async function setServerDetails(newAddress: string | null, newPort: string | number | null): Promise<void> {
|
|
||||||
if (!getWindowDetails().disable) {
|
|
||||||
if (typeof newPort == "number") newPort = newPort.toString();
|
|
||||||
|
|
||||||
// Apply the new details.
|
|
||||||
if (newAddress != null) {
|
|
||||||
address = newAddress;
|
|
||||||
localStorage.setItem("address", newAddress);
|
|
||||||
}
|
|
||||||
if (newPort != null) {
|
|
||||||
port = newPort;
|
|
||||||
localStorage.setItem("port", newPort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the server is encrypted.
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
encrypted = true;
|
|
||||||
fetch(`${url()}`)
|
|
||||||
.catch(() => {
|
|
||||||
encrypted = false;
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.then(() => resolve());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a number.
|
|
||||||
*
|
|
||||||
* @param value The number to validate.
|
|
||||||
*/
|
|
||||||
function invalid(value: number): boolean {
|
|
||||||
return isNaN(value) || value < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grants an avatar to a player.
|
|
||||||
*
|
|
||||||
* @param avatar The avatar's ID.
|
|
||||||
* @param level The avatar's level.
|
|
||||||
* @param constellations The avatar's unlocked constellations.
|
|
||||||
* @param talents The level for the avatar's talents.
|
|
||||||
*/
|
|
||||||
export async function grantAvatar(
|
|
||||||
avatar: number,
|
|
||||||
level = 90,
|
|
||||||
constellations = 6,
|
|
||||||
talents = 10
|
|
||||||
): Promise<CommandResponse> {
|
|
||||||
// Validate the numbers.
|
|
||||||
if (invalid(avatar) || invalid(level) || invalid(constellations) || invalid(talents))
|
|
||||||
return { status: -1, message: "Invalid arguments." };
|
|
||||||
|
|
||||||
return await fetch(`${url()}/handbook/avatar`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playerToken,
|
|
||||||
player: targetPlayer.toString(),
|
|
||||||
avatar: avatar.toString(),
|
|
||||||
level,
|
|
||||||
constellations,
|
|
||||||
talentLevels: talents
|
|
||||||
})
|
|
||||||
}).then((res) => res.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gives an item to the player.
|
|
||||||
* This does not support weapons.
|
|
||||||
* This does not support relics.
|
|
||||||
*
|
|
||||||
* @param item The item's ID.
|
|
||||||
* @param amount The amount of the item to give.
|
|
||||||
*/
|
|
||||||
export async function giveItem(item: number, amount = 1): Promise<CommandResponse> {
|
|
||||||
// Validate the number.
|
|
||||||
if (isNaN(amount) || amount < 1) return { status: -1, message: "Invalid amount." };
|
|
||||||
|
|
||||||
return await fetch(`${url()}/handbook/item`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playerToken,
|
|
||||||
player: targetPlayer.toString(),
|
|
||||||
item: item.toString(),
|
|
||||||
amount
|
|
||||||
})
|
|
||||||
}).then((res) => res.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Teleports the player to a new scene.
|
|
||||||
*
|
|
||||||
* @param scene The scene's ID.
|
|
||||||
*/
|
|
||||||
export async function teleportTo(scene: number): Promise<CommandResponse> {
|
|
||||||
// Validate the number.
|
|
||||||
if (isNaN(scene) || scene < 1) return { status: -1, message: "Invalid scene." };
|
|
||||||
|
|
||||||
return await fetch(`${url()}/handbook/teleport`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playerToken,
|
|
||||||
player: targetPlayer.toString(),
|
|
||||||
scene: scene.toString()
|
|
||||||
})
|
|
||||||
}).then((res) => res.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawns an entity.
|
|
||||||
*
|
|
||||||
* @param entity The entity's ID.
|
|
||||||
* @param amount The amount of the entity to spawn.
|
|
||||||
* @param level The level of the entity to spawn.
|
|
||||||
*/
|
|
||||||
export async function spawnEntity(entity: number, amount = 1, level = 1): Promise<CommandResponse> {
|
|
||||||
// Validate the numbers.
|
|
||||||
if (isNaN(entity) || isNaN(amount) || isNaN(level) || amount < 1 || level < 1 || level > 200)
|
|
||||||
return { status: -1, message: "Invalid arguments." };
|
|
||||||
|
|
||||||
return await fetch(`${url()}/handbook/spawn`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playerToken,
|
|
||||||
player: targetPlayer.toString(),
|
|
||||||
entity: entity.toString(),
|
|
||||||
amount,
|
|
||||||
level
|
|
||||||
})
|
|
||||||
}).then((res) => res.json());
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
export type Page = "Home" | "Commands" | "Avatars" | "Items" | "Entities" | "Scenes" | "Quests" | "Achievements";
|
|
||||||
export type Overlays = "None" | "ServerSettings";
|
|
||||||
export type Days = "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday";
|
|
||||||
|
|
||||||
export type MainQuest = {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Command = {
|
|
||||||
name: string[];
|
|
||||||
description: string;
|
|
||||||
usage: string[];
|
|
||||||
permission: string[];
|
|
||||||
target: Target;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Avatar = {
|
|
||||||
name: string;
|
|
||||||
quality: Quality;
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Scene = {
|
|
||||||
identifier: string;
|
|
||||||
type: SceneType;
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Item = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
quality: Quality;
|
|
||||||
type: ItemType;
|
|
||||||
icon: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Entity = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
internal: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Quest = {
|
|
||||||
id: number;
|
|
||||||
description: string;
|
|
||||||
mainId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Exported from Project Amber.
|
|
||||||
export type ItemInfo = {
|
|
||||||
response: number | 200 | 404;
|
|
||||||
data: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
type: string;
|
|
||||||
recipe: boolean;
|
|
||||||
mapMark: boolean;
|
|
||||||
source: {
|
|
||||||
name: string;
|
|
||||||
type: string | "domain";
|
|
||||||
days: Days;
|
|
||||||
}[];
|
|
||||||
icon: string;
|
|
||||||
rank: 1 | 2 | 3 | 4 | 5;
|
|
||||||
route: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Exported from Project Amber.
|
|
||||||
export type EntityInfo = {
|
|
||||||
response: number | 200 | 404;
|
|
||||||
data: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
icon: string;
|
|
||||||
route: string;
|
|
||||||
title: string;
|
|
||||||
specialName: string;
|
|
||||||
description: string;
|
|
||||||
entries: any[];
|
|
||||||
tips: null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum Target {
|
|
||||||
None = "NONE",
|
|
||||||
Offline = "OFFLINE",
|
|
||||||
Player = "PLAYER",
|
|
||||||
Online = "ONLINE"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Quality {
|
|
||||||
Legendary = "LEGENDARY",
|
|
||||||
Epic = "EPIC",
|
|
||||||
Rare = "RARE",
|
|
||||||
Uncommon = "UNCOMMON",
|
|
||||||
Common = "COMMON",
|
|
||||||
Unknown = "UNKNOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ItemType {
|
|
||||||
None = "ITEM_NONE",
|
|
||||||
Virtual = "ITEM_VIRTUAL",
|
|
||||||
Material = "ITEM_MATERIAL",
|
|
||||||
Reliquary = "ITEM_RELIQUARY",
|
|
||||||
Weapon = "ITEM_WEAPON",
|
|
||||||
Display = "ITEM_DISPLAY",
|
|
||||||
Furniture = "ITEM_FURNITURE"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SceneType {
|
|
||||||
None = "SCENE_NONE",
|
|
||||||
World = "SCENE_WORLD",
|
|
||||||
Dungeon = "SCENE_DUNGEON",
|
|
||||||
Room = "SCENE_ROOM",
|
|
||||||
HomeWorld = "SCENE_HOME_WORLD",
|
|
||||||
HomeRoom = "SCENE_HOME_ROOM",
|
|
||||||
Activity = "SCENE_ACTIVITY"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ItemCategory {
|
|
||||||
Constellation,
|
|
||||||
Avatar,
|
|
||||||
Weapon,
|
|
||||||
Artifact,
|
|
||||||
Furniture,
|
|
||||||
Material,
|
|
||||||
Miscellaneous
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommandResponse = {
|
|
||||||
status: number | 200 | 500;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WindowDetails = {
|
|
||||||
address: string;
|
|
||||||
port: number;
|
|
||||||
disable: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a string is a page.
|
|
||||||
*
|
|
||||||
* @param page The string to check.
|
|
||||||
*/
|
|
||||||
export function isPage(page: string): page is Page {
|
|
||||||
return ["Home", "Commands", "Avatars", "Items", "Entities", "Scenes"].includes(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an item type to a string.
|
|
||||||
*
|
|
||||||
* @param type The item type to convert.
|
|
||||||
*/
|
|
||||||
export function itemTypeToString(type: ItemType): string {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
case ItemType.None:
|
|
||||||
return "None";
|
|
||||||
case ItemType.Virtual:
|
|
||||||
return "Virtual";
|
|
||||||
case ItemType.Material:
|
|
||||||
return "Material";
|
|
||||||
case ItemType.Reliquary:
|
|
||||||
return "Reliquary";
|
|
||||||
case ItemType.Weapon:
|
|
||||||
return "Weapon";
|
|
||||||
case ItemType.Display:
|
|
||||||
return "Display";
|
|
||||||
case ItemType.Furniture:
|
|
||||||
return "Furniture";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
src: url('/data/assets/Poppins-Regular.ttf')
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
--background-color: #25294a;
|
|
||||||
--primary-color: #2d325a;
|
|
||||||
--secondary-color: #202442;
|
|
||||||
--accent-color: #4b5396;
|
|
||||||
|
|
||||||
--text-primary-color: #FFFFFF;
|
|
||||||
--unselected-color: #c7c8d0;
|
|
||||||
--selected-color: #FFFFFF;
|
|
||||||
|
|
||||||
--legendary-color: #926d45;
|
|
||||||
--epic-color: #7b5c90;
|
|
||||||
|
|
||||||
// pq = Primary Quest
|
|
||||||
--pq-bg: #333d49;
|
|
||||||
--pq-text: #d3bc8e;
|
|
||||||
--pq-text2: #8c836f;
|
|
||||||
|
|
||||||
--quest-unselected: #4e5765;
|
|
||||||
--quest-selected: #ede5da;
|
|
||||||
--quest-accent: #9b927d;
|
|
||||||
|
|
||||||
// qt = Quest Text
|
|
||||||
--qt-unselected: #ede5da;
|
|
||||||
--qt2-unselected: #8e9295;
|
|
||||||
--qt-selected: #4d5568;
|
|
||||||
--qt2-selected: #a6a5a7;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: 'SDK_SC_Web', 'SDK_JP_Web', 'Poppins', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--accent-color);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
p {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 48px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
.GridRow {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
.AvatarsPage {
|
|
||||||
display: flex;
|
|
||||||
width: calc(100% - 352px);
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AvatarsPage_Title {
|
|
||||||
max-width: 275px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AvatarsPage_List {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 15px;
|
|
||||||
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
.CommandsPage {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CommandsPage_Title {
|
|
||||||
max-width: 275px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CommandsPage_List {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
.EntitiesPage {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
gap: 30px;
|
|
||||||
align-content: center;
|
|
||||||
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Title {
|
|
||||||
max-width: 230px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Search {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 465px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Input {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-size: 20px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 11px;
|
|
||||||
|
|
||||||
&:focus, &:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Input::placeholder {
|
|
||||||
color: var(--text-secondary-color);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_List {
|
|
||||||
display: grid;
|
|
||||||
gap: 15px 15px;
|
|
||||||
|
|
||||||
grid-template-columns: repeat(15, 100px);
|
|
||||||
|
|
||||||
margin-bottom: 28px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.EntitiesPage_Card {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
min-height: 300px;
|
|
||||||
max-height: 700px;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
.HomePage {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
display:flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 50px;
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Top {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Title {
|
|
||||||
margin-top: 31px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Buttons {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
max-width: 1376px;
|
|
||||||
|
|
||||||
gap: 24px;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Bottom {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Box {
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Disclaimer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 30px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
|
|
||||||
height: 100px;
|
|
||||||
align-self: end;
|
|
||||||
|
|
||||||
margin: 0 0 0 60px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 11px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Discord {
|
|
||||||
max-width: 150px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
align-self: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 44px;
|
|
||||||
max-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #5865F2;
|
|
||||||
box-shadow: 0 0 10px 0 rgba(0,0,0,0.75);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
|
|
||||||
max-width: 300px;
|
|
||||||
|
|
||||||
margin: 13px 60px 0 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Credits {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
:nth-child(1) {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(2) {
|
|
||||||
font-size: 10px;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomePage_Links {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
padding-right: 10px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
.ItemsPage {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
gap: 30px;
|
|
||||||
align-content: center;
|
|
||||||
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Title {
|
|
||||||
max-width: 130px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Search {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 465px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Input {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-size: 20px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 11px;
|
|
||||||
|
|
||||||
&:focus, &:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Input::placeholder {
|
|
||||||
color: var(--text-secondary-color);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_List {
|
|
||||||
margin-bottom: 28px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ItemsPage_Card {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
min-height: 300px;
|
|
||||||
max-height: 700px;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
.QuestsPage {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.QuestsPage_Selector {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 50%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
.ScenesPage {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ScenesPage_Title {
|
|
||||||
max-width: 180px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ScenesPage_List {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ScenesPage_Button {
|
|
||||||
width: 94px;
|
|
||||||
height: 34px;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: transparent;
|
|
||||||
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ScenesPage_Button:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ScenesPage_Button:active {
|
|
||||||
scale: 0.9;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
.Content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
.Overlay {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
background-color: rgb(0, 0, 0, 0.35);
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
.PlainText {
|
|
||||||
margin: 12px 5px 0 12px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: scroll;
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
.SideBar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
|
|
||||||
gap: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Title {
|
|
||||||
margin-top: 42px;
|
|
||||||
line-height: 41px;
|
|
||||||
font-size: 34px;
|
|
||||||
|
|
||||||
max-width: 256px;
|
|
||||||
max-height: 128px;
|
|
||||||
text-align: center;
|
|
||||||
align-self: center;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Title:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Enter {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 250px;
|
|
||||||
max-height: 50px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-self: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Input {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
font-size: 20px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 11px;
|
|
||||||
|
|
||||||
&:focus, &:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Input::placeholder {
|
|
||||||
color: var(--text-secondary-color);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBar_Input:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
border-radius: 10px ;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
.Card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1510px;
|
|
||||||
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Alternate {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Description {
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Card_Button {
|
|
||||||
display: flex;
|
|
||||||
margin-right: 13px;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
.Character {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
max-width: 96px;
|
|
||||||
max-height: 135px;
|
|
||||||
border-radius: 15px;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
box-shadow: 0 0 10px 5px var(--primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Character :hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Character_Icon {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Character_Label {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
|
|
||||||
max-width: 100px;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: center;
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
.HomeButton {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
width: min(10vw, 196px);
|
|
||||||
height: min(20vh, 196px);
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 0 10px 5px var(--accent-color);
|
|
||||||
scale: 1.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomeButton:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 0 10px 5px var(--primary-color);
|
|
||||||
scale: 1.01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomeButton_Icon {
|
|
||||||
max-width: 128px;
|
|
||||||
max-height: 128px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HomeButton_Label {
|
|
||||||
font-size: min(1.3vw, 30px);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
.MiniCard {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
filter: brightness(0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.MiniCard_Background {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
max-width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.MiniCard_Icon {
|
|
||||||
max-width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
object-fit: scale-down;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MiniCard_Label {
|
|
||||||
width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.MiniCard_Info {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
.ObjectCard {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
min-height: 300px;
|
|
||||||
max-height: 700px;
|
|
||||||
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Content {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
:nth-child(1) {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
max-width: 170px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(2) {
|
|
||||||
font-size: 16px;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Icon {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Description {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
max-width: 250px;
|
|
||||||
max-height: 460px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
gap: 5px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Counter {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 260px;
|
|
||||||
max-height: 46px;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 0 13px 0 13px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Operation {
|
|
||||||
user-select: none;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
width: 30px;
|
|
||||||
height: 20px;
|
|
||||||
|
|
||||||
font-size: 24px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Operation:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Count {
|
|
||||||
max-width: 105px;
|
|
||||||
height: 48px;
|
|
||||||
|
|
||||||
font-size: 24px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
border: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Count:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Submit {
|
|
||||||
width: 100%;
|
|
||||||
height: 46px;
|
|
||||||
max-width: 260px;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border: transparent;
|
|
||||||
font-size: 24px;
|
|
||||||
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
transition: 0.1s ease-in-out all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Submit:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
scale: 1.05;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ObjectCard_Submit:active {
|
|
||||||
scale: 0.9;
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
.ServerSettings {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 620px;
|
|
||||||
max-height: 400px;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Top {
|
|
||||||
height: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Frame {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 34px;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 590px;
|
|
||||||
max-height: 50px;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 20px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
|
|
||||||
&:focus, &:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Authenticate {
|
|
||||||
font-size: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 210px;
|
|
||||||
max-height: 46px;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ServerSettings_Save {
|
|
||||||
font-size: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 46px;
|
|
||||||
max-width: 120px;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
color: white;
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
.SideBarButton {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
gap: 15px;
|
|
||||||
padding-left: 27px;
|
|
||||||
|
|
||||||
height: 64px;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
backdrop-filter: brightness(0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
transition: 0.2s ease-in-out all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBarButton_Icon {
|
|
||||||
max-width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SideBarButton_Label {
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 29px;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
max-width: 220px;
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
.NormalQuest {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 431px;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
min-width: 100px;
|
|
||||||
min-height: 25px;
|
|
||||||
max-height: 53px;
|
|
||||||
|
|
||||||
background-color: var(--quest-unselected);
|
|
||||||
padding: 11px 20px 11px 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
p {
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.NormalQuest[datatype="right"] {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.NormalQuest:hover {
|
|
||||||
background-color: var(--quest-selected);
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--qt-selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.NormalQuest_Info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
:nth-child(1) {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--qt-unselected);
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(2) {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--qt2-unselected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.NormalQuest_Icon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--quest-accent);
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
.PrimaryQuest {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: min-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PrimaryQuest_List {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 97%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 5px;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 8px 8px 8px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trigger related CSS. */
|
|
||||||
|
|
||||||
.Trigger {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 10px 10px 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
width: 461px;
|
|
||||||
height: 100%;
|
|
||||||
min-width: 100px;
|
|
||||||
min-height: 25px;
|
|
||||||
max-height: 60px;
|
|
||||||
|
|
||||||
background-color: var(--pq-bg);
|
|
||||||
|
|
||||||
p {
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Trigger_Icon {
|
|
||||||
font-size: 20px;
|
|
||||||
padding-top: 5px;
|
|
||||||
|
|
||||||
color: var(--pq-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Trigger_Info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
:nth-child(1) {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--pq-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(2) {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--pq-text2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>
|
|
Before Width: | Height: | Size: 985 B |
@ -1,20 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { createRoot } from "react-dom/client";
|
|
||||||
|
|
||||||
import * as data from "@backend/data";
|
|
||||||
import * as events from "@backend/events";
|
|
||||||
import * as server from "@backend/server";
|
|
||||||
|
|
||||||
import App from "@ui/App";
|
|
||||||
|
|
||||||
// Call initial setup functions.
|
|
||||||
data.setup();
|
|
||||||
events.setup();
|
|
||||||
server.setup();
|
|
||||||
|
|
||||||
// Render the application.
|
|
||||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
@ -1,56 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import SideBar from "@views/SideBar";
|
|
||||||
import Content from "@views/Content";
|
|
||||||
import Overlay from "@views/Overlay";
|
|
||||||
import PlainText from "@views/PlainText";
|
|
||||||
|
|
||||||
import type { Page } from "@backend/types";
|
|
||||||
import { isPage } from "@backend/types";
|
|
||||||
|
|
||||||
import "@css/App.scss";
|
|
||||||
import "@css/Text.scss";
|
|
||||||
|
|
||||||
// Based on the design at: https://www.figma.com/file/PDeAVDkTDF5vvUGGdaIZ39/GM-Handbook.
|
|
||||||
// Currently designed by: Magix.
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
initial: Page | null;
|
|
||||||
plain: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class App extends React.Component<{}, IState> {
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
// Check if the window's href is a page.
|
|
||||||
let targetPage = null;
|
|
||||||
const page = window.location.href.split("/").pop();
|
|
||||||
|
|
||||||
if (page != undefined && page != "") {
|
|
||||||
// Convert the page to a Page type.
|
|
||||||
const pageName = page.charAt(0).toUpperCase() + page.slice(1);
|
|
||||||
// Check if the page is a valid page.
|
|
||||||
if (isPage(pageName)) targetPage = pageName as Page;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
initial: targetPage as Page | null,
|
|
||||||
plain: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"App"}>
|
|
||||||
<SideBar />
|
|
||||||
|
|
||||||
{this.state.plain ? <PlainText /> : <Content initial={this.state.initial} />}
|
|
||||||
|
|
||||||
<Overlay />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
@ -1,46 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import emitter from "@backend/events";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
initial: boolean;
|
|
||||||
event: string;
|
|
||||||
text1: string;
|
|
||||||
text2: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
state: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TextState extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
state: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the current state.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private update(state: boolean): void {
|
|
||||||
this.setState({ state });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
emitter.on(this.props.event, this.update.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
emitter.off(this.props.event, this.update);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.state.state ? this.props.text2 : this.props.text1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TextState;
|
|
@ -1,102 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { List as _List, ListProps, ListRowProps } from "react-virtualized/dist/es/List";
|
|
||||||
import { AutoSizer as _AutoSizer, AutoSizerProps } from "react-virtualized/dist/es/AutoSizer";
|
|
||||||
|
|
||||||
const List = _List as unknown as React.FC<ListProps>;
|
|
||||||
const AutoSizer = _AutoSizer as unknown as React.FC<AutoSizerProps>;
|
|
||||||
|
|
||||||
import "@css/components/VirtualizedGrid.scss";
|
|
||||||
|
|
||||||
interface IProps<T> {
|
|
||||||
list: T[];
|
|
||||||
render: (item: T) => React.ReactNode;
|
|
||||||
|
|
||||||
itemHeight: number;
|
|
||||||
itemsPerRow?: number;
|
|
||||||
|
|
||||||
gap?: number;
|
|
||||||
itemGap?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
scrollTop: number;
|
|
||||||
itemsPerRow: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class VirtualizedGrid<T> extends React.Component<IProps<T>, IState> {
|
|
||||||
constructor(props: IProps<T>) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
scrollTop: 0,
|
|
||||||
itemsPerRow: 10
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a row of items.
|
|
||||||
*/
|
|
||||||
private rowRender(props: ListRowProps): React.ReactNode {
|
|
||||||
const items: React.ReactNode[] = [];
|
|
||||||
|
|
||||||
// Calculate the items to render.
|
|
||||||
const perRow = this.state.itemsPerRow ?? 10;
|
|
||||||
for (let i = 0; i < perRow; i++) {
|
|
||||||
const itemIndex = props.index * perRow + i;
|
|
||||||
if (itemIndex < this.props.list.length) {
|
|
||||||
items.push(this.props.render(this.props.list[itemIndex]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={props.key}
|
|
||||||
style={{
|
|
||||||
...props.style,
|
|
||||||
gap: this.props.itemGap ?? 0
|
|
||||||
}}
|
|
||||||
className={"GridRow"}
|
|
||||||
>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<div key={index}>{item}</div>
|
|
||||||
))}
|
|
||||||
<div style={{ height: this.props.gap ?? 0 }} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setState({
|
|
||||||
itemsPerRow: Math.floor((window.innerWidth - 650) / (this.props.itemHeight + (this.props.gap ?? 0)))
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
this.setState({
|
|
||||||
itemsPerRow: Math.floor((window.innerWidth - 650) / (this.props.itemHeight + (this.props.gap ?? 0)))
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { list, itemHeight } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AutoSizer>
|
|
||||||
{({ height, width }) => (
|
|
||||||
<List
|
|
||||||
height={height - 150}
|
|
||||||
width={width}
|
|
||||||
rowHeight={itemHeight + (this.props.gap ?? 0)}
|
|
||||||
rowCount={Math.ceil(list.length / (this.state.itemsPerRow ?? 10))}
|
|
||||||
rowRenderer={this.rowRender.bind(this)}
|
|
||||||
scrollTop={this.state.scrollTop}
|
|
||||||
onScroll={(e) => this.setState({ scrollTop: e.scrollTop })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VirtualizedGrid;
|
|
@ -1,39 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import Character from "@app/ui/widgets/Character";
|
|
||||||
|
|
||||||
import type { Avatar } from "@backend/types";
|
|
||||||
import { listAvatars } from "@backend/data";
|
|
||||||
import { grantAvatar } from "@backend/server";
|
|
||||||
|
|
||||||
import "@css/pages/AvatarsPage.scss";
|
|
||||||
|
|
||||||
class AvatarsPage extends React.PureComponent {
|
|
||||||
/**
|
|
||||||
* Grants the avatar to the user.
|
|
||||||
*
|
|
||||||
* @param avatar The avatar to grant.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async grantAvatar(avatar: Avatar): Promise<void> {
|
|
||||||
console.log(await grantAvatar(avatar.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
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} onClick={this.grantAvatar.bind(this, avatar)} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AvatarsPage;
|
|
@ -1,32 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import Card from "@widgets/Card";
|
|
||||||
|
|
||||||
import { listCommands } from "@backend/data";
|
|
||||||
|
|
||||||
import "@css/pages/CommandsPage.scss";
|
|
||||||
|
|
||||||
class CommandsPage extends React.PureComponent {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"CommandsPage"}>
|
|
||||||
<h1 className={"CommandsPage_Title"}>Commands</h1>
|
|
||||||
|
|
||||||
<div className={"CommandsPage_List"}>
|
|
||||||
{listCommands().map((command) => (
|
|
||||||
<Card
|
|
||||||
key={command.name[0]}
|
|
||||||
title={command.name[0]}
|
|
||||||
alternate={
|
|
||||||
command.name.length == 1 ? undefined : `(aka /${command.name.slice(1).join(", /")})`
|
|
||||||
}
|
|
||||||
description={command.description}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommandsPage;
|
|
@ -1,153 +0,0 @@
|
|||||||
import React, { ChangeEvent } from "react";
|
|
||||||
|
|
||||||
import MiniCard from "@widgets/MiniCard";
|
|
||||||
import VirtualizedGrid from "@components/VirtualizedGrid";
|
|
||||||
|
|
||||||
import { Entity, ItemCategory } from "@backend/types";
|
|
||||||
import type { Entity as EntityType, EntityInfo } from "@backend/types";
|
|
||||||
import { getEntities } from "@backend/data";
|
|
||||||
import { entityIcon, fetchEntityData } from "@app/utils";
|
|
||||||
|
|
||||||
import "@css/pages/EntitiesPage.scss";
|
|
||||||
import EntityCard from "@widgets/EntityCard";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
filters: ItemCategory[];
|
|
||||||
search: string;
|
|
||||||
|
|
||||||
selected: EntityType | null;
|
|
||||||
selectedInfo: EntityInfo | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntitiesPage extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
filters: [],
|
|
||||||
search: "",
|
|
||||||
|
|
||||||
selected: null,
|
|
||||||
selectedInfo: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the entity be shown?
|
|
||||||
*
|
|
||||||
* @param entity The entity.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private showEntity(entity: Entity): boolean {
|
|
||||||
// Check if the entity's name starts with N/A.
|
|
||||||
if (entity.name.includes("[N/A]")) return false;
|
|
||||||
|
|
||||||
return entity.id > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the items to render.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getEntities(): EntityType[] {
|
|
||||||
let entities: EntityType[] = [];
|
|
||||||
|
|
||||||
// Add items based on filters.
|
|
||||||
const filters = this.state.filters;
|
|
||||||
if (filters.length == 0) {
|
|
||||||
entities = getEntities();
|
|
||||||
} else {
|
|
||||||
for (const filter of filters) {
|
|
||||||
// Remove duplicate items.
|
|
||||||
entities = entities.filter((item, index) => {
|
|
||||||
return entities.indexOf(item) == index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out items that don't match the search.
|
|
||||||
const search = this.state.search.toLowerCase();
|
|
||||||
if (search != "") {
|
|
||||||
entities = entities.filter((item) => {
|
|
||||||
return item.name.toLowerCase().includes(search);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the search input changes.
|
|
||||||
*
|
|
||||||
* @param event The event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private onChange(event: ChangeEvent<HTMLInputElement>): void {
|
|
||||||
this.setState({ search: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected entity.
|
|
||||||
*
|
|
||||||
* @param entity The entity.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async setSelectedItem(entity: EntityType): Promise<void> {
|
|
||||||
let data: EntityInfo | null = null;
|
|
||||||
try {
|
|
||||||
data = await fetchEntityData(entity);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
selected: entity,
|
|
||||||
selectedInfo: data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const entities = this.getEntities();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"EntitiesPage"}>
|
|
||||||
<div className={"EntitiesPage_Content"}>
|
|
||||||
<div className={"EntitiesPage_Header"}>
|
|
||||||
<h1 className={"EntitiesPage_Title"}>Monsters</h1>
|
|
||||||
|
|
||||||
<div className={"EntitiesPage_Search"}>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
className={"EntitiesPage_Input"}
|
|
||||||
placeholder={"Search..."}
|
|
||||||
onChange={this.onChange.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{entities.length > 0 ? (
|
|
||||||
<VirtualizedGrid
|
|
||||||
list={entities.filter((entity) => this.showEntity(entity))}
|
|
||||||
itemHeight={64}
|
|
||||||
itemsPerRow={18}
|
|
||||||
gap={5}
|
|
||||||
itemGap={5}
|
|
||||||
render={(entity) => (
|
|
||||||
<MiniCard
|
|
||||||
key={entity.id}
|
|
||||||
data={entity}
|
|
||||||
icon={entityIcon(entity)}
|
|
||||||
onClick={() => this.setSelectedItem(entity)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"EntitiesPage_Card"}>
|
|
||||||
<EntityCard entity={this.state.selected} info={this.state.selectedInfo} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EntitiesPage;
|
|
@ -1,75 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import HomeButton from "@widgets/HomeButton";
|
|
||||||
|
|
||||||
import { ReactComponent as DiscordLogo } from "@icons/discord.svg";
|
|
||||||
|
|
||||||
import Icon_Version_Highlights from "@assets/Icon_Version_Highlights.webp";
|
|
||||||
import Icon_Character_Lumine from "@assets/Icon_Character_Lumine.webp";
|
|
||||||
import Icon_Inventory from "@assets/Icon_Inventory.webp";
|
|
||||||
import Icon_Tutorial_Monster from "@assets/Icon_Tutorial_Monster.webp";
|
|
||||||
import Icon_Map from "@assets/Icon_Map.webp";
|
|
||||||
import Icon_Quests from "@assets/Icon_Quests.webp";
|
|
||||||
import Icon_Achievements from "@assets/Icon_Achievements.webp";
|
|
||||||
|
|
||||||
import { openUrl } from "@app/utils";
|
|
||||||
|
|
||||||
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"} icon={Icon_Version_Highlights} />
|
|
||||||
<HomeButton name={"Characters"} anchor={"Avatars"} icon={Icon_Character_Lumine} />
|
|
||||||
<HomeButton name={"Items"} anchor={"Items"} icon={Icon_Inventory} />
|
|
||||||
<HomeButton name={"Entities"} anchor={"Entities"} icon={Icon_Tutorial_Monster} />
|
|
||||||
<HomeButton name={"Scenes"} anchor={"Scenes"} icon={Icon_Map} />
|
|
||||||
<HomeButton name={"Quests"} anchor={"Quests"} icon={Icon_Quests} />
|
|
||||||
<HomeButton name={"Achievements"} anchor={"Achievements"} icon={Icon_Achievements} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"HomePage_Bottom"}>
|
|
||||||
<div className={"HomePage_Box HomePage_Disclaimer"}>
|
|
||||||
<p>
|
|
||||||
<b>This tool is not affiliated with HoYoverse.</b>
|
|
||||||
<br />
|
|
||||||
Genshin Impact, game content and materials are
|
|
||||||
<br />
|
|
||||||
trademarks and copyrights of HoYoverse.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className={"HomePage_Discord"} onClick={() => openUrl("https://discord.gg/grasscutter")}>
|
|
||||||
<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;
|
|
@ -1,157 +0,0 @@
|
|||||||
import React, { ChangeEvent } from "react";
|
|
||||||
|
|
||||||
import MiniCard from "@widgets/MiniCard";
|
|
||||||
import ItemCard from "@widgets/ItemCard";
|
|
||||||
import VirtualizedGrid from "@components/VirtualizedGrid";
|
|
||||||
|
|
||||||
import { ItemCategory } from "@backend/types";
|
|
||||||
import type { Item as ItemType, ItemInfo } from "@backend/types";
|
|
||||||
import { getItems, sortedItems } from "@backend/data";
|
|
||||||
import { fetchItemData, itemIcon } from "@app/utils";
|
|
||||||
|
|
||||||
import "@css/pages/ItemsPage.scss";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
filters: ItemCategory[];
|
|
||||||
search: string;
|
|
||||||
|
|
||||||
selected: ItemType | null;
|
|
||||||
selectedInfo: ItemInfo | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ItemsPage extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
filters: [],
|
|
||||||
search: "",
|
|
||||||
|
|
||||||
selected: null,
|
|
||||||
selectedInfo: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the items to render.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getItems(): ItemType[] {
|
|
||||||
let items: ItemType[] = [];
|
|
||||||
|
|
||||||
// Add items based on filters.
|
|
||||||
const filters = this.state.filters;
|
|
||||||
if (filters.length == 0) {
|
|
||||||
items = getItems();
|
|
||||||
} else {
|
|
||||||
for (const filter of filters) {
|
|
||||||
// Add items from the category.
|
|
||||||
items = items.concat(sortedItems[filter]);
|
|
||||||
// Remove duplicate items.
|
|
||||||
items = items.filter((item, index) => {
|
|
||||||
return items.indexOf(item) == index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out items that don't match the search.
|
|
||||||
const search = this.state.search.toLowerCase();
|
|
||||||
if (search != "") {
|
|
||||||
items = items.filter((item) => {
|
|
||||||
return item.name.toLowerCase().includes(search);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the search input changes.
|
|
||||||
*
|
|
||||||
* @param event The event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private onChange(event: ChangeEvent<HTMLInputElement>): void {
|
|
||||||
this.setState({ search: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the item be showed?
|
|
||||||
*
|
|
||||||
* @param item The item.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private showItem(item: ItemType): boolean {
|
|
||||||
// Check if the item has an icon.
|
|
||||||
if (item.icon.length == 0) return false;
|
|
||||||
// Check if the item is a TCG card.
|
|
||||||
if (item.icon.includes("Gcg")) return false;
|
|
||||||
|
|
||||||
return item.id > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected item.
|
|
||||||
*
|
|
||||||
* @param item The item.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async setSelectedItem(item: ItemType): Promise<void> {
|
|
||||||
let data: ItemInfo | null = null;
|
|
||||||
try {
|
|
||||||
data = await fetchItemData(item);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
selected: item,
|
|
||||||
selectedInfo: data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const items = this.getItems();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"ItemsPage"}>
|
|
||||||
<div className={"ItemsPage_Content"}>
|
|
||||||
<div className={"ItemsPage_Header"}>
|
|
||||||
<h1 className={"ItemsPage_Title"}>Items</h1>
|
|
||||||
|
|
||||||
<div className={"ItemsPage_Search"}>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
className={"ItemsPage_Input"}
|
|
||||||
placeholder={"Search..."}
|
|
||||||
onChange={this.onChange.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{items.length > 0 ? (
|
|
||||||
<VirtualizedGrid
|
|
||||||
list={items.filter((item) => this.showItem(item))}
|
|
||||||
itemHeight={64}
|
|
||||||
itemsPerRow={18}
|
|
||||||
gap={5}
|
|
||||||
itemGap={5}
|
|
||||||
render={(item) => (
|
|
||||||
<MiniCard
|
|
||||||
key={item.id}
|
|
||||||
data={item}
|
|
||||||
icon={itemIcon(item)}
|
|
||||||
onClick={() => this.setSelectedItem(item)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ItemsPage_Card"}>
|
|
||||||
<ItemCard item={this.state.selected} info={this.state.selectedInfo} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ItemsPage;
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import Tree, { RawNodeDatum } from "react-d3-tree";
|
|
||||||
|
|
||||||
import PrimaryQuest from "@widgets/quest/PrimaryQuest";
|
|
||||||
|
|
||||||
import "@css/pages/QuestsPage.scss";
|
|
||||||
|
|
||||||
const defaultTree: RawNodeDatum = {
|
|
||||||
name: "No Quest Selected",
|
|
||||||
attributes: {
|
|
||||||
questId: -1
|
|
||||||
},
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
tree: RawNodeDatum | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuestsPage extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
tree: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"QuestsPage"}>
|
|
||||||
<div className={"QuestsPage_Selector"}>
|
|
||||||
<PrimaryQuest
|
|
||||||
quest={{
|
|
||||||
id: 351,
|
|
||||||
title: "Across the Sea"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"QuestsPage_Tree"}>
|
|
||||||
<Tree data={this.state.tree ?? defaultTree} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default QuestsPage;
|
|
@ -1,78 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import Card from "@widgets/Card";
|
|
||||||
|
|
||||||
import { SceneType } from "@backend/types";
|
|
||||||
import { getScenes } from "@backend/data";
|
|
||||||
import { connected, teleportTo } from "@backend/server";
|
|
||||||
import { action } from "@backend/commands";
|
|
||||||
import { copyToClipboard } from "@app/utils";
|
|
||||||
|
|
||||||
import "@css/pages/ScenesPage.scss";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a scene type to a string.
|
|
||||||
*
|
|
||||||
* @param type The scene type.
|
|
||||||
*/
|
|
||||||
function sceneTypeToString(type: SceneType): string {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
case SceneType.None:
|
|
||||||
return "None";
|
|
||||||
case SceneType.World:
|
|
||||||
return "World";
|
|
||||||
case SceneType.Activity:
|
|
||||||
return "Activity";
|
|
||||||
case SceneType.Dungeon:
|
|
||||||
return "Dungeon";
|
|
||||||
case SceneType.Room:
|
|
||||||
return "Room";
|
|
||||||
case SceneType.HomeRoom:
|
|
||||||
return "Home Room";
|
|
||||||
case SceneType.HomeWorld:
|
|
||||||
return "Home World";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScenesPage extends React.PureComponent {
|
|
||||||
/**
|
|
||||||
* Teleports the player to the specified scene.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async teleport(scene: number): Promise<void> {
|
|
||||||
if (connected) {
|
|
||||||
await teleportTo(scene);
|
|
||||||
} else {
|
|
||||||
await copyToClipboard(action.teleport(scene));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"ScenesPage"}>
|
|
||||||
<h1 className={"ScenesPage_Title"}>Scenes</h1>
|
|
||||||
|
|
||||||
<div className={"ScenesPage_List"}>
|
|
||||||
{getScenes().map((scene) => (
|
|
||||||
<Card
|
|
||||||
key={scene.id}
|
|
||||||
title={scene.identifier}
|
|
||||||
alternate={`ID: ${scene.id} | ${sceneTypeToString(scene.type)}`}
|
|
||||||
button={
|
|
||||||
<button className={"ScenesPage_Button"} onClick={() => this.teleport(scene.id)}>
|
|
||||||
Teleport
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
rightOffset={13}
|
|
||||||
height={75}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ScenesPage;
|
|
@ -1,73 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import HomePage from "@pages/HomePage";
|
|
||||||
import CommandsPage from "@pages/CommandsPage";
|
|
||||||
import AvatarsPage from "@pages/AvatarsPage";
|
|
||||||
import ItemsPage from "@pages/ItemsPage";
|
|
||||||
import EntitiesPage from "@pages/EntitiesPage";
|
|
||||||
import ScenesPage from "@pages/ScenesPage";
|
|
||||||
import QuestsPage from "@pages/QuestsPage";
|
|
||||||
|
|
||||||
import type { Page } from "@backend/types";
|
|
||||||
import { addNavListener, removeNavListener } from "@backend/events";
|
|
||||||
|
|
||||||
import "@css/views/Content.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
initial?: Page | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
switch (this.state.current) {
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
case "Home":
|
|
||||||
return <HomePage />;
|
|
||||||
case "Commands":
|
|
||||||
return <CommandsPage />;
|
|
||||||
case "Avatars":
|
|
||||||
return <AvatarsPage />;
|
|
||||||
case "Items":
|
|
||||||
return <ItemsPage />;
|
|
||||||
case "Entities":
|
|
||||||
return <EntitiesPage />;
|
|
||||||
case "Scenes":
|
|
||||||
return <ScenesPage />;
|
|
||||||
case "Quests":
|
|
||||||
return <QuestsPage />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Content;
|
|
@ -1,57 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import ServerSettings from "@widgets/ServerSettings";
|
|
||||||
|
|
||||||
import type { Overlays } from "@backend/types";
|
|
||||||
|
|
||||||
import "@css/views/Overlay.scss";
|
|
||||||
import events from "@backend/events";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
page: Overlays;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Overlay extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
page: "None"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the page to display.
|
|
||||||
*
|
|
||||||
* @param page The page to display.
|
|
||||||
*/
|
|
||||||
private setPage(page: Overlays): void {
|
|
||||||
this.setState({ page });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the page to display.
|
|
||||||
*/
|
|
||||||
private getPage(): React.ReactNode {
|
|
||||||
switch (this.state.page) {
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
case "ServerSettings":
|
|
||||||
return <ServerSettings />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
events.on("overlay", this.setPage.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
events.off("overlay", this.setPage.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.state.page != "None" ? <div className={"Overlay"}>{this.getPage()}</div> : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Overlay;
|
|
@ -1,171 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
listCommands,
|
|
||||||
listAvatars,
|
|
||||||
getItems,
|
|
||||||
getEntities,
|
|
||||||
getScenes,
|
|
||||||
listQuests,
|
|
||||||
getMainQuestFor
|
|
||||||
} from "@backend/data";
|
|
||||||
|
|
||||||
import "@css/views/PlainText.scss";
|
|
||||||
|
|
||||||
class PlainText extends React.PureComponent {
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of commands.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getCommands(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{listCommands().map((command) => (
|
|
||||||
<p key={command.name[0]}>{`${command.name[0]} : ${command.description}`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of avatars.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getAvatars(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{listAvatars()
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.map((avatar) => (
|
|
||||||
<p key={avatar.id}>{`${avatar.id} : ${avatar.name}`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of items.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getItems(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{getItems()
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.map((item) => (
|
|
||||||
<p key={item.id}>{`${item.id} : ${item.name}`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of monsters.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getMonsters(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{getEntities()
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.map((entity) => (
|
|
||||||
<p key={entity.id}>{`${entity.id} : ${entity.name}`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of scenes.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getScenes(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{getScenes()
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.map((scene) => (
|
|
||||||
<p key={scene.id}>{`${scene.id} : ${scene.identifier} [${scene.type}]`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a paragraph of quests.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private getQuests(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{listQuests()
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.map((quest) => (
|
|
||||||
<p key={quest.id}>{`${quest.id} : ${getMainQuestFor(quest)?.title ?? "Unknown"} - ${
|
|
||||||
quest.description
|
|
||||||
}`}</p>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"PlainText"}>
|
|
||||||
<p>
|
|
||||||
// Grasscutter 3.6.0 GM Handbook
|
|
||||||
<br />
|
|
||||||
// Generated by the HTML GM Handbook.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Commands
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getCommands()}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Avatars
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getAvatars()}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Items
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getItems()}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Monsters
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getMonsters()}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Scenes
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getScenes()}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
// Quests
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.getQuests()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlainText;
|
|
@ -1,123 +0,0 @@
|
|||||||
import React, { ChangeEvent } from "react";
|
|
||||||
|
|
||||||
import SideBarButton from "@app/ui/widgets/SideBarButton";
|
|
||||||
|
|
||||||
import Icon_Version_Highlights from "@assets/Icon_Version_Highlights.webp";
|
|
||||||
import Icon_Character_Lumine from "@assets/Icon_Character_Lumine.webp";
|
|
||||||
import Icon_Inventory from "@assets/Icon_Inventory.webp";
|
|
||||||
import Icon_Tutorial_Monster from "@assets/Icon_Tutorial_Monster.webp";
|
|
||||||
import Icon_Map from "@assets/Icon_Map.webp";
|
|
||||||
import Icon_Quests from "@assets/Icon_Quests.webp";
|
|
||||||
import Icon_Achievements from "@assets/Icon_Achievements.webp";
|
|
||||||
|
|
||||||
import events, { navigate } from "@backend/events";
|
|
||||||
import { targetPlayer, lockedPlayer, setTargetPlayer } from "@backend/server";
|
|
||||||
|
|
||||||
import "@css/views/SideBar.scss";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
uid: string | null;
|
|
||||||
uidLocked: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SideBar extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
uid: targetPlayer > 0 ? targetPlayer.toString() : null,
|
|
||||||
uidLocked: lockedPlayer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the player's UID changes.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private updateUid(): void {
|
|
||||||
this.setState({
|
|
||||||
uid: targetPlayer > 0 ? targetPlayer.toString() : null,
|
|
||||||
uidLocked: lockedPlayer
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the UID input changes.
|
|
||||||
*
|
|
||||||
* @param event The event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private onChange(event: ChangeEvent<HTMLInputElement>): void {
|
|
||||||
const input = event.target.value;
|
|
||||||
const uid = input == "" ? null : input;
|
|
||||||
if (uid && uid.length > 10) return;
|
|
||||||
|
|
||||||
setTargetPlayer(parseInt(uid ?? "0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the UID input is right-clicked.
|
|
||||||
*
|
|
||||||
* @param event The event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private onRightClick(event: React.MouseEvent<HTMLInputElement, MouseEvent>): void {
|
|
||||||
// Remove focus from the input.
|
|
||||||
event.currentTarget.blur();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Open the server settings overlay.
|
|
||||||
events.emit("overlay", "ServerSettings");
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
events.on("connected", this.updateUid.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
events.off("connected", this.updateUid.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"SideBar"}>
|
|
||||||
<h1 className={"SideBar_Title"} onClick={() => navigate("Home")}>
|
|
||||||
The Ultimate Anime Game Handbook
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
height: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={"SideBar_Buttons"}>
|
|
||||||
<SideBarButton name={"Commands"} anchor={"Commands"} icon={Icon_Version_Highlights} />
|
|
||||||
<SideBarButton name={"Characters"} anchor={"Avatars"} icon={Icon_Character_Lumine} />
|
|
||||||
<SideBarButton name={"Items"} anchor={"Items"} icon={Icon_Inventory} />
|
|
||||||
<SideBarButton name={"Entities"} anchor={"Entities"} icon={Icon_Tutorial_Monster} />
|
|
||||||
<SideBarButton name={"Scenes"} anchor={"Scenes"} icon={Icon_Map} />
|
|
||||||
<SideBarButton name={"Quests"} anchor={"Quests"} icon={Icon_Quests} />
|
|
||||||
<SideBarButton name={"Achievements"} anchor={"Achievements"} icon={Icon_Achievements} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"SideBar_Enter"}>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
className={"SideBar_Input"}
|
|
||||||
placeholder={"Enter UID..."}
|
|
||||||
value={this.state.uid ?? undefined}
|
|
||||||
disabled={this.state.uidLocked}
|
|
||||||
onChange={this.onChange.bind(this)}
|
|
||||||
onContextMenu={this.onRightClick.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SideBar;
|
|
@ -1,67 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import "@css/widgets/Card.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
title: string;
|
|
||||||
alternate?: string;
|
|
||||||
description?: string | string[];
|
|
||||||
|
|
||||||
height?: number | string;
|
|
||||||
button?: React.ReactNode;
|
|
||||||
rightOffset?: number;
|
|
||||||
|
|
||||||
onClick?: () => void;
|
|
||||||
onOver?: () => void;
|
|
||||||
onOut?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Card extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={"Card"}
|
|
||||||
onClick={this.props.onClick}
|
|
||||||
onMouseOver={this.props.onOver}
|
|
||||||
onMouseOut={this.props.onOut}
|
|
||||||
style={{
|
|
||||||
height: this.props.height,
|
|
||||||
cursor: this.props.onClick ? "pointer" : undefined
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={"Card_Content"}>
|
|
||||||
<div className={"Card_Header"}>
|
|
||||||
<p className={"Card_Title"}>{this.props.title}</p>
|
|
||||||
{this.props.alternate && <p className={"Card_Alternate"}>{this.props.alternate}</p>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ alignItems: "center" }}>
|
|
||||||
{this.props.description ? (
|
|
||||||
Array.isArray(this.props.description) ? (
|
|
||||||
this.props.description.map((line, index) => (
|
|
||||||
<p className={"Card_Description"} key={index}>
|
|
||||||
{line}
|
|
||||||
</p>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className={"Card_Description"}>{this.props.description}</p>
|
|
||||||
)
|
|
||||||
) : undefined}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.props.button ? (
|
|
||||||
<div className={"Card_Button"} style={{ marginRight: this.props.rightOffset ?? 0 }}>
|
|
||||||
{this.props.button}
|
|
||||||
</div>
|
|
||||||
) : undefined}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Card;
|
|
@ -1,56 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import type { Avatar } from "@backend/types";
|
|
||||||
import { colorFor, formatAvatarName } from "@app/utils";
|
|
||||||
|
|
||||||
import "@css/widgets/Character.scss";
|
|
||||||
|
|
||||||
// Image base URL: https://paimon.moe/images/characters/(name).png
|
|
||||||
|
|
||||||
const ignored = [
|
|
||||||
10000001 // Kate
|
|
||||||
];
|
|
||||||
|
|
||||||
const nameSwitch: { [key: number]: string } = {
|
|
||||||
10000005: "Lumine",
|
|
||||||
10000007: "Aether"
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: Avatar;
|
|
||||||
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Character extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { name, quality, id } = this.props.data;
|
|
||||||
const qualityColor = colorFor(quality);
|
|
||||||
|
|
||||||
// Check if the avatar is blacklisted.
|
|
||||||
if (ignored.includes(id)) return undefined;
|
|
||||||
|
|
||||||
const characterName = nameSwitch[id] ?? name;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"Character"} onClick={this.props.onClick}>
|
|
||||||
<img
|
|
||||||
className={"Character_Icon"}
|
|
||||||
alt={name}
|
|
||||||
src={`https://paimon.moe/images/characters/${formatAvatarName(name, id)}.png`}
|
|
||||||
style={{ backgroundColor: `var(${qualityColor})` }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={"Character_Label"}>
|
|
||||||
<p style={{ fontSize: characterName.length >= 10 ? 13 : 17 }}>{characterName}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Character;
|
|
@ -1,199 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import type { Entity as EntityType, EntityInfo } from "@backend/types";
|
|
||||||
import { copyToClipboard, entityIcon, notNaN } from "@app/utils";
|
|
||||||
import { connected, spawnEntity } from "@backend/server";
|
|
||||||
import { spawn } from "@backend/commands";
|
|
||||||
|
|
||||||
import "@css/widgets/ObjectCard.scss";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a description string into a list of paragraphs.
|
|
||||||
*
|
|
||||||
* @param description The description to convert.
|
|
||||||
*/
|
|
||||||
function toDescription(description: string | undefined): JSX.Element[] {
|
|
||||||
if (!description) return [];
|
|
||||||
|
|
||||||
return description.split("\\n").map((line, index) => {
|
|
||||||
return <p key={index}>{line}</p>;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
entity: EntityType | null;
|
|
||||||
info: EntityInfo | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
icon: boolean;
|
|
||||||
count: number | string;
|
|
||||||
level: number | string;
|
|
||||||
|
|
||||||
showingCount: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
icon: true,
|
|
||||||
count: 1,
|
|
||||||
level: 1,
|
|
||||||
showingCount: true
|
|
||||||
};
|
|
||||||
|
|
||||||
class EntityCard extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = defaultState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the count of the item.
|
|
||||||
*
|
|
||||||
* @param event The change event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private updateCount(event: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
let value = event.target.value;
|
|
||||||
// Remove non-numeric characters.
|
|
||||||
value = value.replace(/[^0-9]/g, "");
|
|
||||||
|
|
||||||
let numeric = parseInt(value);
|
|
||||||
if (isNaN(numeric) && value.length > 1) return;
|
|
||||||
|
|
||||||
// Check if the value should be a level.
|
|
||||||
if (!this.state.showingCount && numeric > 200) numeric = 200;
|
|
||||||
|
|
||||||
const updated: any = this.state.showingCount ? { count: numeric } : { level: numeric };
|
|
||||||
this.setState(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds to the count of the entity.
|
|
||||||
*
|
|
||||||
* @param positive Is the count being added or subtracted?
|
|
||||||
* @param multiple Is the count being multiplied by 10?
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private addCount(positive: boolean, multiple: boolean) {
|
|
||||||
let value = this.state.showingCount ? this.state.count : this.state.level;
|
|
||||||
if (value === "") value = 1;
|
|
||||||
if (typeof value == "string") value = parseInt(value);
|
|
||||||
if (value < 1) value = 1;
|
|
||||||
|
|
||||||
let increment = 1;
|
|
||||||
if (!positive) increment = -1;
|
|
||||||
if (multiple) increment *= 10;
|
|
||||||
|
|
||||||
value = Math.max(1, value + increment);
|
|
||||||
// Check if the value should be a level.
|
|
||||||
if (!this.state.showingCount && value > 200) value = 200;
|
|
||||||
|
|
||||||
const updated: any = this.state.showingCount ? { count: value } : { level: value };
|
|
||||||
this.setState(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Summons the entity at the connected player's position.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async summonAtPlayer(): Promise<void> {
|
|
||||||
const entity = this.props.entity?.id ?? 21010101;
|
|
||||||
const amount = typeof this.state.count == "string" ? parseInt(this.state.count) : this.state.count;
|
|
||||||
const level = typeof this.state.level == "string" ? parseInt(this.state.level) : this.state.level;
|
|
||||||
|
|
||||||
if (connected) {
|
|
||||||
await spawnEntity(entity, amount, level);
|
|
||||||
} else {
|
|
||||||
await copyToClipboard(spawn.monster(entity, amount, level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>, snapshot?: any) {
|
|
||||||
if (this.props.entity != prevProps.entity) {
|
|
||||||
this.setState(defaultState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { entity, info } = this.props;
|
|
||||||
const data = info?.data;
|
|
||||||
|
|
||||||
return entity ? (
|
|
||||||
<div className={"ObjectCard"}>
|
|
||||||
<div className={"ObjectCard_Content"}>
|
|
||||||
<div className={"ObjectCard_Header"}>
|
|
||||||
<div className={"ObjectCard_Info"}>
|
|
||||||
<p>{data?.name ?? entity.name}</p>
|
|
||||||
<p>{data?.type ?? ""}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.icon && (
|
|
||||||
<img
|
|
||||||
className={"ObjectCard_Icon"}
|
|
||||||
alt={entity.name}
|
|
||||||
src={entityIcon(entity)}
|
|
||||||
onError={() => this.setState({ icon: false })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ObjectCard_Description"}>{toDescription(data?.description)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ObjectCard_Actions"}>
|
|
||||||
<div className={"ObjectCard_Counter"}>
|
|
||||||
<div
|
|
||||||
onClick={() => this.addCount(false, false)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.addCount(false, true);
|
|
||||||
}}
|
|
||||||
className={"ObjectCard_Operation"}
|
|
||||||
>
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
value={
|
|
||||||
this.state.showingCount
|
|
||||||
? `x${notNaN(this.state.count)}`
|
|
||||||
: `Lv${notNaN(this.state.level)}`
|
|
||||||
}
|
|
||||||
className={"ObjectCard_Count"}
|
|
||||||
onChange={this.updateCount.bind(this)}
|
|
||||||
onBlur={() => {
|
|
||||||
if (this.state.count == "") {
|
|
||||||
this.setState({ count: 1 });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
onClick={() => this.addCount(true, false)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.addCount(true, true);
|
|
||||||
}}
|
|
||||||
className={"ObjectCard_Operation"}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={"ObjectCard_Submit"}
|
|
||||||
onClick={this.summonAtPlayer.bind(this)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.setState({ showingCount: !this.state.showingCount });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Summon
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EntityCard;
|
|
@ -1,45 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import type { Page } from "@backend/types";
|
|
||||||
import { navigate } from "@backend/events";
|
|
||||||
|
|
||||||
import "@css/widgets/HomeButton.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
anchor: Page;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HomeButton extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects the user to the specified anchor.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private redirect(): void {
|
|
||||||
navigate(this.props.anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this component should be showed.
|
|
||||||
*/
|
|
||||||
private shouldShow(): boolean {
|
|
||||||
return !((window as any).hide as string[]).includes(this.props.anchor.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.shouldShow() ? (
|
|
||||||
<div className={"HomeButton"} onClick={() => this.redirect()}>
|
|
||||||
<img className={"HomeButton_Icon"} src={this.props.icon} alt={this.props.name} />
|
|
||||||
|
|
||||||
<p className={"HomeButton_Label"}>{this.props.name}</p>
|
|
||||||
</div>
|
|
||||||
) : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HomeButton;
|
|
@ -1,179 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import TextState from "@components/TextState";
|
|
||||||
|
|
||||||
import type { Item as ItemType, ItemInfo } from "@backend/types";
|
|
||||||
import { itemTypeToString } from "@backend/types";
|
|
||||||
import { copyToClipboard, itemIcon } from "@app/utils";
|
|
||||||
import { connected, giveItem } from "@backend/server";
|
|
||||||
import { give } from "@backend/commands";
|
|
||||||
|
|
||||||
import "@css/widgets/ObjectCard.scss";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a description string into a list of paragraphs.
|
|
||||||
*
|
|
||||||
* @param description The description to convert.
|
|
||||||
*/
|
|
||||||
function toDescription(description: string | undefined): JSX.Element[] {
|
|
||||||
if (!description) return [];
|
|
||||||
|
|
||||||
return description.split("\\n").map((line, index) => {
|
|
||||||
return <p key={index}>{line}</p>;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
item: ItemType | null;
|
|
||||||
info: ItemInfo | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
icon: boolean;
|
|
||||||
count: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
icon: true,
|
|
||||||
count: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
class ItemCard extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = defaultState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the count of the item.
|
|
||||||
*
|
|
||||||
* @param event The change event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private updateCount(event: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
const value = event.target.value;
|
|
||||||
if (isNaN(parseInt(value)) && value.length > 1) return;
|
|
||||||
|
|
||||||
this.setState({ count: value });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds to the count of the item.
|
|
||||||
*
|
|
||||||
* @param positive Is the count being added or subtracted?
|
|
||||||
* @param multiple Is the count being multiplied by 10?
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private addCount(positive: boolean, multiple: boolean) {
|
|
||||||
let { count } = this.state;
|
|
||||||
if (count === "") count = 1;
|
|
||||||
if (typeof count == "string") count = parseInt(count);
|
|
||||||
if (count < 1) count = 1;
|
|
||||||
|
|
||||||
let increment = 1;
|
|
||||||
if (!positive) increment = -1;
|
|
||||||
if (multiple) increment *= 10;
|
|
||||||
|
|
||||||
count = Math.max(1, count + increment);
|
|
||||||
|
|
||||||
this.setState({ count });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the item to the player's connected inventory.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async addToInventory(): Promise<void> {
|
|
||||||
const item = this.props.item?.id ?? 102;
|
|
||||||
const amount = typeof this.state.count == "string" ? parseInt(this.state.count) : this.state.count;
|
|
||||||
|
|
||||||
if (connected) {
|
|
||||||
await giveItem(item, amount);
|
|
||||||
} else {
|
|
||||||
await copyToClipboard(give.basic(item, amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>, snapshot?: any) {
|
|
||||||
if (this.props.item != prevProps.item) {
|
|
||||||
this.setState(defaultState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { item, info } = this.props;
|
|
||||||
const data = info?.data;
|
|
||||||
|
|
||||||
return item ? (
|
|
||||||
<div className={"ObjectCard"}>
|
|
||||||
<div className={"ObjectCard_Content"}>
|
|
||||||
<div className={"ObjectCard_Header"}>
|
|
||||||
<div className={"ObjectCard_Info"}>
|
|
||||||
<p>{data?.name ?? item.name}</p>
|
|
||||||
<p>{data?.type ?? itemTypeToString(item.type)}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.icon && (
|
|
||||||
<img
|
|
||||||
className={"ObjectCard_Icon"}
|
|
||||||
alt={item.name}
|
|
||||||
src={itemIcon(item)}
|
|
||||||
onError={() => this.setState({ icon: false })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ObjectCard_Description"}>{toDescription(data?.description)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ObjectCard_Actions"}>
|
|
||||||
<div className={"ObjectCard_Counter"}>
|
|
||||||
<div
|
|
||||||
onClick={() => this.addCount(false, false)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.addCount(false, true);
|
|
||||||
}}
|
|
||||||
className={"ObjectCard_Operation"}
|
|
||||||
>
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
value={this.state.count}
|
|
||||||
className={"ObjectCard_Count"}
|
|
||||||
onChange={this.updateCount.bind(this)}
|
|
||||||
onBlur={() => {
|
|
||||||
if (this.state.count == "") {
|
|
||||||
this.setState({ count: 1 });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
onClick={() => this.addCount(true, false)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.addCount(true, true);
|
|
||||||
}}
|
|
||||||
className={"ObjectCard_Operation"}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button className={"ObjectCard_Submit"} onClick={this.addToInventory.bind(this)}>
|
|
||||||
<TextState
|
|
||||||
initial={connected}
|
|
||||||
event={"connected"}
|
|
||||||
text1={"Copy Command"}
|
|
||||||
text2={"Add to Inventory"}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ItemCard;
|
|
@ -1,109 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import "@css/widgets/MiniCard.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: { name: string };
|
|
||||||
icon: string;
|
|
||||||
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
popout: boolean;
|
|
||||||
icon: boolean;
|
|
||||||
loaded: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MiniCard extends React.Component<IProps, IState> {
|
|
||||||
loading: number | any;
|
|
||||||
|
|
||||||
containerRef: React.RefObject<HTMLDivElement>;
|
|
||||||
textRef: React.RefObject<HTMLDivElement>;
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
popout: false,
|
|
||||||
icon: true,
|
|
||||||
loaded: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.containerRef = React.createRef();
|
|
||||||
this.textRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the icon with the item's name.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private replaceIcon(): void {
|
|
||||||
this.setState({ icon: false, loaded: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
private forceReplace(): void {
|
|
||||||
if (!this.state.loaded) this.replaceIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjusts the font size of the text to fit the container.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private adjustFontSize() {
|
|
||||||
const container = this.containerRef.current;
|
|
||||||
const text = this.textRef.current;
|
|
||||||
|
|
||||||
if (!container || !text) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerWidth = container.offsetWidth;
|
|
||||||
const textWidth = text.scrollWidth;
|
|
||||||
|
|
||||||
const fontSize = parseFloat(window.getComputedStyle(text).fontSize);
|
|
||||||
const availableWidth = containerWidth - 10;
|
|
||||||
const scaleFactor = availableWidth / textWidth;
|
|
||||||
|
|
||||||
if (scaleFactor < 1) {
|
|
||||||
const newFontSize = fontSize * scaleFactor;
|
|
||||||
text.style.fontSize = newFontSize + "px";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.loading = setTimeout(this.forceReplace.bind(this), 1e3);
|
|
||||||
this.adjustFontSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearTimeout(this.loading);
|
|
||||||
this.loading = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={"MiniCard"} onClick={this.props.onClick}>
|
|
||||||
<div className={"MiniCard_Background"} ref={this.containerRef}>
|
|
||||||
{this.state.icon && (
|
|
||||||
<img
|
|
||||||
className={"MiniCard_Icon"}
|
|
||||||
alt={this.props.data.name}
|
|
||||||
src={this.props.icon}
|
|
||||||
onError={this.replaceIcon.bind(this)}
|
|
||||||
onLoad={() => this.setState({ loaded: true })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(!this.state.loaded || !this.state.icon) && (
|
|
||||||
<p className={"MiniCard_Label"} ref={this.textRef}>
|
|
||||||
{this.props.data.name}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MiniCard;
|
|
@ -1,183 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import emitter from "@backend/events";
|
|
||||||
import { targetPlayer, address, port, setServerDetails, url, setTargetPlayer } from "@backend/server";
|
|
||||||
import { getWindowDetails } from "@app/utils";
|
|
||||||
|
|
||||||
import "@css/widgets/ServerSettings.scss";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
webview: boolean;
|
|
||||||
|
|
||||||
address: string;
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServerSettings extends React.Component<{}, IState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
webview: false,
|
|
||||||
address: address,
|
|
||||||
port: Number(port)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addEventListener("keyup", this.escapeListener.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener("keyup", this.escapeListener.bind(this));
|
|
||||||
window.removeEventListener("message", this.handleAuthentication.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the escape key is pressed.
|
|
||||||
*
|
|
||||||
* @param e The keyboard event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private escapeListener(e: KeyboardEvent): void {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
// Hide the overlay.
|
|
||||||
emitter.emit("overlay", "None");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the component tries to authenticate.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private authenticate(): void {
|
|
||||||
setServerDetails(this.state.address, this.state.port).then(() => {
|
|
||||||
this.setState({ webview: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the event listener for authentication.
|
|
||||||
window.addEventListener("message", this.handleAuthentication.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finishes the authentication process.
|
|
||||||
*
|
|
||||||
* @param e The message event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private handleAuthentication(e: MessageEvent): void {
|
|
||||||
const data = e.data; // The data sent from the server.
|
|
||||||
if (data == null) return; // If the data is null, return.
|
|
||||||
|
|
||||||
// Check if the data is an object.
|
|
||||||
if (typeof data != "object") return;
|
|
||||||
// Get the data type.
|
|
||||||
const type = data["type"] ?? null;
|
|
||||||
if (type != "handbook-auth") return;
|
|
||||||
|
|
||||||
// Get the data.
|
|
||||||
const uid = data["uid"] ?? null;
|
|
||||||
const token = data["token"] ?? null;
|
|
||||||
|
|
||||||
// Hide the overlay.
|
|
||||||
emitter.emit("overlay", "None");
|
|
||||||
// Set the token and user ID.
|
|
||||||
setTargetPlayer(Number(uid), token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the save button is clicked.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private save(): void {
|
|
||||||
// Hide the overlay.
|
|
||||||
emitter.emit("overlay", "None");
|
|
||||||
|
|
||||||
// Save the server settings.
|
|
||||||
setServerDetails(this.state.address, this.state.port.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { disable } = getWindowDetails();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"ServerSettings"}>
|
|
||||||
{this.state.webview ? (
|
|
||||||
<iframe
|
|
||||||
className={"ServerSettings_Frame"}
|
|
||||||
src={`${url()}/handbook/authenticate?uid=${targetPlayer}`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className={"ServerSettings_Content ServerSettings_Top"}>
|
|
||||||
<h1 className={"ServerSettings_Title"}>Server Settings</h1>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={"ServerSettings_Details"}
|
|
||||||
style={{
|
|
||||||
opacity: disable ? 0.5 : 1,
|
|
||||||
cursor: disable ? "not-allowed" : "default",
|
|
||||||
userSelect: disable ? "none" : "auto"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p>Address:</p>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
value={this.state.address}
|
|
||||||
onChange={(e) => {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
const value = target.value;
|
|
||||||
|
|
||||||
this.setState({ address: value });
|
|
||||||
}}
|
|
||||||
disabled={disable}
|
|
||||||
style={{
|
|
||||||
cursor: disable ? "not-allowed" : "text",
|
|
||||||
userSelect: disable ? "none" : "auto"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>Port:</p>
|
|
||||||
<input
|
|
||||||
type={"text"}
|
|
||||||
value={this.state.port == 0 ? "" : this.state.port}
|
|
||||||
onChange={(e) => {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
const value = target.value;
|
|
||||||
|
|
||||||
if (isNaN(Number(value)) || value.length > 5) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ port: Number(value) });
|
|
||||||
}}
|
|
||||||
disabled={disable}
|
|
||||||
style={{
|
|
||||||
cursor: disable ? "not-allowed" : "text",
|
|
||||||
userSelect: disable ? "none" : "auto"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button className={"ServerSettings_Authenticate"} onClick={this.authenticate.bind(this)}>
|
|
||||||
Authenticate
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"ServerSettings_Content"}>
|
|
||||||
<button className={"ServerSettings_Save"} onClick={this.save.bind(this)}>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ServerSettings;
|
|
@ -1,45 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import type { Page } from "@backend/types";
|
|
||||||
import { navigate } from "@backend/events";
|
|
||||||
|
|
||||||
import "@css/widgets/SideBarButton.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
anchor: Page;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SideBarButton extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects the user to the specified anchor.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private redirect(): void {
|
|
||||||
navigate(this.props.anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this component should be showed.
|
|
||||||
*/
|
|
||||||
private shouldShow(): boolean {
|
|
||||||
return !((window as any).hide as string[]).includes(this.props.anchor.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.shouldShow() ? (
|
|
||||||
<div className={"SideBarButton"} onClick={() => this.redirect()}>
|
|
||||||
<img className={"SideBarButton_Icon"} src={this.props.icon} alt={this.props.name} />
|
|
||||||
|
|
||||||
<p className={"SideBarButton_Label"}>{this.props.name}</p>
|
|
||||||
</div>
|
|
||||||
) : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SideBarButton;
|
|
@ -1,37 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { IoLocationSharp } from "react-icons/io5";
|
|
||||||
|
|
||||||
import type { Quest } from "@backend/types";
|
|
||||||
|
|
||||||
import "@css/widgets/quest/NormalQuest.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
quest: Quest;
|
|
||||||
right?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NormalQuest extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { quest } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"NormalQuest"} datatype={this.props.right ? "right" : "left"}>
|
|
||||||
<div className={"NormalQuest_Info"}>
|
|
||||||
<p className={"font-bold"}>{quest.description}</p>
|
|
||||||
<p>
|
|
||||||
ID: {quest.id} | Main: {quest.mainId}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IoLocationSharp className={"NormalQuest_Icon"} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NormalQuest;
|
|
@ -1,52 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { GiSupersonicArrow } from "react-icons/gi";
|
|
||||||
|
|
||||||
import Collapsible from "react-collapsible";
|
|
||||||
import NormalQuest from "@widgets/quest/NormalQuest";
|
|
||||||
|
|
||||||
import type { MainQuest } from "@backend/types";
|
|
||||||
import { listSubQuestsFor } from "@backend/data";
|
|
||||||
|
|
||||||
import "@css/widgets/quest/PrimaryQuest.scss";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
quest: MainQuest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Trigger(props: IProps): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className={"Trigger"}>
|
|
||||||
<GiSupersonicArrow className={"Trigger_Icon"} />
|
|
||||||
<div className={"Trigger_Info"}>
|
|
||||||
<p className={"font-bold"}>{props.quest.title}</p>
|
|
||||||
<p>ID: {props.quest.id}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrimaryQuest extends React.PureComponent<IProps> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Collapsible
|
|
||||||
className={"PrimaryQuest"}
|
|
||||||
openedClassName={"PrimaryQuest"}
|
|
||||||
trigger={<Trigger quest={this.props.quest} />}
|
|
||||||
transitionTime={50}
|
|
||||||
>
|
|
||||||
<div className={"PrimaryQuest_List"}>
|
|
||||||
{listSubQuestsFor(this.props.quest).map((quest) => (
|
|
||||||
<NormalQuest key={quest.id} quest={quest} right />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Collapsible>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PrimaryQuest;
|
|
@ -1,181 +0,0 @@
|
|||||||
import type { Entity, Item, EntityInfo, ItemInfo, WindowDetails } from "@backend/types";
|
|
||||||
import { ItemType, 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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a value is between two numbers.
|
|
||||||
*
|
|
||||||
* @param value The value to check.
|
|
||||||
* @param min The minimum value.
|
|
||||||
* @param max The maximum value.
|
|
||||||
*/
|
|
||||||
export function inRange(value: number, min: number, max: number): boolean {
|
|
||||||
return value >= min && value <= max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the path to the icon for an item.
|
|
||||||
* Uses the Project Amber API to get the icon.
|
|
||||||
*
|
|
||||||
* @param item The item to get the icon for.
|
|
||||||
*/
|
|
||||||
export function itemIcon(item: Item): string {
|
|
||||||
// Check if the item matches a special case.
|
|
||||||
if (inRange(item.id, 1001, 1099)) {
|
|
||||||
return `https://paimon.moe/images/characters/${formatAvatarName(item.name, item.id)}.png`;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (item.type) {
|
|
||||||
default:
|
|
||||||
return `https://api.ambr.top/assets/UI/UI_${item.icon}.png`;
|
|
||||||
case ItemType.Furniture:
|
|
||||||
return `https://api.ambr.top/assets/UI/furniture/UI_${item.icon}.png`;
|
|
||||||
case ItemType.Reliquary:
|
|
||||||
return `https://api.ambr.top/assets/UI/reliquary/UI_${item.icon}.png`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the path to the icon for an entity.
|
|
||||||
* Uses the Project Amber API to get the icon.
|
|
||||||
*
|
|
||||||
* @param entity The entity to get the icon for. Project Amber data required.
|
|
||||||
*/
|
|
||||||
export function entityIcon(entity: Entity): string {
|
|
||||||
return `https://api.ambr.top/assets/UI/monster/UI_MonsterIcon_${entity.internal}.png`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a character's name to fit with the reference name.
|
|
||||||
* Example: Hu Tao -> hu_tao
|
|
||||||
*
|
|
||||||
* @param name The character's name.
|
|
||||||
* @param id The character's ID.
|
|
||||||
*/
|
|
||||||
export function formatAvatarName(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 refSwitch: { [key: number]: string } = {
|
|
||||||
10000005: "traveler_anemo",
|
|
||||||
10000007: "traveler_geo"
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the route for an item type.
|
|
||||||
*
|
|
||||||
* @param type The type of the item.
|
|
||||||
*/
|
|
||||||
export function typeToRoute(type: ItemType): string {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
return "material";
|
|
||||||
case ItemType.Furniture:
|
|
||||||
return "furniture";
|
|
||||||
case ItemType.Reliquary:
|
|
||||||
return "reliquary";
|
|
||||||
case ItemType.Weapon:
|
|
||||||
return "weapon";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the data for an item.
|
|
||||||
* Uses the Project Amber API to get the data.
|
|
||||||
*
|
|
||||||
* @route GET https://api.ambr.top/v2/EN/{type}/{id}
|
|
||||||
* @param item The item to fetch the data for.
|
|
||||||
*/
|
|
||||||
export async function fetchItemData(item: Item): Promise<ItemInfo> {
|
|
||||||
let url = `https://api.ambr.top/v2/EN/(type)/(id)`;
|
|
||||||
|
|
||||||
// Replace the type and ID in the URL.
|
|
||||||
url = url.replace("(type)", typeToRoute(item.type));
|
|
||||||
url = url.replace("(id)", item.id.toString());
|
|
||||||
|
|
||||||
// Fetch the data.
|
|
||||||
return fetch(url)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the data for an entity.
|
|
||||||
* Uses the Project Amber API to get the data.
|
|
||||||
*
|
|
||||||
* @route GET https://api.ambr.top/v2/en/monster/{id}
|
|
||||||
* @param entity The entity to fetch the data for.
|
|
||||||
*/
|
|
||||||
export async function fetchEntityData(entity: Entity): Promise<EntityInfo> {
|
|
||||||
return fetch(`https://api.ambr.top/v2/en/monster/${entity.id}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to copy text to the clipboard.
|
|
||||||
* Uses the Clipboard API.
|
|
||||||
*
|
|
||||||
* @param text The text to copy.
|
|
||||||
*/
|
|
||||||
export async function copyToClipboard(text: string): Promise<void> {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a URL in a new tab.
|
|
||||||
* Uses the window.open() method.
|
|
||||||
*
|
|
||||||
* @param url The URL to open.
|
|
||||||
*/
|
|
||||||
export function openUrl(url: string): void {
|
|
||||||
window.open(url, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a value is NaN.
|
|
||||||
* Returns an empty string if it is.
|
|
||||||
*
|
|
||||||
* @param value The value to check.
|
|
||||||
*/
|
|
||||||
export function notNaN(value: number | string): string {
|
|
||||||
const number = parseInt(value.toString());
|
|
||||||
return isNaN(number) ? "" : number.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the server details out of the window.
|
|
||||||
*/
|
|
||||||
export function getWindowDetails(): WindowDetails {
|
|
||||||
const details = (window as any).details;
|
|
||||||
const { address, port, disable } = details;
|
|
||||||
|
|
||||||
return {
|
|
||||||
address: (address as string).includes("DETAILS_ADDRESS") ? "127.0.0.1" : address,
|
|
||||||
port: (port as string).includes("DETAILS_PORT") ? 443 : parseInt(port),
|
|
||||||
disable: (disable as string).includes("DETAILS_DISABLE") ? false : disable == "true"
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"allowJs": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@app/*": ["src/*"],
|
|
||||||
"@backend/*": ["src/backend/*"],
|
|
||||||
"@css/*": ["src/css/*"],
|
|
||||||
"@ui/*": ["src/ui/*"],
|
|
||||||
"@icons/*": ["src/icons/*"],
|
|
||||||
"@views/*": ["src/ui/views/*"],
|
|
||||||
"@pages/*": ["src/ui/pages/*"],
|
|
||||||
"@widgets/*": ["src/ui/widgets/*"],
|
|
||||||
"@components/*": ["src/ui/components/*"],
|
|
||||||
"@data/*": ["data/*"],
|
|
||||||
"@assets/*": ["data/assets/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"vite.config.ts"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
|
|
||||||
import { defineConfig } from "vite";
|
|
||||||
|
|
||||||
import react from "@vitejs/plugin-react-swc";
|
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
|
||||||
|
|
||||||
import dsv from "@rollup/plugin-dsv";
|
|
||||||
import viteSvgr from "vite-plugin-svgr";
|
|
||||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
||||||
|
|
||||||
import postcss from "./cfg/postcss.config.js";
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [ react(), tsconfigPaths(), dsv(),
|
|
||||||
viteSvgr(), viteSingleFile() ],
|
|
||||||
css: { postcss },
|
|
||||||
|
|
||||||
optimizeDeps: {
|
|
||||||
exclude: [
|
|
||||||
"react-virtualization"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
@ -21,11 +21,16 @@ import org.java_websocket.client.WebSocketClient;
|
|||||||
import org.java_websocket.handshake.ServerHandshake;
|
import org.java_websocket.handshake.ServerHandshake;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple implementation of a server-to-server IPC client.
|
||||||
|
* It is implemented over WebSockets, and supports all Grasscutter versions past 1.6.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
public final class DispatchClient extends WebSocketClient implements IDispatcher {
|
public final class DispatchClient extends WebSocketClient implements IDispatcher {
|
||||||
@Getter private final Logger logger = Grasscutter.getLogger();
|
private final Logger logger = Grasscutter.getLogger();
|
||||||
@Getter private final Map<Integer, BiConsumer<WebSocket, JsonElement>> handlers = new HashMap<>();
|
private final Map<Integer, BiConsumer<WebSocket, JsonElement>> handlers = new HashMap<>();
|
||||||
|
|
||||||
@Getter private final Map<Integer, List<Consumer<JsonElement>>> callbacks = new HashMap<>();
|
private final Map<Integer, List<Consumer<JsonElement>>> callbacks = new HashMap<>();
|
||||||
|
|
||||||
public DispatchClient(URI serverUri) {
|
public DispatchClient(URI serverUri) {
|
||||||
super(serverUri);
|
super(serverUri);
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<title>Handbook Authentication</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script type="application/javascript">
|
|
||||||
if ("{{VALUE}}" === "true") {
|
|
||||||
parent.postMessage({
|
|
||||||
type: "handbook-auth",
|
|
||||||
token: "{{SESSION_TOKEN}}",
|
|
||||||
uid: "{{PLAYER_ID}}"
|
|
||||||
}, "*");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<p>Input your Player UID here.</p>
|
|
||||||
<form method="post">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
name="playerid"
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="submit" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue
Block a user