add help page

This commit is contained in:
kageru 2023-01-30 17:27:44 +01:00
parent 27adb0f196
commit 51411e3d2b
5 changed files with 78 additions and 6 deletions

View File

@ -30,7 +30,7 @@ pub struct Card {
impl Display for Card {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#"<h2><a href="/{}">{}</a></h2><br/><em>"#, &self.id, &self.name)?;
write!(f, r#"<h2><a href="/card/{}">{}</a></h2><br/><em>"#, &self.id, &self.name)?;
if let Some(level) = self.level {
if self.card_type.contains("XYZ") {
f.write_str("Rank ")?;

View File

@ -28,7 +28,10 @@ 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).service(card_info)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await
HttpServer::new(|| App::new().service(search).service(card_info).service(help))
.bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?
.run()
.await
}
#[derive(Debug, Deserialize)]
@ -37,6 +40,13 @@ struct Query {
}
const HEADER: &str = include_str!("../static/header.html");
const HELP_CONTENT: &str = include_str!("../static/help.html");
const FOOTER: &str = r#"<div id="bottom">
<a href="/">Home</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/help">Query Syntax</a>
</div>
</body></html>"#;
#[get("/")]
async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
@ -57,7 +67,7 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Resul
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
}
#[get("/{id}")]
#[get("/card/{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);
@ -80,6 +90,16 @@ async fn card_info(card_id: web::Path<usize>) -> Result<HttpResponse, Box<dyn st
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
}
#[get("/help")]
async fn help() -> Result<HttpResponse, Box<dyn std::error::Error>> {
let mut res = String::with_capacity(HEADER.len() + HELP_CONTENT.len() + FOOTER.len() + 250);
res.push_str(HEADER);
render_searchbox(&mut res, &None)?;
res.push_str(HELP_CONTENT);
res.push_str(FOOTER);
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
}
fn render_searchbox(res: &mut String, query: &Option<String>) -> std::fmt::Result {
write!(
res,
@ -123,7 +143,7 @@ fn render_results(res: &mut String, query: &str) -> Result<(), Box<dyn std::erro
for card in matches {
write!(
res,
r#"<tr><td>{card}</td><td><a href="/{}"><img src="http://localhost:80/img/{}.jpg" class="thumb"/></a></td></tr>"#,
r#"<tr><td>{card}</td><td><a href="/card/{}"><img src="http://localhost:80/img/{}.jpg" class="thumb"/></a></td></tr>"#,
card.id, card.id
)?;
}
@ -131,5 +151,5 @@ fn render_results(res: &mut String, query: &str) -> Result<(), Box<dyn std::erro
}
fn finish_document(res: &mut String) {
res.push_str("</body></html>")
res.push_str(FOOTER)
}

View File

@ -101,6 +101,7 @@ impl FromStr for Field {
"c" | "class" => Self::Class,
"o" | "eff" | "text" | "effect" | "e" => Self::Text,
"lr" | "linkrating" => Self::LinkRating,
"name" => Self::Name,
_ => Err(s.to_string())?,
})
}

View File

