add filtering support
This commit is contained in:
parent
e564e87c94
commit
77eeaef8cc
6 changed files with 179 additions and 13 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -123,6 +123,30 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_path_to_error",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
|
@ -167,6 +191,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"chrono",
|
||||
"chrono-humanize",
|
||||
"dotenvy",
|
||||
|
@ -1470,6 +1495,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_html_form"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
axum = { version = "0.8.1", features = ["macros"] }
|
||||
axum-extra = { version = "0.10.0", features = ["query"] }
|
||||
chrono = "0.4.39"
|
||||
chrono-humanize = "0.2.3"
|
||||
dotenvy = "0.15.7"
|
||||
|
|
57
src/data.rs
57
src/data.rs
|
@ -8,9 +8,14 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::{
|
||||
clock,
|
||||
forecast::{round_to_last_weather_time, Forecast, ForecastSet, Rate},
|
||||
FilterQuery,
|
||||
};
|
||||
|
||||
const DATA: &str = include_str!("../data.json");
|
||||
pub const PATCHES: [f32; 32] = [
|
||||
2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 5.0,
|
||||
5.1, 5.2, 5.3, 5.4, 5.5, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 7.0, 7.1,
|
||||
];
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Data {
|
||||
|
@ -483,6 +488,58 @@ impl Data {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Filters {
|
||||
/// Display caught fish.
|
||||
pub include_caught: bool,
|
||||
/// Display non-big fish.
|
||||
pub non_big_fish: bool,
|
||||
/// Filter by patch.
|
||||
pub patches: Vec<f32>,
|
||||
}
|
||||
|
||||
impl Filters {
|
||||
pub fn from_query(query: FilterQuery) -> Self {
|
||||
Self {
|
||||
// As caught fish are filtered by default, setting the query parameter un-filters them.
|
||||
include_caught: query.caught.is_some(),
|
||||
non_big_fish: query.big.is_some(),
|
||||
patches: query.patches.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(elided_named_lifetimes)]
|
||||
pub fn filter<'a>(
|
||||
&'a self,
|
||||
fish: Vec<&'a CombinedFish>,
|
||||
caught_fish_ids: Vec<i32>,
|
||||
) -> Vec<&'a CombinedFish> {
|
||||
fish.into_iter()
|
||||
.filter(|fish| {
|
||||
let f_caught = if self.include_caught {
|
||||
true
|
||||
} else {
|
||||
!caught_fish_ids.contains(&(fish.entry.id as i32))
|
||||
};
|
||||
|
||||
let f_big = if self.non_big_fish {
|
||||
true
|
||||
} else {
|
||||
fish.entry.big_fish
|
||||
};
|
||||
|
||||
let f_patch = if !self.patches.is_empty() {
|
||||
self.patches.contains(&fish.entry.patch)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
f_caught && f_patch && f_big
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_weather_name(data: &Data, id: u32) -> &str {
|
||||
&data.db_data.weather_types.get(&id).unwrap().name_en
|
||||
}
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -7,9 +7,10 @@ use axum::{
|
|||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use data::Data;
|
||||
use data::{Data, Filters};
|
||||
use maud::{html, Markup};
|
||||
use nanoid::nanoid;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
|
||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
@ -56,16 +57,25 @@ async fn new_account_handler(state: State<Arc<AppState>>) -> Result<Markup, AppE
|
|||
Ok(templates::new_account(id))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FilterQuery {
|
||||
pub caught: Option<()>,
|
||||
pub big: Option<()>,
|
||||
pub patches: Option<Vec<f32>>,
|
||||
}
|
||||
|
||||
async fn main_handler(
|
||||
state: State<Arc<AppState>>,
|
||||
Path(acc_id): Path<String>,
|
||||
axum_extra::extract::Query(query): axum_extra::extract::Query<FilterQuery>,
|
||||
) -> Result<Response, AppError> {
|
||||
let exists = db::get_account(&acc_id, &state.pool).await?;
|
||||
if !exists {
|
||||
return Ok(html! { "The provided account ID doesn't eixst." }.into_response());
|
||||
}
|
||||
let caught_fish = db::get_caught_fish(&acc_id, &state.pool).await?;
|
||||
Ok(templates::main_page(state, caught_fish, acc_id).into_response())
|
||||
let filters = Filters::from_query(query);
|
||||
Ok(templates::main_page(state, caught_fish, acc_id, &filters).into_response())
|
||||
}
|
||||
|
||||
async fn insert_cf_handler(
|
||||
|
|
|
@ -3,7 +3,11 @@ use std::sync::Arc;
|
|||
use axum::extract::State;
|
||||
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
||||
|
||||
use crate::{clock, data::CombinedFish, AppState};
|
||||
use crate::{
|
||||
clock,
|
||||
data::{self, CombinedFish, Filters},
|
||||
AppState,
|
||||
};
|
||||
|
||||
pub fn layout(content: Markup) -> Markup {
|
||||
html! {
|
||||
|
@ -21,16 +25,16 @@ pub fn layout(content: Markup) -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn main_page(state: State<Arc<AppState>>, caught_fish: Vec<i32>, acc_id: String) -> Markup {
|
||||
pub fn main_page(
|
||||
state: State<Arc<AppState>>,
|
||||
caught_fish: Vec<i32>,
|
||||
acc_id: String,
|
||||
filters: &Filters,
|
||||
) -> Markup {
|
||||
let meta = state.data.fish_with_meta();
|
||||
let mut values: Vec<&CombinedFish> = meta
|
||||
.values()
|
||||
.filter(|f| {
|
||||
let is_big_fish = f.entry.big_fish;
|
||||
let is_caught = caught_fish.contains(&(f.entry.id as i32));
|
||||
is_big_fish && !is_caught
|
||||
})
|
||||
.collect();
|
||||
let mut values: Vec<&CombinedFish> = filters.filter(meta.values().collect(), caught_fish);
|
||||
dbg!(&filters);
|
||||
|
||||
values.sort_by(|afish, bfish| {
|
||||
bfish
|
||||
.is_up
|
||||
|
@ -53,7 +57,49 @@ pub fn main_page(state: State<Arc<AppState>>, caught_fish: Vec<i32>, acc_id: Str
|
|||
// }
|
||||
// });
|
||||
let template = html! {
|
||||
h1 { "Hello! Current ET: " (clock::get_current_eorzea_date().format("%H:%M")) }
|
||||
.header {
|
||||
h1 { "Hello! Current ET: " (clock::get_current_eorzea_date().format("%H:%M")) }
|
||||
.side {
|
||||
details {
|
||||
summary { "Filters" }
|
||||
form {
|
||||
fieldset {
|
||||
@if 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 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 filters.patches.contains(&patch) {
|
||||
option value=(patch) selected { (format!("{:.1}", patch)) }
|
||||
} @else {
|
||||
option value=(patch) { (format!("{:.1}", patch)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button type="submit" { "Apply" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@for fish in values {
|
||||
section.up[fish.is_up].alwaysup[fish.is_always_up] {
|
||||
.title {
|
||||
|
|
|
@ -11,6 +11,20 @@ section {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
|
|
Loading…
Add table
Reference in a new issue