diff --git a/Cargo.toml b/Cargo.toml index 50b5407..6fb1c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ toml = "0.8" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } itertools = "0.14" +blake3 = "1.8" [features] default = ["bbs", "ddd", "kh3", "kh2", "kh1"] diff --git a/public/scripts/kh1.js b/public/scripts/kh1.js new file mode 100644 index 0000000..df5e1f4 --- /dev/null +++ b/public/scripts/kh1.js @@ -0,0 +1,7 @@ +import { + kindFilter, + showOnlyTracked, + track, +} from "./modules/common/mat-kind-filter.js"; + +Object.assign(window, { track }); diff --git a/src/bbs.rs b/src/bbs.rs index cb64afa..84929f2 100644 --- a/src/bbs.rs +++ b/src/bbs.rs @@ -1,13 +1,14 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::OnceLock}; use ability::Ability; use askama::Template; +use blake3::Hash; use command::Command; use finisher::Finisher; use itertools::Itertools; use serde::Deserialize; -use crate::create_file; +use crate::{RuntimeModule, create_file, create_hashes}; mod ability; mod command; @@ -17,6 +18,7 @@ mod melding; const ABILITIES_PATH: &str = "./input/bbs/abilities.json"; const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json"; const COMMANDS_PATH: &str = "./input/bbs/commands.json"; +static JS_HASH: OnceLock = OnceLock::new(); #[derive(Debug, Deserialize, PartialEq, Eq)] enum Character { @@ -35,36 +37,44 @@ struct CommandsTemplate { pub crystals: Vec, } -pub fn init() { - tracing::info!("Loading abilities data from {}", ABILITIES_PATH); - let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap(); - let abilities = serde_json::from_str::>(&abilities_str).unwrap(); +pub struct Module; - tracing::info!("Loading finishers data from {}", ABILITIES_PATH); - let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap(); - let finishers = serde_json::from_str::>(&finishers_str).unwrap(); +impl RuntimeModule for Module { + fn start_module() { + tracing::info!("Loading abilities data from {}", ABILITIES_PATH); + let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap(); + let abilities = serde_json::from_str::>(&abilities_str).unwrap(); - tracing::info!("Loading commands data from {}", ABILITIES_PATH); - let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap(); - let mut commands = serde_json::from_str::>(&commands_str).unwrap(); + tracing::info!("Loading finishers data from {}", ABILITIES_PATH); + let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap(); + let finishers = serde_json::from_str::>(&finishers_str).unwrap(); - // Create a vec with all the crystal variants found in abilities - let crystals = abilities - .iter() - .map(|x| x.from.clone()) - .unique() - .sorted() - .collect(); + tracing::info!("Loading commands data from {}", ABILITIES_PATH); + let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap(); + let mut commands = serde_json::from_str::>(&commands_str).unwrap(); - // Create a vec of crystals and what ability they give for each recipe - for cmd in commands.iter_mut() { - for recipe in cmd.recipes.iter_mut() { - recipe.set_abilities(&abilities); + // Create a vec with all the crystal variants found in abilities + let crystals = abilities + .iter() + .map(|x| x.from.clone()) + .unique() + .sorted() + .collect(); + + // Create a vec of crystals and what ability they give for each recipe + for cmd in commands.iter_mut() { + for recipe in cmd.recipes.iter_mut() { + recipe.set_abilities(&abilities); + } } + + tracing::info!("Generating the BBS melding table template"); + let melding_template = CommandsTemplate { commands, crystals }; + + create_file("./out/bbs", "melding", melding_template.render().unwrap()).unwrap(); } - tracing::info!("Generating the BBS melding table template"); - let melding_template = CommandsTemplate { commands, crystals }; - - create_file("./out/bbs", "melding", melding_template.render().unwrap()).unwrap(); + fn get_js_hash() -> String { + JS_HASH.get_or_init(|| create_hashes("bbs")).to_string() + } } diff --git a/src/ddd.rs b/src/ddd.rs index 4363252..a7fd84d 100644 --- a/src/ddd.rs +++ b/src/ddd.rs @@ -1,8 +1,10 @@ use std::path::PathBuf; +use std::sync::OnceLock; -use crate::create_file; use crate::ddd::ability::AbilityType; +use crate::{RuntimeModule, create_file, create_hashes}; use askama::Template; +use blake3::Hash; use board::Board; mod ability; @@ -11,6 +13,7 @@ mod board_position; mod route; const ABILITIES_PATH: &str = "./input/ddd/abilities"; +static JS_HASH: OnceLock = OnceLock::new(); #[derive(Template)] #[template(path = "pages/ddd/boards.html")] @@ -18,37 +21,44 @@ struct AbilitiesTemplate { pub boards: Vec, } -pub fn init() { - tracing::info!("Loading ability boards data from {}", ABILITIES_PATH); - let mut boards: Vec = vec![]; - // Loading multiple files into one vector due to the size of each board - let paths = std::fs::read_dir(ABILITIES_PATH) - .unwrap() - .filter_map(|f| f.ok()) - .map(|f| f.path()) - .filter_map(|p| match p.extension().is_some_and(|e| e == "toml") { - true => Some(p), - false => None, - }) - .collect::>(); +pub struct Module; - for path in paths { - let board_str = std::fs::read_to_string(path).unwrap(); - let mut board = toml::from_str::(&board_str).unwrap(); +impl RuntimeModule for Module { + fn start_module() { + tracing::info!("Loading ability boards data from {}", ABILITIES_PATH); + let mut boards: Vec = vec![]; + // Loading multiple files into one vector due to the size of each board + let paths = std::fs::read_dir(ABILITIES_PATH) + .unwrap() + .filter_map(|f| f.ok()) + .map(|f| f.path()) + .filter_map(|p| match p.extension().is_some_and(|e| e == "toml") { + true => Some(p), + false => None, + }) + .collect::>(); - board.init_routes(); - board.init_total_lp(); - board.init_max_level(); - board.init_stats(); - board.init_ability_help(); + for path in paths { + let board_str = std::fs::read_to_string(path).unwrap(); + let mut board = toml::from_str::(&board_str).unwrap(); - // dbg!(&board); - boards.push(board); + board.init_routes(); + board.init_total_lp(); + board.init_max_level(); + board.init_stats(); + board.init_ability_help(); + + boards.push(board); + } + boards.sort_by(|a, b| a.order.cmp(&b.order)); + + tracing::info!("Generating the DDD ability boards template"); + let boards_template = AbilitiesTemplate { boards }; + + create_file("./out/ddd", "boards", boards_template.render().unwrap()).unwrap(); } - boards.sort_by(|a, b| a.order.cmp(&b.order)); - tracing::info!("Generating the DDD ability boards template"); - let boards_template = AbilitiesTemplate { boards }; - - create_file("./out/ddd", "boards", boards_template.render().unwrap()).unwrap(); + fn get_js_hash() -> String { + JS_HASH.get_or_init(|| create_hashes("ddd")).to_string() + } } diff --git a/src/kh1.rs b/src/kh1.rs index 216782b..8e9c3f5 100644 --- a/src/kh1.rs +++ b/src/kh1.rs @@ -1,14 +1,16 @@ -use std::path::PathBuf; +use std::sync::OnceLock; use askama::Template; +use blake3::Hash; -use crate::{common::materials::MaterialDrops, create_file}; +use crate::{RuntimeModule, common::materials::MaterialDrops, create_file, create_hashes}; const MATERIAL_KINDS: &[&str] = &[ "lucid", "spirit", "power", "blaze", "frost", "thunder", "shiny", "bright", "mystery", "gale", "mythril", ]; const DROPS_PATH: &str = "./input/kh1/drops"; +static JS_HASH: OnceLock = OnceLock::new(); #[derive(Template)] #[template(path = "pages/kh1/drops.html")] @@ -16,12 +18,20 @@ struct DropsTemplate { pub drops: Vec, } -pub fn init() { - tracing::info!("Loading enemy drops data from {}", DROPS_PATH); - let drops = MaterialDrops::import(DROPS_PATH); +pub struct Module; - tracing::info!("Generating the KH1 drops template"); - let drops_template = DropsTemplate { drops }; +impl RuntimeModule for Module { + fn start_module() { + tracing::info!("Loading enemy drops data from {}", DROPS_PATH); + let drops = MaterialDrops::import(DROPS_PATH); - create_file("./out/kh1", "drops", drops_template.render().unwrap()).unwrap(); + tracing::info!("Generating the KH1 drops template"); + let drops_template = DropsTemplate { drops }; + + create_file("./out/kh1", "drops", drops_template.render().unwrap()).unwrap(); + } + + fn get_js_hash() -> String { + JS_HASH.get_or_init(|| create_hashes("kh1")).to_string() + } } diff --git a/src/kh2.rs b/src/kh2.rs index 54a66e8..cf5481f 100644 --- a/src/kh2.rs +++ b/src/kh2.rs @@ -1,8 +1,9 @@ -use std::path::PathBuf; +use std::sync::OnceLock; use askama::Template; +use blake3::Hash; -use crate::{common::materials::MaterialDrops, create_file}; +use crate::{RuntimeModule, common::materials::MaterialDrops, create_file, create_hashes}; const MATERIAL_KINDS: &[&str] = &[ "blazing", @@ -19,6 +20,7 @@ const MATERIAL_KINDS: &[&str] = &[ "twilight", ]; const DROPS_PATH: &str = "./input/kh2/drops"; +static JS_HASH: OnceLock = OnceLock::new(); #[derive(Template)] #[template(path = "pages/kh2/drops.html")] @@ -26,12 +28,20 @@ struct DropsTemplate { pub drops: Vec, } -pub fn init() { - tracing::info!("Loading enemy drops data from {}", DROPS_PATH); - let drops = MaterialDrops::import(DROPS_PATH); +pub struct Module; - tracing::info!("Generating the KH2 drops template"); - let drops_template = DropsTemplate { drops }; +impl RuntimeModule for Module { + fn start_module() { + tracing::info!("Loading enemy drops data from {}", DROPS_PATH); + let drops = MaterialDrops::import(DROPS_PATH); - create_file("./out/kh2", "drops", drops_template.render().unwrap()).unwrap(); + tracing::info!("Generating the KH2 drops template"); + let drops_template = DropsTemplate { drops }; + + create_file("./out/kh2", "drops", drops_template.render().unwrap()).unwrap(); + } + + fn get_js_hash() -> String { + JS_HASH.get_or_init(|| create_hashes("kh2")).to_string() + } } diff --git a/src/kh3.rs b/src/kh3.rs index 106758a..7e319d4 100644 --- a/src/kh3.rs +++ b/src/kh3.rs @@ -1,11 +1,15 @@ +use std::sync::OnceLock; + use askama::Template; +use blake3::Hash; use food::Recipes; -use crate::create_file; +use crate::{RuntimeModule, create_file, create_hashes}; mod food; const RECIPES_PATH: &str = "./input/kh3/recipes.toml"; +static JS_HASH: OnceLock = OnceLock::new(); #[derive(Template)] #[template(path = "pages/kh3/food-sim.html")] @@ -13,13 +17,21 @@ struct RecipesTemplate { pub recipes: Recipes, } -pub fn init() { - tracing::info!("Loading recipes data from {}", RECIPES_PATH); - let recipes_str = std::fs::read_to_string(RECIPES_PATH).unwrap(); - let recipes = toml::from_str::(&recipes_str).unwrap(); +pub struct Module; - tracing::info!("Generating the KH3 recipes template"); - let food_template = RecipesTemplate { recipes }; +impl RuntimeModule for Module { + fn start_module() { + tracing::info!("Loading recipes data from {}", RECIPES_PATH); + let recipes_str = std::fs::read_to_string(RECIPES_PATH).unwrap(); + let recipes = toml::from_str::(&recipes_str).unwrap(); - create_file("./out/kh3", "food-sim", food_template.render().unwrap()).unwrap(); + tracing::info!("Generating the KH3 recipes template"); + let food_template = RecipesTemplate { recipes }; + + create_file("./out/kh3", "food-sim", food_template.render().unwrap()).unwrap(); + } + + fn get_js_hash() -> String { + JS_HASH.get_or_init(|| create_hashes("kh3")).to_string() + } } diff --git a/src/main.rs b/src/main.rs index 0dcf985..9afecb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ #![allow(dead_code)] +use std::{fs, path::PathBuf}; + use askama::Template; +use blake3::{Hash, Hasher}; use tracing_subscriber::EnvFilter; mod common; @@ -22,6 +25,12 @@ mod kh3; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub trait RuntimeModule { + fn start_module(); + + fn get_js_hash() -> String; +} + #[derive(Template)] #[template(path = "pages/index.html")] struct IndexTemplate {} @@ -43,19 +52,54 @@ fn main() { .unwrap(); #[cfg(feature = "bbs")] - bbs::init(); + start_module::(); #[cfg(feature = "ddd")] - ddd::init(); + start_module::(); #[cfg(feature = "kh1")] - kh1::init(); + start_module::(); #[cfg(feature = "kh2")] - kh2::init(); + start_module::(); #[cfg(feature = "kh3")] - kh3::init(); + start_module::(); +} + +fn start_module() { + M::start_module(); +} + +fn create_hashes(module: &str) -> Hash { + let mut hasher = blake3::Hasher::new(); + hash_file(format!("./public/scripts/{module}.js").into(), &mut hasher); + hash_files_in_dir("./public/scripts/modules/common".into(), &mut hasher); + hash_files_in_dir( + format!("./public/scripts/modules/{module}").into(), + &mut hasher, + ); + hasher.finalize() +} + +fn hash_files_in_dir(path: PathBuf, hasher: &mut Hasher) { + if let Ok(paths) = fs::read_dir(path) { + for path in paths.flatten() { + let path = path.path(); + if path.metadata().is_ok_and(|p| p.is_dir()) { + hash_files_in_dir(path, hasher); + continue; + } + + hash_file(path, hasher); + } + } +} + +fn hash_file(path: PathBuf, hasher: &mut Hasher) { + if let Ok(file) = fs::read(path) { + hasher.update(&file); + } } fn create_file(dir: &str, file: &str, buf: String) -> std::io::Result<()> { diff --git a/templates/components/kh2/kind-filters.html b/templates/components/common/kind-filters.html similarity index 100% rename from templates/components/kh2/kind-filters.html rename to templates/components/common/kind-filters.html diff --git a/templates/components/kh2/only-tracked-filter.html b/templates/components/common/only-tracked-filter.html similarity index 100% rename from templates/components/kh2/only-tracked-filter.html rename to templates/components/common/only-tracked-filter.html diff --git a/templates/pages/bbs/melding.html b/templates/pages/bbs/melding.html index cf12919..b80be26 100644 --- a/templates/pages/bbs/melding.html +++ b/templates/pages/bbs/melding.html @@ -4,7 +4,7 @@ {% block head %} - + {% endblock %} {% block content %} diff --git a/templates/pages/kh1/drops.html b/templates/pages/kh1/drops.html index fa8cfef..f8f73af 100644 --- a/templates/pages/kh1/drops.html +++ b/templates/pages/kh1/drops.html @@ -5,15 +5,15 @@ {% block head %} + {% endblock %} {% block content %} -{# - {% include "components/kh2/only-tracked-filter.html" %} + {% include "components/common/only-tracked-filter.html" %}
- {% include "components/kh2/kind-filters.html" %} + {% include "components/common/kind-filters.html" %}
-#} + {% for category in drops %} {% call macros::drop("shard") %} {% call macros::drop("stone") %} diff --git a/templates/pages/kh2/drops.html b/templates/pages/kh2/drops.html index dbd1e9f..b0da5a0 100644 --- a/templates/pages/kh2/drops.html +++ b/templates/pages/kh2/drops.html @@ -5,13 +5,13 @@ {% block head %} - + {% endblock %} {% block content %} - {% include "components/kh2/only-tracked-filter.html" %} + {% include "components/common/only-tracked-filter.html" %}
- {% include "components/kh2/kind-filters.html" %} + {% include "components/common/kind-filters.html" %}
{% for category in drops %} diff --git a/templates/pages/kh3/food-sim.html b/templates/pages/kh3/food-sim.html index e396035..c8a86d6 100644 --- a/templates/pages/kh3/food-sim.html +++ b/templates/pages/kh3/food-sim.html @@ -4,7 +4,7 @@ {% block head %} - + {% endblock %} {% block content %}