add help page
This commit is contained in:
parent
27adb0f196
commit
51411e3d2b
@ -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, 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 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 ")?;
|
||||||
|
28
src/main.rs
28
src/main.rs
@ -28,7 +28,10 @@ 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).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)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -37,6 +40,13 @@ struct Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HEADER: &str = include_str!("../static/header.html");
|
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>
|
||||||
|
|
||||||
|
<a href="/help">Query Syntax</a>
|
||||||
|
</div>
|
||||||
|
</body></html>"#;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
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))
|
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>> {
|
async fn card_info(card_id: web::Path<usize>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
||||||
let mut res = String::with_capacity(2_000);
|
let mut res = String::with_capacity(2_000);
|
||||||
res.push_str(HEADER);
|
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))
|
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 {
|
fn render_searchbox(res: &mut String, query: &Option<String>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
res,
|
res,
|
||||||
@ -123,7 +143,7 @@ fn render_results(res: &mut String, query: &str) -> Result<(), Box<dyn std::erro
|
|||||||
for card in matches {
|
for card in matches {
|
||||||
write!(
|
write!(
|
||||||
res,
|
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
|
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) {
|
fn finish_document(res: &mut String) {
|
||||||
res.push_str("</body></html>")
|
res.push_str(FOOTER)
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,7 @@ impl FromStr for Field {
|
|||||||
"c" | "class" => Self::Class,
|
"c" | "class" => Self::Class,
|
||||||
"o" | "eff" | "text" | "effect" | "e" => Self::Text,
|
"o" | "eff" | "text" | "effect" | "e" => Self::Text,
|
||||||
"lr" | "linkrating" => Self::LinkRating,
|
"lr" | "linkrating" => Self::LinkRating,
|
||||||
|
"name" => Self::Name,
|
||||||
_ => Err(s.to_string())?,
|
_ => Err(s.to_string())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,11 @@ body {
|
|||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "Hack", "Fira Code", "Courier New", monospace;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
color: var(--fg-dim);
|
color: var(--fg-dim);
|
||||||
@ -76,7 +81,7 @@ td:nth-child(2) {
|
|||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h2 {
|
||||||
margin-block-end: 0;
|
margin-block-end: 0;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
@ -109,6 +114,17 @@ h2 {
|
|||||||
display: table;
|
display: table;
|
||||||
clear: both;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
35
static/help.html
Normal file
35
static/help.html
Normal 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><</code>, <code>></code>, <code><=</code>, <code>>=</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>=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>
|
Loading…
Reference in New Issue
Block a user