diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1ce98cc..97084be 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -311,6 +311,51 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backoff" version = "0.4.0" @@ -387,9 +432,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -653,6 +698,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "combine" version = "4.6.7" @@ -969,6 +1024,68 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "devtools" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed369decdc2145308c198c8b95c9ed800d528639f2be86279dcc01133158284c" +dependencies = [ + "async-stream", + "bytes", + "colored", + "devtools-core", + "futures", + "serde_json", + "tauri", + "tokio", + "tonic", + "tonic-health", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "devtools-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78cdd51f6f62ad4eb9b6581d7e238e1779db3144ddbd711388f552e6ed3194b" +dependencies = [ + "async-stream", + "bytes", + "devtools-wire-format", + "futures", + "http 0.2.12", + "hyper 0.14.28", + "log", + "prost-types", + "ringbuf", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-health", + "tonic-web", + "tower", + "tower-http", + "tower-layer", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "devtools-wire-format" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1c0de542960449c9566001c1879d10ede95f3f2e0013fdae0cc3b153bfabb0d" +dependencies = [ + "bitflags 2.6.0", + "prost", + "prost-types", + "tonic", + "tracing-core", +] + [[package]] name = "digest" version = "0.10.7" @@ -1216,6 +1333,8 @@ name = "fit-launcher" version = "0.0.1" dependencies = [ "anyhow", + "chrono", + "devtools", "futures", "futures-util", "kuchiki", @@ -1985,6 +2104,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.9.4" @@ -2059,6 +2184,18 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.28", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -2244,6 +2381,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2410,7 +2556,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -2432,7 +2578,7 @@ dependencies = [ "futures", "hex 0.4.3", "http 1.1.0", - "itertools", + "itertools 0.13.0", "librqbit-bencode", "librqbit-buffers", "librqbit-clone-to-owned", @@ -2506,7 +2652,7 @@ dependencies = [ "data-encoding", "directories", "hex 0.4.3", - "itertools", + "itertools 0.13.0", "librqbit-bencode", "librqbit-buffers", "librqbit-clone-to-owned", @@ -2556,7 +2702,7 @@ dependencies = [ "bitvec", "byteorder", "bytes", - "itertools", + "itertools 0.13.0", "librqbit-bencode", "librqbit-buffers", "librqbit-clone-to-owned", @@ -2761,6 +2907,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.2" @@ -2920,7 +3072,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -3099,7 +3251,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "block2", "libc", "objc2", @@ -3115,7 +3267,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", @@ -3145,7 +3297,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "block2", "libc", "objc2", @@ -3157,7 +3309,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", @@ -3169,7 +3321,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", @@ -3225,7 +3377,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "foreign-types 0.3.2", "libc", @@ -3617,6 +3769,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3693,6 +3851,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quick-xml" version = "0.31.0" @@ -4066,6 +4256,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuf" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + [[package]] name = "rlimit" version = "0.10.1" @@ -4102,7 +4302,7 @@ version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -4277,7 +4477,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cssparser 0.31.2", "derive_more", "fxhash", @@ -4688,7 +4888,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -4872,6 +5072,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "tracing", "url", "uuid", "webkit2gtk", @@ -4973,6 +5174,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", + "tracing", "uuid", "webkit2gtk", "webview2-com", @@ -5165,6 +5367,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.4.0" @@ -5314,6 +5526,66 @@ dependencies = [ "winnow 0.6.6", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-health" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f80db390246dfb46553481f6024f0082ba00178ea495dbb99e70ba9a4fafb5e1" +dependencies = [ + "async-stream", + "prost", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tonic-web" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddb2a37b247e6adcb9f239f4e5cefdcc5ed526141a416b943929f13aea2cce" +dependencies = [ + "base64 0.21.7", + "bytes", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "pin-project", + "tokio-stream", + "tonic", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -5322,9 +5594,32 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand 0.8.5", + "slab", "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "http-range-header", + "pin-project-lite", "tower-layer", "tower-service", ] @@ -5347,6 +5642,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5749,7 +6045,7 @@ version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "rustix", "wayland-backend", "wayland-scanner", @@ -5761,7 +6057,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5773,7 +6069,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -6501,6 +6797,7 @@ dependencies = [ "soup2", "tao", "thiserror", + "tracing", "url", "webkit2gtk", "webkit2gtk-sys", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fcb51f3..89f4af4 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -31,6 +31,8 @@ tracing = "0.1.40" tracing-appender = "0.2.3" select = "0.6" uiautomation = "0.12.2" +devtools = "0.3.3" +chrono = "0.4.38" [dependencies.windows] version = "0.58.0" features = [ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 43a6b12..08aac8b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,10 +1,9 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - // TODO: Add Logging to a File. -// TODO: Add updater. +// TODO: Add updater. mod scrapingfunc; pub use crate::scrapingfunc::basic_scraping; @@ -21,47 +20,50 @@ mod mighty; use std::fs; use serde_json::Value; +use tauri::AppHandle; use std::error::Error; - use core::str; use tauri::api::path::app_log_dir; -use serde::{Deserialize, Serialize}; +use serde::{ Deserialize, Serialize }; use reqwest::Client; use std::fs::File; use std::io::Read; use std::fmt; -use tauri::{Manager, Window}; -use tauri::{api::path::{BaseDirectory, resolve_path}, Env}; +use tauri::{ Manager, Window, async_runtime::spawn }; +use tauri::{ api::path::{ BaseDirectory, resolve_path }, Env }; -use std::time::{Duration, Instant}; +use std::time::{ Duration, Instant }; use tokio::time::timeout; use std::path::PathBuf; use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber; // use serde_json::json; use std::path::Path; // crates for requests use reqwest; use kuchiki::traits::*; -use anyhow::{Result, Context}; +use anyhow::{ Result, Context }; // stop threads -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::sync::{ Arc, atomic::{ AtomicBool, Ordering } }; // caching use std::num::NonZeroUsize; use lru::LruCache; use tokio::sync::Mutex; use tauri::State; - +use chrono::{ DateTime, Utc }; // Define a shared boolean flag static STOP_FLAG: AtomicBool = AtomicBool::new(false); static PAUSE_FLAG: AtomicBool = AtomicBool::new(false); - - +#[derive(Clone, serde::Serialize)] +struct Payload { + message: String, +} #[derive(Debug, Serialize, Deserialize)] struct Game { @@ -69,7 +71,7 @@ struct Game { img: String, desc: String, magnetlink: String, - href: String + href: String, } #[derive(Debug, Serialize, Deserialize)] @@ -77,17 +79,11 @@ struct SingleGame { my_all_images: Vec, } - - #[derive(Debug, Serialize, Deserialize)] struct GameImages { my_all_images: Vec, } - - - - fn extract_hrefs_from_body(body: &str) -> Result> { let document = kuchiki::parse_html().one(body); let mut hrefs = Vec::new(); @@ -98,8 +94,7 @@ fn extract_hrefs_from_body(body: &str) -> Result> { for anchor_elem in document .select(&href_selector_str) - .map_err(|_| anyhow::anyhow!("Failed to select anchor element"))? - { + .map_err(|_| anyhow::anyhow!("Failed to select anchor element"))? { if let Some(href_link) = anchor_elem.attributes.borrow().get("href") { hrefs.push(href_link.to_string()); } @@ -123,11 +118,7 @@ async fn fetch_and_process_href(client: &Client, href: &str) -> Result Result Result> { } let client = Client::new(); - let res = client - .get(url) - .send() - .await - .context("Failed to send HTTP request")?; + let res = client.get(url).send().await.context("Failed to send HTTP request")?; if !res.status().is_success() { return Err(anyhow::anyhow!("Failed to connect to the website or the website is down.")); @@ -201,7 +186,7 @@ async fn scrape_image_srcs(url: &str) -> Result> { if !images.is_empty() { image_srcs.extend(images); } - }, + } Ok(Err(e)) => println!("Error fetching images from href: {}", e), Err(_) => println!("Timeout occurred while fetching images from href"), } @@ -218,18 +203,20 @@ async fn stop_get_games_images() { STOP_FLAG.store(true, Ordering::Relaxed); } - #[derive(Serialize, Deserialize, Clone)] struct CachedGameImages { game_link: String, images: Vec, } - // TODO: Add `notify` crate to watch a file for changes without resorting to a performance-draining loop. #[tauri::command] -async fn get_games_images(app_handle: tauri::AppHandle, game_link: String, image_cache: State<'_, ImageCache>) -> Result { +async fn get_games_images( + app_handle: tauri::AppHandle, + game_link: String, + image_cache: State<'_, ImageCache> +) -> Result { use std::collections::HashMap; use std::path::Path; use tokio::fs; @@ -242,10 +229,14 @@ async fn get_games_images(app_handle: tauri::AppHandle, game_link: String, image // Load the persistent cache from the file if Path::new(&cache_file_path).exists() { - let data = fs::read_to_string(&cache_file_path).await.context("Failed to read cache file") + let data = fs + ::read_to_string(&cache_file_path).await + .context("Failed to read cache file") + .map_err(|e| CustomError { message: e.to_string() })?; + let loaded_cache: HashMap> = serde_json + ::from_str(&data) + .context("Failed to parse cache file") .map_err(|e| CustomError { message: e.to_string() })?; - let loaded_cache: HashMap> = serde_json::from_str(&data) - .context("Failed to parse cache file").map_err(|e| CustomError { message: e.to_string() })?; // Update in-memory LruCache with the loaded HashMap let mut cache = image_cache.lock().await; @@ -267,32 +258,36 @@ async fn get_games_images(app_handle: tauri::AppHandle, game_link: String, image return Err(CustomError { message: "Function stopped.".to_string() }); } - let image_srcs = scrape_image_srcs(&game_link).await - .map_err(|e| CustomError { message: e.to_string() })?; + let image_srcs = scrape_image_srcs(&game_link).await.map_err(|e| CustomError { + message: e.to_string(), + })?; // Update the in-memory cache and save it to the persistent cache file let mut cache = image_cache.lock().await; cache.put(game_link.clone(), image_srcs.clone()); // Convert LruCache to HashMap for saving to persistent storage - let cache_as_hashmap: HashMap> = cache.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); - let updated_cache_data = serde_json::to_string_pretty(&cache_as_hashmap) + let cache_as_hashmap: HashMap> = cache + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + let updated_cache_data = serde_json + ::to_string_pretty(&cache_as_hashmap) .context("Failed to serialize cache data to JSON") .map_err(|e| CustomError { message: e.to_string() })?; - fs::write(&cache_file_path, updated_cache_data).await + fs + ::write(&cache_file_path, updated_cache_data).await .context("Failed to write cache data to file") .map_err(|e| CustomError { message: e.to_string() })?; - - println!("Done Getting Images"); + println!("Done Getting Images"); Ok(GameImages { my_all_images: image_srcs }) // Return the newly scraped images } - //Always serialize returns... #[derive(Debug, Serialize, Deserialize)] struct FileContent { - content: String + content: String, } #[derive(Debug, Serialize)] @@ -316,47 +311,44 @@ impl From> for CustomError { } } - - #[tauri::command(async)] async fn read_file(file_path: String) -> Result { - let mut file = File::open(&file_path) - .map_err(|e| CustomError { message: e.to_string() })?; + let mut file = File::open(&file_path).map_err(|e| CustomError { message: e.to_string() })?; let mut data_content = String::new(); - file.read_to_string(&mut data_content) - .map_err(|e| CustomError { message: e.to_string() })?; + file.read_to_string(&mut data_content).map_err(|e| CustomError { message: e.to_string() })?; Ok(FileContent { content: data_content }) } - #[tauri::command] async fn clear_file(file_path: String) -> Result<(), CustomError> { let path = Path::new(&file_path); // Attempt to create the file, truncating if it already exists - File::create(&path).map_err(|err| CustomError{ message: err.to_string()})?; - + File::create(&path).map_err(|err| CustomError { message: err.to_string() })?; + Ok(()) } - #[tauri::command] async fn close_splashscreen(window: Window) { - // Close splashscreen - window.get_window("splashscreen").expect("no window labeled 'splashscreen' found").close().unwrap(); - // Show main window - window.get_window("main").expect("no window labeled 'main' found").show().unwrap(); + // Close splashscreen + window + .get_window("splashscreen") + .expect("no window labeled 'splashscreen' found") + .close() + .unwrap(); + // Show main window + window.get_window("main").expect("no window labeled 'main' found").show().unwrap(); } - #[tauri::command] fn check_folder_path(path: String) -> Result { let path_obj = PathBuf::from(&path); - + // Debugging information println!("Checking path: {:?}", path_obj); - + if !path_obj.exists() { println!("Path does not exist."); return Ok(false); @@ -375,7 +367,9 @@ fn delete_invalid_json_files(app_handle: &tauri::AppHandle) -> Result<(), Box Result<(), Box Result<()> { // Read the file content as a string let file_content = fs::read_to_string(path)?; - + // Parse the JSON content into a Value object let json: Value = serde_json::from_str(&file_content)?; @@ -420,7 +414,8 @@ fn setup_logging(logs_dir: PathBuf) -> WorkerGuard { let file_appender = tracing_appender::rolling::never(logs_dir, "app.log"); let (file_writer, guard) = tracing_appender::non_blocking(file_appender); - tracing_subscriber::fmt() + tracing_subscriber + ::fmt() .with_writer(file_writer) .with_ansi(false) .with_max_level(tracing::Level::INFO) @@ -429,25 +424,84 @@ fn setup_logging(logs_dir: PathBuf) -> WorkerGuard { guard } +// Function to perform a network request after ensuring frontend is ready, and emit network-failure if the request fails +async fn perform_network_request(app_handle: tauri::AppHandle) { + println!( + "perform_network_request: Waiting for frontend-ready before starting the network request." + ); + + // Get the main window to listen for 'frontend-ready' + if let Some(main_window) = app_handle.get_window("main") { + // Clone `app_handle` so it can be moved into both closures + let app_handle_clone = app_handle.clone(); + + // Listen for 'frontend-ready' before performing the network request + main_window.listen("frontend-ready", move |_| { + println!("Frontend is ready, starting the network request..."); + + // Clone `app_handle_clone` for use in the async block + let app_handle_inner_clone = app_handle_clone.clone(); + + // Perform the network request directly to the site - This is lazy but it works for now! + //TODO: improve this + spawn(async move { + let result = reqwest::get("https://fitgirl-repacks.site").await; + + // Check if the request was successful + match result { + Ok(resp) => { + let text = resp.text().await.unwrap(); + println!( + "perform_network_request: Network request to Fitgirl website was successful." + ); + } + Err(_) => { + println!("Network request failed, emitting network-failure event."); + + // Emit the network-failure event after the network request fails + let failure_message = Payload { + message: "There was a network issue, unable to retrieve latest game data. (E01)".to_string(), + }; + app_handle_inner_clone + .emit_all("network-failure", failure_message) + .unwrap(); + } + } + }); + }); + } +} + fn main() -> Result<(), Box> { - let image_cache = Arc::new(Mutex::new(LruCache::>::new(NonZeroUsize::new(30).unwrap()))); + println!("{}: Main.rs: Starting the application...", Utc::now().format("%a,%b,%e,%T,%Y")); + let image_cache = Arc::new( + Mutex::new(LruCache::>::new(NonZeroUsize::new(30).unwrap())) + ); let context = tauri::generate_context!(); - + let logs_dir = app_log_dir(context.config()).unwrap(); println!("path to logs : {:#?}", &logs_dir); - let _log_guard = setup_logging(logs_dir); + let _log_guard = setup_logging(logs_dir); - tauri::Builder::default() + tauri::Builder + ::default() .setup(|app| { let splashscreen_window = app.get_window("splashscreen").unwrap(); let main_window = app.get_window("main").unwrap(); - let current_app_handle = app.app_handle(); + let current_app_handle = app.app_handle().clone(); + + let app_handle = app.handle().clone(); // Delete JSON files missing the 'tag' field or corrupted and log the process if let Err(e) = delete_invalid_json_files(¤t_app_handle) { eprintln!("Error during deletion of invalid or corrupted JSON files: {}", e); } + // Perform the network request + spawn(async move { + perform_network_request(app_handle).await; + }); + // Clone the app handle for use in async tasks let first_app_handle = current_app_handle.clone(); let second_app_handle = current_app_handle.clone(); @@ -459,31 +513,88 @@ fn main() -> Result<(), Box> { tracing::info!("Starting async tasks"); let mandatory_tasks_online = tauri::async_runtime::spawn_blocking(move || { - if let Err(e) = basic_scraping::scraping_func(first_app_handle) { + // Clone before emitting to avoid moving the handle + let first_app_handle_clone = first_app_handle.clone(); + if let Err(e) = basic_scraping::scraping_func(first_app_handle_clone.clone()) { eprintln!("Error in scraping_func: {}", e); + tracing::info!("Error in scraping_func: {}", e); // Do not exit, continue running + } else { + tracing::info!( + "[scraping_func] has been completed. No errors are reported." + ); + //TODO: This will be used to emit a signal to the frontend that the scraping is complete + // when the main reload window code is removed + // first_app_handle_clone.emit_all("new-games-ready", {}).unwrap(); } - if let Err(e) = basic_scraping::popular_games_scraping_func(second_app_handle) { + // Clone before emitting to avoid moving the handle + let second_app_handle_clone = second_app_handle.clone(); + if + let Err(e) = basic_scraping::popular_games_scraping_func( + second_app_handle_clone.clone() + ) + { eprintln!("Error in popular_games_scraping_func: {}", e); + tracing::info!("Error in popular_games_scraping_func: {}", e); // Do not exit, continue running + } else { + tracing::info!( + "[popular_games_scraping_func] has been completed. No errors are reported." + ); + //TODO: This will be used to emit a signal to the frontend that the scraping is complete + // when the main reload window code is removed + // second_app_handle_clone.emit_all("popular-games-ready", {}).unwrap(); } - if let Err(e) = commands_scraping::get_sitemaps_website(fourth_app_handle) { - eprintln!("Error in get_sitemaps_website: {}", e); + // Clone before emitting to avoid moving the handle + let third_app_handle_clone = third_app_handle.clone(); + if + let Err(e) = basic_scraping::recently_updated_games_scraping_func( + third_app_handle_clone.clone() + ) + { + eprintln!("Error in recently_updated_games_scraping_func: {}", e); + tracing::info!("Error in recently_updated_games_scraping_func: {}", e); // Do not exit, continue running + } else { + tracing::info!( + "[recently_updated_games_scraping_func] has been completed. No errors are reported." + ); + //TODO: This will be used to emit a signal to the frontend that the scraping is complete + // when the main reload window code is removed + // third_app_handle_clone.emit_all("recent-updated-games-ready", {}).unwrap(); } - if let Err(e) = basic_scraping::recently_updated_games_scraping_func(third_app_handle) { - eprintln!("Error in recently_updated_games_scraping_func: {}", e); + // Clone before emitting to avoid moving the handle + let fourth_app_handle_clone = fourth_app_handle.clone(); + if + let Err(e) = commands_scraping::get_sitemaps_website( + fourth_app_handle_clone.clone() + ) + { + eprintln!("Error in get_sitemaps_website: {}", e); + tracing::info!("Error in get_sitemaps_website: {}", e); // Do not exit, continue running + } else { + tracing::info!( + "[get_sitemaps_website] has been completed. No errors are reported." + ); + //TODO: This will be used to emit a signal to the frontend that the scraping is complete + // when the main reload window code is removed + //fourth_app_handle_clone.emit_all("sitemaps-ready", {}).unwrap(); } }); // Await the completion of the tasks if let Err(e) = mandatory_tasks_online.await { eprintln!("An error occurred during scraping tasks: {:?}", e); - // Continue running even if errors occur + tracing::info!("An error occurred during scraping tasks: {:?}", e); + // Do not exit, continue running + } else { + tracing::info!( + "All scraping tasks have been completed. No errors are reported." + ); } // After all tasks are done, close the splash screen and show the main window @@ -491,41 +602,51 @@ fn main() -> Result<(), Box> { main_window.show().unwrap(); current_app_handle.emit_all("scraping-complete", {}).unwrap(); - current_app_handle.get_window("main").unwrap().eval("window.location.reload();").unwrap(); + + //TODO prevent flashing screen at start and prevented emits from being sent + //TODO: uncommented this for now + current_app_handle + .get_window("main") + .unwrap() + .eval("window.location.reload();") + .unwrap(); println!("Scraping signal has been sent."); }); Ok(()) }) - .invoke_handler(tauri::generate_handler![ - read_file, - close_splashscreen, - get_games_images, - clear_file, - stop_get_games_images, - check_folder_path, - torrent_commands::api_get_torrent_details, - torrent_commands::api_pause_torrent, - torrent_commands::api_resume_torrent, - torrent_commands::api_stop_torrent, - torrent_commands::api_download_with_args, - torrent_commands::api_automate_setup_install, - torrent_commands::api_get_torrent_stats, - torrent_commands::api_initialize_torrent_manager, - commands_scraping::get_singular_game_info, - windows_custom_commands::start_executable - ]) - .manage(image_cache) // Make the cache available to commands + .invoke_handler( + tauri::generate_handler![ + read_file, + close_splashscreen, + get_games_images, + clear_file, + stop_get_games_images, + check_folder_path, + torrent_commands::api_get_torrent_details, + torrent_commands::api_pause_torrent, + torrent_commands::api_resume_torrent, + torrent_commands::api_stop_torrent, + torrent_commands::api_download_with_args, + torrent_commands::api_automate_setup_install, + torrent_commands::api_get_torrent_stats, + torrent_commands::api_initialize_torrent_manager, + commands_scraping::get_singular_game_info, + windows_custom_commands::start_executable + ] + ) + + .manage(image_cache) // Make the cache available to commands .manage(torrent_calls::TorrentState::default()) // Make the torrent state session available to commands - .build({ - tauri::generate_context!() - }) + .build({ tauri::generate_context!() }) .expect("error while building tauri application") - .run(|_app_handle, event| match event { - tauri::RunEvent::ExitRequested { .. } => { - PAUSE_FLAG.store(true, Ordering::Relaxed); + .run(|_app_handle, event| { + match event { + tauri::RunEvent::ExitRequested { .. } => { + PAUSE_FLAG.store(true, Ordering::Relaxed); + } + _ => {} } - _ => {} }); // Keep the guard in scope to ensure proper log flushing diff --git a/src/App.css b/src/App.css index 951a68a..5b96c53 100644 --- a/src/App.css +++ b/src/App.css @@ -1,7 +1,8 @@ -body { +html, body { + height: 100%; /* Ensure the body and html stretch fully */ margin: 0; padding: 0; - font-family: 'IBMPlexSans', sans-serif; + font-family: 'IBMPlexSans', sans-serif; } @font-face { @@ -14,7 +15,6 @@ body { src: url("./assets/fonts/IBMPlexSansVar-Roman.ttf") format("truetype"); } - h2 { color: white; font-weight: 450; @@ -23,21 +23,18 @@ h2 { .app-container { display: grid; grid-template-columns: 260px 1fr; - grid-template-rows: 100%; - height: 100%; + height: 100vh; /* Ensure the app container fills the entire viewport */ overflow: hidden; } .sidebar { position: fixed; width: 260px; - /* background-color: #101115; */ + height: 100%; /* Sidebar takes full height */ + overflow-x: hidden; + z-index: 9999; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); color: white; - - height: 100%; - overflow-x: hidden; - z-index: 9999999; } .main-content { @@ -57,15 +54,74 @@ h2 { } -html::-webkit-scrollbar{ +.notification-wrapper { + position: fixed; /* Makes sure it stays in a specific spot */ + top: 16px; /* Adjust the position to your preference */ + right: 16px; /* Aligns it to the right corner */ + max-width: 450px; /* Keeps it from stretching too wide */ + z-index: 1000; /* Ensures it's on top of other components */ + display: flex; + flex-direction: column; + gap: 8px; /* Adds space between multiple notifications */ +} + +.refreshcontent { + position: fixed; /* Makes sure it stays in a specific spot */ + top: 16px; /* Adjust the position to your preference */ + right: 16px; /* Aligns it to the right corner */ + max-width: 450px; /* Keeps it from stretching too wide */ + z-index: 1000; /* Ensures it's on top of other components */ + display: flex; + flex-direction: column; + gap: 8px; /* Adds space between multiple notifications */ +} + +/* Scrollbar customization */ +html::-webkit-scrollbar { background-color: rgba(43, 42, 42, 0.2); border-radius: 18px; display: none; } - - html::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.3); border-radius: 18px; } + +/* Popup container */ +.popup-container { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; /* Ensure the popup takes full screen */ + background-color: rgba(0, 0, 0, 0.6); + z-index: 1000; + overflow: hidden; +} + +/* Scrollbar customization for changelog */ +.popup-container::-webkit-scrollbar { + width: 8px; + border-radius: 10px; +} + +.popup-container::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); + border-radius: 10px; +} + +/* Ensure scrollbar respects the rounded corners */ +.popup-container { + border-radius: 20px; + padding-right: 10px; /* Add space on the right to ensure scrollbar fits well */ + box-sizing: border-box; + overflow-y: auto; +} + +.hidden { + display: none; +} diff --git a/src/App.jsx b/src/App.jsx index 355e294..04fe197 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,158 +1,189 @@ -import { createEffect, onMount } from 'solid-js'; -import { invoke } from '@tauri-apps/api'; -import { appWindow } from '@tauri-apps/api/window'; -import { listen } from '@tauri-apps/api/event'; -import { Router } from '@solidjs/router'; -import { lazy } from 'solid-js'; // Import lazy here +import { onMount, createSignal } from 'solid-js' +import { appWindow } from '@tauri-apps/api/window' +import { listen } from '@tauri-apps/api/event' +import { Router } from '@solidjs/router' +import { lazy } from 'solid-js' +import { emit } from '@tauri-apps/api/event' -import Sidebar from './components/Sidebar-01/Sidebar'; -import Searchbar from './components/Searchbar-01/Searchbar'; +import Sidebar from './components/Sidebar-01/Sidebar' +import Searchbar from './components/Searchbar-01/Searchbar' +import Notification from './components/Notification-01/Notification' +import ChangelogPopup from './components/Changelog-01/ChangelogPopup' -import './App.css'; -import './templates/titlebar-01/titlebar.css'; +import './App.css' +import './templates/titlebar-01/titlebar.css' function App() { - onMount(() => { + const [isDialogOpen, setIsDialogOpen] = createSignal(false) + const [notificationMessage, setNotificationMessage] = createSignal('') - // //2024-09-05 - Placeholder code for a later update to handle network alert errors - // // Listen for the scraping_failed event globally - // listen('scraping_failed', (event) => { - // console.log("scrap_fail_alert"); - // //alert(event.payload); // Display the alert when scraping fails - // }); + onMount(() => { + console.log('App mounted. Setting up event listeners...') + // Emit an event to the backend indicating that the frontend is ready + emit('frontend-ready') - // Event listeners for window controls - document - .getElementById('titlebar-minimize') - .addEventListener('click', () => appWindow.minimize()); - document - .getElementById('titlebar-maximize') - .addEventListener('click', () => appWindow.toggleMaximize()); - document - .getElementById('titlebar-close') - .addEventListener('click', () => { - let cdgStats = localStorage.getItem('CDG_Stats'); - console.log('Current CDG_Stats from localStorage:', cdgStats); + // Listen for the network failure event + listen('network-failure', (event) => { + setNotificationMessage(`Network failure: ${event.payload.message}`) + setIsDialogOpen(true) + }) - try { - cdgStats = JSON.parse(cdgStats); - console.log('Parsed CDG_Stats:', cdgStats); + // Event listeners for window controls + document + .getElementById('titlebar-minimize') + .addEventListener('click', () => appWindow.minimize()) + document + .getElementById('titlebar-maximize') + .addEventListener('click', () => appWindow.toggleMaximize()) + document + .getElementById('titlebar-close') + .addEventListener('click', () => { + let cdgStats = localStorage.getItem('CDG_Stats') + console.log('Current CDG_Stats from localStorage:', cdgStats) - if (cdgStats) { - cdgStats.state = 'paused'; - console.log('Updated CDG_Stats:', cdgStats); + try { + cdgStats = JSON.parse(cdgStats) + console.log('Parsed CDG_Stats:', cdgStats) - localStorage.setItem('CDG_Stats', JSON.stringify(cdgStats)); + if (cdgStats) { + cdgStats.state = 'paused' + console.log('Updated CDG_Stats:', cdgStats) - const updatedCdgStats = localStorage.getItem('CDG_Stats'); - console.log( - 'Updated CDG_Stats saved in localStorage:', - updatedCdgStats - ); - } else { - console.error( - "CDG_Stats is not in the expected format or missing 'state' property:", - cdgStats - ); - } - } catch (error) { - console.error('Error parsing CDG_Stats:', error); - } - appWindow.close(); - }); - }); + localStorage.setItem( + 'CDG_Stats', + JSON.stringify(cdgStats) + ) - const sidebarRoutes = [ - { - path: "/", - component: lazy(() => import("./templates/Gamehub-01/Gamehub")), - }, - { - path: "/my-library", - component: lazy(() => import("./templates/mylibrary-01/Mylibrary")), - }, - { - path: "/settings", - component: lazy(() => import("./templates/Settings-01/Settings")), - }, - ]; + const updatedCdgStats = + localStorage.getItem('CDG_Stats') + console.log( + 'Updated CDG_Stats saved in localStorage:', + updatedCdgStats + ) + } else { + console.error( + "CDG_Stats is not in the expected format or missing 'state' property:", + cdgStats + ) + } + } catch (error) { + console.error('Error parsing CDG_Stats:', error) + } + appWindow.close() + }) + }) - return ( - <> -
-
-
-
- - - -
-
- - - - - - -
-
- - - -
-
-
+ function closeDialog() { + setIsDialogOpen(false) + } - {/* Contains the sidebar. DO NOT REMOVE. */} - ( - <> -
-
- -
-
-
-
- + const sidebarRoutes = [ + { + path: '/', + component: lazy(() => import('./templates/Gamehub-01/Gamehub')), + }, + { + path: '/my-library', + component: lazy(() => import('./templates/mylibrary-01/Mylibrary')), + }, + { + path: '/settings', + component: lazy(() => import('./templates/Settings-01/Settings')), + }, + ] + + return ( + <> +
+
+
+
+ + + +
+
+ + + + + + +
+
+ + + +
- {props.children} -
-
- - )} - > - {sidebarRoutes} - -
- - ); + + ( + <> +
+
+ +
+
+ +
+
+ +
+
+ {isDialogOpen() && ( + + )} +
+ {props.children} +
+
+
+ + )} + > + {sidebarRoutes} +
+
+ + ) } -export default App; +export default App diff --git a/src/components/Changelog-01/ChangelogPopup.css b/src/components/Changelog-01/ChangelogPopup.css new file mode 100644 index 0000000..cdb5357 --- /dev/null +++ b/src/components/Changelog-01/ChangelogPopup.css @@ -0,0 +1,165 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + + color: #e0e0e0; +} + +/* Popup container */ +.popup-container { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + z-index: 1000; + overflow: hidden; + font-size: 0.9rem; +} + +/* Hide the popup container when not visible */ +.hidden { + display: none; +} + +/* Popup content */ +.changelog-content { + background-color: #2e2e2e; + padding: 20px 30px; + max-width: 500px; + max-height: 80vh; + border-radius: 20px; + box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.5); + text-align: left; + animation: fadeIn 0.3s ease-in-out; + overflow-y: auto; + padding-right: 10px; + box-sizing: border-box; + clip-path: inset(0 round 20px); +} + +/* Center the content more precisely on larger screens */ +@media (min-width: 768px) { + .changelog-content { + max-width: 450px; + margin: auto; + } +} + +/* Scrollbar customization */ +.changelog-content::-webkit-scrollbar { + width: 8px; + border-radius: 10px; +} + +.changelog-content::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.3); + border-radius: 10px; +} + +/* Heading styles */ +.changelog-content h1 { + text-align: center; + font-size: 24px; + margin-bottom: 5px; + margin-top: 0px; + color: #f5f5f5; +} + +.subheading { + text-align: center; + color: #b0b0b0; + margin-top: 5px; + margin-bottom: 20px; +} + +/* Date and list styles */ +.date { + font-weight: bold; + margin-top: 20px; + color: #ffffff; +} + +/* Styles for lists with bullet points */ +.bullet-list { + list-style-type: disc; + padding-left: 1px; +} + +.bullet-list li { + margin-bottom: 10px; + color: #d0d0d0; /* Slightly lighter text color for better readability */ + font-size: 0.9rem; +} + +/* Remove bullet points from label items */ +.label { + list-style-type: none; + display: inline-block; + font-weight: bold; + border-radius: 4px; + padding: 2px 6px; + color: white; +} + +.new { + background-color: #3dbd40; +} + +.bugfix { + background-color: #e74c3c; +} + +.improvement { + background-color: #5a67d8; +} + +/* Button styles */ +.close-btn { + display: block; + margin: 20px auto; + padding: 10px 20px; + background-color: #4a4a4a; /* Slightly darker button background */ + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +.close-btn:hover { + background-color: #65aa6e; +} + +/* Checkbox and label styles */ +.dont-show-again { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 20px; +} + +.dont-show-again label { + margin-left: 5px; + font-size: 14px; + color: #888; +} + +/* Keyframe animation for fade in */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Ensure scrollbar respects the rounded corners */ +.changelog-content { + border-radius: 20px; + padding-right: 10px; + box-sizing: border-box; + overflow-y: auto; +} diff --git a/src/components/Changelog-01/ChangelogPopup.jsx b/src/components/Changelog-01/ChangelogPopup.jsx new file mode 100644 index 0000000..28e1e9d --- /dev/null +++ b/src/components/Changelog-01/ChangelogPopup.jsx @@ -0,0 +1,76 @@ +import { createSignal, onMount } from 'solid-js'; +import './ChangelogPopup.css'; + +const ChangelogPopup = () => { + const [isVisible, setIsVisible] = createSignal(true); + + onMount(() => { + setIsVisible(true); + }); + + const handleClose = () => { + console.log("Closing popup"); + setIsVisible(false); + console.log("Popup visible:", isVisible()); + + }; + + return ( + <> + {isVisible() && ( + + )} + + ); +}; + +export default ChangelogPopup; diff --git a/src/components/Notification-01/Notification.css b/src/components/Notification-01/Notification.css new file mode 100644 index 0000000..3729159 --- /dev/null +++ b/src/components/Notification-01/Notification.css @@ -0,0 +1,40 @@ +.notification { + padding: 16px; + margin: 16px; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + opacity: 1; + transition: opacity 0.3s ease-in-out; + } + + .notification.info { + background-color: #e0f7fa; + color: #00796b; + } + + .notification.error { + background-color: #ffebee; + color: #d32f2f; + } + + .notification.success { + background-color: #e8f5e9; + color: #388e3c; + } + + .notification-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: inherit; + } + + .notification-exit { + opacity: 0; + } + .hidden { + display: none; /* Ensures the div doesn't take any space */ + } \ No newline at end of file diff --git a/src/components/Notification-01/Notification.jsx b/src/components/Notification-01/Notification.jsx new file mode 100644 index 0000000..517d5f2 --- /dev/null +++ b/src/components/Notification-01/Notification.jsx @@ -0,0 +1,32 @@ +import { createSignal, Show } from "solid-js"; +import './Notification.css'; + +function Notification(props) { + const { message, type = "info", autoClose = true, autoCloseTime = 900000 } = props; + const [isVisible, setIsVisible] = createSignal(true); // Notification starts visible + + const closeNotification = () => { + setIsVisible(false); // Close notification manually + }; + + if (autoClose) { + setTimeout(() => { + setIsVisible(false); // Auto-close after `autoCloseTime` + }, autoCloseTime); + } + + return ( + +
+
+ {message} +
+ +
+
+ ); +} + +export default Notification; diff --git a/src/components/RefreshContent-01/RefreshContent.css b/src/components/RefreshContent-01/RefreshContent.css new file mode 100644 index 0000000..11bdb2a --- /dev/null +++ b/src/components/RefreshContent-01/RefreshContent.css @@ -0,0 +1,97 @@ +.refresh-content-container { + display: flex; + width: max-content; + flex-direction: column; + align-items: flex-start; +} + +.refresh-content-main { + display: flex; + padding-left: 25px; + padding-top: 25px; + z-index: 4; + align-items: center; +} + + +/* Modal styling */ +.cookies-card { + width: 350px; + height: fit-content; + background-color: rgb(255, 250, 250); + border-radius: 10px; + border: 1px solid rgb(206, 206, 206); + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 20px; + gap: 15px; + position: relative; + font-family: Arial, Helvetica, sans-serif; + box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.066); + z-index: 9999999; + } + + .cookie-heading { + color: rgb(34, 34, 34); + font-weight: 800; + display: flex; + align-items: center; + gap: 10px; + } + .cookie-para { + font-size: 11px; + font-weight: 400; + color: rgb(51, 51, 51); + } + .button-wrapper { + width: 100%; + height: auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + } + .cookie-button { + width: 50%; + padding: 8px 0; + border: none; + border-radius: 5px; + cursor: pointer; + } + .accept { + background-color: rgb(34, 34, 34); + color: white; + } + .reject { + background-color: #ececec; + color: rgb(34, 34, 34); + } + .accept:hover { + background-color: rgb(0, 0, 0); + } + .reject:hover { + background-color: #ddd; + } + .exit-button { + position: absolute; + top: 17px; + right: 17px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + background-color: transparent; + border: none; + border-radius: 5px; + cursor: pointer; + } + .exit-button:hover { + background-color: #ddd; + color: white; + } + .svgIconCross { + height: 10px; + } diff --git a/src/components/RefreshContent-01/RefreshContent.jsx b/src/components/RefreshContent-01/RefreshContent.jsx new file mode 100644 index 0000000..d80fdc7 --- /dev/null +++ b/src/components/RefreshContent-01/RefreshContent.jsx @@ -0,0 +1,67 @@ +import { createSignal } from 'solid-js'; +import './RefreshContent.css'; + +import {invoke} from '@tauri-apps/api/tauri'; +import { message } from '@tauri-apps/api/dialog'; + +function RefreshContent() { + const [isDialogOpen, setIsDialogOpen, isLoadingOpen, setIsLoadingOpen] = createSignal(false); + + function refreshContent() { + setIsDialogOpen(false); // Close the cookies card when the "Yes" button is clicked + + // delete all content and fetch new content + + invoke ('delete_app_data').then((message) => console.log(message)).catch((error) => console.error(error)); + + } + + function closeDialog() { + setIsDialogOpen(false); // Close the cookies card when the exit button is clicked + } + + function openDialog() { + setIsDialogOpen(true); + // Call refreshContent function when the "Yes" button is clicked + } + + return ( +
+
+
+ + + + +
+
+ + {/* Conditionally render the cookies card based on isDialogOpen */} + {isDialogOpen() && ( +
+ + +
+ + +
+
+ )} +
+ ); +} + +export default RefreshContent; diff --git a/src/components/Searchbar-01/Searchbar.css b/src/components/Searchbar-01/Searchbar.css index db3783c..c86af30 100644 --- a/src/components/Searchbar-01/Searchbar.css +++ b/src/components/Searchbar-01/Searchbar.css @@ -13,6 +13,7 @@ align-items: center; } + #searchbar-input { width: 25vh; height: 5vh; @@ -94,3 +95,4 @@ .search-results a:last-child { margin-bottom: 0; /* No margin bottom on last child */ } + diff --git a/src/components/Searchbar-01/Searchbar.jsx b/src/components/Searchbar-01/Searchbar.jsx index 68eec19..810dc8e 100644 --- a/src/components/Searchbar-01/Searchbar.jsx +++ b/src/components/Searchbar-01/Searchbar.jsx @@ -14,6 +14,7 @@ function Searchbar() { const [searchedGameTitle, setSearchedGameTitle] = createSignal(''); const [selectedGameLink, setSelectedGameLink] = createSignal(null); const [showDragonBallSVG, setShowDragonBallSVG] = createSignal(false); // Signal for SVG display + const [isDialogOpen, setIsDialogopen] = createSignal(false); function clearSearch() { setSearchResults([]); @@ -144,6 +145,8 @@ function Searchbar() { return singularGameFilePath; } + + return (
@@ -180,6 +183,7 @@ function Searchbar() { onInput={handleInputChange} value={searchTerm()} // Ensure the input value is controlled /> +
{searchResults().length === 0 ? ( @@ -200,6 +204,7 @@ function Searchbar() { )}
+
); } diff --git a/src/templates/Gamehub-01/Gamehub.css b/src/templates/Gamehub-01/Gamehub.css index 76de648..0bc8d04 100644 --- a/src/templates/Gamehub-01/Gamehub.css +++ b/src/templates/Gamehub-01/Gamehub.css @@ -15,6 +15,13 @@ z-index: 3; } +.img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; +} + .blur-overlay { position: fixed; top: 0; diff --git a/src/templates/Gamehub-01/Gamehub.jsx b/src/templates/Gamehub-01/Gamehub.jsx index 24c1bb4..59d4176 100644 --- a/src/templates/Gamehub-01/Gamehub.jsx +++ b/src/templates/Gamehub-01/Gamehub.jsx @@ -9,10 +9,10 @@ import { appConfigDir } from "@tauri-apps/api/path"; import { readTextFile, writeTextFile, exists } from "@tauri-apps/api/fs"; import { createDir } from "@tauri-apps/api/fs"; import { hide } from '@tauri-apps/api/app'; +import {listen, emit} from '@tauri-apps/api/event' function Gamehub() { onMount(() => { - // Load settings at startup loadSettings().then((settings) => { console.log("Loaded settings on startup:", settings); @@ -52,6 +52,7 @@ function Gamehub() { importPath: "", two_gb_limit: true, hide_nsfw_content: false, + background_image_path: "", }; // Function to load settings from the JSON file, or create it if not present diff --git a/src/templates/Settings-01/Settings.css b/src/templates/Settings-01/Settings.css index 71429bc..052518a 100644 --- a/src/templates/Settings-01/Settings.css +++ b/src/templates/Settings-01/Settings.css @@ -149,8 +149,8 @@ body { color: white; padding: 15px; position: fixed; - top: 30px; - right: 30px; + top: 16px; + right: 16px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); z-index: 1000; diff --git a/src/templates/Settings-01/Settings.jsx b/src/templates/Settings-01/Settings.jsx index fde1c7d..aac3f5a 100644 --- a/src/templates/Settings-01/Settings.jsx +++ b/src/templates/Settings-01/Settings.jsx @@ -6,6 +6,7 @@ import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'; import './Settings.css'; +// Default settings object const defaultSettings = { defaultDownloadPath: "", autoClean: true, @@ -14,32 +15,38 @@ const defaultSettings = { importPath: "", two_gb_limit: true, hide_nsfw_content: false, + background_image_path: "", }; - +// Load settings async function loadSettings() { const configDir = await appConfigDir(); const dirPath = `${configDir.replace(/\\/g, '/')}/fitgirlConfig`; const settingsPath = `${dirPath}/settings.json`; try { + // Create folder if it does not exist const dirExists = await exists(dirPath); if (!dirExists) { await createDir(dirPath, { recursive: true }); } + // Check if the settings file exists, and if not, create it const fileExists = await exists(settingsPath); if (!fileExists) { await writeTextFile(settingsPath, JSON.stringify(defaultSettings, null, 2)); return defaultSettings; } + // Read and parse the settings file const json = await readTextFile(settingsPath); let settings = JSON.parse(json); + // Check if new settings have been added, and add them with default values if (!settings.hasOwnProperty('hide_nsfw_content')) { settings.hide_nsfw_content = defaultSettings.hide_nsfw_content; await writeTextFile(settingsPath, JSON.stringify(settings, null, 2)); } + return settings; } catch (error) { @@ -48,6 +55,7 @@ async function loadSettings() { } } +// Save settings to a JSON file async function saveSettings(settings) { const configDir = await appConfigDir(); const dirPath = `${configDir.replace(/\\/g, '/')}/fitgirlConfig`; @@ -92,10 +100,14 @@ const SettingsPage = () => { settingsLinkText.style.backgroundColor = '#ffffff0d'; settingsLinkText.style.borderRadius = '5px'; } + // Load settings from the JSON file const initialSettings = await loadSettings(); setSettings(initialSettings); + + // Fetch the app version const appVersionValue = await getVersion(); setVersion(appVersionValue); + } catch (error) { console.error('Error during initialization:', error); } finally { @@ -224,6 +236,18 @@ const SettingsPage = () => { placeholder="Enter path to imported games" />
+ + {/* TODO: Background Image Path */} + {/*
+ + setSettings({ ...settings(), background_image_path: e.target.value })} + placeholder="Enter path to image" + /> +
*/} {/* Fit Launcher Information */}