beacon/src/templates.rs

280 lines
11 KiB
Rust

use std::sync::Arc;
use axum::extract::State;
use maud::{html, Markup, PreEscaped, DOCTYPE};
use crate::{
clock,
data::{self, CombinedFish, Filters},
AppState,
};
pub struct ViewData<'a> {
pub state: State<Arc<AppState>>,
pub fish: Vec<&'a CombinedFish<'a>>,
pub caught_fish: Vec<i32>,
pub acc_id: String,
pub filters: Filters,
pub only_list: bool,
}
pub fn layout(content: Markup) -> Markup {
html! {
(DOCTYPE)
html {
head {
title { "Beacon" }
meta name="viewport" content="width=device-width";
meta charset="utf-8";
meta name="description" content="A big fish tracker for FFXIV.";
meta name="author" content="the insects institute";
meta property="og:url" content="https://fish.insects.institute";
meta property="og:title" content="Beacon";
meta property="og:description" content="A big fish tracker for FFXIV.";
link rel="stylesheet" href="/static/style.css";
script src="/static/htmx.js" {}
}
body { (content) }
}
}
}
pub fn main_page(data: ViewData) -> Markup {
let list = html! {
h2.clock { "ET " (clock::get_current_eorzea_date().format("%H:%M")) }
(fish_list(&data))
script src="/static/scripts/dates.js" type="text/javascript" {}
};
let template = html! {
span style="display: none;" id="account-id" { (data.acc_id) }
(header(&data))
div id="list" hx-get="" hx-trigger="every 10s" hx-swap="innerHTML" hx-target="this" hx-on="changeDates" {
(list)
}
script src="/static/scripts/save.js" type="text/javascript" {}
};
if data.only_list {
list
} else {
layout(html! {
main {
(template)
}
})
}
}
pub fn fish_list(data: &ViewData) -> Markup {
html! {
@for fish in data.fish.clone() {
section.fish.up[fish.is_up].alwaysup[fish.is_always_up] {
.title {
div {
@if !data.caught_fish.contains(&(fish.entry.id as i32)) {
form action=(format!("/{}/catch/{}", data.acc_id, fish.entry.id)) method="post" {
button.catch-button type="submit" { (PreEscaped("&check;")) }
}
}
}
div {
h3 { (fish.meta.name_en) }
.subtitle {
"Patch " (fish.entry.patch)
}
}
}
.when {
@if let Some(window) = fish.windows.first() {
@if fish.is_up {
"closes " (window.display_end_time())
} @else {
"opens " (window.display_start_time())
}
br;
@if fish.is_up {
.date data-ts=(clock::to_earth_time(window.start_time + window.duration).timestamp_millis()) {
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
(clock::to_earth_time(window.start_time + window.duration).format("%c %Z"))
}
}
@if let Some(window2) = fish.windows.get(1) {
.date.tiny data-ts=(clock::to_earth_time(window2.start_time).timestamp_millis()) {
"next: "
.inner id=(format!("nextwindow-{}", fish.entry.id)) hx-preserve {
(clock::to_earth_time(window2.start_time).format("%c %Z"))
}
}
}
} @else {
.date data-ts=(clock::to_earth_time(window.start_time).timestamp_millis()) {
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
(clock::to_earth_time(window.start_time).format("%c %Z"))
}
}
}
}
}
.how {
@for item_id in &fish.entry.best_catch_path {
@if let Some(item) = data.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(&data.state.data) {
img.hookset src=(hookset) width="20";
}
@if let Some(tug) = item.get_tug(&data.state.data) {
span.tug { (tug) }
}
}
}
}
@if let Some(hookset) = fish.entry.hookset {
span.catchpath {
img src=(hookset) width="35";
@if let Some(tug) = fish.entry.tug {
span.tug { (tug) }
}
}
}
}
.meta {
@if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() {
div {
@if !fish.is_always_up {
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.start_hour.unwrap())))
" to "
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.end_hour.unwrap())))
} @else {
"always up!"
}
}
}
div { "Rarity: " (format!("{:.2}", fish.rarity * 100.)) "%" }
}
}
}
}
}
pub fn header(data: &ViewData) -> Markup {
html! {
.header {
div {}
.side {
.menu {
span { "Beacon " (env!("CARGO_PKG_VERSION")) }
a href="/changelog" { "Changelog" }
a href="https://git.insects.institute/insects/beacon" { "Source code" }
a href="/logout" { "Log out" }
}
details {
summary { "Filters" }
form {
fieldset {
@if data.filters.non_big_fish {
input name="big" id="big" type="checkbox" checked;
} @else {
input name="big" id="big" type="checkbox";
}
label for="big" { "Include non big fish?" }
}
fieldset {
@if data.filters.include_caught {
input name="caught" id="caught" type="checkbox" checked;
} @else {
input name="caught" id="caught" type="checkbox";
}
label for="caught" { "Include caught fish?" }
}
fieldset {
label for="patches" { "Filter by patch" }
br;
select name="patches" id="patches" multiple {
@for patch in data::PATCHES {
@if data.filters.patches.contains(&patch) {
option value=(patch) selected { (format!("{:.1}", patch)) }
} @else {
option value=(patch) { (format!("{:.1}", patch)) }
}
}
}
}
button type="submit" { "Apply" }
}
}
}
}
}
}
pub fn root() -> Markup {
layout(html! {
h1 { "Beacon" }
p {
"Beacon is a website to track progress for collecting " i { "big fish" } " in Final Fantasy XIV. "
"To use it, you need an " b { "account ID" } ", which serves to remember which fish you've caught. "
"If you have an account ID, you can enter it here, or if you don't, create a new one. Usually, your "
"browser will remember your ID and automatically redirect you, though."
}
form method="post" {
button type="submit" { "Generate a new account ID" }
}
p { "or" }
form method="get" action="/to" {
input name="id" type="text" placeholder="Enter your account ID...";
button type="submit" { "Go to tracker" }
}
h2 { "What is this about?" }
p {
"In Final Fantasy XIV, you can catch a number of elusive and rare fish called big fish. These can be quite tricky to even find -- most of them have a specific time window and only appear during certain weather patterns. This website helps with keeping track of all of that."
}
h2 { "Why not use the other tracker website?" }
p {
"By all means! " a href="https://ff14fish.carbuncleplushy.com" { "The original tracker" } " is a great resource, and functionally superior to this website. "
"I mostly created this because of two reasons:"
}
ol {
li { "Everything is rendered on the server - meaning it doesn't eat away at your system resources (good for laptop players!)" }
li { "Your caught fish are synchronized across devices, and data doesn't get deleted when you clear your browser data" }
}
p {
"These were the two main reasons why I created this. This website couldn't exist without Carbuncle Plushy's tracker -- it uses the same data "
"that has been painstakingly compiled by hand! If you can, please support them."
}
script src="/static/scripts/load.js" type="text/javascript" {}
})
}
pub fn new_account(id: String) -> Markup {
layout(html! {
p {
"Your new account ID has been created. Plase save it in a place you remember, like a password manager. If you lose it, you will be unable to "
"access your account."
}
h1 id="account-id" { (id) }
a href=(format!("/{}", id)) { "Click here to proceed to the tracker" }
script src="/static/scripts/save.js" type="text/javascript" {}
})
}