Compare commits

..

3 Commits

35 changed files with 301 additions and 95 deletions

View File

@ -1,4 +1,4 @@
import { debounce } from "./modules/common/helper.js"; import { debounce } from "../common/helper.js";
let charFilter = ""; let charFilter = "";
let typeFilter = ""; let typeFilter = "";

View File

@ -1,7 +1,7 @@
export let showOnlyTracked = false; export let showOnlyTracked = false;
export let kindFilter = new Set(); export let kindFilter = new Set();
document.addEventListener("DOMContentLoaded", (event) => { export function init() {
const onlyTrackedFilter = document.querySelector( const onlyTrackedFilter = document.querySelector(
'input[name="onlyTracked"]', 'input[name="onlyTracked"]',
); );
@ -25,7 +25,7 @@ document.addEventListener("DOMContentLoaded", (event) => {
filter(); filter();
}); });
}); });
}); }
function filter() { function filter() {
const categories = document.querySelectorAll(".category-wrapper"); const categories = document.querySelectorAll(".category-wrapper");

View File

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

View File

@ -0,0 +1,12 @@
import {
init,
kindFilter,
showOnlyTracked,
track,
} from "../common/mat-kind-filter.js";
document.addEventListener("DOMContentLoaded", (event) => {
init();
});
Object.assign(window, { track });

View File

@ -0,0 +1,64 @@
document.addEventListener("DOMContentLoaded", (event) => {
const recipes = document.querySelectorAll(".recipe");
for (const recipe of recipes) {
recipe.checked =
localStorage.getItem("kh1-synth-" + recipe.id) === "true" ?? false;
update_synth_completions(recipe);
recipe.addEventListener("input", function () {
localStorage.setItem("kh1-synth-" + this.id, this.checked);
update_synth_completions(this);
calc_total_ingredients();
});
}
calc_total_ingredients();
});
function update_synth_completions(recipe) {
if (recipe.checked) {
recipe.parentElement.classList.add("complete");
} else {
recipe.parentElement.classList.remove("complete");
}
}
function calc_total_ingredients() {
const needed = new Map();
const recipes = document.querySelectorAll(".recipe");
for (const recipe of recipes) {
const isComplete = recipe.checked;
if (isComplete) {
continue;
}
const ingredients =
recipe.parentElement.querySelectorAll("label ul li");
for (const ingredient of ingredients) {
let name = ingredient.dataset["synthItemName"];
let amount = Number(ingredient.dataset["synthItemAmount"]);
if (needed.has(name)) {
let new_amount = Number(needed.get(name)) + amount;
needed.set(name, new_amount);
} else {
needed.set(name, amount);
}
}
}
const sortedMap = new Map([...needed].sort((a, b) => b[1] - a[1]));
// generating the new list with materials needed
const matsList = document.getElementById("mats");
matsList.innerHTML = "<h1>Materials Needed</h1>";
const uiList = document.createElement("ul");
for (const entry of sortedMap) {
const liElem = document.createElement("li");
liElem.innerHTML = entry[0] + " x" + entry[1];
uiList.appendChild(liElem);
}
matsList.appendChild(uiList);
}

View File

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

View File

@ -0,0 +1,12 @@
import {
init,
kindFilter,
showOnlyTracked,
track,
} from "../common/mat-kind-filter.js";
document.addEventListener("DOMContentLoaded", (event) => {
init();
});
Object.assign(window, { track });

View File

@ -1,8 +1,10 @@
@import url("./colors.css");
body { body {
position: relative; position: relative;
min-height: 98vh; min-height: 98vh;
background-color: #333; background-color: var(--bg-color);
color: #fff; color: var(--text-color);
} }
footer { footer {

View File

@ -0,0 +1,4 @@
:root {
--bg-color: #333;
--text-color: #fff;
}

View File

@ -0,0 +1 @@
@import url("../common/drops.css");

View File

@ -0,0 +1,62 @@
#mats {
position: fixed;
width: 20%;
height: 100%;
h1 {
margin: 0;
font-size: 24px;
text-align: center;
}
ul {
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 90%;
list-style: none;
line-height: 2;
}
}
#recipes {
display: flex;
width: calc(80% - 20px);
flex-wrap: wrap;
margin: 20px 10px;
user-select: none;
margin-left: auto;
margin-right: 0;
.recipe-wrapper {
cursor: pointer;
padding: 10px 20px 10px 10px;
input[type="checkbox"] {
display: none;
}
label {
display: inline-block;
width: 100%;
height: 100%;
cursor: pointer;
}
img {
vertical-align: middle;
}
&:hover {
box-shadow: 0px 0px 20px 0px limegreen;
scale: 1.1;
}
&.complete {
color: limegreen;
> * {
text-decoration: line-through;
}
}
}
}

View File

@ -0,0 +1 @@
@import url("../common/drops.css");

View File

@ -1,4 +1,3 @@
unstable_features = true
reorder_imports = true reorder_imports = true
hard_tabs = true hard_tabs = true
control_brace_style = "ClosingNextLine" control_brace_style = "ClosingNextLine"

View File

@ -1,14 +1,13 @@
use std::{collections::HashMap, sync::OnceLock}; use std::collections::HashMap;
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::{RuntimeModule, create_file, create_hashes}; use crate::{RuntimeModule, create_file};
mod ability; mod ability;
mod command; mod command;
@ -18,7 +17,6 @@ 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 {
@ -71,10 +69,6 @@ impl RuntimeModule for Module {
tracing::info!("Generating the BBS melding table template"); tracing::info!("Generating the BBS melding table template");
let melding_template = CommandsTemplate { commands, crystals }; let melding_template = CommandsTemplate { commands, crystals };
create_file("./out/bbs", "melding", melding_template.render().unwrap()).unwrap(); create_file("./out/bbs", "melding", melding_template).unwrap();
}
fn get_js_hash() -> String {
JS_HASH.get_or_init(|| create_hashes("bbs")).to_string()
} }
} }

