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-subscriber = { version = "0.3", features = ["env-filter"] }
itertools = "0.14"
blake3 = "1.8"
[features]
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 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<Hash> = OnceLock::new();
#[derive(Debug, Deserialize, PartialEq, Eq)]
enum Character {
@ -35,36 +37,44 @@ struct CommandsTemplate {
pub crystals: Vec<String>,
}
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::<Vec<Ability>>(&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::<HashMap<String, Finisher>>(&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::<Vec<Ability>>(&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::<Vec<Command>>(&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::<HashMap<String, Finisher>>(&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::<Vec<Command>>(&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()
}
}

View File

@ -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<Hash> = OnceLock::new();
#[derive(Template)]
#[template(path = "pages/ddd/boards.html")]
@ -18,37 +21,44 @@ struct AbilitiesTemplate {
pub boards: Vec<Board>,
}
pub fn init() {
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>>();
pub struct Module;
for path in paths {
let board_str = std::fs::read_to_string(path).unwrap();
let mut board = toml::from_str::<Board>(&board_str).unwrap();
impl RuntimeModule for Module {
fn start_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>>();
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>(&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()
}
}

View File

@ -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<Hash> = OnceLock::new();
#[derive(Template)]
#[template(path = "pages/kh1/drops.html")]
@ -16,12 +18,20 @@ struct DropsTemplate {
pub drops: Vec<MaterialDrops>,
}
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()
}
}

View File

@ -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<Hash> = OnceLock::new();
#[derive(Template)]
#[template(path = "pages/kh2/drops.html")]
@ -26,12 +28,20 @@ struct DropsTemplate {
pub drops: Vec<MaterialDrops>,
}
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()
}
}

View File

@ -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<Hash> = 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>(&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>(&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)]
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::<bbs::Module>();
#[cfg(feature = "ddd")]
ddd::init();
start_module::<ddd::Module>();
#[cfg(feature = "kh1")]
kh1::init();
start_module::<kh1::Module>();
#[cfg(feature = "kh2")]
kh2::init();
start_module::<kh2::Module>();
#[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<()> {

View File

@ -4,7 +4,7 @@
{% block head %}
<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 %}
{% block content %}

View File

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

View File

@ -5,13 +5,13 @@
{% block head %}
<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 %}
{% block content %}
{% include "components/kh2/only-tracked-filter.html" %}
{% include "components/common/only-tracked-filter.html" %}
<br />
{% include "components/kh2/kind-filters.html" %}
{% include "components/common/kind-filters.html" %}
<br />
{% for category in drops %}

View File

@ -4,7 +4,7 @@
{% block head %}
<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 %}
{% block content %}