Merge pull request #353 from DioxusLabs/jk/custom-index-head
feat: allow customizing the index and head
This commit is contained in:
7 files changed
+108
-14
No files matched your search
@@ -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!"}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in new issue
Block a user