feat: overhaul templating system

This commit is contained in:
Jef Roosens 2025-01-10 15:16:01 +01:00
parent 03b3f692e1
commit 4a4b8bba3d
Signed by: Jef Roosens
GPG key ID: 21FD3D77D56BAF49
22 changed files with 157 additions and 127 deletions

View file

@ -32,7 +32,7 @@ async fn main() {
let pool = r2d2::Pool::new(manager).unwrap();
db::run_migrations(&pool, &MIGRATIONS).unwrap();
let tera = load_templates();
let tera = Tera::new("templates/**/*").unwrap();
let ctx = Context {
pool,
tera: Arc::new(tera),
@ -48,40 +48,3 @@ async fn main() {
.await
.unwrap();
}
fn load_templates() -> Tera {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("base.html", include_str!("templates/base.html")),
("index.html", include_str!("templates/index.html")),
(
"partials/plants_ul.html",
include_str!("templates/partials/plants_ul.html"),
),
(
"partials/plant_li.html",
include_str!("templates/partials/plant_li.html"),
),
("plant_page.html", include_str!("templates/plant_page.html")),
(
"partials/plant_info.html",
include_str!("templates/partials/plant_info.html"),
),
(
"partials/comment_li.html",
include_str!("templates/partials/comment_li.html"),
),
(
"partials/event_li.html",
include_str!("templates/partials/event_li.html"),
),
(
"macros/event.html",
include_str!("templates/macros/event.html"),
),
])
.unwrap();
tera
}

View file

@ -17,5 +17,5 @@ async fn post_comment(
let mut context = Context::new();
context.insert("comment", &comment);
Ok(Html(ctx.tera.render("partials/comment_li.html", &context)?))
Ok(Html(ctx.tera.render("updates/comment_li.html", &context)?))
}

View file

@ -17,5 +17,5 @@ async fn post_event(
let mut context = Context::new();
context.insert("event", &event);
Ok(Html(ctx.tera.render("partials/event_li.html", &context)?))
Ok(Html(ctx.tera.render("updates/event_li.html", &context)?))
}

View file

