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
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.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>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
53
package.json
53
package.json
@ -2,49 +2,48 @@
|
||||
"name": "@rypidev/rypi-scrapper",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"description": "Scrapping Habbo gamedata and assets",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"dev:tauri": "vite --config vite.config.tauri.ts",
|
||||
"build:tauri": "vite build --config vite.config.tauri.ts",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/press-start-2p": "^4.5.11",
|
||||
"@motionone/solid": "10.16.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"classnames": "^2.3.2",
|
||||
"fast-xml-parser": "4.1.3",
|
||||
"framer-motion": "^10.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.10"
|
||||
"flag-icons": "^6.6.6",
|
||||
"solid-icons": "1.0.4",
|
||||
"solid-js": "^1.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"@types/node": "^18.16.3",
|
||||
"@walidoux/eslint-config": "1.0.3",
|
||||
"@walidoux/prettier-config": "1.0.2",
|
||||
"@walidoux/prettier-config": "1.0.3",
|
||||
"autoprefixer": "10.4.14",
|
||||
"eslint": "8.36.0",
|
||||
"postcss": "8.4.21",
|
||||
"prettier": "2.8.7",
|
||||
"prettier-plugin-tailwindcss": "0.2.6",
|
||||
"tailwindcss": "3.3.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.0.0"
|
||||
"cssnano": "6.0.1",
|
||||
"cssnano-preset-advanced": "6.0.1",
|
||||
"eslint": "8.39.0",
|
||||
"postcss": "8.4.23",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-tailwindcss": "0.2.8",
|
||||
"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": {
|
||||
"extends": [
|
||||
"@walidoux/eslint-config"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"**/*.config.js",
|
||||
"**/*.config.ts"
|
||||
"**/*.config.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
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]
|
||||
name = "rypi-scrapper"
|
||||
name = "app"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
description = "Scrapping Habbo gamedata and assets"
|
||||
authors = ["Walidoux"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/RypiDev/rypi-scrapper"
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
build = "src/build.rs"
|
||||
|
||||
# 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 = [] }
|
||||
|
||||
[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_json = "1.0"
|
||||
specta = "1.0.3"
|
||||
quickxml_to_serde = "0.5.0"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
# DO NOT REMOVE!!
|
||||
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")]
|
||||
|
||||
pub mod commands;
|
||||
pub mod structs;
|
||||
pub mod utils;
|
||||
|
||||
use commands::download_gamedata;
|
||||
use specta::collect_types;
|
||||
use tauri_specta::ts;
|
||||
|
||||
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()
|
||||
.invoke_handler(tauri::generate_handler![download_gamedata])
|
||||
.run(tauri::generate_context!())
|
||||
.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": {
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeBuildCommand": "yarn build",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "rypi-scrapper",
|
||||
"version": "0.0.0"
|
||||
@ -15,8 +9,7 @@
|
||||
"window": {
|
||||
"close": true,
|
||||
"minimize": true,
|
||||
"maximize": true,
|
||||
"unmaximize": true
|
||||
"startDragging": true
|
||||
},
|
||||
"os": {
|
||||
"all": false
|
||||
@ -25,21 +18,12 @@
|
||||
"createDir": true,
|
||||
"exists": true,
|
||||
"readDir": true,
|
||||
"removeDir": true,
|
||||
"writeFile": true,
|
||||
"scope": ["$DOWNLOAD/**/*"]
|
||||
},
|
||||
"shell": {
|
||||
"all": true,
|
||||
"execute": true,
|
||||
"sidecar": true,
|
||||
"open": true,
|
||||
"scope": [
|
||||
{
|
||||
"name": "download-habbo-downloader",
|
||||
"cmd": "npm",
|
||||
"args": ["install", "-g", "habbo-downloader"]
|
||||
}
|
||||
]
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"http": {
|
||||
"all": true,
|
||||
@ -49,18 +33,12 @@
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"identifier": "org.rypi.dev",
|
||||
"targets": "all"
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
@ -68,10 +46,10 @@
|
||||
"windows": [
|
||||
{
|
||||
"decorations": false,
|
||||
"resizable": false,
|
||||
"center": true,
|
||||
"resizable": true,
|
||||
"width": 800,
|
||||
"height": 500
|
||||
"width": 1000,
|
||||
"height": 575
|
||||
}
|
||||
]
|
||||
}
|
||||
|
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 { 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)
|
||||
}
|
||||
import { Downloaders, TitleBar, LangDropdown } from './components/layout'
|
||||
|
||||
const Main: Component = () => {
|
||||
return (
|
||||
<Window>
|
||||
<span className='mb-20 text-white'>I would like to:</span>
|
||||
<>
|
||||
<TitleBar />
|
||||
|
||||
<ul className='flex gap-x-8'>
|
||||
<Downloader content={GameDataDownloader}>
|
||||
<Image src='/images/Gamedata.png' className='w-[400px]' />
|
||||
<main class='relative flex h-full w-screen flex-col items-center justify-center bg-[#242424] py-20 text-white'>
|
||||
<span class='mb-20'>I would like to:</span>
|
||||
|
||||
<Button
|
||||
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>
|
||||
<LangDropdown />
|
||||
|
||||
<Downloader content={GameAssetsDownloader}>
|
||||
<Image src='/images/GameAssets.png' className='w-[400px]' />
|
||||
|
||||
<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>
|
||||
<Downloaders />
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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: IDownloaderContent = {
|
||||
export const GameDataDownloader = {
|
||||
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:',
|
||||
features: [
|
||||
'Badges + Badgeparts',
|
||||
'Album + Recepetion images',
|
||||
'Catalogue + Furni icons',
|
||||
'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 utilities;
|
||||
|
||||
#root {
|
||||
font-family: 'Press Start 2P';
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.retro-text-shadow {
|
||||
text-shadow: 0px 1.5px 0px #000000, -1px 3px 0px rgba(0, 0, 0, 0.54);
|
||||
@ -20,6 +16,6 @@
|
||||
|
||||
@layer components {
|
||||
.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 {
|
||||
IEffectMapLibrary,
|
||||
IFigureDataPalette,
|
||||
IFigureDataSetType,
|
||||
IFigureMapLibrary,
|
||||
IFurni,
|
||||
IProduct
|
||||
} from './SubConverters'
|
||||
import type { IFigureDataPalette, IFigureDataSetType, IFigureMapLibrary, IProduct } from './SubConverters'
|
||||
|
||||
export interface IFigureData {
|
||||
palettes: IFigureDataPalette[]
|
||||
setTypes: IFigureDataSetType[]
|
||||
setTypes: Record<string, IFigureDataSetType>
|
||||
}
|
||||
|
||||
export interface IFigureMap {
|
||||
libraries: IFigureMapLibrary[]
|
||||
parts: Record<string, Record<number, number>>
|
||||
}
|
||||
|
||||
export interface IFurniData {
|
||||
roomitemtypes: { furnitype: IFurni }
|
||||
wallitemtypes: { furnitype: IFurni }
|
||||
}
|
||||
|
||||
export interface IEffectMap {
|
||||
effects: IEffectMapLibrary[]
|
||||
}
|
||||
export type IEffectMap = Record<string, Record<string, string>>
|
||||
|
||||
export interface IProductData {
|
||||
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 {
|
||||
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 type Club = 'idle' | 'HC' | 'VIP'
|
||||
|
||||
export interface IFigureDataColor {
|
||||
id: number
|
||||
export interface IFigureDataPaletteType {
|
||||
index: number
|
||||
club: number // must be changed, either 0, 1, 2
|
||||
club: number
|
||||
selectable: boolean
|
||||
hexCode: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface IFigureDataPalette {
|
||||
id: number
|
||||
color: IFigureDataColor[]
|
||||
}
|
||||
export type IFigureDataPalette = Record<number, IFigureDataPaletteType>
|
||||
|
||||
export interface IFigureDataPart {
|
||||
id: number
|
||||
type: string // must be changed
|
||||
colorable: boolean // must be changed
|
||||
type: string
|
||||
colorable: boolean
|
||||
index: number
|
||||
colorindex: number
|
||||
}
|
||||
|
||||
export interface IFigureDataHiddenLayer {
|
||||
partType: string // must be changed
|
||||
}
|
||||
|
||||
export interface IFigureDataSet {
|
||||
id: number
|
||||
gender: 'M' | 'F' | 'U'
|
||||
club: number // 0, 1, 2
|
||||
colorable: boolean // must be changed
|
||||
selectable: boolean // must be changed
|
||||
preselectable: boolean // must be changed
|
||||
sellable?: boolean // must be changed
|
||||
club: number
|
||||
colorable: boolean
|
||||
selectable: boolean
|
||||
preselectable: boolean
|
||||
sellable?: boolean
|
||||
hiddenLayers?: string[]
|
||||
parts: IFigureDataPart[]
|
||||
hiddenLayers?: IFigureDataHiddenLayer[]
|
||||
}
|
||||
|
||||
export interface IFigureDataSetType {
|
||||
type: string // must be changed
|
||||
paletteId: number
|
||||
mandatoryF0: boolean // 0, 1
|
||||
mandatoryF1: boolean // 0, 1
|
||||
mandatoryM0: boolean // 0, 1
|
||||
mandatoryM1: boolean // 0, 1
|
||||
sets: IFigureDataSet[]
|
||||
mandatoryF0: boolean
|
||||
mandatoryF1: boolean
|
||||
mandatoryM0: boolean
|
||||
mandatoryM1: boolean
|
||||
sets: Record<number, IFigureDataSet>
|
||||
}
|
||||
|
||||
export interface IFigureMapLibraryPart {
|
||||
@ -81,14 +45,6 @@ export interface IFigureMapLibraryPart {
|
||||
export interface IFigureMapLibrary {
|
||||
id: string
|
||||
revision: number
|
||||
part: IFigureMapLibraryPart[]
|
||||
}
|
||||
|
||||
export interface IEffectMapLibrary {
|
||||
id: number
|
||||
lib: string
|
||||
type: string
|
||||
revision: number
|
||||
}
|
||||
|
||||
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 './SubConverters'
|
||||
export * from './Endpoint'
|
||||
export * from './Domain'
|
||||
export * from './global'
|
||||
|
@ -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} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.tsx'],
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
@ -4,8 +4,6 @@
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
@ -14,8 +12,9 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "preserve",
|
||||
"types": ["vite/client"],
|
||||
"jsxImportSource": "solid-js"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
@ -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 react from '@vitejs/plugin-react'
|
||||
import solidPlugin from 'vite-plugin-solid'
|
||||
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
|
||||
// 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
|
||||
}
|
||||
}))
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [solidPlugin()]
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user