Switch from maud to el

This commit is contained in:
Joscha 2024-12-14 18:08:29 +01:00
parent 28fcb43832
commit b174b796ed
5 changed files with 347 additions and 219 deletions

59
Cargo.lock generated
View file

@ -110,9 +110,9 @@ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
"clap", "clap",
"el",
"fontdb 0.23.0", "fontdb 0.23.0",
"jiff", "jiff",
"maud",
"serde", "serde",
"tokio", "tokio",
"typst", "typst",
@ -610,6 +610,16 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "el"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16d62f2117df9caa2f568124894462af4ba7dfd9a2ab8b82b79c29ce3b0c16eb"
dependencies = [
"axum-core",
"http",
]
[[package]] [[package]]
name = "embedded-io" name = "embedded-io"
version = "0.4.0" version = "0.4.0"
@ -1307,30 +1317,6 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "maud"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df518b75016b4289cdddffa1b01f2122f4a49802c93191f3133f6dc2472ebcaa"
dependencies = [
"axum-core",
"http",
"itoa",
"maud_macros",
]
[[package]]
name = "maud_macros"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa453238ec218da0af6b11fc5978d3b5c3a45ed97b722391a2a11f3306274e18"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -1658,29 +1644,6 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.92"

View file

@ -8,9 +8,9 @@ anyhow = "1.0.94"
axum = "0.7.9" axum = "0.7.9"
axum-extra = { version = "0.9.6", features = ["form"] } axum-extra = { version = "0.9.6", features = ["form"] }
clap = { version = "4.5.23", features = ["derive", "deprecated"] } clap = { version = "4.5.23", features = ["derive", "deprecated"] }
el = { version = "0.1.2", features = ["axum"] }
fontdb = "0.23.0" fontdb = "0.23.0"
jiff = "0.1.15" jiff = "0.1.15"
maud = { version = "0.26.0", features = ["axum"] }
serde = { version = "1.0.216", features = ["derive"] } serde = { version = "1.0.216", features = ["derive"] }
tokio = { version = "1.42.0", features = ["full"] } tokio = { version = "1.42.0", features = ["full"] }
typst = "0.12.0" typst = "0.12.0"

View file

@ -1,19 +1,19 @@
use maud::{html, Markup}; use el::{html::*, Document, ElementComponent};
pub mod index; pub mod index;
pub mod tsg; pub mod tsg;
fn page(head: Markup, body: Markup) -> Markup { fn page(head: impl ElementComponent, body: impl ElementComponent) -> Document {
html! { html((
(maud::DOCTYPE) el::html::head((
html lang="en" { meta((
head { attr::name("viewport"),
meta charset="utf-8"; attr::content("width=device-width, initial-scale=1"),
meta name="viewport" content="width=device-width, initial-scale=1"; )),
title { "AbzDokGen" } title("AbzDokGen"),
(head) head,
} )),
body { (body) } el::html::body(body),
} ))
} .into_document()
} }

View file

