mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2024-11-26 21:30:47 +00:00
Run prettier formatter
This commit is contained in:
parent
e9df0f17db
commit
eb9aa34323
@ -1,48 +1,40 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
{
|
||||
"extendDefaults": true,
|
||||
"types": {
|
||||
"{}": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
{
|
||||
"extendDefaults": true,
|
||||
"types": {
|
||||
"{}": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
.github/workflows/backend-checks.yml
vendored
9
.github/workflows/backend-checks.yml
vendored
@ -3,12 +3,12 @@ name: Check backend
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/backend-checks.yml"
|
||||
- "src-tauri/**"
|
||||
- '.github/workflows/backend-checks.yml'
|
||||
- 'src-tauri/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/backend-checks.yml"
|
||||
- "src-tauri/**"
|
||||
- '.github/workflows/backend-checks.yml'
|
||||
- 'src-tauri/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
@ -59,4 +59,3 @@ jobs:
|
||||
name: clippy (${{ runner.os }})
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --manifest-path ./src-tauri/Cargo.toml --no-default-features -- -D warnings
|
||||
|
||||
|
25
.github/workflows/frontend-checks.yml
vendored
25
.github/workflows/frontend-checks.yml
vendored
@ -3,20 +3,20 @@ name: Check frontend
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
- '.github/workflows/frontend-checks.yml'
|
||||
- 'src/**'
|
||||
- '.eslintrc.json'
|
||||
- 'package.json'
|
||||
- 'tsconfig.json'
|
||||
- 'yarn.lock'
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/frontend-checks.yml"
|
||||
- "src/**"
|
||||
- ".eslintrc.json"
|
||||
- "package.json"
|
||||
- "tsconfig.json"
|
||||
- "yarn.lock"
|
||||
- '.github/workflows/frontend-checks.yml'
|
||||
- 'src/**'
|
||||
- '.eslintrc.json'
|
||||
- 'package.json'
|
||||
- 'tsconfig.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
@ -34,4 +34,3 @@ jobs:
|
||||
run: yarn tsc --noEmit
|
||||
- name: Run ESLint
|
||||
run: yarn eslint src
|
||||
|
||||
|
55
README.md
55
README.md
@ -1,25 +1,28 @@
|
||||
# Client Patching Notice
|
||||
|
||||
For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
|
||||
# Cultivation
|
||||
|
||||
A game launcher designed to easily proxy traffic from anime game to private servers.
|
||||
|
||||
While the Cultivation repository is **open**. This does **not** mean it has released.
|
||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository.
|
||||
|
||||
# Table Of Contents
|
||||
* [Download](#download)
|
||||
* [Developer Quick-start](#developer-quickstart)
|
||||
* [Setup](#setup)
|
||||
* [Building](#building)
|
||||
* [Code Formatting and Linting](#code-formatting-and-linting)
|
||||
* [Generating Update Artifacts](#generating-update-artifacts)
|
||||
* [Theming](#theming)
|
||||
* [Screenshots](#screenshots)
|
||||
* [Credits](#credits)
|
||||
|
||||
- [Download](#download)
|
||||
- [Developer Quick-start](#developer-quickstart)
|
||||
- [Setup](#setup)
|
||||
- [Building](#building)
|
||||
- [Code Formatting and Linting](#code-formatting-and-linting)
|
||||
- [Generating Update Artifacts](#generating-update-artifacts)
|
||||
- [Theming](#theming)
|
||||
- [Screenshots](#screenshots)
|
||||
- [Credits](#credits)
|
||||
|
||||
# Download
|
||||
|
||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
Once downloaded, extract somewhere and open as administrator.
|
||||
@ -27,27 +30,33 @@ Once downloaded, extract somewhere and open as administrator.
|
||||
# Developer Quickstart
|
||||
|
||||
### Setup
|
||||
* Install [NodeJS >12](https://nodejs.org/en/)
|
||||
* Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
|
||||
* Install [Rust](https://www.rust-lang.org/tools/install)
|
||||
* `yarn install`
|
||||
* `yarn start:dev`
|
||||
|
||||
- Install [NodeJS >12](https://nodejs.org/en/)
|
||||
- Install [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (cry about it `npm` lovers)
|
||||
- Install [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### Building
|
||||
|
||||
For a release build,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
For a debug build,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### Code Formatting and Linting
|
||||
|
||||
- `yarn format`
|
||||
- `yarn lint`
|
||||
|
||||
### Generating Update Artifacts
|
||||
* Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
|
||||
* Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
|
||||
* `yarn build`
|
||||
|
||||
- Add the `TAURI_PRIVATE_KEY` as an environment variable with a path to your private key.
|
||||
- Add the `TAURI_KEY_PASSWORD` as an environment variable with the password for your private key.
|
||||
- `yarn build`
|
||||
|
||||
The update will be at `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
@ -56,6 +65,7 @@ The update will be at `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x6
|
||||
A full theming reference can be found [here!](/THEMES.md)
|
||||
|
||||
# Screenshots
|
||||
|
||||
![image](https://user-images.githubusercontent.com/25207995/173211603-e5e85df7-7fd3-430b-9246-749ebbc1e483.png)
|
||||
![image](https://user-images.githubusercontent.com/25207995/173211543-b7e88943-cfd2-418b-ac48-7f856868129b.png)
|
||||
![image](https://user-images.githubusercontent.com/25207995/173211561-a1778fdc-5cfe-4687-9a00-44500d29e470.png)
|
||||
@ -63,8 +73,9 @@ A full theming reference can be found [here!](/THEMES.md)
|
||||
![image](https://user-images.githubusercontent.com/25207995/173211590-6a2242b5-1e8f-4db9-a5c7-06284688b131.png)
|
||||
|
||||
## Credits
|
||||
* [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
* [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
* [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
* [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
* [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
|
47
THEMES.md
47
THEMES.md
@ -2,7 +2,7 @@
|
||||
|
||||
1. Download your favorite theme! (You can find some in the `#themes` channel on Discord)
|
||||
2. Place the unzipped theme folder inside of `%appdata%/cultivation/themes` (The path should look something like this: `cultivation/themes/theme_name/index.json`)
|
||||
4. Enable within Cultivation!
|
||||
3. Enable within Cultivation!
|
||||
|
||||
# Creating your own theme
|
||||
|
||||
@ -16,16 +16,16 @@ You will need CSS and JS experience if you want to do anything cool.
|
||||
|
||||
`index.json` is where you tell Cultivation which files and images to include. It supports the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| :--- | :--- |
|
||||
| `name` | The name of the theme. |
|
||||
| `version` | Not shown anywhere, the version of the theme. |
|
||||
| `description` | Not shown anywhere, the description of the theme. |
|
||||
| `includes` | The files and folders to include. |
|
||||
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
||||
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
|
||||
| `customBackgroundURL` | A custom image URL to set as the background. Backgrounds that users set in their config supercede this. Example: `"https://website.com/image.png"` |
|
||||
| `customBackgroundFile` | Path to a custom background image file. Backgrounds that users set in their config supercede this. Example: `"/image.png"` |
|
||||
| Property | Description |
|
||||
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | The name of the theme. |
|
||||
| `version` | Not shown anywhere, the version of the theme. |
|
||||
| `description` | Not shown anywhere, the description of the theme. |
|
||||
| `includes` | The files and folders to include. |
|
||||
| `includes.css` | Array of CSS files to include. Example: `css: ["index.css"]` |
|
||||
| `includes.js` | Array of JS files to includes. Example `js: ["index.js"]` |
|
||||
| `customBackgroundURL` | A custom image URL to set as the background. Backgrounds that users set in their config supercede this. Example: `"https://website.com/image.png"` |
|
||||
| `customBackgroundFile` | Path to a custom background image file. Backgrounds that users set in their config supercede this. Example: `"/image.png"` |
|
||||
|
||||
A full, complete `index.json` will look something like this:
|
||||
|
||||
@ -55,15 +55,17 @@ Below are some small examples of what you can do:
|
||||
```css
|
||||
/* Change the font */
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* Remove the news section */
|
||||
.NewsSection {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* Change the right bar width */
|
||||
.RightBar {
|
||||
@ -72,6 +74,7 @@ body {
|
||||
```
|
||||
|
||||
## How can I change XYZ element?
|
||||
|
||||
Every element is documented and describe [here](/docs/elementIds.md). Every\* single DOM element is assigned an ID to allow for easy and hyper-specific editing.
|
||||
|
||||
## Writing your JS
|
||||
@ -83,24 +86,26 @@ Below are some examples of what you can do:
|
||||
```js
|
||||
/* Change the version number every 500ms */
|
||||
setInterval(() => {
|
||||
document.getElementById("version").innerHTML = "v" + Math.floor(Math.random() * 100);
|
||||
}, 500);
|
||||
document.getElementById('version').innerHTML = 'v' + Math.floor(Math.random() * 100)
|
||||
}, 500)
|
||||
```
|
||||
|
||||
```js
|
||||
/* Load a custom font */
|
||||
const head = document.head
|
||||
const link = document.createElement("link")
|
||||
const link = document.createElement('link')
|
||||
|
||||
link.href = "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap"
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'
|
||||
link.rel = 'stylesheet'
|
||||
link.type = 'text/css'
|
||||
|
||||
head.appendChild(link)
|
||||
```
|
||||
|
||||
```js
|
||||
/* Create a new button that does nothing */
|
||||
const newButton = document.createElement("button");
|
||||
newButton.innerHTML = "New Button";
|
||||
const newButton = document.createElement('button')
|
||||
newButton.innerHTML = 'New Button'
|
||||
|
||||
document.body.appendChild(newButton);
|
||||
document.body.appendChild(newButton)
|
||||
```
|
||||
|
@ -1,133 +1,135 @@
|
||||
# Documentation of Element ID's and Classes for custom theming
|
||||
|
||||
## IDs
|
||||
|
||||
This does not include commonly used components (buttons, divider lines, commit author and message, etc...) for accessing and modifying those elements, please check `Classes` section bellow.
|
||||
|
||||
| #ID | Description |
|
||||
|----------------------------------------|-----------------------------------------------------------------|
|
||||
| `#miniDialogContainer` | Main container of MiniDialog |
|
||||
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
||||
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
||||
| `#miniDialogContent` | MiniDialog content |
|
||||
| `#rightBarContainer` | Main container of RightBar |
|
||||
| `#rightBarContent` | RightBar content |
|
||||
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
||||
| `#rightBarButtonGithub` | Github button on the RightBar |
|
||||
| `#playButton` | Main container for whole launch buttons section |
|
||||
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
||||
| `#enableGC` | "play on grasscutter" checkbox |
|
||||
| `#ip` | Server ip input if play on grasscutter is enabled |
|
||||
| `#port` | Server port input if play on grasscutter is enabled |
|
||||
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
|
||||
| `#officialPlay` | Launch button |
|
||||
| `#serverLaunch` | Launch server button |
|
||||
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
||||
| `#serverConfigContainer` | Main container of server configuration section |
|
||||
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
||||
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
||||
| `#title` | Title of the TopBar |
|
||||
| `#version` | Version of the launcher in TopBar |
|
||||
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
||||
| `#closeBtn` | Exit launcher button |
|
||||
| `#minBtn` | Minimize launcher button |
|
||||
| `#settingsBtn` | Settings button |
|
||||
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
||||
| `#newsContainer` | Main container of the news section |
|
||||
| `#newsTabsContainer` | Container for news tabs |
|
||||
| `#commits` | News tabs container commits button |
|
||||
| `#latest_version` | News tabs for latest version button |
|
||||
| `#newsContent` | Content section of news container |
|
||||
| `#newsCommitsTable` | Commits table of news section |
|
||||
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
||||
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
||||
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
||||
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
||||
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
||||
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
||||
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
||||
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
||||
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
||||
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
||||
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
||||
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
||||
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
||||
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
||||
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
||||
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
||||
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
||||
| `#downloadMenuLabelResources` | label for resources download |
|
||||
| `#downloadMenuButtonResources` | Button container for resources download button |
|
||||
| `#resourcesBtn` | "Download grasscutter resources" button |
|
||||
| `#menuContainer` | Generic Popup modal like menu container |
|
||||
| `#menuContainerTop` | Top section of menu container |
|
||||
| `#menuHeading` | Menu title |
|
||||
| `#menuButtonCloseContainer` | Container for menu close button |
|
||||
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
||||
| `#menuContent` | Content section of the menu |
|
||||
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
||||
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
||||
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
||||
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
||||
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
||||
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
||||
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
||||
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
||||
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
||||
| `#toggleEnc` | Toggle encryption button |
|
||||
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
||||
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
||||
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
||||
| `#gcWithGame` | Grasscutter with game checkbox |
|
||||
| `#menuOptionsContainerThemes` | Container for themes section |
|
||||
| `#menuOptionsLabelThemes` | Label for set themes option |
|
||||
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
||||
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
||||
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
||||
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
||||
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
||||
| `#menuOptionsContainerBG` | Container for Background option |
|
||||
| `#menuOptionsLabelBG` | Label for background option |
|
||||
| `#menuOptionsDirBG` | Container for background url/local path option |
|
||||
| `#menuOptionsContainerLang` | Container for language change option |
|
||||
| `#menuOptionsLabelLang` | Label for language change option |
|
||||
| `#menuOptionsSelectLang` | Container for language change select menu |
|
||||
| `#menuOptionsSelectMenuLang` | Language select menu |
|
||||
| `#DownloadProgress` | Download progress container |
|
||||
| `#bottomSectionContainer` | Bottom section container |
|
||||
| `#miniDownloadContainer` | Container for mini download |
|
||||
| #ID | Description |
|
||||
| ------------------------------------ | --------------------------------------------------------------- |
|
||||
| `#miniDialogContainer` | Main container of MiniDialog |
|
||||
| `#miniDialogContainerTop` | Affects only top section of MiniDialog |
|
||||
| `#miniDialogButtonClose` | Close button (SVG) of MiniDialog |
|
||||
| `#miniDialogContent` | MiniDialog content |
|
||||
| `#rightBarContainer` | Main container of RightBar |
|
||||
| `#rightBarContent` | RightBar content |
|
||||
| `#rightBarButtonDiscord` | Discord button on the RightBar |
|
||||
| `#rightBarButtonGithub` | Github button on the RightBar |
|
||||
| `#playButton` | Main container for whole launch buttons section |
|
||||
| `#serverControls` | Container of "play on grasscutter" checkbox |
|
||||
| `#enableGC` | "play on grasscutter" checkbox |
|
||||
| `#ip` | Server ip input if play on grasscutter is enabled |
|
||||
| `#port` | Server port input if play on grasscutter is enabled |
|
||||
| `#httpsEnable` | "Enable https" checkbox if play on grasscutter is enabled |
|
||||
| `#officialPlay` | Launch button |
|
||||
| `#serverLaunch` | Launch server button |
|
||||
| `#serverlaunchIcon` | Icon (SVG) of server launch button |
|
||||
| `#serverConfigContainer` | Main container of server configuration section |
|
||||
| `#serverLaunchContainer` | Main container of launch buttons (includes launch server) |
|
||||
| `#topBarContainer` | Main container of launcher TopBar (minimize, exit, settings...) |
|
||||
| `#title` | Title of the TopBar |
|
||||
| `#version` | Version of the launcher in TopBar |
|
||||
| `#topBarButtonContainer` | Container of launcher TopBar buttons only |
|
||||
| `#closeBtn` | Exit launcher button |
|
||||
| `#minBtn` | Minimize launcher button |
|
||||
| `#settingsBtn` | Settings button |
|
||||
| `#downloadsBtn` | Downloads button (grasscutter resources, grasscutter...) |
|
||||
| `#newsContainer` | Main container of the news section |
|
||||
| `#newsTabsContainer` | Container for news tabs |
|
||||
| `#commits` | News tabs container commits button |
|
||||
| `#latest_version` | News tabs for latest version button |
|
||||
| `#newsContent` | Content section of news container |
|
||||
| `#newsCommitsTable` | Commits table of news section |
|
||||
| `#downloadMenuContainerGCStable` | Grasscutter stable update container |
|
||||
| `#downloadMenuLabelGCStable` | Label for stable update button |
|
||||
| `#downloadMenuButtonGCStable` | Button container for stable update button |
|
||||
| `#grasscutterStableBtn` | "Update grasscutter stable" button |
|
||||
| `#downloadMenuContainerGCDev` | Grasscutter development update container |
|
||||
| `#downloadMenuLabelGCDev` | Label for latest update button |
|
||||
| `#downloadMenuButtonGCDev` | Button container for latest update button |
|
||||
| `grasscutterLatestBtn` | "Update grasscutter latest" button |
|
||||
| `#downloadMenuContainerGCStableData` | Grasscutter stable data update container |
|
||||
| `#downloadMenuLabelGCStableData` | Label for stable data update |
|
||||
| `#downloadMenuButtonGCStableData` | Button container for stable data update button |
|
||||
| `#grasscutterStableRepo` | "Update grasscutter stable data" button |
|
||||
| `#downloadMenuContainerGCDevData` | Grasscutter latest data update container |
|
||||
| `#downloadMenuLabelGCDevData` | Label for latest data update |
|
||||
| `#downloadMenuButtonGCDevData` | Button container for latest data update button |
|
||||
| `#grasscutterDevRepo` | "Update grasscutter latest data" button |
|
||||
| `#downloadMenuContainerResources` | Container for grasscutter resources download |
|
||||
| `#downloadMenuLabelResources` | label for resources download |
|
||||
| `#downloadMenuButtonResources` | Button container for resources download button |
|
||||
| `#resourcesBtn` | "Download grasscutter resources" button |
|
||||
| `#menuContainer` | Generic Popup modal like menu container |
|
||||
| `#menuContainerTop` | Top section of menu container |
|
||||
| `#menuHeading` | Menu title |
|
||||
| `#menuButtonCloseContainer` | Container for menu close button |
|
||||
| `#menuButtonCloseIcon` | Menu close icon (SVG) |
|
||||
| `#menuContent` | Content section of the menu |
|
||||
| `#menuOptionsContainerGameExec` | Container for game executable option section |
|
||||
| `#menuOptionsLabelGameExec` | Label for game executable option |
|
||||
| `#menuOptionsDirGameExec` | Set game executable file browser |
|
||||
| `#menuOptionsContainerGCJar` | Container for grasscutter jar option |
|
||||
| `#menuOptionsLabelGCJar` | Label for grasscutter jar option |
|
||||
| `#menuOptionsDirGCJar` | Set grasscutter jar file browser |
|
||||
| `#menuOptionsContainerToggleEnc` | Container for toggle encryption option |
|
||||
| `#menuOptionsLabelToggleEnc` | Label for toggle encryption option |
|
||||
| `#menuOptionsButtonToggleEnc` | Toggle encryption button container |
|
||||
| `#toggleEnc` | Toggle encryption button |
|
||||
| `#menuOptionsContainerGCWGame` | Container for "grasscutter with game" option |
|
||||
| `#menuOptionsLabelGCWDame` | Label for "grasscutter with game" option |
|
||||
| `#menuOptionsCheckboxGCWGame` | Container for "grasscutter with game" option checkbox |
|
||||
| `#gcWithGame` | Grasscutter with game checkbox |
|
||||
| `#menuOptionsContainerThemes` | Container for themes section |
|
||||
| `#menuOptionsLabelThemes` | Label for set themes option |
|
||||
| `#menuOptionsSelectThemes` | Container for themes select menu |
|
||||
| `#menuOptionsSelectMenuThemes` | Set theme select menu |
|
||||
| `#menuOptionsContainerJavaPath` | Container for Java Path option |
|
||||
| `#menuOptionsLabelJavaPath` | Label for Java path option |
|
||||
| `#menuOptionsDirJavaPath` | Container for java path file browser |
|
||||
| `#menuOptionsContainerBG` | Container for Background option |
|
||||
| `#menuOptionsLabelBG` | Label for background option |
|
||||
| `#menuOptionsDirBG` | Container for background url/local path option |
|
||||
| `#menuOptionsContainerLang` | Container for language change option |
|
||||
| `#menuOptionsLabelLang` | Label for language change option |
|
||||
| `#menuOptionsSelectLang` | Container for language change select menu |
|
||||
| `#menuOptionsSelectMenuLang` | Language select menu |
|
||||
| `#DownloadProgress` | Download progress container |
|
||||
| `#bottomSectionContainer` | Bottom section container |
|
||||
| `#miniDownloadContainer` | Container for mini download |
|
||||
|
||||
## Classes
|
||||
|
||||
This is not full list of all classes, rather its list of classes for commonly used components that can not be accessed using element id system.
|
||||
|
||||
| .Class | Description |
|
||||
|-----------------------------|---------------------------------------------------------|
|
||||
| `.BigButton` | Class for all buttons |
|
||||
| `.BigButtonText` | Text inside a button | |
|
||||
| `.Checkbox` | Checkbox container |
|
||||
| `.CheckboxDisplay` | Content of checkbox |
|
||||
| `.DirInput` | Container for DirInput |
|
||||
| `.FileSelectIcon` | Icon of DirInput |
|
||||
| `.DownloadList` | List of all downloads |
|
||||
| `.DownloadSection` | Container for each download |
|
||||
| `.DownloadTitle` | Contains file download path and current status |
|
||||
| `.DownloadPath` | Path of a download |
|
||||
| `.DownloadStatus` | Status of a download |
|
||||
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
||||
| `.HelpSection` | Container for help "?" circle button |
|
||||
| `.HelpButton` | HelpButton itself |
|
||||
| `.HelpContents` | Content of help button once expanded |
|
||||
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
||||
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
||||
| `.InnerProgress` | ProgressBar percentage |
|
||||
| `.MainProgressText` | Text for MainProgressBar |
|
||||
| `.ProgressBarWrapper` | Container for ProgressBar |
|
||||
| `.DownloadControls` | DownloadControls of ProgressBar |
|
||||
| `.downloadStop` | Container for download stop icon (SVG) |
|
||||
| `.ProgressText` | Text of the ProgressBar display current download status |
|
||||
| `.TextInputWrapper` | Container for TextInput |
|
||||
| `.TextClear` | Container for clear input content button |
|
||||
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
||||
| `.Divider` | Container for line dividers |
|
||||
| `.DividerLine` | Divider line itself |
|
||||
| `.CommitAuthor` | Author of a commit |
|
||||
| `.CommitMessage` | Message of a commit |
|
||||
| .Class | Description |
|
||||
| ------------------------- | ------------------------------------------------------- | --- |
|
||||
| `.BigButton` | Class for all buttons |
|
||||
| `.BigButtonText` | Text inside a button | |
|
||||
| `.Checkbox` | Checkbox container |
|
||||
| `.CheckboxDisplay` | Content of checkbox |
|
||||
| `.DirInput` | Container for DirInput |
|
||||
| `.FileSelectIcon` | Icon of DirInput |
|
||||
| `.DownloadList` | List of all downloads |
|
||||
| `.DownloadSection` | Container for each download |
|
||||
| `.DownloadTitle` | Contains file download path and current status |
|
||||
| `.DownloadPath` | Path of a download |
|
||||
| `.DownloadStatus` | Status of a download |
|
||||
| `.DownloadSectionInner` | Contains progressbar of the download section |
|
||||
| `.HelpSection` | Container for help "?" circle button |
|
||||
| `.HelpButton` | HelpButton itself |
|
||||
| `.HelpContents` | Content of help button once expanded |
|
||||
| `.MainProgressBarWrapper` | Container for MainProgressBar |
|
||||
| `.ProgressBar` | ProgressBar (creativity left the brain) |
|
||||
| `.InnerProgress` | ProgressBar percentage |
|
||||
| `.MainProgressText` | Text for MainProgressBar |
|
||||
| `.ProgressBarWrapper` | Container for ProgressBar |
|
||||
| `.DownloadControls` | DownloadControls of ProgressBar |
|
||||
| `.downloadStop` | Container for download stop icon (SVG) |
|
||||
| `.ProgressText` | Text of the ProgressBar display current download status |
|
||||
| `.TextInputWrapper` | Container for TextInput |
|
||||
| `.TextClear` | Container for clear input content button |
|
||||
| `.TextInputClear` | TextInput clear button icon (SVG) |
|
||||
| `.Divider` | Container for line dividers |
|
||||
| `.DividerLine` | Divider line itself |
|
||||
| `.CommitAuthor` | Author of a commit |
|
||||
| `.CommitMessage` | Message of a commit |
|
||||
|
@ -1,15 +1,19 @@
|
||||
# Troubleshooting
|
||||
|
||||
A guide dedicated for trying to troubleshoot Cultivation.
|
||||
|
||||
## The launcher doesn't appear to open.
|
||||
|
||||
Try running the launcher with **administrative privileges**.\
|
||||
If this fixes your issue, you can force enable it in the **Compatability**\
|
||||
tab for the launcher's executable.
|
||||
|
||||
## Unable to play on `localhost`.
|
||||
|
||||
Make sure your server is running with **encryption disabled** and `useInRouting` to **false**.\
|
||||
Additionally, make sure Cultivation **is set to not use HTTPS**.
|
||||
|
||||
## "I can't do anything requiring the internet after closing Cultivation!"
|
||||
|
||||
You probably didn't close Cultivation properly.\
|
||||
Go to your *Windows Settings*, then *Network*, then *Proxy*, then disable it.
|
||||
Go to your _Windows Settings_, then _Network_, then _Proxy_, then disable it.
|
||||
|
@ -5,10 +5,7 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Tauri-powered anime game launcher"
|
||||
/>
|
||||
<meta name="description" content="Tauri-powered anime game launcher" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Cultivation</title>
|
||||
|
@ -1,61 +1,61 @@
|
||||
{
|
||||
"lang_name": "Deutsch",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Starten",
|
||||
"gc_enable": "Über Grasscutter verbinden",
|
||||
"https_enable": "HTTPS nutzen",
|
||||
"ip_placeholder": "Server Adresse...",
|
||||
"port_placeholder": "Port...",
|
||||
"files_downloading": "Herunterladen von Dateien: ",
|
||||
"files_extracting": "Extrahieren von Dateien: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||
"language": "Sprache auswählen",
|
||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||
"theme": "Theme auswählen"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
||||
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
||||
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
||||
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||
"resources": "Grasscutter Ressourcen herunterladen"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Lädt herunter",
|
||||
"extracting": "Extrahiert",
|
||||
"error": "Fehler",
|
||||
"finished": "Fertig",
|
||||
"stopped": "Gestoppt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Datei oder Ordner auswählen...",
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Letzte Commits",
|
||||
"latest_version": "Letzte Version"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
|
||||
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
|
||||
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
|
||||
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben"
|
||||
}
|
||||
"lang_name": "Deutsch",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Starten",
|
||||
"gc_enable": "Über Grasscutter verbinden",
|
||||
"https_enable": "HTTPS nutzen",
|
||||
"ip_placeholder": "Server Adresse...",
|
||||
"port_placeholder": "Port...",
|
||||
"files_downloading": "Herunterladen von Dateien: ",
|
||||
"files_extracting": "Extrahieren von Dateien: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||
"language": "Sprache auswählen",
|
||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||
"theme": "Theme auswählen"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
|
||||
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
|
||||
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
|
||||
"grasscutter_stable": "Stabile Grasscutter Version herunterladen",
|
||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||
"resources": "Grasscutter Ressourcen herunterladen"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Lädt herunter",
|
||||
"extracting": "Extrahiert",
|
||||
"error": "Fehler",
|
||||
"finished": "Fertig",
|
||||
"stopped": "Gestoppt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Datei oder Ordner auswählen...",
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Letzte Commits",
|
||||
"latest_version": "Letzte Version"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
|
||||
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
|
||||
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
|
||||
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben"
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,12 @@
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"fs": {
|
||||
"scope": [
|
||||
"$DATA",
|
||||
"$DATA/cultivation",
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
"scope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||
},
|
||||
"protocol": {
|
||||
"all": true,
|
||||
"asset": true,
|
||||
"assetScope": [
|
||||
"$DATA",
|
||||
"$DATA/cultivation",
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
"assetScope": ["$DATA", "$DATA/cultivation", "$DATA/cultivation/*"]
|
||||
},
|
||||
"all": true
|
||||
},
|
||||
@ -37,13 +29,7 @@
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"identifier": "io.grasscutter",
|
||||
"shortDescription": "A game launcher.",
|
||||
"longDescription": "A launcher for a certain anime game that proxies all related game traffic to external servers.",
|
||||
@ -54,11 +40,7 @@
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [
|
||||
"lang/*.json",
|
||||
"keys/*",
|
||||
"./mhycrypto.dll"
|
||||
],
|
||||
"resources": ["lang/*.json", "keys/*", "./mhycrypto.dll"],
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"allowDowngrades": false,
|
||||
|
@ -1,13 +1,11 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||
sans-serif;
|
||||
font-family: 'MiHoYo_SDK_Web', 'Helvetica Neue', BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||
'Cantarell', 'Fira Sans', 'Droid Sans', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
@ -7,23 +7,15 @@ import Debug from './ui/Debug'
|
||||
|
||||
import { getConfigOption } from './utils/configuration'
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
)
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
|
||||
let isDebug = false;
|
||||
let isDebug = false
|
||||
|
||||
(async() => {
|
||||
;async () => {
|
||||
isDebug = await getConfigOption('debug_enabled')
|
||||
})
|
||||
}
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
{
|
||||
isDebug ? <Debug /> : <App />
|
||||
}
|
||||
</React.StrictMode>
|
||||
)
|
||||
root.render(<React.StrictMode>{isDebug ? <Debug /> : <App />}</React.StrictMode>)
|
||||
|
||||
import reportWebVitals from './utils/reportWebVitals'
|
||||
isDebug && reportWebVitals(console.log)
|
@ -22,7 +22,8 @@ select:focus {
|
||||
border-bottom-color: #ffd326;
|
||||
}
|
||||
|
||||
#root, .App {
|
||||
#root,
|
||||
.App {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@ -82,28 +83,28 @@ select:focus {
|
||||
|
||||
.BottomSection {
|
||||
position: absolute;
|
||||
bottom: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
bottom: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media(max-height: 580px) {
|
||||
.BottomSection {
|
||||
height: 150px;
|
||||
}
|
||||
@media (max-height: 580px) {
|
||||
.BottomSection {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-height: 500px) {
|
||||
.BottomSection {
|
||||
height: 140px;
|
||||
}
|
||||
@media (max-height: 500px) {
|
||||
.BottomSection {
|
||||
height: 140px;
|
||||
}
|
||||
}
|
@ -25,16 +25,16 @@ import { getTheme, loadTheme } from '../utils/themes'
|
||||
import { unpatchGame } from '../utils/metadata'
|
||||
|
||||
interface IProps {
|
||||
[key: string]: never;
|
||||
[key: string]: never
|
||||
}
|
||||
|
||||
interface IState {
|
||||
isDownloading: boolean;
|
||||
optionsOpen: boolean;
|
||||
miniDownloadsOpen: boolean;
|
||||
downloadsOpen: boolean;
|
||||
gameDownloadsOpen: boolean;
|
||||
bgFile: string;
|
||||
isDownloading: boolean
|
||||
optionsOpen: boolean
|
||||
miniDownloadsOpen: boolean
|
||||
downloadsOpen: boolean
|
||||
gameDownloadsOpen: boolean
|
||||
bgFile: string
|
||||
}
|
||||
|
||||
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
||||
@ -57,7 +57,7 @@ class App extends React.Component<IProps, IState> {
|
||||
console.log(payload)
|
||||
})
|
||||
|
||||
listen('jar_extracted', ({ payload }: { payload: string}) => {
|
||||
listen('jar_extracted', ({ payload }: { payload: string }) => {
|
||||
setConfigOption('grasscutter_path', payload)
|
||||
})
|
||||
|
||||
@ -71,7 +71,9 @@ class App extends React.Component<IProps, IState> {
|
||||
console.log(`unpatched game? ${unpatched}`)
|
||||
|
||||
if (!unpatched) {
|
||||
alert(`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`)
|
||||
alert(
|
||||
`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -108,45 +110,55 @@ class App extends React.Component<IProps, IState> {
|
||||
// Get custom bg AFTER theme is loaded !! important !!
|
||||
const custom_bg = await getConfigOption('customBackground')
|
||||
|
||||
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||
if(game_path) {
|
||||
if (!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||
if (game_path) {
|
||||
// Get the bg by invoking, then set the background to that bg.
|
||||
const bgLoc: string = await invoke('get_bg_file', {
|
||||
bgPath: root_path,
|
||||
appdata: await dataDir()
|
||||
appdata: await dataDir(),
|
||||
})
|
||||
|
||||
bgLoc && this.setState({
|
||||
bgFile: bgLoc
|
||||
}, this.forceUpdate)
|
||||
bgLoc &&
|
||||
this.setState(
|
||||
{
|
||||
bgFile: bgLoc,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
||||
|
||||
if (!isUrl) {
|
||||
const isValid = await invoke('dir_exists', {
|
||||
path: custom_bg
|
||||
path: custom_bg,
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
|
||||
}, this.forceUpdate)
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
} else {
|
||||
// Check if URL returns a valid image.
|
||||
const isValid = await invoke('valid_url', {
|
||||
url: custom_bg
|
||||
url: custom_bg,
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG
|
||||
}, this.forceUpdate)
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!cert_generated) {
|
||||
// Generate the certificate
|
||||
await invoke('generate_ca_files', {
|
||||
path: await dataDir() + 'cultivation'
|
||||
path: (await dataDir()) + 'cultivation',
|
||||
})
|
||||
|
||||
await setConfigOption('cert_generated', true)
|
||||
@ -155,18 +167,23 @@ class App extends React.Component<IProps, IState> {
|
||||
// Period check to only show progress bar when downloading files
|
||||
setInterval(() => {
|
||||
this.setState({
|
||||
isDownloading: downloadHandler.getDownloads().filter(d => d.status !== 'finished')?.length > 0
|
||||
isDownloading: downloadHandler.getDownloads().filter((d) => d.status !== 'finished')?.length > 0,
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App" style={
|
||||
this.state.bgFile ? {
|
||||
background: `url("${this.state.bgFile}") fixed`,
|
||||
} : {}
|
||||
}>
|
||||
<div
|
||||
className="App"
|
||||
style={
|
||||
this.state.bgFile
|
||||
? {
|
||||
background: `url("${this.state.bgFile}") fixed`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<TopBar
|
||||
optFunc={() => {
|
||||
this.setState({ optionsOpen: !this.state.optionsOpen })
|
||||
@ -199,10 +216,7 @@ class App extends React.Component<IProps, IState> {
|
||||
{
|
||||
// Download menu
|
||||
this.state.downloadsOpen ? (
|
||||
<Downloads
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ downloadsOpen: false })}
|
||||
/>
|
||||
<Downloads downloadManager={downloadHandler} closeFn={() => this.setState({ downloadsOpen: false })} />
|
||||
) : null
|
||||
}
|
||||
|
||||
@ -219,22 +233,18 @@ class App extends React.Component<IProps, IState> {
|
||||
{
|
||||
// Game downloads menu
|
||||
this.state.gameDownloadsOpen ? (
|
||||
<Game
|
||||
downloadManager={downloadHandler}
|
||||
closeFn={() => this.setState({ gameDownloadsOpen: false })}
|
||||
/>
|
||||
<Game downloadManager={downloadHandler} closeFn={() => this.setState({ gameDownloadsOpen: false })} />
|
||||
) : null
|
||||
}
|
||||
|
||||
<div className="BottomSection" id="bottomSectionContainer">
|
||||
<ServerLaunchSection />
|
||||
|
||||
<div id="DownloadProgress"
|
||||
<div
|
||||
id="DownloadProgress"
|
||||
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
|
||||
>
|
||||
{ this.state.isDownloading ?
|
||||
<MainProgressBar downloadManager={downloadHandler} />
|
||||
: null }
|
||||
{this.state.isDownloading ? <MainProgressBar downloadManager={downloadHandler} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,8 +3,8 @@ import './App.css'
|
||||
|
||||
import TopBar from './components/TopBar'
|
||||
|
||||
import {invoke} from '@tauri-apps/api/tauri'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import TextInput from './components/common/TextInput'
|
||||
|
||||
let proxyAddress = ''
|
||||
@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
|
||||
}
|
||||
|
||||
async function startProxy() {
|
||||
await invoke('connect', { port: 2222, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
|
||||
}
|
||||
|
||||
@ -24,14 +24,14 @@ async function stopProxy() {
|
||||
}
|
||||
|
||||
async function generateCertificates() {
|
||||
await invoke('generate_ca_files', { path: await dataDir() + '\\cultivation' })
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
|
||||
}
|
||||
|
||||
async function generateInfo() {
|
||||
console.log({
|
||||
certificatePath: await dataDir() + '\\cultivation\\ca',
|
||||
certificatePath: (await dataDir()) + '\\cultivation\\ca',
|
||||
isAdmin: await invoke('is_elevated'),
|
||||
connectingTo: proxyAddress
|
||||
connectingTo: proxyAddress,
|
||||
})
|
||||
alert('check your dev console and send that in #cultivation')
|
||||
}
|
||||
@ -40,7 +40,7 @@ function none() {
|
||||
alert('none')
|
||||
}
|
||||
|
||||
class Debug extends React.Component{
|
||||
class Debug extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
|
@ -4,10 +4,10 @@ import Close from '../../resources/icons/close.svg'
|
||||
import './MiniDialog.css'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode[] | React.ReactNode;
|
||||
title?: string;
|
||||
closeable?: boolean;
|
||||
closeFn: () => void;
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
title?: string
|
||||
closeable?: boolean
|
||||
closeFn: () => void
|
||||
}
|
||||
|
||||
export default class MiniDialog extends React.Component<IProps, never> {
|
||||
@ -33,13 +33,12 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
||||
render() {
|
||||
return (
|
||||
<div className="MiniDialog" id="miniDialogContainer">
|
||||
{
|
||||
this.props.closeable !== undefined && this.props.closeable ?
|
||||
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
||||
<span>{this.props?.title}</span>
|
||||
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
||||
</div> : null
|
||||
}
|
||||
{this.props.closeable !== undefined && this.props.closeable ? (
|
||||
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
|
||||
<span>{this.props?.title}</span>
|
||||
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="MiniDialogInner" id="miniDialogContent">
|
||||
{this.props.children}
|
||||
|
@ -2,7 +2,7 @@
|
||||
position: absolute;
|
||||
transform: translate(0%, 0%);
|
||||
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@ -36,14 +36,14 @@
|
||||
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||
}
|
||||
|
||||
@media(max-height: 580px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
@media (max-height: 580px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-height: 500px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 170px);
|
||||
}
|
||||
@media (max-height: 500px) {
|
||||
.RightBar {
|
||||
height: calc(100vh - 170px);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import React from 'react'
|
||||
|
||||
import Discord from '../../resources/icons/discord.svg'
|
||||
import Github from '../../resources/icons/github.svg'
|
||||
import Discord from '../../resources/icons/discord.svg'
|
||||
import Github from '../../resources/icons/github.svg'
|
||||
|
||||
import './RightBar.css'
|
||||
|
||||
|
@ -109,17 +109,17 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1040px) {
|
||||
#playButton {
|
||||
right: 5%;
|
||||
}
|
||||
#playButton {
|
||||
right: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 870px) {
|
||||
#playButton {
|
||||
min-width: 235px;
|
||||
}
|
||||
#playButton {
|
||||
min-width: 235px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
width: 40%;
|
||||
}
|
||||
#officialPlay {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
@ -11,26 +11,26 @@ import Server from '../../resources/icons/server.svg'
|
||||
import Akebi from '../../resources/icons/akebi.svg'
|
||||
|
||||
import './ServerLaunchSection.css'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { getGameExecutable } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||
|
||||
interface IState {
|
||||
grasscutterEnabled: boolean;
|
||||
buttonLabel: string;
|
||||
checkboxLabel: string;
|
||||
ip: string;
|
||||
port: string;
|
||||
grasscutterEnabled: boolean
|
||||
buttonLabel: string
|
||||
checkboxLabel: string
|
||||
ip: string
|
||||
port: string
|
||||
|
||||
ipPlaceholder: string;
|
||||
portPlaceholder: string;
|
||||
ipPlaceholder: string
|
||||
portPlaceholder: string
|
||||
|
||||
portHelpText: string;
|
||||
portHelpText: string
|
||||
|
||||
httpsLabel: string;
|
||||
httpsEnabled: boolean;
|
||||
httpsLabel: string
|
||||
httpsEnabled: boolean
|
||||
|
||||
swag: boolean;
|
||||
swag: boolean
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
@ -48,7 +48,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
portHelpText: '',
|
||||
httpsLabel: '',
|
||||
httpsEnabled: false,
|
||||
swag: false
|
||||
swag: false,
|
||||
}
|
||||
|
||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||
@ -74,7 +74,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
portHelpText: await translate('help.port_help_text'),
|
||||
httpsLabel: await translate('main.https_enable'),
|
||||
httpsEnabled: config.https_enabled || false,
|
||||
swag: config.swag_mode || false
|
||||
swag: config.swag_mode || false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
// Set state as well
|
||||
this.setState({
|
||||
grasscutterEnabled: config.toggle_grasscutter
|
||||
grasscutterEnabled: config.toggle_grasscutter,
|
||||
})
|
||||
|
||||
await saveConfig(config)
|
||||
@ -94,7 +94,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
async playGame(exe?: string, proc_name?: string) {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!await getGameExecutable()) {
|
||||
if (!(await getGameExecutable())) {
|
||||
alert('Game executable not set!')
|
||||
return
|
||||
}
|
||||
@ -117,14 +117,16 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await setConfigOption('last_port', this.state.port)
|
||||
|
||||
await invoke('enable_process_watcher', {
|
||||
process: proc_name || game_exe
|
||||
process: proc_name || game_exe,
|
||||
})
|
||||
|
||||
if (config.use_internal_proxy) {
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
|
||||
await invoke('set_proxy_addr', {
|
||||
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
|
||||
})
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
}
|
||||
|
||||
// Open server as well if the options are set
|
||||
@ -137,21 +139,23 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await invoke('run_jar', {
|
||||
path: config.grasscutter_path,
|
||||
executeIn: jarFolder,
|
||||
javaPath: config.java_path || ''
|
||||
javaPath: config.java_path || '',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const unpatched = await unpatchGame()
|
||||
|
||||
if (!unpatched) {
|
||||
alert(`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`)
|
||||
alert(
|
||||
`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Launch the program
|
||||
const gameExists = await invoke('dir_exists', {
|
||||
path: exe || config.game_install_path
|
||||
path: exe || config.game_install_path,
|
||||
})
|
||||
|
||||
if (gameExists) await invoke('run_program', { path: exe || config.game_install_path })
|
||||
@ -175,7 +179,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
await invoke('run_jar', {
|
||||
path: config.grasscutter_path,
|
||||
executeIn: jarFolder,
|
||||
javaPath: config.java_path || ''
|
||||
javaPath: config.java_path || '',
|
||||
})
|
||||
}
|
||||
|
||||
@ -194,7 +198,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
// First launch 3dm
|
||||
invoke('run_program', {
|
||||
path: config.migoto_path
|
||||
path: config.migoto_path,
|
||||
})
|
||||
|
||||
// Then play the game as normal
|
||||
@ -203,13 +207,13 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
setIp(text: string) {
|
||||
this.setState({
|
||||
ip: text
|
||||
ip: text,
|
||||
})
|
||||
}
|
||||
|
||||
setPort(text: string) {
|
||||
this.setState({
|
||||
port: text
|
||||
port: text,
|
||||
})
|
||||
}
|
||||
|
||||
@ -220,7 +224,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
|
||||
// Set state as well
|
||||
this.setState({
|
||||
httpsEnabled: config.https_enabled
|
||||
httpsEnabled: config.https_enabled,
|
||||
})
|
||||
|
||||
await saveConfig(config)
|
||||
@ -230,40 +234,59 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
return (
|
||||
<div id="playButton">
|
||||
<div id="serverControls">
|
||||
<Checkbox id="enableGC" label={this.state.checkboxLabel} onChange={this.toggleGrasscutter} checked={this.state.grasscutterEnabled}/>
|
||||
<Checkbox
|
||||
id="enableGC"
|
||||
label={this.state.checkboxLabel}
|
||||
onChange={this.toggleGrasscutter}
|
||||
checked={this.state.grasscutterEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
this.state.grasscutterEnabled && (
|
||||
<div>
|
||||
<div className="ServerConfig" id="serverConfigContainer">
|
||||
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
|
||||
<TextInput style={{
|
||||
{this.state.grasscutterEnabled && (
|
||||
<div>
|
||||
<div className="ServerConfig" id="serverConfigContainer">
|
||||
<TextInput
|
||||
id="ip"
|
||||
key="ip"
|
||||
placeholder={this.state.ipPlaceholder}
|
||||
onChange={this.setIp}
|
||||
initalValue={this.state.ip}
|
||||
/>
|
||||
<TextInput
|
||||
style={{
|
||||
width: '10%',
|
||||
}} id="port" key="port" placeholder={this.state.portPlaceholder} onChange={this.setPort} initalValue={this.state.port} />
|
||||
<HelpButton contents={this.state.portHelpText} />
|
||||
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
|
||||
</div>
|
||||
}}
|
||||
id="port"
|
||||
key="port"
|
||||
placeholder={this.state.portPlaceholder}
|
||||
onChange={this.setPort}
|
||||
initalValue={this.state.port}
|
||||
/>
|
||||
<HelpButton contents={this.state.portHelpText} />
|
||||
<Checkbox
|
||||
id="httpsEnable"
|
||||
label={this.state.httpsLabel}
|
||||
onChange={this.toggleHttps}
|
||||
checked={this.state.httpsEnabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
|
||||
{
|
||||
this.state.swag && (
|
||||
<>
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
</BigButton>
|
||||
<BigButton onClick={this.launch3dm} id="serverLaunch">
|
||||
3DM
|
||||
</BigButton>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
<BigButton onClick={this.playGame} id="officialPlay">
|
||||
{this.state.buttonLabel}
|
||||
</BigButton>
|
||||
{this.state.swag && (
|
||||
<>
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
</BigButton>
|
||||
<BigButton onClick={this.launch3dm} id="serverLaunch">
|
||||
3DM
|
||||
</BigButton>
|
||||
</>
|
||||
)}
|
||||
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||
</BigButton>
|
||||
|
@ -12,15 +12,15 @@ import './TopBar.css'
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
|
||||
interface IProps {
|
||||
optFunc: () => void;
|
||||
downFunc: () => void;
|
||||
gameFunc: () => void;
|
||||
optFunc: () => void
|
||||
downFunc: () => void
|
||||
gameFunc: () => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
version: string;
|
||||
clicks: number;
|
||||
intv: NodeJS.Timeout | null;
|
||||
version: string
|
||||
clicks: number
|
||||
intv: NodeJS.Timeout | null
|
||||
}
|
||||
|
||||
export default class TopBar extends React.Component<IProps, IState> {
|
||||
@ -30,7 +30,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
version: '0.0.0',
|
||||
clicks: 0,
|
||||
intv: null
|
||||
intv: null,
|
||||
}
|
||||
|
||||
this.activateClick = this.activateClick.bind(this)
|
||||
@ -59,7 +59,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
setTimeout(() => {
|
||||
// Gotta clear it so it goes back to regular colors
|
||||
this.setState({
|
||||
clicks: 0
|
||||
clicks: 0,
|
||||
})
|
||||
}, 600)
|
||||
|
||||
@ -75,7 +75,7 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
if (this.state.clicks < 3) {
|
||||
this.setState({
|
||||
clicks: this.state.clicks + 1,
|
||||
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500)
|
||||
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500),
|
||||
})
|
||||
|
||||
return
|
||||
@ -89,29 +89,31 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
<span data-tauri-drag-region>
|
||||
<Tr text="main.title" />
|
||||
</span>
|
||||
<span data-tauri-drag-region id="version">{this.state?.version}</span>
|
||||
<span data-tauri-drag-region id="version">
|
||||
{this.state?.version}
|
||||
</span>
|
||||
</div>
|
||||
{/**
|
||||
* HEY YOU
|
||||
*
|
||||
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
|
||||
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
|
||||
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
|
||||
*/}
|
||||
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>
|
||||
?
|
||||
</div>
|
||||
{
|
||||
/**
|
||||
* HEY YOU
|
||||
*
|
||||
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
|
||||
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
|
||||
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
|
||||
*/
|
||||
}
|
||||
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>?</div>
|
||||
<div className="TopBtns" id="topBarButtonContainer">
|
||||
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
|
||||
<div id="closeBtn" onClick={this.handleClose} className="TopButton">
|
||||
<img src={closeIcon} alt="close" />
|
||||
</div>
|
||||
<div id="minBtn" onClick={this.handleMinimize} className='TopButton'>
|
||||
<div id="minBtn" onClick={this.handleMinimize} className="TopButton">
|
||||
<img src={minIcon} alt="minimize" />
|
||||
</div>
|
||||
<div id="settingsBtn" onClick={this.props.optFunc} className='TopButton'>
|
||||
<div id="settingsBtn" onClick={this.props.optFunc} className="TopButton">
|
||||
<img src={cogBtn} alt="settings" />
|
||||
</div>
|
||||
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
|
||||
<div id="downloadsBtn" className="TopButton" onClick={this.props.downFunc}>
|
||||
<img src={downBtn} alt="downloads" />
|
||||
</div>
|
||||
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
|
||||
|
@ -2,14 +2,14 @@ import React from 'react'
|
||||
import './BigButton.css'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
onClick: () => unknown;
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode
|
||||
onClick: () => unknown
|
||||
id: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IState {
|
||||
disabled?: boolean;
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default class BigButton extends React.Component<IProps, IState> {
|
||||
@ -17,7 +17,7 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
disabled: this.props.disabled
|
||||
disabled: this.props.disabled,
|
||||
}
|
||||
|
||||
this.handleClick = this.handleClick.bind(this)
|
||||
@ -25,7 +25,7 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, _state: IState) {
|
||||
return {
|
||||
disabled: props.disabled
|
||||
disabled: props.disabled,
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,11 @@ export default class BigButton extends React.Component<IProps, IState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={'BigButton ' + (this.state.disabled ? 'disabled' : '')} onClick={this.handleClick} id={this.props.id}>
|
||||
<div
|
||||
className={'BigButton ' + (this.state.disabled ? 'disabled' : '')}
|
||||
onClick={this.handleClick}
|
||||
id={this.props.id}
|
||||
>
|
||||
<div className="BigButtonText">{this.props.children}</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
.Checkbox input[type="checkbox"] {
|
||||
.Checkbox input[type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
.CheckboxDisplay img {
|
||||
height: 100%;
|
||||
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%)
|
||||
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%);
|
||||
}
|
||||
|
||||
.Checkbox label {
|
||||
|
@ -4,9 +4,9 @@ import checkmark from '../../../resources/icons/check.svg'
|
||||
import './Checkbox.css'
|
||||
|
||||
interface IProps {
|
||||
label?: string,
|
||||
checked: boolean,
|
||||
onChange: () => void,
|
||||
label?: string
|
||||
checked: boolean
|
||||
onChange: () => void
|
||||
id: string
|
||||
}
|
||||
|
||||
@ -19,14 +19,14 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
checked: props.checked
|
||||
checked: props.checked,
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
if (props.checked !== state.checked) {
|
||||
return {
|
||||
checked: props.checked
|
||||
checked: props.checked,
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,11 +41,9 @@ export default class Checkbox extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="Checkbox">
|
||||
<input type='checkbox' id={this.props.id} checked={this.state.checked} onChange={this.handleChange} />
|
||||
<input type="checkbox" id={this.props.id} checked={this.state.checked} onChange={this.handleChange} />
|
||||
<label htmlFor={this.props.id}>
|
||||
<div className="CheckboxDisplay">
|
||||
{this.state.checked ? <img src={checkmark} alt='Checkmark' /> : null}
|
||||
</div>
|
||||
<div className="CheckboxDisplay">{this.state.checked ? <img src={checkmark} alt="Checkmark" /> : null}</div>
|
||||
<span>{this.props.label || ''}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ interface IProps {
|
||||
readonly?: boolean
|
||||
placeholder?: string
|
||||
folder?: boolean
|
||||
customClearBehaviour?: () => void,
|
||||
customClearBehaviour?: () => void
|
||||
openFolder?: string
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
value: props.value || '',
|
||||
placeholder: this.props.placeholder || 'Select file or folder...',
|
||||
folder: this.props.folder || false
|
||||
folder: this.props.folder || false,
|
||||
}
|
||||
|
||||
this.handleIconClick = this.handleIconClick.bind(this)
|
||||
@ -54,8 +54,8 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
async componentDidMount() {
|
||||
if (!this.props.placeholder) {
|
||||
const translation = await translate('components.select_file')
|
||||
this.setState( {
|
||||
placeholder: translation
|
||||
this.setState({
|
||||
placeholder: translation,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -65,15 +65,13 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
|
||||
if (this.state.folder) {
|
||||
path = await open({
|
||||
directory: true
|
||||
directory: true,
|
||||
})
|
||||
} else {
|
||||
console.log(this.props.openFolder)
|
||||
path = await open({
|
||||
filters: [
|
||||
{ name: 'Files', extensions: this.props.extensions || ['*'] }
|
||||
],
|
||||
defaultPath: this.props.openFolder
|
||||
filters: [{ name: 'Files', extensions: this.props.extensions || ['*'] }],
|
||||
defaultPath: this.props.openFolder,
|
||||
})
|
||||
}
|
||||
|
||||
@ -81,7 +79,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
if (!path) return
|
||||
|
||||
this.setState({
|
||||
value: path
|
||||
value: path,
|
||||
})
|
||||
|
||||
if (this.props.onChange) this.props.onChange(path)
|
||||
@ -89,12 +87,13 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='DirInput'>
|
||||
<div className="DirInput">
|
||||
<TextInput
|
||||
value={this.state.value}
|
||||
placeholder={this.state.placeholder}
|
||||
clearable={this.props.clearable !== undefined ? this.props.clearable : true}
|
||||
readOnly={this.props.readonly !== undefined ? this.props.readonly : true } onChange={(text: string) => {
|
||||
readOnly={this.props.readonly !== undefined ? this.props.readonly : true}
|
||||
onChange={(text: string) => {
|
||||
this.setState({ value: text })
|
||||
|
||||
if (this.props.onChange) this.props.onChange(text)
|
||||
|
@ -5,7 +5,7 @@ import DownloadSection from './DownloadSection'
|
||||
import './DownloadList.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler;
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
export default class DownloadList extends React.Component<IProps, never> {
|
||||
@ -16,17 +16,14 @@ export default class DownloadList extends React.Component<IProps, never> {
|
||||
render() {
|
||||
const list = this.props.downloadManager.getDownloads().map((download) => {
|
||||
return (
|
||||
<DownloadSection key={download.path} downloadName={download.path} downloadManager={this.props.downloadManager} />
|
||||
<DownloadSection
|
||||
key={download.path}
|
||||
downloadName={download.path}
|
||||
downloadManager={this.props.downloadManager}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<div className="DownloadList">
|
||||
{
|
||||
list.length > 0 ? list : 'No downloads present'
|
||||
}
|
||||
</div>
|
||||
)
|
||||
return <div className="DownloadList">{list.length > 0 ? list : 'No downloads present'}</div>
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ import ProgressBar from './ProgressBar'
|
||||
import './DownloadSection.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler;
|
||||
downloadName: string;
|
||||
downloadManager: DownloadHandler
|
||||
downloadName: string
|
||||
}
|
||||
|
||||
export default class DownloadSection extends React.Component<IProps, never> {
|
||||
|
@ -5,7 +5,7 @@ import Help from '../../../resources/icons/help.svg'
|
||||
import MiniDialog from '../MiniDialog'
|
||||
|
||||
interface IProps {
|
||||
children?: React.ReactNode[] | React.ReactNode;
|
||||
children?: React.ReactNode[] | React.ReactNode
|
||||
contents?: string
|
||||
id?: string
|
||||
}
|
||||
@ -19,7 +19,7 @@ export default class HelpButton extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
opened: false
|
||||
opened: false,
|
||||
}
|
||||
|
||||
this.setOpen = this.setOpen.bind(this)
|
||||
@ -41,12 +41,13 @@ export default class HelpButton extends React.Component<IProps, IState> {
|
||||
<img src={Help} />
|
||||
</div>
|
||||
|
||||
<div className="HelpContents" style={{
|
||||
display: this.state.opened ? 'block' : 'none'
|
||||
}}>
|
||||
<MiniDialog closeFn={this.setClosed}>
|
||||
{this.props.contents || this.props.children}
|
||||
</MiniDialog>
|
||||
<div
|
||||
className="HelpContents"
|
||||
style={{
|
||||
display: this.state.opened ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
<MiniDialog closeFn={this.setClosed}>{this.props.contents || this.props.children}</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -4,15 +4,15 @@ import Tr from '../../../utils/language'
|
||||
import './ProgressBar.css'
|
||||
|
||||
interface IProps {
|
||||
downloadManager: DownloadHandler,
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
average: number,
|
||||
files: number,
|
||||
extracting: number,
|
||||
total: number,
|
||||
speed: string,
|
||||
average: number
|
||||
files: number
|
||||
extracting: number
|
||||
total: number
|
||||
speed: string
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,7 +29,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
files,
|
||||
extracting,
|
||||
total: totalSize,
|
||||
speed: '0 B/s'
|
||||
speed: '0 B/s',
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,20 +51,23 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<div className="MainProgressBarWrapper">
|
||||
<div className="ProgressBar">
|
||||
<div className="InnerProgress" style={{
|
||||
width: `${(() => {
|
||||
// Handles no files downloading
|
||||
if (this.state.files === 0) {
|
||||
return '100'
|
||||
}
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles no files downloading
|
||||
if (this.state.files === 0) {
|
||||
return '100'
|
||||
}
|
||||
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return this.state.average
|
||||
})()}%`,
|
||||
}}></div>
|
||||
return this.state.average
|
||||
})()}%`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="MainProgressText">
|
||||
|
@ -1,4 +1,5 @@
|
||||
.ProgressBar, .InnerProgress {
|
||||
.ProgressBar,
|
||||
.InnerProgress {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,14 @@ import DownloadHandler from '../../../utils/download'
|
||||
import { translate } from '../../../utils/language'
|
||||
|
||||
interface IProps {
|
||||
path: string,
|
||||
downloadManager: DownloadHandler,
|
||||
path: string
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
progress: number,
|
||||
status: string,
|
||||
total: number,
|
||||
progress: number
|
||||
status: string
|
||||
total: number
|
||||
}
|
||||
|
||||
export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
@ -36,7 +36,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
const prog = this.props.downloadManager.getDownloadProgress(this.props.path)
|
||||
this.setState({
|
||||
progress: prog?.progress || 0,
|
||||
status: await translate(`download_status.${prog?.status || 'stopped'}`) || 'stopped',
|
||||
status: (await translate(`download_status.${prog?.status || 'stopped'}`)) || 'stopped',
|
||||
total: prog?.total || 0,
|
||||
})
|
||||
|
||||
@ -54,24 +54,29 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ProgressBarWrapper">
|
||||
<div style={{
|
||||
width: '80%'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: '80%',
|
||||
}}
|
||||
>
|
||||
<div className="ProgressBar">
|
||||
<div className="InnerProgress" style={{
|
||||
width: `${(() => {
|
||||
// Handles files with content-lengths of 0
|
||||
if (this.state.status === 'finished') {
|
||||
return '100'
|
||||
}
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles files with content-lengths of 0
|
||||
if (this.state.status === 'finished') {
|
||||
return '100'
|
||||
}
|
||||
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
if (this.state.total <= 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return this.state.progress / this.state.total * 100
|
||||
})()}%`,
|
||||
}}></div>
|
||||
return (this.state.progress / this.state.total) * 100
|
||||
})()}%`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="DownloadControls">
|
||||
<div onClick={this.stopDownload} className="downloadStop">
|
||||
@ -80,9 +85,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ProgressText">
|
||||
{capitalize(this.state.status) || 'Waiting'}
|
||||
</div>
|
||||
<div className="ProgressText">{capitalize(this.state.status) || 'Waiting'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -4,15 +4,15 @@ import './TextInput.css'
|
||||
import Close from '../../../resources/icons/close.svg'
|
||||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
initalValue?: string;
|
||||
placeholder?: string;
|
||||
onChange?: (value: string) => void;
|
||||
readOnly?: boolean;
|
||||
id?: string;
|
||||
clearable?: boolean;
|
||||
customClearBehaviour?: () => void;
|
||||
style?: React.CSSProperties;
|
||||
value?: string
|
||||
initalValue?: string
|
||||
placeholder?: string
|
||||
onChange?: (value: string) => void
|
||||
readOnly?: boolean
|
||||
id?: string
|
||||
clearable?: boolean
|
||||
customClearBehaviour?: () => void
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@ -24,14 +24,14 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
value: props.value || ''
|
||||
value: props.value || '',
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
if (this.props.initalValue) {
|
||||
this.setState({
|
||||
value: this.props.initalValue
|
||||
value: this.props.initalValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -43,13 +43,21 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="TextInputWrapper" style={this.props.style || {}}>
|
||||
<input id={this.props?.id} readOnly={this.props.readOnly || false} placeholder={this.props.placeholder || ''} className="TextInput" value={this.state.value} onChange={(e) => {
|
||||
this.setState({ value: e.target.value })
|
||||
if (this.props.onChange) this.props.onChange(e.target.value)
|
||||
}} />
|
||||
{
|
||||
this.props.clearable ?
|
||||
<div className="TextClear" onClick={() => {
|
||||
<input
|
||||
id={this.props?.id}
|
||||
readOnly={this.props.readOnly || false}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
className="TextInput"
|
||||
value={this.state.value}
|
||||
onChange={(e) => {
|
||||
this.setState({ value: e.target.value })
|
||||
if (this.props.onChange) this.props.onChange(e.target.value)
|
||||
}}
|
||||
/>
|
||||
{this.props.clearable ? (
|
||||
<div
|
||||
className="TextClear"
|
||||
onClick={() => {
|
||||
// Run custom behaviour first
|
||||
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
|
||||
|
||||
@ -58,10 +66,11 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
if (this.props.onChange) this.props.onChange('')
|
||||
|
||||
this.forceUpdate()
|
||||
}}>
|
||||
<img src={Close} className="TextInputClear" />
|
||||
</div> : null
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img src={Close} className="TextInputClear" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
.Divider {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -20,8 +20,8 @@ const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/bu
|
||||
const RESOURCES_DOWNLOAD = 'https://gitlab.com/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
closeFn: () => void
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@ -41,7 +41,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
grasscutter_set: false,
|
||||
resources_exist: false
|
||||
resources_exist: false,
|
||||
}
|
||||
|
||||
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
||||
@ -63,7 +63,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
if (!gc_path || gc_path === '') {
|
||||
this.setState({
|
||||
grasscutter_set: false,
|
||||
resources_exist: false
|
||||
resources_exist: false,
|
||||
})
|
||||
|
||||
return
|
||||
@ -72,15 +72,17 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
||||
|
||||
if (gc_path) {
|
||||
const resources_exist: boolean = await invoke('dir_exists', {
|
||||
path: path + '\\resources'
|
||||
}) as boolean && !(await invoke('dir_is_empty', {
|
||||
path: path + '\\resources'
|
||||
})) as boolean
|
||||
const resources_exist: boolean =
|
||||
((await invoke('dir_exists', {
|
||||
path: path + '\\resources',
|
||||
})) as boolean) &&
|
||||
(!(await invoke('dir_is_empty', {
|
||||
path: path + '\\resources',
|
||||
})) as boolean)
|
||||
|
||||
this.setState({
|
||||
grasscutter_set: gc_path !== '',
|
||||
resources_exist
|
||||
resources_exist,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -109,7 +111,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStableRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@ -118,7 +120,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterDevRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@ -127,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () => {
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@ -139,7 +141,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () => {
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@ -153,11 +155,13 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||
// Delete the existing folder if it exists
|
||||
if (await invoke('dir_exists', {
|
||||
path: folder + '\\resources'
|
||||
})) {
|
||||
if (
|
||||
await invoke('dir_exists', {
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
) {
|
||||
await invoke('dir_delete', {
|
||||
path: folder + '\\resources'
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
}
|
||||
|
||||
@ -165,7 +169,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folder + '\\Resources',
|
||||
newName: 'resources'
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
@ -190,32 +194,40 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
render() {
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} className="Downloads" heading="Downloads">
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStable">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCStable">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'
|
||||
} />
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCStable">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCStable">
|
||||
<Tr
|
||||
text={this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'}
|
||||
/>
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_stable_jar" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStable">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCStable">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterStable}
|
||||
id="grasscutterStableBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDev">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCDev">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'
|
||||
} />
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
|
||||
<Tr
|
||||
text={this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'}
|
||||
/>
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_dev_jar" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDev">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCDev">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterLatest}
|
||||
id="grasscutterLatestBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@ -223,32 +235,48 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCStableData">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCStableData">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable_data' : 'downloads.grasscutter_stable_data_update'
|
||||
} />
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set
|
||||
? 'downloads.grasscutter_stable_data'
|
||||
: 'downloads.grasscutter_stable_data_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_stable_data" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStableData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
|
||||
<BigButton
|
||||
disabled={this.state.repo_downloading}
|
||||
onClick={this.downloadGrasscutterStableRepo}
|
||||
id="grasscutterStableRepo"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerGCDevData">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelGCDevData">
|
||||
<Tr text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest_data' : 'downloads.grasscutter_latest_data_update'
|
||||
} />
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set
|
||||
? 'downloads.grasscutter_latest_data'
|
||||
: 'downloads.grasscutter_latest_data_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton>
|
||||
<Tr text="help.gc_dev_data" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDevData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCDevData">
|
||||
<BigButton
|
||||
disabled={this.state.repo_downloading}
|
||||
onClick={this.downloadGrasscutterStableRepo}
|
||||
id="grasscutterDevRepo"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@ -256,15 +284,19 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='DownloadMenuSection' id="downloadMenuContainerResources">
|
||||
<div className='DownloadLabel' id="downloadMenuLabelResources">
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelResources">
|
||||
<Tr text="downloads.resources" />
|
||||
<HelpButton>
|
||||
<Tr text="help.resources" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonResources">
|
||||
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
||||
<div className="DownloadValue" id="downloadMenuButtonResources">
|
||||
<BigButton
|
||||
disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist}
|
||||
onClick={this.downloadResources}
|
||||
id="resourcesBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
|
@ -12,14 +12,14 @@ import { unzip } from '../../../utils/zipUtils'
|
||||
const GAME_DOWNLOAD = ''
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
closeFn: () => void
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
gameDownloading: boolean;
|
||||
gameDownloadFolder: string;
|
||||
dirPlaceholder: string;
|
||||
gameDownloading: boolean
|
||||
gameDownloadFolder: string
|
||||
dirPlaceholder: string
|
||||
}
|
||||
|
||||
export default class Downloads extends React.Component<IProps, IState> {
|
||||
@ -29,7 +29,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
gameDownloading: false,
|
||||
gameDownloadFolder: '',
|
||||
dirPlaceholder: ''
|
||||
dirPlaceholder: '',
|
||||
}
|
||||
|
||||
this.downloadGame = this.downloadGame.bind(this)
|
||||
@ -37,7 +37,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async componentDidMount() {
|
||||
this.setState({
|
||||
dirPlaceholder: await translate('components.select_folder')
|
||||
dirPlaceholder: await translate('components.select_folder'),
|
||||
})
|
||||
|
||||
console.log(this.state)
|
||||
@ -45,37 +45,49 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGame() {
|
||||
const folder = this.state.gameDownloadFolder
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () =>{
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () => {
|
||||
unzip(folder + '\\game.zip', folder + '\\', () => {
|
||||
this.setState({
|
||||
gameDownloading: false
|
||||
gameDownloading: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.setState({
|
||||
gameDownloading: true
|
||||
gameDownloading: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||
<Menu heading="Download Game" closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||
<div className="GameDownload">
|
||||
{
|
||||
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ?
|
||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
|
||||
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
|
||||
}
|
||||
{this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ? (
|
||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>
|
||||
Download Game
|
||||
</BigButton>
|
||||
) : (
|
||||
<BigButton id="disabledGameBtn" onClick={() => null} disabled>
|
||||
Download Game
|
||||
</BigButton>
|
||||
)}
|
||||
<HelpButton>
|
||||
<Tr text="main.game_help_text" />
|
||||
</HelpButton>
|
||||
</div>
|
||||
|
||||
<div className="GameDownloadDir">
|
||||
<DirInput folder placeholder={this.state.dirPlaceholder} clearable={false} readonly={true} onChange={(value: string) => this.setState({
|
||||
gameDownloadFolder: value
|
||||
})}/>
|
||||
<DirInput
|
||||
folder
|
||||
placeholder={this.state.dirPlaceholder}
|
||||
clearable={false}
|
||||
readonly={true}
|
||||
onChange={(value: string) =>
|
||||
this.setState({
|
||||
gameDownloadFolder: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
)
|
||||
|
@ -4,10 +4,10 @@ import './Menu.css'
|
||||
import Close from '../../../resources/icons/close.svg'
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode[] | React.ReactNode;
|
||||
className?: string;
|
||||
heading: string;
|
||||
closeFn: () => void;
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
className?: string
|
||||
heading: string
|
||||
closeFn: () => void
|
||||
}
|
||||
|
||||
export default class Menu extends React.Component<IProps, never> {
|
||||
@ -18,13 +18,15 @@ export default class Menu extends React.Component<IProps, never> {
|
||||
render() {
|
||||
return (
|
||||
<div className={'Menu ' + this.props.className} id="menuContainer">
|
||||
<div className='MenuTop' id="menuContainerTop">
|
||||
<div className="MenuHeading" id="menuHeading">{this.props.heading}</div>
|
||||
<div className="MenuTop" id="menuContainerTop">
|
||||
<div className="MenuHeading" id="menuHeading">
|
||||
{this.props.heading}
|
||||
</div>
|
||||
<div className="MenuExit" id="menuButtonCloseContainer" onClick={this.props.closeFn}>
|
||||
<img src={Close} className="MenuClose" id="menuButtonCloseIcon" />
|
||||
</div>
|
||||
</div>
|
||||
<div className='MenuInner' id="menuContent">
|
||||
<div className="MenuInner" id="menuContent">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,8 +16,8 @@ import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/metadata'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
downloadManager: DownloadHandler;
|
||||
closeFn: () => void
|
||||
downloadManager: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@ -25,7 +25,7 @@ interface IState {
|
||||
grasscutter_path: string
|
||||
java_path: string
|
||||
grasscutter_with_game: boolean
|
||||
language_options: { [key: string]: string }[],
|
||||
language_options: { [key: string]: string }[]
|
||||
current_language: string
|
||||
bg_url_or_path: string
|
||||
themes: string[]
|
||||
@ -61,7 +61,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: '',
|
||||
migoto_path: ''
|
||||
migoto_path: '',
|
||||
}
|
||||
|
||||
this.setGameExecutable = this.setGameExecutable.bind(this)
|
||||
@ -136,7 +136,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setConfigOption('akebi_path', value)
|
||||
|
||||
this.setState({
|
||||
akebi_path: value
|
||||
akebi_path: value,
|
||||
})
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setConfigOption('migoto_path', value)
|
||||
|
||||
this.setState({
|
||||
migoto_path: value
|
||||
migoto_path: value,
|
||||
})
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
async installCert() {
|
||||
await invoke('generate_ca_files', {
|
||||
path: await dataDir() + 'cultivation'
|
||||
path: (await dataDir()) + 'cultivation',
|
||||
})
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsButtonmetaDownload">
|
||||
<BigButton onClick={this.restoreMetadata} id="metaDownload">
|
||||
<Tr text='components.download' />
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
@ -270,11 +270,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<Tr text="options.patch_metadata" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxPatchMeta">
|
||||
<Checkbox
|
||||
onChange={this.toggleMetadata}
|
||||
checked={this.state?.patch_metadata}
|
||||
id="patchMeta"
|
||||
/>
|
||||
<Checkbox onChange={this.toggleMetadata} checked={this.state?.patch_metadata} id="patchMeta" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerUseProxy">
|
||||
@ -282,18 +278,14 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<Tr text="options.use_proxy" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxUseProxy">
|
||||
<Checkbox
|
||||
onChange={this.toggleProxy}
|
||||
checked={this.state?.use_internal_proxy}
|
||||
id="useProxy"
|
||||
/>
|
||||
<Checkbox onChange={this.toggleProxy} checked={this.state?.use_internal_proxy} id="useProxy" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className='OptionSection' id="menuOptionsContainerGCJar">
|
||||
<div className='OptionLabel' id="menuOptionsLabelGCJar">
|
||||
<div className="OptionSection" id="menuOptionsContainerGCJar">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGCJar">
|
||||
<Tr text="options.grasscutter_jar" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsDirGCJar">
|
||||
@ -310,39 +302,34 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerInstallCert">
|
||||
<div className='OptionLabel' id="menuOptionsLabelInstallCert">
|
||||
<div className="OptionSection" id="menuOptionsContainerInstallCert">
|
||||
<div className="OptionLabel" id="menuOptionsLabelInstallCert">
|
||||
<Tr text="options.install_certificate" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsButtonInstallCert">
|
||||
<div className="OptionValue" id="menuOptionsButtonInstallCert">
|
||||
<BigButton disabled={false} onClick={this.installCert} id="installCert">
|
||||
<Tr text="components.install" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className='OptionSection' id="menuOptionsContainerAkebi">
|
||||
<div className='OptionLabel' id="menuOptionsLabelAkebi">
|
||||
<Tr text="swag.akebi" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirAkebi">
|
||||
<DirInput onChange={this.setAkebi} value={this.state?.akebi_path} extensions={['exe']} />
|
||||
</div>
|
||||
{this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="OptionSection" id="menuOptionsContainerAkebi">
|
||||
<div className="OptionLabel" id="menuOptionsLabelAkebi">
|
||||
<Tr text="swag.akebi" />
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerMigoto">
|
||||
<div className='OptionLabel' id="menuOptionsLabelMigoto">
|
||||
<div className="OptionSection" id="menuOptionsContainerMigoto">
|
||||
<div className="OptionLabel" id="menuOptionsLabelMigoto">
|
||||
<Tr text="swag.migoto" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirMigoto">
|
||||
<div className="OptionValue" id="menuOptionsDirMigoto">
|
||||
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
|
@ -1,97 +1,97 @@
|
||||
.NewsSection {
|
||||
background-color: rgba(106, 105, 106, 0.6);
|
||||
background-color: rgba(106, 105, 106, 0.6);
|
||||
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
|
||||
min-height: 219px;
|
||||
height: 40%;
|
||||
width: 512px;
|
||||
min-height: 219px;
|
||||
height: 40%;
|
||||
width: 512px;
|
||||
|
||||
bottom: 35%;
|
||||
left: 5%;
|
||||
bottom: 35%;
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
@media (max-width: 830px) {
|
||||
.NewsSection {
|
||||
width: 61%;
|
||||
}
|
||||
.NewsSection {
|
||||
width: 61%;
|
||||
}
|
||||
}
|
||||
|
||||
.NewsTabs {
|
||||
background-color: rgba(77, 77, 77, 0.6);
|
||||
background-color: rgba(77, 77, 77, 0.6);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
|
||||
width: 100%;
|
||||
height: 43px;
|
||||
width: 100%;
|
||||
height: 43px;
|
||||
}
|
||||
|
||||
.NewsTab {
|
||||
height: 50%;
|
||||
height: 50%;
|
||||
|
||||
margin: 0 10px;
|
||||
margin: 0 10px;
|
||||
|
||||
text-align: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.NewsTab:hover {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.NewsTab.selected {
|
||||
border-bottom: 2px solid #ffc61e;
|
||||
border-bottom: 2px solid #ffc61e;
|
||||
}
|
||||
|
||||
.NewsContent {
|
||||
display: block;
|
||||
height: calc(100% - 43px);
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
display: block;
|
||||
height: calc(100% - 43px);
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.NewsContent tbody {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.NewsContent tbody::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Commit {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
max-height: 42px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
max-height: 42px;
|
||||
}
|
||||
|
||||
.CommitAuthor {
|
||||
width: calc(100% * 0.4);
|
||||
padding-left: 5px;
|
||||
vertical-align: top;
|
||||
font-weight: bold;
|
||||
width: calc(100% * 0.4);
|
||||
padding-left: 5px;
|
||||
vertical-align: top;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.CommitMessage {
|
||||
width: calc(100% * 0.6);
|
||||
width: calc(100% * 0.6);
|
||||
}
|
||||
|
||||
.CommitMessage span {
|
||||
display: -webkit-box;
|
||||
width: 100%;
|
||||
max-height: 42px;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
display: -webkit-box;
|
||||
width: 100%;
|
||||
max-height: 42px;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
}
|
@ -6,33 +6,33 @@ import Tr from '../../../utils/language'
|
||||
import './NewsSection.css'
|
||||
|
||||
interface IProps {
|
||||
selected?: string;
|
||||
selected?: string
|
||||
}
|
||||
|
||||
interface IState {
|
||||
selected: string;
|
||||
news?: JSX.Element;
|
||||
commitList?: JSX.Element[];
|
||||
selected: string
|
||||
news?: JSX.Element
|
||||
commitList?: JSX.Element[]
|
||||
}
|
||||
|
||||
interface GrasscutterAPIResponse {
|
||||
commits: {
|
||||
gc_stable: CommitResponse[];
|
||||
gc_dev: CommitResponse[];
|
||||
cultivation: CommitResponse[];
|
||||
gc_stable: CommitResponse[]
|
||||
gc_dev: CommitResponse[]
|
||||
cultivation: CommitResponse[]
|
||||
}
|
||||
}
|
||||
|
||||
interface CommitResponse {
|
||||
sha: string;
|
||||
commit: Commit;
|
||||
sha: string
|
||||
commit: Commit
|
||||
}
|
||||
|
||||
interface Commit {
|
||||
author: {
|
||||
name: string;
|
||||
};
|
||||
message: string;
|
||||
name: string
|
||||
}
|
||||
message: string
|
||||
}
|
||||
|
||||
export default class NewsSection extends React.Component<IProps, IState> {
|
||||
@ -65,14 +65,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
|
||||
try {
|
||||
grasscutterApiResponse = JSON.parse(response)
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
grasscutterApiResponse = null
|
||||
}
|
||||
|
||||
let commits: CommitResponse[]
|
||||
if (grasscutterApiResponse?.commits == null) {
|
||||
// If it didn't work, use official API
|
||||
const response: string = await invoke('req_get', { url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits' })
|
||||
const response: string = await invoke('req_get', {
|
||||
url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits',
|
||||
})
|
||||
commits = JSON.parse(response)
|
||||
} else {
|
||||
commits = grasscutterApiResponse.commits.gc_stable
|
||||
@ -86,15 +88,19 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
const commitsListHtml = commitsList.map((commitResponse: CommitResponse) => {
|
||||
return (
|
||||
<tr className="Commit" id="newsCommitsTable" key={commitResponse.sha}>
|
||||
<td className="CommitAuthor"><span>{commitResponse.commit.author.name}</span></td>
|
||||
<td className="CommitMessage"><span>{commitResponse.commit.message}</span></td>
|
||||
<td className="CommitAuthor">
|
||||
<span>{commitResponse.commit.author.name}</span>
|
||||
</td>
|
||||
<td className="CommitMessage">
|
||||
<span>{commitResponse.commit.message}</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
this.setState({
|
||||
commitList: commitsListHtml,
|
||||
news: <>{commitsListHtml}</>
|
||||
news: <>{commitsListHtml}</>,
|
||||
})
|
||||
}
|
||||
|
||||
@ -104,7 +110,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
async showNews() {
|
||||
let news: JSX.Element | JSX.Element[] = <tr></tr>
|
||||
|
||||
switch(this.state.selected) {
|
||||
switch (this.state.selected) {
|
||||
case 'commits': {
|
||||
const commits = await this.showLatestCommits()
|
||||
if (commits != null) {
|
||||
@ -114,16 +120,24 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
case 'latest_version':
|
||||
news = <tr><td>Latest version</td></tr>
|
||||
news = (
|
||||
<tr>
|
||||
<td>Latest version</td>
|
||||
</tr>
|
||||
)
|
||||
break
|
||||
|
||||
default:
|
||||
news = <tr><td>Unknown</td></tr>
|
||||
news = (
|
||||
<tr>
|
||||
<td>Unknown</td>
|
||||
</tr>
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
this.setState({
|
||||
news: <>{news}</>
|
||||
news: <>{news}</>,
|
||||
})
|
||||
}
|
||||
|
||||
@ -131,17 +145,23 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<div className="NewsSection" id="newsContainer">
|
||||
<div className="NewsTabs" id="newsTabsContainer">
|
||||
<div className={'NewsTab ' + (this.state.selected === 'commits' ? 'selected' : '')} id="commits" onClick={() => this.setSelected('commits')}>
|
||||
<div
|
||||
className={'NewsTab ' + (this.state.selected === 'commits' ? 'selected' : '')}
|
||||
id="commits"
|
||||
onClick={() => this.setSelected('commits')}
|
||||
>
|
||||
<Tr text="news.latest_commits" />
|
||||
</div>
|
||||
<div className={'NewsTab ' + (this.state.selected === 'latest_version' ? 'selected' : '')} id="latest_version" onClick={() => this.setSelected('latest_version')}>
|
||||
<div
|
||||
className={'NewsTab ' + (this.state.selected === 'latest_version' ? 'selected' : '')}
|
||||
id="latest_version"
|
||||
onClick={() => this.setSelected('latest_version')}
|
||||
>
|
||||
<Tr text="news.latest_version" />
|
||||
</div>
|
||||
</div>
|
||||
<table className="NewsContent" id="newsContent">
|
||||
<tbody>
|
||||
{this.state.news}
|
||||
</tbody>
|
||||
<tbody>{this.state.news}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
|
@ -3,8 +3,7 @@ import { dataDir } from '@tauri-apps/api/path'
|
||||
|
||||
let configFilePath: string
|
||||
let defaultConfig: Configuration
|
||||
|
||||
(async() => {
|
||||
;(async () => {
|
||||
defaultConfig = {
|
||||
toggle_grasscutter: false,
|
||||
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game\\GenshinImpact.exe',
|
||||
@ -58,7 +57,7 @@ export async function setConfigOption<K extends keyof Configuration>(key: K, val
|
||||
const config = await getConfig()
|
||||
config[key] = value
|
||||
|
||||
await saveConfig(<Configuration> config)
|
||||
await saveConfig(<Configuration>config)
|
||||
}
|
||||
|
||||
export async function getConfigOption<K extends keyof Configuration>(key: K): Promise<Configuration[K]> {
|
||||
@ -73,8 +72,8 @@ export async function getConfig() {
|
||||
let parsed: Configuration = defaultConfig
|
||||
|
||||
try {
|
||||
parsed = <Configuration> JSON.parse(raw)
|
||||
} catch(e) {
|
||||
parsed = <Configuration>JSON.parse(raw)
|
||||
} catch (e) {
|
||||
// We could not open the file
|
||||
console.log(e)
|
||||
|
||||
@ -100,7 +99,7 @@ async function readConfigFile() {
|
||||
|
||||
if (!dirs.find((fileOrDir) => fileOrDir?.name === 'cultivation')) {
|
||||
// Create dir
|
||||
await fs.createDir(local + 'cultivation').catch(e => console.log(e))
|
||||
await fs.createDir(local + 'cultivation').catch((e) => console.log(e))
|
||||
}
|
||||
|
||||
const innerDirs = await fs.readDir(local + '/cultivation')
|
||||
@ -108,7 +107,7 @@ async function readConfigFile() {
|
||||
// Create grasscutter dir for potential installation
|
||||
if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) {
|
||||
// Create dir
|
||||
await fs.createDir(local + 'cultivation/grasscutter').catch(e => console.log(e))
|
||||
await fs.createDir(local + 'cultivation/grasscutter').catch((e) => console.log(e))
|
||||
}
|
||||
|
||||
const dataFiles = await fs.readDir(local + 'cultivation')
|
||||
@ -118,7 +117,7 @@ async function readConfigFile() {
|
||||
// Create config file
|
||||
const file: fs.FsTextFileOption = {
|
||||
path: configFilePath,
|
||||
contents: JSON.stringify(defaultConfig)
|
||||
contents: JSON.stringify(defaultConfig),
|
||||
}
|
||||
|
||||
await fs.writeFile(file)
|
||||
@ -132,6 +131,6 @@ async function writeConfigFile(raw: string) {
|
||||
// All external config functions call readConfigFile, which ensure files exists
|
||||
await fs.writeFile({
|
||||
path: configFilePath,
|
||||
contents: raw
|
||||
contents: raw,
|
||||
})
|
||||
}
|
||||
|
@ -4,15 +4,15 @@ import { byteToString } from './string'
|
||||
|
||||
export default class DownloadHandler {
|
||||
downloads: {
|
||||
path: string,
|
||||
progress: number,
|
||||
total: number,
|
||||
total_downloaded: number,
|
||||
status: string,
|
||||
startTime: number,
|
||||
error?: string,
|
||||
speed?: string,
|
||||
onFinish?: () => void,
|
||||
path: string
|
||||
progress: number
|
||||
total: number
|
||||
total_downloaded: number
|
||||
status: string
|
||||
startTime: number
|
||||
error?: string
|
||||
speed?: string
|
||||
onFinish?: () => void
|
||||
}[]
|
||||
|
||||
// Pass tauri invoke function
|
||||
@ -22,13 +22,13 @@ export default class DownloadHandler {
|
||||
listen('download_progress', ({ payload }) => {
|
||||
// @ts-expect-error Payload may be unknown but backend always returns this object
|
||||
const obj: {
|
||||
downloaded: string,
|
||||
total: string,
|
||||
path: string,
|
||||
total_downloaded: string,
|
||||
downloaded: string
|
||||
total: string
|
||||
path: string
|
||||
total_downloaded: string
|
||||
} = payload
|
||||
|
||||
const index = this.downloads.findIndex(download => download.path === obj.path)
|
||||
const index = this.downloads.findIndex((download) => download.path === obj.path)
|
||||
this.downloads[index].progress = parseInt(obj.downloaded, 10)
|
||||
this.downloads[index].total = parseInt(obj.total, 10)
|
||||
this.downloads[index].total_downloaded = parseInt(obj.total_downloaded, 10)
|
||||
@ -52,7 +52,7 @@ export default class DownloadHandler {
|
||||
const filename = payload
|
||||
|
||||
// set status to finished
|
||||
const index = this.downloads.findIndex(download => download.path === filename)
|
||||
const index = this.downloads.findIndex((download) => download.path === filename)
|
||||
this.downloads[index].status = 'finished'
|
||||
|
||||
// Call onFinish callback
|
||||
@ -65,12 +65,12 @@ export default class DownloadHandler {
|
||||
listen('download_error', ({ payload }) => {
|
||||
// @ts-expect-error shut up typescript
|
||||
const errorData: {
|
||||
path: string,
|
||||
error: string,
|
||||
path: string
|
||||
error: string
|
||||
} = payload
|
||||
|
||||
// Set download to error
|
||||
const index = this.downloads.findIndex(download => download.path === errorData.path)
|
||||
const index = this.downloads.findIndex((download) => download.path === errorData.path)
|
||||
this.downloads[index].status = 'error'
|
||||
this.downloads[index].error = errorData.error
|
||||
})
|
||||
@ -78,13 +78,13 @@ export default class DownloadHandler {
|
||||
// Extraction events
|
||||
listen('extract_start', ({ payload }) => {
|
||||
// Find the download that is no extracting and set it's status as such
|
||||
const index = this.downloads.findIndex(download => download.path === payload)
|
||||
const index = this.downloads.findIndex((download) => download.path === payload)
|
||||
this.downloads[index].status = 'extracting'
|
||||
})
|
||||
|
||||
listen('extract_end', ({ payload }) => {
|
||||
// Find the download that is no extracting and set it's status as such
|
||||
const index = this.downloads.findIndex(download => download.path === payload)
|
||||
const index = this.downloads.findIndex((download) => download.path === payload)
|
||||
this.downloads[index].status = 'finished'
|
||||
})
|
||||
}
|
||||
@ -95,16 +95,16 @@ export default class DownloadHandler {
|
||||
|
||||
downloadingJar() {
|
||||
// Kinda hacky but it works
|
||||
return this.downloads.some(d => d.path.includes('grasscutter.zip'))
|
||||
return this.downloads.some((d) => d.path.includes('grasscutter.zip'))
|
||||
}
|
||||
|
||||
downloadingResources() {
|
||||
// Kinda hacky but it works
|
||||
return this.downloads.some(d => d.path.includes('resources'))
|
||||
return this.downloads.some((d) => d.path.includes('resources'))
|
||||
}
|
||||
|
||||
downloadingRepo() {
|
||||
return this.downloads.some(d => d.path.includes('grasscutter_repo.zip'))
|
||||
return this.downloads.some((d) => d.path.includes('grasscutter_repo.zip'))
|
||||
}
|
||||
|
||||
addDownload(url: string, path: string, onFinish?: () => void) {
|
||||
@ -128,24 +128,24 @@ export default class DownloadHandler {
|
||||
invoke('stop_download', { path })
|
||||
|
||||
// Remove from list
|
||||
const index = this.downloads.findIndex(download => download.path === path)
|
||||
const index = this.downloads.findIndex((download) => download.path === path)
|
||||
this.downloads.splice(index, 1)
|
||||
}
|
||||
|
||||
getDownloadProgress(path: string) {
|
||||
const index = this.downloads.findIndex(download => download.path === path)
|
||||
const index = this.downloads.findIndex((download) => download.path === path)
|
||||
return this.downloads[index] || null
|
||||
}
|
||||
|
||||
getDownloadSize(path: string) {
|
||||
const index = this.downloads.findIndex(download => download.path === path)
|
||||
const index = this.downloads.findIndex((download) => download.path === path)
|
||||
return byteToString(this.downloads[index].total) || null
|
||||
}
|
||||
|
||||
getTotalAverage() {
|
||||
const files = this.downloads.filter(d => d.status === 'downloading')
|
||||
const files = this.downloads.filter((d) => d.status === 'downloading')
|
||||
const total = files.reduce((acc, d) => acc + d.total, 0)
|
||||
const progress = files.reduce((acc, d) => d.progress !== 0 ? acc + d.progress : acc + d.total_downloaded, 0)
|
||||
const progress = files.reduce((acc, d) => (d.progress !== 0 ? acc + d.progress : acc + d.total_downloaded), 0)
|
||||
let speedStr = '0 B/s'
|
||||
|
||||
// Get download speed based on startTimes
|
||||
@ -158,10 +158,10 @@ export default class DownloadHandler {
|
||||
|
||||
return {
|
||||
average: (progress / total) * 100 || 0,
|
||||
files: this.downloads.filter(d => d.status === 'downloading').length,
|
||||
extracting: this.downloads.filter(d => d.status === 'extracting').length,
|
||||
files: this.downloads.filter((d) => d.status === 'downloading').length,
|
||||
extracting: this.downloads.filter((d) => d.status === 'extracting').length,
|
||||
totalSize: total,
|
||||
speed: speedStr
|
||||
speed: speedStr,
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import { getConfig } from './configuration'
|
||||
export async function getGameExecutable() {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!config.game_install_path) {
|
||||
if (!config.game_install_path) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export async function getGameExecutable() {
|
||||
export async function getGameFolder() {
|
||||
const config = await getConfig()
|
||||
|
||||
if(!config.game_install_path) {
|
||||
if (!config.game_install_path) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,12 @@ import React from 'react'
|
||||
import { getConfigOption } from './configuration'
|
||||
|
||||
interface IProps {
|
||||
text: string;
|
||||
text: string
|
||||
}
|
||||
|
||||
interface IState {
|
||||
language: string;
|
||||
translated_text: string;
|
||||
language: string
|
||||
translated_text: string
|
||||
}
|
||||
|
||||
export default class Tr extends React.Component<IProps, IState> {
|
||||
@ -28,7 +28,7 @@ export default class Tr extends React.Component<IProps, IState> {
|
||||
if (!language) language = 'en'
|
||||
|
||||
invoke('get_lang', { lang: language }).then((response) => {
|
||||
const translation_obj = JSON.parse(response as string || '{}')
|
||||
const translation_obj = JSON.parse((response as string) || '{}')
|
||||
|
||||
// Traversal
|
||||
if (text.includes('.')) {
|
||||
@ -39,7 +39,7 @@ export default class Tr extends React.Component<IProps, IState> {
|
||||
if (!translation) {
|
||||
translation = ''
|
||||
} else {
|
||||
translation = typeof translation !== 'string' ? translation[keys[i]] : translation as string
|
||||
translation = typeof translation !== 'string' ? translation[keys[i]] : (translation as string)
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ export default class Tr extends React.Component<IProps, IState> {
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
translated_text: translation_obj[text] || ''
|
||||
translated_text: translation_obj[text] || '',
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -62,13 +62,13 @@ export default class Tr extends React.Component<IProps, IState> {
|
||||
|
||||
export async function getLanguages() {
|
||||
const resp: {
|
||||
[key: string]: string;
|
||||
[key: string]: string
|
||||
} = await invoke('get_languages')
|
||||
const lang_list: {
|
||||
[key: string]: string;
|
||||
[key: string]: string
|
||||
}[] = []
|
||||
|
||||
Object.keys(resp).forEach(k => {
|
||||
Object.keys(resp).forEach((k) => {
|
||||
const parsed = JSON.parse(resp[k])
|
||||
|
||||
if (parsed.lang_name) {
|
||||
@ -80,8 +80,8 @@ export async function getLanguages() {
|
||||
}
|
||||
|
||||
export async function translate(text: string) {
|
||||
const language = await getConfigOption('language') || 'en'
|
||||
const translation_json = JSON.parse(await invoke('get_lang', { lang: language }) || '{}')
|
||||
const language = (await getConfigOption('language')) || 'en'
|
||||
const translation_json = JSON.parse((await invoke('get_lang', { lang: language })) || '{}')
|
||||
|
||||
// Traversal
|
||||
if (text.includes('.')) {
|
||||
@ -92,7 +92,7 @@ export async function translate(text: string) {
|
||||
if (!translation) {
|
||||
translation = ''
|
||||
} else {
|
||||
translation = typeof translation !== 'string' ? translation[keys[i]] : translation as string
|
||||
translation = typeof translation !== 'string' ? translation[keys[i]] : (translation as string)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { getGameExecutable, getGameFolder } from './game'
|
||||
|
||||
export async function patchMetadata() {
|
||||
const metadataExists = await invoke('dir_exists', {
|
||||
path: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
path: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!metadataExists) {
|
||||
@ -16,9 +16,9 @@ export async function patchMetadata() {
|
||||
|
||||
// Copy unpatched metadata to backup location
|
||||
const copiedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: await getGameMetadataPath() + '\\global-metadata.dat',
|
||||
path: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
newPath: await getBackupMetadataPath(),
|
||||
newName: 'global-metadata-unpatched.dat'
|
||||
newName: 'global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!copiedMeta) {
|
||||
@ -42,9 +42,9 @@ export async function patchMetadata() {
|
||||
console.log('Replacing unpatched game metadata with patched metadata')
|
||||
|
||||
const replacedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!replacedMeta) {
|
||||
@ -58,7 +58,7 @@ export async function patchMetadata() {
|
||||
|
||||
export async function patchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
@ -72,7 +72,7 @@ export async function patchGame() {
|
||||
|
||||
// Do we have a patch already?
|
||||
const patchedExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
})
|
||||
|
||||
if (!patchedExists) {
|
||||
@ -86,8 +86,8 @@ export async function patchGame() {
|
||||
|
||||
// Are we already patched? If so, that's fine, just continue as normal
|
||||
const gameIsPatched = await invoke('are_files_identical', {
|
||||
path1: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
path2: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
path1: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
path2: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
if (gameIsPatched) {
|
||||
@ -96,17 +96,17 @@ export async function patchGame() {
|
||||
|
||||
// Is the current backup the same as the games current metadata?
|
||||
const backupIsCurrent = await invoke('are_files_identical', {
|
||||
path1: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat',
|
||||
path2: await getGameMetadataPath() + '\\global-metadata.dat'
|
||||
path1: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
path2: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
// Game has probably been updated. We need to repatch the game...
|
||||
if (!backupIsCurrent) {
|
||||
const deletedOldBackup = await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
const deletedOldPatched = await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
})
|
||||
|
||||
// It's fine if these deletes fail. The game will be replaced anyway.
|
||||
@ -134,9 +134,9 @@ export async function patchGame() {
|
||||
|
||||
// Finally, replace the unpatched metadata with the patched one
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-patched.dat',
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!replaced) {
|
||||
@ -148,7 +148,7 @@ export async function patchGame() {
|
||||
|
||||
export async function unpatchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
@ -157,9 +157,9 @@ export async function unpatchGame() {
|
||||
}
|
||||
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat',
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat'
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
return replaced
|
||||
@ -172,20 +172,26 @@ export async function getGameMetadataPath() {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder() + '\\' + gameExec.replace('.exe', '_Data') + '\\Managed\\Metadata').replace(/\\/g, '/')
|
||||
return ((await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data') + '\\Managed\\Metadata').replace(
|
||||
/\\/g,
|
||||
'/'
|
||||
)
|
||||
}
|
||||
|
||||
export async function getBackupMetadataPath() {
|
||||
return await dataDir() + 'cultivation\\metadata'
|
||||
return (await dataDir()) + 'cultivation\\metadata'
|
||||
}
|
||||
|
||||
export async function globalMetadataLink() {
|
||||
const versionAPIUrl = 'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
const versionAPIUrl =
|
||||
'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
|
||||
// Get versions from API
|
||||
const versions = JSON.parse(await invoke('web_get', {
|
||||
url: versionAPIUrl
|
||||
}))
|
||||
const versions = JSON.parse(
|
||||
await invoke('web_get', {
|
||||
url: versionAPIUrl,
|
||||
})
|
||||
)
|
||||
|
||||
if (!versions || versions.retcode !== 0) {
|
||||
console.log('Failed to get versions from API')
|
||||
@ -195,7 +201,7 @@ export async function globalMetadataLink() {
|
||||
// Get latest version
|
||||
const latest = versions.data.game.latest
|
||||
|
||||
return latest.decompressed_path as string + '/GenshinImpact_Data/Managed/Metadata/global-metadata.dat'
|
||||
return (latest.decompressed_path as string) + '/GenshinImpact_Data/Managed/Metadata/global-metadata.dat'
|
||||
}
|
||||
|
||||
export async function restoreMetadata(manager: DownloadHandler) {
|
||||
@ -208,16 +214,16 @@ export async function restoreMetadata(manager: DownloadHandler) {
|
||||
|
||||
// Should make sure metadata path exists since the user may have deleted it
|
||||
await invoke('dir_create', {
|
||||
path: await getBackupMetadataPath()
|
||||
path: await getBackupMetadataPath(),
|
||||
})
|
||||
|
||||
// It is possible the unpatched backup is mistakenly patched
|
||||
await invoke('delete_file', {
|
||||
path: await getBackupMetadataPath() + '\\global-metadata-unpatched.dat'
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
// Download the file
|
||||
manager.addDownload(metaLink, await getBackupMetadataPath() + '\\global-metadata-unpatched.dat', () => {
|
||||
manager.addDownload(metaLink, (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat', () => {
|
||||
unpatchGame()
|
||||
})
|
||||
console.log('Restoring backedup metadata')
|
||||
|
@ -4,10 +4,12 @@ export async function toggleEncryption(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
path,
|
||||
}))
|
||||
} catch(e) {
|
||||
serverConf = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path,
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(`Server config at ${path} not found or invalid`)
|
||||
return
|
||||
}
|
||||
@ -28,10 +30,12 @@ export async function encryptionEnabled(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
path,
|
||||
}))
|
||||
} catch(e) {
|
||||
serverConf = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path,
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(`Server config at ${path} not found or invalid`)
|
||||
return false
|
||||
}
|
||||
|
@ -33,15 +33,15 @@ const defaultTheme = {
|
||||
description: 'Default theme',
|
||||
includes: {
|
||||
css: [],
|
||||
js: []
|
||||
js: [],
|
||||
},
|
||||
path: 'default'
|
||||
path: 'default',
|
||||
}
|
||||
export async function getThemeList() {
|
||||
// Do some invoke to backend to get the theme list
|
||||
const themes = await invoke('get_theme_list', {
|
||||
dataDir: `${await dataDir()}/cultivation`
|
||||
}) as BackendThemeList[]
|
||||
const themes = (await invoke('get_theme_list', {
|
||||
dataDir: `${await dataDir()}/cultivation`,
|
||||
})) as BackendThemeList[]
|
||||
const list: ThemeList[] = [
|
||||
// ALWAYS include default theme
|
||||
{
|
||||
@ -50,13 +50,13 @@ export async function getThemeList() {
|
||||
description: 'Default theme',
|
||||
includes: {
|
||||
css: [],
|
||||
js: []
|
||||
js: [],
|
||||
},
|
||||
path: 'default'
|
||||
}
|
||||
path: 'default',
|
||||
},
|
||||
]
|
||||
|
||||
themes.forEach(t => {
|
||||
themes.forEach((t) => {
|
||||
let obj
|
||||
|
||||
try {
|
||||
@ -74,7 +74,7 @@ export async function getThemeList() {
|
||||
export async function getTheme(name: string) {
|
||||
const themes = await getThemeList()
|
||||
|
||||
return themes.find(t => t.name === name) || defaultTheme
|
||||
return themes.find((t) => t.name === name) || defaultTheme
|
||||
}
|
||||
|
||||
export async function loadTheme(theme: ThemeList, document: Document) {
|
||||
@ -89,7 +89,7 @@ export async function loadTheme(theme: ThemeList, document: Document) {
|
||||
const jsIncludes = theme.includes.js
|
||||
|
||||
// Load CSS files
|
||||
cssIncludes.forEach(css => {
|
||||
cssIncludes.forEach((css) => {
|
||||
if (!css) return
|
||||
|
||||
const link = document.createElement('link')
|
||||
@ -100,7 +100,7 @@ export async function loadTheme(theme: ThemeList, document: Document) {
|
||||
})
|
||||
|
||||
// Load JS files
|
||||
jsIncludes.forEach(js => {
|
||||
jsIncludes.forEach((js) => {
|
||||
if (!js) return
|
||||
|
||||
const script = document.createElement('script')
|
||||
@ -125,7 +125,7 @@ export async function loadTheme(theme: ThemeList, document: Document) {
|
||||
// Save the background to our data dir
|
||||
await invoke('copy_file', {
|
||||
path: theme.path + '/' + theme.customBackgroundPath,
|
||||
newPath: bgPath
|
||||
newPath: bgPath,
|
||||
})
|
||||
|
||||
// Set the background
|
||||
|
@ -7,7 +7,7 @@ export function unzip(file: string, dest: string, onFinish?: () => void) {
|
||||
destpath: dest,
|
||||
})
|
||||
|
||||
listen('extract_end', ({payload}) => {
|
||||
listen('extract_end', ({ payload }) => {
|
||||
if (payload === file && onFinish) {
|
||||
onFinish()
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@ -20,7 +16,5 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user