Run prettier formatter

This commit is contained in:
Brian Bowman
2022-07-19 04:37:38 -05:00
parent e9df0f17db
commit eb9aa34323
67 changed files with 1157 additions and 1071 deletions

View File

@@ -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;
}

View File

@@ -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)
isDebug && reportWebVitals(console.log)

View File

@@ -22,7 +22,8 @@ select:focus {
border-bottom-color: #ffd326;
}
#root, .App {
#root,
.App {
height: 100%;
}
@@ -64,8 +65,8 @@ select:focus {
}
.arrow-down {
width: 0;
height: 0;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 50px solid transparent;
@@ -82,28 +83,28 @@ select:focus {
.BottomSection {
position: absolute;
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
width: 100%;
height: 160px;
width: 100%;
height: 160px;
backdrop-filter: blur(10px);
box-shadow: inset 0px 5px 12px -3px rgb(50 50 50 / 75%);
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
@media(max-height: 580px) {
.BottomSection {
height: 150px;
}
@media (max-height: 580px) {
.BottomSection {
height: 150px;
}
}
@media(max-height: 500px) {
.BottomSection {
height: 140px;
}
}
@media (max-height: 500px) {
.BottomSection {
height: 140px;
}
}

View File

@@ -25,16 +25,16 @@ import { getTheme, loadTheme } from '../utils/themes'
import { unpatchGame } from '../utils/metadata'
interface IProps {
[key: string]: never;
[key: string]: never
}
interface IState {
isDownloading: boolean;
optionsOpen: boolean;
miniDownloadsOpen: boolean;
downloadsOpen: boolean;
gameDownloadsOpen: boolean;
bgFile: string;
isDownloading: boolean
optionsOpen: boolean
miniDownloadsOpen: boolean
downloadsOpen: boolean
gameDownloadsOpen: boolean
bgFile: string
}
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
@@ -57,7 +57,7 @@ class App extends React.Component<IProps, IState> {
console.log(payload)
})
listen('jar_extracted', ({ payload }: { payload: string}) => {
listen('jar_extracted', ({ payload }: { payload: string }) => {
setConfigOption('grasscutter_path', payload)
})
@@ -69,9 +69,11 @@ class App extends React.Component<IProps, IState> {
const unpatched = await unpatchGame()
console.log(`unpatched game? ${unpatched}`)
if (!unpatched) {
alert(`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`)
alert(
`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`
)
}
}
})
@@ -108,45 +110,55 @@ class App extends React.Component<IProps, IState> {
// Get custom bg AFTER theme is loaded !! important !!
const custom_bg = await getConfigOption('customBackground')
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
if(game_path) {
if (!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
if (game_path) {
// Get the bg by invoking, then set the background to that bg.
const bgLoc: string = await invoke('get_bg_file', {
bgPath: root_path,
appdata: await dataDir()
appdata: await dataDir(),
})
bgLoc && this.setState({
bgFile: bgLoc
}, this.forceUpdate)
bgLoc &&
this.setState(
{
bgFile: bgLoc,
},
this.forceUpdate
)
}
} else {
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
if (!isUrl) {
const isValid = await invoke('dir_exists', {
path: custom_bg
path: custom_bg,
})
this.setState({
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
}, this.forceUpdate)
this.setState(
{
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
},
this.forceUpdate
)
} else {
// Check if URL returns a valid image.
const isValid = await invoke('valid_url', {
url: custom_bg
url: custom_bg,
})
this.setState({
bgFile: isValid ? custom_bg : DEFAULT_BG
}, this.forceUpdate)
this.setState(
{
bgFile: isValid ? custom_bg : DEFAULT_BG,
},
this.forceUpdate
)
}
}
if (!cert_generated) {
// Generate the certificate
await invoke('generate_ca_files', {
path: await dataDir() + 'cultivation'
path: (await dataDir()) + 'cultivation',
})
await setConfigOption('cert_generated', true)
@@ -155,18 +167,23 @@ class App extends React.Component<IProps, IState> {
// Period check to only show progress bar when downloading files
setInterval(() => {
this.setState({
isDownloading: downloadHandler.getDownloads().filter(d => d.status !== 'finished')?.length > 0
isDownloading: downloadHandler.getDownloads().filter((d) => d.status !== 'finished')?.length > 0,
})
}, 1000)
}
render() {
return (
<div className="App" style={
this.state.bgFile ? {
background: `url("${this.state.bgFile}") fixed`,
} : {}
}>
<div
className="App"
style={
this.state.bgFile
? {
background: `url("${this.state.bgFile}") fixed`,
}
: {}
}
>
<TopBar
optFunc={() => {
this.setState({ optionsOpen: !this.state.optionsOpen })
@@ -199,10 +216,7 @@ class App extends React.Component<IProps, IState> {
{
// Download menu
this.state.downloadsOpen ? (
<Downloads
downloadManager={downloadHandler}
closeFn={() => this.setState({ downloadsOpen: false })}
/>
<Downloads downloadManager={downloadHandler} closeFn={() => this.setState({ downloadsOpen: false })} />
) : null
}
@@ -219,22 +233,18 @@ class App extends React.Component<IProps, IState> {
{
// Game downloads menu
this.state.gameDownloadsOpen ? (
<Game
downloadManager={downloadHandler}
closeFn={() => this.setState({ gameDownloadsOpen: false })}
/>
<Game downloadManager={downloadHandler} closeFn={() => this.setState({ gameDownloadsOpen: false })} />
) : null
}
<div className="BottomSection" id="bottomSectionContainer">
<ServerLaunchSection />
<div id="DownloadProgress"
<div
id="DownloadProgress"
onClick={() => this.setState({ miniDownloadsOpen: !this.state.miniDownloadsOpen })}
>
{ this.state.isDownloading ?
<MainProgressBar downloadManager={downloadHandler} />
: null }
{this.state.isDownloading ? <MainProgressBar downloadManager={downloadHandler} /> : null}
</div>
</div>
</div>

View File

@@ -3,8 +3,8 @@ import './App.css'
import TopBar from './components/TopBar'
import {invoke} from '@tauri-apps/api/tauri'
import {dataDir} from '@tauri-apps/api/path'
import { invoke } from '@tauri-apps/api/tauri'
import { dataDir } from '@tauri-apps/api/path'
import TextInput from './components/common/TextInput'
let proxyAddress = ''
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
}
async function startProxy() {
await invoke('connect', { port: 2222, certificatePath: await dataDir() + '\\cultivation\\ca' })
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
}
@@ -24,14 +24,14 @@ async function stopProxy() {
}
async function generateCertificates() {
await invoke('generate_ca_files', { path: await dataDir() + '\\cultivation' })
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
}
async function generateInfo() {
console.log({
certificatePath: await dataDir() + '\\cultivation\\ca',
certificatePath: (await dataDir()) + '\\cultivation\\ca',
isAdmin: await invoke('is_elevated'),
connectingTo: proxyAddress
connectingTo: proxyAddress,
})
alert('check your dev console and send that in #cultivation')
}
@@ -40,7 +40,7 @@ function none() {
alert('none')
}
class Debug extends React.Component{
class Debug extends React.Component {
render() {
return (
<div className="App">
@@ -55,4 +55,4 @@ class Debug extends React.Component{
}
}
export default Debug
export default Debug

View File

@@ -1,7 +1,7 @@
.MiniDialog {
position: fixed;
z-index: 99;
/* Len and width */
height: 30%;
width: 30%;
@@ -32,4 +32,4 @@
.MiniDialog .ProgressText {
color: #000;
}
}

View File

@@ -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> {
@@ -19,7 +19,7 @@ export default class MiniDialog extends React.Component<IProps, never> {
document.addEventListener('mousedown', (evt) => {
const tgt = evt.target as HTMLElement
const isInside = tgt.closest('.MiniDialog') !== null
if (!isInside) {
this.props.closeFn()
}
@@ -33,13 +33,12 @@ export default class MiniDialog extends React.Component<IProps, never> {
render() {
return (
<div className="MiniDialog" id="miniDialogContainer">
{
this.props.closeable !== undefined && this.props.closeable ?
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
<span>{this.props?.title}</span>
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
</div> : null
}
{this.props.closeable !== undefined && this.props.closeable ? (
<div className="MiniDialogTop" id="miniDialogContainerTop" onClick={this.props.closeFn}>
<span>{this.props?.title}</span>
<img src={Close} className="MiniDialogClose" id="miniDialogButtonClose" />
</div>
) : null}
<div className="MiniDialogInner" id="miniDialogContent">
{this.props.children}
@@ -47,4 +46,4 @@ export default class MiniDialog extends React.Component<IProps, never> {
</div>
)
}
}
}

View File

@@ -2,7 +2,7 @@
position: absolute;
transform: translate(0%, 0%);
display:flex;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
@@ -36,14 +36,14 @@
filter: invert(75%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
}
@media(max-height: 580px) {
.RightBar {
height: calc(100vh - 180px);
}
@media (max-height: 580px) {
.RightBar {
height: calc(100vh - 180px);
}
}
@media(max-height: 500px) {
.RightBar {
height: calc(100vh - 170px);
}
}
@media (max-height: 500px) {
.RightBar {
height: calc(100vh - 170px);
}
}

View File

@@ -1,8 +1,8 @@
import { invoke } from '@tauri-apps/api'
import React from 'react'
import Discord from '../../resources/icons/discord.svg'
import Github from '../../resources/icons/github.svg'
import Discord from '../../resources/icons/discord.svg'
import Github from '../../resources/icons/github.svg'
import './RightBar.css'
@@ -28,4 +28,4 @@ export default class RightBar extends React.Component {
</div>
)
}
}
}

View File

@@ -109,17 +109,17 @@
}
@media (max-width: 1040px) {
#playButton {
right: 5%;
}
#playButton {
right: 5%;
}
}
@media (max-width: 870px) {
#playButton {
min-width: 235px;
}
#playButton {
min-width: 235px;
}
#officialPlay {
width: 40%;
}
#officialPlay {
width: 40%;
}
}

View File

@@ -11,26 +11,26 @@ import Server from '../../resources/icons/server.svg'
import Akebi from '../../resources/icons/akebi.svg'
import './ServerLaunchSection.css'
import {dataDir} from '@tauri-apps/api/path'
import { dataDir } from '@tauri-apps/api/path'
import { getGameExecutable } from '../../utils/game'
import { patchGame, unpatchGame } from '../../utils/metadata'
interface IState {
grasscutterEnabled: boolean;
buttonLabel: string;
checkboxLabel: string;
ip: string;
port: string;
grasscutterEnabled: boolean
buttonLabel: string
checkboxLabel: string
ip: string
port: string
ipPlaceholder: string;
portPlaceholder: string;
ipPlaceholder: string
portPlaceholder: string
portHelpText: string;
portHelpText: string
httpsLabel: string;
httpsEnabled: boolean;
httpsLabel: string
httpsEnabled: boolean
swag: boolean;
swag: boolean
}
export default class ServerLaunchSection extends React.Component<{}, IState> {
@@ -48,7 +48,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
portHelpText: '',
httpsLabel: '',
httpsEnabled: false,
swag: false
swag: false,
}
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
@@ -74,7 +74,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
portHelpText: await translate('help.port_help_text'),
httpsLabel: await translate('main.https_enable'),
httpsEnabled: config.https_enabled || false,
swag: config.swag_mode || false
swag: config.swag_mode || false,
})
}
@@ -85,7 +85,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
// Set state as well
this.setState({
grasscutterEnabled: config.toggle_grasscutter
grasscutterEnabled: config.toggle_grasscutter,
})
await saveConfig(config)
@@ -94,11 +94,11 @@ 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
return
}
// Connect to proxy
if (config.toggle_grasscutter) {
if (config.patch_metadata) {
@@ -117,14 +117,16 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
await setConfigOption('last_port', this.state.port)
await invoke('enable_process_watcher', {
process: proc_name || game_exe
process: proc_name || game_exe,
})
if (config.use_internal_proxy) {
// Set IP
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
await invoke('set_proxy_addr', {
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
})
// Connect to proxy
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
}
// Open server as well if the options are set
@@ -137,21 +139,23 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
await invoke('run_jar', {
path: config.grasscutter_path,
executeIn: jarFolder,
javaPath: config.java_path || ''
javaPath: config.java_path || '',
})
}
} else {
const unpatched = await unpatchGame()
if (!unpatched) {
alert(`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`)
alert(
`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`
)
return
}
}
// Launch the program
const gameExists = await invoke('dir_exists', {
path: exe || config.game_install_path
path: exe || config.game_install_path,
})
if (gameExists) await invoke('run_program', { path: exe || config.game_install_path })
@@ -175,7 +179,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
await invoke('run_jar', {
path: config.grasscutter_path,
executeIn: jarFolder,
javaPath: config.java_path || ''
javaPath: config.java_path || '',
})
}
@@ -194,7 +198,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
// First launch 3dm
invoke('run_program', {
path: config.migoto_path
path: config.migoto_path,
})
// Then play the game as normal
@@ -203,13 +207,13 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
setIp(text: string) {
this.setState({
ip: text
ip: text,
})
}
setPort(text: string) {
this.setState({
port: text
port: text,
})
}
@@ -220,7 +224,7 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
// Set state as well
this.setState({
httpsEnabled: config.https_enabled
httpsEnabled: config.https_enabled,
})
await saveConfig(config)
@@ -230,40 +234,59 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
return (
<div id="playButton">
<div id="serverControls">
<Checkbox id="enableGC" label={this.state.checkboxLabel} onChange={this.toggleGrasscutter} checked={this.state.grasscutterEnabled}/>
<Checkbox
id="enableGC"
label={this.state.checkboxLabel}
onChange={this.toggleGrasscutter}
checked={this.state.grasscutterEnabled}
/>
</div>
{
this.state.grasscutterEnabled && (
<div>
<div className="ServerConfig" id="serverConfigContainer">
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
<TextInput style={{
{this.state.grasscutterEnabled && (
<div>
<div className="ServerConfig" id="serverConfigContainer">
<TextInput
id="ip"
key="ip"
placeholder={this.state.ipPlaceholder}
onChange={this.setIp}
initalValue={this.state.ip}
/>
<TextInput
style={{
width: '10%',
}} id="port" key="port" placeholder={this.state.portPlaceholder} onChange={this.setPort} initalValue={this.state.port} />
<HelpButton contents={this.state.portHelpText} />
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
</div>
}}
id="port"
key="port"
placeholder={this.state.portPlaceholder}
onChange={this.setPort}
initalValue={this.state.port}
/>
<HelpButton contents={this.state.portHelpText} />
<Checkbox
id="httpsEnable"
label={this.state.httpsLabel}
onChange={this.toggleHttps}
checked={this.state.httpsEnabled}
/>
</div>
)
}
</div>
)}
<div className="ServerLaunchButtons" id="serverLaunchContainer">
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
{
this.state.swag && (
<>
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
</BigButton>
<BigButton onClick={this.launch3dm} id="serverLaunch">
3DM
</BigButton>
</>
)
}
<BigButton onClick={this.playGame} id="officialPlay">
{this.state.buttonLabel}
</BigButton>
{this.state.swag && (
<>
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
</BigButton>
<BigButton onClick={this.launch3dm} id="serverLaunch">
3DM
</BigButton>
</>
)}
<BigButton onClick={this.launchServer} id="serverLaunch">
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
</BigButton>
@@ -271,4 +294,4 @@ export default class ServerLaunchSection extends React.Component<{}, IState> {
</div>
)
}
}
}

View File

@@ -51,4 +51,4 @@
to {
transform: rotate(360deg);
}
}
}

