use crate::{prelude::*, racy_cell::RacyCell};
use once_cell::sync::Lazy;
use std::{collections::{HashMap, BTreeSet}, hash::{Hash, Hasher}};
use sugars::hmap;
pub use sugars::hash;
#[derive(Default)]
pub struct StyleStorage {
inserted_style_hashes: BTreeSet<u64>,
style_elements: HashMap<String, (web_sys::Document, Vec<web_sys::HtmlStyleElement>)>,
}
pub(crate) static STYLE_STORAGE: Lazy<RacyCell<StyleStorage>> = Lazy::new(|| RacyCell::new(StyleStorage {
inserted_style_hashes: BTreeSet::new(),
style_elements: hmap!["default".to_owned() => {
let dom = web_sys::window().expect("no window").document().expect("no document");
let head = dom.head().expect("dom has no head");
let element = dom.create_element(web_str::style()).expect("can't create style element");
head.append_child(&element).expect("can't append child");
(dom, vec![element.unchecked_into()])
}],
}));
#[extend::ext]
impl css::Style {
fn fixup_class_placeholders(&mut self, class: &str) {
for rule in self.0.iter_mut() {
match rule {
css::Rule::Style(style_rule) => {
for selector_component in (style_rule.0).0.iter_mut() {
if *selector_component == css::selector::SelectorComponent::ClassPlaceholder {
*selector_component = css::selector::SelectorComponent::Class(class.to_owned());
}
}
},
css::Rule::Media(_, style) => style.fixup_class_placeholders(class),
_ => {},
}
}
}
fn sort_properties(&mut self) {
for rule in self.0.iter_mut() {
match rule {
css::Rule::Style(style_rule) => {
style_rule.1.sort();
},
css::Rule::Media(_, style) => style.sort_properties(),
_ => {},
}
}
}
}
impl StyleStorage {
pub fn fetch(&mut self, mut style: css::Style, ordinal: usize) -> String {
style.sort_properties();
let mut hasher = ahash::AHasher::default();
style.hash(&mut hasher);
ordinal.hash(&mut hasher);
let id = hasher.finish();
let class = format!("s-{id:x}");
if self.inserted_style_hashes.contains(&id) { return class; }
self.inserted_style_hashes.insert(id);
style.fixup_class_placeholders(&class);
let style_string = style.to_string();
for (_, (dom, ordered_style_elements)) in self.style_elements.iter_mut() {
if ordered_style_elements.get(ordinal).is_none() {
let style_element = dom.create_element(web_str::style()).expect("can't create style element");
let head = dom.head().expect("dom has no head");
head.append_child(&style_element).expect("can't append child");
ordered_style_elements.push(style_element.unchecked_into());
}
ordered_style_elements[ordinal].append_with_str_1(&style_string).expect("can't append css string");
}
class
}
pub fn unregister_window(&mut self, window_name: &str) {
self.style_elements.remove(window_name);
}
pub fn register_window(&mut self, window: &web_sys::Window, window_name: String) {
let dom = window.document().expect("window has no dom");
let head = dom.head().expect("dom has no head");
for default_window_style_index in 0..self.style_elements.get("default").expect("no default window").1.len() {
let new_style_element = dom.create_element(web_str::style()).expect("can't create style element");
head.append_child(&new_style_element).expect("can't append child");
let style_element = &self.style_elements.get("default").expect("no default window").1[default_window_style_index];
new_style_element.set_inner_html(&style_element.inner_html());
self.style_elements.entry(window_name.clone()).or_insert((dom.clone(), Vec::new())).1.push(new_style_element.unchecked_into());
}
}
}