Skip to main content

wavefunk_ui/
layouts.rs

1use crate::{assets, html};
2use askama::Template;
3
4#[derive(Debug, Template)]
5#[template(path = "layouts/app_shell.html")]
6pub struct AppShell<'a> {
7    pub title: &'a str,
8    pub app_name: &'a str,
9    pub mode: &'a str,
10    pub density_class: &'a str,
11    pub asset_base_path: &'a str,
12    pub nav_html: &'a str,
13    pub actions_html: &'a str,
14    pub content_html: &'a str,
15    pub status_left: &'a str,
16    pub status_right: &'a str,
17}
18
19impl<'a> AppShell<'a> {
20    pub const fn new(title: &'a str, app_name: &'a str, content_html: &'a str) -> Self {
21        Self {
22            title,
23            app_name,
24            mode: "dark",
25            density_class: "density-dense",
26            asset_base_path: assets::DEFAULT_BASE_PATH,
27            nav_html: "",
28            actions_html: "",
29            content_html,
30            status_left: app_name,
31            status_right: "",
32        }
33    }
34
35    pub const fn with_mode(mut self, mode: &'a str) -> Self {
36        self.mode = mode;
37        self
38    }
39
40    pub const fn light(self) -> Self {
41        self.with_mode("light")
42    }
43
44    pub const fn dark(self) -> Self {
45        self.with_mode("dark")
46    }
47
48    pub const fn dense(mut self) -> Self {
49        self.density_class = "density-dense";
50        self
51    }
52
53    pub const fn default_density(mut self) -> Self {
54        self.density_class = "";
55        self
56    }
57
58    pub const fn with_asset_base_path(mut self, asset_base_path: &'a str) -> Self {
59        self.asset_base_path = asset_base_path;
60        self
61    }
62
63    pub const fn with_nav(mut self, nav_html: &'a str) -> Self {
64        self.nav_html = nav_html;
65        self
66    }
67
68    pub const fn with_actions(mut self, actions_html: &'a str) -> Self {
69        self.actions_html = actions_html;
70        self
71    }
72
73    pub const fn with_status(mut self, status_left: &'a str, status_right: &'a str) -> Self {
74        self.status_left = status_left;
75        self.status_right = status_right;
76        self
77    }
78
79    pub fn stylesheet_link(&self) -> String {
80        html::stylesheet_link(self.asset_base_path)
81    }
82
83    pub fn htmx_script_link(&self) -> String {
84        html::htmx_script_link(self.asset_base_path)
85    }
86
87    pub fn script_link(&self) -> String {
88        html::script_link(self.asset_base_path)
89    }
90}
91
92impl<'a> askama::filters::HtmlSafe for AppShell<'a> {}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn app_shell_builders_render_variants() {
100        let html = AppShell::new("Title", "Wave Funk", "<section>Content</section>")
101            .light()
102            .default_density()
103            .with_nav(r#"<a class="wf-nav-item" href="/">Home</a>"#)
104            .with_actions(r#"<button class="wf-btn">Save</button>"#)
105            .with_status("Ready", "v0.1")
106            .render()
107            .unwrap();
108
109        assert!(html.contains(r#"data-mode="light""#));
110        assert!(html.contains(r#"class="wf-app ""#));
111        assert!(html.contains(r#"<a class="wf-nav-item" href="/">Home</a>"#));
112        assert!(html.contains(r#"<button class="wf-btn">Save</button>"#));
113        assert!(html.contains(">Ready<"));
114        assert!(html.contains(">v0.1<"));
115    }
116}