Compare commits
60 Commits
develop
...
feat/game(
Author | SHA1 | Date | |
---|---|---|---|
cbebfa86e7 | |||
2b9a2720bc | |||
1b14be445e | |||
341f0fae65 | |||
40b9f29e03 | |||
28ac1597c9 | |||
a2180bed42 | |||
f6bd3e8ab7 | |||
636ebe8099 | |||
8d9bf58019 | |||
87e13f71f5 | |||
5b86ebbf7e | |||
656ce45d44 | |||
90720c34f9 | |||
d26b429ee8 | |||
6ba06e09d2 | |||
ea7c2cbf78 | |||
fbf1a5dc7b | |||
a25d278115 | |||
868e675f8e | |||
1206af1e1d | |||
2b2a5483e5 | |||
7badc5a83b | |||
069a37b550 | |||
cb66593b2c | |||
4f6b2045f2 | |||
51ba8d4fcb | |||
b26459feeb | |||
a946baf1d7 | |||
2909cbe3c4 | |||
1919787c11 | |||
052ad1c6ab | |||
63d9921dd5 | |||
22ecfdd9a5 | |||
6e253823ba | |||
4b9290636f | |||
2771e7b4cc | |||
1272b00014 | |||
3ae150f448 | |||
508e999a0c | |||
7d5b2cbc89 | |||
bdcf825de7 | |||
01adee763f | |||
19637f44e7 | |||
bb0c6f32f8 | |||
195bb2c360 | |||
ef9c094e5d | |||
90ee57c67d | |||
deb1ca5ad9 | |||
9652ec9122 | |||
32dfc41589 | |||
4b4f58a426 | |||
b8dcbc4f5d | |||
c058b1d1e7 | |||
36ea248c9f | |||
9a1feb829c | |||
243b9f3d36 | |||
a4f1ed4694 | |||
0315f32a25 | |||
8dadf20a64 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,8 +13,6 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
*.suo
|
||||||
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -1,3 +1,9 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
"recommendations": [
|
||||||
|
"tauri-apps.tauri-vscode",
|
||||||
|
"rust-lang.rust-analyzer",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"DavidAnson.vscode-markdownlint",
|
||||||
|
"solidjs-community.solid-snippets"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||||
|
"tailwindCSS.classAttributes": ["class", "className"]
|
||||||
|
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Rypî Dev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# 🧿 Getting started
|
||||||
|
|
||||||
|
## 🎯 Todos
|
||||||
|
|
||||||
|
- [ ] Two options for downloading Habbo resources:
|
||||||
|
- [ ] Default data extraction. (Without conversion)
|
||||||
|
- [ ] Using [Scuti](https://scuti.netlify.app/) dataset for its [renderer.](https://github.com/kozennnn/scuti-renderer)
|
||||||
|
- [x] Languages feature. (Default: EN)
|
||||||
|
- [ ] Download Habbo assets. (>800 MB of disk memory, make sure the IPC handles it correctly)
|
||||||
|
- [x] Fix rendering animation issues.
|
||||||
|
- [ ] Add "abort" button with data suppression feature.
|
||||||
|
- [x] Convert front-end with SolidJS
|
||||||
|
- [x] Fix issue of prettier-plugin-tailwindcss
|
||||||
|
|
||||||
|
- [ ] Export helpers/utils methods into Rust:
|
||||||
|
|
||||||
|
- [ ] Implementing typesafety with types' collections using Tauri Specta
|
||||||
|
- [ ] Handling extracted data in:
|
||||||
|
- [ ] JSON (Default + Adjustements for Scuti)
|
||||||
|
- [ ] XML (Using quickxml_to_serde)
|
||||||
|
- [ ] TXT (no idea...)
|
||||||
|
- [ ] Parsing data using `std::{fs::File, io::Write}`
|
||||||
|
|
||||||
|
- [ ] Learning Rust:
|
||||||
|
|
||||||
|
- [ ] Getting to know about vectors in depth.
|
||||||
|
|
||||||
|
- [ ] Avoid conflicts in open-source.
|
||||||
|
- [ ] Undertsand: Memory cache and disk cache.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- [How it works](#🏗️-how-it-works)
|
||||||
|
- [Techs stack](#📦-techs-stack)
|
||||||
|
- [Interesting topics](#📤-interesting-topics)
|
||||||
|
- [Credits](#🤝-credits)
|
||||||
|
|
||||||
|
## 📦 Techs stack
|
||||||
|
|
||||||
|
![Rust lang](https://img.shields.io/badge/Rust-black?style=for-the-badge&logo=rust&logoColor=#E57324)
|
||||||
|
![Typescript lang](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
|
||||||
|
|
||||||
|
![Tauri](https://img.shields.io/badge/tauri-%2324C8DB.svg?style=for-the-badge&logo=tauri&logoColor=%23FFFFFF)
|
||||||
|
![Vite](https://img.shields.io/badge/Vite-B73BFE?style=for-the-badge&logo=vite&logoColor=FFD62E)
|
||||||
|
![SolidJS](https://img.shields.io/badge/SolidJS-2c4f7c?style=for-the-badge&logo=solid&logoColor=c8c9cb)
|
||||||
|
![Tailwind framework](https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white)
|
||||||
|
|
||||||
|
![Eslint linter](https://img.shields.io/badge/eslint-3A33D1?style=for-the-badge&logo=eslint&logoColor=white)
|
||||||
|
![Prettier formatter](https://img.shields.io/badge/prettier-1A2C34?style=for-the-badge&logo=prettier&logoColor=F7BA3E)
|
||||||
|
|
||||||
|
## 🏗️ How it works
|
||||||
|
|
||||||
|
### Gamedata/Generic
|
||||||
|
|
||||||
|
It comes first with fetching data using the `@tauri-apps/api/http` module.
|
||||||
|
Converting uncoming data into minified JSON file formats.
|
||||||
|
(No need for avro, parquet, protobuf nor CSV for efficient data compression
|
||||||
|
and encoding schemas for fast data storing/retrieval)
|
||||||
|
|
||||||
|
## 📤 Interesting topics
|
||||||
|
|
||||||
|
### 🦀 Rust-related
|
||||||
|
|
||||||
|
- [Why serde_json crate is the best choice?](https://blog.logrocket.com/json-and-rust-why-serde_json-is-the-top-choice/)
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx cloc . \
|
||||||
|
--exclude-dir=target,_site,node_modules \
|
||||||
|
--not-match-f=pnpm-lock.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Language files blank comment code
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
TypeScript 43 158 19 855
|
||||||
|
JSON 6 0 0 138
|
||||||
|
Rust 5 39 81 83
|
||||||
|
Markdown 1 19 0 45
|
||||||
|
CSS 2 4 0 30
|
||||||
|
TOML 1 5 2 27
|
||||||
|
SVG 1 0 0 24
|
||||||
|
JavaScript 3 0 1 23
|
||||||
|
HTML 1 2 0 11
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
SUM: 63 227 103 1236
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤝 Credits
|
||||||
|
|
||||||
|
This wouldn't be possible without [KOZEN](https://github.com/kozennnn)'s help and bare bones the
|
@ -8,6 +8,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
53
package.json
53
package.json
@ -2,49 +2,48 @@
|
|||||||
"name": "@rypidev/rypi-scrapper",
|
"name": "@rypidev/rypi-scrapper",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "",
|
"description": "Scrapping Habbo gamedata and assets",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite dev",
|
||||||
"build": "tsc && vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"dev:tauri": "vite --config vite.config.tauri.ts",
|
||||||
"tauri": "tauri"
|
"build:tauri": "vite build --config vite.config.tauri.ts",
|
||||||
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/press-start-2p": "^4.5.11",
|
"@motionone/solid": "10.16.0",
|
||||||
"@tauri-apps/api": "^1.2.0",
|
"@tauri-apps/api": "^1.2.0",
|
||||||
|
"@tauri-apps/cli": "^1.2.3",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"fast-xml-parser": "4.1.3",
|
"flag-icons": "^6.6.6",
|
||||||
"framer-motion": "^10.10.0",
|
"solid-icons": "1.0.4",
|
||||||
"react": "^18.2.0",
|
"solid-js": "^1.7.4"
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-feather": "^2.0.10"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.2.2",
|
"@types/node": "^18.16.3",
|
||||||
"@types/node": "^18.7.10",
|
|
||||||
"@types/react": "^18.0.15",
|
|
||||||
"@types/react-dom": "^18.0.6",
|
|
||||||
"@vitejs/plugin-react": "^3.0.0",
|
|
||||||
"@walidoux/eslint-config": "1.0.3",
|
"@walidoux/eslint-config": "1.0.3",
|
||||||
"@walidoux/prettier-config": "1.0.2",
|
"@walidoux/prettier-config": "1.0.3",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"eslint": "8.36.0",
|
"cssnano": "6.0.1",
|
||||||
"postcss": "8.4.21",
|
"cssnano-preset-advanced": "6.0.1",
|
||||||
"prettier": "2.8.7",
|
"eslint": "8.39.0",
|
||||||
"prettier-plugin-tailwindcss": "0.2.6",
|
"postcss": "8.4.23",
|
||||||
"tailwindcss": "3.3.1",
|
"prettier": "2.8.8",
|
||||||
"typescript": "^4.7.4",
|
"prettier-plugin-tailwindcss": "0.2.8",
|
||||||
"vite": "^4.0.0"
|
"tailwindcss": "3.3.2",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"vite": "^4.3.4",
|
||||||
|
"vite-plugin-solid": "^2.7.0",
|
||||||
|
"vite-plugin-tauri": "3.1.0"
|
||||||
},
|
},
|
||||||
"prettier": "@walidoux/prettier-config",
|
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"@walidoux/eslint-config"
|
"@walidoux/eslint-config"
|
||||||
],
|
],
|
||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
"**/*.config.js",
|
"**/*.config.cjs"
|
||||||
"**/*.config.ts"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2143
pnpm-lock.yaml
generated
2143
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
7
postcss.config.cjs
Normal file
7
postcss.config.cjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
...(!process.env.TAURI_DEBUG ? { cssnano: { preset: 'advanced' } } : {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {}
|
|
||||||
}
|
|
||||||
}
|
|
4
prettier.config.cjs
Normal file
4
prettier.config.cjs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
...require("@walidoux/prettier-config"),
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
};
|
BIN
public/PressStart2P-Regular.ttf
Normal file
BIN
public/PressStart2P-Regular.ttf
Normal file
Binary file not shown.
BIN
public/icons/cross.png
Executable file
BIN
public/icons/cross.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 373 B |
527
src-tauri/Cargo.lock
generated
527
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rypi-scrapper"
|
name = "app"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "A Tauri App"
|
description = "Scrapping Habbo gamedata and assets"
|
||||||
authors = ["you"]
|
authors = ["Walidoux"]
|
||||||
license = ""
|
license = "MIT"
|
||||||
repository = ""
|
repository = "https://github.com/RypiDev/rypi-scrapper"
|
||||||
|
default-run = "app"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
build = "src/build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,11 +15,20 @@ edition = "2021"
|
|||||||
tauri-build = { version = "1.2", features = [] }
|
tauri-build = { version = "1.2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1.2", features = ["fs-create-dir", "fs-exists", "fs-read-dir", "fs-write-file", "http-all", "shell-all", "window-close", "window-maximize", "window-minimize", "window-unmaximize"] }
|
tauri = { version = "1.2", features = ["fs-create-dir", "fs-exists", "fs-read-dir", "fs-remove-dir", "fs-write-file", "http-all", "path-all", "window-close", "window-minimize", "window-start-dragging"] }
|
||||||
|
tauri-specta = { version = "1.0.0", features = ["typescript"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
specta = "1.0.3"
|
||||||
|
quickxml_to_serde = "0.5.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
# DO NOT REMOVE!!
|
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true # Automatically strip symbols from the binary
|
||||||
|
panic = "abort" # Strip expensive panic clean-up logic
|
||||||
|
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||||
|
lto = true # Enables link to optimizations
|
||||||
|
opt-level = "s" # Optimize for binary size
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
tauri_build::build()
|
|
||||||
}
|
|
1
src-tauri/data/draworder.json
Normal file
1
src-tauri/data/draworder.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"std":[["li","lh","ls","lc","ri","bd","sh","lg","ch","cc","cp","wa","ca","rh","rs","rc","hd","fc","ey","hr","hrb","fa","ea","ha","he"],["li","lh","ls","lc","bd","sh","lg","ch","cc","cp","wa","ca","ri","rh","rs","rc","hd","fc","ey","hr","hrb","fa","ea","ha","he"],["li","lh","ls","lc","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc"],["bd","sh","lg","ch","cc","cp","wa","ca","li","lh","ls","lc","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc"],["bd","sh","lg","ch","cc","cp","wa","ca","lh","ls","lc","li","hd","fc","ey","hr","hrb","fa","ea","ha","he","rh","rs","rc","ri"],["rh","rs","rc","ri","bd","sh","lg","ch","cc","cp","wa","ca","li","lh","ls","lc","hd","fc","ey","hr","hrb","fa","ea","ha","he"],["bd","sh","lg","ch","cc","cp","wa","ca","li","lh","ls","lc","hd","fc","ey","hr","hrb","fa","ea","ha","he","rh","rs","rc","ri"],["li","lh","ls","lc","ri","rh","rs","rc","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he"]],"lh-up":{"4":["rh","rs","rc","bd","sh","lg","ch","cc","cp","wa","ri","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","lh","ls","lc","li"],"5":["rh","rs","rc","ri","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","li","lh","ls","lc"],"6":["rh","rs","rc","ri","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","li","lh","ls","lc"]},"rh-up":[["li","lh","ls","lc","ri","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","rh","rs","rc"],["li","lh","ls","lc","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc"],["li","lh","ls","lc","bd","sh","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc"],["bd","sh","lg","ch","cc","cp","wa","li","lh","ls","lc","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc"]],"sit":{"2":["li","lh","ls","lc","bd","lg","ch","cc","cp","wa","ca","rh","rs","rc","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","sh"],"3":["bd","lg","ch","cc","cp","wa","ca","li","lh","ls","lc","rh","rs","rc","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","sh"],"4":["rh","rs","rc","bd","lg","ch","cc","cp","wa","ca","lh","ls","lc","li","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","sh"]},"sit.lh-up":{"4":["rh","rs","rc","bd","lg","ch","cc","cp","wa","ri","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","lh","ls","lc","li","sh"]},"sit.rh-up":{"2":["li","lh","ls","lc","bd","lg","ch","cc","cp","wa","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc","sh"],"3":["bd","lg","ch","cc","cp","wa","li","lh","ls","lc","ca","hd","fc","ey","hr","hrb","fa","ea","ha","he","ri","rh","rs","rc","sh"]},"lay":{"2":["lh","ls","lc","li","bd","lg","sh","ch","cc","cp","hd","fc","ey","wa","ri","rh","rs","rc","ca","hr","hrb","fa","ea","ha","he"],"4":["rh","rs","rc","ri","bd","lg","sh","ch","cc","cp","hd","fc","ey","wa","li","lh","ls","lc","ca","hr","hrb","fa","ea","ha","he"]}}
|
5
src-tauri/src/build.rs
Normal file
5
src-tauri/src/build.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
use tauri_build::build;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
build()
|
||||||
|
}
|
112
src-tauri/src/commands.rs
Normal file
112
src-tauri/src/commands.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use quickxml_to_serde::{xml_string_to_json, Config, NullValue};
|
||||||
|
use specta::specta;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
structs::{ConvertTypes, GamedataEndpoints},
|
||||||
|
utils::convert_json,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[specta]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn download_gamedata(data: &str, endpoint: GamedataEndpoints) {
|
||||||
|
match endpoint.convert {
|
||||||
|
ConvertTypes::XML => {
|
||||||
|
let conf = Config::new_with_custom_values(true, "", "txt", NullValue::Null);
|
||||||
|
let json = xml_string_to_json(data.to_owned(), &conf);
|
||||||
|
|
||||||
|
println!("{}", json.expect("Malformed XML").to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertTypes::TXT => println!("TXT CONVERTION"),
|
||||||
|
ConvertTypes::JSON => convert_json(data, endpoint.file_name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[specta]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn parse_data(
|
||||||
|
path: &str,
|
||||||
|
file_name: &str,
|
||||||
|
file_content: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let file_dir = PathBuf::from(&path).join(format!("{}.json", file_name));
|
||||||
|
|
||||||
|
println!("{:?}", file_dir);
|
||||||
|
|
||||||
|
// By default, output files will be overwritten. I cannot recursively remove the entire output folder
|
||||||
|
// and create it again because it just won't parse files' contents for some reason
|
||||||
|
/* if !exists(&Convertion::gamedata_dir()) {
|
||||||
|
create_dir_all(&Convertion::gamedata_dir())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file(&file_dir, file_content.as_bytes()); */
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pub async fn convert_txt(path: &str, data: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut badges: Vec<Badge> = Vec::new();
|
||||||
|
|
||||||
|
let lines: Vec<&str> = data.split('\n').collect();
|
||||||
|
|
||||||
|
let badge_pattern = Regex::new(r"^badge_(?:name|desc)_([^=]+)").unwrap();
|
||||||
|
let description_pattern = Regex::new(r"^badge_desc_(\s*\w+)").unwrap();
|
||||||
|
let name_pattern = Regex::new(r"^badge_name_(\s*\w+)").unwrap();
|
||||||
|
|
||||||
|
for (index, line) in lines.iter().enumerate() {
|
||||||
|
let parts: Vec<&str> = line.split('=').collect();
|
||||||
|
|
||||||
|
if parts.len() < 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = parts[0];
|
||||||
|
let value = parts[1];
|
||||||
|
|
||||||
|
if badge_pattern.is_match(key) {
|
||||||
|
if name_pattern.is_match(key) {
|
||||||
|
let badge_code = name_pattern.captures(key).unwrap()[1].to_owned();
|
||||||
|
let existing_badge = badges.iter_mut().find(|badge| badge.code == badge_code);
|
||||||
|
|
||||||
|
if let Some(badge) = existing_badge {
|
||||||
|
badge.name = Some(value.to_owned());
|
||||||
|
} else {
|
||||||
|
badges.push(Badge {
|
||||||
|
code: badge_code,
|
||||||
|
name: Some(value.to_owned()),
|
||||||
|
description: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if description_pattern.is_match(key) {
|
||||||
|
let badge_code = description_pattern.captures(key).unwrap()[1].to_owned();
|
||||||
|
let existing_badge = badges.iter_mut().find(|badge| badge.code == badge_code);
|
||||||
|
|
||||||
|
if let Some(badge) = existing_badge {
|
||||||
|
badge.description = Some(value.to_owned());
|
||||||
|
} else {
|
||||||
|
badges.push(Badge {
|
||||||
|
code: badge_code,
|
||||||
|
name: None,
|
||||||
|
description: Some(value.to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the line from the vector
|
||||||
|
lines[index] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty lines
|
||||||
|
let lines: Vec<&str> = lines
|
||||||
|
.iter()
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
parse_habbo_data(path, "Badges", &badges);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} */
|
@ -1,8 +1,28 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
pub mod commands;
|
||||||
|
pub mod structs;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use commands::download_gamedata;
|
||||||
|
use specta::collect_types;
|
||||||
|
use tauri_specta::ts;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
ts::export(collect_types![download_gamedata], "../src/tools/rusty.ts").unwrap();
|
||||||
|
|
||||||
|
// This is useful for custom eslint, prettier overrides at the top of the file.
|
||||||
|
/* ts::export_with_cfg_with_header(
|
||||||
|
collect_types![download_gamedata].unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
"../src/tools/rusty.ts",
|
||||||
|
"/* eslint-disable */
|
||||||
|
\n".into(),
|
||||||
|
)
|
||||||
|
.unwrap(); */
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.invoke_handler(tauri::generate_handler![download_gamedata])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
70
src-tauri/src/structs.rs
Normal file
70
src-tauri/src/structs.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
|
|
||||||
|
/* pub struct Badge {
|
||||||
|
pub code: String,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
} */
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub enum ConvertTypes {
|
||||||
|
TXT,
|
||||||
|
XML,
|
||||||
|
JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub enum Converters {
|
||||||
|
FigureData,
|
||||||
|
FigureMap,
|
||||||
|
EffectMap,
|
||||||
|
FurniData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct GamedataEndpoints {
|
||||||
|
pub src: String,
|
||||||
|
pub convert: ConvertTypes,
|
||||||
|
pub file_name: Converters,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct FurniData {
|
||||||
|
pub floor_items: Vec<IFloorItem>,
|
||||||
|
pub wall_items: Vec<IFurni>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct IFloorItem {
|
||||||
|
dimensions: IFloorItemDimensions,
|
||||||
|
permissions: IFloorItemPermissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct IFloorItemDimensions {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
default_direction: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct IFloorItemPermissions {
|
||||||
|
can_sit_on: bool,
|
||||||
|
can_lay_on: bool,
|
||||||
|
can_stand_on: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
|
pub struct IFurni {
|
||||||
|
id: i32,
|
||||||
|
classname: String,
|
||||||
|
description: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
furni_line: Option<String>,
|
||||||
|
custom_params: Option<String>,
|
||||||
|
adurl: Option<String>,
|
||||||
|
offer_id: Option<i32>,
|
||||||
|
exclude_dynamic: bool,
|
||||||
|
special_type: i32,
|
||||||
|
}
|
54
src-tauri/src/utils.rs
Normal file
54
src-tauri/src/utils.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::structs::{Converters, FurniData, IFloorItem, IFurni};
|
||||||
|
|
||||||
|
pub fn debug_typeof<T>(_: &T) {
|
||||||
|
println!("{}", std::any::type_name::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_json(data: &str, file_name: Converters) {
|
||||||
|
let object: Value = serde_json::from_str(data).unwrap();
|
||||||
|
|
||||||
|
match file_name {
|
||||||
|
Converters::FurniData => {
|
||||||
|
let mut data = FurniData {
|
||||||
|
floor_items: Vec::new(),
|
||||||
|
wall_items: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let floor_items_result: Result<_, Vec<IFloorItem>> =
|
||||||
|
Ok(serde_json::from_value::<Vec<IFloorItem>>(
|
||||||
|
object["wallitemtypes"]["furnitype"].clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let wall_items_result: Result<_, Vec<IFurni>> = Ok(
|
||||||
|
serde_json::from_value::<Vec<IFurni>>(object["roomitemtypes"]["furnitype"].clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle the floor_items_result and wall_items_result appropriately
|
||||||
|
match (floor_items_result, wall_items_result) {
|
||||||
|
(Ok(floor_items), Ok(wall_items)) => {
|
||||||
|
while let Ok(ref value) = wall_items {
|
||||||
|
println!("{:?}", value);
|
||||||
|
}
|
||||||
|
while let Ok(ref value) = floor_items {
|
||||||
|
println!("{:?}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Err(e), _) => {
|
||||||
|
println!("Error deserializing floor items: {:?}", e);
|
||||||
|
}
|
||||||
|
(_, Err(e)) => {
|
||||||
|
println!("Error deserializing wall items: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop through each key value pairs (make sure your handle null exceptions)
|
||||||
|
// parse the data to the global variable above
|
||||||
|
//
|
||||||
|
}
|
||||||
|
Converters::FigureData => {}
|
||||||
|
Converters::FigureMap => {}
|
||||||
|
Converters::EffectMap => {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,5 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"beforeDevCommand": "yarn dev",
|
|
||||||
"beforeBuildCommand": "yarn build",
|
|
||||||
"devPath": "http://localhost:1420",
|
|
||||||
"distDir": "../dist",
|
|
||||||
"withGlobalTauri": false
|
|
||||||
},
|
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "rypi-scrapper",
|
"productName": "rypi-scrapper",
|
||||||
"version": "0.0.0"
|
"version": "0.0.0"
|
||||||
@ -15,8 +9,7 @@
|
|||||||
"window": {
|
"window": {
|
||||||
"close": true,
|
"close": true,
|
||||||
"minimize": true,
|
"minimize": true,
|
||||||
"maximize": true,
|
"startDragging": true
|
||||||
"unmaximize": true
|
|
||||||
},
|
},
|
||||||
"os": {
|
"os": {
|
||||||
"all": false
|
"all": false
|
||||||
@ -25,21 +18,12 @@
|
|||||||
"createDir": true,
|
"createDir": true,
|
||||||
"exists": true,
|
"exists": true,
|
||||||
"readDir": true,
|
"readDir": true,
|
||||||
|
"removeDir": true,
|
||||||
"writeFile": true,
|
"writeFile": true,
|
||||||
"scope": ["$DOWNLOAD/**/*"]
|
"scope": ["$DOWNLOAD/**/*"]
|
||||||
},
|
},
|
||||||
"shell": {
|
"path": {
|
||||||
"all": true,
|
"all": true
|
||||||
"execute": true,
|
|
||||||
"sidecar": true,
|
|
||||||
"open": true,
|
|
||||||
"scope": [
|
|
||||||
{
|
|
||||||
"name": "download-habbo-downloader",
|
|
||||||
"cmd": "npm",
|
|
||||||
"args": ["install", "-g", "habbo-downloader"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"all": true,
|
"all": true,
|
||||||
@ -49,18 +33,12 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"icon": [
|
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||||
"icons/32x32.png",
|
"identifier": "org.rypi.dev",
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"identifier": "com.tauri.dev",
|
|
||||||
"targets": "all"
|
"targets": "all"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false
|
"active": false
|
||||||
@ -68,10 +46,10 @@
|
|||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"decorations": false,
|
"decorations": false,
|
||||||
|
"resizable": false,
|
||||||
"center": true,
|
"center": true,
|
||||||
"resizable": true,
|
"width": 1000,
|
||||||
"width": 800,
|
"height": 575
|
||||||
"height": 500
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
54
src/App.tsx
54
src/App.tsx
@ -1,52 +1,20 @@
|
|||||||
import { useState } from 'react'
|
import type { Component } from 'solid-js'
|
||||||
|
|
||||||
import { Image, Window, Button, Downloader } from './components'
|
import { Downloaders, TitleBar, LangDropdown } from './components/layout'
|
||||||
import { fetchGameData } from './utils/fetchGameData'
|
|
||||||
import {
|
|
||||||
GameAssetsDownloader,
|
|
||||||
GameDataDownloader
|
|
||||||
} from './config/GameDownloader'
|
|
||||||
|
|
||||||
const Main: React.FC = () => {
|
|
||||||
const [error, setError] = useState('')
|
|
||||||
const [response, setResponse] = useState('')
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
|
|
||||||
const callback = (message: string, error = false): void => {
|
|
||||||
if (error) return setError(message)
|
|
||||||
if (message === 'COMPLETED') setLoading(false)
|
|
||||||
else setResponse(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const Main: Component = () => {
|
||||||
return (
|
return (
|
||||||
<Window>
|
<>
|
||||||
<span className='mb-20 text-white'>I would like to:</span>
|
<TitleBar />
|
||||||
|
|
||||||
<ul className='flex gap-x-8'>
|
<main class='relative flex h-full w-screen flex-col items-center justify-center bg-[#242424] py-20 text-white'>
|
||||||
<Downloader content={GameDataDownloader}>
|
<span class='mb-20'>I would like to:</span>
|
||||||
<Image src='/images/Gamedata.png' className='w-[400px]' />
|
|
||||||
|
|
||||||
<Button
|
<LangDropdown />
|
||||||
value='Download Gamedata'
|
|
||||||
icon={<Image src='/icons/game.png' size={22} />}
|
|
||||||
className='download-button border-gamedata-secondary bg-gamedata-primary shadow-gamedata-primary/20'
|
|
||||||
handler={async () => {
|
|
||||||
return await fetchGameData('com', callback)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Downloader>
|
|
||||||
|
|
||||||
<Downloader content={GameAssetsDownloader}>
|
<Downloaders />
|
||||||
<Image src='/images/GameAssets.png' className='w-[400px]' />
|
</main>
|
||||||
|
</>
|
||||||
<Button
|
|
||||||
value='Download GameAssets'
|
|
||||||
icon={<Image src='/icons/picture.png' icon />}
|
|
||||||
className='download-button border-gameAssets-secondary bg-gameAssets-primary shadow-gameAssets-primary/40'
|
|
||||||
/>
|
|
||||||
</Downloader>
|
|
||||||
</ul>
|
|
||||||
</Window>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
||||||
icon: JSX.Element
|
|
||||||
value: string
|
|
||||||
className?: string
|
|
||||||
handler?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Button: React.FC<ButtonProps> = ({
|
|
||||||
icon,
|
|
||||||
value,
|
|
||||||
className,
|
|
||||||
handler
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={handler}
|
|
||||||
className={classNames(
|
|
||||||
className,
|
|
||||||
'flex items-center justify-center gap-x-5'
|
|
||||||
)}>
|
|
||||||
{icon}
|
|
||||||
<span className='uppercase'>{value}</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
export interface IDownloaderContent {
|
|
||||||
title: string
|
|
||||||
features: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DownloaderProps {
|
|
||||||
className?: string
|
|
||||||
children: React.ReactNode
|
|
||||||
content: IDownloaderContent
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Downloader: React.FC<DownloaderProps> = ({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
content
|
|
||||||
}) => {
|
|
||||||
const [activeContent, setActiveContent] = useState(false)
|
|
||||||
|
|
||||||
const handleActiveContent = (): void => {
|
|
||||||
return setActiveContent(!activeContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={classNames(
|
|
||||||
className,
|
|
||||||
'relative rounded-xl shadow-red-500 hover:shadow-2xl'
|
|
||||||
)}
|
|
||||||
onMouseEnter={handleActiveContent}
|
|
||||||
onMouseLeave={handleActiveContent}>
|
|
||||||
{children}
|
|
||||||
|
|
||||||
<AnimatePresence>
|
|
||||||
{activeContent && (
|
|
||||||
<motion.ul
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1, transition: { staggerChildren: 0.5 } }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className='pointer-events-none absolute left-0 top-0 flex h-full w-full flex-col items-center justify-center rounded-xl bg-black/70 pb-5 text-[12px] text-white'>
|
|
||||||
<motion.h1
|
|
||||||
initial={{ opacity: 0, y: -20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: -20 }}
|
|
||||||
className='mb-3 text-[16px]'>
|
|
||||||
{content.title}
|
|
||||||
</motion.h1>
|
|
||||||
|
|
||||||
{content.features.map((feature) => {
|
|
||||||
return (
|
|
||||||
<motion.span
|
|
||||||
initial={{ opacity: 0, y: 50, scale: 0.75 }}
|
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
||||||
exit={{ opacity: 0, y: 50, scale: 0.75 }}
|
|
||||||
key={feature}>
|
|
||||||
{feature}
|
|
||||||
</motion.span>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</motion.ul>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
interface ImageProps extends React.ComponentPropsWithoutRef<'img'> {
|
|
||||||
size?: number
|
|
||||||
icon?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Image: React.FC<ImageProps> = ({ size, icon, ...rest }) => {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
{...rest}
|
|
||||||
draggable={false}
|
|
||||||
style={{ imageRendering: Boolean(icon) ? 'pixelated' : 'unset' }}
|
|
||||||
className={classNames(rest.className, 'select-none')}
|
|
||||||
height={size ?? rest.height}
|
|
||||||
width={size ?? rest.width}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Maximize, Minus, X } from 'react-feather'
|
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
|
||||||
|
|
||||||
import { Image } from '../Image'
|
|
||||||
|
|
||||||
export const TitleBar: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<nav className='flex h-[37.5px] items-center justify-between bg-[#1f1f1f] text-white'>
|
|
||||||
<Image src='/Logo.svg' size={60} className='ml-3 p-1' />
|
|
||||||
|
|
||||||
<ul className='flex h-full'>
|
|
||||||
<li
|
|
||||||
onClick={async () => {
|
|
||||||
return await appWindow.minimize()
|
|
||||||
}}
|
|
||||||
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-[#2a2a2a]'>
|
|
||||||
<Minus />
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
onClick={async () => {
|
|
||||||
return (await appWindow.isMaximized())
|
|
||||||
? await appWindow.unmaximize()
|
|
||||||
: await appWindow.maximize()
|
|
||||||
}}
|
|
||||||
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-[#2a2a2a]'>
|
|
||||||
<Maximize size={20} />
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
onClick={async () => {
|
|
||||||
return await appWindow.close()
|
|
||||||
}}
|
|
||||||
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-red-500'>
|
|
||||||
<X />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { Fragment } from 'react'
|
|
||||||
|
|
||||||
import { TitleBar } from '../TitleBar/TitleBar'
|
|
||||||
|
|
||||||
interface WindowProps {
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Window: React.FC<WindowProps> = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<TitleBar />
|
|
||||||
|
|
||||||
<main className='flex h-screen w-screen flex-col items-center justify-center overflow-hidden bg-[#242424] p-10'>
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './Window'
|
|
22
src/components/design/AnimateView/AnimateView.tsx
Normal file
22
src/components/design/AnimateView/AnimateView.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Show } from 'solid-js'
|
||||||
|
import type { Component } from 'solid-js'
|
||||||
|
import type { MotionComponentProps, Variant } from '@motionone/solid'
|
||||||
|
import { Motion, Presence } from '@motionone/solid'
|
||||||
|
|
||||||
|
export interface AnimateViewProps extends MotionComponentProps {
|
||||||
|
animation?: Record<string, Variant>
|
||||||
|
condition: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AnimateView: Component<AnimateViewProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<Presence>
|
||||||
|
<Show when={props.condition}>
|
||||||
|
<Motion.div class={props.class} {...props.animation}>
|
||||||
|
{props.children}
|
||||||
|
</Motion.div>
|
||||||
|
</Show>
|
||||||
|
</Presence>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/design/AnimateView/index.ts
Normal file
1
src/components/design/AnimateView/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './AnimateView'
|
18
src/components/design/Button/Button.tsx
Normal file
18
src/components/design/Button/Button.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import type { Component, ComponentProps, JSXElement } from 'solid-js'
|
||||||
|
|
||||||
|
interface ButtonProps extends ComponentProps<'button'> {
|
||||||
|
icon: JSXElement
|
||||||
|
value: string
|
||||||
|
class?: string
|
||||||
|
handler?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: Component<ButtonProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<button onClick={props.handler} class={classNames(props.class, 'flex items-center justify-center gap-x-5')}>
|
||||||
|
{props.icon}
|
||||||
|
<span class='uppercase'>{props.value}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
22
src/components/design/Flag/Flag.tsx
Normal file
22
src/components/design/Flag/Flag.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import type { Component } from 'solid-js'
|
||||||
|
|
||||||
|
import { SUPPORTED_LANGS } from '../../../config'
|
||||||
|
|
||||||
|
interface FlagProps {
|
||||||
|
label?: boolean
|
||||||
|
domain: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Flag: Component<FlagProps> = (props) => {
|
||||||
|
const lang = SUPPORTED_LANGS.filter((lang) => {
|
||||||
|
return lang.domain === props.domain
|
||||||
|
})[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span class={classNames('', `fi fi-${lang.code}`)} />
|
||||||
|
{Boolean(props.label) && <span class='ml-3'>{lang.name}</span>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/design/Flag/index.ts
Normal file
1
src/components/design/Flag/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Flag'
|
25
src/components/design/Image/Image.tsx
Normal file
25
src/components/design/Image/Image.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import type { Component } from 'solid-js'
|
||||||
|
|
||||||
|
interface ImageProps {
|
||||||
|
size?: number
|
||||||
|
pixelated?: boolean
|
||||||
|
src: string
|
||||||
|
class?: string
|
||||||
|
height?: number
|
||||||
|
width?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Image: Component<ImageProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...props}
|
||||||
|
elementtiming=''
|
||||||
|
fetchpriority='auto'
|
||||||
|
style={{ 'image-rendering': Boolean(props.pixelated) ? 'pixelated' : 'unset' }}
|
||||||
|
class={classNames(props.class, 'select-none')}
|
||||||
|
height={props.size ?? props.height}
|
||||||
|
width={props.size ?? props.width}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
67
src/components/design/Loader/Loader.tsx
Normal file
67
src/components/design/Loader/Loader.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import type { Component } from 'solid-js'
|
||||||
|
|
||||||
|
import { Animation } from '../../../config'
|
||||||
|
import { AnimateView } from '../AnimateView'
|
||||||
|
|
||||||
|
interface LoaderProps {
|
||||||
|
class?: string
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Loader: Component<LoaderProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<AnimateView
|
||||||
|
class={props.class}
|
||||||
|
condition={props.active}
|
||||||
|
variants={Animation.fadeInOut({ scale: [0, 1, 0], y: [1, 4, 1] })}>
|
||||||
|
<svg width='44' height='44' viewBox='0 0 44 44' xmlns='http://www.w3.org/2000/svg' stroke='#fff'>
|
||||||
|
<g fill='none' fill-rule='evenodd' stroke-width={2}>
|
||||||
|
<circle cx='22' cy='22' r='1'>
|
||||||
|
<animate
|
||||||
|
attributeName='r'
|
||||||
|
begin='0s'
|
||||||
|
dur='1.8s'
|
||||||
|
values='1; 20'
|
||||||
|
calcMode='spline'
|
||||||
|
keyTimes='0; 1'
|
||||||
|
keySplines='0.165, 0.84, 0.44, 1'
|
||||||
|
repeatCount='indefinite'
|
||||||
|
/>
|
||||||
|
<animate
|
||||||
|
attributeName='stroke-opacity'
|
||||||
|
begin='0s'
|
||||||
|
dur='1.8s'
|
||||||
|
values='1; 0'
|
||||||
|
calcMode='spline'
|
||||||
|
keyTimes='0; 1'
|
||||||
|
keySplines='0.3, 0.61, 0.355, 1'
|
||||||
|
repeatCount='indefinite'
|
||||||
|
/>
|
||||||
|
</circle>
|
||||||
|
<circle cx='22' cy='22' r='1'>
|
||||||
|
<animate
|
||||||
|
attributeName='r'
|
||||||
|
begin='-0.9s'
|
||||||
|
dur='1.8s'
|
||||||
|
values='1; 20'
|
||||||
|
calcMode='spline'
|
||||||
|
keyTimes='0; 1'
|
||||||
|
keySplines='0.165, 0.84, 0.44, 1'
|
||||||
|
repeatCount='indefinite'
|
||||||
|
/>
|
||||||
|
<animate
|
||||||
|
attributeName='stroke-opacity'
|
||||||
|
begin='-0.9s'
|
||||||
|
dur='1.8s'
|
||||||
|
values='1; 0'
|
||||||
|
calcMode='spline'
|
||||||
|
keyTimes='0; 1'
|
||||||
|
keySplines='0.3, 0.61, 0.355, 1'
|
||||||
|
repeatCount='indefinite'
|
||||||
|
/>
|
||||||
|
</circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</AnimateView>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/design/Loader/index.ts
Normal file
1
src/components/design/Loader/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Loader'
|
5
src/components/design/index.ts
Normal file
5
src/components/design/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './Loader'
|
||||||
|
export * from './AnimateView'
|
||||||
|
export * from './Image'
|
||||||
|
export * from './Button'
|
||||||
|
export * from './Flag'
|
@ -1,4 +0,0 @@
|
|||||||
export * from './Window'
|
|
||||||
export * from './Image'
|
|
||||||
export * from './Button'
|
|
||||||
export * from './Downloader'
|
|
48
src/components/layout/Downloaders/Downloader/Downloader.tsx
Normal file
48
src/components/layout/Downloaders/Downloader/Downloader.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Motion } from '@motionone/solid'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import type { Component, JSXElement } from 'solid-js'
|
||||||
|
import { For, createSignal } from 'solid-js'
|
||||||
|
|
||||||
|
import type { GameDataDownloader } from '../../../../config'
|
||||||
|
import { Animation } from '../../../../config'
|
||||||
|
import { AnimateView } from '../../../design'
|
||||||
|
|
||||||
|
interface DownloaderProps {
|
||||||
|
className?: string
|
||||||
|
children: JSXElement
|
||||||
|
content: typeof GameDataDownloader
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Downloader: Component<DownloaderProps> = (props) => {
|
||||||
|
const [activeContent, setActiveContent] = createSignal(false)
|
||||||
|
|
||||||
|
const handleActiveContent = (): boolean => {
|
||||||
|
return setActiveContent(!activeContent())
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
class={classNames(props.className, 'relative rounded-xl shadow-red-500 hover:shadow-2xl')}
|
||||||
|
onMouseEnter={handleActiveContent}
|
||||||
|
onMouseLeave={handleActiveContent}>
|
||||||
|
{props.children}
|
||||||
|
|
||||||
|
<AnimateView
|
||||||
|
animation={Animation.fadeInOut()}
|
||||||
|
condition={activeContent()}
|
||||||
|
class='pointer-events-none absolute left-0 top-0 flex h-full w-full flex-col items-center justify-center rounded-xl bg-black/70 pb-5 text-center text-[12px] text-white'>
|
||||||
|
<Motion.h1 {...Animation.fadeInOut({ y: [-20, 0, -20] })} class='mb-3 text-[16px]'>
|
||||||
|
{props.content.title}
|
||||||
|
</Motion.h1>
|
||||||
|
|
||||||
|
<For each={props.content.features}>
|
||||||
|
{(feature) => {
|
||||||
|
return (
|
||||||
|
<Motion.span {...Animation.fadeInOut({ y: [50, 0, 50], scale: [0.75, 1, 0.75] })}>{feature}</Motion.span>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</AnimateView>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
96
src/components/layout/Downloaders/Downloaders.tsx
Normal file
96
src/components/layout/Downloaders/Downloaders.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import type { Component } from 'solid-js'
|
||||||
|
import { createSignal } from 'solid-js'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import type { ConvertionHandler } from '../../../types'
|
||||||
|
import { handleConvertion } from '../../../tools/handleConvertion'
|
||||||
|
import { GameAssetsDownloader, GameDataDownloader } from '../../../config'
|
||||||
|
import { Button, Image, Loader } from '../../design'
|
||||||
|
import { Popup } from '../Popup'
|
||||||
|
import { Downloader } from './Downloader/Downloader'
|
||||||
|
|
||||||
|
export const Downloaders: Component = () => {
|
||||||
|
const [message, setMessage] = createSignal('')
|
||||||
|
const [error, setError] = createSignal(false)
|
||||||
|
const [popup, setPopup] = createSignal(false)
|
||||||
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const callback: ConvertionHandler = (message, state = 'idle') => {
|
||||||
|
switch (state) {
|
||||||
|
case 'loading':
|
||||||
|
setMessage(message)
|
||||||
|
return setLoading(true)
|
||||||
|
case 'success':
|
||||||
|
setMessage(message)
|
||||||
|
return setLoading(false)
|
||||||
|
case 'error':
|
||||||
|
setMessage(message)
|
||||||
|
setError(true)
|
||||||
|
return setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadGameData = async (): Promise<void> => {
|
||||||
|
setPopup(true)
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
const startTime = new Date()
|
||||||
|
await handleConvertion('com', callback)
|
||||||
|
|
||||||
|
const endTime = new Date()
|
||||||
|
const seconds = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(2)
|
||||||
|
|
||||||
|
return callback(`Completed in: ${seconds} seconds`, 'success')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popup condition={popup()}>
|
||||||
|
<span class={classNames('', { 'text-red-600': error })}>{message()}</span>
|
||||||
|
|
||||||
|
<Loader active={loading()} class='mt-10' />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
value='Abort'
|
||||||
|
icon={<Image src='/icons/cross.png' />}
|
||||||
|
class='mt-6 bg-red-600 p-2 px-4 active:opacity-40'
|
||||||
|
handler={() => {}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
value='Close'
|
||||||
|
icon={<Image src='/icons/cross.png' />}
|
||||||
|
class={classNames('invisible mt-6 bg-red-600 p-2 px-4 opacity-0 active:opacity-40', {
|
||||||
|
'!visible !opacity-100': !loading()
|
||||||
|
})}
|
||||||
|
handler={() => {
|
||||||
|
return setPopup(!popup())
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
<ul class='flex gap-x-8'>
|
||||||
|
<Downloader content={GameDataDownloader}>
|
||||||
|
<Image src='/images/Gamedata.png' />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
value='Download Gamedata'
|
||||||
|
icon={<Image src='/icons/game.png' size={22} />}
|
||||||
|
class='download-button border-gamedata-secondary bg-gamedata-primary shadow-gamedata-primary/20'
|
||||||
|
handler={downloadGameData}
|
||||||
|
/>
|
||||||
|
</Downloader>
|
||||||
|
|
||||||
|
<Downloader content={GameAssetsDownloader}>
|
||||||
|
<Image src='/images/GameAssets.png' />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
value='Download GameAssets'
|
||||||
|
icon={<Image src='/icons/picture.png' pixelated />}
|
||||||
|
class='download-button border-gameAssets-secondary bg-gameAssets-primary shadow-gameAssets-primary/40'
|
||||||
|
/>
|
||||||
|
</Downloader>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/layout/Downloaders/index.ts
Normal file
1
src/components/layout/Downloaders/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Downloaders'
|
51
src/components/layout/LangDropdown/LangDropdown.tsx
Normal file
51
src/components/layout/LangDropdown/LangDropdown.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { Component } from 'solid-js'
|
||||||
|
import { createSignal, For } from 'solid-js'
|
||||||
|
import { Motion } from '@motionone/solid'
|
||||||
|
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { AnimateView, Flag } from '../../design'
|
||||||
|
import { OutSideEventHandler } from '../OutSideEventHandler'
|
||||||
|
import { Animation, SUPPORTED_LANGS } from '../../../config'
|
||||||
|
|
||||||
|
export const LangDropdown: Component = () => {
|
||||||
|
const { lang, setLang } = useLocalStorage()
|
||||||
|
|
||||||
|
let ref: HTMLDivElement | undefined
|
||||||
|
const [active, setActive] = createSignal(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OutSideEventHandler
|
||||||
|
class='relative text-sm'
|
||||||
|
onOutsideClick={() => {
|
||||||
|
return setActive(false)
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
class='cursor-pointer'
|
||||||
|
onclick={() => {
|
||||||
|
return setActive(!active())
|
||||||
|
}}>
|
||||||
|
Selected Lang: <Flag domain={lang()} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimateView condition={active()} class='absolute top-8 z-20' animation={Animation.fadeInOut()}>
|
||||||
|
<For each={SUPPORTED_LANGS}>
|
||||||
|
{(lang, index) => {
|
||||||
|
return (
|
||||||
|
<Motion.p
|
||||||
|
{...Animation.fadeInOut({ x: [50 / (index() + 1), 0, 0] })}
|
||||||
|
transition={{ delay: (index() + 1) / 15 }}
|
||||||
|
class='cursor-pointer'
|
||||||
|
onclick={() => {
|
||||||
|
setActive(false)
|
||||||
|
return setLang(lang.domain)
|
||||||
|
}}>
|
||||||
|
<Flag domain={lang.domain} label />
|
||||||
|
</Motion.p>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</AnimateView>
|
||||||
|
</OutSideEventHandler>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/layout/LangDropdown/index.ts
Normal file
1
src/components/layout/LangDropdown/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './LangDropdown'
|
@ -0,0 +1,35 @@
|
|||||||
|
import type { Component, ComponentProps } from 'solid-js'
|
||||||
|
import { onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
|
interface OutSideEventHandlerProps extends ComponentProps<'div'> {
|
||||||
|
onOutsideClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OutSideEventHandler: Component<OutSideEventHandlerProps> = (props) => {
|
||||||
|
let ref: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent | KeyboardEvent): void => {
|
||||||
|
const currentEvent = event as KeyboardEvent
|
||||||
|
|
||||||
|
if (ref == null) return
|
||||||
|
if (ref.contains(event.target as Node) || currentEvent.key !== 'Escape') return
|
||||||
|
|
||||||
|
return props.onOutsideClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener('keydown', handleClickOutside)
|
||||||
|
return window.addEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
window.removeEventListener('keydown', handleClickOutside)
|
||||||
|
return window.removeEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/layout/OutSideEventHandler/index.ts
Normal file
1
src/components/layout/OutSideEventHandler/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './OutSideEventHandler'
|
20
src/components/layout/Popup/Popup.tsx
Normal file
20
src/components/layout/Popup/Popup.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { Component, JSXElement } from 'solid-js'
|
||||||
|
|
||||||
|
import { AnimateView } from '../../design'
|
||||||
|
import { Animation } from '../../../config'
|
||||||
|
|
||||||
|
interface PopupProps {
|
||||||
|
condition: boolean
|
||||||
|
children: JSXElement
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Popup: Component<PopupProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<AnimateView
|
||||||
|
animation={Animation.fadeInOut()}
|
||||||
|
class='absolute left-0 top-0 z-20 flex h-screen w-screen flex-col items-center justify-center bg-black/40'
|
||||||
|
condition={props.condition}>
|
||||||
|
{props.children}
|
||||||
|
</AnimateView>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/layout/Popup/index.ts
Normal file
1
src/components/layout/Popup/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Popup'
|
41
src/components/layout/TitleBar/TitleBar.tsx
Normal file
41
src/components/layout/TitleBar/TitleBar.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { FiMaximize, FiMinus, FiX } from 'solid-icons/fi'
|
||||||
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
|
import type { Component } from 'solid-js'
|
||||||
|
|
||||||
|
import { Image } from '../../design'
|
||||||
|
|
||||||
|
export const TitleBar: Component = () => {
|
||||||
|
return (
|
||||||
|
<nav class='!z-50 flex h-[40px] w-full items-center justify-between bg-[#1f1f1f] text-white'>
|
||||||
|
<div
|
||||||
|
class='w-full select-none'
|
||||||
|
onMouseDown={async () => {
|
||||||
|
return await appWindow.startDragging()
|
||||||
|
}}>
|
||||||
|
<Image src='/Logo.svg' size={60} class='ml-3 p-1' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class='flex h-full'>
|
||||||
|
<li
|
||||||
|
class='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-[#2a2a2a]'
|
||||||
|
onClick={async () => {
|
||||||
|
return await appWindow.minimize()
|
||||||
|
}}>
|
||||||
|
<FiMinus />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class='grid h-full w-14 cursor-not-allowed place-items-center opacity-40'>
|
||||||
|
<FiMaximize size={20} />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-red-500'
|
||||||
|
onClick={async () => {
|
||||||
|
return await appWindow.close()
|
||||||
|
}}>
|
||||||
|
<FiX />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
5
src/components/layout/index.ts
Normal file
5
src/components/layout/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './Downloaders'
|
||||||
|
export * from './Popup'
|
||||||
|
export * from './TitleBar'
|
||||||
|
export * from './LangDropdown'
|
||||||
|
export * from './OutSideEventHandler'
|
23
src/config/Animation.ts
Normal file
23
src/config/Animation.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { Variant } from '@motionone/solid'
|
||||||
|
|
||||||
|
const fadeInOut = (variant?: Variant): Record<string, Variant> => {
|
||||||
|
const getVariantAtPosition = (pos: 0 | 1 | 2): Record<string, number> | null => {
|
||||||
|
if (variant == null) return null
|
||||||
|
|
||||||
|
const variantAtPos: Record<string, number> = {}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(variant)) {
|
||||||
|
variantAtPos[key] = (value as number[])[pos as keyof typeof value]
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantAtPos
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
initial: { opacity: 0, ...getVariantAtPosition(0) },
|
||||||
|
animate: { opacity: 1, ...getVariantAtPosition(1) },
|
||||||
|
exit: { opacity: 0, ...getVariantAtPosition(2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Animation = { fadeInOut }
|
10
src/config/Convertion.ts
Normal file
10
src/config/Convertion.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { downloadDir } from '@tauri-apps/api/path'
|
||||||
|
|
||||||
|
import { PROD_VERSION } from './Endpoints'
|
||||||
|
|
||||||
|
const outputDir = (await downloadDir()).concat(String(PROD_VERSION))
|
||||||
|
|
||||||
|
const gamedataDir = outputDir.concat('/gamedata')
|
||||||
|
const genericDir = outputDir.concat('/generic')
|
||||||
|
|
||||||
|
export const Convertion = { outputDir, gamedataDir, genericDir }
|
13
src/config/Domain.ts
Normal file
13
src/config/Domain.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// ISO 3166-1-alpha-2 Flags
|
||||||
|
|
||||||
|
export const SUPPORTED_LANGS = [
|
||||||
|
{ name: 'Portuguese', code: 'br', domain: 'com.br' },
|
||||||
|
{ name: 'Turkish', code: 'tr', domain: 'com.tr' },
|
||||||
|
{ name: 'English', code: 'us', domain: 'com' },
|
||||||
|
{ name: 'German', code: 'de', domain: 'de' },
|
||||||
|
{ name: 'Spanish', code: 'es', domain: 'es' },
|
||||||
|
{ name: 'Finnish', code: 'fi', domain: 'fi' },
|
||||||
|
{ name: 'French', code: 'fr', domain: 'fr' },
|
||||||
|
{ name: 'Italian', code: 'it', domain: 'it' },
|
||||||
|
{ name: 'Dutch', code: 'nl', domain: 'nl' }
|
||||||
|
]
|
@ -1,62 +0,0 @@
|
|||||||
import { getClient, ResponseType } from '@tauri-apps/api/http'
|
|
||||||
|
|
||||||
import type { DomainTypes, GameEndPointsTypes } from '../types'
|
|
||||||
|
|
||||||
const PROD_VERSION_REGEX = /(production-[^/]+)/im
|
|
||||||
const STABLE_PROD_VERSION = 'PRODUCTION-202303282207-162719871'
|
|
||||||
|
|
||||||
export let PROD_VERSION: string | undefined
|
|
||||||
|
|
||||||
const HABBO_URL = (domain: DomainTypes): string => {
|
|
||||||
return `https://www.habbo.${domain}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const HABBO_IMAGES = `https://images.habbo.com/gordon/${
|
|
||||||
PROD_VERSION ?? STABLE_PROD_VERSION
|
|
||||||
}`
|
|
||||||
|
|
||||||
export const client = await getClient()
|
|
||||||
await client
|
|
||||||
.get(`${HABBO_URL('com')}/gamedata/external_variables/0`, {
|
|
||||||
responseType: ResponseType.Text
|
|
||||||
})
|
|
||||||
.then(({ data }) => {
|
|
||||||
return (PROD_VERSION = (data as string).match(PROD_VERSION_REGEX)?.[0])
|
|
||||||
})
|
|
||||||
|
|
||||||
export const GAME_ENDPOINTS: GameEndPointsTypes = (domain) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
src: `${HABBO_URL(domain)}/gamedata/figuredata/0`,
|
|
||||||
convert: 'XML',
|
|
||||||
fileName: 'FigureData'
|
|
||||||
}
|
|
||||||
/* {
|
|
||||||
src: `${HABBO_IMAGES}/figuremap.xml`,
|
|
||||||
convert: 'XML',
|
|
||||||
fileName: 'FigureMap'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: `${HABBO_URL(domain)}/gamedata/furnidata_json/0`,
|
|
||||||
fileName: 'FurniData'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: `${HABBO_URL(domain)}/gamedata/productdata_json/0`,
|
|
||||||
fileName: 'ProductData'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: `${HABBO_IMAGES}/effectmap.xml`,
|
|
||||||
convert: 'XML',
|
|
||||||
fileName: 'EffectMap'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: `${HABBO_URL(domain)}/gamedata/external_variables/0`,
|
|
||||||
convert: 'TXT',
|
|
||||||
fileName: 'ExternalTexts'
|
|
||||||
} */
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ASSETS_ENDPOINTS = (domain: DomainTypes): string[] => {
|
|
||||||
return [`${HABBO_URL(domain)}/`]
|
|
||||||
}
|
|
39
src/config/Endpoints.ts
Normal file
39
src/config/Endpoints.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getClient, ResponseType } from '@tauri-apps/api/http'
|
||||||
|
|
||||||
|
import type { GamedataEndpoints } from '../tools/rusty'
|
||||||
|
import { useLocalStorage } from '../hooks/useLocalStorage'
|
||||||
|
|
||||||
|
const { lang } = useLocalStorage()
|
||||||
|
export const client = await getClient()
|
||||||
|
|
||||||
|
const PROD_VERSION_REGEX = /(production-[^/]+)/im
|
||||||
|
const STABLE_PROD_VERSION = 'PRODUCTION-202304181630-471782382'
|
||||||
|
export let PROD_VERSION: string | undefined
|
||||||
|
|
||||||
|
const HABBO_URL = (domain: string): string => {
|
||||||
|
return `https://www.habbo.${domain}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HABBO_GORDON_URL = `https://images.habbo.com/gordon/${PROD_VERSION ?? STABLE_PROD_VERSION}`
|
||||||
|
|
||||||
|
export const GAMEDATA_ENDPOINTS = async (domain: string): Promise<GamedataEndpoints[]> => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
src: `${HABBO_URL(domain)}/gamedata/furnidata_json/0`,
|
||||||
|
convert: 'JSON',
|
||||||
|
file_name: 'FurniData'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ASSETS_ENDPOINTS = (domain: string): string[] => {
|
||||||
|
return [`${HABBO_URL(domain)}/`]
|
||||||
|
}
|
||||||
|
|
||||||
|
await client
|
||||||
|
.get(`${HABBO_URL(lang())}/gamedata/external_variables/0`, {
|
||||||
|
responseType: ResponseType.Text
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
return (PROD_VERSION = (data as string).match(PROD_VERSION_REGEX)?.[0])
|
||||||
|
})
|
@ -1,17 +1,15 @@
|
|||||||
import type { IDownloaderContent } from '../components'
|
export const GameDataDownloader = {
|
||||||
|
|
||||||
export const GameDataDownloader: IDownloaderContent = {
|
|
||||||
title: 'Converts and bundles:',
|
title: 'Converts and bundles:',
|
||||||
features: ['XML/TXT to minified JSON files', 'Converts SWF files to JSV']
|
features: ['XML/TXT to minified JSON files', 'Converts SWF files to Sprite']
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GameAssetsDownloader: IDownloaderContent = {
|
export const GameAssetsDownloader = {
|
||||||
title: 'Fetches PNG/JPEG:',
|
title: 'Fetches PNG/JPEG:',
|
||||||
features: [
|
features: [
|
||||||
'Badges + Badgeparts',
|
'Badges + Badgeparts',
|
||||||
'Album + Recepetion images',
|
'Album + Recepetion images',
|
||||||
'Catalogue + Furni icons',
|
'Catalogue + Furni icons',
|
||||||
'Habbo Web Promo + Articles',
|
'Habbo Web Promo + Articles',
|
||||||
'MP3 Sounds machine'
|
'MP3 Sounds'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
5
src/config/index.ts
Normal file
5
src/config/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './Animation'
|
||||||
|
export * from './Convertion'
|
||||||
|
export * from './Domain'
|
||||||
|
export * from './Endpoints'
|
||||||
|
export * from './GameDownloader'
|
19
src/controllers/EffectMap.ts
Normal file
19
src/controllers/EffectMap.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { IEffectMap } from '../types'
|
||||||
|
|
||||||
|
export class EffectMap {
|
||||||
|
public data: IEffectMap = {}
|
||||||
|
public fileName: string
|
||||||
|
|
||||||
|
constructor(XML: any, fileName: string) {
|
||||||
|
this.fileName = fileName
|
||||||
|
|
||||||
|
this.parseLibrairies(XML.map.effect)
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseLibrairies(effects: any[]): void {
|
||||||
|
for (const libraryXML of effects) {
|
||||||
|
this.data[libraryXML.type] == null && (this.data[libraryXML.type] = {})
|
||||||
|
this.data[libraryXML.type][libraryXML.id] = libraryXML.lib
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
src/controllers/FigureData.ts
Normal file
78
src/controllers/FigureData.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import type { IFigureData, IFigureDataPalette, IFigureDataSet, IFigureDataSetType, IXML } from '../types'
|
||||||
|
|
||||||
|
export class FigureData {
|
||||||
|
public data: IFigureData = { palettes: [], setTypes: {} }
|
||||||
|
public fileName: string
|
||||||
|
|
||||||
|
constructor(XML: IXML, fileName: string) {
|
||||||
|
this.fileName = fileName
|
||||||
|
|
||||||
|
this.parsePalettes(XML.figuredata.colors.palette)
|
||||||
|
this.parseSetTypes(XML.figuredata.sets.settype)
|
||||||
|
}
|
||||||
|
|
||||||
|
private parsePalettes(palettes: any[]): void {
|
||||||
|
for (const paletteXML of palettes) {
|
||||||
|
const palette = {} as IFigureDataPalette
|
||||||
|
|
||||||
|
for (const colorXML of paletteXML.color) {
|
||||||
|
palette[Number(colorXML.id)] = {
|
||||||
|
index: Number(colorXML.index),
|
||||||
|
club: Number(colorXML.club),
|
||||||
|
selectable: Boolean(colorXML.selectable),
|
||||||
|
color: '#' + String(colorXML['#text'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.palettes.push(palette)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseSetTypes(setTypes: any[]): void {
|
||||||
|
for (const setTypeXML of setTypes) {
|
||||||
|
const settype: IFigureDataSetType = {
|
||||||
|
paletteId: Number(setTypeXML.paletteid),
|
||||||
|
mandatoryF0: Boolean(Number(setTypeXML.mand_f_0)),
|
||||||
|
mandatoryF1: Boolean(Number(setTypeXML.mand_f_1)),
|
||||||
|
mandatoryM0: Boolean(Number(setTypeXML.mand_m_0)),
|
||||||
|
mandatoryM1: Boolean(Number(setTypeXML.mand_m_1)),
|
||||||
|
sets: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const setXML of setTypeXML.set) {
|
||||||
|
const setType: IFigureDataSet = {
|
||||||
|
gender: setXML.gender,
|
||||||
|
club: Number(setXML.club),
|
||||||
|
colorable: Boolean(Number(setXML.colorable)),
|
||||||
|
selectable: Boolean(Number(setXML.selectable)),
|
||||||
|
preselectable: Boolean(Number(setXML.preselectable)),
|
||||||
|
sellable: setXML.sellable != null ? Boolean(Number(setXML.sellable)) : undefined,
|
||||||
|
parts: [],
|
||||||
|
hiddenLayers: []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const partXML of Array.isArray(setXML.part) ? setXML.part : [setXML.part]) {
|
||||||
|
setType.parts.push({
|
||||||
|
id: Number(partXML.id),
|
||||||
|
type: partXML.type,
|
||||||
|
colorable: Boolean(Number(partXML.colorable)),
|
||||||
|
index: Number(partXML.index),
|
||||||
|
colorindex: Number(partXML.colorindex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setXML.hiddenLayers != null) {
|
||||||
|
for (const hiddenLayerXML of Array.isArray(setXML.hiddenLayers)
|
||||||
|
? setXML.hiddenLayers
|
||||||
|
: [setXML.hiddenLayers]) {
|
||||||
|
setType.hiddenLayers?.push(hiddenLayerXML.partType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settype.sets[Number(setXML.id)] = setType
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.setTypes[String(setTypeXML.type)] = settype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/controllers/FigureMap.ts
Normal file
25
src/controllers/FigureMap.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { IFigureMap, IFigureMapLibrary, IXML } from '../types'
|
||||||
|
|
||||||
|
export class FigureMap {
|
||||||
|
public data: IFigureMap = { libraries: [], parts: {} }
|
||||||
|
public fileName: string
|
||||||
|
|
||||||
|
constructor(XML: IXML, fileName: string) {
|
||||||
|
this.fileName = fileName
|
||||||
|
|
||||||
|
this.parseLibrairies(XML.map.lib)
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseLibrairies(librairies: any[]): void {
|
||||||
|
for (const libraryXML of librairies) {
|
||||||
|
const library: IFigureMapLibrary = { id: libraryXML.id, revision: Number(libraryXML.revision) }
|
||||||
|
|
||||||
|
for (const libraryPart of Array.isArray(libraryXML.part) ? libraryXML.part : [libraryXML.part]) {
|
||||||
|
this.data.parts[libraryPart.type] == null && (this.data.parts[libraryPart.type] = {})
|
||||||
|
this.data.parts[libraryPart.type][Number(libraryPart.id)] = librairies.indexOf(libraryXML)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.libraries.push(library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/hooks/useLocalStorage.ts
Normal file
18
src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { Accessor } from 'solid-js'
|
||||||
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
|
export interface ILocalStorage {
|
||||||
|
lang: Accessor<string>
|
||||||
|
setLang: (domain: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const [lang, setLangeState] = createSignal<string>((localStorage.getItem('LANG_KEY') as string) ?? 'com')
|
||||||
|
|
||||||
|
const setLang: ILocalStorage['setLang'] = (domain) => {
|
||||||
|
localStorage.setItem('LANG_KEY', domain)
|
||||||
|
return setLangeState(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLocalStorage = (): ILocalStorage => {
|
||||||
|
return { lang, setLang }
|
||||||
|
}
|
11
src/index.tsx
Normal file
11
src/index.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { render } from 'solid-js/web'
|
||||||
|
|
||||||
|
import './styles/fonts.css'
|
||||||
|
import './styles/styles.css'
|
||||||
|
import 'flag-icons/css/flag-icons.min.css'
|
||||||
|
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
render(() => {
|
||||||
|
return <App />
|
||||||
|
}, document.getElementById('root') as HTMLElement)
|
12
src/main.tsx
12
src/main.tsx
@ -1,12 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { createRoot } from 'react-dom/client'
|
|
||||||
|
|
||||||
import './styles.css'
|
|
||||||
import '@fontsource/press-start-2p'
|
|
||||||
import App from './App'
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root') as HTMLElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
)
|
|
@ -1,3 +0,0 @@
|
|||||||
export const convertTXT = (data: string) => {
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
import type {
|
|
||||||
IEffectMap,
|
|
||||||
IEffectMapLibrary,
|
|
||||||
IFigureData,
|
|
||||||
IFigureDataColor,
|
|
||||||
IFigureDataHiddenLayer,
|
|
||||||
IFigureDataPalette,
|
|
||||||
IFigureDataPart,
|
|
||||||
IFigureDataSet,
|
|
||||||
IFigureDataSetType,
|
|
||||||
IFigureMap,
|
|
||||||
IFigureMapLibrary,
|
|
||||||
IFigureMapLibraryPart
|
|
||||||
} from '../types'
|
|
||||||
|
|
||||||
export const convertXML = (
|
|
||||||
XML: any,
|
|
||||||
url: string
|
|
||||||
): IFigureData | IFigureMap | IEffectMap => {
|
|
||||||
if (url.includes('figuredata')) {
|
|
||||||
const output: IFigureData = { palettes: [], setTypes: [] }
|
|
||||||
|
|
||||||
for (const paletteXML of XML.figuredata.colors.palette) {
|
|
||||||
const palette: IFigureDataPalette = { id: 0, color: [] }
|
|
||||||
|
|
||||||
if (paletteXML.id !== undefined) palette.id = paletteXML.id
|
|
||||||
|
|
||||||
if (paletteXML.color !== undefined) {
|
|
||||||
for (const colorXML of paletteXML.color) {
|
|
||||||
const color: IFigureDataColor = {
|
|
||||||
id: 0,
|
|
||||||
index: 0,
|
|
||||||
club: 0,
|
|
||||||
selectable: false,
|
|
||||||
hexCode: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const hexColor = String(colorXML['#text' as keyof IFigureDataColor])
|
|
||||||
|
|
||||||
if (colorXML.id !== undefined) color.id = colorXML.id
|
|
||||||
if (colorXML.index !== undefined) color.index = colorXML.index
|
|
||||||
if (colorXML.club !== undefined) color.club = colorXML.club
|
|
||||||
if (colorXML.selectable !== undefined)
|
|
||||||
color.selectable = colorXML.selectable === 0 ? true : false
|
|
||||||
if (hexColor !== undefined) color.hexCode = hexColor
|
|
||||||
|
|
||||||
palette.color.push(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.palettes.push(palette)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const setTypeXML of XML.figuredata.sets.settype) {
|
|
||||||
const settype: IFigureDataSetType = {
|
|
||||||
type: '',
|
|
||||||
paletteId: 0,
|
|
||||||
mandatoryF0: false,
|
|
||||||
mandatoryF1: false,
|
|
||||||
mandatoryM0: false,
|
|
||||||
mandatoryM1: false,
|
|
||||||
sets: []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setTypeXML.type !== undefined) settype.type = setTypeXML.type
|
|
||||||
if (setTypeXML.paletteId !== undefined)
|
|
||||||
settype.paletteId = setTypeXML.paletteId
|
|
||||||
if (setTypeXML.mandatoryF0 !== undefined)
|
|
||||||
settype.mandatoryF0 = setTypeXML.mandatoryF0
|
|
||||||
if (setTypeXML.mandatoryF1 !== undefined)
|
|
||||||
settype.mandatoryF1 = setTypeXML.mandatoryF1
|
|
||||||
if (setTypeXML.mandatoryM0 !== undefined)
|
|
||||||
settype.mandatoryM0 = setTypeXML.mandatoryM0
|
|
||||||
if (setTypeXML.mandatoryM1 !== undefined)
|
|
||||||
settype.mandatoryM1 = setTypeXML.mandatoryM1
|
|
||||||
|
|
||||||
if (setTypeXML.sets !== undefined) {
|
|
||||||
for (const setXML of setTypeXML.sets) {
|
|
||||||
const setType: IFigureDataSet = {
|
|
||||||
id: 0,
|
|
||||||
gender: 'U',
|
|
||||||
club: 0,
|
|
||||||
colorable: false,
|
|
||||||
selectable: false,
|
|
||||||
preselectable: false,
|
|
||||||
sellable: false,
|
|
||||||
parts: [],
|
|
||||||
hiddenLayers: []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setXML.id !== undefined) setType.id = setXML.id
|
|
||||||
if (setXML.gender !== undefined) setType.gender = setXML.gender
|
|
||||||
if (setXML.club !== undefined) setType.club = setXML.club
|
|
||||||
if (setXML.colorable !== undefined)
|
|
||||||
setType.colorable = setXML.colorable === 0 ? true : false
|
|
||||||
if (setXML.selectable !== undefined)
|
|
||||||
setType.selectable = setXML.selectable
|
|
||||||
if (setXML.preselectable !== undefined)
|
|
||||||
setType.preselectable = setXML.preselectable
|
|
||||||
if (setXML.sellable !== undefined) setType.sellable = setXML.sellable
|
|
||||||
|
|
||||||
if (setXML.parts !== undefined) {
|
|
||||||
for (const partXML of setXML.parts) {
|
|
||||||
const part: IFigureDataPart = {
|
|
||||||
id: 0,
|
|
||||||
type: '',
|
|
||||||
colorable: false,
|
|
||||||
index: 0,
|
|
||||||
colorindex: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partXML.id !== undefined) part.id = partXML.id
|
|
||||||
if (partXML.type !== undefined) part.type = partXML.type
|
|
||||||
if (partXML.colorable !== undefined)
|
|
||||||
part.colorable = partXML.colorable
|
|
||||||
if (partXML.index !== undefined) part.index = partXML.index
|
|
||||||
if (partXML.colorindex !== undefined)
|
|
||||||
part.colorindex = partXML.colorindex
|
|
||||||
|
|
||||||
setType.parts.push(part)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setXML.hiddenLayers !== undefined) {
|
|
||||||
for (const hiddenLayerXML of setXML.hiddenLayers) {
|
|
||||||
const hiddenLayer: IFigureDataHiddenLayer = { partType: '' }
|
|
||||||
|
|
||||||
if (hiddenLayerXML.partType !== undefined)
|
|
||||||
hiddenLayer.partType = hiddenLayerXML.partType
|
|
||||||
|
|
||||||
setType.hiddenLayers?.push(hiddenLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settype.sets.push(setType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.setTypes.push(setTypeXML)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
} else if (url.includes('figuremap')) {
|
|
||||||
const output: IFigureMap = { libraries: [] }
|
|
||||||
|
|
||||||
for (const libraryXML of XML.map.lib as IFigureMapLibrary[]) {
|
|
||||||
const library: IFigureMapLibrary = { id: '', revision: 0, part: [] }
|
|
||||||
|
|
||||||
if (libraryXML.id !== undefined) library.id = libraryXML.id
|
|
||||||
if (libraryXML.revision !== undefined)
|
|
||||||
library.revision = libraryXML.revision
|
|
||||||
|
|
||||||
if (Array.isArray(libraryXML.part)) {
|
|
||||||
for (const libraryPart of libraryXML.part) {
|
|
||||||
const libraryPartXML: IFigureMapLibraryPart = { id: 0, type: '' }
|
|
||||||
|
|
||||||
if (libraryPart.id !== undefined) libraryPartXML.id = libraryPart.id
|
|
||||||
if (libraryPart.type !== undefined)
|
|
||||||
libraryPartXML.type = libraryPart.type
|
|
||||||
|
|
||||||
library.part.push(libraryPartXML)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const libraryPart = libraryXML.part as unknown as IFigureMapLibraryPart
|
|
||||||
library.part.push({ id: libraryPart.id, type: libraryPart.type })
|
|
||||||
}
|
|
||||||
|
|
||||||
output.libraries.push(library)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
} else {
|
|
||||||
const output: IEffectMap = { effects: [] }
|
|
||||||
|
|
||||||
for (const libraryXML of XML.map.effect as IEffectMapLibrary[]) {
|
|
||||||
const library: IEffectMapLibrary = {
|
|
||||||
id: 0,
|
|
||||||
lib: '',
|
|
||||||
type: '',
|
|
||||||
revision: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (libraryXML.id !== undefined) library.id = libraryXML.id
|
|
||||||
if (libraryXML.lib !== undefined) library.lib = libraryXML.lib
|
|
||||||
if (libraryXML.type !== undefined) library.type = libraryXML.type
|
|
||||||
if (libraryXML.revision !== undefined)
|
|
||||||
library.revision = libraryXML.revision
|
|
||||||
|
|
||||||
output.effects.push(library)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './convertXML'
|
|
||||||
export * from './convertTXT'
|
|
13
src/styles/fonts.css
Normal file
13
src/styles/fonts.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./PressStart2P-Regular.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
@ -2,10 +2,6 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
#root {
|
|
||||||
font-family: 'Press Start 2P';
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.retro-text-shadow {
|
.retro-text-shadow {
|
||||||
text-shadow: 0px 1.5px 0px #000000, -1px 3px 0px rgba(0, 0, 0, 0.54);
|
text-shadow: 0px 1.5px 0px #000000, -1px 3px 0px rgba(0, 0, 0, 0.54);
|
||||||
@ -20,6 +16,6 @@
|
|||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.download-button {
|
.download-button {
|
||||||
@apply retro-text-shadow center-x-axis center-x-axis -bottom-4 z-10 h-12 w-10/12 border-2 text-xs text-white shadow-2xl transition-all hover:-translate-x-1/2 hover:scale-105 hover:duration-200 active:-translate-x-1/2 active:scale-95 active:opacity-80 active:shadow-none active:duration-75;
|
@apply retro-text-shadow center-x-axis center-x-axis -bottom-4 z-10 h-12 w-10/12 border-2 text-xs shadow-2xl transition-all hover:-translate-x-1/2 hover:scale-105 hover:duration-200 active:-translate-x-1/2 active:scale-95 active:opacity-80 active:shadow-none active:duration-75;
|
||||||
}
|
}
|
||||||
}
|
}
|
54
src/tools/convertTXT.ts
Normal file
54
src/tools/convertTXT.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { parseData } from './parseData'
|
||||||
|
|
||||||
|
interface IBadge {
|
||||||
|
code: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const badgePattern = /^badge_(?:name|desc)_([^=]+)/gim
|
||||||
|
const descriptionPattern = /^badge_desc_(\s*\w+)/
|
||||||
|
const namePattern = /^badge_name_(\s*\w+)/
|
||||||
|
|
||||||
|
export const convertTXT = async (path: string, data: string): Promise<void> => {
|
||||||
|
const badges: IBadge[] = []
|
||||||
|
|
||||||
|
const lines = data.split(/\r?\n/)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const [key, value] = line.split('=')
|
||||||
|
const badge = key.match(badgePattern)
|
||||||
|
|
||||||
|
if (badge != null) {
|
||||||
|
if (key.match(namePattern) != null) {
|
||||||
|
const badgeCode = key.match(namePattern)?.[1] as string
|
||||||
|
const existingBadge = badges.filter((badge) => {
|
||||||
|
return badge.code === badgeCode
|
||||||
|
})[0]
|
||||||
|
|
||||||
|
if (Boolean(existingBadge)) {
|
||||||
|
const index = badges.indexOf(existingBadge)
|
||||||
|
badges[index].name = value
|
||||||
|
} else {
|
||||||
|
badges.push({ code: badgeCode, name: value })
|
||||||
|
}
|
||||||
|
} else if (key.match(descriptionPattern) != null) {
|
||||||
|
const badgeCode = key.match(descriptionPattern)?.[1] as string
|
||||||
|
const existingBadge = badges.filter((badge) => {
|
||||||
|
return badge.code === badgeCode
|
||||||
|
})[0]
|
||||||
|
|
||||||
|
if (Boolean(existingBadge)) {
|
||||||
|
const index = badges.indexOf(existingBadge)
|
||||||
|
badges[index].description = value
|
||||||
|
} else {
|
||||||
|
badges.push({ code: badgeCode, description: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.splice(lines.indexOf(line), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await parseData(path, 'Badges', badges)
|
||||||
|
}
|
41
src/tools/fetchGamedata.ts
Normal file
41
src/tools/fetchGamedata.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { FigureMap } from '../controllers/FigureMap'
|
||||||
|
import { EffectMap } from '../controllers/EffectMap'
|
||||||
|
import { convertTXT } from './convertTXT'
|
||||||
|
import { FigureData } from '../controllers/FigureData'
|
||||||
|
import { FurniData } from '../controllers/FurniData'
|
||||||
|
import { Convertion } from '../config/Convertion'
|
||||||
|
import { parseData } from './parseData'
|
||||||
|
|
||||||
|
export const fetchGamedataConfig = async (data: string, endpoint: GamedataEndpoints): Promise<unknown> => {
|
||||||
|
switch (endpoint.convert) {
|
||||||
|
case 'XML':
|
||||||
|
const convertedData = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '' }).parse(data)
|
||||||
|
let parsedData: FigureData | FigureMap | EffectMap | undefined
|
||||||
|
|
||||||
|
if (endpoint.file_name === 'FigureData') {
|
||||||
|
parsedData = new FigureData(convertedData, endpoint.file_name)
|
||||||
|
} else if (endpoint.file_name === 'FigureMap') {
|
||||||
|
parsedData = new FigureMap(convertedData, endpoint.file_name)
|
||||||
|
} else if (endpoint.file_name === 'EffectMap') {
|
||||||
|
parsedData = new EffectMap(convertedData, endpoint.file_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await parseData(Convertion.gamedataDir, parsedData?.fileName, parsedData?.data).catch((error) => {
|
||||||
|
return console.error(error)
|
||||||
|
})
|
||||||
|
case 'TXT':
|
||||||
|
return await convertTXT(Convertion.gamedataDir, data)
|
||||||
|
|
||||||
|
default: {
|
||||||
|
let parsedData: FurniData | undefined
|
||||||
|
|
||||||
|
if (endpoint.file_name === 'FurniData') {
|
||||||
|
parsedData = new FurniData(JSON.parse(data), endpoint.file_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await parseData(Convertion.gamedataDir, parsedData?.fileName, parsedData?.data).catch((error) => {
|
||||||
|
return console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/tools/handleConvertion.ts
Normal file
47
src/tools/handleConvertion.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { ResponseType } from '@tauri-apps/api/http'
|
||||||
|
|
||||||
|
import { GAMEDATA_ENDPOINTS, client } from '../config/Endpoints'
|
||||||
|
import type { ConvertionHandler } from '../types'
|
||||||
|
import { parseData } from './parseData'
|
||||||
|
import { Convertion } from '../config/Convertion'
|
||||||
|
import { downloadGamedata } from './rusty'
|
||||||
|
|
||||||
|
export const handleConvertion = async (
|
||||||
|
domain: string,
|
||||||
|
callback: ConvertionHandler,
|
||||||
|
assetsOption = false
|
||||||
|
): Promise<void> => {
|
||||||
|
if (!assetsOption) {
|
||||||
|
callback('Initializing Gamedata configuration...', 'loading')
|
||||||
|
|
||||||
|
const gameData = await GAMEDATA_ENDPOINTS(domain)
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
gameData.map(async (endpoint) => {
|
||||||
|
if (endpoint.src.startsWith('http')) {
|
||||||
|
return await client
|
||||||
|
.get(endpoint.src, { responseType: ResponseType.Text })
|
||||||
|
.then(async ({ data }) => {
|
||||||
|
return await downloadGamedata(data as string, endpoint).catch((error) => {
|
||||||
|
return console.log(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return callback(error, 'error')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return await parseData(Convertion.gamedataDir, endpoint.file_name, endpoint.src)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// callback('Converting shockwave files...', 'loading')
|
||||||
|
|
||||||
|
// fetch, read and convert the files from the production folder in the user downloads' folder
|
||||||
|
// write the files into a seperate folder
|
||||||
|
} else {
|
||||||
|
/* ASSETS_ENDPOINTS(domain).map((endpoint) => {
|
||||||
|
client.get()
|
||||||
|
}) */
|
||||||
|
}
|
||||||
|
}
|
20
src/tools/parseData.ts
Normal file
20
src/tools/parseData.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createDir, exists, writeFile } from '@tauri-apps/api/fs'
|
||||||
|
|
||||||
|
import { Convertion } from '../config/Convertion'
|
||||||
|
|
||||||
|
export const parseData = async (
|
||||||
|
path: string,
|
||||||
|
fileName: string | undefined,
|
||||||
|
fileContent: string | object | undefined
|
||||||
|
): Promise<void> => {
|
||||||
|
if (fileName == null || fileContent == null) return
|
||||||
|
|
||||||
|
const fileDir = path.concat(`/${fileName}.json`)
|
||||||
|
|
||||||
|
// By default, output files will be overwritten. I cannot recursively remove the entire output folder
|
||||||
|
// and create it again because it just won't parse files' contents for some reason
|
||||||
|
|
||||||
|
if (!(await exists(Convertion.gamedataDir))) await createDir(Convertion.gamedataDir, { recursive: true })
|
||||||
|
|
||||||
|
return await writeFile(fileDir, typeof fileContent === 'object' ? JSON.stringify(fileContent) : fileContent)
|
||||||
|
}
|
17
src/tools/rusty.ts
Normal file
17
src/tools/rusty.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoke = window.__TAURI_INVOKE__;
|
||||||
|
|
||||||
|
export function downloadGamedata(data: string, endpoint: GamedataEndpoints) {
|
||||||
|
return invoke<null>("download_gamedata", { data,endpoint })
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Converters = "FigureData" | "FigureMap" | "EffectMap" | "FurniData"
|
||||||
|
export type ConvertTypes = "TXT" | "XML" | "JSON"
|
||||||
|
export type GamedataEndpoints = { src: string; convert: ConvertTypes; file_name: Converters }
|
21
src/types/Converters.d.ts
vendored
21
src/types/Converters.d.ts
vendored
@ -1,29 +1,16 @@
|
|||||||
import type {
|
import type { IFigureDataPalette, IFigureDataSetType, IFigureMapLibrary, IProduct } from './SubConverters'
|
||||||
IEffectMapLibrary,
|
|
||||||
IFigureDataPalette,
|
|
||||||
IFigureDataSetType,
|
|
||||||
IFigureMapLibrary,
|
|
||||||
IFurni,
|
|
||||||
IProduct
|
|
||||||
} from './SubConverters'
|
|
||||||
|
|
||||||
export interface IFigureData {
|
export interface IFigureData {
|
||||||
palettes: IFigureDataPalette[]
|
palettes: IFigureDataPalette[]
|
||||||
setTypes: IFigureDataSetType[]
|
setTypes: Record<string, IFigureDataSetType>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFigureMap {
|
export interface IFigureMap {
|
||||||
libraries: IFigureMapLibrary[]
|
libraries: IFigureMapLibrary[]
|
||||||
|
parts: Record<string, Record<number, number>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFurniData {
|
export type IEffectMap = Record<string, Record<string, string>>
|
||||||
roomitemtypes: { furnitype: IFurni }
|
|
||||||
wallitemtypes: { furnitype: IFurni }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEffectMap {
|
|
||||||
effects: IEffectMapLibrary[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IProductData {
|
export interface IProductData {
|
||||||
productData: { product: IProduct }
|
productData: { product: IProduct }
|
||||||
|
10
src/types/Domain.d.ts
vendored
10
src/types/Domain.d.ts
vendored
@ -1,10 +0,0 @@
|
|||||||
export type DomainTypes =
|
|
||||||
| 'com.br'
|
|
||||||
| 'com.tr'
|
|
||||||
| 'com'
|
|
||||||
| 'de'
|
|
||||||
| 'es'
|
|
||||||
| 'fi'
|
|
||||||
| 'fr'
|
|
||||||
| 'it'
|
|
||||||
| 'nl'
|
|
3
src/types/Endpoint.d.ts
vendored
3
src/types/Endpoint.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
export type GameEndPointsTypes = (
|
|
||||||
domain: DomainTypes
|
|
||||||
) => Array<{ src: string; convert?: 'TXT' | 'XML'; fileName: string }>
|
|
80
src/types/SubConverters.d.ts
vendored
80
src/types/SubConverters.d.ts
vendored
@ -1,76 +1,40 @@
|
|||||||
export interface IFurni {
|
export type Club = 'idle' | 'HC' | 'VIP'
|
||||||
id: number
|
|
||||||
classname: string
|
|
||||||
revision: number
|
|
||||||
category: string
|
|
||||||
defaultdir: number
|
|
||||||
xdim: number
|
|
||||||
ydim: number
|
|
||||||
partcolors: { color: string[] }
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
adurl: string
|
|
||||||
offerid: number
|
|
||||||
buyout: boolean
|
|
||||||
rentofferid: number
|
|
||||||
rentbuyout: boolean
|
|
||||||
bc: boolean
|
|
||||||
excludeddynamic: boolean
|
|
||||||
customparams: string
|
|
||||||
specialtype: number
|
|
||||||
canstandon: boolean
|
|
||||||
cansiton: boolean
|
|
||||||
canlayon: boolean
|
|
||||||
furniline: string
|
|
||||||
environment: string
|
|
||||||
rare: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFigureDataColor {
|
export interface IFigureDataPaletteType {
|
||||||
id: number
|
|
||||||
index: number
|
index: number
|
||||||
club: number // must be changed, either 0, 1, 2
|
club: number
|
||||||
selectable: boolean
|
selectable: boolean
|
||||||
hexCode: string
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFigureDataPalette {
|
export type IFigureDataPalette = Record<number, IFigureDataPaletteType>
|
||||||
id: number
|
|
||||||
color: IFigureDataColor[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFigureDataPart {
|
export interface IFigureDataPart {
|
||||||
id: number
|
id: number
|
||||||
type: string // must be changed
|
type: string
|
||||||
colorable: boolean // must be changed
|
colorable: boolean
|
||||||
index: number
|
index: number
|
||||||
colorindex: number
|
colorindex: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFigureDataHiddenLayer {
|
|
||||||
partType: string // must be changed
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFigureDataSet {
|
export interface IFigureDataSet {
|
||||||
id: number
|
|
||||||
gender: 'M' | 'F' | 'U'
|
gender: 'M' | 'F' | 'U'
|
||||||
club: number // 0, 1, 2
|
club: number
|
||||||
colorable: boolean // must be changed
|
colorable: boolean
|
||||||
selectable: boolean // must be changed
|
selectable: boolean
|
||||||
preselectable: boolean // must be changed
|
preselectable: boolean
|
||||||
sellable?: boolean // must be changed
|
sellable?: boolean
|
||||||
|
hiddenLayers?: string[]
|
||||||
parts: IFigureDataPart[]
|
parts: IFigureDataPart[]
|
||||||
hiddenLayers?: IFigureDataHiddenLayer[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFigureDataSetType {
|
export interface IFigureDataSetType {
|
||||||
type: string // must be changed
|
|
||||||
paletteId: number
|
paletteId: number
|
||||||
mandatoryF0: boolean // 0, 1
|
mandatoryF0: boolean
|
||||||
mandatoryF1: boolean // 0, 1
|
mandatoryF1: boolean
|
||||||
mandatoryM0: boolean // 0, 1
|
mandatoryM0: boolean
|
||||||
mandatoryM1: boolean // 0, 1
|
mandatoryM1: boolean
|
||||||
sets: IFigureDataSet[]
|
sets: Record<number, IFigureDataSet>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFigureMapLibraryPart {
|
export interface IFigureMapLibraryPart {
|
||||||
@ -81,14 +45,6 @@ export interface IFigureMapLibraryPart {
|
|||||||
export interface IFigureMapLibrary {
|
export interface IFigureMapLibrary {
|
||||||
id: string
|
id: string
|
||||||
revision: number
|
revision: number
|
||||||
part: IFigureMapLibraryPart[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEffectMapLibrary {
|
|
||||||
id: number
|
|
||||||
lib: string
|
|
||||||
type: string
|
|
||||||
revision: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProduct {
|
export interface IProduct {
|
||||||
|
2
src/types/global.d.ts
vendored
Normal file
2
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type StateTypes = 'idle' | 'loading' | 'error' | 'success'
|
||||||
|
export type ConvertionHandler = (message: string, state: StateTypes) => void
|
3
src/types/index.d.ts
vendored
3
src/types/index.d.ts
vendored
@ -1,4 +1,3 @@
|
|||||||
export * from './Converters'
|
export * from './Converters'
|
||||||
export * from './SubConverters'
|
export * from './SubConverters'
|
||||||
export * from './Endpoint'
|
export * from './global'
|
||||||
export * from './Domain'
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { ResponseType } from '@tauri-apps/api/http'
|
|
||||||
import { XMLParser } from 'fast-xml-parser'
|
|
||||||
|
|
||||||
import { GAME_ENDPOINTS, client } from '../config/ENDPOINTS'
|
|
||||||
import type { DomainTypes } from '../types/Domain'
|
|
||||||
import { convertXML } from '../mapping'
|
|
||||||
import { parseData } from './parseData'
|
|
||||||
|
|
||||||
export const fetchGameData = async (
|
|
||||||
domain: DomainTypes,
|
|
||||||
callback: (message: string, error: boolean) => void,
|
|
||||||
assetsOption = false
|
|
||||||
): Promise<void> => {
|
|
||||||
if (!assetsOption) {
|
|
||||||
await Promise.all(
|
|
||||||
GAME_ENDPOINTS(domain).map(async (endpoint) => {
|
|
||||||
await client
|
|
||||||
.get(endpoint.src, { responseType: ResponseType.Text })
|
|
||||||
.then(async ({ data }) => {
|
|
||||||
switch (endpoint.convert) {
|
|
||||||
case 'XML':
|
|
||||||
const convertedData = new XMLParser({
|
|
||||||
ignoreAttributes: false,
|
|
||||||
attributeNamePrefix: ''
|
|
||||||
}).parse(data as string)
|
|
||||||
|
|
||||||
const XML2JSON = convertXML(convertedData, endpoint.src)
|
|
||||||
|
|
||||||
console.log(XML2JSON)
|
|
||||||
|
|
||||||
return await parseData(endpoint.fileName, XML2JSON)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
return callback(error, true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
/* ASSETS_ENDPOINTS(domain).map((endpoint) => {
|
|
||||||
client.get()
|
|
||||||
}) */
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { downloadDir } from '@tauri-apps/api/path'
|
|
||||||
import { createDir, exists, writeTextFile } from '@tauri-apps/api/fs'
|
|
||||||
|
|
||||||
import { PROD_VERSION } from '../config/ENDPOINTS'
|
|
||||||
|
|
||||||
export const parseData = async (
|
|
||||||
fileName: string,
|
|
||||||
fileContent: string | object
|
|
||||||
): Promise<void> => {
|
|
||||||
const outputDir = (await downloadDir()).concat(String(PROD_VERSION))
|
|
||||||
const fileDir = outputDir.concat(`/${fileName}.json`)
|
|
||||||
|
|
||||||
if (!(await exists(outputDir))) await createDir(outputDir)
|
|
||||||
|
|
||||||
await writeTextFile(
|
|
||||||
fileDir,
|
|
||||||
typeof fileContent === 'object' ? JSON.stringify(fileContent) : fileContent
|
|
||||||
)
|
|
||||||
}
|
|
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
@ -1,6 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/**/*.tsx'],
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
@ -4,8 +4,6 @@
|
|||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": false,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
@ -14,8 +12,9 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "preserve",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"jsxImportSource": "solid-js"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
32
vite.config.tauri.ts
Normal file
32
vite.config.tauri.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { defineConfig, mergeConfig } from 'vite'
|
||||||
|
import baseViteConfig from './vite.config'
|
||||||
|
import { tauri } from 'vite-plugin-tauri'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(
|
||||||
|
mergeConfig(
|
||||||
|
baseViteConfig,
|
||||||
|
defineConfig({
|
||||||
|
plugins: [tauri()],
|
||||||
|
// prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false,
|
||||||
|
// Tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: {
|
||||||
|
strictPort: true,
|
||||||
|
open: false
|
||||||
|
},
|
||||||
|
// to make use of `TAURI_PLATFORM`, `TAURI_ARCH`, `TAURI_FAMILY`,
|
||||||
|
// `TAURI_PLATFORM_VERSION`, `TAURI_PLATFORM_TYPE` and `TAURI_DEBUG`
|
||||||
|
// env variables
|
||||||
|
envPrefix: ['VITE_', 'TAURI_'],
|
||||||
|
build: {
|
||||||
|
// Tauri supports es2021
|
||||||
|
target: ['es2022', 'chrome100', 'safari13'],
|
||||||
|
// don't minify for debug builds
|
||||||
|
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
||||||
|
// produce sourcemaps for debug builds
|
||||||
|
sourcemap: !!process.env.TAURI_DEBUG,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
@ -1,26 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
export default defineConfig(async () => ({
|
// https://vitejs.dev/config/
|
||||||
plugins: [react()],
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin()]
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
})
|
||||||
// prevent vite from obscuring rust errors
|
|
||||||
clearScreen: false,
|
|
||||||
// tauri expects a fixed port, fail if that port is not available
|
|
||||||
server: {
|
|
||||||
port: 1420,
|
|
||||||
strictPort: true
|
|
||||||
},
|
|
||||||
// to make use of `TAURI_DEBUG` and other env variables
|
|
||||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
|
||||||
envPrefix: ['VITE_', 'TAURI_'],
|
|
||||||
build: {
|
|
||||||
// Tauri supports es2021
|
|
||||||
target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari13',
|
|
||||||
// don't minify for debug builds
|
|
||||||
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
|
||||||
// produce sourcemaps for debug builds
|
|
||||||
sourcemap: !!process.env.TAURI_DEBUG
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
Loading…
Reference in New Issue
Block a user