use serde::Deserialize;
use tera::{Kwargs, State, Tera, TeraResult, Value};
use crate::view_model::{ActixAdminViewModelField, ActixAdminViewModelFieldType};
fn from_value<'de, T: Deserialize<'de>>(filter_name: &str, value: &'de Value) -> TeraResult<T> {
T::deserialize(value).map_err(|e| {
tera::Error::message(format!(
"Filter `{filter_name}` was called on an incorrect value: {e}"
))
})
}
pub fn get_tera() -> Tera {
let mut tera = Tera::new();
tera.register_filter("get_html_input_type", get_html_input_type);
tera.register_filter("get_html_input_class", get_html_input_class);
tera.register_filter("get_icon", get_icon);
tera.register_filter("get_regex_val", get_regex_val);
tera.register_filter("shorten", shorten_filter);
tera.register_filter("date", date_filter);
tera.register_filter("filter", filter_attribute);
load_templates_into(&mut tera);
tera
}
fn shorten_filter(value: &Value, kwargs: Kwargs, _: &State) -> TeraResult<Value> {
let max_length: Option<u64> = kwargs.get("max_length")?;
let input = value.as_str().unwrap_or("");
if let Some(max) = max_length {
if input.len() <= max as usize {
Ok(Value::from(input))
} else {
let shortened: String = input.chars().take(max as usize).collect();
Ok(Value::from(shortened))
}
} else {
Ok(Value::from(input))
}
}
fn get_html_input_class(value: &Value, _: Kwargs, _: &State) -> TeraResult<Value> {
let field: ActixAdminViewModelField = from_value("get_html_input_class", value)?;
let html_input_type = match field.field_type {
ActixAdminViewModelFieldType::TextArea => "textarea",
ActixAdminViewModelFieldType::Checkbox => "checkbox",
_ => "input",
};
Ok(Value::from(html_input_type))
}
fn get_icon(value: &Value, _: Kwargs, _: &State) -> TeraResult<Value> {
let field: String = from_value("get_icon", value)?;
let font_awesome_icon = match field.as_str() {
"true" => "<i class=\"fa-solid fa-check\"></i>",
"false" => "<i class=\"fa-solid fa-xmark\"></i>",
other => {
return Err(tera::Error::message(format!(
"get_icon: unsupported value '{other}' (expected 'true' or 'false')"
)))
}
};
Ok(Value::from(font_awesome_icon))
}
fn get_regex_val(value: &Value, kwargs: Kwargs, _: &State) -> TeraResult<Value> {
let field: ActixAdminViewModelField = from_value("get_regex_val", value)?;
let values: &tera::Map = kwargs.must_get("values")?;
let field_val = values.get(&tera::value::Key::Str(&field.field_name));
match (field_val, field.list_regex_mask) {
(Some(val), Some(r)) => {
let val_str = val.to_string();
let result_str = r.replace_all(&val_str, "*").into_owned();
Ok(Value::from(result_str))
}
(Some(val), None) => Ok(val.clone()),
(None, _) => Err(tera::Error::message(format!(
"key '{}' not found in model values",
field.field_name
))),
}
}
fn get_html_input_type(value: &Value, _: Kwargs, _: &State) -> TeraResult<Value> {
let field: ActixAdminViewModelField = from_value("get_html_input_type", value)?;
if !field.html_input_type.is_empty() {
return Ok(Value::from(field.html_input_type));
}
let html_input_type = match field.field_type {
ActixAdminViewModelFieldType::Text => "text",
ActixAdminViewModelFieldType::DateTime => "datetime-local",
ActixAdminViewModelFieldType::Date => "date",
ActixAdminViewModelFieldType::Checkbox => "checkbox",
ActixAdminViewModelFieldType::FileUpload => "file",
_ => "text",
};
Ok(Value::from(html_input_type))
}
fn date_filter(value: &Value, kwargs: Kwargs, _: &State) -> TeraResult<Value> {
use chrono::{DateTime, NaiveDate, NaiveDateTime};
let format: String = kwargs
.get("format")?
.unwrap_or_else(|| "%Y-%m-%d".to_string());
let Some(input) = value.as_str() else {
return Ok(value.clone());
};
if let Ok(dt) = DateTime::parse_from_rfc3339(input) {
return Ok(Value::from(dt.format(&format).to_string()));
}
if let Ok(dt) = NaiveDateTime::parse_from_str(input, "%Y-%m-%dT%H:%M:%S") {
return Ok(Value::from(dt.format(&format).to_string()));
}
if let Ok(dt) = NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S") {
return Ok(Value::from(dt.format(&format).to_string()));
}
if let Ok(d) = NaiveDate::parse_from_str(input, "%Y-%m-%d") {
return Ok(Value::from(d.format(&format).to_string()));
}
Ok(Value::from(input))
}
fn filter_attribute(value: &Value, kwargs: Kwargs, _: &State) -> TeraResult<Value> {
let attribute: String = kwargs.must_get("attribute")?;
let target: Option<&Value> = kwargs.get("value")?;
let arr = value
.as_array()
.ok_or_else(|| tera::Error::message("`filter` expects an array as input".to_string()))?;
let key = tera::value::Key::Str(&attribute);
let filtered: Vec<Value> = arr
.iter()
.filter(|item| {
let Some(map) = item.as_map() else {
return false;
};
match (map.get(&key), target) {
(Some(v), Some(t)) => v == t,
(Some(v), None) => !v.is_none() && !v.is_undefined(),
_ => false,
}
})
.cloned()
.collect();
Ok(Value::from(filtered))
}
fn add_templates_to_tera(tera: &mut Tera, templates: &[(&'static str, &'static str)]) {
tera.add_raw_templates(templates.iter().copied())
.expect("failed to register built-in actix-admin templates");
}
#[cfg(all(feature = "bootstrapv5_css", feature = "bulma_css"))]
compile_error!(
"features `bulma_css` and `bootstrapv5_css` cannot be enabled at the same time. \
Disable default features with `default-features = false`."
);
#[cfg(not(any(feature = "bulma_css", feature = "bootstrapv5_css")))]
compile_error!("At least one CSS theme feature must be enabled: `bulma_css` or `bootstrapv5_css`.");
#[cfg(feature = "bulma_css")]
fn load_templates_into(tera: &mut Tera) {
const TEMPLATES: &[(&str, &str)] = &[
("base.html", include_str!("templates/bulma/base.html")),
("list.html", include_str!("templates/bulma/list.html")),
(
"create_or_edit.html",
include_str!("templates/bulma/create_or_edit.html"),
),
("head.html", include_str!("templates/bulma/head.html")),
("index.html", include_str!("templates/bulma/index.html")),
("loader.html", include_str!("templates/bulma/loader.html")),
("navbar.html", include_str!("templates/bulma/navbar.html")),
(
"not_found.html",
include_str!("templates/bulma/not_found.html"),
),
("show.html", include_str!("templates/bulma/show.html")),
(
"unauthorized.html",
include_str!("templates/bulma/unauthorized.html"),
),
(
"create_or_edit/checkbox.html",
include_str!("templates/bulma/create_or_edit/checkbox.html"),
),
(
"create_or_edit/input.html",
include_str!("templates/bulma/create_or_edit/input.html"),
),
(
"create_or_edit/selectlist.html",
include_str!("templates/bulma/create_or_edit/selectlist.html"),
),
(
"create_or_edit/inline.html",
include_str!("templates/bulma/create_or_edit/inline.html"),
),
(
"list/header.html",
include_str!("templates/bulma/list/header.html"),
),
(
"list/row.html",
include_str!("templates/bulma/list/row.html"),
),
(
"list/filter.html",
include_str!("templates/bulma/list/filter.html"),
),
(
"card_grid.html",
include_str!("templates/bulma/card_grid.html"),
),
];
add_templates_to_tera(tera, TEMPLATES);
}
#[cfg(feature = "bootstrapv5_css")]
fn load_templates_into(tera: &mut Tera) {
const TEMPLATES: &[(&str, &str)] = &[
("base.html", include_str!("templates/bootstrapv5/base.html")),
("list.html", include_str!("templates/bootstrapv5/list.html")),
(
"create_or_edit.html",
include_str!("templates/bootstrapv5/create_or_edit.html"),
),
("head.html", include_str!("templates/bootstrapv5/head.html")),
(
"index.html",
include_str!("templates/bootstrapv5/index.html"),
),
(
"loader.html",
include_str!("templates/bootstrapv5/loader.html"),
),
(
"navbar.html",
include_str!("templates/bootstrapv5/navbar.html"),
),
(
"not_found.html",
include_str!("templates/bootstrapv5/not_found.html"),
),
("show.html", include_str!("templates/bootstrapv5/show.html")),
(
"unauthorized.html",
include_str!("templates/bootstrapv5/unauthorized.html"),
),
(
"create_or_edit/checkbox.html",
include_str!("templates/bootstrapv5/create_or_edit/checkbox.html"),
),
(
"create_or_edit/input.html",
include_str!("templates/bootstrapv5/create_or_edit/input.html"),
),
(
"create_or_edit/selectlist.html",
include_str!("templates/bootstrapv5/create_or_edit/selectlist.html"),
),
(
"create_or_edit/inline.html",
include_str!("templates/bootstrapv5/create_or_edit/inline.html"),
),
(
"list/header.html",
include_str!("templates/bootstrapv5/list/header.html"),
),
(
"list/row.html",
include_str!("templates/bootstrapv5/list/row.html"),
),
(
"list/filter.html",
include_str!("templates/bootstrapv5/list/filter.html"),
),
(
"card_grid.html",
include_str!("templates/bootstrapv5/card_grid.html"),
),
];
add_templates_to_tera(tera, TEMPLATES);
}