From 42daffee848a1e0194c31ede52ab5b33143bf8e1 Mon Sep 17 00:00:00 2001 From: Lncvrt Date: Tue, 22 Jul 2025 00:02:28 -0700 Subject: [PATCH] Download leaderboards button and single instance enforcement --- src-tauri/Cargo.toml | 3 ++ src-tauri/src/lib.rs | 64 ++++++++++++++++++++++++++++--- src-tauri/tauri.windows.conf.json | 10 +---- src/routes/Leaderboards.tsx | 35 +++++++++++++---- 4 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8a994e5..98e3cbe 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,3 +26,6 @@ zip = "4.3.0" libc = "0.2.174" tauri-plugin-dialog = "2.3.1" +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-single-instance = "2.3.1" + diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 8e2598f..3028bc8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,13 +1,15 @@ #[cfg_attr(mobile, tauri::mobile_entry_point)] use futures_util::stream::StreamExt; use std::{ - fs::{create_dir_all, File}, - io::{copy, BufReader}, + fs::{File, create_dir_all}, + io::{BufReader, Write, copy}, path::PathBuf, - process::Command, time::Duration, + process::Command, + time::Duration, }; use tauri::{AppHandle, Emitter, Manager}; use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; +use tauri_plugin_opener::OpenerExt; use tokio::{io::AsyncWriteExt, task::spawn_blocking, time::timeout}; use zip::ZipArchive; @@ -100,7 +102,8 @@ async fn download( 0 }; - app.emit("download-progress", format!("{}:{}", &name, progress)).unwrap(); + app.emit("download-progress", format!("{}:{}", &name, progress)) + .unwrap(); } if total_size > 0 && downloaded < total_size { @@ -163,15 +166,66 @@ fn launch_game(app: AppHandle, name: String, executable: String) { } } +#[tauri::command] +fn download_leaderboard(app: AppHandle, content: String) { + app.dialog().file().save_file(move |file_path| { + if let Some(path) = file_path { + let mut path_buf = PathBuf::from(path.to_string()); + if path_buf.extension().map(|ext| ext != "csv").unwrap_or(true) { + path_buf.set_extension("csv"); + } + let path_str = path_buf.to_string_lossy().to_string(); + if path_str.is_empty() { + app.dialog() + .message("No file selected.") + .kind(MessageDialogKind::Error) + .title("Error") + .show(|_| {}); + return; + } + let mut file = match File::create(&path_buf) { + Ok(f) => f, + Err(e) => { + app.dialog() + .message(format!("Failed to create file: {}", e)) + .kind(MessageDialogKind::Error) + .title("Error") + .show(|_| {}); + return; + } + }; + if let Err(e) = file.write_all(content.as_bytes()) { + app.dialog() + .message(format!("Failed to write to file: {}", e)) + .kind(MessageDialogKind::Error) + .title("Error") + .show(|_| {}); + } else { + let _ = app.opener().open_path(path.to_string(), None::<&str>); + } + } + }) +} + pub fn run() { #[allow(unused_variables)] tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + let _ = app + .get_webview_window("main") + .expect("no main window") + .set_focus(); + })) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_decorum::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![download, launch_game]) + .invoke_handler(tauri::generate_handler![ + download, + launch_game, + download_leaderboard + ]) .setup(|app| { #[cfg(target_os = "windows")] { diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index 33d9c51..c543fdd 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -29,14 +29,6 @@ } }, "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] + "active": false } } diff --git a/src/routes/Leaderboards.tsx b/src/routes/Leaderboards.tsx index fad50be..bf8f120 100644 --- a/src/routes/Leaderboards.tsx +++ b/src/routes/Leaderboards.tsx @@ -5,6 +5,7 @@ import { LeaderboardEntry } from '../types/LeaderboardEntry' import { app } from '@tauri-apps/api' import { platform } from '@tauri-apps/plugin-os' import { decrypt } from '../util/Encryption' +import { invoke } from '@tauri-apps/api/core' export default function Leaderboards () { const [leaderboardData, setLeaderboardData] = useState([]) @@ -30,6 +31,17 @@ export default function Leaderboards () { .finally(() => setLoading(false)) } + function downloadLeaderboard () { + let content = 'Username,Score\n' + leaderboardData.forEach(entry => { + content += `${entry.username},${entry.value}\n` + }) + while (content.endsWith('\n')) { + content = content.slice(0, -1) + } + invoke('download_leaderboard', { content }) + } + useEffect(() => { refresh() }, []) @@ -38,13 +50,22 @@ export default function Leaderboards () {

Leaderboards

- +
+ + +