Skip to main content

sim_web_shell/
assets.rs

1//! Embedded WebUI assets.
2//!
3//! The shell and cookbook pages are embedded at compile time so the binary
4//! serves a complete WebUI regardless of the working directory. The current
5//! bundle paints Scene data and emits Intent values; a live runtime session
6//! bridge can replace or extend these assets without changing this routing
7//! contract.
8
9/// A static asset resolved for an HTTP path: its bytes and MIME content type.
10pub struct Asset {
11    /// The response body.
12    pub body: &'static [u8],
13    /// The `Content-Type` header value.
14    pub content_type: &'static str,
15}
16
17const INDEX_HTML: &str = include_str!("../web/index.html");
18const COOKBOOK_HTML: &str = include_str!("../web/cookbook.html");
19const ATELIER_HTML: &str = include_str!("../web/atelier.html");
20const THEME_CSS: &str = include_str!("../web/styles/theme.css");
21const COOKBOOK_CSS: &str = include_str!("../web/cookbook/cookbook.css");
22const ATELIER_CSS: &str = include_str!("../web/atelier/atelier.css");
23const BOOT_JS: &str = include_str!("../web/interpreter/boot.js");
24const APP_JS: &str = include_str!("../web/interpreter/app.js");
25const GLASSES_JS: &str = include_str!("../web/interpreter/glasses.js");
26const SCENE_JS: &str = include_str!("../web/interpreter/scene.js");
27const HEATMAP_JS: &str = include_str!("../web/interpreter/heatmap.js");
28const DIFF_JS: &str = include_str!("../web/interpreter/diff.js");
29const INTENT_JS: &str = include_str!("../web/interpreter/intent.js");
30const KEYMAP_JS: &str = include_str!("../web/interpreter/keymap.js");
31const SESSION_JS: &str = include_str!("../web/interpreter/session.js");
32const PWA_JS: &str = include_str!("../web/interpreter/pwa.js");
33const COOKBOOK_JS: &str = include_str!("../web/cookbook/cookbook.js");
34const ATELIER_JS: &str = include_str!("../web/atelier/atelier.js");
35const SERVICE_WORKER_JS: &str = include_str!("../web/sw.js");
36const WEB_MANIFEST: &str = include_str!("../web/manifest.webmanifest");
37const ICON_SVG: &str = include_str!("../web/assets/icon.svg");
38const ICON_MASKABLE_SVG: &str = include_str!("../web/assets/icon-maskable.svg");
39
40const JS_CONTENT_TYPE: &str = "text/javascript; charset=utf-8";
41
42/// Resolve a request path to an embedded asset, or `None` for a 404.
43///
44/// `/` and `/index.html` both resolve to the shell page. Only the small set of
45/// embedded assets is routable; everything else is a miss and the server fails
46/// closed with a 404 rather than touching the filesystem.
47pub fn asset_for(path: &str) -> Option<Asset> {
48    let path = path.split(['?', '#']).next().unwrap_or(path);
49    match path {
50        "/" | "/index.html" => Some(Asset {
51            body: INDEX_HTML.as_bytes(),
52            content_type: "text/html; charset=utf-8",
53        }),
54        "/cookbook" | "/cookbook.html" => Some(Asset {
55            body: COOKBOOK_HTML.as_bytes(),
56            content_type: "text/html; charset=utf-8",
57        }),
58        "/atelier" | "/atelier.html" => Some(Asset {
59            body: ATELIER_HTML.as_bytes(),
60            content_type: "text/html; charset=utf-8",
61        }),
62        "/styles/theme.css" => Some(Asset {
63            body: THEME_CSS.as_bytes(),
64            content_type: "text/css; charset=utf-8",
65        }),
66        "/cookbook/cookbook.css" => Some(Asset {
67            body: COOKBOOK_CSS.as_bytes(),
68            content_type: "text/css; charset=utf-8",
69        }),
70        "/atelier/atelier.css" => Some(Asset {
71            body: ATELIER_CSS.as_bytes(),
72            content_type: "text/css; charset=utf-8",
73        }),
74        "/interpreter/boot.js" => Some(Asset {
75            body: BOOT_JS.as_bytes(),
76            content_type: JS_CONTENT_TYPE,
77        }),
78        "/interpreter/app.js" => Some(Asset {
79            body: APP_JS.as_bytes(),
80            content_type: JS_CONTENT_TYPE,
81        }),
82        "/interpreter/glasses.js" => Some(Asset {
83            body: GLASSES_JS.as_bytes(),
84            content_type: JS_CONTENT_TYPE,
85        }),
86        "/interpreter/scene.js" => Some(Asset {
87            body: SCENE_JS.as_bytes(),
88            content_type: JS_CONTENT_TYPE,
89        }),
90        "/interpreter/heatmap.js" => Some(Asset {
91            body: HEATMAP_JS.as_bytes(),
92            content_type: JS_CONTENT_TYPE,
93        }),
94        "/interpreter/diff.js" => Some(Asset {
95            body: DIFF_JS.as_bytes(),
96            content_type: JS_CONTENT_TYPE,
97        }),
98        "/interpreter/intent.js" => Some(Asset {
99            body: INTENT_JS.as_bytes(),
100            content_type: JS_CONTENT_TYPE,
101        }),
102        "/interpreter/keymap.js" => Some(Asset {
103            body: KEYMAP_JS.as_bytes(),
104            content_type: JS_CONTENT_TYPE,
105        }),
106        "/interpreter/session.js" => Some(Asset {
107            body: SESSION_JS.as_bytes(),
108            content_type: JS_CONTENT_TYPE,
109        }),
110        "/interpreter/pwa.js" => Some(Asset {
111            body: PWA_JS.as_bytes(),
112            content_type: JS_CONTENT_TYPE,
113        }),
114        "/sw.js" => Some(Asset {
115            body: SERVICE_WORKER_JS.as_bytes(),
116            content_type: JS_CONTENT_TYPE,
117        }),
118        "/manifest.webmanifest" => Some(Asset {
119            body: WEB_MANIFEST.as_bytes(),
120            content_type: "application/manifest+json; charset=utf-8",
121        }),
122        "/assets/icon.svg" => Some(Asset {
123            body: ICON_SVG.as_bytes(),
124            content_type: "image/svg+xml",
125        }),
126        "/assets/icon-maskable.svg" => Some(Asset {
127            body: ICON_MASKABLE_SVG.as_bytes(),
128            content_type: "image/svg+xml",
129        }),
130        "/cookbook/cookbook.js" => Some(Asset {
131            body: COOKBOOK_JS.as_bytes(),
132            content_type: JS_CONTENT_TYPE,
133        }),
134        "/atelier/atelier.js" => Some(Asset {
135            body: ATELIER_JS.as_bytes(),
136            content_type: JS_CONTENT_TYPE,
137        }),
138        _ => None,
139    }
140}