View File

@@ -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,10 +59,10 @@ 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)
// Activate... SWAG MODE
await setConfigOption('swag_mode', true)
@@ -75,7 +75,7 @@ export default class TopBar extends React.Component<IProps, IState> {
if (this.state.clicks < 3) {
this.setState({
clicks: this.state.clicks + 1,
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500)
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500),
})
return
@@ -89,29 +89,31 @@ export default class TopBar extends React.Component<IProps, IState> {
<span data-tauri-drag-region>
<Tr text="main.title" />
</span>
<span data-tauri-drag-region id="version">{this.state?.version}</span>
<span data-tauri-drag-region id="version">
{this.state?.version}
</span>
</div>
{/**
* HEY YOU
*
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
*/}
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>
?
</div>
{
/**
* HEY YOU
*
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
*/
}
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>?</div>
<div className="TopBtns" id="topBarButtonContainer">
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
<div id="closeBtn" onClick={this.handleClose} className="TopButton">
<img src={closeIcon} alt="close" />
</div>
<div id="minBtn" onClick={this.handleMinimize} className='TopButton'>
<div id="minBtn" onClick={this.handleMinimize} className="TopButton">
<img src={minIcon} alt="minimize" />
</div>
<div id="settingsBtn" onClick={this.props.optFunc} className='TopButton'>
<div id="settingsBtn" onClick={this.props.optFunc} className="TopButton">
<img src={cogBtn} alt="settings" />
</div>
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
<div id="downloadsBtn" className="TopButton" onClick={this.props.downFunc}>
<img src={downBtn} alt="downloads" />
</div>
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
@@ -121,4 +123,4 @@ export default class TopBar extends React.Component<IProps, IState> {
</div>
)
}
}
}

