use super::model::{NodeType, TreeNode};
use crate::js::chars::to_lower;
use crate::scene::Path;
use crate::value::{Map, Value};
pub(crate) const BUILTIN_PACK: &str = "mermaid-treeview";
pub(crate) const GLYPHS: [(&str, &str); 2] = [
(
"folder",
"M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2.4 2.5h8A1.5 1.5 0 0 1 21 9v9a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18z",
),
(
"file",
"M6.5 3h7L19 8.5v11a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 5 19.5v-15A1.5 1.5 0 0 1 6.5 3zM13.5 3v5.5H19",
),
];
pub(crate) const GLYPH_STROKE: f64 = 1.5;
pub(crate) fn glyph(name: &str) -> Option<(&'static str, Path)> {
let &(name, d) = GLYPHS.iter().find(|(n, _)| *n == name)?;
Some((name, Path::parse(d).expect("a glyph's path data reads")))
}
pub(crate) struct IconConfig<'a> {
pub(crate) show_icons: bool,
pub(crate) default_icon_pack: &'a str,
pub(crate) filename_icons: Option<&'a Map>,
pub(crate) extension_icons: Option<&'a Map>,
}
fn own<'a>(map: Option<&'a Map>, key: &str) -> Option<&'a str> {
map?.get(key)?.as_str()
}
fn detect_icon<'a>(name: &str, config: &IconConfig<'a>) -> Option<&'a str> {
if let Some(by_name) = own(config.filename_icons, name).filter(|s| !s.is_empty()) {
return Some(by_name);
}
let dot = name.rfind('.').filter(|&dot| dot > 0)?;
let ext = to_lower(&name[dot..]);
own(config.extension_icons, &ext).or_else(|| own(config.extension_icons, &ext[1..]))
}
fn qualify_icon(icon: &str, default_icon_pack: &str) -> String {
if icon.contains(':') {
icon.to_string()
} else if glyph_named(icon) || default_icon_pack.is_empty() {
format!("{BUILTIN_PACK}:{icon}")
} else {
format!("{default_icon_pack}:{icon}")
}
}
fn glyph_named(name: &str) -> bool {
GLYPHS.iter().any(|(n, _)| *n == name)
}
pub(crate) fn get_node_icon(node: &TreeNode, config: &IconConfig) -> Option<String> {
match node.icon.as_deref() {
Some("none") => return None,
Some(icon) if !icon.is_empty() => {
return Some(qualify_icon(icon, config.default_icon_pack));
}
_ => {}
}
if !config.show_icons {
return None;
}
if node.node_type == NodeType::File {
match detect_icon(&node.name, config) {
Some("none") => return None,
Some(detected) if !detected.is_empty() => {
return Some(qualify_icon(detected, config.default_icon_pack));
}
_ => {}
}
}
let name = match node.node_type {
NodeType::Directory => "folder",
NodeType::File => "file",
};
Some(format!("{BUILTIN_PACK}:{name}"))
}
pub(crate) fn icon_config(config: &Map) -> IconConfig<'_> {
let section = config.get("treeView").and_then(Value::as_object);
let get = |key: &str| section.and_then(|c| c.get(key));
IconConfig {
show_icons: get("showIcons").is_some_and(Value::truthy),
default_icon_pack: get("defaultIconPack").and_then(Value::as_str).unwrap_or(""),
filename_icons: get("filenameIcons").and_then(Value::as_object),
extension_icons: get("extensionIcons").and_then(Value::as_object),
}
}