karaty_blueprint/
lib.rs

1use std::collections::HashMap;
2
3use dioxus::{
4    core::{Element, Scope},
5    core_macro::Props,
6};
7
8use reqwasm::http::Request;
9pub use toml::Value;
10pub mod config;
11
12#[derive(Debug, Props, PartialEq)]
13pub struct TemplateProps {
14    pub route: TemplateRouteData,
15    pub data: TemplateData,
16    pub utility: SharedUtility,
17    pub config: HashMap<String, Value>,
18}
19
20#[derive(Debug, Props, PartialEq)]
21pub struct TemplateRouteData {
22    pub bound_path: String,
23    pub access_path: String,
24    pub segments: HashMap<String, String>,
25    pub queries: HashMap<String, String>,
26}
27
28#[derive(Debug, PartialEq)]
29pub struct SharedUtility {
30    /// footer template
31    pub footer: fn(Scope) -> Element,
32    /// navbar template
33    pub navbar: fn(Scope) -> Element,
34    /// giscus Component
35    pub giscus: fn(Scope) -> Element,
36    /// 404 not found template
37    pub _404: fn(Scope) -> Element,
38    /// error template
39    pub error: fn(Scope<ErrorProps>) -> Element,
40    // preset renderers
41    pub renderers: HashMap<String, fn(Scope<RendererProps>) -> Element>,
42    /// `karaty.toml` content
43    pub app_config: config::Config,
44}
45
46#[derive(Debug, Props, PartialEq)]
47pub struct ErrorProps {
48    pub title: String,
49    pub content: String,
50}
51
52#[derive(Debug, Props, PartialEq)]
53pub struct RendererProps {
54    pub content: String,
55    pub config: HashMap<String, Value>,
56}
57
58/// use for store template load data.
59#[derive(Debug, Clone, PartialEq)]
60pub enum TemplateData {
61    /// File provide a single file content.
62    File(String),
63    /// Directory will provide a directory struct and inner data.
64    Directory(HashMap<String, TemplateData>),
65}
66
67impl TemplateData {
68    pub fn text(&self) -> String {
69        match self {
70            TemplateData::File(content) => content.to_string(),
71            TemplateData::Directory(dir) => format!("{:?}", dir),
72        }
73    }
74    pub fn get(&self, mut index: Vec<String>) -> Option<TemplateData> {
75        if index.is_empty() {
76            return None;
77        }
78        let first = index.remove(0);
79
80        match self {
81            TemplateData::File(_) => {
82                if index.is_empty() {
83                    Some(self.clone())
84                } else {
85                    None
86                }
87            }
88            TemplateData::Directory(dir) => {
89                if let Some(next) = dir.get(&first) {
90                    if index.is_empty() {
91                        Some(next.clone())
92                    } else {
93                        next.get(index)
94                    }
95                } else {
96                    None
97                }
98            }
99        }
100    }
101}
102
103pub type Component = fn(Scope<TemplateProps>) -> Element;
104
105type TemplatesData = HashMap<TemplateDataType, HashMap<String, Component>>;
106
107#[derive(Debug, Clone)]
108pub struct Templates(TemplatesData);
109impl Templates {
110    pub fn new() -> Self {
111        Self(Default::default())
112    }
113
114    pub fn template(&mut self, name: &str, data_type: Vec<TemplateDataType>, template: Component) {
115        for i in data_type {
116            if self.0.contains_key(&i) {
117                let t = self.0.get_mut(&i).unwrap();
118                t.insert(name.to_string(), template);
119            } else {
120                let mut t = HashMap::new();
121                t.insert(name.to_string(), template);
122                self.0.insert(i, t);
123            }
124        }
125    }
126
127    pub fn sub_module(&mut self, name: &str, templates: Self) {
128        for (k, i) in templates.0 {
129            for j in i {
130                self.template(&format!("{name}::{}", j.0), vec![k.clone()], j.1);
131            }
132        }
133    }
134
135    pub fn load(&self, name: &str, data_type: TemplateDataType) -> Option<&Component> {
136        if self.0.contains_key(&data_type) {
137            let part = self.0.get(&data_type).unwrap();
138            part.get(name)
139        } else {
140            None
141        }
142    }
143}
144
145#[derive(Debug, Eq, Hash, PartialEq, Clone)]
146pub enum TemplateDataType {
147    Markdown,
148    HTML,
149    Json,
150
151    None,
152    DirectoryData,
153    Any,
154    Other(String),
155}
156
157impl TemplateDataType {
158    pub fn to_string(&self) -> String {
159        match self {
160            TemplateDataType::Markdown => "md",
161            TemplateDataType::HTML => "html",
162            TemplateDataType::Json => "json",
163
164            TemplateDataType::None => "",
165            TemplateDataType::DirectoryData => "#dir",
166            TemplateDataType::Any => "*",
167            TemplateDataType::Other(s) => s,
168        }
169        .to_string()
170    }
171
172    pub fn from_string(s: &str) -> Self {
173        match s {
174            "md" => TemplateDataType::Markdown,
175            "html" => TemplateDataType::HTML,
176            "json" => TemplateDataType::Json,
177
178            "" => TemplateDataType::None,
179            "#dir" => TemplateDataType::DirectoryData,
180            "*" => TemplateDataType::Any,
181            _ => Self::Other(s.to_string()),
182        }
183    }
184}
185
186#[derive(Debug, Clone, PartialEq)]
187pub struct LazyLoader(String);
188impl LazyLoader {
189    pub async fn load(self) -> Result<String, ErrorProps> {
190        let url = self.0;
191        let resp = Request::get(&url).send().await;
192        if resp.is_err() {
193            return Err(ErrorProps {
194                title: "content load failed".to_string(),
195                content: format!("lazy loader load content `{}` failed.", url),
196            });
197        } else {
198            return Ok(resp.unwrap().text().await.unwrap());
199        }
200    }
201}