diff --git a/Cargo.toml b/Cargo.toml index 90d77a9..3feffed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,9 @@ polars = { version = "0.39.2", default-features = false, features = [ fantoccini = "0.19" [target.'cfg(engine)'.dependencies] +axum = { version = "0.6", features = ["macros"] } tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } perseus-axum = { version = "0.4.2" } -axum = "0.6" futures = "0.3.28" sea-orm = { version = "1.0", features = [ "sqlx-postgres", @@ -60,3 +60,6 @@ sea-orm = { version = "1.0" } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(engine)', 'cfg(client)'] } + +[profile.release] +lto = true diff --git a/src/components/layout.rs b/src/components/layout.rs index 2fcda47..37943ce 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -11,6 +11,14 @@ use crate::{ use perseus::prelude::*; use sycamore::prelude::*; +cfg_if::cfg_if! { + if #[cfg(client)] { + use crate::endpoints::CARD_INFO; + use crate::templates::get_api_path; + use crate::models::card::CardTable; + } +} + #[derive(Prop)] pub struct LayoutProps<'a, G: Html> { pub content_state: ContentState, @@ -51,12 +59,42 @@ pub fn Layout<'a, G: Html>( let content_state_header = content_state.clone(); + #[cfg(client)] + { + // TODO -> try to use suspense + spawn_local_scoped(cx, async move { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + + let card_table_loaded = (*global_state.constants.is_loaded.get()).clone(); + if !card_table_loaded { + let client = reqwest::Client::new(); + let response = client + .get(get_api_path(CARD_INFO).as_str()) + .send() + .await + .unwrap(); + + // TODO add error handling + let response = response.json::().await.unwrap(); + global_state.constants.card_table.set(Some(response)); + global_state.constants.is_loaded.set(true); + } + }); + } + view! { cx, // Main page header, including login functionality Header(content_state = content_state_header) // Modals section(class = "flex-2") { + (match (*global_state.constants.card_table.get()).clone() { + Some(card_table) => { view!{ cx, + p { "DONE" } + } }, + None => { view!{ cx, p { "Loading cards" } } }, + }) + (match *global_state.modals_open.login.get() { OpenState::Open => { view! { cx, diff --git a/src/endpoints.rs b/src/endpoints.rs index a166646..5e2ea9b 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -4,3 +4,4 @@ pub const LOGIN: &str = "/api/login"; #[cfg(engine)] pub const LOGIN_TEST: &str = "/api/login-test"; pub const FORGOT_PASSWORD: &str = "/api/forgot-password"; +pub const CARD_INFO: &str = "/api/card-info"; diff --git a/src/global_state.rs b/src/global_state.rs index 12f6f95..cf66121 100644 --- a/src/global_state.rs +++ b/src/global_state.rs @@ -4,13 +4,16 @@ use perseus::{prelude::*, state::GlobalStateCreator}; use serde::{Deserialize, Serialize}; use crate::{ - models::auth::WebAuthInfo, + models::{auth::WebAuthInfo, card::CardTable}, state_enums::{LoginState, OpenState}, }; + #[derive(Serialize, Deserialize, ReactiveState, Clone)] #[rx(alias = "AppStateRx")] pub struct AppState { + #[rx(nested)] + pub constants: ConstData, #[rx(nested)] pub auth: AuthData, #[rx(nested)] @@ -19,6 +22,13 @@ pub struct AppState { pub style: StyleData, } +#[derive(Serialize, Deserialize, ReactiveState, Clone)] +#[rx(alias = "ConstDataRx")] +pub struct ConstData { + pub is_loaded: bool, + pub card_table: Option, +} + #[derive(Serialize, Deserialize, ReactiveState, Clone)] #[rx(alias = "AuthDataRx")] pub struct AuthData { @@ -58,6 +68,10 @@ pub fn get_global_state_creator() -> GlobalStateCreator { #[engine_only_fn] pub async fn get_build_state() -> AppState { AppState { + constants: ConstData { + is_loaded: false, + card_table: None + }, auth: AuthData { state: LoginState::Unknown, pending_username: String::new(), diff --git a/src/models/card.rs b/src/models/card.rs index 08dac82..411f414 100644 --- a/src/models/card.rs +++ b/src/models/card.rs @@ -34,6 +34,7 @@ pub enum MonsterAttribute { Light, Water, Wind, + None, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -51,13 +52,14 @@ pub enum TrapType { Normal, Continuous, Counter, + Unknown, } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum CardTypeInfo { Monster { - level: u32, // level/rank/link rating - atk: u32, + level: Option, // level/rank/link rating + atk: Option, def: Option, pendulum_scale: Option, attribute: MonsterAttribute, @@ -170,16 +172,16 @@ impl CardTable { "Continuous" => TrapType::Continuous, "Counter" => TrapType::Counter, "Normal" => TrapType::Normal, - unknown => panic!("Unknown trap type {}", unknown), + _ => TrapType::Unknown, }, } } else { CardTypeInfo::Monster { - level: row[level_idx].try_extract().unwrap(), - atk: row[atk_idx].try_extract().unwrap(), + level: row[level_idx].try_extract().ok(), + atk: row[atk_idx].try_extract().ok(), def: row[def_idx].try_extract().ok(), pendulum_scale: row[pendulum_scale_idx].try_extract().ok(), - attribute: match row[attribute_idx].get_str().unwrap() { + attribute: match row[attribute_idx].get_str().unwrap_or("NONE") { "DARK" => MonsterAttribute::Dark, "DIVINE" => MonsterAttribute::Divine, "EARTH" => MonsterAttribute::Earth, @@ -187,6 +189,7 @@ impl CardTable { "LIGHT" => MonsterAttribute::Light, "WATER" => MonsterAttribute::Water, "WIND" => MonsterAttribute::Wind, + "NONE" => MonsterAttribute::None, unknown => panic!("Unknown attribute {}", unknown), }, monster_type: row[monster_type_idx] @@ -390,18 +393,14 @@ impl CardTable { "frameType", "ygoprodeck_url", "linkval", - "race" + "race", ])]) // Remove link markers, unless it's needed later .select([col("*").exclude(["linkmarkers"])]) // TODO add banlist support .select([col("*").exclude(["banlist_info"])]) // TODO readd - .select([col("*").exclude([ - "card_sets", - "card_images", - "card_prices", - ])]) + .select([col("*").exclude(["card_sets", "card_images", "card_prices"])]) // Filter out "Skill Card" .filter(col("type").str().contains(lit("Skill Card"), false).not()) // Filters for testing diff --git a/src/server/constants/card_table.rs b/src/server/constants/card_table.rs new file mode 100644 index 0000000..b1747e7 --- /dev/null +++ b/src/server/constants/card_table.rs @@ -0,0 +1,18 @@ +use std::path::Path; + +use lazy_static::lazy_static; + +use crate::server::server_state::ServerState; +use axum::{debug_handler, extract::State, http::StatusCode, Json}; + +use crate::models::card::CardTable; + +lazy_static! { + static ref CARD_TABLE: CardTable = + CardTable::new_from_server_json(Path::new("./data/cardinfo.json")); +} + +#[debug_handler] +pub async fn get_card_table(State(_): State) -> Result, StatusCode> { + Ok(Json(CARD_TABLE.clone())) +} diff --git a/src/server/constants/mod.rs b/src/server/constants/mod.rs new file mode 100644 index 0000000..ec43098 --- /dev/null +++ b/src/server/constants/mod.rs @@ -0,0 +1 @@ +pub mod card_table; diff --git a/src/server/mod.rs b/src/server/mod.rs index a3505e9..f9b6973 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,4 @@ pub mod auth; +pub mod constants; pub mod routes; pub mod server_state; diff --git a/src/server/routes.rs b/src/server/routes.rs index 16c52ea..aa6fb68 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -1,6 +1,6 @@ // (Server only) Routes -use crate::endpoints::{FORGOT_PASSWORD, LOGIN, LOGIN_TEST, REGISTER}; -use axum::routing::{post, Router}; +use crate::endpoints::{CARD_INFO, FORGOT_PASSWORD, LOGIN, LOGIN_TEST, REGISTER}; +use axum::routing::{get, post, Router}; use super::{ auth::{ @@ -8,6 +8,7 @@ use super::{ login::{post_login_user, post_test_login}, register::post_register_user, }, + constants::card_table::get_card_table, server_state::ServerState, }; @@ -17,5 +18,6 @@ pub fn get_api_router(state: ServerState) -> Router { .route(LOGIN, post(post_login_user)) .route(LOGIN_TEST, post(post_test_login)) .route(FORGOT_PASSWORD, post(post_forgot_password)) + .route(CARD_INFO, get(get_card_table)) .with_state(state) } diff --git a/src/templates/global_state.rs b/src/templates/global_state.rs deleted file mode 100644 index 110aec7..0000000 --- a/src/templates/global_state.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Not a page, global state that is shared between all pages - -use perseus::{prelude::*, state::GlobalStateCreator}; -use serde::{Deserialize, Serialize}; - -use crate::data::card::CardTable; - -cfg_if::cfg_if! { - if #[cfg(engine)] { - use std::thread; - use std::ops::Deref; - use crate::data::store::DATA; - } -} - -#[derive(Serialize, Deserialize, ReactiveState, Clone)] -#[rx(alias = "AppStateRx")] -pub struct AppState { - pub card_table: CardTable, -} - -pub fn get_global_state_creator() -> GlobalStateCreator { - GlobalStateCreator::new() - .build_state_fn(get_build_state) - .request_state_fn(get_request_state) -} - -#[engine_only_fn] -fn get_state() -> AppState { - let card_table = thread::spawn(move || DATA.lock().unwrap().deref().card_table.clone()) - .join() - .unwrap(); - - AppState { card_table } -} - -#[engine_only_fn] -pub async fn get_build_state() -> AppState { - get_state() -} - -#[engine_only_fn] -pub async fn get_request_state(_req: Request) -> AppState { - get_state() -}