@ -20,14 +20,32 @@ pub type Result<T> = std::result::Result<T, error::AppError>;
const HX_REQUEST_HEADER: &str = "HX-Request";
const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request";
pub fn render_partial(headers: &HeaderMap) -> bool {
pub fn should_render_full(headers: &HeaderMap) -> bool {
let is_htmx_req = headers.get(HX_REQUEST_HEADER).is_some();
let is_hist_restore_req = headers
.get(HX_HISTORY_RESTORE_HEADER)
.map(|val| val == HeaderValue::from_static("true"))
.unwrap_or(false);
is_htmx_req && !is_hist_restore_req
!is_htmx_req || is_hist_restore_req
}
pub fn render_view(
tera: &tera::Tera,
view: &str,
ctx: &tera::Context,
headers: &HeaderMap,
) -> tera::Result<String> {
let view = tera.render(view, ctx)?;
if should_render_full(headers) {
let mut ctx = tera::Context::new();
ctx.insert("view", &view);
tera.render("base.html", &ctx)
} else {
Ok(view)
}
}
pub fn app(ctx: crate::Context) -> axum::Router {
@ -52,12 +70,17 @@ pub fn app(ctx: crate::Context) -> axum::Router {
))
}
async fn get_index(State(ctx): State<crate::Context>) -> Result<Html<String>> {
async fn get_index(State(ctx): State<crate::Context>, headers: HeaderMap) -> Result<Html<String>> {
let plants = tokio::task::spawn_blocking(move || Plant::all(&ctx.pool))
.await
.unwrap()?;
let mut context = Context::new();
context.insert("plants", &plants);
Ok(Html(ctx.tera.render("index.html", &context)?))
Ok(Html(render_view(
&ctx.tera,
"views/index.html",
&context,
&headers,
)?))
}

View file

@ -9,7 +9,7 @@ use tera::Context;
use crate::db::{self, DbError, Plant};
use super::{error::AppError, render_partial};
use super::error::AppError;
pub fn app() -> axum::Router<crate::Context> {
Router::new()
@ -45,13 +45,12 @@ async fn get_plant_page(
context.insert("events", &events);
context.insert("event_types", &db::EVENT_TYPES);
let tmpl = if render_partial(&headers) {
"partials/plant_info.html"
} else {
"plant_page.html"
};
Ok(Html(ctx.tera.render(tmpl, &context)?))
Ok(Html(super::render_view(
&ctx.tera,
"views/plant.html",
&context,
&headers,
)?))
}
None => Err(AppError::NotFound),
}
@ -67,5 +66,5 @@ async fn post_plant(
let mut context = Context::new();
context.insert("plant", &plant);
Ok(Html(ctx.tera.render("partials/plant_li.html", &context)?))
Ok(Html(ctx.tera.render("updates/plant_li.html", &context)?))
}

View file

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/static/htmx_2.0.4.min.js" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"></script>
</head>
<body>
<div id="content">
{% block content %}
{% endblock content %}
</div>
</body>
</html>

View file

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h1>Calathea</h1>
<h2>Plants</h2>
{% include "partials/plants_ul.html" %}
<h3>Add new plant</h3>
<form hx-post="/plants" hx-target="#plants" hx-swap="beforeend">
<label for="name">Name:</label>
<input type="text" id="name" name="name"></br>
<label for="species">Species:</label>
<input type="text" id="species" name="species"></br>
<label for="description">Description:</label>
<textarea id="description" name="description" rows=4></textarea></br>
<input type="submit">
</form>
{% endblock content %}

View file

@ -1,26 +0,0 @@
{% macro li(event) %}
<li>
<div class="event">
<b>{{ event.event_type }}</b>
<p>{{ event.date }}</p>
<p>{{ event.description }}</p>
</div>
</li>
{% endmacro li %}
{% macro form(plant_id, target="#events_ul") %}
<form hx-post="/events" hx-target="{{ target }}" hx-swap="beforeend">
<input type="hidden" id="plant_id" name="plant_id" value="{{ plant_id }}">
<label for="event_type">Type:</label>
<select id="event_type" name="event_type">
{% for type in event_types %}
<option value="{{ type }}">{{ type }}</option>
{% endfor %}
</select>
<label for="date">Date:</label>
<input type="date" id="date" name="date">
<label for="description">Description:</label>
<textarea id="description" name="description" rows=2></textarea></br>
<input type="submit">
</form>
{% endmacro form %}

View file

@ -1 +0,0 @@
<li>{{ comment.comment }}</li>

View file

@ -1,2 +0,0 @@
{% import "macros/event.html" as macros_event %}
{{ macros_event::li(event=event) }}

View file

@ -1,31 +0,0 @@
{% import "macros/event.html" as macros_event %}
<h1>Calathea</h1>
<h2>{{ plant.name }}</h2>
<h3>Species</h3>
<p>{{ plant.species }}</p>
<h3>Description</h3>
<p>{{ plant.description }}</p>
<h3>Events</h3>
<div id="events">
<ul id="events_ul">
{% for event in events %}
{{ macros_event::li(event=event) }}
{% endfor %}
</ul>
{{ macros_event::form(plant_id=plant.id) }}
</div>
<h3>Comments</h3>
<ul id="comments">
{% for comment in comments %}
<li>
{{ comment.comment }}
</li>
{% endfor %}
</ul>
<form hx-post="/comments" hx-target="#comments" hx-swap="beforeend">
<input type="hidden" id="plant_id" name="plant_id" value="{{ plant.id }}">
<label for="comment">Comment:</label>
<textarea id="comment" name="comment" rows=4></textarea></br>
<input type="submit">
</form>

View file

@ -1 +0,0 @@
<li>{{ plant.name }} ({{ plant.species }})</li>

View file

@ -1,7 +0,0 @@
<ul id="plants">
{% for plant in plants %}
<li>
<a hx-get="/plants/{{ plant.id }}" hx-target="#content" hx-push-url="true">{{ plant.name }}</a> ({{ plant.species }})
</li>
{% endfor %}
</ul>

View file

@ -1,8 +0,0 @@
{% import "macros/event.html" as macros_event %}
{% extends "base.html" %}
{% block content %}
{% include "partials/plant_info.html" %}
{% endblock content %}