View File

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
use super::{ability::Ability, Character}; use super::{Character, ability::Ability};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CommandRecipe { pub struct CommandRecipe {

View File

@ -1,6 +1,5 @@
use std::collections::{BTreeMap, HashMap}; use std::collections::HashMap;
use itertools::Itertools;
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] #[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]

View File

@ -1,10 +1,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::OnceLock;
use crate::ddd::ability::AbilityType; use crate::ddd::ability::AbilityType;
use crate::{RuntimeModule, create_file, create_hashes}; use crate::{RuntimeModule, create_file};
use askama::Template; use askama::Template;
use blake3::Hash;
use board::Board; use board::Board;
mod ability; mod ability;
@ -13,7 +11,6 @@ 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")]
@ -55,10 +52,6 @@ impl RuntimeModule for Module {
tracing::info!("Generating the DDD ability boards template"); tracing::info!("Generating the DDD ability boards template");
let boards_template = AbilitiesTemplate { boards }; let boards_template = AbilitiesTemplate { boards };
create_file("./out/ddd", "boards", boards_template.render().unwrap()).unwrap(); create_file("./out/ddd", "boards", boards_template).unwrap();
}
fn get_js_hash() -> String {
JS_HASH.get_or_init(|| create_hashes("ddd")).to_string()
} }
} }

View File

@ -1,18 +1,14 @@
use std::sync::OnceLock;
use askama::Template; use askama::Template;
use blake3::Hash;
use itertools::Itertools; use itertools::Itertools;
use crate::{ use crate::{
RuntimeModule, RuntimeModule,
common::{Game, enemy::Enemy, materials::MaterialDrops, synthesis::SynthesisData}, common::{Game, enemy::Enemy, materials::MaterialDrops, synthesis::SynthesisData},
create_file, create_hashes, create_file,
}; };
const ENEMIES_PATH: &str = "./input/kh1/enemies"; const ENEMIES_PATH: &str = "./input/kh1/enemies";
const SYNTHESIS_PATH: &str = "./input/kh1/synthesis.toml"; const SYNTHESIS_PATH: &str = "./input/kh1/synthesis.toml";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/kh1/drops.html")] #[template(path = "pages/kh1/drops.html")]
@ -22,6 +18,12 @@ struct DropsTemplate {
pub material_kinds: Vec<String>, pub material_kinds: Vec<String>,
} }
#[derive(Template)]
#[template(path = "pages/kh1/synth.html")]
struct SynthTemplate {
pub data: SynthesisData,
}
pub struct Module; pub struct Module;
impl RuntimeModule for Module { impl RuntimeModule for Module {
@ -38,7 +40,6 @@ impl RuntimeModule for Module {
tracing::info!("Loading synthesis data from {}", SYNTHESIS_PATH); tracing::info!("Loading synthesis data from {}", SYNTHESIS_PATH);
let synth = SynthesisData::new(SYNTHESIS_PATH); let synth = SynthesisData::new(SYNTHESIS_PATH);
dbg!(synth.total_amounts());
tracing::info!("Generating the KH1 drops template"); tracing::info!("Generating the KH1 drops template");
let drops_template = DropsTemplate { let drops_template = DropsTemplate {
@ -47,10 +48,11 @@ impl RuntimeModule for Module {
material_kinds, material_kinds,
}; };
create_file("./out/kh1", "drops", drops_template.render().unwrap()).unwrap(); create_file("./out/kh1", "drops", drops_template).unwrap();
}
fn get_js_hash() -> String { tracing::info!("Generating the KH1 synth template");
JS_HASH.get_or_init(|| create_hashes("kh1")).to_string() let synth_template = SynthTemplate { data: synth };
create_file("./out/kh1", "synth", synth_template).unwrap();
} }
} }

