Przeglądaj źródła

feat: allow customizing the index and head

Jonathan Kelley 3 lat temu
rodzic
commit
049976d23a

+ 37 - 0
examples/custom_html.rs

@@ -0,0 +1,37 @@
+//! This example shows how to use a custom index.html and custom <HEAD> extensions
+//! to add things like stylesheets, scripts, and third-party JS libraries.
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch_cfg(app, |c| {
+        c.with_custom_head("<style>body { background-color: red; }</style>".into())
+    });
+
+    dioxus::desktop::launch_cfg(app, |c| {
+        c.with_custom_index(
+            r#"
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Dioxus app</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <style>body { background-color: blue; }</style>
+  </head>
+  <body>
+    <div id="main"></div>
+  </body>
+</html>
+        "#
+            .into(),
+        )
+    });
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            h1 {"hello world!"}
+        }
+    })
+}

+ 26 - 0
packages/desktop/src/cfg.rs

@@ -21,6 +21,8 @@ pub struct DesktopConfig {
     pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
     pub(crate) disable_context_menu: bool,
     pub(crate) resource_dir: Option<PathBuf>,
+    pub(crate) custom_head: Option<String>,
+    pub(crate) custom_index: Option<String>,
 }
 
 pub(crate) type WryProtocol = (
@@ -42,6 +44,8 @@ impl DesktopConfig {
             pre_rendered: None,
             disable_context_menu: !cfg!(debug_assertions),
             resource_dir: None,
+            custom_head: None,
+            custom_index: None,
         }
     }
 
@@ -100,10 +104,32 @@ impl DesktopConfig {
         self
     }
 
+
+    /// Add a custom icon for this application
     pub fn with_icon(&mut self, icon: Icon) -> &mut Self {
         self.window.window.window_icon = Some(icon);
         self
     }
+
+
+    /// Inject additional content into the document's HEAD.
+    ///
+    /// This is useful for loading CSS libraries, JS libraries, etc.
+    pub fn with_custom_head(&mut self, head: String) -> &mut Self {
+        self.custom_head = Some(head);
+        self
+    }
+
+    /// Use a custom index.html instead of the default Dioxus one.
+    ///
+    /// Make sure your index.html is valid HTML.
+    ///
+    /// Dioxus injects some loader code into the closing body tag. Your document
+    /// must include a body element!
+    pub fn with_custom_index(&mut self, index: String) -> &mut Self {
+        self.custom_index = Some(index);
+        self
+    }
 }
 
 impl DesktopConfig {

+ 1 - 1
packages/desktop/src/desktop_context.rs

@@ -201,7 +201,7 @@ pub(super) fn handler(
 
 /// Get a closure that executes any JavaScript in the WebView context.
 pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) {
-    let desktop = use_window(&cx).clone();
+    let desktop = use_window(cx).clone();
 
     cx.use_hook(|_| move |script| desktop.eval(script))
 }

+ 2 - 5
packages/desktop/src/index.html

@@ -3,13 +3,10 @@
   <head>
     <title>Dioxus app</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <!-- CUSTOM HEAD -->
   </head>
   <body>
     <div id="main"></div>
-    <script>
-      import("./index.js").then(function (module) {
-        module.main();
-      });
-    </script>
+    <!-- MODULE LOADER -->
   </body>
 </html>

+ 8 - 2
packages/desktop/src/lib.rs

@@ -125,8 +125,9 @@ pub fn launch_with_props<P: 'static + Send>(
                 let proxy = proxy.clone();
 
                 let file_handler = cfg.file_drop_handler.take();
-
+                let custom_head = cfg.custom_head.clone();
                 let resource_dir = cfg.resource_dir.clone();
+                let index_file = cfg.custom_index.clone();
 
                 let mut webview = WebViewBuilder::new(window)
                     .unwrap()
@@ -164,7 +165,12 @@ pub fn launch_with_props<P: 'static + Send>(
                             });
                     })
                     .with_custom_protocol(String::from("dioxus"), move |r| {
-                        protocol::desktop_handler(r, resource_dir.clone())
+                        protocol::desktop_handler(
+                            r,
+                            resource_dir.clone(),
+                            custom_head.clone(),
+                            index_file.clone(),
+                        )
                     })
                     .with_file_drop_handler(move |window, evet| {
                         file_handler

+ 33 - 4
packages/desktop/src/protocol.rs

@@ -4,7 +4,20 @@ use wry::{
     Result,
 };
 
-pub(super) fn desktop_handler(request: &Request, asset_root: Option<PathBuf>) -> Result<Response> {
+const MODULE_LOADER: &str = r#"
+<script>
+    import("./index.js").then(function (module) {
+    module.main();
+    });
+</script>
+"#;
+
+pub(super) fn desktop_handler(
+    request: &Request,
+    asset_root: Option<PathBuf>,
+    custom_head: Option<String>,
+    custom_index: Option<String>,
+) -> Result<Response> {
     // Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case".
     // For now, we only serve two pieces of content which get included as bytes into the final binary.
     let path = request.uri().replace("dioxus://", "");
@@ -13,9 +26,25 @@ pub(super) fn desktop_handler(request: &Request, asset_root: Option<PathBuf>) ->
     let trimmed = path.trim_start_matches("index.html/");
 
     if trimmed.is_empty() {
-        ResponseBuilder::new()
-            .mimetype("text/html")
-            .body(include_bytes!("./index.html").to_vec())
+        // If a custom index is provided, just defer to that, expecting the user to know what they're doing.
+        // we'll look for the closing </body> tag and insert our little module loader there.
+        if let Some(custom_index) = custom_index {
+            let rendered = custom_index
+                .replace("</body>", &format!("{}</body>", MODULE_LOADER))
+                .into_bytes();
+            ResponseBuilder::new().mimetype("text/html").body(rendered)
+        } else {
+            // Otherwise, we'll serve the default index.html and apply a custom head if that's specified.
+            let mut template = include_str!("./index.html").to_string();
+            if let Some(custom_head) = custom_head {
+                template = template.replace("<!-- CUSTOM HEAD -->", &custom_head);
+            }
+            template = template.replace("<!-- MODULE LOADER -->", MODULE_LOADER);
+
+            ResponseBuilder::new()
+                .mimetype("text/html")
+                .body(template.into_bytes())
+        }
     } else if trimmed == "index.js" {
         ResponseBuilder::new()
             .mimetype("text/javascript")