Skip to main content

allowthem_server/
ui.rs

1use std::fmt;
2
3use wavefunk_ui::Template;
4pub use wavefunk_ui::components::{HtmlAttr, TrustedHtml};
5
6pub type UiRenderResult<T> = Result<T, wavefunk_ui::askama::Error>;
7
8#[derive(Debug, Default, Eq, PartialEq)]
9pub struct RenderedUi {
10    html: String,
11}
12
13impl RenderedUi {
14    pub fn new(html: String) -> Self {
15        Self { html }
16    }
17
18    pub fn as_str(&self) -> &str {
19        &self.html
20    }
21
22    pub fn as_trusted_html(&self) -> TrustedHtml<'_> {
23        TrustedHtml::new(&self.html)
24    }
25
26    pub fn into_string(self) -> String {
27        self.html
28    }
29}
30
31impl AsRef<str> for RenderedUi {
32    fn as_ref(&self) -> &str {
33        self.as_str()
34    }
35}
36
37impl fmt::Display for RenderedUi {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.write_str(&self.html)
40    }
41}
42
43impl wavefunk_ui::askama::FastWritable for RenderedUi {
44    #[inline]
45    fn write_into(
46        &self,
47        dest: &mut dyn fmt::Write,
48        _: &dyn wavefunk_ui::askama::Values,
49    ) -> wavefunk_ui::askama::Result<()> {
50        Ok(dest.write_str(&self.html)?)
51    }
52}
53
54impl wavefunk_ui::askama::filters::HtmlSafe for RenderedUi {}
55
56pub fn render_component<T>(component: &T) -> UiRenderResult<RenderedUi>
57where
58    T: Template + ?Sized,
59{
60    component.render().map(RenderedUi::new)
61}
62
63pub const fn trusted_html<'a>(html: &'a str) -> TrustedHtml<'a> {
64    TrustedHtml::new(html)
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use wavefunk_ui::components::{FormPanel, SplitShell};
71
72    #[test]
73    fn rendered_html_can_be_reused_as_trusted_component_slot() {
74        let body = trusted_html(r#"<form action="/login"></form>"#);
75        let panel = FormPanel::new("Sign in", body);
76        let panel_html = render_component(&panel).expect("form panel should render");
77
78        let shell = SplitShell::new(panel_html.as_trusted_html()).with_mode("dark");
79        let shell_html = render_component(&shell).expect("split shell should render");
80
81        assert!(shell_html.as_str().contains("wf-split-shell"));
82        assert!(shell_html.as_str().contains("data-mode=\"dark\""));
83        assert!(shell_html.as_str().contains("wf-form-panel"));
84        assert!(
85            shell_html
86                .as_str()
87                .contains(r#"<form action="/login"></form>"#)
88        );
89    }
90
91    #[test]
92    fn rendered_ui_wraps_owned_html_without_losing_access_to_string() {
93        let rendered = RenderedUi::new("<section>owned</section>".to_owned());
94
95        assert_eq!(rendered.as_str(), "<section>owned</section>");
96        assert_eq!(
97            rendered.as_trusted_html().as_str(),
98            "<section>owned</section>"
99        );
100        assert_eq!(
101            rendered.into_string(),
102            "<section>owned</section>".to_owned()
103        );
104    }
105
106    fn assert_html_safe<T: wavefunk_ui::askama::filters::HtmlSafe>() {}
107
108    #[test]
109    fn rendered_ui_can_be_embedded_as_owned_safe_html() {
110        assert_html_safe::<RenderedUi>();
111    }
112}