mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-13 07:34:36 +01:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # src/ui/App.tsx
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
use crate::system_helpers::*;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_lang(window: tauri::Window, lang: String) -> String {
|
||||
let lang = lang.to_lowercase();
|
||||
|
||||
// Send contents of language file back
|
||||
let contents = match std::fs::read_to_string(format!("./lang/{}.json", lang)) {
|
||||
let contents = match std::fs::read_to_string(format!("{}/lang/{}.json", install_location(), lang)) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
emit_lang_err(window, format!("Failed to read language file: {}", e));
|
||||
@@ -19,7 +21,7 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
|
||||
// for each lang file, set the key as the filename and the value as the lang_name contained in the file
|
||||
let mut languages = std::collections::HashMap::new();
|
||||
|
||||
let mut lang_files = std::fs::read_dir("./lang").unwrap();
|
||||
let mut lang_files = std::fs::read_dir(format!("{}/lang", install_location())).unwrap();
|
||||
|
||||
while let Some(entry) = lang_files.next() {
|
||||
let entry = entry.unwrap();
|
||||
|
||||
@@ -29,6 +29,12 @@ lazy_static! {
|
||||
fn main() {
|
||||
process_watcher();
|
||||
|
||||
// Make BG folder if it doesn't exist
|
||||
let bg_folder = format!("{}/bg", system_helpers::install_location());
|
||||
if !std::path::Path::new(&bg_folder).exists() {
|
||||
std::fs::create_dir_all(&bg_folder).unwrap();
|
||||
}
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
@@ -42,6 +48,7 @@ fn main() {
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::copy_file,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
@@ -50,7 +57,8 @@ fn main() {
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages
|
||||
lang::get_languages,
|
||||
web::valid_url
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
@@ -136,7 +144,8 @@ async fn req_get(url: String) -> String {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_bg_file(bg_path: String) -> String {
|
||||
async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||
let copy_loc = appdata;
|
||||
let query = web::query("https://api.grasscutters.xyz/cultivation/query").await;
|
||||
let response_data: APIQuery = match serde_json::from_str(&query) {
|
||||
Ok(data) => data,
|
||||
@@ -149,9 +158,8 @@ async fn get_bg_file(bg_path: String) -> String {
|
||||
let file_name = response_data.bg_file.to_string();
|
||||
|
||||
// First we see if the file already exists in our local bg folder.
|
||||
if file_helpers::dir_exists(format!(".\\bg\\{}", file_name).as_str()) {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
return format!("{}\\{}", cwd.display(), response_data.bg_file.as_str());
|
||||
if file_helpers::dir_exists(format!("{}\\bg\\{}", copy_loc, file_name).as_str()) {
|
||||
return format!("{}\\{}", copy_loc, response_data.bg_file.as_str());
|
||||
}
|
||||
|
||||
// Now we check if the bg folder, which is one directory above the game_path, exists.
|
||||
@@ -169,13 +177,12 @@ async fn get_bg_file(bg_path: String) -> String {
|
||||
}
|
||||
|
||||
// The image exists, lets copy it to our local '\bg' folder.
|
||||
let bg_img_path_local = format!(".\\bg\\{}", file_name.as_str());
|
||||
let bg_img_path_local = format!("{}\\bg\\{}", copy_loc, file_name.as_str());
|
||||
|
||||
return match std::fs::copy(bg_img_path, bg_img_path_local) {
|
||||
Ok(_) => {
|
||||
// Copy was successful, lets return true.
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
format!("{}\\{}", cwd.display(), response_data.bg_file.as_str())
|
||||
format!("{}\\{}", copy_loc, response_data.bg_file.as_str())
|
||||
}
|
||||
Err(e) => {
|
||||
// Copy failed, lets return false
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::process::Command;
|
||||
use tauri;
|
||||
use open;
|
||||
|
||||
use crate::file_helpers;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_program(path: String) {
|
||||
// Open the program from the specified path.
|
||||
@@ -54,3 +56,32 @@ pub fn open_in_browser(url: String) {
|
||||
Err(e) => println!("Failed to open URL: {}", e),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
let filename = &path.split("/").last().unwrap();
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !file_helpers::dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
std::fs::create_dir_all(&new_path).unwrap();
|
||||
}
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path, format!("{}/{}", new_path, filename)) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install_location() -> String {
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
|
||||
// Get the path to the executable.
|
||||
exe_path.pop();
|
||||
|
||||
return exe_path.to_str().unwrap().to_string();
|
||||
}
|
||||
@@ -5,4 +5,14 @@ pub(crate) async fn query(site: &str) -> String {
|
||||
|
||||
let response = client.get(site).header(USER_AGENT, "cultivation").send().await.unwrap();
|
||||
return response.text().await.unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn valid_url(url: String) -> bool {
|
||||
// Check if we get a 200 response
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = client.get(url).header(USER_AGENT, "cultivation").send().await.unwrap();
|
||||
|
||||
return response.status().as_str() == "200";
|
||||
}
|
||||
@@ -18,6 +18,15 @@
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
},
|
||||
"protocol": {
|
||||
"all": true,
|
||||
"asset": true,
|
||||
"assetScope": [
|
||||
"$DATA",
|
||||
"$DATA/cultivation",
|
||||
"$DATA/cultivation/*"
|
||||
]
|
||||
},
|
||||
"all": true
|
||||
},
|
||||
"bundle": {
|
||||
@@ -54,7 +63,7 @@
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
|
||||
@@ -27,11 +27,8 @@ select:focus {
|
||||
}
|
||||
|
||||
.App {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
|
||||
.TopButton {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { getConfigOption, setConfigOption } from '../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
|
||||
interface IProps {
|
||||
[key: string]: never;
|
||||
@@ -34,6 +35,8 @@ interface IState {
|
||||
bgFile: string;
|
||||
}
|
||||
|
||||
const DEFAULT_BG = 'https://api.grasscutters.xyz/content/bgfile'
|
||||
|
||||
const downloadHandler = new DownloadHandler()
|
||||
|
||||
class App extends React.Component<IProps, IState> {
|
||||
@@ -45,7 +48,7 @@ class App extends React.Component<IProps, IState> {
|
||||
miniDownloadsOpen: false,
|
||||
downloadsOpen: false,
|
||||
gameDownloadsOpen: false,
|
||||
bgFile: 'https://api.grasscutters.xyz/content/bgfile',
|
||||
bgFile: DEFAULT_BG,
|
||||
}
|
||||
|
||||
listen('lang_error', (payload) => {
|
||||
@@ -76,23 +79,39 @@ class App extends React.Component<IProps, IState> {
|
||||
const cert_generated = await getConfigOption('cert_generated')
|
||||
const game_exe = await getConfigOption('game_install_path')
|
||||
const custom_bg = await getConfigOption('customBackground')
|
||||
const game_path = game_exe.substring(0, game_exe.lastIndexOf('\\'))
|
||||
const root_path = game_path.substring(0, game_path.lastIndexOf('\\'))
|
||||
const game_path = game_exe.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/'))
|
||||
const root_path = game_path.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/'))
|
||||
|
||||
if(!custom_bg) {
|
||||
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
|
||||
bgPath: root_path,
|
||||
appdata: await dataDir()
|
||||
})
|
||||
|
||||
bgLoc && this.setState({
|
||||
bgFile: bgLoc
|
||||
})
|
||||
}, this.forceUpdate)
|
||||
}
|
||||
} else this.setState({
|
||||
bgFile: custom_bg
|
||||
})
|
||||
} else {
|
||||
const isUrl = /^(?:http(s)?:\/\/)/gm.test(custom_bg)
|
||||
|
||||
if (!isUrl) {
|
||||
this.setState({
|
||||
bgFile: convertFileSrc(custom_bg)
|
||||
}, this.forceUpdate)
|
||||
} else {
|
||||
// Check if URL returns a valid image.
|
||||
const isValid = await invoke('valid_url', {
|
||||
url: custom_bg
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG
|
||||
}, this.forceUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
if (!cert_generated) {
|
||||
// Generate the certificate
|
||||
@@ -109,15 +128,13 @@ class App extends React.Component<IProps, IState> {
|
||||
isDownloading: downloadHandler.getDownloads().filter(d => d.status !== 'finished')?.length > 0
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
console.log('mounting app component with background: ' + this.state.bgFile)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App" style={
|
||||
this.state.bgFile ? {
|
||||
background: `url(${this.state.bgFile} fixed`,
|
||||
background: `url("${this.state.bgFile}") fixed`,
|
||||
} : {}
|
||||
}>
|
||||
<TopBar
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
height: calc(100vh - 190px);
|
||||
height: 100vh;
|
||||
width: 80px;
|
||||
right: 0%;
|
||||
|
||||
z-index: 99;
|
||||
|
||||
background-color: rgba(77, 77, 77, 0.6);
|
||||
z-index: 99;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.BarImg:hover {
|
||||
|
||||
@@ -107,7 +107,12 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
}
|
||||
|
||||
// Launch the program
|
||||
await invoke('run_program', { path: config.game_install_path })
|
||||
const gameExists = await invoke('dir_exists', {
|
||||
path: config.game_install_path
|
||||
})
|
||||
|
||||
if (gameExists) await invoke('run_program', { path: config.game_install_path })
|
||||
else alert('Game not found! At: ' + config.game_install_path)
|
||||
}
|
||||
|
||||
async launchServer() {
|
||||
|
||||
@@ -30,12 +30,6 @@ export default class TextInput extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
if (!props.readOnly) {
|
||||
return {
|
||||
value: state.value
|
||||
}
|
||||
}
|
||||
|
||||
return { value: props.value || '' }
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import './Downloads.css'
|
||||
import Divider from './Divider'
|
||||
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
@@ -48,12 +49,16 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.downloadGrasscutterStable = this.downloadGrasscutterStable.bind(this)
|
||||
this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this)
|
||||
this.downloadResources = this.downloadResources.bind(this)
|
||||
this.disableButtons = this.disableButtons.bind(this)
|
||||
this.toggleButtons = this.toggleButtons.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const gc_path = await getConfigOption('grasscutter_path')
|
||||
|
||||
listen('jar_extracted', () => {
|
||||
this.setState({ grasscutter_set: true }, this.forceUpdate)
|
||||
})
|
||||
|
||||
if (!gc_path || gc_path === '') {
|
||||
this.setState({
|
||||
grasscutter_set: false,
|
||||
@@ -102,43 +107,43 @@ 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', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\')
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
this.disableButtons()
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterDevRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\')
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
this.disableButtons()
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterStable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\')
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
// Also add repo download
|
||||
this.downloadGrasscutterStableRepo()
|
||||
|
||||
this.disableButtons()
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\')
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
// Also add repo download
|
||||
this.downloadGrasscutterDevRepo()
|
||||
|
||||
this.disableButtons()
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadResources() {
|
||||
@@ -150,18 +155,23 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
path: folder + '\\Resources',
|
||||
newName: 'resources'
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
})
|
||||
})
|
||||
|
||||
this.disableButtons()
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
disableButtons() {
|
||||
async toggleButtons() {
|
||||
const gc_path = await getConfigOption('grasscutter_path')
|
||||
|
||||
// Set states since we know we are downloading something if this is called
|
||||
this.setState({
|
||||
grasscutter_downloading: this.props.downloadManager.downloadingJar(),
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo()
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
grasscutter_set: gc_path && gc_path !== '',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import './Options.css'
|
||||
import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import Divider from './Divider'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
@@ -36,6 +38,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -80,8 +83,28 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
setCustomBackground(value: string) {
|
||||
setConfigOption('customBackground', value)
|
||||
async setCustomBackground(value: string) {
|
||||
const isUrl = /^(?:http(s)?:\/\/)/gm.test(value)
|
||||
|
||||
if (!value) return await setConfigOption('customBackground', '')
|
||||
|
||||
if (!isUrl) {
|
||||
const filename = value.replace(/\\/g, '/').split('/').pop()
|
||||
const localBgPath = (await dataDir() as string).replace(/\\/g, '/')
|
||||
|
||||
await setConfigOption('customBackground', `${localBgPath}/cultivation/bg/${filename}`)
|
||||
|
||||
// Copy the file over to the local directory
|
||||
await invoke('copy_file', {
|
||||
path: value.replace(/\\/g, '/'),
|
||||
newPath: `${localBgPath}cultivation/bg/`
|
||||
})
|
||||
|
||||
window.location.reload()
|
||||
} else {
|
||||
await setConfigOption('customBackground', value)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user