View File

@@ -27,4 +27,4 @@
.BigButton.disabled:hover {
background: linear-gradient(#949494, #9c9c9c);
}
}

View File

@@ -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,9 +37,13 @@ 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>
)
}
}
}

View File

@@ -1,4 +1,4 @@
.Checkbox input[type="checkbox"] {
.Checkbox input[type='checkbox'] {
display: none;
}
@@ -17,10 +17,10 @@
.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 {
display: flex;
flex-direction: row;
}
}

View File

@@ -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,14 +41,12 @@ 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>
)
}
}
}

View File

@@ -24,4 +24,4 @@
.FileSelectIcon img {
height: 100%;
}
}

View File

@@ -14,7 +14,7 @@ interface IProps {
readonly?: boolean
placeholder?: string
folder?: boolean
customClearBehaviour?: () => void,
customClearBehaviour?: () => void
openFolder?: string
}
@@ -31,7 +31,7 @@ export default class DirInput extends React.Component<IProps, IState> {
this.state = {
value: props.value || '',
placeholder: this.props.placeholder || 'Select file or folder...',
folder: this.props.folder || false
folder: this.props.folder || false,
}
this.handleIconClick = this.handleIconClick.bind(this)
@@ -54,8 +54,8 @@ export default class DirInput extends React.Component<IProps, IState> {
async componentDidMount() {
if (!this.props.placeholder) {
const translation = await translate('components.select_file')
this.setState( {
placeholder: translation
this.setState({
placeholder: translation,
})
}
}
@@ -65,15 +65,13 @@ export default class DirInput extends React.Component<IProps, IState> {
if (this.state.folder) {
path = await open({
directory: true
directory: true,
})
} else {
console.log(this.props.openFolder)
path = await open({
filters: [
{ name: 'Files', extensions: this.props.extensions || ['*'] }
],
defaultPath: this.props.openFolder
filters: [{ name: 'Files', extensions: this.props.extensions || ['*'] }],
defaultPath: this.props.openFolder,
})
}
@@ -81,7 +79,7 @@ export default class DirInput extends React.Component<IProps, IState> {
if (!path) return
this.setState({
value: path
value: path,
})
if (this.props.onChange) this.props.onChange(path)
@@ -89,12 +87,13 @@ export default class DirInput extends React.Component<IProps, IState> {
render() {
return (
<div className='DirInput'>
<div className="DirInput">
<TextInput
value={this.state.value}
placeholder={this.state.placeholder}
clearable={this.props.clearable !== undefined ? this.props.clearable : true}
readOnly={this.props.readonly !== undefined ? this.props.readonly : true } onChange={(text: string) => {
readOnly={this.props.readonly !== undefined ? this.props.readonly : true}
onChange={(text: string) => {
this.setState({ value: text })
if (this.props.onChange) this.props.onChange(text)
@@ -108,4 +107,4 @@ export default class DirInput extends React.Component<IProps, IState> {
</div>
)
}
}
}

View File

@@ -4,4 +4,4 @@
flex: 1;
overflow-y: auto;
padding: 10px;
}
}

View File

@@ -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>
}
}
}

View File

@@ -26,4 +26,4 @@
.DownloadStatus {
text-align: right;
}
}

View File

@@ -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> {
@@ -32,4 +32,4 @@ export default class DownloadSection extends React.Component<IProps, never> {
</div>
)
}
}
}

View File

@@ -30,4 +30,4 @@
right: -450%;
width: 200px;
height: 120px;
}
}

