oranda/site/layout/
css.rs

1use std::env;
2use std::sync::RwLock;
3
4use crate::errors::*;
5
6use crate::config::style::ORANDA_CSS_TAG;
7use axoasset::{Asset, LocalAsset};
8use camino::Utf8Path;
9use minifier::css;
10
11static CSS_CACHE: RwLock<Vec<CssItem>> = RwLock::new(Vec::new());
12
13struct CssItem {
14    release_tag: String,
15    contents: String,
16}
17
18fn concat_minify(css_files: &[String]) -> Result<String> {
19    let mut css = String::new();
20    for file in css_files {
21        let future = Asset::load_string(file);
22        let unminified = tokio::runtime::Handle::current().block_on(future)?;
23        let minified = match css::minify(&unminified) {
24            Ok(css) => Ok(css),
25            Err(e) => Err(OrandaError::Other(e.to_string())),
26        };
27        css = format!("{css}/* {file} */{minified}", minified = minified?);
28    }
29
30    Ok(css)
31}
32
33pub fn get_css_link(path_prefix: &Option<String>, release_tag: &str) -> Result<String> {
34    let filename = get_css_filename(release_tag);
35    Ok(crate::site::link::generate_relative(path_prefix, &filename))
36}
37
38/// Places CSS in the destination directory. Depending on the results of the build script, the
39/// output of this will differ.
40pub fn place_css(dist_dir: &str, release_tag: &str) -> Result<()> {
41    // Above all, we respect specifying `style.oranda_css_version`.
42    if release_tag == ORANDA_CSS_TAG {
43        // If the user has set `ORANDA_USE_TAILWIND_BINARY`, build using the Tailwind binary.
44        #[cfg(css = "tailwind")]
45        {
46            oranda_generate_css::build_css(Utf8Path::new(dist_dir))?;
47        }
48
49        // If a `oranda-css/dist/oranda.css` file exists, inline it into the binary.
50        #[cfg(css = "file")]
51        {
52            let css = include_str!("../../../oranda-css/dist/oranda.css");
53            LocalAsset::write_new_all(css, format!("{dist_dir}/oranda-{release_tag}.css"))?;
54        }
55
56        // Otherwise, fall back to fetching from GitHub releases.
57        #[cfg(css = "fetch")]
58        {
59            fetch_css(dist_dir, release_tag)?;
60        }
61        Ok(())
62    } else {
63        // If we specified a custom oranda version, or someone compiled oranda without Cargo (how?),
64        // fall back to fetching that version off GitHub.
65        fetch_css(dist_dir, release_tag)
66    }
67}
68
69fn fetch_css(dist_dir: &str, release_tag: &str) -> Result<()> {
70    match env::var("ORANDA_CSS") {
71        Ok(path) => {
72            let msg = format!("Overriding oranda_css path with {}", &path);
73            tracing::warn!("{}", &msg);
74            LocalAsset::copy(&path, dist_dir)?;
75            Ok(())
76        }
77        Err(_) => {
78            let filename = format!("oranda-{release_tag}.css");
79            let dest_path = Utf8Path::new(dist_dir).join(filename);
80
81            // Do we already have this value cached?
82            let cache_val = {
83                let cache = CSS_CACHE.read().expect("CSS Cache should not be poisoned");
84                cache
85                    .iter()
86                    .find(|elem| elem.release_tag.as_str() == release_tag)
87                    .map(|elem| elem.contents.clone())
88            };
89
90            let oranda_css_response = if let Some(c) = cache_val {
91                // Yes, we do!
92                c
93            } else {
94                // Nope, sure don't. Get it, and if we are successful, store it for next time.
95                let fresh =
96                    tokio::runtime::Handle::current().block_on(fetch_oranda(release_tag))?;
97
98                let mut cache = CSS_CACHE.write().expect("CSS Cache should not be poisoned");
99                cache.push(CssItem {
100                    release_tag: release_tag.to_string(),
101                    contents: fresh.clone(),
102                });
103                fresh
104            };
105
106            LocalAsset::write_new_all(&oranda_css_response, dest_path)?;
107            Ok(())
108        }
109    }
110}
111
112async fn fetch_oranda(release_tag: &str) -> Result<String> {
113    let oranda_css_request =
114        octolotl::request::ReleaseAsset::new("axodotdev", "oranda", release_tag, "oranda.css");
115    Ok(octolotl::Request::send(&oranda_css_request, true)
116        .await?
117        .text()
118        .await?)
119}
120
121fn get_css_filename(release_tag: &str) -> String {
122    if (release_tag == ORANDA_CSS_TAG && cfg!(css = "tailwind")) || env::var("ORANDA_CSS").is_ok() {
123        "oranda.css".into()
124    } else {
125        format!("oranda-{release_tag}.css")
126    }
127}
128
129pub fn write_additional_css(additional_css: &[String], dist_dir: &Utf8Path) -> Result<()> {
130    let minified_css = concat_minify(additional_css)?;
131
132    LocalAsset::write_new(&minified_css, dist_dir.join("custom.css"))?;
133    Ok(())
134}