css_in_rust_next/style/
mod.rs

1// Copyright © 2020 Lukas Wagner
2
3pub mod ast;
4
5use super::parser::Parser;
6use ast::Scope;
7#[cfg(target_arch = "wasm32")]
8use ast::ToCss;
9#[cfg(not(target_arch = "wasm32"))]
10use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13#[cfg(target_arch = "wasm32")]
14use web_sys::Element;
15
16#[cfg(target_arch = "wasm32")]
17use wasm_bindgen::prelude::*;
18
19lazy_static! {
20    static ref STYLE_REGISTRY: Arc<Mutex<StyleRegistry>> = Arc::new(Mutex::default());
21}
22
23#[cfg(target_arch = "wasm32")]
24#[wasm_bindgen]
25extern "C" {
26    #[wasm_bindgen(js_namespace = Math)]
27    fn random() -> f64;
28}
29
30/// The style registry is just a global struct that makes sure no style gets lost.
31/// Every style automatically registers with the style registry.
32#[derive(Clone, Debug, Default)]
33struct StyleRegistry {
34    styles: HashMap<String, Style>,
35}
36
37unsafe impl Send for StyleRegistry {}
38unsafe impl Sync for StyleRegistry {}
39
40#[cfg(all(target_arch = "wasm32"))]
41#[derive(Debug, Clone)]
42pub struct Style {
43    /// The designated class name of this style
44    class_name: String,
45    /// The abstract syntax tree of the css
46    ast: Option<Vec<Scope>>,
47    /// Style DOM node the data in this struct is turned into.
48    node: Option<Element>,
49}
50
51#[cfg(not(target_arch = "wasm32"))]
52#[allow(dead_code)]
53#[derive(Debug, Clone)]
54pub struct Style {
55    /// The designated class name of this style
56    class_name: String,
57    /// The abstract syntax tree of the css
58    ast: Option<Vec<Scope>>,
59}
60
61#[cfg(target_arch = "wasm32")]
62impl Style {
63    /// Creates a new style and, stores it into the registry and returns the
64    /// newly created style.
65    ///
66    /// This function will already mount the style to the HTML head for the browser to use.
67    pub fn create<I1: Into<String>, I2: Into<String>>(
68        class_name: I1,
69        css: I2,
70    ) -> Result<Style, String> {
71        let (class_name, css) = (class_name.into(), css.into());
72        let ast = Parser::parse(css)?;
73        let mut new_style = Self {
74            class_name: format!("{}-{}", class_name, random().to_bits()),
75            ast: Some(ast),
76            node: None,
77        };
78        new_style = new_style.mount();
79        let style_registry_mutex = Arc::clone(&STYLE_REGISTRY);
80        let mut style_registry = match style_registry_mutex.lock() {
81            Ok(guard) => guard,
82            Err(poisoned) => poisoned.into_inner(),
83        };
84        (*style_registry)
85            .styles
86            .insert(new_style.class_name.clone(), new_style.clone());
87        Ok(new_style)
88    }
89
90    pub fn get_class_name(self) -> String {
91        self.class_name
92    }
93
94    /// Mounts the styles to the document head web-sys style
95    fn mount(&mut self) -> Self {
96        let mut style = self.unmount();
97        style.node = self.generate_element().ok();
98        if let Some(node) = style.node {
99            let window = web_sys::window().expect("no global `window` exists");
100            let document = window.document().expect("should have a document on window");
101            let head = document.head().expect("should have a head in document");
102            head.append_child(&node).ok();
103        }
104        self.clone()
105    }
106
107    /// Unmounts the style from the HTML head web-sys style
108    fn unmount(&mut self) -> Self {
109        if let Some(node) = &self.node {
110            let window = web_sys::window().expect("no global `window` exists");
111            let document = window.document().expect("should have a document on window");
112            let head = document.head().expect("should have a head in document");
113            head.remove_child(node).ok();
114        }
115        self.clone()
116    }
117
118    /// Takes all Scopes and lets them translate themselves into CSS.
119    fn generate_css(&self) -> String {
120        match &self.ast {
121            Some(ast) => ast
122                .clone()
123                .into_iter()
124                .map(|scope| scope.to_css(self.class_name.clone()))
125                .fold(String::new(), |acc, css_part| {
126                    format!("{}\n{}", acc, css_part)
127                }),
128            None => String::new(),
129        }
130    }
131
132    /// Generates the `<style/>` tag web-sys style for inserting into the head of the
133    /// HTML document.
134    fn generate_element(&self) -> Result<Element, JsValue> {
135        let window = web_sys::window().expect("no global `window` exists");
136        let document = window.document().expect("should have a document on window");
137        let style_element = document.create_element("style").unwrap();
138        style_element
139            .set_attribute("data-style", self.class_name.as_str())
140            .ok();
141        style_element.set_text_content(Some(self.generate_css().as_str()));
142        Ok(style_element)
143    }
144}
145
146/// The style represents all the CSS belonging to a single component.
147#[cfg(not(target_arch = "wasm32"))]
148impl Style {
149    /// Creates a new style and, stores it into the registry and returns the
150    /// newly created style.
151    ///
152    /// This function will already mount the style to the HTML head for the browser to use.
153    pub fn create<I1: Into<String>, I2: Into<String>>(
154        class_name: I1,
155        css: I2,
156    ) -> Result<Style, String> {
157        let (class_name, css) = (class_name.into(), css.into());
158        let small_rng = SmallRng::from_entropy();
159        let new_style = Self {
160            class_name: format!(
161                "{}-{}",
162                class_name,
163                small_rng
164                    .sample_iter(Alphanumeric)
165                    .take(30)
166                    .map(|number| number.to_string())
167                    .collect::<String>()
168            ),
169            // TODO log out an error
170            ast: Parser::parse(css).ok(),
171        };
172        let style_registry_mutex = Arc::clone(&STYLE_REGISTRY);
173        let mut style_registry = match style_registry_mutex.lock() {
174            Ok(guard) => guard,
175            Err(poisoned) => poisoned.into_inner(),
176        };
177        style_registry
178            .styles
179            .insert(new_style.class_name.clone(), new_style.clone());
180        Ok(new_style)
181    }
182
183    pub fn get_class_name(self) -> String {
184        self.class_name
185    }
186}
187
188impl ToString for Style {
189    /// Just returns the classname
190    fn to_string(&self) -> String {
191        self.class_name.clone()
192    }
193}