Download leaderboards button and single instance enforcement
This commit is contained in:
@@ -26,3 +26,6 @@ zip = "4.3.0"
|
|||||||
libc = "0.2.174"
|
libc = "0.2.174"
|
||||||
tauri-plugin-dialog = "2.3.1"
|
tauri-plugin-dialog = "2.3.1"
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
tauri-plugin-single-instance = "2.3.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{create_dir_all, File},
|
fs::{File, create_dir_all},
|
||||||
io::{copy, BufReader},
|
io::{BufReader, Write, copy},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::Command, time::Duration,
|
process::Command,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||||
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use tokio::{io::AsyncWriteExt, task::spawn_blocking, time::timeout};
|
use tokio::{io::AsyncWriteExt, task::spawn_blocking, time::timeout};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
@@ -100,7 +102,8 @@ async fn download(
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
app.emit("download-progress", format!("{}:{}", &name, progress)).unwrap();
|
app.emit("download-progress", format!("{}:{}", &name, progress))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if total_size > 0 && downloaded < total_size {
|
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() {
|
pub fn run() {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
tauri::Builder::default()
|
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_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_decorum::init())
|
.plugin(tauri_plugin_decorum::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_opener::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| {
|
.setup(|app| {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,14 +29,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": false
|
||||||
"targets": "all",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { LeaderboardEntry } from '../types/LeaderboardEntry'
|
|||||||
import { app } from '@tauri-apps/api'
|
import { app } from '@tauri-apps/api'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { decrypt } from '../util/Encryption'
|
import { decrypt } from '../util/Encryption'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
export default function Leaderboards () {
|
export default function Leaderboards () {
|
||||||
const [leaderboardData, setLeaderboardData] = useState<LeaderboardEntry[]>([])
|
const [leaderboardData, setLeaderboardData] = useState<LeaderboardEntry[]>([])
|
||||||
@@ -30,6 +31,17 @@ export default function Leaderboards () {
|
|||||||
.finally(() => setLoading(false))
|
.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(() => {
|
useEffect(() => {
|
||||||
refresh()
|
refresh()
|
||||||
}, [])
|
}, [])
|
||||||
@@ -38,13 +50,22 @@ export default function Leaderboards () {
|
|||||||
<div className='mx-4 mt-4'>
|
<div className='mx-4 mt-4'>
|
||||||
<div className='flex justify-between items-center mb-4'>
|
<div className='flex justify-between items-center mb-4'>
|
||||||
<p className='text-3xl'>Leaderboards</p>
|
<p className='text-3xl'>Leaderboards</p>
|
||||||
<button
|
<div className='flex gap-2'>
|
||||||
className='button text-3xl'
|
<button
|
||||||
onClick={refresh}
|
className='button text-3xl'
|
||||||
disabled={loading}
|
onClick={downloadLeaderboard}
|
||||||
>
|
disabled={loading || leaderboardData.length === 0}
|
||||||
Refresh
|
>
|
||||||
</button>
|
Download Leaderboards
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button text-3xl'
|
||||||
|
onClick={refresh}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='leaderboard-container'>
|
<div className='leaderboard-container'>
|
||||||
<div className='leaderboard-scroll'>
|
<div className='leaderboard-scroll'>
|
||||||
|
|||||||
Reference in New Issue
Block a user