Added JS hashes for better browser caching

master
Wynd 2025-06-25 13:44:08 +03:00
parent 630439bc48
commit fd33426219
14 changed files with 198 additions and 94 deletions

View File

@ -11,6 +11,7 @@ toml = "0.8"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
itertools = "0.14" itertools = "0.14"
blake3 = "1.8"
[features] [features]
default = ["bbs", "ddd", "kh3", "kh2", "kh1"] default = ["bbs", "ddd", "kh3", "kh2", "kh1"]

View File

@ -0,0 +1,7 @@
import {
kindFilter,
showOnlyTracked,
track,
} from "./modules/common/mat-kind-filter.js";
Object.assign(window, { track });

View File

@ -1,13 +1,14 @@
use std::collections::HashMap; use std::{collections::HashMap, sync::OnceLock};
use ability::Ability; use ability::Ability;
use askama::Template; use askama::Template;
use blake3::Hash;
use command::Command; use command::Command;
use finisher::Finisher; use finisher::Finisher;
use itertools::Itertools; use itertools::Itertools;
use serde::Deserialize; use serde::Deserialize;
use crate::create_file; use crate::{RuntimeModule, create_file, create_hashes};
mod ability; mod ability;
mod command; mod command;
@ -17,6 +18,7 @@ mod melding;
const ABILITIES_PATH: &str = "./input/bbs/abilities.json"; const ABILITIES_PATH: &str = "./input/bbs/abilities.json";
const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json"; const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json";
const COMMANDS_PATH: &str = "./input/bbs/commands.json"; const COMMANDS_PATH: &str = "./input/bbs/commands.json";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Debug, Deserialize, PartialEq, Eq)] #[derive(Debug, Deserialize, PartialEq, Eq)]
enum Character { enum Character {
@ -35,36 +37,44 @@ struct CommandsTemplate {
pub crystals: Vec<String>, pub crystals: Vec<String>,
} }
pub fn init() { pub struct 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::<Vec<Ability>>(&abilities_str).unwrap();
tracing::info!("Loading finishers data from {}", ABILITIES_PATH); impl RuntimeModule for Module {
let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap(); fn start_module() {
let finishers = serde_json::from_str::<HashMap<String, Finisher>>(&finishers_str).unwrap(); 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::<Vec<Ability>>(&abilities_str).unwrap();
tracing::info!("Loading commands data from {}", ABILITIES_PATH); tracing::info!("Loading finishers data from {}", ABILITIES_PATH);
let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap(); let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap();
let mut commands = serde_json::from_str::<Vec<Command>>(&commands_str).unwrap(); let finishers = serde_json::from_str::<HashMap<String, Finisher>>(&finishers_str).unwrap();
// Create a vec with all the crystal variants found in abilities tracing::info!("Loading commands data from {}", ABILITIES_PATH);
let crystals = abilities let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap();
.iter() let mut commands = serde_json::from_str::<Vec<Command>>(&commands_str).unwrap();
.map(|x| x.from.clone())
.unique()
.sorted()
.collect();
// Create a vec of crystals and what ability they give for each recipe // Create a vec with all the crystal variants found in abilities
for cmd in commands.iter_mut() { let crystals = abilities
for recipe in cmd.recipes.iter_mut() { .iter()
recipe.set_abilities(&abilities); .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"); fn get_js_hash() -> String {
let melding_template = CommandsTemplate { commands, crystals }; JS_HASH.get_or_init(|| create_hashes("bbs")).to_string()
}
create_file("./out/bbs", "melding", melding_template.render().unwrap()).unwrap();
} }

View File

@ -1,8 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::OnceLock;
use crate::create_file;
use crate::ddd::ability::AbilityType; use crate::ddd::ability::AbilityType;
use crate::{RuntimeModule, create_file, create_hashes};
use askama::Template; use askama::Template;
use blake3::Hash;
use board::Board; use board::Board;
mod ability; mod ability;
@ -11,6 +13,7 @@ mod board_position;
mod route; mod route;
const ABILITIES_PATH: &str = "./input/ddd/abilities"; const ABILITIES_PATH: &str = "./input/ddd/abilities";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/ddd/boards.html")] #[template(path = "pages/ddd/boards.html")]
@ -18,37 +21,44 @@ struct AbilitiesTemplate {
pub boards: Vec<Board>, pub boards: Vec<Board>,
} }
pub fn init() { pub struct Module;
tracing::info!("Loading ability boards data from {}", ABILITIES_PATH);
let mut boards: Vec<Board> = 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::<Vec<PathBuf>>();
for path in paths { impl RuntimeModule for Module {
let board_str = std::fs::read_to_string(path).unwrap(); fn start_module() {
let mut board = toml::from_str::<Board>(&board_str).unwrap(); tracing::info!("Loading ability boards data from {}", ABILITIES_PATH);
let mut boards: Vec<Board> = 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::<Vec<PathBuf>>();
board.init_routes(); for path in paths {
board.init_total_lp(); let board_str = std::fs::read_to_string(path).unwrap();
board.init_max_level(); let mut board = toml::from_str::<Board>(&board_str).unwrap();
board.init_stats();
board.init_ability_help();
// dbg!(&board); board.init_routes();
boards.push(board); 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"); fn get_js_hash() -> String {
let boards_template = AbilitiesTemplate { boards }; JS_HASH.get_or_init(|| create_hashes("ddd")).to_string()
}
create_file("./out/ddd", "boards", boards_template.render().unwrap()).unwrap();
} }

View File

@ -1,14 +1,16 @@
use std::path::PathBuf; use std::sync::OnceLock;
use askama::Template; 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] = &[ const MATERIAL_KINDS: &[&str] = &[
"lucid", "spirit", "power", "blaze", "frost", "thunder", "shiny", "bright", "mystery", "gale", "lucid", "spirit", "power", "blaze", "frost", "thunder", "shiny", "bright", "mystery", "gale",
"mythril", "mythril",
]; ];
const DROPS_PATH: &str = "./input/kh1/drops"; const DROPS_PATH: &str = "./input/kh1/drops";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/kh1/drops.html")] #[template(path = "pages/kh1/drops.html")]
@ -16,12 +18,20 @@ struct DropsTemplate {
pub drops: Vec<MaterialDrops>, pub drops: Vec<MaterialDrops>,
} }
pub fn init() { pub struct Module;
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
let drops = MaterialDrops::import(DROPS_PATH);
tracing::info!("Generating the KH1 drops template"); impl RuntimeModule for Module {
let drops_template = DropsTemplate { drops }; 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()
}
} }

View File

@ -1,8 +1,9 @@
use std::path::PathBuf; use std::sync::OnceLock;
use askama::Template; 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] = &[ const MATERIAL_KINDS: &[&str] = &[
"blazing", "blazing",
@ -19,6 +20,7 @@ const MATERIAL_KINDS: &[&str] = &[
"twilight", "twilight",
]; ];
const DROPS_PATH: &str = "./input/kh2/drops"; const DROPS_PATH: &str = "./input/kh2/drops";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/kh2/drops.html")] #[template(path = "pages/kh2/drops.html")]
@ -26,12 +28,20 @@ struct DropsTemplate {
pub drops: Vec<MaterialDrops>, pub drops: Vec<MaterialDrops>,
} }
pub fn init() { pub struct Module;
tracing::info!("Loading enemy drops data from {}", DROPS_PATH);
let drops = MaterialDrops::import(DROPS_PATH);
tracing::info!("Generating the KH2 drops template"); impl RuntimeModule for Module {
let drops_template = DropsTemplate { drops }; 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()
}
} }

