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
@ -4,12 +4,7 @@
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
@ -18,10 +13,7 @@
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"warn",
|
||||
|
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.
|
||||
|
29
THEMES.md
29
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
|
||||
|
||||
@ -17,7 +17,7 @@ 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. |
|
||||
@ -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,10 +1,11 @@
|
||||
# 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 |
|
||||
@ -97,10 +98,11 @@ This does not include commonly used components (buttons, divider lines, commit a
|
||||
| `#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 |
|
||||
|
@ -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>
|
||||
|
@ -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%;
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
@ -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\\)`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -113,40 +115,50 @@ class App extends React.Component<IProps, IState> {
|
||||
// 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 ? {
|
||||
<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>
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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 ?
|
||||
{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>
|
||||
) : null}
|
||||
|
||||
<div className="MiniDialogInner" id="miniDialogContent">
|
||||
{this.props.children}
|
||||
|
@ -16,21 +16,21 @@ 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,29 +234,50 @@ 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 && (
|
||||
{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={{
|
||||
<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} />
|
||||
}}
|
||||
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} />
|
||||
<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.playGame} id="officialPlay">
|
||||
{this.state.buttonLabel}
|
||||
</BigButton>
|
||||
{this.state.swag && (
|
||||
<>
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
@ -261,9 +286,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
|
||||
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>
|
||||
*/}
|
||||
<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)
|
||||
@ -55,7 +55,7 @@ export default class DirInput extends React.Component<IProps, IState> {
|
||||
if (!this.props.placeholder) {
|
||||
const translation = await translate('components.select_file')
|
||||
this.setState({
|
||||
placeholder: translation
|
||||
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,7 +51,9 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<div className="MainProgressBarWrapper">
|
||||
<div className="ProgressBar">
|
||||
<div className="InnerProgress" style={{
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles no files downloading
|
||||
if (this.state.files === 0) {
|
||||
@ -64,7 +66,8 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
|
||||
return this.state.average
|
||||
})()}%`,
|
||||
}}></div>
|
||||
}}
|
||||
></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,11 +54,15 @@ 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={{
|
||||
<div
|
||||
className="InnerProgress"
|
||||
style={{
|
||||
width: `${(() => {
|
||||
// Handles files with content-lengths of 0
|
||||
if (this.state.status === 'finished') {
|
||||
@ -69,9 +73,10 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return this.state.progress / this.state.total * 100
|
||||
return (this.state.progress / this.state.total) * 100
|
||||
})()}%`,
|
||||
}}></div>
|
||||
}}
|
||||
></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) => {
|
||||
<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={() => {
|
||||
}}
|
||||
/>
|
||||
{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
|
||||
}
|
||||
</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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -48,34 +48,46 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
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 && (
|
||||
{this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className='OptionSection' id="menuOptionsContainerAkebi">
|
||||
<div className='OptionLabel' id="menuOptionsLabelAkebi">
|
||||
<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>
|
||||
</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 />
|
||||
|
||||
|
@ -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> {
|
||||
@ -72,7 +72,9 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
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}</>,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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',
|
||||
@ -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,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,9 +4,11 @@ export async function toggleEncryption(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
serverConf = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path,
|
||||
}))
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(`Server config at ${path} not found or invalid`)
|
||||
return
|
||||
@ -28,9 +30,11 @@ export async function encryptionEnabled(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(await invoke('read_file', {
|
||||
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
|
||||
|
@ -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