Merge pull request #353 from DioxusLabs/jk/custom-index-head

feat: allow customizing the index and head
This commit is contained in:
Jon Kelley authored and GitHub committed 2022-04-18 16:35:56 -04:00
commit c9525966a2
7 files changed
+108 -14

No files matched your search

+37
View File
@@ -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!"}
}
})
}
+3 -2
View File
@@ -11,12 +11,13 @@
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
dioxus::desktop::launch(app); dioxus::desktop::launch_cfg(app, |c| {
c.with_custom_head("<script src=\"https://cdn.tailwindcss.com\"></script>".to_string())
});
} }
pub fn app(cx: Scope) -> Element { pub fn app(cx: Scope) -> Element {
cx.render(rsx!( cx.render(rsx!(
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
div { div {
header { class: "text-gray-400 bg-gray-900 body-font", header { class: "text-gray-400 bg-gray-900 body-font",
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
+24
View File
@@ -21,6 +21,8 @@ pub struct DesktopConfig {
pub(crate) event_handler: Option<Box<DynEventHandlerFn>>, pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
pub(crate) disable_context_menu: bool, pub(crate) disable_context_menu: bool,
pub(crate) resource_dir: Option<PathBuf>, pub(crate) resource_dir: Option<PathBuf>,
pub(crate) custom_head: Option<String>,
pub(crate) custom_index: Option<String>,
} }
pub(crate) type WryProtocol = ( pub(crate) type WryProtocol = (
@@ -42,6 +44,8 @@ impl DesktopConfig {
pre_rendered: None, pre_rendered: None,
disable_context_menu: !cfg!(debug_assertions), disable_context_menu: !cfg!(debug_assertions),
resource_dir: None, resource_dir: None,
custom_head: None,
custom_index: None,
} }
} }
@@ -100,10 +104,30 @@ impl DesktopConfig {
self self
} }
/// Add a custom icon for this application
pub fn with_icon(&mut self, icon: Icon) -> &mut Self { pub fn with_icon(&mut self, icon: Icon) -> &mut Self {
self.window.window.window_icon = Some(icon); self.window.window.window_icon = Some(icon);
self 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 { impl DesktopConfig {
+1 -1
View File
@@ -201,7 +201,7 @@ pub(super) fn handler(
/// Get a closure that executes any JavaScript in the WebView context. /// Get a closure that executes any JavaScript in the WebView context.
pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) { 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)) cx.use_hook(|_| move |script| desktop.eval(script))
} }
+2 -5
View File
@@ -3,13 +3,10 @@
<head> <head>
<title>Dioxus app</title> <title>Dioxus app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- CUSTOM HEAD -->
</head> </head>
<body> <body>
<div id="main"></div> <div id="main"></div>
<script> <!-- MODULE LOADER -->
import("./index.js").then(function (module) {
module.main();
});
</script>
</body> </body>
</html> </html>
+8 -2
View File
@@ -125,8 +125,9 @@ pub fn launch_with_props<P: 'static + Send>(
let proxy = proxy.clone(); let proxy = proxy.clone();
let file_handler = cfg.file_drop_handler.take(); let file_handler = cfg.file_drop_handler.take();
let custom_head = cfg.custom_head.clone();
let resource_dir = cfg.resource_dir.clone(); let resource_dir = cfg.resource_dir.clone();
let index_file = cfg.custom_index.clone();
let mut webview = WebViewBuilder::new(window) let mut webview = WebViewBuilder::new(window)
.unwrap() .unwrap()
@@ -164,7 +165,12 @@ pub fn launch_with_props<P: 'static + Send>(
}); });
}) })
.with_custom_protocol(String::from("dioxus"), move |r| { .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| { .with_file_drop_handler(move |window, evet| {
file_handler file_handler
+33 -4
View File
@@ -4,7 +4,20 @@ use wry::{
Result, 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". // 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. // For now, we only serve two pieces of content which get included as bytes into the final binary.
let path = request.uri().replace("dioxus://", ""); 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/"); let trimmed = path.trim_start_matches("index.html/");
if trimmed.is_empty() { if trimmed.is_empty() {
ResponseBuilder::new() // If a custom index is provided, just defer to that, expecting the user to know what they're doing.
.mimetype("text/html") // we'll look for the closing </body> tag and insert our little module loader there.
.body(include_bytes!("./index.html").to_vec()) 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" { } else if trimmed == "index.js" {
ResponseBuilder::new() ResponseBuilder::new()
.mimetype("text/javascript") .mimetype("text/javascript")