More fullstack fixes (#4737)

* allow `--server` only builds!

* rollback example

* capture `/:route` routes

* fix: properly implement file uploads in forms

* implement desktop form uploads

* Performant serialization of form bytes

* finish up file stuff

* cleanups

* add request headers to streaming context

* remove some unwraps
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-07 23:07:43 -07:00
1 parent fc01a002fd
commit 92792aded9
47 files changed
+1241 -703

No files matched your search

+91 -7
View File
@@ -1,5 +1,8 @@
use crate::document::NATIVE_EVAL_JS;
use crate::{assets::*, webview::WebviewEdits};
use crate::{document::NATIVE_EVAL_JS, file_upload::FileDialogRequest};
use base64::prelude::BASE64_STANDARD;
use dioxus_core::AnyhowContext;
use dioxus_html::{SerializedFileData, SerializedFormObject};
use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
use dioxus_interpreter_js::NATIVE_JS;
use wry::{
@@ -8,13 +11,13 @@ use wry::{
};
#[cfg(target_os = "android")]
const EVENTS_PATH: &str = "https://dioxus.index.html/__events";
const BASE_URI: &str = "https://dioxus.index.html/";
#[cfg(target_os = "windows")]
const EVENTS_PATH: &str = "http://dioxus.index.html/__events";
const BASE_URI: &str = "http://dioxus.index.html/";
#[cfg(not(any(target_os = "android", target_os = "windows")))]
const EVENTS_PATH: &str = "dioxus://index.html/__events";
const BASE_URI: &str = "dioxus://index.html/";
#[cfg(debug_assertions)]
static DEFAULT_INDEX: &str = include_str!("./assets/dev.index.html");
@@ -58,6 +61,14 @@ pub(super) fn desktop_handler(
return edit_state.handle_event(request, responder);
}
// If the request is asking for a file dialog, handle that, returning the list of files selected
if trimmed_uri == "__file_dialog" {
if let Err(err) = file_dialog_responder(request, responder) {
tracing::error!("Failed to handle file dialog request: {err:?}");
}
return;
}
// todo: we want to move the custom assets onto a different protocol or something
if let Some(name) = request.uri().path().split('/').nth(1) {
if asset_handlers.has_handler(name) {
@@ -104,7 +115,6 @@ fn index_request(
// 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("</head>").expect("Head element to exist"), &head);
}
@@ -147,14 +157,14 @@ fn module_loader(root_id: &str, headless: bool, edit_state: &WebviewEdits) -> St
{NATIVE_JS}
// The native interpreter extends the sledgehammer interpreter with a few extra methods that we use for IPC
window.interpreter = new NativeInterpreter("{EVENTS_PATH}", {headless});
window.interpreter = new NativeInterpreter("{BASE_URI}", {headless});
// Wait for the page to load before sending the initialize message
window.onload = function() {{
let root_element = window.document.getElementById("{root_id}");
if (root_element != null) {{
window.interpreter.initialize(root_element);
window.ipc.postMessage(window.interpreter.serializeIpcMessage("initialize"));
window.interpreter.sendIpcMessage("initialize");
}}
window.interpreter.waitForRequest("{edits_path}", "{expected_key}");
}}
@@ -166,3 +176,77 @@ fn module_loader(root_id: &str, headless: bool, edit_state: &WebviewEdits) -> St
"#
)
}
fn file_dialog_responder(
request: wry::http::Request<Vec<u8>>,
responder: wry::RequestAsyncResponder,
) -> dioxus_core::Result<()> {
// Handle the file dialog request
// We can't use the body, just the headers
let header = request
.headers()
.get("x-dioxus-data")
.context("Failed to get x-dioxus-data header")?;
let data_from_header = base64::Engine::decode(&BASE64_STANDARD, header.as_bytes())
.context("Failed to decode x-dioxus-data header from base64")?;
let mut file_dialog: FileDialogRequest = serde_json::from_slice(&data_from_header)
.context("Failed to parse x-dioxus-data header as JSON")?;
let file_list = file_dialog.get_file_event();
let position_of_entry = file_dialog
.values
.iter()
.position(|x| x.key == file_dialog.target_name)
.context("Failed to find target_name in values")?;
// Remove any existing entries
file_dialog
.values
.retain(|x| x.key != file_dialog.target_name);
// And then insert the new ones
for path in file_list {
let file = std::fs::metadata(&path).context("Failed to get file metadata")?;
file_dialog.values.insert(
position_of_entry,
SerializedFormObject {
key: file_dialog.target_name.clone(),
text: None,
file: Some(SerializedFileData {
size: file.len(),
last_modified: file
.modified()
.context("Failed to get file modified time")?
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or_default() as _,
content_type: Some(
dioxus_asset_resolver::native::get_mime_from_ext(
path.extension().and_then(|s| s.to_str()),
)
.to_string(),
),
contents: Default::default(),
path,
}),
},
);
}
// And then respond with the updated file dialog
let response_data = serde_json::to_vec(&file_dialog)
.context("Failed to serialize FileDialogRequest to JSON")?;
responder.respond(
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/json")
.body(response_data)
.context("Failed to build response")?,
);
Ok(())
}