View File

@ -1,11 +1,15 @@
use std::sync::OnceLock;
use askama::Template; use askama::Template;
use blake3::Hash;
use food::Recipes; use food::Recipes;
use crate::create_file; use crate::{RuntimeModule, create_file, create_hashes};
mod food; mod food;
const RECIPES_PATH: &str = "./input/kh3/recipes.toml"; const RECIPES_PATH: &str = "./input/kh3/recipes.toml";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/kh3/food-sim.html")] #[template(path = "pages/kh3/food-sim.html")]
@ -13,13 +17,21 @@ struct RecipesTemplate {
pub recipes: Recipes, pub recipes: Recipes,
} }
pub fn init() { pub struct 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>(&recipes_str).unwrap();
tracing::info!("Generating the KH3 recipes template"); impl RuntimeModule for Module {
let food_template = RecipesTemplate { recipes }; 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>(&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()
}
} }

View File

@ -1,6 +1,9 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::{fs, path::PathBuf};
use askama::Template; use askama::Template;
use blake3::{Hash, Hasher};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
mod common; mod common;
@ -22,6 +25,12 @@ mod kh3;
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub trait RuntimeModule {
fn start_module();
fn get_js_hash() -> String;
}
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/index.html")] #[template(path = "pages/index.html")]
struct IndexTemplate {} struct IndexTemplate {}
@ -43,19 +52,54 @@ fn main() {
.unwrap(); .unwrap();
#[cfg(feature = "bbs")] #[cfg(feature = "bbs")]
bbs::init(); start_module::<bbs::Module>();
#[cfg(feature = "ddd")] #[cfg(feature = "ddd")]
ddd::init(); start_module::<ddd::Module>();
#[cfg(feature = "kh1")] #[cfg(feature = "kh1")]
kh1::init(); start_module::<kh1::Module>();
#[cfg(feature = "kh2")] #[cfg(feature = "kh2")]
kh2::init(); start_module::<kh2::Module>();
#[cfg(feature = "kh3")] #[cfg(feature = "kh3")]
kh3::init(); start_module::<kh3::Module>();
}
fn start_module<M: RuntimeModule>() {
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<()> { fn create_file(dir: &str, file: &str, buf: String) -> std::io::Result<()> {

View File

@ -4,7 +4,7 @@
{% block head %} {% block head %}
<style> {% include "components/bbs/style.css" %}</style> <style> {% include "components/bbs/style.css" %}</style>
<script type="module" src="/public/scripts/bbs.js"></script> <script type="module" src="/public/scripts/bbs.js?v={{Module::get_js_hash()}}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -5,15 +5,15 @@
{% block head %} {% block head %}
<style>{% include "components/kh2/style.css" %}</style> <style>{% include "components/kh2/style.css" %}</style>
<script type="module" src="/public/scripts/kh1.js?v={{Module::get_js_hash()}}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{# {% include "components/common/only-tracked-filter.html" %}
{% include "components/kh2/only-tracked-filter.html" %}
<br /> <br />
{% include "components/kh2/kind-filters.html" %} {% include "components/common/kind-filters.html" %}
<br /> <br />
#}
{% for category in drops %} {% for category in drops %}
{% call macros::drop("shard") %} {% call macros::drop("shard") %}
{% call macros::drop("stone") %} {% call macros::drop("stone") %}

View File

@ -5,13 +5,13 @@
{% block head %} {% block head %}
<style>{% include "components/kh2/style.css" %}</style> <style>{% include "components/kh2/style.css" %}</style>
<script type="module" src="/public/scripts/kh2.js"></script> <script type="module" src="/public/scripts/kh2.js?v={{Module::get_js_hash()}}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% include "components/kh2/only-tracked-filter.html" %} {% include "components/common/only-tracked-filter.html" %}
<br /> <br />
{% include "components/kh2/kind-filters.html" %} {% include "components/common/kind-filters.html" %}
<br /> <br />
{% for category in drops %} {% for category in drops %}

View File

@ -4,7 +4,7 @@
{% block head %} {% block head %}
<style>{% include "components/kh3/style.css" %}</style> <style>{% include "components/kh3/style.css" %}</style>
<script type="module" src="/public/scripts/kh3.js"></script> <script type="module" src="/public/scripts/kh3.js?v={{Module::get_js_hash()}}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}