feat: allow customizing the index and head

This commit is contained in:
Jonathan Kelley 2022-04-16 16:53:47 -04:00
parent ab4f75e4ea
commit 049976d23a
6 changed files with 107 additions and 12 deletions

37
examples/custom_html.rs Normal file
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!"}
}
})
}

View file

@ -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 {

View file

@ -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))
}

View file

@ -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>

View file

@ -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

View file

@ -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")