diff --git a/examples/clock.rs b/examples/clock.rs index 4b7be3d3b..ba47d994a 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -1,8 +1,7 @@ use dioxus::prelude::*; use dioxus_signals::use_signal; -#[tokio::main] -async fn main() { +fn main() { dioxus_desktop::launch(app); } diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 0704fd0f0..45926e3f1 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -1,12 +1,12 @@ - - Dioxus app - - - - -
- - + + Dioxus app + + + + +
+ + diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index bdcefa473..be3c12231 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -5,10 +5,11 @@ use std::{ }; use wry::{ http::{status::StatusCode, Request, Response}, - Result, + RequestAsyncResponder, Result, }; static MINIFIED: &str = include_str!("./minified.js"); +static DEFAULT_INDEX: &str = include_str!("./index.html"); pub(super) async fn desktop_handler( request: Request>, @@ -18,120 +19,110 @@ pub(super) async fn desktop_handler( asset_handlers: &AssetHandlerRegistry, edit_queue: &EditQueue, headless: bool, - responder: wry::RequestAsyncResponder, + responder: RequestAsyncResponder, ) { let request = AssetRequest::from(request); // If the request is for the root, we'll serve the index.html file. if request.uri().path() == "/" { - // 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 tag and insert our little module loader there. - let body = match custom_index { - Some(custom_index) => custom_index - .replace( - "", - &format!("{}", module_loader(root_name, headless)), - ) - .into_bytes(), - - None => { - // 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); - } - - template - .replace( - "", - &module_loader(root_name, headless), - ) - .into_bytes() - } - }; - - match Response::builder() - .header("Content-Type", "text/html") - .header("Access-Control-Allow-Origin", "*") - .body(Cow::from(body)) - { - Ok(response) => { - responder.respond(response); - return; - } - Err(err) => tracing::error!("error building response: {}", err), + match build_index_file(custom_index, custom_head, root_name, headless) { + Ok(response) => return responder.respond(response), + Err(err) => return tracing::error!("error building response: {}", err), } - } else if request.uri().path().trim_matches('/') == "edits" { - edit_queue.handle_request(responder); - return; } - // If the user provided a custom asset handler, then call it and return the response - // if the request was handled. + // If the request is asking for edits (ie binary protocol streaming, do that) + if request.uri().path().trim_matches('/') == "edits" { + return edit_queue.handle_request(responder); + } + + // If the user provided a custom asset handler, then call it and return the response if the request was handled. if let Some(response) = asset_handlers.try_handlers(&request).await { - responder.respond(response); - return; + return responder.respond(response); } // Else, try to serve a file from the filesystem. + match serve_from_fs(request) { + Ok(res) => responder.respond(res), + Err(e) => tracing::error!("Error serving request from filesystem {}", e), + } +} +fn serve_from_fs(request: AssetRequest) -> Result { // If the path is relative, we'll try to serve it from the assets directory. let mut asset = get_asset_root() .unwrap_or_else(|| Path::new(".").to_path_buf()) .join(&request.path); + // If we can't find it, make it absolute and try again if !asset.exists() { asset = PathBuf::from("/").join(request.path); } if asset.exists() { - let content_type = match get_mime_from_path(&asset) { - Ok(content_type) => content_type, - Err(err) => { - tracing::error!("error getting mime type: {}", err); - return; - } - }; - let asset = match std::fs::read(asset) { - Ok(asset) => asset, - Err(err) => { - tracing::error!("error reading asset: {}", err); - return; - } - }; - match Response::builder() - .header("Content-Type", content_type) - .body(Cow::from(asset)) - { - Ok(response) => { - responder.respond(response); - return; - } - Err(err) => tracing::error!("error building response: {}", err), - } - } + let content_type = get_mime_from_path(&asset)?; + let asset = std::fs::read(asset)?; - match Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Cow::from(String::from("Not Found").into_bytes())) - { - Ok(response) => { - responder.respond(response); - return; - } - Err(err) => tracing::error!("error building response: {}", err), + Ok(Response::builder() + .header("Content-Type", content_type) + .body(Cow::from(asset))?) + } else { + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Cow::from(String::from("Not Found").into_bytes()))?) } } -fn module_loader(root_name: &str, headless: bool) -> String { +/// Build the index.html file we use for bootstrapping a new app +/// +/// We use wry/webview by building a special index.html that forms a bridge between the webview and your rust code +/// +/// This is similar to tauri, except we give more power to your rust code and less power to your frontend code. +/// This lets us skip a build/bundle step - your code just works - but limits how your Rust code can actually +/// mess with UI elements. We make this decision since other renderers like LiveView are very separate and can +/// never properly bridge the gap. Eventually of course, the idea is to build a custom CSS/HTML renderer where you +/// *do* have native control over elements, but that still won't work with liveview. +fn build_index_file( + custom_index: Option, + custom_head: Option, + root_name: &str, + headless: bool, +) -> std::result::Result>, wry::http::Error> { + // Load a custom index file if provided + let mut index = custom_index.unwrap_or_else(|| DEFAULT_INDEX.to_string()); + + // Insert a custom head if provided + // We look just for the closing head tag. If a user provided a custom index with weird syntax, this might fail + if let Some(head) = custom_head { + index.insert_str(index.find("").expect("Head element to exist"), &head); + } + + // Inject our module loader + index.insert_str( + index.find("").expect("Body element to exist"), + &module_loader(root_name, headless), + ); + + Response::builder() + .header("Content-Type", "text/html") + .header("Access-Control-Allow-Origin", "*") + .body(Cow::from(index.into_bytes())) +} + +/// Construct the inline script that boots up the page and bridges the webview with rust code. +/// +/// The arguments here: +/// - root_name: the root element (by Id) that we stream edits into +/// - headless: is this page being loaded but invisible? Important because not all windows are visible and the +/// interpreter can't connect until the window is ready. +fn module_loader(root_id: &str, headless: bool) -> String { format!( r#"