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 {
|
impl Display for Card {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
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 let Some(level) = self.level {
|
||||||
if self.card_type.contains("XYZ") {
|
if self.card_type.contains("XYZ") {
|
||||||
f.write_str("Rank ")?;
|
f.write_str("Rank ")?;
|
||||||
@ -44,9 +44,7 @@ impl Display for Card {
|
|||||||
if let Some(attr) = &self.attribute {
|
if let Some(attr) = &self.attribute {
|
||||||
write!(f, "{attr}/")?;
|
write!(f, "{attr}/")?;
|
||||||
}
|
}
|
||||||
write!(f, "{} {})", self.r#type, self.card_type)?;
|
write!(f, "{} {}", self.r#type, self.card_type)?;
|
||||||
f.write_str("<br/>")?;
|
|
||||||
f.write_str(&self.text)?;
|
|
||||||
if self.card_type.contains(&String::from("Monster")) {
|
if self.card_type.contains(&String::from("Monster")) {
|
||||||
f.write_str("<br/>")?;
|
f.write_str("<br/>")?;
|
||||||
match (self.atk, self.def) {
|
match (self.atk, self.def) {
|
||||||
@ -57,6 +55,8 @@ impl Display for Card {
|
|||||||
(None, None) => write!(f, "? ATK / ? DEF")?,
|
(None, None) => write!(f, "? ATK / ? DEF")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
f.write_str("</em><br/>")?;
|
||||||
|
f.write_str(&self.text)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
src/main.rs
77
src/main.rs
@ -28,7 +28,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
// tap these so they’re initialized
|
// tap these so they’re initialized
|
||||||
let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2;
|
let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2;
|
||||||
println!("Read {num_cards} cards in {:?}", now.elapsed());
|
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)]
|
#[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::Left(web::Query(Query { q }))) => Some(q),
|
||||||
Some(Either::Right(web::Form(Query { q }))) => Some(q),
|
Some(Either::Right(web::Form(Query { q }))) => Some(q),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
}
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
let mut res = String::with_capacity(10_000);
|
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!(
|
write!(
|
||||||
res,
|
res,
|
||||||
r#"
|
r#"
|
||||||
<form action="/">
|
<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="🔍">
|
<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>"#,
|
</form>"#,
|
||||||
match &q {
|
match &query {
|
||||||
Some(q) => q,
|
Some(q) => q,
|
||||||
None => "",
|
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 now = Instant::now();
|
||||||
let matches: Vec<&Card> = SEARCH_CARDS
|
let matches: Vec<&Card> = SEARCH_CARDS
|
||||||
.iter()
|
.iter()
|
||||||
@ -69,18 +111,25 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Resul
|
|||||||
.collect();
|
.collect();
|
||||||
write!(
|
write!(
|
||||||
res,
|
res,
|
||||||
"<span class=\"meta\">Showing {} results where {} (took {:?})</span><hr/>",
|
"<span class=\"meta\">Showing {} results where {} (took {:?})</span>",
|
||||||
matches.len(),
|
matches.len(),
|
||||||
query.iter().map(|(f, _)| f.to_string()).join(" and "),
|
query.iter().map(|(f, _)| f.to_string()).join(" and "),
|
||||||
now.elapsed()
|
now.elapsed()
|
||||||
)?;
|
)?;
|
||||||
|
if matches.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
res.push_str("<table>");
|
||||||
for card in matches {
|
for card in matches {
|
||||||
res.push_str(&card.to_string());
|
write!(
|
||||||
res.push_str("<hr/>");
|
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>")?;
|
Ok(())
|
||||||
} else {
|
|
||||||
res.write_str("Enter a query above to search")?;
|
|
||||||
}
|
}
|
||||||
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
|
|
||||||
|
fn finish_document(res: &mut String) {
|
||||||
|
res.push_str("</body></html>")
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
padding-top: 2em;
|
padding: 2em 0;
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-family: 'Lato', 'Segoe UI', sans-serif;
|
font-family: 'Lato', 'Segoe UI', sans-serif;
|
||||||
@ -52,9 +52,63 @@ form > *:focus {
|
|||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--hl);
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border-color: var(--hl);
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
Loading…
Reference in New Issue
Block a user