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:
1 parent
fc01a002fd
commit
92792aded9
47 files changed
+1241
-703
No files matched your search
@@ -1,5 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::html::geometry::euclid::Vector3D;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::DesktopContext;
|
||||
@@ -448,7 +446,7 @@ fn test_focus_out_div() -> Element {
|
||||
}
|
||||
|
||||
fn test_form_input() -> Element {
|
||||
let mut values = use_signal(HashMap::new);
|
||||
let mut values = use_signal(Vec::new);
|
||||
|
||||
utils::mock_event_with_extra(
|
||||
"form-username",
|
||||
@@ -464,10 +462,10 @@ fn test_form_input() -> Element {
|
||||
|
||||
// And then the value the form gives us should also match
|
||||
values.with_mut(|x| {
|
||||
assert_eq!(x.get("username").unwrap(), "hello");
|
||||
assert_eq!(x.get("full-name").unwrap(), "lorem");
|
||||
assert_eq!(x.get("password").unwrap(), "ipsum");
|
||||
assert_eq!(x.get("color").unwrap(), "red");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "username").unwrap().1, "hello");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "full-name").unwrap().1, "lorem");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "password").unwrap().1, "ipsum");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "color").unwrap().1, "red");
|
||||
});
|
||||
RECEIVED_EVENTS.with_mut(|x| *x += 1);
|
||||
};
|
||||
@@ -505,7 +503,7 @@ fn test_form_input() -> Element {
|
||||
}
|
||||
|
||||
fn test_form_submit() -> Element {
|
||||
let mut values = use_signal(HashMap::new);
|
||||
let mut values = use_signal(Vec::new);
|
||||
|
||||
utils::mock_event_with_extra(
|
||||
"form-submitter",
|
||||
@@ -516,10 +514,10 @@ fn test_form_submit() -> Element {
|
||||
let set_values = move |ev: FormEvent| {
|
||||
values.set(ev.values());
|
||||
values.with_mut(|x| {
|
||||
assert_eq!(x.get("username").unwrap(), "goodbye");
|
||||
assert_eq!(x.get("full-name").unwrap(), "lorem");
|
||||
assert_eq!(x.get("password").unwrap(), "ipsum");
|
||||
assert_eq!(x.get("color").unwrap(), "red");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "username").unwrap().1, "goodbye");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "full-name").unwrap().1, "lorem");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "password").unwrap().1, "ipsum");
|
||||
assert_eq!(x.iter().find(|f| f.0 == "color").unwrap().1, "red");
|
||||
});
|
||||
RECEIVED_EVENTS.with_mut(|x| *x += 1);
|
||||
};
|
||||
|
||||
@@ -2,17 +2,14 @@ use crate::{
|
||||
config::{Config, WindowCloseBehaviour},
|
||||
edits::EditWebsocket,
|
||||
event_handlers::WindowEventHandlers,
|
||||
file_upload::{DesktopFileUploadForm, FileDialogRequest},
|
||||
ipc::{IpcMessage, UserWindowEvent},
|
||||
query::QueryResult,
|
||||
shortcut::ShortcutRegistry,
|
||||
webview::{PendingWebview, WebviewInstance},
|
||||
};
|
||||
use dioxus_core::{consume_context, ElementId, ScopeId, VirtualDom};
|
||||
use dioxus_core::{consume_context, ScopeId, VirtualDom};
|
||||
use dioxus_history::History;
|
||||
use dioxus_html::PlatformEventData;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
@@ -409,35 +406,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) {
|
||||
let Ok(file_dialog) = serde_json::from_value::<FileDialogRequest>(msg.params()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let id = ElementId(file_dialog.target);
|
||||
let event_name = &file_dialog.event;
|
||||
let event_bubbles = file_dialog.bubbles;
|
||||
let files = file_dialog.get_file_event();
|
||||
|
||||
let as_any = Box::new(DesktopFileUploadForm { files });
|
||||
|
||||
let data = Rc::new(PlatformEventData::new(as_any));
|
||||
|
||||
let Some(view) = self.webviews.get_mut(&window) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let event = dioxus_core::Event::new(data as Rc<dyn Any>, event_bubbles);
|
||||
|
||||
let runtime = view.dom.runtime();
|
||||
if event_name == "change&input" {
|
||||
runtime.handle_event("input", event.clone(), id);
|
||||
runtime.handle_event("change", event, id);
|
||||
} else {
|
||||
runtime.handle_event(event_name, event, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll the virtualdom until it's pending
|
||||
///
|
||||
/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
|
||||
|
||||
@@ -2,13 +2,34 @@
|
||||
|
||||
use crate::{
|
||||
element::DesktopElement,
|
||||
file_upload::{DesktopFileDragEvent, DesktopFileUploadForm},
|
||||
file_upload::{DesktopFileDragEvent, DesktopFormData},
|
||||
};
|
||||
use dioxus_html::*;
|
||||
|
||||
pub(crate) struct SerializedHtmlEventConverter;
|
||||
|
||||
impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
/// This impl is special because it relies on the renderer to convert from serialized data to native data
|
||||
/// as an intermediate step.
|
||||
fn convert_drag_data(&self, event: &PlatformEventData) -> DragData {
|
||||
event
|
||||
.downcast::<DesktopFileDragEvent>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// This impl is special because it relies on the renderer to convert from serialized data to native data
|
||||
/// as an intermediate step.
|
||||
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
|
||||
event.downcast::<DesktopFormData>().cloned().unwrap().into()
|
||||
}
|
||||
|
||||
/// This impl is special because it leverages the `DesktopElement` ref between the virtualdom and webview
|
||||
fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {
|
||||
event.downcast::<DesktopElement>().cloned().unwrap().into()
|
||||
}
|
||||
|
||||
fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData {
|
||||
event
|
||||
.downcast::<SerializedAnimationData>()
|
||||
@@ -41,20 +62,6 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_drag_data(&self, event: &PlatformEventData) -> DragData {
|
||||
// Attempt a simple serialized data conversion
|
||||
if let Some(_data) = event.downcast::<SerializedDragData>() {
|
||||
return _data.clone().into();
|
||||
}
|
||||
|
||||
// If that failed then it's a file drag form
|
||||
event
|
||||
.downcast::<DesktopFileDragEvent>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData {
|
||||
event
|
||||
.downcast::<SerializedFocusData>()
|
||||
@@ -63,20 +70,6 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
|
||||
// Attempt a simple serialized form data conversion
|
||||
if let Some(_data) = event.downcast::<SerializedFormData>() {
|
||||
return _data.clone().into();
|
||||
}
|
||||
|
||||
// If that failed then it's a file upload form
|
||||
event
|
||||
.downcast::<DesktopFileUploadForm>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_image_data(&self, event: &PlatformEventData) -> ImageData {
|
||||
event
|
||||
.downcast::<SerializedImageData>()
|
||||
@@ -101,10 +94,6 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {
|
||||
event.downcast::<DesktopElement>().cloned().unwrap().into()
|
||||
}
|
||||
|
||||
fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {
|
||||
event
|
||||
.downcast::<SerializedMouseData>()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::any::Any;
|
||||
use std::{any::Any, collections::HashMap};
|
||||
|
||||
#[cfg(feature = "tokio_runtime")]
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
@@ -11,11 +11,11 @@ use dioxus_html::{
|
||||
point_interaction::{
|
||||
InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
|
||||
},
|
||||
FileData, HasDragData, HasFileData, HasFormData, HasMouseData, NativeFileData,
|
||||
SerializedMouseData, SerializedPointInteraction,
|
||||
FileData, FormValue, HasDragData, HasFileData, HasFormData, HasMouseData, NativeFileData,
|
||||
SerializedFormData, SerializedFormObject, SerializedMouseData, SerializedPointInteraction,
|
||||
};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
path::PathBuf,
|
||||
@@ -25,7 +25,7 @@ use std::{
|
||||
};
|
||||
use wry::DragDropEvent;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct FileDialogRequest {
|
||||
#[serde(default)]
|
||||
accept: Option<String>,
|
||||
@@ -34,6 +34,8 @@ pub(crate) struct FileDialogRequest {
|
||||
pub event: String,
|
||||
pub target: usize,
|
||||
pub bubbles: bool,
|
||||
pub target_name: String,
|
||||
pub values: Vec<SerializedFormObject>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -61,57 +63,69 @@ impl FileDialogRequest {
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {
|
||||
fn get_file_event_for_folder(
|
||||
request: &FileDialogRequest,
|
||||
dialog: rfd::FileDialog,
|
||||
) -> Vec<PathBuf> {
|
||||
if request.multiple {
|
||||
dialog.pick_folders().into_iter().flatten().collect()
|
||||
} else {
|
||||
dialog.pick_folder().into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_event_for_file(
|
||||
request: &FileDialogRequest,
|
||||
mut dialog: rfd::FileDialog,
|
||||
) -> Vec<PathBuf> {
|
||||
let filters: Vec<_> = request
|
||||
.accept
|
||||
.as_deref()
|
||||
.unwrap_or(".*")
|
||||
.split(',')
|
||||
.filter_map(|s| Filters::from_str(s.trim()).ok())
|
||||
.collect();
|
||||
|
||||
let file_extensions: Vec<_> = filters
|
||||
.iter()
|
||||
.flat_map(|f| f.as_extensions().into_iter())
|
||||
.collect();
|
||||
|
||||
let filter_name = file_extensions
|
||||
.iter()
|
||||
.map(|extension| format!("*.{extension}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
dialog = dialog.add_filter(filter_name, file_extensions.as_slice());
|
||||
|
||||
let files: Vec<_> = if request.multiple {
|
||||
dialog.pick_files().into_iter().flatten().collect()
|
||||
} else {
|
||||
dialog.pick_file().into_iter().collect()
|
||||
};
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
let dialog = rfd::FileDialog::new();
|
||||
|
||||
if self.directory {
|
||||
get_file_event_for_folder(self, dialog)
|
||||
self.get_file_event_for_folder(dialog)
|
||||
} else {
|
||||
get_file_event_for_file(self, dialog)
|
||||
self.get_file_event_for_file(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn get_file_event_for_file(&self, mut dialog: rfd::FileDialog) -> Vec<PathBuf> {
|
||||
let filters: Vec<_> = self
|
||||
.accept
|
||||
.as_deref()
|
||||
.unwrap_or(".*")
|
||||
.split(',')
|
||||
.filter_map(|s| Filters::from_str(s.trim()).ok())
|
||||
.collect();
|
||||
|
||||
let file_extensions: Vec<_> = filters
|
||||
.iter()
|
||||
.flat_map(|f| f.as_extensions().into_iter())
|
||||
.collect();
|
||||
|
||||
let filter_name = file_extensions
|
||||
.iter()
|
||||
.map(|extension| format!("*.{extension}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
dialog = dialog.add_filter(filter_name, file_extensions.as_slice());
|
||||
|
||||
let files: Vec<_> = if self.multiple {
|
||||
dialog.pick_files().into_iter().flatten().collect()
|
||||
} else {
|
||||
dialog.pick_file().into_iter().collect()
|
||||
};
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn get_file_event_for_folder(&self, dialog: rfd::FileDialog) -> Vec<PathBuf> {
|
||||
if self.multiple {
|
||||
dialog.pick_folders().into_iter().flatten().collect()
|
||||
} else {
|
||||
dialog.pick_folder().into_iter().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,24 +168,53 @@ impl FromStr for Filters {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DesktopFileUploadForm {
|
||||
pub files: Vec<PathBuf>,
|
||||
pub(crate) struct DesktopFormData {
|
||||
pub value: String,
|
||||
pub valid: bool,
|
||||
pub values: Vec<(String, FormValue)>,
|
||||
}
|
||||
|
||||
impl HasFileData for DesktopFileUploadForm {
|
||||
impl DesktopFormData {
|
||||
pub fn new(values: Vec<(String, FormValue)>) -> Self {
|
||||
Self {
|
||||
value: String::new(),
|
||||
valid: true,
|
||||
values,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFileData for DesktopFormData {
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
self.files
|
||||
self.values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|f| FileData::new(DesktopFileData(f)))
|
||||
.filter_map(|(_, v)| {
|
||||
if let FormValue::File(Some(f)) = v {
|
||||
Some(f.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFormData for DesktopFileUploadForm {
|
||||
impl HasFormData for DesktopFormData {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn value(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
|
||||
fn values(&self) -> Vec<(String, FormValue)> {
|
||||
self.values.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
||||
@@ -57,7 +57,6 @@ pub struct IpcMessage {
|
||||
/// A set of known messages that we need to respond to
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub enum IpcMethod<'a> {
|
||||
FileDialog,
|
||||
UserEvent,
|
||||
Query,
|
||||
BrowserOpen,
|
||||
@@ -68,7 +67,6 @@ pub enum IpcMethod<'a> {
|
||||
impl IpcMessage {
|
||||
pub(crate) fn method(&self) -> IpcMethod<'_> {
|
||||
match self.method.as_str() {
|
||||
"file_dialog" => IpcMethod::FileDialog,
|
||||
"user_event" => IpcMethod::UserEvent,
|
||||
"query" => IpcMethod::Query,
|
||||
"browser_open" => IpcMethod::BrowserOpen,
|
||||
|
||||
@@ -91,7 +91,6 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config:
|
||||
|
||||
UserWindowEvent::Ipc { id, msg } => match msg.method() {
|
||||
IpcMethod::Initialize => app.handle_initialize_msg(id),
|
||||
IpcMethod::FileDialog => app.handle_file_dialog_msg(msg, id),
|
||||
IpcMethod::UserEvent => {}
|
||||
IpcMethod::Query => app.handle_query_msg(msg, id),
|
||||
IpcMethod::BrowserOpen => app.handle_browser_open(msg),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::element::DesktopElement;
|
||||
use crate::file_upload::DesktopFileDragEvent;
|
||||
use crate::file_upload::{DesktopFileData, DesktopFileDragEvent};
|
||||
use crate::menubar::DioxusMenu;
|
||||
use crate::PendingDesktopContext;
|
||||
use crate::WindowCloseBehaviour;
|
||||
@@ -9,12 +8,13 @@ use crate::{
|
||||
DesktopContext, DesktopService,
|
||||
};
|
||||
use crate::{document::DesktopDocument, WeakDesktopContext};
|
||||
use crate::{element::DesktopElement, file_upload::DesktopFormData};
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use dioxus_core::{consume_context, provide_context, Runtime, ScopeId, VirtualDom};
|
||||
use dioxus_document::Document;
|
||||
use dioxus_history::{History, MemoryHistory};
|
||||
use dioxus_hooks::to_owned;
|
||||
use dioxus_html::{HtmlEvent, PlatformEventData};
|
||||
use dioxus_html::{FileData, FormValue, HtmlEvent, PlatformEventData};
|
||||
use futures_util::{pin_mut, FutureExt};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::{cell::OnceCell, time::Duration};
|
||||
@@ -115,7 +115,7 @@ impl WebviewEdits {
|
||||
let desktop_context = desktop_context.upgrade().unwrap();
|
||||
|
||||
let query = desktop_context.query.clone();
|
||||
let recent_file = desktop_context.file_hover.clone();
|
||||
let hovered_file = desktop_context.file_hover.clone();
|
||||
|
||||
// check for a mounted event placeholder and replace it with a desktop specific element
|
||||
let as_any = match data {
|
||||
@@ -123,9 +123,39 @@ impl WebviewEdits {
|
||||
let element = DesktopElement::new(element, desktop_context.clone(), query.clone());
|
||||
Rc::new(PlatformEventData::new(Box::new(element)))
|
||||
}
|
||||
dioxus_html::EventData::Form(form) => {
|
||||
Rc::new(PlatformEventData::new(Box::new(DesktopFormData {
|
||||
value: form.value,
|
||||
valid: form.valid,
|
||||
values: form
|
||||
.values
|
||||
.into_iter()
|
||||
.map(|obj| {
|
||||
if let Some(text) = obj.text {
|
||||
return (obj.key, FormValue::Text(text));
|
||||
}
|
||||
|
||||
if let Some(file_data) = obj.file {
|
||||
if file_data.path.capacity() == 0 {
|
||||
return (obj.key, FormValue::File(None));
|
||||
}
|
||||
|
||||
return (
|
||||
obj.key,
|
||||
FormValue::File(Some(FileData::new(DesktopFileData(
|
||||
file_data.path,
|
||||
)))),
|
||||
);
|
||||
};
|
||||
|
||||
(obj.key, FormValue::Text(String::new()))
|
||||
})
|
||||
.collect(),
|
||||
})))
|
||||
}
|
||||
dioxus_html::EventData::Drag(ref drag) => {
|
||||
// we want to override this with a native file engine, provided by the most recent drag event
|
||||
let file_event = recent_file.current().unwrap();
|
||||
let file_event = hovered_file.current().unwrap();
|
||||
let paths = match file_event {
|
||||
wry::DragDropEvent::Enter { paths, .. } => paths,
|
||||
wry::DragDropEvent::Drop { paths, .. } => paths,
|
||||
|
||||
Reference in new issue
Block a user