1+use actix_files::NamedFile;
2+use actix_web::{HttpRequest, HttpResponse, Scope, web};
3+use http::StatusCode;
4+use tera::Context;
5+use crate::util::template_utils::TEMPLATES;
6+7+struct SinglePageApplication {
8+view_name: String
9+}
10+11+/// 'route': the route where the SPA should be served from, for example: "/app"
12+/// 'view': the view which renders the SPA, for example: "spa/index.html"
13+pub fn render_single_page_application(route: String, view: String) -> Scope {
14+use actix_web::web::Data;
15+16+let route = route.strip_prefix("/").unwrap();
17+let view = view.strip_prefix("/").unwrap();
18+19+ actix_web::web::scope(&format!("/{}{{tail:(/.*)?}}", route))
20+.app_data(Data::new(SinglePageApplication {
21+view_name: view.to_string()
22+}))
23+.route("", web::get().to(render_spa_handler))
24+}
25+26+async fn render_spa_handler(req: HttpRequest, spa_info: web::Data<SinglePageApplication>) -> HttpResponse {
27+let content = TEMPLATES.render(spa_info.view_name.as_str(), &Context::new()).unwrap();
28+template_response(content)
29+}
30+31+const DEFAULT_TEMPLATE: &'static str = "index.html";
32+fn to_template_name(request_path: &str) -> &'_ str {
33+let request_path = request_path.strip_prefix("/").unwrap();
34+return if request_path.eq("") { DEFAULT_TEMPLATE } else { request_path }
35+}
36+37+pub async fn render_views(req: HttpRequest) -> HttpResponse {
38+let path = req.path();
39+40+if path.eq("/__vite_ping") {
41+println!("The vite dev server seems to be down...");
42+return HttpResponse::NotFound().finish();
43+}
44+45+let mut template_path = to_template_name(req.path());
46+let mut content_result = TEMPLATES.render(template_path, &Context::new());
47+48+if content_result.is_err() {
49+#[cfg(debug_assertions)] {
50+// dev asset serving
51+let asset_path = &format!("./frontend{path}");
52+if std::path::PathBuf::from(asset_path).is_file() {
53+println!("ASSET_FILE {path} => {asset_path}");
54+return NamedFile::open(asset_path).unwrap().into_response(&req)
55+}
56+57+let public_path = &format!("./frontend/public{path}");
58+if std::path::PathBuf::from(public_path).is_file() {
59+println!("PUBLIC_FILE {path} => {public_path}");
60+return NamedFile::open(public_path).unwrap().into_response(&req)
61+}
62+}
63+64+#[cfg(not(debug_assertions))] {
65+// production asset serving
66+let static_path = &format!("./frontend/dist{path}");
67+if std::path::PathBuf::from(static_path).is_file() {
68+return NamedFile::open(static_path).unwrap().into_response(&req);
69+}
70+}
71+72+ content_result = TEMPLATES.render(DEFAULT_TEMPLATE, &Context::new());
73+ template_path = DEFAULT_TEMPLATE;
74+if content_result.is_err() {
75+// default template doesn't exist -- return 404 not found
76+return HttpResponse::NotFound().finish()
77+}
78+}
79+80+println!("TEMPLATE_FILE {path} => {template_path}");
81+82+let content = content_result.unwrap();
83+84+template_response(content)
85+}
86+87+fn template_response(content: String) -> HttpResponse {
88+let mut content = content;
89+#[cfg(debug_assertions)] {
90+let inject: &str = r##"
91+ <!-- development mode -->
92+ <script type="module">
93+ import RefreshRuntime from 'http://localhost:3000/@react-refresh'
94+ RefreshRuntime.injectIntoGlobalHook(window)
95+ window.$RefreshReg$ = () => {}
96+ window.$RefreshSig$ = () => (type) => type
97+ window.__vite_plugin_react_preamble_installed__ = true
98+ </script>
99+ <script type="module" src="http://localhost:3000/src/main.tsx"></script>
100+ "##;
101+102+if content.contains("<body>") {
103+ content = content.replace("<body>", &format!("<body>{inject}"));
104+} else {
105+ content = format!("{inject}{content}");
106+}
107+}
108+109+HttpResponse::build(StatusCode::OK)
110+.content_type("text/html")
111+.body(content)
112+}