Compare commits

..
11 Commits
Author SHA1 Message Date
Lncvrt c69060a360 1.8.1 2026-03-27 12:47:27 -07:00
Lncvrt f2b0358605 Big smart 2026-03-27 12:45:58 -07:00
Lncvrt 57441a8872 Close popup & notify on download finish 2026-03-27 12:45:29 -07:00
Lncvrt df522122ac Remove debug logging and fix hashing 2026-03-27 12:40:25 -07:00
Lncvrt f4e16c12e5 Fix overscroll 2026-03-27 12:28:33 -07:00
Lncvrt e2d98307c0 Optimize builds 2026-03-27 12:27:05 -07:00
Lncvrt 387bd54ce2 Rename "unix wine" to "linux wine" 2026-03-24 22:50:09 -07:00
Lncvrt e7a61872b7 Switch to Tauris json store management for settings 2026-03-24 22:45:28 -07:00
Lncvrt ac66c78dbf Fix this not using Rust for sending requests 2026-03-24 21:31:52 -07:00
Lncvrt d9302319fa Fix sorting issue 2026-03-24 20:15:41 -07:00
Lncvrt 5206a7f25d Fix downloads 2026-03-24 19:55:00 -07:00
29 changed files with 348 additions and 499 deletions

No files matched your search