@ -1,10 +1,12 @@
use std::iter;
use axum::{ use axum::{
http::{header, StatusCode}, http::{header, StatusCode},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use axum_extra::extract::Form; use axum_extra::extract::Form;
use el::{html::*, Document};
use jiff::{ToSpan, Zoned}; use jiff::{ToSpan, Zoned};
use maud::{html, Markup, PreEscaped};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
@ -12,7 +14,12 @@ use crate::{
render::{self, Entry, Note, Timesheet, WorkingArea}, render::{self, Entry, Note, Timesheet, WorkingArea},
}; };
pub async fn get() -> Markup { const LINK_SOURCE: &str = "https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator";
const LINK_TSG: &str = "https://github.com/kit-sdq/TimeSheetGenerator";
const LINK_TEMPLATE: &str =
"https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator/blob/master/kit_timesheet.md";
pub async fn get() -> Document {
// We assume that people still want to fill out the previous month's time // We assume that people still want to fill out the previous month's time
// sheet during the first two weeks of the following month. // sheet during the first two weeks of the following month.
let month = Zoned::now() let month = Zoned::now()
@ -20,117 +27,252 @@ pub async fn get() -> Markup {
.unwrap() .unwrap()
.strftime("%Y-%m"); .strftime("%Y-%m");
page( let head = (
html! { style(include_str!("index.css")),
style { (PreEscaped(include_str!("index.css"))) } script((attr::TypeScript::Module, include_str!("index.js"))),
script type="module" { (PreEscaped(include_str!("index.js"))) } );
},
html! {
form #form {
h1 {
"Arbeitszeitdokumentationsgenerator "
a #source href="https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator" { "(source)" }
}
p { let body = form((
"Du kannst auch " attr::id("form"),
a href="tsg/" { "JSON eingeben" } h1((
", das kompatibel mit dem " "Arbeitszeitdokumentationsgenerator ",
a href="https://github.com/kit-sdq/TimeSheetGenerator" { "TimeSheetGenerator" } a((attr::id("source"), attr::href(LINK_SOURCE), "(source)")),
" ist, oder das dem Generator zugrunde liegende " )),
a href="https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator/blob/master/kit_timesheet.md" { "Typst-Template" } p((
" direkt benutzen." "Du kannst auch ",
} a((attr::href("tsg/"), "JSON eingeben")),
", das kompatibel mit dem ",
a((attr::href(LINK_TSG), "TimeSheetGenerator")),
" ist, oder das dem Generator zugrunde liegende ",
a((attr::href(LINK_TEMPLATE), "Typst-Template")),
" direkt benutzen.",
)),
div((
attr::id("header"),
label((attr::id("l-month"), attr::r#for("i-month"), "Monat / Jahr:")),
input((
attr::id("i-month"),
attr::name("month"),
attr::TypeInput::Month,
attr::placeholder(&month),
attr::value(&month),
)),
label((
attr::id("l-name"),
attr::r#for("i-name"),
"Name, Vorname des/r Beschäftigten:",
)),
input((
attr::id("i-name"),
attr::class("twocol"),
attr::name("name"),
attr::TypeInput::Text,
attr::placeholder("McStudentface, Student"),
)),
label((
attr::id("l-staffid"),
attr::r#for("i-staffid"),
"Personalnummer:",
)),
input((
attr::id("i-staffid"),
attr::name("staff_id"),
attr::TypeInput::Text,
attr::placeholder("1337420"),
)),
div((
attr::id("gfub"),
label((
attr::id("l-gf"),
attr::title("Großforschung"),
"GF: ",
input((
attr::id("i-gf"),
attr::name("working_area"),
attr::TypeInput::Radio,
attr::value("GF"),
)),
)),
label((
attr::id("l-ub"),
attr::title("Unibereich"),
"UB: ",
input((
attr::id("i-ub"),
attr::name("working_area"),
attr::TypeInput::Radio,
attr::value("UB"),
attr::checked(),
)),
)),
)),
label((
attr::id("l-department"),
attr::r#for("i-department"),
attr::title("Institut/Organisationseinheit"),
"OE:",
)),
input((
attr::id("i-department"),
attr::class("twocol"),
attr::name("department"),
attr::TypeInput::Text,
attr::placeholder("Institut für Informatik"),
attr::value("Institut für Informatik"),
)),
label((
attr::id("l-monthlyhours"),
attr::r#for("i-monthlyhours"),
"Vertraglich vereinbarte Arbeitszeit:",
)),
div((
attr::id("mhhr"),
attr::class("twocol"),
span((
input((
attr::id("i-monthlyhours"),
attr::name("monthly_hours"),
attr::TypeInput::Number,
attr::value(40),
attr::min(0),
)),
" Std.",
)),
span((
label((
attr::id("l-hourlywage"),
attr::r#for("i-hourlywage"),
"Stundensatz: ",
)),
input((
attr::id("i-hourlywage"),
attr::name("hourly_wage"),
attr::TypeInput::Number,
attr::step(0.01),
attr::value(14.09),
)),
"",
)),
)),
div((
attr::id("carry"),
attr::class("twocol"),
span((
label((
attr::id("l-carry"),
attr::r#for("i-carry"),
"Übertrag vom Vormonat: ",
)),
input((
attr::id("i-carry"),
attr::class("i-dur"),
attr::name("carry_prev_month"),
attr::TypeInput::Text,
attr::placeholder("00:00"),
)),
)),
)),
label((
attr::id("check"),
attr::title(concat!(
"Die Tabelleneinträge werden chronologisch sortiert,",
" anstatt dass ihre Reihenfolge beibehalten wird."
)),
"Einträge sortieren ",
input((
attr::name("sort"),
attr::TypeInput::Checkbox,
attr::value(true),
attr::checked(),
)),
)),
label((
attr::id("validate"),
attr::title(concat!(
"Die Tabelleneinträge werden auf Konsistenz und Korrektheit überprüft,",
" bevor das Dokument generiert wird."
)),
"Einträge validieren ",
input((
attr::name("validate"),
attr::TypeInput::Checkbox,
attr::value(true),
attr::checked(),
)),
)),
)),
div((
attr::id("table"),
div((
attr::id("task"),
"Tätigkeit",
br(()),
"(Stichwort, Projekt)",
)),
div("Tag"),
div("Beginn"),
div("Ende"),
div("Pause"),
div("Arbeitszeit"),
div(()),
div("(hh:mm)"),
div("(hh:mm)"),
div("(hh:mm)"),
div(()),
iter::repeat((
div(input((
attr::class("i-task"),
attr::name("task"),
attr::TypeInput::Text,
))),
div(input((
attr::class("i-day"),
attr::name("day"),
attr::TypeInput::Number,
attr::placeholder(1),
attr::min(1),
attr::max(31),
))),
div(input((
attr::class("i-dur"),
attr::name("start"),
attr::TypeInput::Text,
attr::placeholder("12:34"),
))),
div(input((
attr::class("i-dur"),
attr::name("end"),
attr::TypeInput::Text,
attr::placeholder("12:34"),
))),
div(input((
attr::class("i-dur"),
attr::name("rest"),
attr::TypeInput::Text,
attr::placeholder("00:00"),
))),
div(select((
attr::name("note"),
attr::value(""),
option((attr::value(""), "Normal")),
option((attr::value("U"), "Urlaub")),
option((attr::value("K"), "Krankheit")),
option((attr::value("F"), "Feiertag")),
option((attr::value("S"), "Sonstiges")),
))),
))
.take(22)
.collect::<Vec<_>>(),
)),
button((
attr::id("submit"),
attr::TypeButton::Button,
"Arbeitszeitdokumentation generieren",
)),
pre(attr::id("info")),
));
div #header { page(head, body)
label #l-month for="i-month" { "Monat / Jahr:" }
input #i-month name="month" type="month" placeholder=(month) value=(month) {}
label #l-name for="i-name" { "Name, Vorname des/r Beschäftigten:" }
input #i-name .twocol name="name" type="text" placeholder="McStudentface, Student" {}
label #l-staffid for="i-staffid" { "Personalnummer:" }
input #i-staffid name="staff_id" type="text" placeholder="1337420" {}
div #gfub {
label #l-gf title="Großforschung" { "GF: "
input #i-gf name="working_area" type="radio" value="GF" {}
}
label #l-ub for="i-ub" title="Unibereich" { "UB: "
input #i-ub name="working_area" type="radio" value="UB" checked {}
}
}
label #l-department for="i-department" title="Organisationseinheit" { "OE:" }
input #i-department .twocol name="department" type="text" placeholder="Institut für Informatik" value="Institut für Informatik" {}
label #l-monthlyhours for="i-monthlyhours" { "Vertraglich vereinbarte Arbeitszeit:" }
div #mhhr .twocol {
span {
input #i-monthlyhours name="monthly_hours" type="number" value="40" min="0" {}
" Std."
}
span {
label #l-hourlywage for="i-hourlywage" { "Stundensatz: " }
input #i-hourlywage name="hourly_wage" type="number" step="0.01" value="14.09" {}
""
}
}
div #carry .twocol {
span {
label #l-carry for="i-carry" { "Übertrag vom Vormonat: " }
input #i-carry .i-dur name="carry_prev_month" type="text" placeholder="00:00" {}
}
}
label #check title="Die Tabelleneinträge werden chronologisch sortiert, anstatt dass ihre Reihenfolge beibehalten wird." {
"Einträge sortieren "
input name="sort" type="checkbox" value="true" checked {}
}
label #validate title="Die Tabelleneinträge werden auf Konsistenz und Korrektheit überprüft, bevor das Dokument generiert wird." {
"Einträge validieren "
input name="validate" type="checkbox" value="true" checked {}
}
}
div #table {
div #task { "Tätigkeit" br; "(Stichwort, Projekt)" }
div { "Tag" }
div { "Beginn" }
div { "Ende" }
div { "Pause" }
div { "Arbeitszeit" }
div { }
div { "(hh:mm)" }
div { "(hh:mm)" }
div { "(hh:mm)" }
div { }
@for _ in 0..22 {
div { input .i-task name="task" type="text" {} }
div { input .i-day name="day" type="number" placeholder="1" min="1" max="31" {} }
div { input .i-dur name="start" type="text" placeholder="12:34" {} }
div { input .i-dur name="end" type="text" placeholder="12:34" {} }
div { input .i-dur name="rest" type="text" placeholder="00:00" {} }
div { select name="note" value="" {
option value="" { "Normal" }
option value="U" { "Urlaub" }
option value="K" { "Krankheit" }
option value="F" { "Feiertag" }
option value="S" { "Sonstiges" }
} }
}
}
button #submit type="button" { "Arbeitszeitdokumentation generieren" }
pre #info {}
}
},
)
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View file