View File

@ -1,17 +1,13 @@
use std::sync::OnceLock;
use askama::Template; use askama::Template;
use blake3::Hash;
use itertools::Itertools; use itertools::Itertools;
use crate::{ use crate::{
RuntimeModule, RuntimeModule,
common::{Game, enemy::Enemy, materials::MaterialDrops}, common::{Game, enemy::Enemy, materials::MaterialDrops},
create_file, create_hashes, create_file,
}; };
const ENEMIES_PATH: &str = "./input/kh2/enemies"; const ENEMIES_PATH: &str = "./input/kh2/enemies";
static JS_HASH: OnceLock<Hash> = OnceLock::new();
#[derive(Template)] #[derive(Template)]
#[template(path = "pages/kh2/drops.html")] #[template(path = "pages/kh2/drops.html")]
@ -42,10 +38,6 @@ impl RuntimeModule for Module {
material_kinds, material_kinds,
}; };
create_file("./out/kh2", "drops", drops_template.render().unwrap()).unwrap(); create_file("./out/kh2", "drops", drops_template).unwrap();
}
fn get_js_hash() -> String {
JS_HASH.get_or_init(|| create_hashes("kh2")).to_string()
} }
} }

View File

@ -1,15 +1,11 @@
use std::sync::OnceLock;
use askama::Template; use askama::Template;
use blake3::Hash;
use food::Recipes; use food::Recipes;
use crate::{RuntimeModule, create_file, create_hashes}; use crate::{RuntimeModule, create_file};
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")]
@ -28,10 +24,6 @@ impl RuntimeModule for Module {
tracing::info!("Generating the KH3 recipes template"); tracing::info!("Generating the KH3 recipes template");
let food_template = RecipesTemplate { recipes }; let food_template = RecipesTemplate { recipes };
create_file("./out/kh3", "food-sim", food_template.render().unwrap()).unwrap(); create_file("./out/kh3", "food-sim", food_template).unwrap();
}
fn get_js_hash() -> String {
JS_HASH.get_or_init(|| create_hashes("kh3")).to_string()
} }
} }

View File

