From dc3f955b1b5dc3d448585f6186811678a99d4d26 Mon Sep 17 00:00:00 2001 From: insects Date: Wed, 5 Feb 2025 22:32:23 +0100 Subject: [PATCH] indicate best catch path --- src/data.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++-- src/templates.rs | 35 ++++++++++++++++++++-- static/style.css | 44 +++++++++++++++++++++++++-- 3 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/data.rs b/src/data.rs index 00740d3..c8f752f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,9 +1,12 @@ -use std::collections::HashMap; +use core::fmt; +use std::{ + collections::HashMap, + fmt::{write, Display}, +}; use chrono::{DateTime, Datelike, Duration, Timelike, Utc}; use chrono_humanize::HumanTime; use serde::{Deserialize, Serialize}; -use serde_json::map::Entry; use crate::{ clock, @@ -32,6 +35,8 @@ pub struct SubData { pub fishing_spots: HashMap, #[serde(alias = "WEATHER_TYPES")] pub weather_types: HashMap, + #[serde(alias = "ITEMS")] + pub items: HashMap, } #[derive(Serialize, Deserialize, Debug, Default)] @@ -51,6 +56,75 @@ pub struct FishEntry { pub big_fish: bool, pub weather_set: Vec, pub previous_weather_set: Vec, + pub hookset: Option, + pub tug: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)] +pub enum Hookset { + #[default] + Precision, + Powerful, +} + +impl Display for Hookset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + let string = match self { + Self::Precision => { + "https://v2.xivapi.com/api/asset?path=ui/icon/001000/001116.tex&format=png" + } + Self::Powerful => { + "https://v2.xivapi.com/api/asset?path=ui/icon/001000/001115.tex&format=png" + } + }; + write!(f, "{}", string) + } +} + +impl Display for Tug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Light => write!(f, "!"), + Self::Medium => write!(f, "!!"), + Self::Heavy => write!(f, "!!!"), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum Tug { + #[default] + Light, + Medium, + Heavy, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Item { + #[serde(alias = "_id")] + pub id: u32, + pub name_en: String, + pub icon: String, +} + +impl Item { + pub fn get_icon_url(&self) -> String { + let mut icon_cat = self.icon.clone(); + icon_cat.replace_range(3..6, "000"); + format!( + "https://v2.xivapi.com/api/asset?path=ui/icon/{}/{}.tex&format=png", + icon_cat, self.icon + ) + } + + pub fn get_hookset(&self, data: &Data) -> Option { + data.db_data.fish.get(&self.id).and_then(|f| f.hookset) + } + + pub fn get_tug(&self, data: &Data) -> Option { + data.db_data.fish.get(&self.id).and_then(|f| f.tug) + } } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/templates.rs b/src/templates.rs index e19d8bd..d412126 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,7 +1,6 @@ use std::{cmp::Ordering, sync::Arc}; use axum::extract::State; -use chrono::Duration; use maud::{html, Markup, DOCTYPE}; use crate::{clock, data::CombinedFish, AppState}; @@ -33,10 +32,23 @@ pub fn main_page(state: State>, with_layout: bool) -> Markup { .then(bfish.rarity.total_cmp(&afish.rarity).reverse()) .then(bfish.meta.name_en.cmp(&afish.meta.name_en)) }); + // values.sort_by(|afish, bfish| { + // if !afish.is_up && !bfish.is_up && !afish.windows.is_empty() && !bfish.windows.is_empty() { + // bfish + // .windows + // .first() + // .unwrap() + // .start_time + // .cmp(&afish.windows.first().unwrap().start_time) + // .reverse() + // } else { + // Ordering::Equal + // } + // }); let template = html! { h1 { "Hello! Current ET: " (clock::get_current_eorzea_date().format("%H:%M")) } @for fish in values { - section.up[fish.is_up || fish.is_always_up].alwaysup[fish.is_always_up] { + section.up[fish.is_up].alwaysup[fish.is_always_up] { .title { h3 { (fish.meta.name_en) } .subtitle { @@ -52,6 +64,23 @@ pub fn main_page(state: State>, with_layout: bool) -> Markup { } } } + .how { + @for item_id in &fish.entry.best_catch_path { + @if let Some(item) = state.data.db_data.items.get(item_id) { + span.catchpath title=(item.name_en) { + img src=(item.get_icon_url()) width="35"; + + @if let Some(hookset) = item.get_hookset(&state.data) { + img.hookset src=(hookset) width="20"; + } + + @if let Some(tug) = item.get_tug(&state.data) { + span.tug { (tug) } + } + } + } + } + } .meta { @if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() { div { @@ -72,7 +101,7 @@ pub fn main_page(state: State>, with_layout: bool) -> Markup { if with_layout { layout(html! { - main hx-get="/" hx-trigger="every 3s" { + main hx-get="/" hx-trigger="every 10s" { (template) } }) diff --git a/static/style.css b/static/style.css index 4d5600d..473599e 100644 --- a/static/style.css +++ b/static/style.css @@ -6,7 +6,7 @@ body { section { margin-bottom: 5px; display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr 1fr; padding: 0 10px; align-items: center; } @@ -30,7 +30,8 @@ section { text-align: end; } -.when { +.when, +.how { text-align: center; } @@ -52,3 +53,42 @@ section.alwaysup { ); background-size: 50px 50px; } + +/* .catchpath:not(:first-of-type) { + margin-left: 5px; +} */ + +.catchpath { + align-items: center; + position: relative; +} + +.catchpath img { + vertical-align: middle; +} + +.hookset { + position: absolute; + top: -10px; + right: -10px; +} + +.tug { + position: absolute; + background-color: black; + color: white; + bottom: -10px; + right: -10px; + padding: 2px; + font-size: 10px; + font-weight: bolder; + font-family: monospace; +} + +.catchpath:not(:first-of-type)::before { + content: ">"; + display: inline-block; + margin: 0 5px; + vertical-align: middle; + color: gray; +}