+3
View File
@@ -17,6 +17,7 @@
"@tauri-apps/plugin-notification": "2.3.3", "@tauri-apps/plugin-notification": "2.3.3",
"@tauri-apps/plugin-opener": "2.5.3", "@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-os": "2.3.2", "@tauri-apps/plugin-os": "2.3.2",
"@tauri-apps/plugin-store": "2.4.2",
"@tauri-apps/plugin-window-state": "2.4.1", "@tauri-apps/plugin-window-state": "2.4.1",
"next": "16.2.1", "next": "16.2.1",
"pretty-bytes": "7.1.0", "pretty-bytes": "7.1.0",
@@ -280,6 +281,8 @@
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="],
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="], "@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+2 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "lncvrt-games-launcher", "name": "lncvrt-games-launcher",
"private": true, "private": true,
"version": "1.8.0", "version": "1.8.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -23,6 +23,7 @@
"@tauri-apps/plugin-notification": "2.3.3", "@tauri-apps/plugin-notification": "2.3.3",
"@tauri-apps/plugin-opener": "2.5.3", "@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-os": "2.3.2", "@tauri-apps/plugin-os": "2.3.2",
"@tauri-apps/plugin-store": "2.4.2",
"@tauri-apps/plugin-window-state": "2.4.1", "@tauri-apps/plugin-window-state": "2.4.1",
"next": "16.2.1", "next": "16.2.1",
"pretty-bytes": "7.1.0", "pretty-bytes": "7.1.0",
+13 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "lncvrt-games-launcher" name = "lncvrt-games-launcher"
version = "1.8.0" version = "1.8.1"
authors = ["Lncvrt"] authors = ["Lncvrt"]
edition = "2024" edition = "2024"
@@ -25,14 +25,18 @@ tauri-plugin-fs = "2.4.5"
zip = "8.4.0" zip = "8.4.0"
tauri-plugin-dialog = "2.6.0" tauri-plugin-dialog = "2.6.0"
tauri-plugin-notification = "2.3.3" tauri-plugin-notification = "2.3.3"
sysinfo = "0.38.4" sha2 = "0.11.0"
sha2 = "0.10.9"
tauri-plugin-clipboard-manager = "2.3.2" tauri-plugin-clipboard-manager = "2.3.2"
tauri-plugin-prevent-default = "4.0.4" tauri-plugin-prevent-default = "4.0.4"
openssl = { version = "0.10.76", features = ["v110", "vendored"] } openssl = { version = "0.10.76", features = ["v110", "vendored"] }
base64 = "0.22.1" base64 = "0.22.1"
tauri-plugin-http = "2.5.7" tauri-plugin-http = "2.5.7"
dashmap = "6.1.0" dashmap = "6.1.0"
tauri-plugin-store = "2.4.2"
hex = "0.4.3"
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
sysinfo = "0.38.4"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2.4.0" tauri-plugin-single-instance = "2.4.0"
@@ -40,3 +44,9 @@ tauri-plugin-window-state = "2.4.1"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
tauri-plugin-decorum = "1.1.1" tauri-plugin-decorum = "1.1.1"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
+9 -11
View File
@@ -2,9 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default", "identifier": "default",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": [ "windows": ["main"],
"main"
],
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
@@ -26,19 +24,18 @@
"notification:default", "notification:default",
"clipboard-manager:default", "clipboard-manager:default",
"clipboard-manager:allow-write-text", "clipboard-manager:allow-write-text",
"store:default",
"store:allow-load",
"store:allow-save",
"fs:allow-applocaldata-read", "fs:allow-applocaldata-read",
"fs:allow-applocaldata-write", "fs:allow-applocaldata-write",
{ {
"identifier": "fs:scope", "identifier": "fs:scope",
"allow": [ "allow": [{ "path": "$APPLOCALDATA/game/**" }]
{ "path": "$APPLOCALDATA/game/**" }
]
}, },
{ {
"identifier": "opener:allow-open-path", "identifier": "opener:allow-open-path",
"allow": [ "allow": [{ "path": "$APPLOCALDATA/game/**" }]
{ "path": "$APPLOCALDATA/game/**" }
]
}, },
{ {
"identifier": "http:default", "identifier": "http:default",
@@ -51,8 +48,9 @@
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/berry" }, { "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/berry" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/coin" }, { "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/coin" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/legacy" }, { "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/legacy" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" } { "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" },
{ "url": "https://games.lncvrt.xyz/api/launcher/mods" }
] ]
} }
] ]
} }
+19 -118
View File
@@ -15,7 +15,6 @@ use std::{
process::Command, process::Command,
time::Duration, time::Duration,
}; };
use sysinfo::System;
use tauri::window::{ProgressBarState, ProgressBarStatus}; use tauri::window::{ProgressBarState, ProgressBarStatus};
use tauri::{AppHandle, Emitter, Manager}; use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_os::platform; use tauri_plugin_os::platform;
@@ -25,6 +24,9 @@ use tokio::{io::AsyncWriteExt, time::timeout};
use zip::ZipArchive; use zip::ZipArchive;
use zip::result::ZipError; use zip::result::ZipError;
#[cfg(any(target_os = "windows", target_os = "linux"))]
use sysinfo::System;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@@ -38,6 +40,7 @@ fn is_cancelled(name: &str) -> bool {
cancel_map().get(name).map(|v| *v).unwrap_or(false) cancel_map().get(name).map(|v| *v).unwrap_or(false)
} }
#[cfg(any(target_os = "windows", target_os = "linux"))]
fn is_running_by_path(path: &Path) -> bool { fn is_running_by_path(path: &Path) -> bool {
let sys = System::new_all(); let sys = System::new_all();
let target = match path.canonicalize() { let target = match path.canonicalize() {
@@ -151,25 +154,19 @@ async fn download(
download_type: i8, download_type: i8,
mod_id: String, mod_id: String,
) -> String { ) -> String {
println!("[download] Starting download for '{}' from '{}'", name, url);
let _ = app.emit("download-start", format!("{}", name)); let _ = app.emit("download-start", format!("{}", name));
let window = app.get_webview_window("main").expect("main window missing"); let window = app.get_webview_window("main").expect("main window missing");
let downloads_path = match app.path().app_local_data_dir() { let downloads_path = match app.path().app_local_data_dir() {
Ok(p) => p.join("downloads"), Ok(p) => p.join("downloads"),
Err(e) => { Err(_) => {
println!(
"[download] ERROR: Failed to resolve app_local_data_dir: {}",
e
);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
}; };
let game_path = match app.path().app_local_data_dir() { let game_path = match app.path().app_local_data_dir() {
Ok(p) => p.join("game"), Ok(p) => p.join("game"),
Err(e) => { Err(_) => {
println!("[download] ERROR: Failed to resolve game path: {}", e);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -177,18 +174,12 @@ async fn download(
let download_part_path = downloads_path.join(format!("{}.part", name)); let download_part_path = downloads_path.join(format!("{}.part", name));
let download_zip_path = downloads_path.join(format!("{}.zip", name)); let download_zip_path = downloads_path.join(format!("{}.zip", name));
println!("[download] Part file: {:?}", download_part_path);
println!("[download] Zip file: {:?}", download_zip_path);
let _ = tokio::fs::create_dir_all(&downloads_path).await; let _ = tokio::fs::create_dir_all(&downloads_path).await;
let resume_from: u64 = match tokio::fs::metadata(&download_part_path).await { let resume_from: u64 = match tokio::fs::metadata(&download_part_path).await {
Ok(meta) => { Ok(meta) => {
let size = meta.len(); let size = meta.len();
println!(
"[download] Found existing .part file ({} bytes), will attempt to resume",
size
);
size size
} }
Err(_) => 0, Err(_) => 0,
@@ -198,16 +189,11 @@ async fn download(
let mut request = client.get(&url); let mut request = client.get(&url);
if resume_from > 0 { if resume_from > 0 {
request = request.header("Range", format!("bytes={}-", resume_from)); request = request.header("Range", format!("bytes={}-", resume_from));
println!("[download] Requesting range: bytes={}-", resume_from);
} }
let resp = match request.send().await { let resp = match request.send().await {
Ok(r) => { Ok(r) => r,
println!("[download] HTTP request succeeded, status: {}", r.status()); Err(_) => {
r
}
Err(e) => {
println!("[download] ERROR: HTTP request failed: {:?}", e);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -216,19 +202,11 @@ async fn download(
let status = resp.status(); let status = resp.status();
let actual_resume_from = if resume_from > 0 && status == reqwest::StatusCode::OK { let actual_resume_from = if resume_from > 0 && status == reqwest::StatusCode::OK {
println!(
"[download] Server does not support range requests (got 200), restarting from scratch"
);
let _ = tokio::fs::remove_file(&download_part_path).await; let _ = tokio::fs::remove_file(&download_part_path).await;
0 0
} else if resume_from > 0 && status == reqwest::StatusCode::PARTIAL_CONTENT { } else if resume_from > 0 && status == reqwest::StatusCode::PARTIAL_CONTENT {
println!("[download] Server accepted range request (206 Partial Content)");
resume_from resume_from
} else if resume_from > 0 { } else if resume_from > 0 {
println!(
"[download] Unexpected status {} while resuming, restarting",
status
);
let _ = tokio::fs::remove_file(&download_part_path).await; let _ = tokio::fs::remove_file(&download_part_path).await;
0 0
} else { } else {
@@ -237,13 +215,6 @@ async fn download(
let remaining_size = resp.content_length().unwrap_or(0); let remaining_size = resp.content_length().unwrap_or(0);
let total_size = actual_resume_from + remaining_size; let total_size = actual_resume_from + remaining_size;
println!(
"[download] Resume offset: {} bytes | Remaining: {} bytes | Total: {} bytes ({:.2} MB)",
actual_resume_from,
remaining_size,
total_size,
total_size as f64 / 1_048_576.0
);
let _ = app.emit("download-size", format!("{}:{}", &name, &total_size)); let _ = app.emit("download-size", format!("{}:{}", &name, &total_size));
let mut file = if actual_resume_from > 0 { let mut file = if actual_resume_from > 0 {
@@ -252,15 +223,8 @@ async fn download(
.open(&download_part_path) .open(&download_part_path)
.await .await
{ {
Ok(f) => { Ok(f) => f,
println!("[download] Opened .part file in append mode for resume"); Err(_) => {
f
}
Err(e) => {
println!(
"[download] ERROR: Failed to open .part file for append: {}",
e
);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -268,22 +232,14 @@ async fn download(
} else { } else {
if download_type == 0 { if download_type == 0 {
if let Ok(true) = tokio::fs::try_exists(&game_path.join(&name)).await { if let Ok(true) = tokio::fs::try_exists(&game_path.join(&name)).await {
println!(
"[download] Existing game dir found, removing: {:?}",
game_path.join(&name)
);
let _ = tokio::fs::remove_dir_all(&game_path.join(&name)).await; let _ = tokio::fs::remove_dir_all(&game_path.join(&name)).await;
} }
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await; let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
} }
match tokio::fs::File::create(&download_part_path).await { match tokio::fs::File::create(&download_part_path).await {
Ok(f) => { Ok(f) => f,
println!("[download] Created new .part file"); Err(_) => {
f
}
Err(e) => {
println!("[download] ERROR: Failed to create .part file: {}", e);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -298,7 +254,6 @@ async fn download(
while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await { while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await {
if is_cancelled(&name) { if is_cancelled(&name) {
println!("[download] Cancelled '{}' during chunk loop", name);
cancel_map().remove(&name); cancel_map().remove(&name);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
@@ -309,11 +264,7 @@ async fn download(
let chunk = match chunk_result { let chunk = match chunk_result {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(_) => {
println!(
"[download] ERROR: Failed to read chunk #{}: {}",
chunk_count, e
);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -321,11 +272,7 @@ async fn download(
chunk_count += 1; chunk_count += 1;
if let Err(e) = file.write_all(&chunk).await { if let Err(_) = file.write_all(&chunk).await {
println!(
"[download] ERROR: Failed to write chunk #{} to disk: {}",
chunk_count, e
);
let _ = &app.emit("download-stop", format!("{}", name)); let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string(); return "-1".to_string();
} }
@@ -352,15 +299,6 @@ async fn download(
}; };
if chunk_count % 50 == 0 || downloaded == total_size { if chunk_count % 50 == 0 || downloaded == total_size {
println!(
"[download] [{:.1}%] {:.2} MB / {:.2} MB | Speed: {:.1} KB/s | ETA: {:.1}s | Chunks: {}",
progress,
downloaded as f64 / 1_048_576.0,
total_size as f64 / 1_048_576.0,
speed / 1024.0,
eta_secs,
chunk_count,
);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::Normal), status: Some(ProgressBarStatus::Normal),
progress: Some(progress as u64), progress: Some(progress as u64),
@@ -377,12 +315,6 @@ async fn download(
} }
if total_size > 0 && downloaded < total_size { if total_size > 0 && downloaded < total_size {
println!(
"[download] ERROR: Download incomplete — got {} of {} bytes ({:.1}% complete)",
downloaded,
total_size,
(downloaded as f64 / total_size as f64) * 100.0
);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
progress: Some(0), progress: Some(0),
@@ -390,36 +322,21 @@ async fn download(
return "-1".to_string(); return "-1".to_string();
} }
println!(
"[download] Download complete: {} bytes total in {:.2}s (this session)",
downloaded,
start.elapsed().as_secs_f64()
);
println!("[download] Verifying SHA-512 hash...");
let _ = app.emit("download-hash-checking", format!("{}", &name)); let _ = app.emit("download-hash-checking", format!("{}", &name));
let download_hash = { let download_hash = {
let mut file = match tokio::fs::File::open(&download_part_path).await { let mut file = match tokio::fs::File::open(&download_part_path).await {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(_) => {
println!(
"[download] ERROR: Failed to open .part file for hashing: {}",
e
);
return "-1".to_string(); return "-1".to_string();
} }
}; };
let mut hasher = Sha512::new(); let mut hasher = Sha512::new();
let mut bytes_hashed: u64 = 0;
let mut buffer = [0u8; 8192]; let mut buffer = [0u8; 8192];
loop { loop {
let bytes_read = match file.read(&mut buffer).await { let bytes_read = match file.read(&mut buffer).await {
Ok(n) => n, Ok(n) => n,
Err(e) => { Err(_) => {
println!(
"[download] ERROR: Failed to read file during hashing: {}",
e
);
return "-1".to_string(); return "-1".to_string();
} }
}; };
@@ -427,18 +344,12 @@ async fn download(
break; break;
} }
hasher.update(&buffer[..bytes_read]); hasher.update(&buffer[..bytes_read]);
bytes_hashed += bytes_read as u64;
} }
drop(file); drop(file);
println!("[download] Hashed {} bytes", bytes_hashed); hex::encode(hasher.finalize())
format!("{:x}", hasher.finalize())
}; };
println!("[download] Expected hash: {}", hash);
println!("[download] Computed hash: {}", download_hash);
if hash != download_hash { if hash != download_hash {
println!("[download] ERROR: Hash mismatch! Deleting corrupt .part file.");
let _ = tokio::fs::remove_file(&download_part_path).await; let _ = tokio::fs::remove_file(&download_part_path).await;
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
@@ -447,10 +358,7 @@ async fn download(
return "-1".to_string(); return "-1".to_string();
} }
println!("[download] Hash verified OK"); if let Err(_) = tokio::fs::rename(&download_part_path, &download_zip_path).await {
println!("[download] Renaming .part -> .zip: {:?}", download_zip_path);
if let Err(e) = tokio::fs::rename(&download_part_path, &download_zip_path).await {
println!("[download] ERROR: Failed to rename .part to .zip: {}", e);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
progress: Some(0), progress: Some(0),
@@ -460,7 +368,6 @@ async fn download(
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await; let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
println!("[download] Unzipping to: {:?}", game_path.join(&name));
let unzip_res = unzip_to_dir( let unzip_res = unzip_to_dir(
app.clone(), app.clone(),
download_zip_path.clone(), download_zip_path.clone(),
@@ -477,11 +384,9 @@ async fn download(
) )
.await; .await;
println!("[download] Removing zip file: {:?}", download_zip_path);
let _ = tokio::fs::remove_file(&download_zip_path).await; let _ = tokio::fs::remove_file(&download_zip_path).await;
if unzip_res == "-1" { if unzip_res == "-1" {
println!("[download] ERROR: Unzip failed for '{}'", name);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
progress: Some(0), progress: Some(0),
@@ -489,10 +394,6 @@ async fn download(
return "-1".to_string(); return "-1".to_string();
} }
println!(
"[download] SUCCESS: '{}' downloaded, verified, and extracted",
&name
);
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
progress: Some(0), progress: Some(0),
@@ -612,7 +513,6 @@ fn verify_signature(body: String, signature: String, public_key: String) -> bool
#[tauri::command] #[tauri::command]
async fn cancel_download(name: String) { async fn cancel_download(name: String) {
println!("[cancel_download] Cancellation requested for '{}'", name);
cancel_map().insert(name.clone(), true); cancel_map().insert(name.clone(), true);
} }
@@ -620,6 +520,7 @@ async fn cancel_download(name: String) {
pub fn run() { pub fn run() {
#[allow(unused_variables)] #[allow(unused_variables)]
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_http::init())
.plugin( .plugin(
tauri_plugin_prevent_default::Builder::new() tauri_plugin_prevent_default::Builder::new()
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher", "productName": "Lncvrt Games Launcher",
"version": "1.8.0", "version": "1.8.1",
"identifier": "xyz.lncvrt.games-launcher", "identifier": "xyz.lncvrt.games-launcher",
"build": { "build": {
"beforeDevCommand": "next dev --webpack", "beforeDevCommand": "next dev --webpack",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher", "productName": "Lncvrt Games Launcher",
"version": "1.8.0", "version": "1.8.1",
"identifier": "xyz.lncvrt.games-launcher", "identifier": "xyz.lncvrt.games-launcher",
"build": { "build": {
"beforeDevCommand": "next dev --webpack", "beforeDevCommand": "next dev --webpack",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher", "productName": "Lncvrt Games Launcher",
"version": "1.8.0", "version": "1.8.1",
"identifier": "xyz.lncvrt.games-launcher", "identifier": "xyz.lncvrt.games-launcher",
"build": { "build": {
"beforeDevCommand": "next dev --webpack", "beforeDevCommand": "next dev --webpack",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher", "productName": "Lncvrt Games Launcher",
"version": "1.8.0", "version": "1.8.1",
"identifier": "xyz.lncvrt.games-launcher", "identifier": "xyz.lncvrt.games-launcher",
"build": { "build": {
"beforeDevCommand": "next dev --webpack", "beforeDevCommand": "next dev --webpack",
+7 -3
View File
@@ -9,11 +9,11 @@ import {
} from 'react' } from 'react'
import { DownloadProgress } from '@/types/DownloadProgress' import { DownloadProgress } from '@/types/DownloadProgress'
import { VersionsConfig } from '@/types/VersionsConfig' import { VersionsConfig } from '@/types/VersionsConfig'
import { NormalConfig } from '@/types/NormalConfig'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse' import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion' import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game' import { Game } from '@/types/Game'
import { Mod } from '@/types/Mod' import { Mod } from '@/types/Mod'
import { Store } from '@tauri-apps/plugin-store'
type GlobalCtxType = { type GlobalCtxType = {
serverVersionList: ServerVersionsResponse | null serverVersionList: ServerVersionsResponse | null
@@ -29,8 +29,6 @@ type GlobalCtxType = {
setFadeOut: Dispatch<SetStateAction<boolean>> setFadeOut: Dispatch<SetStateAction<boolean>>
downloadedVersionsConfig: VersionsConfig | null downloadedVersionsConfig: VersionsConfig | null
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>> setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
normalConfig: NormalConfig | null
setNormalConfig: Dispatch<SetStateAction<NormalConfig | null>>
managingVersion: string | null managingVersion: string | null
setManagingVersion: Dispatch<SetStateAction<string | null>> setManagingVersion: Dispatch<SetStateAction<string | null>>
setSelectedGame: Dispatch<SetStateAction<number | null>> setSelectedGame: Dispatch<SetStateAction<number | null>>
@@ -63,6 +61,12 @@ type GlobalCtxType = {
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>> setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
showModInfo: Mod | null showModInfo: Mod | null
setShowModInfo: Dispatch<SetStateAction<Mod | null>> setShowModInfo: Dispatch<SetStateAction<Mod | null>>
settings: Store | null
notificationsAllowed: boolean
sidebarAlwaysShowGames: boolean
linuxUseWine: boolean
linuxWineCommand: string
theme: string
} }
const GlobalCtx = createContext<GlobalCtxType | null>(null) const GlobalCtx = createContext<GlobalCtxType | null>(null)
+4
View File
@@ -1,5 +1,9 @@
@import "tailwindcss"; @import "tailwindcss";
html {
overscroll-behavior: none;
}
body { body {
@apply bg-(--col0) text-white select-none; @apply bg-(--col0) text-white select-none;
} }
+10 -18
View File
@@ -21,7 +21,6 @@ export default function Installs () {
setFadeOut, setFadeOut,
setSelectedVersionList, setSelectedVersionList,
downloadedVersionsConfig, downloadedVersionsConfig,
normalConfig,
setManagingVersion, setManagingVersion,
getVersionInfo, getVersionInfo,
getGameInfo, getGameInfo,
@@ -31,7 +30,9 @@ export default function Installs () {
setCategory, setCategory,
setDownloadedVersionsConfig, setDownloadedVersionsConfig,
downloadVersions, downloadVersions,
downloadProgress downloadProgress,
linuxUseWine,
linuxWineCommand
} = useGlobal() } = useGlobal()
const params = useSearchParams() const params = useSearchParams()
@@ -43,7 +44,7 @@ export default function Installs () {
useEffect(() => { useEffect(() => {
if (!showPopup) return if (!showPopup) return
setSelectedVersionList([]) setSelectedVersionList([])
}, [normalConfig, setSelectedVersionList, showPopup]) }, [setSelectedVersionList, showPopup])
if (!id || !game) return <p>Invalid game</p> if (!id || !game) return <p>Invalid game</p>
@@ -129,11 +130,7 @@ export default function Installs () {
const info = getVersionInfo(v) const info = getVersionInfo(v)
if (!info) return false if (!info) return false
if ( if (platform() == 'linux' && info.wine && !linuxUseWine)
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
return false return false
return info.game === id && info.category === Number(key) return info.game === id && info.category === Number(key)
@@ -168,8 +165,7 @@ export default function Installs () {
if ( if (
platform() == 'linux' && platform() == 'linux' &&
info.wine && info.wine &&
!normalConfig?.settings !linuxUseWine
.useWineOnUnixWhenNeeded
) )
return false return false
return ( return (
@@ -194,17 +190,13 @@ export default function Installs () {
.sort((a, b) => { .sort((a, b) => {
const infoA = getVersionInfo(a) const infoA = getVersionInfo(a)
const infoB = getVersionInfo(b) const infoB = getVersionInfo(b)
if (!infoA || !infoB) return -1 if (!infoA || !infoB) return 0
return infoB.place - infoA.place return infoB.place - infoA.place
}) })
.filter(v => { .filter(v => {
const info = getVersionInfo(v) const info = getVersionInfo(v)
if (!info) return false if (!info) return false
if ( if (platform() == 'linux' && info.wine && !linuxUseWine)
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
return false return false
return ( return (
info.game === id && info.game === id &&
@@ -299,9 +291,9 @@ export default function Installs () {
useWine: !!( useWine: !!(
platform() == 'linux' && platform() == 'linux' &&
verInfo.wine && verInfo.wine &&
normalConfig?.settings.useWineOnUnixWhenNeeded linuxUseWine
), ),
wineCommand: normalConfig?.settings.wineOnUnixCommand wineCommand: linuxWineCommand
}) })
}} }}
onContextMenu={e => { onContextMenu={e => {
+169 -43
View File
@@ -2,18 +2,13 @@
import './Globals.css' import './Globals.css'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import Sidebar from '@/componets/Sidebar' import Sidebar from '@/components/Sidebar'
import { DownloadProgress } from '@/types/DownloadProgress' import { DownloadProgress } from '@/types/DownloadProgress'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons' import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
import { import { readVersionsConfig, writeVersionsConfig } from '@/lib/BazookaManager'
readNormalConfig,
readVersionsConfig,
writeVersionsConfig
} from '@/lib/BazookaManager'
import { VersionsConfig } from '@/types/VersionsConfig' import { VersionsConfig } from '@/types/VersionsConfig'
import { NormalConfig } from '@/types/NormalConfig'
import { app } from '@tauri-apps/api' import { app } from '@tauri-apps/api'
import { openUrl } from '@tauri-apps/plugin-opener' import { openUrl } from '@tauri-apps/plugin-opener'
import { GlobalProvider } from './GlobalProvider' import { GlobalProvider } from './GlobalProvider'
@@ -24,22 +19,28 @@ import { Game } from '@/types/Game'
import { listen } from '@tauri-apps/api/event' import { listen } from '@tauri-apps/api/event'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { arch, platform } from '@tauri-apps/plugin-os' import { arch, platform } from '@tauri-apps/plugin-os'
import { notifyUser } from '@/lib/Notifications'
import { import {
isPermissionGranted, isPermissionGranted,
requestPermission requestPermission
} from '@tauri-apps/plugin-notification' } from '@tauri-apps/plugin-notification'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' import {
BaseDirectory,
exists,
readTextFile,
remove
} from '@tauri-apps/plugin-fs'
import { fetch } from '@tauri-apps/plugin-http' import { fetch } from '@tauri-apps/plugin-http'
import { verifySignature } from '@/lib/Util' import { verifySignature } from '@/lib/Util'
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window' import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
import VersionsDownloadPopup from '@/componets/popups/VersionsDownload' import VersionsDownloadPopup from '@/components/popups/VersionsDownload'
import GamesDownloadPopup from '@/componets/popups/GamesDownload' import GamesDownloadPopup from '@/components/popups/GamesDownload'
import DownloadsPopup from '@/componets/popups/Downloads' import DownloadsPopup from '@/components/popups/Downloads'
import ManageVersionPopup from '@/componets/popups/ManageVersion' import ManageVersionPopup from '@/components/popups/ManageVersion'
import ModDownloadsPopup from '@/componets/popups/ModDownloads' import ModDownloadsPopup from '@/components/popups/ModDownloads'
import { Mod } from '@/types/Mod' import { Mod } from '@/types/Mod'
import { load, Store } from '@tauri-apps/plugin-store'
import { notifyUser } from '@/lib/Notifications'
const roboto = Roboto({ const roboto = Roboto({
subsets: ['latin'] subsets: ['latin']
@@ -62,7 +63,6 @@ export default function RootLayout ({
const [downloadedVersionsConfig, setDownloadedVersionsConfig] = const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
useState<VersionsConfig | null>(null) useState<VersionsConfig | null>(null)
const [normalConfig, setNormalConfig] = useState<NormalConfig | null>(null)
const [showPopup, setShowPopup] = useState(false) const [showPopup, setShowPopup] = useState(false)
const [popupMode, setPopupMode] = useState<null | number>(null) const [popupMode, setPopupMode] = useState<null | number>(null)
@@ -84,20 +84,26 @@ export default function RootLayout ({
const pathname = usePathname() const pathname = usePathname()
const revisionCheck = useRef(false) const revisionCheck = useRef(false)
const [settings, setSettings] = useState<Store | null>(null)
const [notificationsAllowed, setNotificationsAllowed] =
useState<boolean>(false)
const [sidebarAlwaysShowGames, setSidebarAlwaysShowGames] =
useState<boolean>(false)
const [linuxUseWine, setLinuxUseWine] = useState<boolean>(false)
const [linuxWineCommand, setLinuxWineCommand] =
useState<string>('wine %path%')
const [theme, setTheme] = useState<string>('dark')
function getSpecialVersionsList (game?: number): GameVersion[] { function getSpecialVersionsList (game?: number): GameVersion[] {
if (!normalConfig || !serverVersionList) return [] if (!serverVersionList) return []
return serverVersionList.versions return serverVersionList.versions
.filter( .filter(
v => !Object.keys(downloadedVersionsConfig?.list ?? []).includes(v.id) v => !Object.keys(downloadedVersionsConfig?.list ?? []).includes(v.id)
) )
.filter(v => { .filter(v => {
if ( if (platformName == 'linux' && v.wine && linuxUseWine) return false
platformName == 'linux' &&
v.wine &&
!normalConfig.settings.useWineOnUnixWhenNeeded
)
return false
if (game && v.game != game) return false if (game && v.game != game) return false
if (category != -1 && v.category != category) return false if (category != -1 && v.category != category) return false
@@ -150,8 +156,7 @@ export default function RootLayout ({
} | null { } | null {
if (!downloadedVersionsConfig || !serverVersionList) return null if (!downloadedVersionsConfig || !serverVersionList) return null
const allowWine = const allowWine = platformName !== 'linux' || linuxUseWine
platformName !== 'linux' || normalConfig?.settings.useWineOnUnixWhenNeeded
const installed = Object.keys(downloadedVersionsConfig.list).filter(v => { const installed = Object.keys(downloadedVersionsConfig.list).filter(v => {
const info = getVersionInfo(v) const info = getVersionInfo(v)
@@ -353,10 +358,85 @@ export default function RootLayout ({
setLoadingText('Failed to download versions list.') setLoadingText('Failed to download versions list.')
return return
} }
const normalConfig = await readNormalConfig() const settingsLocal = await load('settings.json', {
autoSave: true,
defaults: {
version: client,
notificationsAllowed: true,
sidebarAlwaysShowGames: true,
linuxUseWine: false,
linuxWineCommand: 'wine %path%',
theme: 'dark'
}
})
settingsLocal.set('version', client)
const oldSettingsOptions = {
baseDir: BaseDirectory.AppLocalData
}
if (await exists('config.json', oldSettingsOptions)) {
const config = await readTextFile('config.json', oldSettingsOptions)
const raw = JSON.parse(config)
if (
raw.settings &&
raw.settings.theme &&
(raw.version == '1.0.0' ||
raw.version == '1.1.0' ||
raw.version == '1.1.1' ||
raw.version == '1.2.0' ||
raw.version == '1.3.0' ||
raw.version == '1.3.1' ||
raw.version == '1.4.0' ||
raw.version == '1.5.0' ||
raw.version == '1.5.1' ||
raw.version == '1.5.2' ||
raw.version == '1.5.3' ||
raw.version == '1.5.4')
) {
const parsed = Number(raw.settings.theme)
if (parsed == 3) raw.settings.theme = 2
if (parsed == 4) raw.settings.theme = 3
else if (parsed != 0 && parsed != 1) raw.settings.theme = 0
}
if (raw.settings.allowNotifications)
settingsLocal?.set(
'notificationsAllowed',
Boolean(raw.settings.allowNotifications)
)
if (raw.settings.alwaysShowGamesInSidebar)
settingsLocal?.set(
'sidebarAlwaysShowGames',
Boolean(raw.settings.alwaysShowGamesInSidebar)
)
if (raw.settings.useWineOnUnixWhenNeeded)
settingsLocal?.set(
'linuxUseWine',
Boolean(raw.settings.useWineOnUnixWhenNeeded)
)
if (raw.settings.wineOnUnixCommand)
settingsLocal?.set(
'linuxWineCommand',
String(raw.settings.wineOnUnixCommand)
)
if (raw.settings.theme) {
const tempTheme = (() => {
switch (Number(raw.settings.theme)) {
case 1:
return 'red'
case 2:
return 'blue'
case 3:
return 'purple'
default:
return 'dark'
}
})()
settingsLocal?.set('theme', tempTheme)
}
await remove('config.json', oldSettingsOptions)
}
const versionsConfig = await readVersionsConfig() const versionsConfig = await readVersionsConfig()
setDownloadedVersionsConfig(versionsConfig) setDownloadedVersionsConfig(versionsConfig)
setNormalConfig(normalConfig) setSettings(settingsLocal)
setLoading(false) setLoading(false)
if (!(await isPermissionGranted())) { if (!(await isPermissionGranted())) {
@@ -475,7 +555,7 @@ export default function RootLayout ({
: d : d
) )
) )
if (normalConfig?.settings.allowNotifications) if (await settings?.get<boolean>('notifications-allowed'))
await notifyUser( await notifyUser(
'Download Failed', 'Download Failed',
`The download for version ${info.displayName} has failed.` `The download for version ${info.displayName} has failed.`
@@ -560,7 +640,7 @@ export default function RootLayout ({
: d : d
) )
) )
if (normalConfig?.settings.allowNotifications) if (await settings?.get<boolean>('notifications-allowed'))
await notifyUser( await notifyUser(
'Download Failed', 'Download Failed',
`The download for version ${info.displayName} has failed.` `The download for version ${info.displayName} has failed.`
@@ -581,7 +661,7 @@ export default function RootLayout ({
getVersionInfo, getVersionInfo,
getGameInfo, getGameInfo,
downloadProgress, downloadProgress,
normalConfig settings
]) ])
useEffect(() => { useEffect(() => {
@@ -612,20 +692,62 @@ export default function RootLayout ({
})() })()
}, [serverVersionList, downloadedVersionsConfig, downloadVersions]) }, [serverVersionList, downloadedVersionsConfig, downloadVersions])
useEffect(() => {
if (
downloadProgress.length === 0 &&
downloadQueue.length === 0 &&
showPopup &&
popupMode === 1
) {
setTimeout(async () => {
if (notificationsAllowed) {
await notifyUser('Downloads Complete', 'All downloads finished!')
}
await getCurrentWindow().requestUserAttention(
UserAttentionType.Informational
)
closePopup()
}, 0)
}
}, [
downloadProgress,
downloadQueue,
showPopup,
popupMode,
closePopup,
notificationsAllowed
])
useEffect(() => {
if (!settings) return
const unlisteners: Promise<() => void>[] = []
function watch<T> (key: string, fn: (v: T | undefined) => void) {
settings!.get<T>(key).then(fn)
unlisteners.push(settings!.onKeyChange<T>(key, fn))
}
watch<string>('theme', v => setTheme(v ?? 'dark'))
watch<boolean>('notificationsAllowed', v =>
setNotificationsAllowed(v ?? true)
)
watch<boolean>('sidebarAlwaysShowGames', v =>
setSidebarAlwaysShowGames(v ?? true)
)
watch<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false))
watch<string>('linuxWineCommand', v =>
setLinuxWineCommand(v ?? 'wine %path%')
)
return () => {
unlisteners.forEach(u => u.then(fn => fn()))
}
}, [settings])
return ( return (
<> <>
<html lang='en' className={roboto.className}> <html lang='en' className={roboto.className}>
<body <body className={theme + '-theme'}>
className={
normalConfig?.settings.theme === 1
? 'red-theme'
: normalConfig?.settings.theme === 2
? 'blue-theme'
: normalConfig?.settings.theme === 3
? 'purple-theme'
: 'dark-theme'
}
>
{loading ? ( {loading ? (
<> <>
<div <div
@@ -675,8 +797,6 @@ export default function RootLayout ({
setFadeOut, setFadeOut,
downloadedVersionsConfig, downloadedVersionsConfig,
setDownloadedVersionsConfig, setDownloadedVersionsConfig,
normalConfig,
setNormalConfig,
managingVersion, managingVersion,
setManagingVersion, setManagingVersion,
getVersionInfo, getVersionInfo,
@@ -696,7 +816,13 @@ export default function RootLayout ({
selectedGame, selectedGame,
setViewingInfoFromDownloads, setViewingInfoFromDownloads,
showModInfo, showModInfo,
setShowModInfo setShowModInfo,
settings,
notificationsAllowed,
sidebarAlwaysShowGames,
linuxUseWine,
linuxWineCommand,
theme
}} }}
> >
<div <div
+1 -2
View File
@@ -20,7 +20,6 @@ export default function Installs () {
setFadeOut, setFadeOut,
setSelectedVersionList, setSelectedVersionList,
downloadedVersionsConfig, downloadedVersionsConfig,
normalConfig,
setSelectedGame, setSelectedGame,
getListOfGames, getListOfGames,
getVersionsAmountData, getVersionsAmountData,
@@ -32,7 +31,7 @@ export default function Installs () {
useEffect(() => { useEffect(() => {
if (!showPopup) return if (!showPopup) return
setSelectedVersionList([]) setSelectedVersionList([])
}, [normalConfig, setSelectedVersionList, showPopup]) }, [setSelectedVersionList, showPopup])
return ( return (
<div className='mx-4 mt-4'> <div className='mx-4 mt-4'>
+65 -162
View File
@@ -1,181 +1,84 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { Setting } from '@/components/Setting'
import { Setting } from '@/componets/Setting'
import { writeNormalConfig } from '@/lib/BazookaManager'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/app/GlobalProvider'
import { copyToClipboard } from '@/lib/Clipboard' import { copyToClipboard } from '@/lib/Clipboard'
import { platform } from '@tauri-apps/plugin-os' import { platform } from '@tauri-apps/plugin-os'
export default function Settings () { export default function Settings () {
const [allowNotifications, setAllowNotifications] = useState(false) const {
const [alwaysShowGamesInSidebar, setAlwaysShowGamesInSidebar] = settings,
useState(false) version,
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false) notificationsAllowed,
const [wineOnUnixCommand, setWineOnUnixCommand] = useState('wine %path%') sidebarAlwaysShowGames,
const [theme, setTheme] = useState(0) linuxUseWine,
linuxWineCommand,
const [loaded, setLoaded] = useState(false) theme
const { normalConfig, setNormalConfig, version } = useGlobal() } = useGlobal()
useEffect(() => {
;(async () => {
while (normalConfig != null) {
setAllowNotifications(normalConfig.settings.allowNotifications)
setAlwaysShowGamesInSidebar(
normalConfig.settings.alwaysShowGamesInSidebar
)
setUseWineOnUnixWhenNeeded(
normalConfig.settings.useWineOnUnixWhenNeeded
)
setWineOnUnixCommand(normalConfig.settings.wineOnUnixCommand)
setTheme(normalConfig.settings.theme)
setLoaded(true)
break
}
})()
}, [normalConfig])
return ( return (
<> <>
<p className='text-3xl ml-4 mt-4'>Settings</p> <p className='text-3xl ml-4 mt-4'>Settings</p>
{loaded && ( <div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'> <Setting
<Setting label='Allow sending notifications'
label='Allow sending notifications' value={notificationsAllowed}
value={allowNotifications} onChange={async () => {
onChange={async () => { await settings?.set('notificationsAllowed', !notificationsAllowed)
if (!normalConfig) return }}
setAllowNotifications(!allowNotifications) title='This setting does as you expect, allow the launcher to send notifications for when stuff like downloading is done.'
setNormalConfig({ />
...normalConfig, <Setting
settings: { label='Always show games in sidebar'
...normalConfig.settings, value={sidebarAlwaysShowGames}
allowNotifications: !allowNotifications onChange={async () => {
} await settings?.set(
}) 'sidebarAlwaysShowGames',
await writeNormalConfig({ !sidebarAlwaysShowGames
...normalConfig, )
settings: { }}
...normalConfig.settings, title="This setting will make it so when you are on a page like this, the games won't disappear."
allowNotifications: !allowNotifications />
} <Setting
}) label='Use wine if needed'
}} value={linuxUseWine}
title='This setting does as you expect, allow the launcher to send notifications for when stuff like downloading is done.' onChange={async () => {
/> await settings?.set('linuxUseWine', !linuxUseWine)
<Setting }}
label='Always show games in sidebar' className={platform() == 'linux' ? '' : 'hidden'}
value={alwaysShowGamesInSidebar} />
onChange={async () => { <p hidden={!(platform() == 'linux' && linuxUseWine)}>Wine Command:</p>
if (!normalConfig) return <input
setAlwaysShowGamesInSidebar(!alwaysShowGamesInSidebar) type='text'
setNormalConfig({ value={linuxWineCommand}
...normalConfig, onChange={async e => {
settings: { await settings?.set('linuxWineCommand', e.target.value)
...normalConfig.settings, }}
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar className={`input-field my-1 ${
} platform() == 'linux' && linuxWineCommand ? '' : 'hidden'
}) }`}
await writeNormalConfig({ ></input>
...normalConfig, <div title='The theme you want the launcher to use.'>
settings: { <label className='text-lg'>Theme:</label>
...normalConfig.settings, <select
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar className='ml-2 bg-(--col2) border border-(--col4) rounded-md'
} value={theme}
})
}}
title="This setting will make it so when you are on a page like this, the games won't disappear."
/>
<Setting
label='Use wine when needed to launch games'
value={useWineOnUnixWhenNeeded}
onChange={async () => {
if (!normalConfig) return
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
setNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
}
})
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
}
})
}}
className={platform() == 'linux' ? '' : 'hidden'}
/>
<p hidden={!(platform() == 'linux' && useWineOnUnixWhenNeeded)}>
Wine Command:
</p>
<input
type='text'
value={wineOnUnixCommand}
onChange={async e => { onChange={async e => {
if (!normalConfig) return await settings?.set('theme', e.target.value)
setWineOnUnixCommand(e.target.value)
setNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
wineOnUnixCommand: e.target.value
}
})
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
wineOnUnixCommand: e.target.value
}
})
}} }}
className={`input-field my-1 ${ >
platform() == 'linux' && useWineOnUnixWhenNeeded ? '' : 'hidden' <option value={'dark'}>Dark (default)</option>
}`} <option value={'red'}>Red</option>
></input> <option value={'blue'}>Blue</option>
<div title='The theme you want the launcher to use.'> <option value={'purple'}>Purple</option>
<label className='text-lg'>Theme:</label> </select>
<select
className='ml-2 bg-(--col2) border border-(--col4) rounded-md'
value={theme}
onChange={async e => {
if (!normalConfig) return
const newTheme = parseInt(e.target.value)
setTheme(newTheme)
setNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
theme: newTheme
}
})
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
theme: newTheme
}
})
}}
>
<option value={0}>Dark (default)</option>
<option value={1}>Red</option>
<option value={2}>Blue</option>
<option value={3}>Purple</option>
</select>
</div>
</div> </div>
)} </div>
<p <p
className='fixed bottom-1.5 right-1.5 rounded-md cursor-pointer px-1 border z-100 transition-colors btntheme1' className='fixed bottom-1.5 right-1.5 rounded-md cursor-pointer px-1 border z-100 transition-colors btntheme1'
onClick={async () => { onClick={async () =>
await copyToClipboard(`v${version}`, normalConfig) await copyToClipboard(`v${version}`, notificationsAllowed)
}} }
title='The current launcher version.' title='The current launcher version.'
> >
v{version} v{version}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -27,7 +27,6 @@ const lexend = Lexend({
export default function Sidebar () { export default function Sidebar () {
const { const {
normalConfig,
getListOfGames, getListOfGames,
setShowPopup, setShowPopup,
setPopupMode, setPopupMode,
@@ -36,7 +35,9 @@ export default function Sidebar () {
downloadedVersionsConfig, downloadedVersionsConfig,
getVersionInfo, getVersionInfo,
category, category,
setCategory setCategory,
sidebarAlwaysShowGames,
linuxUseWine
} = useGlobal() } = useGlobal()
const pathname = usePathname() const pathname = usePathname()
@@ -104,7 +105,7 @@ export default function Sidebar () {
? 'active' ? 'active'
: '' : ''
} ml-auto w-50 ${ } ml-auto w-50 ${
normalConfig?.settings.alwaysShowGamesInSidebar || sidebarAlwaysShowGames ||
pathname === '/' || pathname === '/' ||
pathname.startsWith('/game') pathname.startsWith('/game')
? '' ? ''
@@ -136,11 +137,7 @@ export default function Sidebar () {
const info = getVersionInfo(v) const info = getVersionInfo(v)
if (!info) return false if (!info) return false
if ( if (platform() == 'linux' && info.wine && !linuxUseWine)
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
return false return false
return info.game === i.id && info.category === Number(key) return info.game === i.id && info.category === Number(key)
@@ -161,7 +158,7 @@ export default function Sidebar () {
? 'active' ? 'active'
: '' : ''
} ml-auto w-47.5 ${ } ml-auto w-47.5 ${
normalConfig?.settings.alwaysShowGamesInSidebar || sidebarAlwaysShowGames ||
pathname === '/' || pathname === '/' ||
pathname.startsWith('/game') pathname.startsWith('/game')
? '' ? ''
@@ -21,8 +21,8 @@ export default function DownloadsPopup () {
setDownloadProgress, setDownloadProgress,
downloadQueue, downloadQueue,
setDownloadQueue, setDownloadQueue,
normalConfig, setDownloadedVersionsConfig,
setDownloadedVersionsConfig notificationsAllowed
} = useGlobal() } = useGlobal()
return ( return (
@@ -58,28 +58,42 @@ export default function DownloadsPopup () {
const res = await invoke<string>('download', { const res = await invoke<string>('download', {
url: v.url, url: v.url,
name: v.version, name: v.version,
executable: v.executable, hash: v.hash,
hash: v.hash downloadType: v.type,
modId: String(v.modId ?? '')
}) })
if (res === '1') { if (res === '1') {
setDownloadProgress(prev => setDownloadProgress(prev =>
prev.filter(d => d.version !== v.version) prev.filter(d => d.version !== v.version)
) )
setDownloadedVersionsConfig(prev => { if (v.type != 1) {
if (!prev) return prev setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updated = { const updated =
...prev, v.type == 2
list: { ? {
...prev.list, ...prev,
[v.version]: Date.now() mods: {
} ...prev.mods,
} [v.modGame! + '-' + v.modId!]: {
[v.modVersion!]: Date.now()
}
}
}
: {
...prev,
list: {
...prev.list,
[v.version]: Date.now()
}
}
writeVersionsConfig(updated) writeVersionsConfig(updated)
return updated return updated
}) })
}
} else if (res == '0') { } else if (res == '0') {
setDownloadProgress(prev => { setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === v.version) const i = prev.findIndex(d => d.version === v.version)
@@ -109,7 +123,7 @@ export default function DownloadsPopup () {
: d : d
) )
) )
if (normalConfig?.settings.allowNotifications) if (notificationsAllowed)
await notifyUser( await notifyUser(
'Download Failed', 'Download Failed',
`The download for version ${ `The download for version ${
@@ -17,6 +17,7 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { message } from '@tauri-apps/plugin-dialog' import { message } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { fetch } from '@tauri-apps/plugin-http'
import { arch, platform } from '@tauri-apps/plugin-os' import { arch, platform } from '@tauri-apps/plugin-os'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
-75
View File
@@ -1,5 +1,4 @@
import { app } from '@tauri-apps/api' import { app } from '@tauri-apps/api'
import { NormalConfig } from '@/types/NormalConfig'
import { import {
BaseDirectory, BaseDirectory,
create, create,
@@ -10,80 +9,6 @@ import {
} from '@tauri-apps/plugin-fs' } from '@tauri-apps/plugin-fs'
import { VersionsConfig } from '@/types/VersionsConfig' import { VersionsConfig } from '@/types/VersionsConfig'
export async function readNormalConfig (): Promise<NormalConfig> {
const version = await app.getVersion()
try {
const options = {
baseDir: BaseDirectory.AppLocalData
}
const doesFolderExist = await exists('', options)
const doesConfigExist = await exists('config.json', options)
if (!doesFolderExist || !doesConfigExist) {
if (!doesFolderExist) {
await mkdir('', options)
}
const file = await create('config.json', options)
await file.write(
new TextEncoder().encode(
JSON.stringify(new NormalConfig(version), null, 2)
)
)
await file.close()
return new NormalConfig(version)
}
const config = await readTextFile('config.json', options)
const raw = JSON.parse(config)
if (
raw.settings &&
raw.settings.theme &&
(raw.version == '1.0.0' ||
raw.version == '1.1.0' ||
raw.version == '1.1.1' ||
raw.version == '1.2.0' ||
raw.version == '1.3.0' ||
raw.version == '1.3.1' ||
raw.version == '1.4.0' ||
raw.version == '1.5.0' ||
raw.version == '1.5.1' ||
raw.version == '1.5.2' ||
raw.version == '1.5.3' ||
raw.version == '1.5.4')
) {
const parsed = Number(raw.settings.theme)
if (parsed == 3) raw.settings.theme = 2
if (parsed == 4) raw.settings.theme = 3
else if (parsed != 0 && parsed != 1) raw.settings.theme = 0
}
raw.version = version
writeNormalConfig(raw)
return NormalConfig.import(raw)
} catch {
return new NormalConfig(version)
}
}
export async function writeNormalConfig (data: NormalConfig) {
const options = {
baseDir: BaseDirectory.AppLocalData
}
const doesFolderExist = await exists('', options)
const doesConfigExist = await exists('config.json', options)
if (!doesFolderExist || !doesConfigExist) {
if (!doesFolderExist) {
await mkdir('', options)
}
const file = await create('config.json', options)
await file.write(new TextEncoder().encode(JSON.stringify(data, null, 2)))
await file.close()
} else {
await writeFile(
'config.json',
new TextEncoder().encode(JSON.stringify(data, null, 2)),
options
)
}
}
export async function readVersionsConfig (): Promise<VersionsConfig> { export async function readVersionsConfig (): Promise<VersionsConfig> {
const version = await app.getVersion() const version = await app.getVersion()
try { try {
+4 -3
View File
@@ -1,10 +1,11 @@
import { NormalConfig } from '@/types/NormalConfig' import { writeText } from '@tauri-apps/plugin-clipboard-manager'
import { notifyUser } from './Notifications' import { notifyUser } from './Notifications'
export async function copyToClipboard ( export async function copyToClipboard (
text: string, text: string,
normalConfig: NormalConfig | null notificationsAllowed: boolean
) { ) {
if (normalConfig?.settings.allowNotifications) await writeText(text)
if (notificationsAllowed)
await notifyUser('Copied', 'Text "' + text + '" copied to clipboard') await notifyUser('Copied', 'Text "' + text + '" copied to clipboard')
} }
-21
View File
@@ -1,21 +0,0 @@
import { SettingsType } from './SettingsType'
type NormalConfigData = {
version: string
settings?: Partial<SettingsType>
}
export class NormalConfig {
constructor (
public version: string,
public settings: SettingsType = new SettingsType()
) {}
static import (data: NormalConfigData) {
const cfg = new NormalConfig(data.version)
if (data.settings) {
cfg.settings = { ...cfg.settings, ...data.settings }
}
return cfg
}
}
-9
View File
@@ -1,9 +0,0 @@
export class SettingsType {
constructor (
public allowNotifications: boolean = true,
public alwaysShowGamesInSidebar: boolean = true,
public useWineOnUnixWhenNeeded: boolean = false,
public wineOnUnixCommand: string = 'wine %path%',
public theme: number = 0
) {}
}