Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2777e6dac0
|
||
|
|
a551d5fb52
|
||
|
|
08915ad2ae
|
||
|
|
1dd75f8ccf
|
||
|
|
3dc24ab3d7
|
||
|
|
a3e9067ba0
|
||
|
|
a0a763ae57
|
||
|
|
c56a903d55
|
||
|
|
c07828f244
|
||
|
|
cf4f1c15ea
|
||
|
|
76797c6120
|
||
|
|
36ed3e0095
|
||
|
|
e0ba25da77
|
||
|
|
e89b0fac8a
|
||
|
|
fa256dd004
|
||
|
|
b5c6598722
|
||
|
|
db111aaa2e
|
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "lncvrt-games-launcher",
|
"name": "lncvrt-games-launcher",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.0",
|
"version": "1.5.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lncvrt-games-launcher"
|
name = "lncvrt-games-launcher"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
authors = ["Lncvrt"]
|
authors = ["Lncvrt"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@@ -15,15 +15,15 @@ tauri-build = { version = "2.5.3", features = [] }
|
|||||||
tauri = { version = "2.9.5", features = ["macos-private-api"] }
|
tauri = { version = "2.9.5", features = ["macos-private-api"] }
|
||||||
tauri-plugin-opener = "2.5.2"
|
tauri-plugin-opener = "2.5.2"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.148"
|
serde_json = "1.0.149"
|
||||||
tauri-plugin-os = "2.3.2"
|
tauri-plugin-os = "2.3.2"
|
||||||
reqwest = { version = "0.13.1", default-features = false, features = ["stream", "rustls"] }
|
reqwest = { version = "0.13.1", default-features = false, features = ["stream", "rustls"] }
|
||||||
tokio = "1.48.0"
|
tokio = "1.49.0"
|
||||||
futures-util = { version = "0.3.31", features = ["io"] }
|
futures-util = { version = "0.3.31", features = ["io"] }
|
||||||
tauri-plugin-decorum = "1.1.1"
|
tauri-plugin-decorum = "1.1.1"
|
||||||
tauri-plugin-fs = "2.4.4"
|
tauri-plugin-fs = "2.4.4"
|
||||||
zip = "7.0.0"
|
zip = "7.0.0"
|
||||||
libc = "0.2.178"
|
libc = "0.2.179"
|
||||||
tauri-plugin-dialog = "2.4.2"
|
tauri-plugin-dialog = "2.4.2"
|
||||||
tauri-plugin-notification = "2.3.3"
|
tauri-plugin-notification = "2.3.3"
|
||||||
sysinfo = "0.37.2"
|
sysinfo = "0.37.2"
|
||||||
|
|||||||
@@ -21,11 +21,17 @@
|
|||||||
"core:window:allow-toggle-maximize",
|
"core:window:allow-toggle-maximize",
|
||||||
"decorum:allow-show-snap-overlay",
|
"decorum:allow-show-snap-overlay",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"fs:allow-applocaldata-read",
|
|
||||||
"fs:allow-applocaldata-write",
|
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"clipboard-manager:default",
|
"clipboard-manager:default",
|
||||||
"clipboard-manager:allow-write-text"
|
"clipboard-manager:allow-write-text",
|
||||||
|
"fs:allow-applocaldata-read",
|
||||||
|
"fs:allow-applocaldata-write",
|
||||||
|
{
|
||||||
|
"identifier": "fs:scope",
|
||||||
|
"allow": [
|
||||||
|
{ "path": "$APPLOCALDATA/game/**" }
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+70
-76
@@ -15,22 +15,25 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use tauri_plugin_os::platform;
|
use tauri_plugin_os::platform;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::{io::AsyncWriteExt, time::timeout};
|
use tokio::{io::AsyncWriteExt, time::timeout};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
|
|
||||||
fn is_running_by_path(path: &PathBuf) -> bool {
|
#[allow(unused)]
|
||||||
|
fn is_running_by_path(path: &Path) -> bool {
|
||||||
let sys = System::new_all();
|
let sys = System::new_all();
|
||||||
|
let target = path.canonicalize().ok();
|
||||||
|
if target.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let target = target.unwrap();
|
||||||
sys.processes().values().any(|proc| {
|
sys.processes().values().any(|proc| {
|
||||||
if let Some(exe) = proc.exe() {
|
proc.exe()
|
||||||
exe == path
|
.and_then(|exe| exe.canonicalize().ok())
|
||||||
} else {
|
.map_or(false, |exe| exe == target)
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +185,26 @@ async fn download(
|
|||||||
|
|
||||||
app.emit("download-hash-checking", format!("{}", &name))
|
app.emit("download-hash-checking", format!("{}", &name))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut hasher = Sha512::new();
|
|
||||||
hasher.update(&tokio::fs::read(download_part_path.clone()).await.unwrap());
|
let download_hash = {
|
||||||
let download_hash = format!("{:x}", hasher.finalize());
|
let mut file = tokio::fs::File::open(download_part_path.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut hasher = Sha512::new();
|
||||||
|
{
|
||||||
|
let mut buffer = [0; 8192];
|
||||||
|
loop {
|
||||||
|
let bytes_read = file.read(&mut buffer).await.unwrap();
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buffer[..bytes_read]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(file);
|
||||||
|
format!("{:x}", hasher.finalize())
|
||||||
|
};
|
||||||
|
|
||||||
if hash != download_hash {
|
if hash != download_hash {
|
||||||
tokio::fs::remove_file(download_part_path.clone())
|
tokio::fs::remove_file(download_part_path.clone())
|
||||||
.await
|
.await
|
||||||
@@ -205,102 +225,77 @@ async fn download(
|
|||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
let executable_path = game_path.join(&name).join(&executable);
|
use std::{fs, os::unix::fs::PermissionsExt};
|
||||||
|
|
||||||
|
let executable_path = if cfg!(target_os = "linux") {
|
||||||
|
game_path.join(&name).join(&executable)
|
||||||
|
} else {
|
||||||
|
game_path
|
||||||
|
.join(&name)
|
||||||
|
.join(&executable)
|
||||||
|
.join("Contents")
|
||||||
|
.join("MacOS")
|
||||||
|
.join(
|
||||||
|
&executable
|
||||||
|
.chars()
|
||||||
|
.take(executable.chars().count() - 4)
|
||||||
|
.collect::<String>(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut perms = fs::metadata(&executable_path).unwrap().permissions();
|
let mut perms = fs::metadata(&executable_path).unwrap().permissions();
|
||||||
perms.set_mode(0o755);
|
perms.set_mode(0o755);
|
||||||
fs::set_permissions(executable_path, perms).unwrap();
|
fs::set_permissions(&executable_path, perms).unwrap();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use std::fs;
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
|
|
||||||
let macos_app_path = game_path
|
|
||||||
.join(&name)
|
|
||||||
.join(&executable)
|
|
||||||
.join("Contents")
|
|
||||||
.join("MacOS")
|
|
||||||
.join(
|
|
||||||
&executable
|
|
||||||
.chars()
|
|
||||||
.take(executable.chars().count() - 4)
|
|
||||||
.collect::<String>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut perms = fs::metadata(&macos_app_path).unwrap().permissions();
|
|
||||||
perms.set_mode(0o755);
|
|
||||||
fs::set_permissions(&macos_app_path, perms).unwrap();
|
|
||||||
}
|
|
||||||
return "1".to_string();
|
return "1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn launch_game(app: AppHandle, name: String, executable: String) {
|
fn launch_game(app: AppHandle, name: String, executable: String, display_name: String) {
|
||||||
let game_folder = app
|
let game_folder = app
|
||||||
.path()
|
.path()
|
||||||
.app_local_data_dir()
|
.app_local_data_dir()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("game")
|
.join("game")
|
||||||
.join(&name);
|
.join(&name);
|
||||||
let game_path = game_folder.join(&executable);
|
if !game_folder.exists() {
|
||||||
if !game_path.exists() {
|
|
||||||
app.dialog()
|
|
||||||
.message(
|
|
||||||
format!(
|
|
||||||
"Executable \"{}\" not found.\n\nTry reinstalling the game or make a support request in the Community link on the sidebar.",
|
|
||||||
game_path.display().to_string()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.kind(MessageDialogKind::Error)
|
|
||||||
.title("Game not found")
|
|
||||||
.show(|_| {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_running_by_path(&game_path) {
|
|
||||||
app.dialog()
|
//if already running on macos, it'll auto take the user to that proccess
|
||||||
.message(format!("The version {} is already running.", name))
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
.kind(MessageDialogKind::Error)
|
{
|
||||||
.title("Game already running")
|
if is_running_by_path(&game_folder.join(&executable)) {
|
||||||
.show(|_| {});
|
app.dialog()
|
||||||
return;
|
.message(format!(
|
||||||
|
"{} is already running, if this doesn't seem true, try to kill the proccess.",
|
||||||
|
display_name
|
||||||
|
))
|
||||||
|
.kind(MessageDialogKind::Error)
|
||||||
|
.title("Game already running")
|
||||||
|
.show(|_| {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform() == "macos" {
|
if platform() == "macos" {
|
||||||
Command::new("open")
|
Command::new("open")
|
||||||
.arg(&game_path)
|
.arg(&executable)
|
||||||
.current_dir(&game_folder)
|
.current_dir(&game_folder)
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
Command::new(&game_path)
|
Command::new(&game_folder.join(&executable))
|
||||||
.current_dir(&game_folder)
|
.current_dir(&game_folder)
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn uninstall_version(app: AppHandle, name: String) {
|
|
||||||
let game_path = app
|
|
||||||
.path()
|
|
||||||
.app_local_data_dir()
|
|
||||||
.unwrap()
|
|
||||||
.join("game")
|
|
||||||
.join(&name);
|
|
||||||
if game_path.exists() {
|
|
||||||
if let Err(_) = tokio::fs::remove_dir_all(&game_path).await {
|
|
||||||
app.emit("version-failed", &name).unwrap();
|
|
||||||
} else {
|
|
||||||
app.emit("version-uninstalled", &name).unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.emit("version-uninstalled", &name).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn open_folder(app: AppHandle, name: String) {
|
async fn open_folder(app: AppHandle, name: String) {
|
||||||
let game_path = app
|
let game_path = app
|
||||||
@@ -345,7 +340,6 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
download,
|
download,
|
||||||
launch_game,
|
launch_game,
|
||||||
uninstall_version,
|
|
||||||
open_folder,
|
open_folder,
|
||||||
folder_size
|
folder_size
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -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.4.0",
|
"version": "1.5.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "next dev",
|
||||||
|
|||||||
@@ -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.4.0",
|
"version": "1.5.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "next dev",
|
||||||
|
|||||||
@@ -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.4.0",
|
"version": "1.5.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "next dev",
|
||||||
|
|||||||
@@ -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.4.0",
|
"version": "1.5.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev",
|
"beforeDevCommand": "next dev",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type GlobalCtxType = {
|
|||||||
setManagingVersion: (v: string | null) => void
|
setManagingVersion: (v: string | null) => void
|
||||||
setSelectedGame: (v: number | null) => void
|
setSelectedGame: (v: number | null) => void
|
||||||
getVersionInfo: (id: string | undefined) => GameVersion | undefined
|
getVersionInfo: (id: string | undefined) => GameVersion | undefined
|
||||||
getVersionGame: (id: number | undefined) => Game | undefined
|
getGameInfo: (id: number | undefined) => Game | undefined
|
||||||
getListOfGames: () => Game[]
|
getListOfGames: () => Game[]
|
||||||
getVersionsAmountData: (gameId: number) => {
|
getVersionsAmountData: (gameId: number) => {
|
||||||
installed: number
|
installed: number
|
||||||
|
|||||||
+2
-2
@@ -77,7 +77,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@apply rounded-md cursor-pointer text-[16px] py-1.5 px-3 transition-colors border;
|
@apply rounded-md cursor-pointer text-[16px] py-1 px-2 transition-colors border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btntheme1 {
|
.btntheme1 {
|
||||||
@@ -147,7 +147,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.close-button {
|
.close-button {
|
||||||
@apply flex justify-center items-center absolute bg-(--col3) hover:bg-(--col4) text-2xl cursor-pointer text-gray-300 hover:text-white h-12 w-12 p-3 rounded-xl left-2 top-2 transition-colors border border-(--col4) hover:border-(--col5);
|
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg left-2 top-2 transition-colors border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*:focus {
|
*:focus {
|
||||||
|
|||||||
@@ -11,3 +11,13 @@
|
|||||||
.downloads-entry {
|
.downloads-entry {
|
||||||
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5);
|
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.downloads-entry .entry-info-item,
|
||||||
|
.downloads-entry .button {
|
||||||
|
@apply bg-(--col3) border-(--col5) hover:bg-(--col5) hover:border-(--col7) disabled:text-gray-300 disabled:bg-(--col2) disabled:border-(--col6) disabled:hover:text-gray-100 disabled:hover:bg-(--col3) disabled:hover:border-(--col7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloads-entry:hover .entry-info-item,
|
||||||
|
.downloads-entry:hover .button {
|
||||||
|
@apply bg-(--col4) border-(--col6) hover:bg-(--col6) hover:border-(--col8) disabled:text-gray-300 disabled:bg-(--col2) disabled:border-(--col7) disabled:hover:text-gray-100 disabled:hover:bg-(--col4) disabled:hover:border-(--col8);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { useGlobal } from '../GlobalProvider'
|
|||||||
|
|
||||||
export default function VersionChangelog () {
|
export default function VersionChangelog () {
|
||||||
const {
|
const {
|
||||||
getVersionGame,
|
getGameInfo,
|
||||||
getVersionInfo,
|
getVersionInfo,
|
||||||
managingVersion,
|
managingVersion,
|
||||||
downloadedVersionsConfig
|
downloadedVersionsConfig
|
||||||
@@ -12,7 +12,7 @@ export default function VersionChangelog () {
|
|||||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||||
|
|
||||||
const versionInfo = getVersionInfo(managingVersion)
|
const versionInfo = getVersionInfo(managingVersion)
|
||||||
const gameInfo = getVersionGame(versionInfo?.game)
|
const gameInfo = getGameInfo(versionInfo?.game)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import prettyBytes from 'pretty-bytes'
|
|||||||
|
|
||||||
export default function VersionInfo () {
|
export default function VersionInfo () {
|
||||||
const {
|
const {
|
||||||
getVersionGame,
|
getGameInfo,
|
||||||
getVersionInfo,
|
getVersionInfo,
|
||||||
managingVersion,
|
managingVersion,
|
||||||
downloadedVersionsConfig,
|
downloadedVersionsConfig,
|
||||||
@@ -27,7 +27,7 @@ export default function VersionInfo () {
|
|||||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||||
|
|
||||||
const versionInfo = getVersionInfo(managingVersion)
|
const versionInfo = getVersionInfo(managingVersion)
|
||||||
const gameInfo = getVersionGame(versionInfo?.game)
|
const gameInfo = getGameInfo(versionInfo?.game)
|
||||||
const [versionSize, setVersionSize] = useState<number | null>(null)
|
const [versionSize, setVersionSize] = useState<number | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+68
-64
@@ -19,12 +19,11 @@ export default function Installs () {
|
|||||||
normalConfig,
|
normalConfig,
|
||||||
setManagingVersion,
|
setManagingVersion,
|
||||||
getVersionInfo,
|
getVersionInfo,
|
||||||
getVersionGame,
|
getGameInfo,
|
||||||
setSelectedGame
|
setSelectedGame
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
const params = useSearchParams()
|
const params = useSearchParams()
|
||||||
const [hoveredIds, setHoveredIds] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPopup) return
|
if (!showPopup) return
|
||||||
@@ -36,7 +35,7 @@ export default function Installs () {
|
|||||||
<div className='flex justify-between items-center mb-4'>
|
<div className='flex justify-between items-center mb-4'>
|
||||||
<p className='text-3xl'>Installs</p>
|
<p className='text-3xl'>Installs</p>
|
||||||
<button
|
<button
|
||||||
className='button btntheme1 text-3xl'
|
className='button btntheme1'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedGame(Number(params.get('id') || 0))
|
setSelectedGame(Number(params.get('id') || 0))
|
||||||
setPopupMode(0)
|
setPopupMode(0)
|
||||||
@@ -85,31 +84,36 @@ export default function Installs () {
|
|||||||
title={
|
title={
|
||||||
normalConfig?.settings.useLegacyInteractButtons
|
normalConfig?.settings.useLegacyInteractButtons
|
||||||
? ''
|
? ''
|
||||||
: 'Click to launch game'
|
: 'Click to launch game. Right-click to manage this version install'
|
||||||
}
|
}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||||
const verInfo = getVersionInfo(entry)
|
const verInfo = getVersionInfo(entry)
|
||||||
if (verInfo == undefined) return
|
if (verInfo == undefined) return
|
||||||
|
const gameInfo = getGameInfo(verInfo.game)
|
||||||
|
if (gameInfo == undefined) return
|
||||||
invoke('launch_game', {
|
invoke('launch_game', {
|
||||||
name: verInfo.id,
|
name: verInfo.id,
|
||||||
executable: verInfo.executable
|
executable: verInfo.executable,
|
||||||
|
displayName: `${gameInfo.name} v${verInfo.versionName}`
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredIds(prev => [...prev, entry])}
|
onContextMenu={e => {
|
||||||
onMouseLeave={() =>
|
e.preventDefault()
|
||||||
setHoveredIds(prev => prev.filter(i => i !== i))
|
setManagingVersion(entry)
|
||||||
}
|
setPopupMode(2)
|
||||||
|
setShowPopup(true)
|
||||||
|
setFadeOut(false)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col'>
|
<div className='h-18 w-screen relative'>
|
||||||
<p className='text-2xl'>
|
<p className='text-2xl'>
|
||||||
{getVersionGame(getVersionInfo(entry)?.game)?.name} v
|
{getGameInfo(getVersionInfo(entry)?.game)?.name} v
|
||||||
{getVersionInfo(entry)?.versionName}
|
{getVersionInfo(entry)?.versionName}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`entry-info-item mt-2 ${
|
className='entry-info-item absolute left-0 bottom-0'
|
||||||
hoveredIds.includes(entry) ? 'btntheme3' : 'btntheme2'
|
|
||||||
}`}
|
|
||||||
title='The date the game was installed in MM/dd/yyyy format'
|
title='The date the game was installed in MM/dd/yyyy format'
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
@@ -120,56 +124,56 @@ export default function Installs () {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className='flex flex-row items-center gap-2 mt-auto'>
|
<div className='flex gap-2 absolute right-0 bottom-0'>
|
||||||
<button
|
<button
|
||||||
className={`button ${
|
className='button'
|
||||||
hoveredIds.includes(entry) ? 'btntheme3' : 'btntheme2'
|
onClick={e => {
|
||||||
}`}
|
e.stopPropagation()
|
||||||
onClick={e => {
|
setManagingVersion(entry)
|
||||||
e.stopPropagation()
|
setPopupMode(3)
|
||||||
setManagingVersion(entry)
|
setShowPopup(true)
|
||||||
setPopupMode(3)
|
setFadeOut(false)
|
||||||
setShowPopup(true)
|
}}
|
||||||
setFadeOut(false)
|
title='Click to view version info'
|
||||||
}}
|
>
|
||||||
title='Click to view version info'
|
View Info
|
||||||
>
|
</button>
|
||||||
View Info
|
<button
|
||||||
</button>
|
className='button'
|
||||||
<button
|
hidden={
|
||||||
className={`button ${
|
!normalConfig?.settings.useLegacyInteractButtons
|
||||||
hoveredIds.includes(entry) ? 'btntheme3' : 'btntheme2'
|
}
|
||||||
}`}
|
onClick={e => {
|
||||||
onClick={e => {
|
e.stopPropagation()
|
||||||
e.stopPropagation()
|
setManagingVersion(entry)
|
||||||
setManagingVersion(entry)
|
setPopupMode(2)
|
||||||
setPopupMode(2)
|
setShowPopup(true)
|
||||||
setShowPopup(true)
|
setFadeOut(false)
|
||||||
setFadeOut(false)
|
}}
|
||||||
}}
|
title='Click to manage this version install'
|
||||||
title='Click to manage this version install'
|
>
|
||||||
>
|
Manage
|
||||||
Manage
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
className='button'
|
||||||
className={`button ${
|
onClick={e => {
|
||||||
hoveredIds.includes(entry) ? 'btntheme3' : 'btntheme2'
|
e.stopPropagation()
|
||||||
}`}
|
const verInfo = getVersionInfo(entry)
|
||||||
onClick={e => {
|
if (verInfo == undefined) return
|
||||||
e.stopPropagation()
|
invoke('launch_game', {
|
||||||
const verInfo = getVersionInfo(entry)
|
name: verInfo.id,
|
||||||
if (verInfo == undefined) return
|
executable: verInfo.executable
|
||||||
invoke('launch_game', {
|
})
|
||||||
name: verInfo.id,
|
}}
|
||||||
executable: verInfo.executable
|
hidden={
|
||||||
})
|
!normalConfig?.settings.useLegacyInteractButtons
|
||||||
}}
|
}
|
||||||
hidden={!normalConfig?.settings.useLegacyInteractButtons}
|
title='Click to launch game'
|
||||||
title='Click to launch game'
|
>
|
||||||
>
|
Launch
|
||||||
Launch
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
+227
-243
@@ -45,6 +45,7 @@ import {
|
|||||||
requestPermission
|
requestPermission
|
||||||
} from '@tauri-apps/plugin-notification'
|
} from '@tauri-apps/plugin-notification'
|
||||||
import VersionChangelog from './componets/VersionChangelog'
|
import VersionChangelog from './componets/VersionChangelog'
|
||||||
|
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||||
|
|
||||||
const roboto = Roboto({
|
const roboto = Roboto({
|
||||||
subsets: ['latin']
|
subsets: ['latin']
|
||||||
@@ -80,24 +81,100 @@ export default function RootLayout ({
|
|||||||
useState<boolean>(false)
|
useState<boolean>(false)
|
||||||
const [selectedGame, setSelectedGame] = useState<number | null>(null)
|
const [selectedGame, setSelectedGame] = useState<number | null>(null)
|
||||||
|
|
||||||
function handleOverlayClick (e: React.MouseEvent<HTMLDivElement>) {
|
const pathname = usePathname()
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
if (viewingInfoFromDownloads) {
|
function getSpecialVersionsList (game?: number): GameVersion[] {
|
||||||
setPopupMode(0)
|
if (!normalConfig || !serverVersionList) return []
|
||||||
setViewingInfoFromDownloads(false)
|
|
||||||
setManagingVersion(null)
|
return serverVersionList.versions
|
||||||
setSelectedGame(null)
|
.filter(v => !downloadedVersionsConfig?.list.includes(v.id))
|
||||||
}
|
.filter(v => {
|
||||||
|
if (game && v.game != game) return false
|
||||||
|
if (downloadProgress.length != 0) {
|
||||||
|
return !downloadProgress.some(d => d.version === v.id)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (b.game !== a.game) return a.game - b.game
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionInfo (id: string | undefined): GameVersion | undefined {
|
||||||
|
if (!id) return undefined
|
||||||
|
return serverVersionList?.versions.find(v => v.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGameInfo (game: number | undefined): Game | undefined {
|
||||||
|
if (!game) return undefined
|
||||||
|
return serverVersionList?.games.find(g => g.id === game)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getListOfGames (): Game[] {
|
||||||
|
if (!downloadedVersionsConfig?.list) return []
|
||||||
|
|
||||||
|
const gamesMap = new Map<number, Game>()
|
||||||
|
|
||||||
|
downloadedVersionsConfig.list.forEach(i => {
|
||||||
|
const version = getVersionInfo(i)
|
||||||
|
if (!version) return
|
||||||
|
const game = getGameInfo(version.game)
|
||||||
|
if (!game) return
|
||||||
|
gamesMap.set(game.id, game)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(gamesMap.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionsAmountData (gameId: number): {
|
||||||
|
installed: number
|
||||||
|
total: number
|
||||||
|
} | null {
|
||||||
|
if (!downloadedVersionsConfig || !serverVersionList) return null
|
||||||
|
|
||||||
|
const installed = downloadedVersionsConfig.list.filter(
|
||||||
|
v => getGameInfo(getVersionInfo(v)?.game)?.id === gameId
|
||||||
|
).length
|
||||||
|
|
||||||
|
const total = serverVersionList.versions.filter(
|
||||||
|
v => getGameInfo(v?.game)?.id === gameId
|
||||||
|
).length
|
||||||
|
|
||||||
|
return { installed, total }
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEtaSmart (seconds: number) {
|
||||||
|
if (seconds < 60) return `${Math.floor(seconds)}s`
|
||||||
|
if (seconds < 3600)
|
||||||
|
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
||||||
|
if (seconds < 86400) {
|
||||||
|
const h = Math.floor(seconds / 3600)
|
||||||
|
const m = Math.floor((seconds % 3600) / 60)
|
||||||
|
return `${h}h ${m}m`
|
||||||
|
}
|
||||||
|
const d = Math.floor(seconds / 86400)
|
||||||
|
const h = Math.floor((seconds % 86400) / 3600)
|
||||||
|
return `${d}d ${h}h`
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePopup () {
|
||||||
|
if (popupMode == 0 && selectedGame && pathname === '/') {
|
||||||
|
setSelectedGame(null)
|
||||||
|
setSelectedVersionList([])
|
||||||
|
} else if (viewingInfoFromDownloads) {
|
||||||
|
setViewingInfoFromDownloads(false)
|
||||||
|
setPopupMode(0)
|
||||||
|
} else if (popupMode == 4) {
|
||||||
|
setPopupMode(3)
|
||||||
|
} else {
|
||||||
setFadeOut(true)
|
setFadeOut(true)
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathname = usePathname()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unlistenProgress: (() => void) | null = null
|
let unlistenProgress: (() => void) | null = null
|
||||||
let unlistenUninstalled: (() => void) | null = null
|
|
||||||
|
|
||||||
listen<string>('download-progress', event => {
|
listen<string>('download-progress', event => {
|
||||||
const [versionName, progStr, totalSizeStr, speedStr, etaSecsStr] =
|
const [versionName, progStr, totalSizeStr, speedStr, etaSecsStr] =
|
||||||
@@ -143,30 +220,8 @@ export default function RootLayout ({
|
|||||||
})
|
})
|
||||||
}).then(f => (unlistenProgress = f))
|
}).then(f => (unlistenProgress = f))
|
||||||
|
|
||||||
listen<string>('version-uninstalled', event => {
|
|
||||||
const versionName = event.payload
|
|
||||||
setDownloadedVersionsConfig(prev => {
|
|
||||||
if (!prev) return prev
|
|
||||||
const updatedList = prev.list.filter(v => v !== versionName)
|
|
||||||
const updatedTimestamps = Object.fromEntries(
|
|
||||||
Object.entries(prev.timestamps).filter(([k]) => k !== versionName)
|
|
||||||
)
|
|
||||||
const updatedConfig = {
|
|
||||||
...prev,
|
|
||||||
list: updatedList,
|
|
||||||
timestamps: updatedTimestamps
|
|
||||||
}
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
setManagingVersion(null)
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
return updatedConfig
|
|
||||||
})
|
|
||||||
}).then(f => (unlistenUninstalled = f))
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlistenProgress?.()
|
unlistenProgress?.()
|
||||||
unlistenUninstalled?.()
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -219,50 +274,6 @@ export default function RootLayout ({
|
|||||||
return () => document.removeEventListener('contextmenu', handler)
|
return () => document.removeEventListener('contextmenu', handler)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function getSpecialVersionsList (game?: number): GameVersion[] {
|
|
||||||
if (!normalConfig || !serverVersionList) return []
|
|
||||||
|
|
||||||
return serverVersionList.versions
|
|
||||||
.filter(v => !downloadedVersionsConfig?.list.includes(v.id))
|
|
||||||
.filter(v => {
|
|
||||||
if (game && v.game != game) return false
|
|
||||||
if (downloadProgress.length != 0) {
|
|
||||||
return !downloadProgress.some(d => d.version === v.id)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (b.game !== a.game) return a.game - b.game
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVersionInfo (id: string | undefined): GameVersion | undefined {
|
|
||||||
if (!id) return undefined
|
|
||||||
return serverVersionList?.versions.find(v => v.id === id)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVersionGame (game: number | undefined): Game | undefined {
|
|
||||||
if (!game) return undefined
|
|
||||||
return serverVersionList?.games.find(g => g.id === game)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getListOfGames (): Game[] {
|
|
||||||
if (!downloadedVersionsConfig?.list) return []
|
|
||||||
|
|
||||||
const gamesMap = new Map<number, Game>()
|
|
||||||
|
|
||||||
downloadedVersionsConfig.list.forEach(i => {
|
|
||||||
const version = getVersionInfo(i)
|
|
||||||
if (!version) return
|
|
||||||
const game = getVersionGame(version.game)
|
|
||||||
if (!game) return
|
|
||||||
gamesMap.set(game.id, game)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(gamesMap.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadVersions (): Promise<void> {
|
async function downloadVersions (): Promise<void> {
|
||||||
const list = selectedVersionList
|
const list = selectedVersionList
|
||||||
setSelectedVersionList([])
|
setSelectedVersionList([])
|
||||||
@@ -282,7 +293,7 @@ export default function RootLayout ({
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const gameInfo = getVersionGame(info.game)
|
const gameInfo = getGameInfo(info.game)
|
||||||
if (!gameInfo) {
|
if (!gameInfo) {
|
||||||
setDownloadProgress(prev =>
|
setDownloadProgress(prev =>
|
||||||
prev.filter(d => d.version !== download.version)
|
prev.filter(d => d.version !== download.version)
|
||||||
@@ -336,37 +347,6 @@ export default function RootLayout ({
|
|||||||
await notifyUser('Downloads Finished', 'All downloads have finished.')
|
await notifyUser('Downloads Finished', 'All downloads have finished.')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVersionsAmountData (gameId: number): {
|
|
||||||
installed: number
|
|
||||||
total: number
|
|
||||||
} | null {
|
|
||||||
if (!downloadedVersionsConfig || !serverVersionList) return null
|
|
||||||
|
|
||||||
const installed = downloadedVersionsConfig.list.filter(
|
|
||||||
v => getVersionGame(getVersionInfo(v)?.game)?.id === gameId
|
|
||||||
).length
|
|
||||||
|
|
||||||
const total = serverVersionList.versions.filter(
|
|
||||||
v => getVersionGame(v?.game)?.id === gameId
|
|
||||||
).length
|
|
||||||
|
|
||||||
return { installed, total }
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEtaSmart (seconds: number) {
|
|
||||||
if (seconds < 60) return `${Math.floor(seconds)}s`
|
|
||||||
if (seconds < 3600)
|
|
||||||
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
|
||||||
if (seconds < 86400) {
|
|
||||||
const h = Math.floor(seconds / 3600)
|
|
||||||
const m = Math.floor((seconds % 3600) / 60)
|
|
||||||
return `${h}h ${m}m`
|
|
||||||
}
|
|
||||||
const d = Math.floor(seconds / 86400)
|
|
||||||
const h = Math.floor((seconds % 86400) / 3600)
|
|
||||||
return `${d}d ${h}h`
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<html lang='en' className={roboto.className}>
|
<html lang='en' className={roboto.className}>
|
||||||
@@ -427,7 +407,7 @@ export default function RootLayout ({
|
|||||||
managingVersion,
|
managingVersion,
|
||||||
setManagingVersion,
|
setManagingVersion,
|
||||||
getVersionInfo,
|
getVersionInfo,
|
||||||
getVersionGame,
|
getGameInfo,
|
||||||
getListOfGames,
|
getListOfGames,
|
||||||
setSelectedGame,
|
setSelectedGame,
|
||||||
getVersionsAmountData,
|
getVersionsAmountData,
|
||||||
@@ -438,20 +418,7 @@ export default function RootLayout ({
|
|||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
if (showPopup && e.key === 'Escape') {
|
if (showPopup && e.key === 'Escape') closePopup()
|
||||||
if (popupMode == 0 && selectedGame && pathname === '/') {
|
|
||||||
setSelectedGame(null)
|
|
||||||
setSelectedVersionList([])
|
|
||||||
} else if (viewingInfoFromDownloads) {
|
|
||||||
setViewingInfoFromDownloads(false)
|
|
||||||
setPopupMode(0)
|
|
||||||
} else if (popupMode == 4) {
|
|
||||||
setPopupMode(3)
|
|
||||||
} else {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
@@ -467,29 +434,23 @@ export default function RootLayout ({
|
|||||||
{showPopup && (
|
{showPopup && (
|
||||||
<div
|
<div
|
||||||
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
||||||
onClick={handleOverlayClick}
|
onClick={(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
if (viewingInfoFromDownloads) {
|
||||||
|
setPopupMode(0)
|
||||||
|
setViewingInfoFromDownloads(false)
|
||||||
|
setManagingVersion(null)
|
||||||
|
setSelectedGame(null)
|
||||||
|
}
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className='popup-box'>
|
<div className='popup-box'>
|
||||||
<button
|
<button
|
||||||
className='close-button'
|
className='close-button btntheme1'
|
||||||
onClick={() => {
|
onClick={() => closePopup()}
|
||||||
if (
|
|
||||||
popupMode == 0 &&
|
|
||||||
selectedGame &&
|
|
||||||
pathname === '/'
|
|
||||||
) {
|
|
||||||
setSelectedGame(null)
|
|
||||||
setSelectedVersionList([])
|
|
||||||
} else if (viewingInfoFromDownloads) {
|
|
||||||
setViewingInfoFromDownloads(false)
|
|
||||||
setPopupMode(0)
|
|
||||||
} else if (popupMode == 4) {
|
|
||||||
setPopupMode(3)
|
|
||||||
} else {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={
|
icon={
|
||||||
@@ -516,16 +477,16 @@ export default function RootLayout ({
|
|||||||
<p
|
<p
|
||||||
className={`text-2xl truncate ${
|
className={`text-2xl truncate ${
|
||||||
selectedVersionList.includes(v.id)
|
selectedVersionList.includes(v.id)
|
||||||
? 'max-w-80.5'
|
? 'max-w-84.5'
|
||||||
: 'max-w-87.5'
|
: 'max-w-91.5'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{getVersionGame(v.game)?.name} v
|
{getGameInfo(v.game)?.name} v
|
||||||
{v.versionName}
|
{v.versionName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className='button btntheme3 right-21.5 bottom-1.25'
|
className='button btntheme3 right-20.75 bottom-1.75'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedVersionList(prev =>
|
setSelectedVersionList(prev =>
|
||||||
prev.includes(v.id)
|
prev.includes(v.id)
|
||||||
@@ -551,7 +512,7 @@ export default function RootLayout ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className='button btntheme3 right-1 bottom-1.25'
|
className='button btntheme3 right-1.5 bottom-1.75'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setManagingVersion(v.id)
|
setManagingVersion(v.id)
|
||||||
setViewingInfoFromDownloads(true)
|
setViewingInfoFromDownloads(true)
|
||||||
@@ -653,97 +614,89 @@ export default function RootLayout ({
|
|||||||
<>
|
<>
|
||||||
<p className='text-xl text-center'>Downloads</p>
|
<p className='text-xl text-center'>Downloads</p>
|
||||||
<div className='popup-content'>
|
<div className='popup-content'>
|
||||||
{downloadProgress.length === 0 ? (
|
{downloadProgress.map((v, i) => (
|
||||||
<p className='text-center mt-6'>
|
<div
|
||||||
No more downloads!
|
key={i}
|
||||||
</p>
|
className='popup-entry flex flex-col justify-between'
|
||||||
) : (
|
>
|
||||||
downloadProgress.map((v, i) => (
|
<p className='text-2xl text-center'>
|
||||||
<div
|
{
|
||||||
key={i}
|
getGameInfo(getVersionInfo(v.version)?.game)
|
||||||
className='popup-entry flex flex-col justify-between'
|
?.name
|
||||||
>
|
}{' '}
|
||||||
<p className='text-2xl text-center'>
|
v{getVersionInfo(v.version)?.versionName}
|
||||||
{
|
</p>
|
||||||
getVersionGame(
|
<div className='mt-6.25 flex items-center justify-between'>
|
||||||
getVersionInfo(v.version)?.game
|
{v.failed ? (
|
||||||
)?.name
|
<>
|
||||||
}{' '}
|
<div className='flex items-center'>
|
||||||
v{getVersionInfo(v.version)?.versionName}
|
<span className='text-red-500 inline-block w-full text-center'>
|
||||||
</p>
|
Download failed
|
||||||
<div className='mt-6.25 flex items-center justify-between'>
|
|
||||||
{v.failed ? (
|
|
||||||
<>
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<span className='text-red-500 inline-block w-full text-center'>
|
|
||||||
Download failed
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className='button btntheme3 ml-30 mb-2'
|
|
||||||
onClick={() => {
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.filter(
|
|
||||||
d => d.version !== v.version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
title='Click to remove this version from this menu.'
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : v.queued ? (
|
|
||||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
|
||||||
Queued…
|
|
||||||
</span>
|
|
||||||
) : v.queued ? (
|
|
||||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
|
||||||
Queued…
|
|
||||||
</span>
|
|
||||||
) : v.hash_checking ? (
|
|
||||||
<span className='text-blue-500 inline-block w-full text-center'>
|
|
||||||
Checking hash...
|
|
||||||
</span>
|
|
||||||
) : v.finishing ? (
|
|
||||||
<span className='text-green-500 inline-block w-full text-center'>
|
|
||||||
Finishing...
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<div className='flex flex-col gap-1 w-full'>
|
|
||||||
<span className='text-center'>
|
|
||||||
Downloaded{' '}
|
|
||||||
{prettyBytes(v.progressBytes, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
})}{' '}
|
|
||||||
of{' '}
|
|
||||||
{prettyBytes(
|
|
||||||
getVersionInfo(v.version)?.size ??
|
|
||||||
0,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
}
|
|
||||||
)}{' '}
|
|
||||||
(ETA: {formatEtaSmart(v.etaSecs)}{' '}
|
|
||||||
• Speed:{' '}
|
|
||||||
{prettyBytes(v.speed, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
})}
|
|
||||||
/s)
|
|
||||||
</span>
|
</span>
|
||||||
<ProgressBar
|
<button
|
||||||
progress={v.progress}
|
className='button btntheme3 ml-30 mb-2'
|
||||||
className='w-full'
|
onClick={() => {
|
||||||
/>
|
setDownloadProgress(prev =>
|
||||||
|
prev.filter(
|
||||||
|
d => d.version !== v.version
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
title='Click to remove this version from this menu.'
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</>
|
||||||
</div>
|
) : v.queued ? (
|
||||||
|
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||||
|
Queued…
|
||||||
|
</span>
|
||||||
|
) : v.queued ? (
|
||||||
|
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||||
|
Queued…
|
||||||
|
</span>
|
||||||
|
) : v.hash_checking ? (
|
||||||
|
<span className='text-blue-500 inline-block w-full text-center'>
|
||||||
|
Checking hash...
|
||||||
|
</span>
|
||||||
|
) : v.finishing ? (
|
||||||
|
<span className='text-green-500 inline-block w-full text-center'>
|
||||||
|
Finishing...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<div className='flex flex-col gap-1 w-full'>
|
||||||
|
<span className='text-center'>
|
||||||
|
Downloaded{' '}
|
||||||
|
{prettyBytes(v.progressBytes, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
})}{' '}
|
||||||
|
of{' '}
|
||||||
|
{prettyBytes(
|
||||||
|
getVersionInfo(v.version)?.size ?? 0,
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
}
|
||||||
|
)}{' '}
|
||||||
|
(ETA: {formatEtaSmart(v.etaSecs)} •
|
||||||
|
Speed:{' '}
|
||||||
|
{prettyBytes(v.speed, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
})}
|
||||||
|
/s)
|
||||||
|
</span>
|
||||||
|
<ProgressBar
|
||||||
|
progress={v.progress}
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
</div>
|
||||||
)}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : popupMode === 2 ? (
|
) : popupMode === 2 ? (
|
||||||
@@ -752,7 +705,7 @@ export default function RootLayout ({
|
|||||||
<p className='text-xl text-center'>
|
<p className='text-xl text-center'>
|
||||||
Manage{' '}
|
Manage{' '}
|
||||||
{
|
{
|
||||||
getVersionGame(
|
getGameInfo(
|
||||||
getVersionInfo(managingVersion)?.game
|
getVersionInfo(managingVersion)?.game
|
||||||
)?.name
|
)?.name
|
||||||
}{' '}
|
}{' '}
|
||||||
@@ -762,11 +715,42 @@ export default function RootLayout ({
|
|||||||
<button
|
<button
|
||||||
className='button btntheme2'
|
className='button btntheme2'
|
||||||
disabled={downloadProgress.length != 0}
|
disabled={downloadProgress.length != 0}
|
||||||
onClick={() =>
|
onClick={async () => {
|
||||||
invoke('uninstall_version', {
|
closePopup()
|
||||||
name: managingVersion
|
|
||||||
|
setDownloadedVersionsConfig(prev => {
|
||||||
|
if (!prev) return prev
|
||||||
|
const updatedList = prev.list.filter(
|
||||||
|
v => v !== managingVersion
|
||||||
|
)
|
||||||
|
const updatedTimestamps =
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(prev.timestamps).filter(
|
||||||
|
([k]) => k !== managingVersion
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const updatedConfig = {
|
||||||
|
...prev,
|
||||||
|
list: updatedList,
|
||||||
|
timestamps: updatedTimestamps
|
||||||
|
}
|
||||||
|
writeVersionsConfig(updatedConfig)
|
||||||
|
setManagingVersion(null)
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
return updatedConfig
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
if (
|
||||||
|
await exists('game/' + managingVersion, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await remove('game/' + managingVersion, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData,
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
}}
|
||||||
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
|
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
|
||||||
>
|
>
|
||||||
Uninstall
|
Uninstall
|
||||||
@@ -825,7 +809,7 @@ export default function RootLayout ({
|
|||||||
selectedVersionList.length
|
selectedVersionList.length
|
||||||
} version${
|
} version${
|
||||||
selectedVersionList.length == 1 ? '' : 's'
|
selectedVersionList.length == 1 ? '' : 's'
|
||||||
} of ${getVersionGame(selectedGame)?.name}`
|
} of ${getGameInfo(selectedGame)?.name}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Download {selectedVersionList.length} version
|
Download {selectedVersionList.length} version
|
||||||
|
|||||||
+11
-26
@@ -28,7 +28,6 @@ export default function Installs () {
|
|||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [hoveredIds, setHoveredIds] = useState<number[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPopup) return
|
if (!showPopup) return
|
||||||
@@ -40,7 +39,7 @@ export default function Installs () {
|
|||||||
<div className='flex justify-between items-center mb-4'>
|
<div className='flex justify-between items-center mb-4'>
|
||||||
<p className='text-3xl'>Games</p>
|
<p className='text-3xl'>Games</p>
|
||||||
<button
|
<button
|
||||||
className='button btntheme1 text-3xl'
|
className='button btntheme1'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedGame(null)
|
setSelectedGame(null)
|
||||||
setPopupMode(0)
|
setPopupMode(0)
|
||||||
@@ -82,18 +81,13 @@ export default function Installs () {
|
|||||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||||
router.push('/game?id=' + i.id)
|
router.push('/game?id=' + i.id)
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredIds(prev => [...prev, i.id])}
|
|
||||||
onMouseLeave={() =>
|
|
||||||
setHoveredIds(prev => prev.filter(e => e !== i.id))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div className='flex flex-col'>
|
<div className='h-18 w-screen relative'>
|
||||||
<p className='text-2xl'>{i.name}</p>
|
<p className='text-2xl'>{i.name}</p>
|
||||||
<div className='flex gap-2 mt-2'>
|
|
||||||
|
<div className='flex gap-2 absolute left-0 bottom-0'>
|
||||||
<div
|
<div
|
||||||
className={`entry-info-item ${
|
className='entry-info-item'
|
||||||
hoveredIds.includes(i.id) ? 'btntheme3' : 'btntheme2'
|
|
||||||
}`}
|
|
||||||
title='The amount of versions installed of this game in installed/installable format.'
|
title='The amount of versions installed of this game in installed/installable format.'
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
@@ -106,9 +100,7 @@ export default function Installs () {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`entry-info-item ${
|
className='entry-info-item'
|
||||||
hoveredIds.includes(i.id) ? 'btntheme3' : 'btntheme2'
|
|
||||||
}`}
|
|
||||||
hidden={!i.official}
|
hidden={!i.official}
|
||||||
title='This game is official.'
|
title='This game is official.'
|
||||||
>
|
>
|
||||||
@@ -116,9 +108,7 @@ export default function Installs () {
|
|||||||
<p>Official</p>
|
<p>Official</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`entry-info-item ${
|
className='entry-info-item'
|
||||||
hoveredIds.includes(i.id) ? 'btntheme3' : 'btntheme2'
|
|
||||||
}`}
|
|
||||||
hidden={i.official}
|
hidden={i.official}
|
||||||
title={
|
title={
|
||||||
i.verified
|
i.verified
|
||||||
@@ -133,17 +123,12 @@ export default function Installs () {
|
|||||||
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='flex flex-row items-center gap-2 mt-auto'
|
|
||||||
hidden={!normalConfig?.settings.useLegacyInteractButtons}
|
|
||||||
title='Click to view game installs'
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
className={`button ${
|
className='button absolute right-0 bottom-0'
|
||||||
hoveredIds.includes(i.id) ? 'btntheme3' : 'btntheme2'
|
|
||||||
}`}
|
|
||||||
href={'/game?id=' + i.id}
|
href={'/game?id=' + i.id}
|
||||||
|
hidden={!normalConfig?.settings.useLegacyInteractButtons}
|
||||||
|
title='Click to view game installs'
|
||||||
>
|
>
|
||||||
Installs
|
Installs
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default function Settings () {
|
|||||||
title="This setting will make it so when you are on a page like this, the games won't disappear."
|
title="This setting will make it so when you are on a page like this, the games won't disappear."
|
||||||
/>
|
/>
|
||||||
<Setting
|
<Setting
|
||||||
label='Show Installs/Launch Buttons'
|
label='Show Installs/Launch/Manage Buttons'
|
||||||
value={useLegacyInteractButtons}
|
value={useLegacyInteractButtons}
|
||||||
onChange={async () => {
|
onChange={async () => {
|
||||||
while (normalConfig != null) {
|
while (normalConfig != null) {
|
||||||
@@ -112,7 +112,7 @@ export default function Settings () {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title='Enable the legacy method of using the installs/launch buttons. In the future this setting may be removed so try and get used to the new method.'
|
title='Enable the legacy method of using the installs/launch/manage buttons. In the future this setting may be removed so try and get used to the new method.'
|
||||||
/>
|
/>
|
||||||
<div title='The theme you want the launcher to use.'>
|
<div title='The theme you want the launcher to use.'>
|
||||||
<label className='text-lg'>Theme:</label>
|
<label className='text-lg'>Theme:</label>
|
||||||
|
|||||||
Reference in new issue
Block a user