@ -3,7 +3,7 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Json, Json,
}; };
use maud::{html, Markup, PreEscaped}; use el::{html::*, Document};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
@ -11,53 +11,76 @@ use crate::{
render::{self, Entry, Note, Timesheet, WorkingArea}, render::{self, Entry, Note, Timesheet, WorkingArea},
}; };
pub async fn get() -> Markup { const LINK_SOURCE: &str = "https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator";
page(
html! {
style { (PreEscaped(include_str!("tsg.css"))) }
script type="module" { (PreEscaped(include_str!("tsg.js"))) }
},
html! {
form #form {
h1 {
"Arbeitszeitdokumentationsgenerator "
a #source href="https://github.com/Garmelon/Arbeitszeitdokumentationsgenerator" { "(source)" }
}
p { pub async fn get() -> Document {
"Du kannst deine Daten auch in einem " let head = (
a href=".." { "coolen Formular" } style(include_str!("tsg.css")),
" eingeben." script((attr::TypeScript::Module, include_str!("tsg.js"))),
} );
p { let body = form((
label for="i-global" { "Global.json" } attr::id("form"),
textarea #i-global name="global" placeholder="{}" {} h1((
} "Arbeitszeitdokumentationsgenerator ",
a((attr::id("source"), attr::href(LINK_SOURCE), "(source)")),
)),
p((
"Du kannst deine Daten auch in einem ",
a((attr::href(".."), "coolen Formular")),
" eingeben.",
)),
p((
label((attr::r#for("i-global"), "Global.json")),
textarea((
attr::id("i-global"),
attr::name("global"),
attr::placeholder("{}"),
)),
)),
p((
label((attr::r#for("i-month"), "Month.json")),
textarea((
attr::id("i-month"),
attr::name("month"),
attr::placeholder("{}"),
)),
)),
p((
label((
attr::title(concat!(
"Die Einträge werden chronologisch sortiert,",
" anstatt dass ihre Reihenfolge beibehalten wird."
)),
input((
attr::name("sort"),
attr::TypeInput::Checkbox,
attr::checked(),
)),
" Einträge sortieren",
)),
label((
attr::title(concat!(
"Die Einträge werden auf Konsistenz und Korrektheit überprüft,",
" bevor das Dokument generiert wird."
)),
input((
attr::name("validate"),
attr::TypeInput::Checkbox,
attr::checked(),
)),
" Einträge validieren",
)),
)),
button((
attr::id("submit"),
attr::TypeButton::Button,
"Arbeitszeitdokumentation generieren",
)),
pre(attr::id("info")),
));
p { page(head, body)
label for="i-month" { "Month.json" }
textarea #i-month name="month" placeholder="{}" {}
}
p {
label title="Die Einträge werden chronologisch sortiert, anstatt dass ihre Reihenfolge beibehalten wird." {
input name="sort" type="checkbox" checked {}
" Einträge sortieren"
}
label title="Die Einträge werden auf Konsistenz und Korrektheit überprüft, bevor das Dokument generiert wird." {
input name="validate" type="checkbox" checked {}
" Einträge validieren"
}
}
button #submit type="button" { "Arbeitszeitdokumentation generieren" }
pre #info {}
}
},
)
} }
fn default_vacation() -> bool { fn default_vacation() -> bool {