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
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:
@@ -33,200 +33,10 @@ are written with this in mind.\
|
||||
I do not expect the average student to finish all problems during this two-hour session.
|
||||
If the class finishes early, the lesson is either too short or too easy.
|
||||
|
||||
The sources for all these handouts are available [here](https://git.betalupi.com/mark/handouts).\
|
||||
Some are written in LaTeX, some are in [Typst](https://typst.app). \
|
||||
The latter is vastly superior.
|
||||
|
||||
<br></br>
|
||||
<hr></hr>
|
||||
<br></br>
|
||||
|
||||
## Warm-Ups
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<ul id="handout-ul-Warm-Ups" class="handout-ul"></ul>
|
||||
|
||||
<script>
|
||||
fetch("https://git.betalupi.com/api/packages/Mark/generic/ormc-handouts/latest/index.json")
|
||||
.then(res => res.json())
|
||||
.then(out => {
|
||||
out = out.sort((a, b) => (
|
||||
a["title"].toLowerCase() < b["title"].toLowerCase()
|
||||
));
|
||||
|
||||
out.forEach(element => {
|
||||
if (element["group"] != "Warm-Ups") { return }
|
||||
|
||||
// Handout title
|
||||
const title = document.createElement("span");
|
||||
const title_a = document.createElement("strong");
|
||||
title_a.appendChild(document.createTextNode(element["title"] + " "));
|
||||
title.appendChild(title_a)
|
||||
title.classList.add("handout-li-title");
|
||||
|
||||
// Handout title
|
||||
const desc = document.createElement("span");
|
||||
desc.appendChild(document.createTextNode(element["description"]));
|
||||
desc.classList.add("handout-li-desc");
|
||||
|
||||
const handout_link = element["handout"];
|
||||
const solutions_link = element["solutions"];
|
||||
|
||||
const links = document.createElement("span");
|
||||
links.classList.add("handout-li-links");
|
||||
const h = document.createElement("a");
|
||||
h.appendChild(document.createTextNode("handout"))
|
||||
h.href = handout_link;
|
||||
if (solutions_link === null) {
|
||||
links.appendChild(document.createTextNode("[ "));
|
||||
links.appendChild(h);
|
||||
links.appendChild(document.createTextNode(" ]"));
|
||||
} else {
|
||||
var s = document.createElement("a");
|
||||
s.appendChild(document.createTextNode("solutions"))
|
||||
s.href = solutions_link;
|
||||
links.appendChild(document.createTextNode("[ "));
|
||||
links.appendChild(h);
|
||||
links.appendChild(document.createTextNode(" | "));
|
||||
links.appendChild(s);
|
||||
links.appendChild(document.createTextNode(" ]"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add to main list
|
||||
const item = document.createElement("li");
|
||||
item.appendChild(title)
|
||||
item.appendChild(links);
|
||||
//item.appendChild(desc)
|
||||
const list = document.getElementById("handout-ul-Warm-Ups");
|
||||
list.insertBefore(item, list.children[0]);
|
||||
})}
|
||||
)
|
||||
.catch(err => {
|
||||
// Print fallback link if we failed to load json index
|
||||
console.log(err)
|
||||
|
||||
const title = document.createElement("span");
|
||||
const title_a = document.createElement("strong");
|
||||
title_a.appendChild(document.createTextNode("Error: "));
|
||||
title.appendChild(title_a)
|
||||
title.appendChild(document.createTextNode("failed to load handouts, something broke."))
|
||||
title.classList.add("handout-li-title");
|
||||
|
||||
const fallback = "https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest";
|
||||
const link = document.createElement("a");
|
||||
link.href = fallback
|
||||
link.appendChild(document.createTextNode("ormc-handouts"));
|
||||
|
||||
const item_a = document.createElement("li");
|
||||
item_a.appendChild(title)
|
||||
const item_b = document.createElement("li");
|
||||
item_b.appendChild(document.createTextNode("Fallback link: "))
|
||||
item_b.appendChild(link)
|
||||
|
||||
const list = document.getElementById("handout-ul-Warm-Ups");
|
||||
list.insertBefore(item_b, list.children[0]);
|
||||
list.insertBefore(item_a, list.children[0]);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<br></br>
|
||||
|
||||
|
||||
## Advanced
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<ul id="handout-ul-Advanced" class="handout-ul"></ul>
|
||||
|
||||
<script>
|
||||
fetch("https://git.betalupi.com/api/packages/Mark/generic/ormc-handouts/latest/index.json")
|
||||
.then(res => res.json())
|
||||
.then(out => {
|
||||
out = out.sort((a, b) => (
|
||||
a["title"].toLowerCase() < b["title"].toLowerCase()
|
||||
));
|
||||
|
||||
out.forEach(element => {
|
||||
if (element["group"] != "Advanced") { return }
|
||||
|
||||
// Handout title
|
||||
const title = document.createElement("span");
|
||||
const title_a = document.createElement("strong");
|
||||
title_a.appendChild(document.createTextNode(element["title"] + " "));
|
||||
title.appendChild(title_a)
|
||||
title.classList.add("handout-li-title");
|
||||
|
||||
// Handout title
|
||||
const desc = document.createElement("span");
|
||||
desc.appendChild(document.createTextNode(element["description"]));
|
||||
desc.classList.add("handout-li-desc");
|
||||
|
||||
const handout_link = element["handout"];
|
||||
const solutions_link = element["solutions"];
|
||||
|
||||
const links = document.createElement("span");
|
||||
links.classList.add("handout-li-links");
|
||||
const h = document.createElement("a");
|
||||
h.appendChild(document.createTextNode("handout"))
|
||||
h.href = handout_link;
|
||||
if (solutions_link === null) {
|
||||
links.appendChild(document.createTextNode("[ "));
|
||||
links.appendChild(h);
|
||||
links.appendChild(document.createTextNode(" ]"));
|
||||
} else {
|
||||
var s = document.createElement("a");
|
||||
s.appendChild(document.createTextNode("solutions"))
|
||||
s.href = solutions_link;
|
||||
links.appendChild(document.createTextNode("[ "));
|
||||
links.appendChild(h);
|
||||
links.appendChild(document.createTextNode(" | "));
|
||||
links.appendChild(s);
|
||||
links.appendChild(document.createTextNode(" ]"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add to main list
|
||||
const item = document.createElement("li");
|
||||
item.appendChild(title)
|
||||
item.appendChild(links);
|
||||
//item.appendChild(desc)
|
||||
const list = document.getElementById("handout-ul-Advanced");
|
||||
list.insertBefore(item, list.children[0]);
|
||||
})}
|
||||
)
|
||||
.catch(err => {
|
||||
// Print fallback link if we failed to load json index
|
||||
console.log(err)
|
||||
|
||||
const title = document.createElement("span");
|
||||
const title_a = document.createElement("strong");
|
||||
title_a.appendChild(document.createTextNode("Error: "));
|
||||
title.appendChild(title_a)
|
||||
title.appendChild(document.createTextNode("failed to load handouts, something broke."))
|
||||
title.classList.add("handout-li-title");
|
||||
|
||||
const fallback = "https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest";
|
||||
const link = document.createElement("a");
|
||||
link.href = fallback
|
||||
link.appendChild(document.createTextNode("ormc-handouts"));
|
||||
|
||||
const item_a = document.createElement("li");
|
||||
item_a.appendChild(title)
|
||||
const item_b = document.createElement("li");
|
||||
item_b.appendChild(document.createTextNode("Fallback link: "))
|
||||
item_b.appendChild(link)
|
||||
|
||||
const list = document.getElementById("handout-ul-Advanced");
|
||||
list.insertBefore(item_b, list.children[0]);
|
||||
list.insertBefore(item_a, list.children[0]);
|
||||
});
|
||||
</script>
|
||||
|
||||
<br></br>
|
||||
|
||||
173
crates/service/service-webpage/src/pages/handouts.rs
Normal file
173
crates/service/service-webpage/src/pages/handouts.rs
Normal 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 {}
|
||||
}
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -23,53 +23,55 @@ pub fn index() -> Page {
|
||||
},
|
||||
|
||||
generate_html: Box::new(move |_page| {
|
||||
html! {
|
||||
h2 id="about" { "About" }
|
||||
Box::pin(async {
|
||||
html! {
|
||||
h2 id="about" { "About" }
|
||||
|
||||
div {
|
||||
img
|
||||
src=(Image_Cover::URL)
|
||||
style="float:left;margin:10px 10px 10px 10px;display:block;width:25%;"
|
||||
{}
|
||||
div {
|
||||
img
|
||||
src=(Image_Cover::URL)
|
||||
style="float:left;margin:10px 10px 10px 10px;display:block;width:25%;"
|
||||
{}
|
||||
|
||||
div style="margin:2ex 1ex 2ex 1ex;display:inline-block;overflow:hidden;width:60%;" {
|
||||
"Welcome, you've reached Mark's main page. Here you'll find"
|
||||
" links to various projects I've worked on."
|
||||
div style="margin:2ex 1ex 2ex 1ex;display:inline-block;overflow:hidden;width:60%;" {
|
||||
"Welcome, you've reached Mark's main page. Here you'll find"
|
||||
" links to various projects I've worked on."
|
||||
|
||||
ul {
|
||||
li { (MangledBetaEmail {}) }
|
||||
li { (MangledGoogleEmail {}) }
|
||||
ul {
|
||||
li { (MangledBetaEmail {}) }
|
||||
li { (MangledGoogleEmail {}) }
|
||||
|
||||
li {
|
||||
(
|
||||
FarLink(
|
||||
"https://github.com/rm-dr",
|
||||
html!(
|
||||
(FAIcon::Github)
|
||||
"rm-dr"
|
||||
li {
|
||||
(
|
||||
FarLink(
|
||||
"https://github.com/rm-dr",
|
||||
html!(
|
||||
(FAIcon::Github)
|
||||
"rm-dr"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
(
|
||||
FarLink(
|
||||
"https://git.betalupi.com",
|
||||
html!(
|
||||
(FAIcon::Git)
|
||||
"git.betalupi.com"
|
||||
li {
|
||||
(
|
||||
FarLink(
|
||||
"https://git.betalupi.com",
|
||||
html!(
|
||||
(FAIcon::Git)
|
||||
"git.betalupi.com"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
br style="clear:both;" {}
|
||||
}
|
||||
br style="clear:both;" {}
|
||||
}
|
||||
|
||||
(Markdown(include_str!("index.md")))
|
||||
}
|
||||
(Markdown(include_str!("index.md")))
|
||||
}
|
||||
})
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
mod index;
|
||||
use assetserver::Asset;
|
||||
pub use index::index;
|
||||
|
||||
use crate::{page::Page, routes::assets::Image_Icon};
|
||||
|
||||
mod handouts;
|
||||
mod index;
|
||||
|
||||
pub use handouts::handouts;
|
||||
pub use index::index;
|
||||
|
||||
pub fn links() -> Page {
|
||||
/*
|
||||
Dead links:
|
||||
@@ -12,19 +16,12 @@ pub fn links() -> Page {
|
||||
http://www.3dprintmath.com/
|
||||
*/
|
||||
|
||||
Page::from_markdown(include_str!("links.md"), Some(Image_Icon::URL.to_string()))
|
||||
Page::from_markdown(include_str!("links.md"), Some(Image_Icon::URL.to_owned()))
|
||||
}
|
||||
|
||||
pub fn betalupi() -> Page {
|
||||
Page::from_markdown(
|
||||
include_str!("betalupi.md"),
|
||||
Some(Image_Icon::URL.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handouts() -> Page {
|
||||
Page::from_markdown(
|
||||
include_str!("handouts.md"),
|
||||
Some(Image_Icon::URL.to_string()),
|
||||
Some(Image_Icon::URL.to_owned()),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user