Skip to main content

track/webui/
templates.rs

1//! MiniJinja template engine setup.
2
3use crate::utils::{Result, TrackError};
4use minijinja::{path_loader, AutoEscape, Environment};
5use std::path::PathBuf;
6use std::sync::Arc;
7
8/// Template engine wrapper
9pub struct Templates {
10    env: Environment<'static>,
11}
12
13impl Templates {
14    /// Create new template engine with templates from the given directory
15    #[allow(dead_code)]
16    pub fn new(template_dir: PathBuf) -> Self {
17        let mut env = Environment::new();
18        env.set_loader(path_loader(template_dir));
19        env.set_auto_escape_callback(|name| {
20            if name.ends_with(".html") {
21                AutoEscape::Html
22            } else {
23                AutoEscape::None
24            }
25        });
26
27        Self { env }
28    }
29
30    /// Create template engine with embedded templates (for distribution)
31    pub fn embedded() -> Self {
32        let mut env = Environment::new();
33        env.set_auto_escape_callback(|name| {
34            if name.ends_with(".html") {
35                AutoEscape::Html
36            } else {
37                AutoEscape::None
38            }
39        });
40
41        // Embed templates at compile time
42        env.add_template("base.html", include_str!("../../templates/base.html"))
43            .expect("Failed to add base.html template");
44        env.add_template("index.html", include_str!("../../templates/index.html"))
45            .expect("Failed to add index.html template");
46        env.add_template(
47            "partials/todo_list.html",
48            include_str!("../../templates/partials/todo_list.html"),
49        )
50        .expect("Failed to add todo_list.html template");
51        env.add_template(
52            "partials/scrap_list.html",
53            include_str!("../../templates/partials/scrap_list.html"),
54        )
55        .expect("Failed to add scrap_list.html template");
56        env.add_template(
57            "partials/description.html",
58            include_str!("../../templates/partials/description.html"),
59        )
60        .expect("Failed to add description.html template");
61        env.add_template(
62            "partials/ticket.html",
63            include_str!("../../templates/partials/ticket.html"),
64        )
65        .expect("Failed to add ticket.html template");
66        env.add_template(
67            "partials/links.html",
68            include_str!("../../templates/partials/links.html"),
69        )
70        .expect("Failed to add links.html template");
71        env.add_template(
72            "partials/repos.html",
73            include_str!("../../templates/partials/repos.html"),
74        )
75        .expect("Failed to add repos.html template");
76        env.add_template(
77            "partials/calendar.html",
78            include_str!("../../templates/partials/calendar.html"),
79        )
80        .expect("Failed to add calendar.html template");
81
82        Self { env }
83    }
84
85    /// Render a template with the given context
86    pub fn render<S: serde::Serialize>(&self, name: &str, ctx: S) -> Result<String> {
87        let tmpl = self
88            .env
89            .get_template(name)
90            .map_err(|err| TrackError::TemplateRenderFailed {
91                name: name.to_string(),
92                detail: err.to_string(),
93            })?;
94        tmpl.render(ctx)
95            .map_err(|err| TrackError::TemplateRenderFailed {
96                name: name.to_string(),
97                detail: err.to_string(),
98            })
99    }
100}
101
102/// Thread-safe template engine
103pub type SharedTemplates = Arc<Templates>;