diff --git a/src/main.rs b/src/main.rs index 5536a58..83627d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod cli; mod db; mod server; +mod template; use std::{ fs, diff --git a/src/server/images.rs b/src/server/images.rs index 0ea87f1..bd9203b 100644 --- a/src/server/images.rs +++ b/src/server/images.rs @@ -20,6 +20,7 @@ use std::{io::BufWriter, path::PathBuf}; use super::error::AppError; use crate::{ db::{self, Image, NewImage, Pagination}, + template::{Template, View}, IMG_DIR, }; @@ -77,12 +78,9 @@ async fn get_images( let is_final_page = images.len() < page.per_page.try_into().unwrap(); context.insert("is_final_page", &is_final_page); - Ok(Html(super::render_view( - &ctx.tera, - "views/images.html", - &context, - &headers, - )?)) + Ok(Html( + View::Images.headers(&headers).render(&ctx.tera, &context)?, + )) } async fn get_image_original( diff --git a/src/server/mod.rs b/src/server/mod.rs index 1af8eed..bb99d8e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -18,40 +18,14 @@ use axum::{ use tera::Context; use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer}; -use crate::db::Plant; +use crate::{ + db::Plant, + template::{Template, View}, +}; pub type Result = std::result::Result; const HX_REQUEST_HEADER: &str = "HX-Request"; -const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request"; - -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 -} - -pub fn render_view( - tera: &tera::Tera, - view: &str, - ctx: &tera::Context, - headers: &HeaderMap, -) -> tera::Result { - 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, static_dir: impl AsRef) -> axum::Router { let router = Router::new() @@ -84,23 +58,18 @@ pub async fn render_home(ctx: crate::Context, headers: &HeaderMap) -> Result Result> { let context = Context::new(); - Ok(Html(render_view( - &ctx.tera, - "views/login.html", - &context, - &headers, - )?)) + Ok(Html( + View::Login.headers(headers).render(&ctx.tera, &context)?, + )) } async fn get_index(State(ctx): State, headers: HeaderMap) -> Result> { diff --git a/src/server/plants.rs b/src/server/plants.rs index 2bf5c0e..ba96a10 100644 --- a/src/server/plants.rs +++ b/src/server/plants.rs @@ -7,7 +7,10 @@ use axum::{ }; use tera::Context; -use crate::db::{self, DbError, Event, Pagination, Plant}; +use crate::{ + db::{self, DbError, Event, Pagination, Plant}, + template::{Template, View}, +}; use super::{error::AppError, query::ToQuery}; @@ -43,12 +46,9 @@ async fn get_plant_page( context.insert("events", &events); context.insert("event_types", &db::EVENT_TYPES); - Ok(Html(super::render_view( - &ctx.tera, - "views/plant.html", - &context, - &headers, - )?)) + Ok(Html( + View::Plant.headers(&headers).render(&ctx.tera, &context)?, + )) } None => Err(AppError::NotFound), } @@ -75,12 +75,9 @@ async fn get_plants( context.insert("query", &page.to_query().encode()); } - Ok(Html(super::render_view( - &ctx.tera, - "views/plants.html", - &context, - &headers, - )?)) + Ok(Html( + View::Plants.headers(&headers).render(&ctx.tera, &context)?, + )) } async fn post_plant( diff --git a/src/template/mod.rs b/src/template/mod.rs new file mode 100644 index 0000000..8409337 --- /dev/null +++ b/src/template/mod.rs @@ -0,0 +1,13 @@ +mod view; + +pub use view::View; + +pub trait Template { + /// Returns the name or path used to identify the template in the Tera struct + fn template(&self) -> &'static str; + + /// Render the template with the given context + fn render(&self, tera: &tera::Tera, ctx: &tera::Context) -> tera::Result { + tera.render(self.template(), ctx) + } +} diff --git a/src/template/view.rs b/src/template/view.rs new file mode 100644 index 0000000..6d06da2 --- /dev/null +++ b/src/template/view.rs @@ -0,0 +1,68 @@ +use axum::http::{HeaderMap, HeaderValue}; + +use super::Template; + +const HX_REQUEST_HEADER: &str = "HX-Request"; +const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request"; + +#[derive(Clone, Copy)] +pub enum View { + Plant, + Plants, + Images, + Index, + Login, +} + +impl View { + pub fn headers(self, headers: &HeaderMap) -> ViewWrapper { + 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); + + let include_base = !is_htmx_req || is_hist_restore_req; + + ViewWrapper { + view: self, + include_base, + } + } +} + +impl Template for View { + fn template(&self) -> &'static str { + match self { + View::Plant => "views/plant.html", + View::Plants => "views/plants.html", + View::Index => "views/index.html", + View::Images => "views/images.html", + View::Login => "views/login.html", + } + } +} + +pub struct ViewWrapper { + view: View, + include_base: bool, +} + +impl Template for ViewWrapper { + fn template(&self) -> &'static str { + self.view.template() + } + + fn render(&self, tera: &tera::Tera, ctx: &tera::Context) -> tera::Result { + let view = self.view.render(tera, ctx)?; + + if self.include_base { + let mut ctx = tera::Context::new(); + ctx.insert("view", &view); + + tera.render("base.html", &ctx) + } else { + Ok(view) + } + } +}