1use dioxus::prelude::*;
49use fxhash::FxHasher;
50use lazy_static::lazy_static;
51use std::{
52 hash::{Hash, Hasher},
53 sync::Mutex,
54};
55
56lazy_static! {
57 static ref INIT_CACHE: Mutex<Vec<u64>> = Mutex::new(Vec::new());
58}
59
60#[derive(Props)]
61pub struct HelmetProps<'a> {
62 children: Element<'a>,
63}
64
65#[allow(non_snake_case)]
66pub fn Helmet<'a>(cx: Scope<'a, HelmetProps<'a>>) -> Element {
67 if let Some(window) = web_sys::window() {
68 if let Some(document) = window.document() {
69 if let Some(head) = document.head() {
70 if let Some(element_maps) = extract_element_maps(&cx.props.children) {
71 if let Ok(mut init_cache) = INIT_CACHE.try_lock() {
72 element_maps.iter().for_each(|element_map| {
73 let mut hasher = FxHasher::default();
74 element_map.hash(&mut hasher);
75 let hash = hasher.finish();
76
77 if !init_cache.contains(&hash) {
78 init_cache.push(hash);
79
80 if let Some(new_element) =
81 element_map.try_into_element(&document, &hash)
82 {
83 let _ = head.append_child(&new_element);
84 }
85 }
86 });
87 }
88 }
89 }
90 }
91 }
92
93 None
94}
95
96impl Drop for HelmetProps<'_> {
97 fn drop(&mut self) {
98 if let Some(window) = web_sys::window() {
99 if let Some(document) = window.document() {
100 if let Some(element_maps) = extract_element_maps(&self.children) {
101 if let Ok(mut init_cache) = INIT_CACHE.try_lock() {
102 element_maps.iter().for_each(|element_map| {
103 let mut hasher = FxHasher::default();
104 element_map.hash(&mut hasher);
105 let hash = hasher.finish();
106
107 if let Some(index) = init_cache.iter().position(|&c| c == hash) {
108 init_cache.remove(index);
109 }
110
111 if let Ok(children) =
112 document.query_selector_all(&format!("[data-helmet-id='{hash}']"))
113 {
114 if let Ok(Some(children_iter)) = js_sys::try_iter(&children) {
115 children_iter.for_each(|child| {
116 if let Ok(child) = child {
117 let el = web_sys::Element::from(child);
118 el.remove();
119 };
120 });
121 }
122 }
123 });
124 }
125 }
126 }
127 }
128 }
129}
130
131#[derive(Debug, Hash)]
132struct ElementMap<'a> {
133 tag: &'a str,
134 attributes: Vec<(&'a str, &'a str)>,
135 inner_html: Option<&'a str>,
136}
137
138impl<'a> ElementMap<'a> {
139 fn try_into_element(
140 &self,
141 document: &web_sys::Document,
142 hash: &u64,
143 ) -> Option<web_sys::Element> {
144 if let Ok(new_element) = document.create_element(self.tag) {
145 self.attributes.iter().for_each(|(name, value)| {
146 let _ = new_element.set_attribute(name, value);
147 });
148 let _ = new_element.set_attribute("data-helmet-id", &hash.to_string());
149
150 if let Some(inner_html) = self.inner_html {
151 new_element.set_inner_html(inner_html);
152 }
153
154 Some(new_element)
155 } else {
156 None
157 }
158 }
159}
160
161fn extract_element_maps<'a>(children: &'a Element) -> Option<Vec<ElementMap<'a>>> {
162 if let Some(VNode::Fragment(fragment)) = &children {
163 let elements = fragment
164 .children
165 .iter()
166 .flat_map(|child| {
167 if let VNode::Element(element) = child {
168 let attributes = element
169 .attributes
170 .iter()
171 .map(|attribute| (attribute.name, attribute.value))
172 .collect();
173
174 let inner_html = match element.children.first() {
175 Some(VNode::Text(vtext)) => Some(vtext.text),
176 Some(VNode::Fragment(fragment)) if fragment.children.len() == 1 => {
177 if let Some(VNode::Text(vtext)) = fragment.children.first() {
178 Some(vtext.text)
179 } else {
180 None
181 }
182 }
183 _ => None,
184 };
185
186 Some(ElementMap {
187 tag: element.tag,
188 attributes,
189 inner_html,
190 })
191 } else {
192 None
193 }
194 })
195 .collect();
196
197 Some(elements)
198 } else {
199 None
200 }
201}