From 27adb0f19661de600753a6c515da17a74a0199b7 Mon Sep 17 00:00:00 2001 From: kageru Date: Mon, 30 Jan 2023 15:57:08 +0100 Subject: [PATCH] add card images and detail view --- src/data.rs | 8 ++-- src/main.rs | 99 ++++++++++++++++++++++++++++++++++------------ static/header.html | 56 +++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 30 deletions(-) diff --git a/src/data.rs b/src/data.rs index ac52e94..7bbc210 100644 --- a/src/data.rs +++ b/src/data.rs @@ -30,7 +30,7 @@ pub struct Card { impl Display for Card { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} (", &self.name)?; + write!(f, r#"

{}


"#, &self.id, &self.name)?; if let Some(level) = self.level { if self.card_type.contains("XYZ") { f.write_str("Rank ")?; @@ -44,9 +44,7 @@ impl Display for Card { if let Some(attr) = &self.attribute { write!(f, "{attr}/")?; } - write!(f, "{} {})", self.r#type, self.card_type)?; - f.write_str("
")?; - f.write_str(&self.text)?; + write!(f, "{} {}", self.r#type, self.card_type)?; if self.card_type.contains(&String::from("Monster")) { f.write_str("
")?; match (self.atk, self.def) { @@ -57,6 +55,8 @@ impl Display for Card { (None, None) => write!(f, "? ATK / ? DEF")?, } } + f.write_str("

")?; + f.write_str(&self.text)?; Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 693f494..c2819d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ async fn main() -> std::io::Result<()> { // tap these so they’re initialized let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2; println!("Read {num_cards} cards in {:?}", now.elapsed()); - HttpServer::new(|| App::new().service(search)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await + HttpServer::new(|| App::new().service(search).service(card_info)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await } #[derive(Debug, Deserialize)] @@ -44,43 +44,92 @@ async fn search(q: Option, web::Form>>) -> Resul Some(Either::Left(web::Query(Query { q }))) => Some(q), Some(Either::Right(web::Form(Query { q }))) => Some(q), None => None, - }; + } + .filter(|s| !s.is_empty()); let mut res = String::with_capacity(10_000); - res.write_str(HEADER)?; + res.push_str(HEADER); + render_searchbox(&mut res, &q)?; + match q { + Some(q) => render_results(&mut res, &q)?, + None => res.push_str("Enter a query above to search"), + } + finish_document(&mut res); + Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) +} + +#[get("/{id}")] +async fn card_info(card_id: web::Path) -> Result> { + let mut res = String::with_capacity(2_000); + res.push_str(HEADER); + render_searchbox(&mut res, &None)?; + match CARDS_BY_ID.get(&card_id) { + Some(card) => { + res.push_str(r#""#); + write!( + res, + r#" +
+
{card}
+
+
"#, + card.id, + )?; + } + None => res.push_str("Card not found"), + } + Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) +} + +fn render_searchbox(res: &mut String, query: &Option) -> std::fmt::Result { write!( res, r#"
"#, - match &q { + match &query { Some(q) => q, None => "", } + ) +} + +fn render_results(res: &mut String, query: &str) -> Result<(), Box> { + let query = match parser::parse_filters(query) { + Ok(q) => q, + Err(e) => { + write!(res, "Could not parse query: {e:?}")?; + return Ok(()); + } + }; + let now = Instant::now(); + let matches: Vec<&Card> = SEARCH_CARDS + .iter() + .filter(|card| query.iter().all(|(_, q)| q(card))) + .map(|c| CARDS_BY_ID.get(&c.id).unwrap()) + .take(RESULT_LIMIT) + .collect(); + write!( + res, + "Showing {} results where {} (took {:?})", + matches.len(), + query.iter().map(|(f, _)| f.to_string()).join(" and "), + now.elapsed() )?; - if let Some(q) = q { - let query = parser::parse_filters(&q)?; - let now = Instant::now(); - let matches: Vec<&Card> = SEARCH_CARDS - .iter() - .filter(|card| query.iter().all(|(_, q)| q(card))) - .map(|c| CARDS_BY_ID.get(&c.id).unwrap()) - .take(RESULT_LIMIT) - .collect(); + if matches.is_empty() { + return Ok(()); + } + res.push_str(""); + for card in matches { write!( res, - "Showing {} results where {} (took {:?})
", - matches.len(), - query.iter().map(|(f, _)| f.to_string()).join(" and "), - now.elapsed() + r#""#, + card.id, card.id )?; - for card in matches { - res.push_str(&card.to_string()); - res.push_str("
"); - } - write!(res, "")?; - } else { - res.write_str("Enter a query above to search")?; } - Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) + Ok(()) +} + +fn finish_document(res: &mut String) { + res.push_str("") } diff --git a/static/header.html b/static/header.html index a1074bc..6e2ee02 100644 --- a/static/header.html +++ b/static/header.html @@ -10,7 +10,7 @@ } html { - padding-top: 2em; + padding: 2em 0; background-color: var(--bg); color: var(--fg); font-family: 'Lato', 'Segoe UI', sans-serif; @@ -52,9 +52,63 @@ form > *:focus { width: 10%; } +a { + color: var(--hl); +} + hr { border-color: var(--hl); } + +table { + width: 100%; +} +td { + border-bottom: 2px solid var(--hl); + padding: 0.5em 0; + text-align: justify; +} +td:first-child { + width: 80%; + padding-right: 1em; +} +td:nth-child(2) { + width: 20%; +} + +h1, h2 { + margin-block-end: 0; + font-weight: normal; +} +h2 { + font-size: 120%; +} + +.thumb { + max-height: 250px; +} + +.column { + float: left; + margin-bottom: 2em; +} +.left { + width: 60%; + padding-right: 1em; +} +.right { + width: 35%; +} +@media screen and (max-width: 680px) { + .column { + width: 100%; + } +} +.row:after { + content: ""; + display: table; + clear: both; +}
{card}