View File

@@ -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,14 +41,15 @@ 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>
)
}
}
}

View File

@@ -4,15 +4,15 @@ import Tr from '../../../utils/language'
import './ProgressBar.css'
interface IProps {
downloadManager: DownloadHandler,
downloadManager: DownloadHandler
}
interface IState {
average: number,
files: number,
extracting: number,
total: number,
speed: string,
average: number
files: number
extracting: number
total: number
speed: string
}
/**
@@ -29,7 +29,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
files,
extracting,
total: totalSize,
speed: '0 B/s'
speed: '0 B/s',
}
}
@@ -51,20 +51,23 @@ export default class ProgressBar extends React.Component<IProps, IState> {
return (
<div className="MainProgressBarWrapper">
<div className="ProgressBar">
<div className="InnerProgress" style={{
width: `${(() => {
// Handles no files downloading
if (this.state.files === 0) {
return '100'
}
<div
className="InnerProgress"
style={{
width: `${(() => {
// Handles no files downloading
if (this.state.files === 0) {
return '100'
}
if (this.state.total <= 0) {
return '0'
}
if (this.state.total <= 0) {
return '0'
}
return this.state.average
})()}%`,
}}></div>
return this.state.average
})()}%`,
}}
></div>
</div>
<div className="MainProgressText">
@@ -75,4 +78,4 @@ export default class ProgressBar extends React.Component<IProps, IState> {
</div>
)
}
}
}

View File

@@ -1,4 +1,5 @@
.ProgressBar, .InnerProgress {
.ProgressBar,
.InnerProgress {
border-radius: 4px;
}
@@ -91,4 +92,4 @@
.downloadStop:hover {
cursor: pointer;
}
}

View File

@@ -1,20 +1,20 @@
import React from 'react'
import { capitalize } from '../../../utils/string'
import Stop from '../../../resources/icons/close.svg'
import Stop from '../../../resources/icons/close.svg'
import './ProgressBar.css'
import DownloadHandler from '../../../utils/download'
import { translate } from '../../../utils/language'
interface IProps {
path: string,
downloadManager: DownloadHandler,
path: string
downloadManager: DownloadHandler
}
interface IState {
progress: number,
status: string,
total: number,
progress: number
status: string
total: number
}
export default class ProgressBar extends React.Component<IProps, IState> {
@@ -36,7 +36,7 @@ export default class ProgressBar extends React.Component<IProps, IState> {
const prog = this.props.downloadManager.getDownloadProgress(this.props.path)
this.setState({
progress: prog?.progress || 0,
status: await translate(`download_status.${prog?.status || 'stopped'}`) || 'stopped',
status: (await translate(`download_status.${prog?.status || 'stopped'}`)) || 'stopped',
total: prog?.total || 0,
})
@@ -54,24 +54,29 @@ export default class ProgressBar extends React.Component<IProps, IState> {
render() {
return (
<div className="ProgressBarWrapper">
<div style={{
width: '80%'
}}>
<div
style={{
width: '80%',
}}
>
<div className="ProgressBar">
<div className="InnerProgress" style={{
width: `${(() => {
// Handles files with content-lengths of 0
if (this.state.status === 'finished') {
return '100'
}
<div
className="InnerProgress"
style={{
width: `${(() => {
// Handles files with content-lengths of 0
if (this.state.status === 'finished') {
return '100'
}
if (this.state.total <= 0) {
return '0'
}
if (this.state.total <= 0) {
return '0'
}
return this.state.progress / this.state.total * 100
})()}%`,
}}></div>
return (this.state.progress / this.state.total) * 100
})()}%`,
}}
></div>
</div>
<div className="DownloadControls">
<div onClick={this.stopDownload} className="downloadStop">
@@ -80,10 +85,8 @@ 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>
)
}
}
}

View File

@@ -17,7 +17,7 @@
display: inline-block;
position: absolute;
right: 16%;
filter: invert(99%) sepia(0%) saturate(1188%) hue-rotate(186deg) brightness(97%) contrast(67%);
}
@@ -28,4 +28,4 @@
.TextInputClear {
height: 100%;
}
}

View File

@@ -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,26 +43,35 @@ export default class TextInput extends React.Component<IProps, IState> {
render() {
return (
<div className="TextInputWrapper" style={this.props.style || {}}>
<input id={this.props?.id} readOnly={this.props.readOnly || false} placeholder={this.props.placeholder || ''} className="TextInput" value={this.state.value} onChange={(e) => {
this.setState({ value: e.target.value })
if (this.props.onChange) this.props.onChange(e.target.value)
}} />
{
this.props.clearable ?
<div className="TextClear" onClick={() => {
<input
id={this.props?.id}
readOnly={this.props.readOnly || false}
placeholder={this.props.placeholder || ''}
className="TextInput"
value={this.state.value}
onChange={(e) => {
this.setState({ value: e.target.value })
if (this.props.onChange) this.props.onChange(e.target.value)
}}
/>
{this.props.clearable ? (
<div
className="TextClear"
onClick={() => {
// Run custom behaviour first
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
this.setState({ value: '' })
if (this.props.onChange) this.props.onChange('')
this.forceUpdate()
}}>
<img src={Close} className="TextInputClear" />
</div> : null
}
}}
>
<img src={Close} className="TextInputClear" />
</div>
) : null}
</div>
)
}
}
}

View File

@@ -1,4 +1,3 @@
.Divider {
display: flex;
flex-direction: row;
@@ -13,4 +12,4 @@
.DividerLine {
width: 60%;
border-top: 1px solid #ccc;
}
}

View File

@@ -10,4 +10,4 @@ export default class Divider extends React.Component {
</div>
)
}
}
}

View File

@@ -28,4 +28,4 @@
.DownloadMenuSection .HelpButton img {
filter: none;
}
}

View File

@@ -20,8 +20,8 @@ const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/bu
const RESOURCES_DOWNLOAD = 'https://gitlab.com/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip'
interface IProps {
closeFn: () => void;
downloadManager: DownloadHandler;
closeFn: () => void
downloadManager: DownloadHandler
}
interface IState {
@@ -41,7 +41,7 @@ export default class Downloads extends React.Component<IProps, IState> {
resources_downloading: this.props.downloadManager.downloadingResources(),
repo_downloading: this.props.downloadManager.downloadingRepo(),
grasscutter_set: false,
resources_exist: false
resources_exist: false,
}
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
@@ -63,7 +63,7 @@ export default class Downloads extends React.Component<IProps, IState> {
if (!gc_path || gc_path === '') {
this.setState({
grasscutter_set: false,
resources_exist: false
resources_exist: false,
})
return
@@ -72,15 +72,17 @@ export default class Downloads extends React.Component<IProps, IState> {
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
if (gc_path) {
const resources_exist: boolean = await invoke('dir_exists', {
path: path + '\\resources'
}) as boolean && !(await invoke('dir_is_empty', {
path: path + '\\resources'
})) as boolean
const resources_exist: boolean =
((await invoke('dir_exists', {
path: path + '\\resources',
})) as boolean) &&
(!(await invoke('dir_is_empty', {
path: path + '\\resources',
})) as boolean)
this.setState({
grasscutter_set: gc_path !== '',
resources_exist
resources_exist,
})
}
}
@@ -109,7 +111,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStableRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
})
@@ -118,7 +120,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterDevRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
})
@@ -127,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStable() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () => {
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
})
@@ -135,11 +137,11 @@ export default class Downloads extends React.Component<IProps, IState> {
this.downloadGrasscutterStableRepo()
this.toggleButtons()
}
}
async downloadGrasscutterLatest() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () => {
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
})
@@ -152,12 +154,14 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadResources() {
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'
})) {
// Delete the existing folder if it exists
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>
@@ -272,4 +304,4 @@ export default class Downloads extends React.Component<IProps, IState> {
</Menu>
)
}
}
}

View File

@@ -34,4 +34,4 @@
.GameDownloadDir .DirInput .TextInputWrapper input {
width: 80%;
}
}

View File

@@ -12,14 +12,14 @@ import { unzip } from '../../../utils/zipUtils'
const GAME_DOWNLOAD = ''
interface IProps {
closeFn: () => void;
downloadManager: DownloadHandler;
closeFn: () => void
downloadManager: DownloadHandler
}
interface IState {
gameDownloading: boolean;
gameDownloadFolder: string;
dirPlaceholder: string;
gameDownloading: boolean
gameDownloadFolder: string
dirPlaceholder: string
}
export default class Downloads extends React.Component<IProps, IState> {
@@ -29,7 +29,7 @@ export default class Downloads extends React.Component<IProps, IState> {
this.state = {
gameDownloading: false,
gameDownloadFolder: '',
dirPlaceholder: ''
dirPlaceholder: '',
}
this.downloadGame = this.downloadGame.bind(this)
@@ -37,7 +37,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async componentDidMount() {
this.setState({
dirPlaceholder: await translate('components.select_folder')
dirPlaceholder: await translate('components.select_folder'),
})
console.log(this.state)
@@ -45,39 +45,51 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGame() {
const folder = this.state.gameDownloadFolder
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () =>{
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () => {
unzip(folder + '\\game.zip', folder + '\\', () => {
this.setState({
gameDownloading: false
gameDownloading: false,
})
})
})
this.setState({
gameDownloading: true
gameDownloading: true,
})
}
render() {
return (
<Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
<Menu heading="Download Game" closeFn={this.props.closeFn} className="GameDownloadMenu">
<div className="GameDownload">
{
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ?
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
}
{this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ? (
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>
Download Game
</BigButton>
) : (
<BigButton id="disabledGameBtn" onClick={() => null} disabled>
Download Game
</BigButton>
)}
<HelpButton>
<Tr text="main.game_help_text" />
</HelpButton>
</div>
<div className="GameDownloadDir">
<DirInput folder placeholder={this.state.dirPlaceholder} clearable={false} readonly={true} onChange={(value: string) => this.setState({
gameDownloadFolder: value
})}/>
<DirInput
folder
placeholder={this.state.dirPlaceholder}
clearable={false}
readonly={true}
onChange={(value: string) =>
this.setState({
gameDownloadFolder: value,
})
}
/>
</div>
</Menu>
)
}
}
}

View File

@@ -12,7 +12,7 @@
background: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.2);
overflow-y: auto;
@@ -52,4 +52,4 @@
.MenuExit img {
height: 100%;
}
}

View File

@@ -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,16 +18,18 @@ 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>
)
}
}
}

View File

@@ -15,4 +15,4 @@
.OptionSection .BigButtonText {
font-size: 12px;
}
}

View File

@@ -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,
})
}
@@ -217,10 +217,10 @@ export default class Options extends React.Component<IProps, IState> {
console.log(this.props)
await meta.restoreMetadata(this.props.downloadManager)
}
async installCert() {
await invoke('generate_ca_files', {
path: await dataDir() + 'cultivation'
path: (await dataDir()) + 'cultivation',
})
}
@@ -261,7 +261,7 @@ export default class Options extends React.Component<IProps, IState> {
</div>
<div className="OptionValue" id="menuOptionsButtonmetaDownload">
<BigButton onClick={this.restoreMetadata} id="metaDownload">
<Tr text='components.download' />
<Tr text="components.download" />
</BigButton>
</div>
</div>
@@ -270,11 +270,7 @@ export default class Options extends React.Component<IProps, IState> {
<Tr text="options.patch_metadata" />
</div>
<div className="OptionValue" id="menuOptionsCheckboxPatchMeta">
<Checkbox
onChange={this.toggleMetadata}
checked={this.state?.patch_metadata}
id="patchMeta"
/>
<Checkbox onChange={this.toggleMetadata} checked={this.state?.patch_metadata} id="patchMeta" />
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerUseProxy">
@@ -282,18 +278,14 @@ export default class Options extends React.Component<IProps, IState> {
<Tr text="options.use_proxy" />
</div>
<div className="OptionValue" id="menuOptionsCheckboxUseProxy">
<Checkbox
onChange={this.toggleProxy}
checked={this.state?.use_internal_proxy}
id="useProxy"
/>
<Checkbox onChange={this.toggleProxy} checked={this.state?.use_internal_proxy} id="useProxy" />
</div>
</div>
<Divider />
<div className='OptionSection' id="menuOptionsContainerGCJar">
<div className='OptionLabel' id="menuOptionsLabelGCJar">
<div className="OptionSection" id="menuOptionsContainerGCJar">
<div className="OptionLabel" id="menuOptionsLabelGCJar">
<Tr text="options.grasscutter_jar" />
</div>
<div className="OptionValue" id="menuOptionsDirGCJar">
@@ -310,39 +302,34 @@ export default class Options extends React.Component<IProps, IState> {
</BigButton>
</div>
</div>
<div className='OptionSection' id="menuOptionsContainerInstallCert">
<div className='OptionLabel' id="menuOptionsLabelInstallCert">
<div className="OptionSection" id="menuOptionsContainerInstallCert">
<div className="OptionLabel" id="menuOptionsLabelInstallCert">
<Tr text="options.install_certificate" />
</div>
<div className='OptionValue' id="menuOptionsButtonInstallCert">
<div className="OptionValue" id="menuOptionsButtonInstallCert">
<BigButton disabled={false} onClick={this.installCert} id="installCert">
<Tr text="components.install" />
</BigButton>
</div>
</div>
{
this.state.swag && (
<>
<Divider />
<div className='OptionSection' id="menuOptionsContainerAkebi">
<div className='OptionLabel' id="menuOptionsLabelAkebi">
<Tr text="swag.akebi" />
</div>
<div className='OptionValue' id="menuOptionsDirAkebi">
<DirInput onChange={this.setAkebi} value={this.state?.akebi_path} extensions={['exe']} />
</div>
{this.state.swag && (
<>
<Divider />
<div className="OptionSection" id="menuOptionsContainerAkebi">
<div className="OptionLabel" id="menuOptionsLabelAkebi">
<Tr text="swag.akebi" />
</div>
<div className='OptionSection' id="menuOptionsContainerMigoto">
<div className='OptionLabel' id="menuOptionsLabelMigoto">
<div className="OptionSection" id="menuOptionsContainerMigoto">
<div className="OptionLabel" id="menuOptionsLabelMigoto">
<Tr text="swag.migoto" />
</div>
<div className='OptionValue' id="menuOptionsDirMigoto">
<div className="OptionValue" id="menuOptionsDirMigoto">
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
</div>
</div>
</>
)
}
</div>
</>
)}
<Divider />

View File

@@ -1,97 +1,97 @@
.NewsSection {
background-color: rgba(106, 105, 106, 0.6);
background-color: rgba(106, 105, 106, 0.6);
position: absolute;
position: absolute;
min-height: 219px;
height: 40%;
width: 512px;
min-height: 219px;
height: 40%;
width: 512px;
bottom: 35%;
left: 5%;
bottom: 35%;
left: 5%;
}
@media (max-width: 830px) {
.NewsSection {
width: 61%;
}
.NewsSection {
width: 61%;
}
}
.NewsTabs {
background-color: rgba(77, 77, 77, 0.6);
background-color: rgba(77, 77, 77, 0.6);
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
font-weight: bold;
color: #fff;
font-weight: bold;
color: #fff;
width: 100%;
height: 43px;
width: 100%;
height: 43px;
}
.NewsTab {
height: 50%;
height: 50%;
margin: 0 10px;
margin: 0 10px;
text-align: center;
border-bottom: 1px solid transparent;
text-align: center;
border-bottom: 1px solid transparent;
}
.NewsTab:hover {
cursor: pointer;
cursor: pointer;
}
.NewsTab.selected {
border-bottom: 2px solid #ffc61e;
border-bottom: 2px solid #ffc61e;
}
.NewsContent {
display: block;
height: calc(100% - 43px);
width: 100%;
color: #fff;
display: block;
height: calc(100% - 43px);
width: 100%;
color: #fff;
}
.NewsContent tbody {
display: inline-block;
height: 100%;
width: 100%;
display: inline-block;
height: 100%;
width: 100%;
overflow-y: auto;
scrollbar-width: none;
overflow-y: auto;
scrollbar-width: none;
}
.NewsContent tbody::-webkit-scrollbar {
display: none;
display: none;
}
.Commit {
margin: 0;
color: #fff;
max-height: 42px;
margin: 0;
color: #fff;
max-height: 42px;
}
.CommitAuthor {
width: calc(100% * 0.4);
padding-left: 5px;
vertical-align: top;
font-weight: bold;
width: calc(100% * 0.4);
padding-left: 5px;
vertical-align: top;
font-weight: bold;
}
.CommitMessage {
width: calc(100% * 0.6);
width: calc(100% * 0.6);
}
.CommitMessage span {
display: -webkit-box;
width: 100%;
max-height: 42px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 5px;
}
display: -webkit-box;
width: 100%;
max-height: 42px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 5px;
}

View File

@@ -6,33 +6,33 @@ import Tr from '../../../utils/language'
import './NewsSection.css'
interface IProps {
selected?: string;
selected?: string
}
interface IState {
selected: string;
news?: JSX.Element;
commitList?: JSX.Element[];
selected: string
news?: JSX.Element
commitList?: JSX.Element[]
}
interface GrasscutterAPIResponse {
commits: {
gc_stable: CommitResponse[];
gc_dev: CommitResponse[];
cultivation: CommitResponse[];
gc_stable: CommitResponse[]
gc_dev: CommitResponse[]
cultivation: CommitResponse[]
}
}
interface CommitResponse {
sha: string;
commit: Commit;
sha: string
commit: Commit
}
interface Commit {
author: {
name: string;
};
message: string;
name: string
}
message: string
}
export default class NewsSection extends React.Component<IProps, IState> {
@@ -65,14 +65,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
try {
grasscutterApiResponse = JSON.parse(response)
} catch(e) {
} catch (e) {
grasscutterApiResponse = null
}
let commits: CommitResponse[]
if (grasscutterApiResponse?.commits == null) {
// If it didn't work, use official API
const response: string = await invoke('req_get', { url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits' })
const response: string = await invoke('req_get', {
url: 'https://api.github.com/repos/Grasscutters/Grasscutter/commits',
})
commits = JSON.parse(response)
} else {
commits = grasscutterApiResponse.commits.gc_stable
@@ -80,21 +82,25 @@ export default class NewsSection extends React.Component<IProps, IState> {
// Probably rate-limited
if (!Array.isArray(commits)) return
// Get only first 5
const commitsList = commits.slice(0, 10)
const commitsListHtml = commitsList.map((commitResponse: CommitResponse) => {
return (
<tr className="Commit" id="newsCommitsTable" key={commitResponse.sha}>
<td className="CommitAuthor"><span>{commitResponse.commit.author.name}</span></td>
<td className="CommitMessage"><span>{commitResponse.commit.message}</span></td>
<td className="CommitAuthor">
<span>{commitResponse.commit.author.name}</span>
</td>
<td className="CommitMessage">
<span>{commitResponse.commit.message}</span>
</td>
</tr>
)
})
this.setState({
commitList: commitsListHtml,
news: <>{commitsListHtml}</>
news: <>{commitsListHtml}</>,
})
}
@@ -104,7 +110,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
async showNews() {
let news: JSX.Element | JSX.Element[] = <tr></tr>
switch(this.state.selected) {
switch (this.state.selected) {
case 'commits': {
const commits = await this.showLatestCommits()
if (commits != null) {
@@ -114,16 +120,24 @@ export default class NewsSection extends React.Component<IProps, IState> {
}
case 'latest_version':
news = <tr><td>Latest version</td></tr>
news = (
<tr>
<td>Latest version</td>
</tr>
)
break
default:
news = <tr><td>Unknown</td></tr>
news = (
<tr>
<td>Unknown</td>
</tr>
)
break
}
this.setState({
news: <>{news}</>
news: <>{news}</>,
})
}
@@ -131,19 +145,25 @@ 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>
)
}
}
}

View File

@@ -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',
@@ -57,8 +56,8 @@ export interface Configuration {
export async function setConfigOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {
const config = await getConfig()
config[key] = value
await saveConfig(<Configuration> config)
await saveConfig(<Configuration>config)
}
export async function getConfigOption<K extends keyof Configuration>(key: K): Promise<Configuration[K]> {
@@ -71,13 +70,13 @@ export async function getConfigOption<K extends keyof Configuration>(key: K): Pr
export async function getConfig() {
const raw = await readConfigFile()
let parsed: Configuration = defaultConfig
try {
parsed = <Configuration> JSON.parse(raw)
} catch(e) {
parsed = <Configuration>JSON.parse(raw)
} catch (e) {
// We could not open the file
console.log(e)
// TODO: Create a popup saying the config file is corrupted.
}
@@ -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,13 +117,13 @@ async function readConfigFile() {
// Create config file
const file: fs.FsTextFileOption = {
path: configFilePath,
contents: JSON.stringify(defaultConfig)
contents: JSON.stringify(defaultConfig),
}
await fs.writeFile(file)
}
// Finally, read the file
// Finally, read the file
return await fs.readTextFile(configFilePath)
}
@@ -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,
})
}

View File

@@ -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,
}
}
}
}

View File

@@ -3,7 +3,7 @@ import { getConfig } from './configuration'
export async function getGameExecutable() {
const config = await getConfig()
if(!config.game_install_path) {
if (!config.game_install_path) {
return null
}
@@ -14,7 +14,7 @@ export async function getGameExecutable() {
export async function getGameFolder() {
const config = await getConfig()
if(!config.game_install_path) {
if (!config.game_install_path) {
return null
}
@@ -24,4 +24,4 @@ export async function getGameFolder() {
const path = pathArr.join('/')
return path
}
}

View File

@@ -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,9 +80,9 @@ 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('.')) {
const keys = text.split('.')
@@ -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)
}
}
@@ -100,4 +100,4 @@ export async function translate(text: string) {
} else {
return translation_json[text] || ''
}
}
}

View File

@@ -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) {
@@ -82,12 +82,12 @@ export async function patchGame() {
if (!patched) {
return false
}
}
}
// 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,21 +172,27 @@ 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')
return null
@@ -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')

View File

@@ -4,10 +4,12 @@ export async function toggleEncryption(path: string) {
let serverConf
try {
serverConf = JSON.parse(await invoke('read_file', {
path,
}))
} catch(e) {
serverConf = JSON.parse(
await invoke('read_file', {
path,
})
)
} catch (e) {
console.log(`Server config at ${path} not found or invalid`)
return
}
@@ -28,13 +30,15 @@ export async function encryptionEnabled(path: string) {
let serverConf
try {
serverConf = JSON.parse(await invoke('read_file', {
path,
}))
} catch(e) {
serverConf = JSON.parse(
await invoke('read_file', {
path,
})
)
} catch (e) {
console.log(`Server config at ${path} not found or invalid`)
return false
}
return serverConf.server.http.encryption.useEncryption
}
}

View File

@@ -8,4 +8,4 @@ export function byteToString(bytes: number) {
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10)
if (i === 0) return `${bytes} ${sizes[i]}`
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`
}
}

View File

@@ -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
@@ -139,4 +139,4 @@ export async function loadTheme(theme: ThemeList, document: Document) {
await setConfigOption('customBackground', config.customBackground)
return
}
}

View File

@@ -7,9 +7,9 @@ export function unzip(file: string, dest: string, onFinish?: () => void) {
destpath: dest,
})
listen('extract_end', ({payload}) => {
listen('extract_end', ({ payload }) => {
if (payload === file && onFinish) {
onFinish()
}
})
}
}