| 1 |
|
| 2 |
|
| 3 |
use axum::extract::Path; |
| 4 |
use axum::http::{header, StatusCode}; |
| 5 |
use axum::response::{IntoResponse, Response}; |
| 6 |
|
| 7 |
const CSS: &str = include_str!("../../../../templates/style.css"); |
| 8 |
const FAVICON: &str = include_str!("../../../../assets/icons/oxygenhub.svg"); |
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
pub fn assets_version() -> &'static str { |
| 15 |
static V: std::sync::OnceLock<String> = std::sync::OnceLock::new(); |
| 16 |
V.get_or_init(|| { |
| 17 |
let mut h = blake3::Hasher::new(); |
| 18 |
h.update(CSS.as_bytes()); |
| 19 |
for a in ASSETS.iter().filter(|a| { |
| 20 |
matches!( |
| 21 |
a.path, |
| 22 |
"github.css" | "hub.js" | "report-frame.js" | "report.css" | "backdrop/backdrop.js" | "backdrop/backdrop_bg.wasm" |
| 23 |
) |
| 24 |
}) { |
| 25 |
h.update(a.bytes); |
| 26 |
} |
| 27 |
h.finalize().to_hex()[..12].to_string() |
| 28 |
}) |
| 29 |
} |
| 30 |
|
| 31 |
struct Asset { |
| 32 |
path: &'static str, |
| 33 |
mime: &'static str, |
| 34 |
bytes: &'static [u8], |
| 35 |
} |
| 36 |
|
| 37 |
const ASSETS: &[Asset] = &[ |
| 38 |
Asset { path: "fonts/Barlow-300.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-300.woff2") }, |
| 39 |
Asset { path: "fonts/Barlow-400.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-400.woff2") }, |
| 40 |
Asset { path: "fonts/Barlow-500.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-500.woff2") }, |
| 41 |
Asset { path: "fonts/Barlow-600.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-600.woff2") }, |
| 42 |
Asset { |
| 43 |
path: "fonts/PressStart2P-400.woff2", |
| 44 |
mime: "font/woff2", |
| 45 |
bytes: include_bytes!("../../../../assets/fonts/PressStart2P-400.woff2"), |
| 46 |
}, |
| 47 |
Asset { path: "github.css", mime: "text/css; charset=utf-8", bytes: include_bytes!("../../../../assets/github.css") }, |
| 48 |
Asset { path: "hub.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/hub.js") }, |
| 49 |
|
| 50 |
|
| 51 |
Asset { path: "report-frame.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/report-frame.js") }, |
| 52 |
Asset { path: "report.css", mime: "text/css; charset=utf-8", bytes: include_bytes!("../../../../assets/report.css") }, |
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
Asset { |
| 57 |
path: "backdrop/backdrop.js", |
| 58 |
mime: "text/javascript; charset=utf-8", |
| 59 |
bytes: include_bytes!("../../../../assets/backdrop/backdrop.js"), |
| 60 |
}, |
| 61 |
Asset { |
| 62 |
path: "backdrop/backdrop_bg.wasm", |
| 63 |
mime: "application/wasm", |
| 64 |
bytes: include_bytes!("../../../../assets/backdrop/backdrop_bg.wasm"), |
| 65 |
}, |
| 66 |
Asset { |
| 67 |
path: "vendor/editor.js", |
| 68 |
mime: "text/javascript; charset=utf-8", |
| 69 |
bytes: include_bytes!("../../../../assets/vendor/editor.js"), |
| 70 |
}, |
| 71 |
Asset { |
| 72 |
path: "vendor/easymde.min.js", |
| 73 |
mime: "text/javascript; charset=utf-8", |
| 74 |
bytes: include_bytes!("../../../../assets/vendor/easymde.min.js"), |
| 75 |
}, |
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
Asset { |
| 80 |
path: "vendor/mermaid.min.js", |
| 81 |
mime: "text/javascript; charset=utf-8", |
| 82 |
bytes: include_bytes!("../../../../assets/vendor/mermaid.min.js"), |
| 83 |
}, |
| 84 |
Asset { |
| 85 |
path: "vendor/easymde.min.css", |
| 86 |
mime: "text/css; charset=utf-8", |
| 87 |
bytes: include_bytes!("../../../../assets/vendor/easymde.min.css"), |
| 88 |
}, |
| 89 |
]; |
| 90 |
|
| 91 |
const CACHE: &str = "public, max-age=86400"; |
| 92 |
|
| 93 |
pub async fn style() -> Response { |
| 94 |
([(header::CONTENT_TYPE, "text/css; charset=utf-8"), (header::CACHE_CONTROL, CACHE)], CSS).into_response() |
| 95 |
} |
| 96 |
|
| 97 |
pub async fn favicon() -> Response { |
| 98 |
([(header::CONTENT_TYPE, "image/svg+xml"), (header::CACHE_CONTROL, CACHE)], FAVICON).into_response() |
| 99 |
} |
| 100 |
|
| 101 |
pub async fn asset(Path(path): Path<String>) -> Response { |
| 102 |
match ASSETS.iter().find(|a| a.path == path) { |
| 103 |
|
| 104 |
|
| 105 |
Some(a) => ( |
| 106 |
[ |
| 107 |
(header::CONTENT_TYPE, a.mime), |
| 108 |
(header::CACHE_CONTROL, "public, max-age=31536000, immutable"), |
| 109 |
(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"), |
| 110 |
], |
| 111 |
a.bytes, |
| 112 |
) |
| 113 |
.into_response(), |
| 114 |
None => StatusCode::NOT_FOUND.into_response(), |
| 115 |
} |
| 116 |
} |