@ -1,6 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::{fs, path::PathBuf}; use std::{collections::HashMap, fs, path::PathBuf, sync::LazyLock};
use askama::Template; use askama::Template;
use blake3::{Hash, Hasher}; use blake3::{Hash, Hasher};
@ -25,11 +25,45 @@ mod kh3;
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ASSETS_FOLDER_PATH: &str = "./public/assets"; pub const ASSETS_FOLDER_PATH: &str = "./public/assets";
pub const SCRIPTS_FOLDER_PATH: &str = "./public/scripts";
pub const STYLES_FOLDER_PATH: &str = "./public/styles";
static FILE_HASHES: LazyLock<HashMap<String, Hash>> = LazyLock::new(|| {
let mut map = HashMap::new();
fn parse_path(path: PathBuf, map: &mut HashMap<String, Hash>) {
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()) {
parse_path(path, map);
continue;
}
let dir = path.parent().unwrap().to_str().unwrap();
let path = path.to_str().unwrap();
let is_module = path.contains("/common");
let mut hasher = Hasher::new();
hash_file(path.to_string().into(), &mut hasher);
if !is_module {
hash_files_in_dir(format!("{dir}/common").into(), &mut hasher);
}
let hash = hasher.finalize();
map.insert(path.to_string(), hash);
}
}
}
parse_path(SCRIPTS_FOLDER_PATH.into(), &mut map);
parse_path(STYLES_FOLDER_PATH.into(), &mut map);
map
});
pub trait RuntimeModule { pub trait RuntimeModule {
fn start_module(); fn start_module();
fn get_js_hash() -> String;
} }
#[derive(Template)] #[derive(Template)]
@ -72,15 +106,14 @@ fn start_module<M: RuntimeModule>() {
M::start_module(); M::start_module();
} }
fn create_hashes(module: &str) -> Hash { fn find_hash(file: &str) -> String {
let mut hasher = Hasher::new(); let map = &*FILE_HASHES;
hash_file(format!("./public/scripts/{module}.js").into(), &mut hasher); let hash = map.get(&format!(".{file}"));
hash_files_in_dir("./public/scripts/modules/common".into(), &mut hasher); if let Some(hash) = hash {
hash_files_in_dir( return format!("{file}?hash={hash}");
format!("./public/scripts/modules/{module}").into(), }
&mut hasher,
); file.to_string()
hasher.finalize()
} }
fn hash_files_in_dir(path: PathBuf, hasher: &mut Hasher) { fn hash_files_in_dir(path: PathBuf, hasher: &mut Hasher) {
@ -103,8 +136,8 @@ fn hash_file(path: PathBuf, hasher: &mut Hasher) {
} }
} }
fn create_file(dir: &str, file: &str, buf: String) -> std::io::Result<()> { fn create_file(dir: &str, file: &str, buf: impl Template) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?; std::fs::create_dir_all(dir)?;
std::fs::write(format!("{}/{}.html", dir, file), buf)?; std::fs::write(format!("{}/{}.html", dir, file), buf.render().unwrap())?;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,7 @@
<head> <head>
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<style>{% include "components/core/style.css"%} </style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/common/base.css") }}"></link>
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>

View File

@ -3,8 +3,11 @@
{% block title %}BBS - Command Melding{% endblock %} {% block title %}BBS - Command Melding{% endblock %}
{% block head %} {% block head %}
<style> {% include "components/bbs/style.css" %}</style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/bbs/melding.css") }}"></link>
<script type="module" src="/public/scripts/bbs.js?v={{Module::get_js_hash()}}"></script> <script
type="module"
src="{{ crate::find_hash("/public/scripts/bbs/melding.js") }}"
></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -3,7 +3,7 @@
{% block title %}DDD - Spirit Boards{% endblock %} {% block title %}DDD - Spirit Boards{% endblock %}
{% block head %} {% block head %}
<style>{% include "components/ddd/style.css" %}</style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/ddd/boards.css") }}"></link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -38,6 +38,7 @@
<h1>Kingdom Hearts I</h1> <h1>Kingdom Hearts I</h1>
<ul> <ul>
<li><a href="./kh1/drops.html">Material Drops</a></li> <li><a href="./kh1/drops.html">Material Drops</a></li>
<li><a href="./kh1/synth.html">Synthesis</a></li>
</ul> </ul>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -4,8 +4,11 @@
{% block title %}KH1 - Drops{% endblock %} {% block title %}KH1 - Drops{% endblock %}
{% block head %} {% block head %}
<style>{% include "components/kh2/style.css" %}</style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/kh1/drops.css") }}"></link>
<script type="module" src="/public/scripts/kh1.js?v={{Module::get_js_hash()}}"></script> <script
type="module"
src="{{ crate::find_hash("/public/scripts/kh1/drops.js") }}"
></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -0,0 +1,45 @@
{% extends "layouts/base.html" %}
{% import "macros/common/macros.html" as macros %}
{% block title %}KH1 - Sythensis{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ crate::find_hash("/public/styles/kh1/synth.css") }}"></link>
<script
type="module"
src="{{ crate::find_hash("/public/scripts/kh1/synth.js") }}"
></script>
{% endblock %}
{% block content %}
<div id="mats"></div>
<div id="recipes">
{% for recipe in data.recipes %}
<div class="recipe-wrapper">
<input
type="checkbox"
id="recipe-{{ loop.index }}"
name="recipe-{{ loop.index }}"
class="recipe"
/>
<label for="recipe-{{ loop.index }}">
<img
src="../public/assets/materials/generic.webp"
width="16"
height="16"
/>{{ recipe.result }}
<ul>
{% for item in recipe.items %}
<li
data-synth-item-name="{{ item.name }}"
data-synth-item-amount="{{ item.amount }}"
>
{{ item.name +}} x{{ item.amount }}
</li>
{% endfor %}
</ul>
</label>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -4,8 +4,11 @@
{% block title %}KH2 - Drops{% endblock %} {% block title %}KH2 - Drops{% endblock %}
{% block head %} {% block head %}
<style>{% include "components/kh2/style.css" %}</style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/kh2/drops.css") }}"></link>
<script type="module" src="/public/scripts/kh2.js?v={{Module::get_js_hash()}}"></script> <script
type="module"
src="{{ crate::find_hash("/public/scripts/kh2/drops.js") }}"
></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -3,8 +3,11 @@
{% block title %}KH3 - Food Simulator{% endblock %} {% block title %}KH3 - Food Simulator{% endblock %}
{% block head %} {% block head %}
<style>{% include "components/kh3/style.css" %}</style> <link rel="stylesheet" href="{{ crate::find_hash("/public/styles/kh3/food-sim.css") }}"></link>
<script type="module" src="/public/scripts/kh3.js?v={{Module::get_js_hash()}}"></script> <script
type="module"
src="{{ crate::find_hash("/public/scripts/kh3/food-sim.js") }}"
></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}