@ -27,6 +27,11 @@ body {
text-align: justify;
}
code {
font-family: "Hack", "Fira Code", "Courier New", monospace;
background-color: #2a2a2a;
}
.meta {
font-size: 75%;
color: var(--fg-dim);
@ -76,7 +81,7 @@ td:nth-child(2) {
width: 20%;
}
h1, h2 {
h2 {
margin-block-end: 0;
font-weight: normal;
}
@ -109,6 +114,17 @@ h2 {
display: table;
clear: both;
}
/* little floaty thing at the bottom with links to home and help */
#bottom {
position: fixed;
bottom: 3em;
right: 10%;
background-color: var(--bg2);
z-index: 2;
padding: 1em;
border-radius: 1em;
}
</style>
</head>
<body>

35
static/help.html Normal file
View File

@ -0,0 +1,35 @@
<h1>Query Syntax</h1>
The syntax is heavily inspired by <a href="https://scryfall.com/docs/syntax">Scryfall</a> with some changes and a lot fewer features.<br/>
You can filter different characteristics of a card and combine multiple filters into one search. See below for examples.
<h2>Search fields</h2>
Currently supported search fields are:
<ul>
<li><code>atk</code> and <code>def</code>.</li>
<li>The <code>level</code> (or <code>l</code>) of a monster. Note that the search does not distinguish between level and rank, so <a href="/?q=l%3A4"><code>l:4</code></a> will return all monsters that are either level 4 or rank 4.</li>
<li>The <code>linkrating</code> (or <code>lr</code>) of a monster.</li>
<li>The <code>class</code> (or <code>c</code>) which you might call card type. Since “type” already means something else, the search uses <code>class</code> for “Spell”, “Trap”, “Effect”, “XYZ”, etc., so <a href="/?q=c%3Alink"><code>c:link</code></a> will return all link monsters.</li>
<li>The <code>type</code> (or <code>t</code>) of a card (this is “Warrior”, “Pyro”, “Insect”, etc. for monsters, but also “quick-play”, “counter”, or “normal” for Spells/Traps).</li>
<li>The <code>attribute</code> (or <code>attr</code> or <code>a</code>) of a card. This is “Light”, “Dark”, “Earth”, etc.</li>
<li>The <code>text</code> (or <code>effect</code>, <code>eff</code>, <code>e</code>, or <code>o</code>) of a card. This is either the effect or flavor text (for normal monsters). For pendulum cards, this searches in both pendulum and monster effects. The <code>o</code> alias is to help my muscle memory coming from Scryfall.</li>
</ul>
Anything not associated with a search field is interpreted as a search in the card name, so <a href="/?q=l%3A4+utopia"><code>l:4 utopia</code></a> will show all level/rank 4 monsters with “Utopia” in their name.<br/>
If your search contains spaces (e.g. searching for an effect that says “destroy that target”), the text must be quoted like <code>effect:"destroy that target"</code>.
<br/><br/>
Note that all fields are case-insensitive, so <code>class:NORMAL</code> is the same as <code>class:Normal</code> or <code>class:normal</code>.
<h2>Search operators</h2>
The following search operators are supported:
<ul>
<li>Equality (<code>:</code>, <code>=</code>, or <code>==</code>) checks if the value is equal to your search. For text fields, this checks if your search is contained in the field, so <a href="/?q=effect%3Abanish"><code>effect:banish</code></a> will show all cards that have the word “banish” anywhere in their text.</li>
<li>Inequality (<code>!=</code>) checks if the value is not equal to your search. For text fields, this return cards that do not contain the word you searched.</li>
<li>Comparisons (<code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>) check if the value is less than, greater than, less than or equal, and greater than or equal to your search. <a href="/?q=atk%3E%3D4000"><code>atk&gt;=4000</code></a> will show all cards with an ATK of at least 4000. These operators do not work for text fields.</li>
</ul>
<h2>Examples</h2>
<ul>
<li>All Fire monsters with exactly 200 DEF: <a href="/?q=a%3Afire+def%3A200"><code>a:fire def:200</code></a></li>
<li>All “Blue-eyes” fusion monsters except the ones that are level 12: <a href="/?q=c%3Afusion+l%21%3D12+blue-eyes"><code>c:fusion l!=12 blue-eyes</code></a></li>
<li>All Synchro monsters that are Dark attribute, level 5 or higher, and have exactly 2200 ATK: <a href="/?q=c%3Asynchro+a%3Adark+l%3E%3D5+atk%3A2200"><code>c:synchro a:dark l>=5 atk:2200</code></a></li>
<li>All counter traps that can negate summons: <a href="/?q=c%3Atrap+t%3Acounter+e%3A%22negate+the+summon%22"><code>c:trap t:counter e:"negate the summon"</code></a></li>
</ul>