Render handout page on server
All checks were successful
CI / Check typos (push) Successful in 22s
CI / Check links (push) Successful in 23s
CI / Clippy (push) Successful in 1m5s
CI / Build and test (push) Successful in 1m12s
CI / Build container (push) Successful in 1m37s
CI / Deploy on waypoint (push) Successful in 45s

This commit is contained in:
2025-11-04 19:28:21 -08:00
parent 62a3da195f
commit 7afc0b2a29
11 changed files with 609 additions and 365 deletions

View File

@@ -0,0 +1,173 @@
use std::time::Instant;
use assetserver::Asset;
use chrono::TimeDelta;
use maud::{Markup, PreEscaped, html};
use serde::Deserialize;
use tracing::{debug, warn};
use crate::{
components::{
md::Markdown,
misc::{Backlinks, FarLink},
},
page::{Page, PageMetadata},
routes::assets::Image_Icon,
};
#[derive(Debug, Deserialize)]
struct HandoutEntry {
title: String,
group: String,
handout: String,
solutions: Option<String>,
}
async fn get_index() -> Result<Vec<HandoutEntry>, reqwest::Error> {
let start = Instant::now();
let res = reqwest::get(
"https://git.betalupi.com/api/packages/Mark/generic/ormc-handouts/latest/index.json",
)
.await;
let res = match res {
Ok(x) => x,
Err(err) => {
warn!("Error while getting index: {err:?}");
return Err(err);
}
};
let mut res: Vec<HandoutEntry> = res.json().await?;
res.sort_by_key(|x| x.title.clone());
debug!(
message = "Fetched handout index",
n_handouts = res.len(),
time_ms = start.elapsed().as_millis()
);
return Ok(res);
}
fn build_list_for_group(handouts: &[HandoutEntry], group: &str) -> Markup {
html! {
ul class="handout-ul" {
@for h in handouts {
@if h.group ==group {
li {
span class="handdout-li-title" {
strong { (h.title) }
}
span class="handout-li-links" {
"[ "
@if let Some(solutions) = &h.solutions {
a href=(h.handout) {"handout"}
" | "
a href=(solutions) {"solutions"}
} @else {
a href=(h.handout) {"handout"}
}
"] "
}
}
}
}
}
}
}
//
// MARK: page
//
pub fn handouts() -> Page {
let md = Markdown::parse(include_str!("handouts.md"));
#[expect(clippy::unwrap_used)]
let mut meta = PageMetadata::from_markdown_frontmatter(&md)
.unwrap()
.unwrap();
if meta.image.is_none() {
meta.image = Some(Image_Icon::URL.to_owned());
}
let html = PreEscaped(md.render());
Page {
meta,
html_ttl: Some(TimeDelta::seconds(300)),
generate_html: Box::new(move |page| {
let html = html.clone(); // TODO: find a way to not clone here
Box::pin(async move {
let handouts = get_index().await;
let warmups = match &handouts {
Ok(handouts) => build_list_for_group(handouts, "Warm-Ups"),
Err(error) => {
warn!("Could not load handout index: {error:?}");
html! {
span style="color:var(--yellow)" {
"Could not load handouts, something broke."
}
" "
(
FarLink(
"https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest",
"Try this direct link."
)
)
}
}
};
let advanced = match &handouts {
Ok(handouts) => build_list_for_group(handouts, "Advanced"),
Err(_) => html! {
span style="color:var(--yellow)" {
"Could not load handouts, something broke."
}
" "
(
FarLink(
"https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest",
"Try this direct link."
)
)
},
};
html! {
@if let Some(slug) = &page.meta.slug {
(Backlinks(&[("/", "home")], slug))
}
(html)
(Markdown(concat!(
"## Warm-Ups",
"\n\n",
"Students never show up on time. Some come early, some come late. Warm-ups ",
"are my solution to this problem: we hand these out as students walk in, ",
"giving them something to do until we can start the lesson.",
)))
(warmups)
br {}
(Markdown(concat!(
"## Advanced",
"\n\n",
"The highest level of the ORMC, and the group I spend most of my time with. ",
"Students in ORMC Advanced are in high school, which means ",
"they're ~14-18 years old.",
)))
(advanced)
br {}
}
})
}),
}
}