add card images and detail view
This commit is contained in:
parent
78975b00af
commit
27adb0f196
@ -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#"<h2><a href="/{}">{}</a></h2><br/><em>"#, &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("<br/>")?;
|
||||
f.write_str(&self.text)?;
|
||||
write!(f, "{} {}", self.r#type, self.card_type)?;
|
||||
if self.card_type.contains(&String::from("Monster")) {
|
||||
f.write_str("<br/>")?;
|
||||
match (self.atk, self.def) {
|
||||
@ -57,6 +55,8 @@ impl Display for Card {
|
||||
(None, None) => write!(f, "? ATK / ? DEF")?,
|
||||
}
|
||||
}
|
||||
f.write_str("</em><br/>")?;
|
||||
f.write_str(&self.text)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
79
src/main.rs
79
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,22 +44,64 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> 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<usize>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
||||
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#"
|
||||
<div class="row">
|
||||
<div class="column left">{card}</div>
|
||||
<div class="column right"><img style="width: 100%;" src="http://localhost:80/img/{}.jpg"/></div>
|
||||
</div>"#,
|
||||
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<String>) -> std::fmt::Result {
|
||||
write!(
|
||||
res,
|
||||
r#"
|
||||
<form action="/">
|
||||
<input type="text" name="q" id="searchbox" placeholder="Enter query (e.g. l:5 c:synchro atk>2000)" value="{}"><input type="submit" id="submit" value="🔍">
|
||||
</form>"#,
|
||||
match &q {
|
||||
match &query {
|
||||
Some(q) => q,
|
||||
None => "",
|
||||
}
|
||||
)?;
|
||||
if let Some(q) = q {
|
||||
let query = parser::parse_filters(&q)?;
|
||||
)
|
||||
}
|
||||
|
||||
fn render_results(res: &mut String, query: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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()
|
||||
@ -69,18 +111,25 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Resul
|
||||
.collect();
|
||||
write!(
|
||||
res,
|
||||
"<span class=\"meta\">Showing {} results where {} (took {:?})</span><hr/>",
|
||||
"<span class=\"meta\">Showing {} results where {} (took {:?})</span>",
|
||||
matches.len(),
|
||||
query.iter().map(|(f, _)| f.to_string()).join(" and "),
|
||||
now.elapsed()
|
||||
)?;
|
||||
if matches.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
res.push_str("<table>");
|
||||
for card in matches {
|
||||
res.push_str(&card.to_string());
|
||||
res.push_str("<hr/>");
|
||||
write!(
|
||||
res,
|
||||
r#"<tr><td>{card}</td><td><a href="/{}"><img src="http://localhost:80/img/{}.jpg" class="thumb"/></a></td></tr>"#,
|
||||
card.id, card.id
|
||||
)?;
|
||||
}
|
||||
write!(res, "</body></html>")?;
|
||||
} 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("</body></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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
Loading…
Reference in New Issue
Block a user