diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d51bb42..f268f5a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2091,12 +2091,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minisign-verify" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" - [[package]] name = "miniz_oxide" version = "0.5.1" @@ -3860,7 +3854,6 @@ checksum = "a34cef4a0ebee0230baaa319b1709c4336f4add550149d2b005a9a5dc5d33617" dependencies = [ "anyhow", "attohttpc", - "base64", "bincode", "cocoa", "dirs-next", @@ -3874,7 +3867,6 @@ dependencies = [ "heck 0.4.0", "http", "ignore", - "minisign-verify", "notify-rust", "objc", "once_cell", @@ -3906,7 +3898,6 @@ dependencies = [ "webkit2gtk", "webview2-com", "windows 0.30.0", - "zip 0.6.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 78b766f..9e70763 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,7 +16,7 @@ tauri-build = { version = "1.0.0-rc.8", features = [] } [dependencies] serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.0.0-rc.9", features = ["api-all", "updater"] } +tauri = { version = "1.0.0-rc.9", features = ["api-all"] } # Access system process info. sysinfo = "0.23.12" diff --git a/src-tauri/lang/en.json b/src-tauri/lang/en.json index 04ae9da..0f7e95e 100644 --- a/src-tauri/lang/en.json +++ b/src-tauri/lang/en.json @@ -7,9 +7,7 @@ "ip_placeholder": "Server Address...", "port_placeholder": "Port...", "files_downloading": "Files Downloading: ", - "files_extracting": "Files Extracting: ", - "port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.", - "game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed." + "files_extracting": "Files Extracting: " }, "options": { "game_exec": "Set Game Executable", @@ -46,5 +44,14 @@ "news": { "latest_commits": "Recent Commits", "latest_version": "Latest Version" + }, + "help": { + "port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.", + "game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed.", + "gc_stable_jar": "Download the current stable Grasscutter build, which includes jar file and data files.", + "gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.", + "gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.", + "gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.", + "resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside" } } \ No newline at end of file diff --git a/src-tauri/src/downloader.rs b/src-tauri/src/downloader.rs index 1883170..be6708a 100644 --- a/src-tauri/src/downloader.rs +++ b/src-tauri/src/downloader.rs @@ -41,6 +41,7 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu } }; let mut downloaded: u64 = 0; + let mut total_downloaded: u64 = 0; // File stream let mut stream = res.bytes_stream(); @@ -77,6 +78,8 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu let new = min(downloaded + (chunk.len() as u64), total_size); downloaded = new; + total_downloaded = total_downloaded + chunk.len() as u64; + let mut res_hash = std::collections::HashMap::new(); res_hash.insert( @@ -94,6 +97,11 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu path.to_string(), ); + res_hash.insert( + "total_downloaded".to_string(), + total_downloaded.to_string(), + ); + // Create event to send to frontend window.emit("download_progress", &res_hash).unwrap(); } diff --git a/src-tauri/src/file_helpers.rs b/src-tauri/src/file_helpers.rs index aa4ce96..c47f63b 100644 --- a/src-tauri/src/file_helpers.rs +++ b/src-tauri/src/file_helpers.rs @@ -22,4 +22,14 @@ pub fn rename(path: String, new_name: String) { #[tauri::command] pub fn dir_exists(path: &str) -> bool { return fs::metadata(&path).is_ok(); +} + +#[tauri::command] +pub fn dir_is_empty(path: &str) -> bool { + return fs::read_dir(&path).unwrap().count() == 0; +} + +#[tauri::command] +pub fn dir_delete(path: &str) { + fs::remove_dir_all(path).unwrap(); } \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1cbc020..19fe467 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -58,6 +58,8 @@ fn main() { unzip::unzip, file_helpers::rename, file_helpers::dir_exists, + file_helpers::dir_is_empty, + file_helpers::dir_delete, downloader::download_file, downloader::stop_download, lang::get_lang, diff --git a/src/ui/components/MiniDialog.tsx b/src/ui/components/MiniDialog.tsx index a6881f5..69d1c27 100644 --- a/src/ui/components/MiniDialog.tsx +++ b/src/ui/components/MiniDialog.tsx @@ -15,6 +15,21 @@ export default class MiniDialog extends React.Component { super(props) } + componentDidMount() { + document.addEventListener('mousedown', (evt) => { + const tgt = evt.target as HTMLElement + const isInside = tgt.closest('.MiniDialog') !== null + + if (!isInside) { + this.props.closeFn() + } + }) + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.props.closeFn) + } + render() { return (
diff --git a/src/ui/components/ServerLaunchSection.tsx b/src/ui/components/ServerLaunchSection.tsx index ea7a4f5..13d3621 100644 --- a/src/ui/components/ServerLaunchSection.tsx +++ b/src/ui/components/ServerLaunchSection.tsx @@ -59,8 +59,8 @@ export default class ServerLaunchSection extends React.Component ip: config.last_ip || '', port: config.last_port || '', ipPlaceholder: await translate('main.ip_placeholder'), - portPlaceholder: await translate('main.port_placeholder'), - portHelpText: await translate('main.port_help_text') + portPlaceholder: await translate('help.port_placeholder'), + portHelpText: await translate('help.port_help_text') }) } @@ -104,6 +104,23 @@ export default class ServerLaunchSection extends React.Component // Connect to proxy await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' }) + + // Open server as well if the options are set + if (config.grasscutter_with_game) { + let jarFolder = config.grasscutter_path + + if (jarFolder.includes('/')) { + jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('/')) + } else { + jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\')) + } + + await invoke('run_jar', { + path: config.grasscutter_path, + executeIn: jarFolder, + javaPath: config.java_path || '' + }) + } } // Launch the program diff --git a/src/ui/components/common/HelpButton.css b/src/ui/components/common/HelpButton.css index 0b23e3e..6b40109 100644 --- a/src/ui/components/common/HelpButton.css +++ b/src/ui/components/common/HelpButton.css @@ -20,11 +20,14 @@ .HelpContents { text-align: center; + position: relative; } .HelpContents .MiniDialog { - bottom: 80%; - left: 35%; - width: 60%; - height: 60%; + position: absolute; + + bottom: 40px; + right: -450%; + width: 200px; + height: 120px; } \ No newline at end of file diff --git a/src/ui/components/menu/Downloads.css b/src/ui/components/menu/Downloads.css index d20f5d0..60f7c6e 100644 --- a/src/ui/components/menu/Downloads.css +++ b/src/ui/components/menu/Downloads.css @@ -24,4 +24,8 @@ .DownloadValue .BigButtonText { font-size: 12px; +} + +.DownloadMenuSection .HelpButton img { + filter: none; } \ No newline at end of file diff --git a/src/ui/components/menu/Downloads.tsx b/src/ui/components/menu/Downloads.tsx index 6fb891f..6b2c184 100644 --- a/src/ui/components/menu/Downloads.tsx +++ b/src/ui/components/menu/Downloads.tsx @@ -11,6 +11,7 @@ import Divider from './Divider' import { getConfigOption, setConfigOption } from '../../../utils/configuration' import { invoke } from '@tauri-apps/api' import { listen } from '@tauri-apps/api/event' +import HelpButton from '../common/HelpButton' 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' @@ -73,7 +74,9 @@ export default class Downloads extends React.Component { 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 this.setState({ grasscutter_set: gc_path !== '', @@ -149,6 +152,15 @@ export default class Downloads extends React.Component { 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' + })) { + await invoke('dir_delete', { + path: folder + '\\resources' + }) + } + await unzip(folder + '\\resources.zip', folder + '\\', () => { // Rename folder to resources invoke('rename', { @@ -183,6 +195,9 @@ export default class Downloads extends React.Component { + + +
@@ -195,6 +210,9 @@ export default class Downloads extends React.Component { + + +
@@ -210,6 +228,9 @@ export default class Downloads extends React.Component { + + +
@@ -222,6 +243,9 @@ export default class Downloads extends React.Component { + + +
@@ -235,6 +259,9 @@ export default class Downloads extends React.Component {
+ + +
diff --git a/src/utils/download.ts b/src/utils/download.ts index 680bacd..f7de7ec 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -7,6 +7,7 @@ export default class DownloadHandler { path: string, progress: number, total: number, + total_downloaded: number, status: string, startTime: number, error?: string, @@ -24,16 +25,25 @@ export default class DownloadHandler { downloaded: string, total: string, path: string, + total_downloaded: string, } = payload 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) // Set download speed based on startTime const now = Date.now() const timeDiff = now - this.downloads[index].startTime - const speed = (this.downloads[index].progress / timeDiff) * 1000 + let speed = (this.downloads[index].progress / timeDiff) * 1000 + + if (this.downloads[index].total === 0) { + // If our total is 0, then we are downloading a file without a size + // Calculate the average speed based total_downloaded and startTme + speed = (this.downloads[index].total_downloaded / timeDiff) * 1000 + } + this.downloads[index].speed = byteToString(speed) + '/s' }) @@ -104,6 +114,7 @@ export default class DownloadHandler { path, progress: 0, total: 0, + total_downloaded: 0, status: 'downloading', startTime: Date.now(), onFinish, @@ -134,7 +145,7 @@ export default class DownloadHandler { getTotalAverage() { 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) => acc + d.progress, 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 diff --git a/src/utils/themes.ts b/src/utils/themes.ts index 24f9d44..e0c3309 100644 --- a/src/utils/themes.ts +++ b/src/utils/themes.ts @@ -39,7 +39,7 @@ const defaultTheme = { export async function getThemeList() { // Do some invoke to backend to get the theme list const themes = await invoke('get_theme_list', { - data_dir: `${await dataDir()}/cultivation` + dataDir: `${await dataDir()}/cultivation` }) as BackendThemeList[] const list: ThemeList[] = [ // ALWAYS include default theme