codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -14,6 +14,7 @@ use wasm_bindgen::JsCast as _;
|
||||
/// ```
|
||||
pub struct Config {
|
||||
pub(crate) hydrate: bool,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) panic_hook: bool,
|
||||
pub(crate) root: ConfigRoot,
|
||||
#[cfg(feature = "document")]
|
||||
|
||||
@@ -100,7 +100,7 @@ pub fn init_document() {
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn init_fullstack_document() {
|
||||
use dioxus_fullstack_hooks::{
|
||||
use dioxus_fullstack_core::{
|
||||
document::FullstackWebDocument, history::provide_fullstack_history_context,
|
||||
};
|
||||
|
||||
@@ -131,14 +131,14 @@ impl Document for WebDocument {
|
||||
/// Create a new meta tag in the head
|
||||
fn create_meta(&self, props: MetaProps) {
|
||||
queue_effect(move || {
|
||||
append_element_to_head("meta", &props.attributes(), None);
|
||||
_ = append_element_to_head("meta", &props.attributes(), None);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a new script tag in the head
|
||||
fn create_script(&self, props: ScriptProps) {
|
||||
queue_effect(move || {
|
||||
append_element_to_head(
|
||||
_ = append_element_to_head(
|
||||
"script",
|
||||
&props.attributes(),
|
||||
props.script_contents().ok().as_deref(),
|
||||
@@ -149,7 +149,7 @@ impl Document for WebDocument {
|
||||
/// Create a new style tag in the head
|
||||
fn create_style(&self, props: StyleProps) {
|
||||
queue_effect(move || {
|
||||
append_element_to_head(
|
||||
_ = append_element_to_head(
|
||||
"style",
|
||||
&props.attributes(),
|
||||
props.style_contents().ok().as_deref(),
|
||||
@@ -160,7 +160,7 @@ impl Document for WebDocument {
|
||||
/// Create a new link tag in the head
|
||||
fn create_link(&self, props: LinkProps) {
|
||||
queue_effect(move || {
|
||||
append_element_to_head("link", &props.attributes(), None);
|
||||
_ = append_element_to_head("link", &props.attributes(), None);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -169,19 +169,21 @@ fn append_element_to_head(
|
||||
local_name: &str,
|
||||
attributes: &Vec<(&'static str, String)>,
|
||||
text_content: Option<&str>,
|
||||
) {
|
||||
) -> Result<(), JsValue> {
|
||||
let window = web_sys::window().expect("no global `window` exists");
|
||||
let document = window.document().expect("should have a document on window");
|
||||
let head = document.head().expect("document should have a head");
|
||||
|
||||
let element = document.create_element(local_name).unwrap();
|
||||
let element = document.create_element(local_name)?;
|
||||
for (name, value) in attributes {
|
||||
element.set_attribute(name, value).unwrap();
|
||||
element.set_attribute(name, value)?;
|
||||
}
|
||||
if text_content.is_some() {
|
||||
element.set_text_content(text_content);
|
||||
}
|
||||
head.append_child(&element).unwrap();
|
||||
head.append_child(&element)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Required to avoid blocking the Rust WASM thread.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::{WebFileData, WebFileEngine};
|
||||
|
||||
use super::{Synthetic, WebEventExt};
|
||||
use dioxus_html::{
|
||||
geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},
|
||||
input_data::{decode_mouse_button_set, MouseButton},
|
||||
HasDragData, HasFileData, HasMouseData, InteractionElementOffset, InteractionLocation,
|
||||
Modifiers, ModifiersInteraction, PointerInteraction,
|
||||
FileData, HasDragData, HasFileData, HasMouseData, InteractionElementOffset,
|
||||
InteractionLocation, Modifiers, ModifiersInteraction, PointerInteraction,
|
||||
};
|
||||
use web_sys::DragEvent;
|
||||
use web_sys::{DragEvent, FileReader};
|
||||
|
||||
impl InteractionLocation for Synthetic<DragEvent> {
|
||||
fn client_coordinates(&self) -> ClientPoint {
|
||||
@@ -71,31 +73,35 @@ impl HasDragData for Synthetic<DragEvent> {
|
||||
}
|
||||
|
||||
impl HasFileData for Synthetic<DragEvent> {
|
||||
fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
|
||||
#[cfg(feature = "file_engine")]
|
||||
{
|
||||
use wasm_bindgen::JsCast;
|
||||
let files = self
|
||||
.event
|
||||
.dyn_ref::<web_sys::DragEvent>()
|
||||
.and_then(|drag_event| {
|
||||
drag_event.data_transfer().and_then(|dt| {
|
||||
dt.files().and_then(|files| {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
crate::file_engine::WebFileEngine::new(files).map(|f| {
|
||||
std::sync::Arc::new(f)
|
||||
as std::sync::Arc<dyn dioxus_html::FileEngine>
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
files
|
||||
}
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
{
|
||||
None
|
||||
if let Some(target) = self.event.dyn_ref::<web_sys::DragEvent>() {
|
||||
if let Some(data_transfer) = target.data_transfer() {
|
||||
if let Some(file_list) = data_transfer.files() {
|
||||
return WebFileEngine::new(file_list).to_files();
|
||||
} else {
|
||||
let items = data_transfer.items();
|
||||
let mut files = vec![];
|
||||
for i in 0..items.length() {
|
||||
if let Some(item) = items.get(i) {
|
||||
if item.kind() == "file" {
|
||||
if let Ok(Some(file)) = item.get_as_file() {
|
||||
let web_data =
|
||||
WebFileData::new(file, FileReader::new().unwrap());
|
||||
files.push(FileData::new(web_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("DragEvent target was not a DragEvent");
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,47 @@
|
||||
use dioxus_html::HasFileData;
|
||||
use dioxus_html::{FileData, HasFileData};
|
||||
use web_sys::FileReader;
|
||||
|
||||
use crate::{WebFileData, WebFileEngine};
|
||||
|
||||
use super::Synthetic;
|
||||
|
||||
impl HasFileData for Synthetic<web_sys::Event> {
|
||||
fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
|
||||
#[cfg(feature = "file_engine")]
|
||||
{
|
||||
use wasm_bindgen::JsCast;
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let target = self.event.target();
|
||||
|
||||
let files = self
|
||||
.event
|
||||
.dyn_ref()
|
||||
.and_then(|input: &web_sys::HtmlInputElement| {
|
||||
input.files().and_then(|files| {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
crate::file_engine::WebFileEngine::new(files).map(|f| {
|
||||
std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
files
|
||||
}
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
if let Some(target) = target
|
||||
.clone()
|
||||
.and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
|
||||
{
|
||||
None
|
||||
if let Some(file_list) = target.files() {
|
||||
return WebFileEngine::new(file_list).to_files();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(target) = target.and_then(|t| t.dyn_into::<web_sys::DragEvent>().ok()) {
|
||||
if let Some(data_transfer) = target.data_transfer() {
|
||||
if let Some(file_list) = data_transfer.files() {
|
||||
return WebFileEngine::new(file_list).to_files();
|
||||
} else {
|
||||
let items = data_transfer.items();
|
||||
let mut files = vec![];
|
||||
for i in 0..items.length() {
|
||||
if let Some(item) = items.get(i) {
|
||||
if item.kind() == "file" {
|
||||
if let Ok(Some(file)) = item.get_as_file() {
|
||||
let web_data =
|
||||
WebFileData::new(file, FileReader::new().unwrap());
|
||||
files.push(FileData::new(web_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{any::Any, collections::HashMap};
|
||||
|
||||
use dioxus_html::{FormValue, HasFileData, HasFormData};
|
||||
use dioxus_html::{FileData, FormValue, HasFileData, HasFormData};
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
|
||||
@@ -10,7 +10,7 @@ use super::WebEventExt;
|
||||
|
||||
pub(crate) struct WebFormData {
|
||||
element: Element,
|
||||
raw: Event,
|
||||
event: Event,
|
||||
}
|
||||
|
||||
impl WebEventExt for dioxus_html::FormData {
|
||||
@@ -23,8 +23,8 @@ impl WebEventExt for dioxus_html::FormData {
|
||||
}
|
||||
|
||||
impl WebFormData {
|
||||
pub fn new(element: Element, raw: Event) -> Self {
|
||||
Self { element, raw }
|
||||
pub fn new(element: Element, event: Event) -> Self {
|
||||
Self { element, event }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,39 +32,39 @@ impl HasFormData for WebFormData {
|
||||
fn value(&self) -> String {
|
||||
let target = &self.element;
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlInputElement| {
|
||||
// todo: special case more input types
|
||||
match input.type_().as_str() {
|
||||
"checkbox" => {
|
||||
match input.checked() {
|
||||
true => "true".to_string(),
|
||||
false => "false".to_string(),
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlInputElement| {
|
||||
// todo: special case more input types
|
||||
match input.type_().as_str() {
|
||||
"checkbox" => {
|
||||
match input.checked() {
|
||||
true => "true".to_string(),
|
||||
false => "false".to_string(),
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
input.value()
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
input.value()
|
||||
}
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||
})
|
||||
// select elements are NOT input events - because - why woudn't they be??
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlSelectElement| input.value())
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.unwrap()
|
||||
.text_content()
|
||||
})
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||
})
|
||||
// select elements are NOT input events - because - why woudn't they be??
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlSelectElement| input.value())
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.unwrap()
|
||||
.text_content()
|
||||
})
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
|
||||
}
|
||||
|
||||
fn values(&self) -> HashMap<String, FormValue> {
|
||||
@@ -101,32 +101,20 @@ impl HasFormData for WebFormData {
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
&self.raw as &dyn Any
|
||||
&self.event as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFileData for WebFormData {
|
||||
fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
|
||||
#[cfg(feature = "file_engine")]
|
||||
{
|
||||
let files = self
|
||||
.element
|
||||
.dyn_ref()
|
||||
.and_then(|input: &web_sys::HtmlInputElement| {
|
||||
input.files().and_then(|files| {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
crate::file_engine::WebFileEngine::new(files).map(|f| {
|
||||
std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
files
|
||||
}
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
use wasm_bindgen::JsCast;
|
||||
self.event
|
||||
.target()
|
||||
.and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
|
||||
.and_then(|input| input.files())
|
||||
.map(crate::files::WebFileEngine::new)
|
||||
.map(|engine| engine.to_files())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
use std::any::Any;
|
||||
|
||||
use dioxus_html::FileEngine;
|
||||
use futures_channel::oneshot;
|
||||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
use web_sys::{File, FileList, FileReader};
|
||||
|
||||
/// A file engine for the web platform
|
||||
pub struct WebFileEngine {
|
||||
file_reader: FileReader,
|
||||
file_list: FileList,
|
||||
}
|
||||
|
||||
impl WebFileEngine {
|
||||
/// Create a new file engine from a file list
|
||||
pub fn new(file_list: FileList) -> Option<Self> {
|
||||
Some(Self {
|
||||
file_list,
|
||||
file_reader: FileReader::new().ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.file_list.length() as usize
|
||||
}
|
||||
|
||||
fn get(&self, index: usize) -> Option<File> {
|
||||
self.file_list.item(index as u32)
|
||||
}
|
||||
|
||||
fn find(&self, name: &str) -> Option<File> {
|
||||
(0..self.len())
|
||||
.filter_map(|i| self.get(i))
|
||||
.find(|f| f.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FileEngine for WebFileEngine {
|
||||
fn files(&self) -> Vec<String> {
|
||||
(0..self.len())
|
||||
.filter_map(|i| self.get(i).map(|f| f.name()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn file_size(&self, file: &str) -> Option<u64> {
|
||||
let file = self.find(file)?;
|
||||
Some(file.size() as u64)
|
||||
}
|
||||
|
||||
// read a file to bytes
|
||||
async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
|
||||
let file = self.find(file)?;
|
||||
|
||||
let file_reader = self.file_reader.clone();
|
||||
let (rx, tx) = oneshot::channel();
|
||||
let on_load: Closure<dyn FnMut()> = Closure::new({
|
||||
let mut rx = Some(rx);
|
||||
move || {
|
||||
let result = file_reader.result();
|
||||
let _ = rx
|
||||
.take()
|
||||
.expect("multiple files read without refreshing the channel")
|
||||
.send(result);
|
||||
}
|
||||
});
|
||||
|
||||
self.file_reader
|
||||
.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||
on_load.forget();
|
||||
self.file_reader.read_as_array_buffer(&file).ok()?;
|
||||
|
||||
if let Ok(Ok(js_val)) = tx.await {
|
||||
let as_u8_arr = Uint8Array::new(&js_val);
|
||||
let as_u8_vec = as_u8_arr.to_vec();
|
||||
|
||||
Some(as_u8_vec)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// read a file to string
|
||||
async fn read_file_to_string(&self, file: &str) -> Option<String> {
|
||||
let file = self.find(file)?;
|
||||
|
||||
let file_reader = self.file_reader.clone();
|
||||
let (rx, tx) = oneshot::channel();
|
||||
let on_load: Closure<dyn FnMut()> = Closure::new({
|
||||
let mut rx = Some(rx);
|
||||
move || {
|
||||
let result = file_reader.result();
|
||||
let _ = rx
|
||||
.take()
|
||||
.expect("multiple files read without refreshing the channel")
|
||||
.send(result);
|
||||
}
|
||||
});
|
||||
|
||||
self.file_reader
|
||||
.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||
on_load.forget();
|
||||
self.file_reader.read_as_text(&file).ok()?;
|
||||
|
||||
if let Ok(Ok(js_val)) = tx.await {
|
||||
js_val.as_string()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||
let file = self.find(file)?;
|
||||
Some(Box::new(file))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for WebFileEngine
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait WebFileEngineExt {
|
||||
/// returns web_sys::File
|
||||
async fn get_web_file(&self, file: &str) -> Option<web_sys::File>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl WebFileEngineExt for std::sync::Arc<dyn FileEngine> {
|
||||
async fn get_web_file(&self, file: &str) -> Option<web_sys::File> {
|
||||
let native_file = self.get_native_file(file).await?;
|
||||
let ret = native_file.downcast::<web_sys::File>().ok()?;
|
||||
Some(*ret)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
use dioxus_core::AnyhowContext;
|
||||
use dioxus_html::{bytes::Bytes, FileData, NativeFileData};
|
||||
use futures_channel::oneshot;
|
||||
use js_sys::Uint8Array;
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{pin::Pin, prelude::rust_2024::Future};
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
use web_sys::{File, FileList, FileReader};
|
||||
|
||||
/// A file representation for the web platform
|
||||
#[derive(Clone)]
|
||||
pub struct WebFileData {
|
||||
file: File,
|
||||
reader: FileReader,
|
||||
}
|
||||
|
||||
unsafe impl Send for WebFileData {}
|
||||
unsafe impl Sync for WebFileData {}
|
||||
|
||||
impl WebFileData {
|
||||
/// Create a new WebFileData from a web_sys::File
|
||||
pub fn new(file: File, reader: FileReader) -> Self {
|
||||
Self { file, reader }
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeFileData for WebFileData {
|
||||
fn name(&self) -> String {
|
||||
self.file.name()
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.file.size() as u64
|
||||
}
|
||||
|
||||
fn last_modified(&self) -> u64 {
|
||||
self.file.last_modified() as u64
|
||||
}
|
||||
|
||||
fn read_bytes(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
|
||||
let file_reader = self.reader.clone();
|
||||
let file_reader_ = self.reader.clone();
|
||||
let file = self.file.clone();
|
||||
Box::pin(async move {
|
||||
let (rx, tx) = oneshot::channel();
|
||||
let on_load: Closure<dyn FnMut()> = Closure::new({
|
||||
let mut rx = Some(rx);
|
||||
move || {
|
||||
let result = file_reader.result();
|
||||
let _ = rx
|
||||
.take()
|
||||
.expect("multiple files read without refreshing the channel")
|
||||
.send(result);
|
||||
}
|
||||
});
|
||||
|
||||
file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||
on_load.forget();
|
||||
file_reader_
|
||||
.read_as_array_buffer(&file)
|
||||
.ok()
|
||||
.context("Failed to read file")?;
|
||||
|
||||
let js_val = tx.await?.ok().context("Failed to read file")?;
|
||||
let as_u8_arr = Uint8Array::new(&js_val);
|
||||
let as_u8_vec = as_u8_arr.to_vec().into();
|
||||
Ok(as_u8_vec)
|
||||
})
|
||||
}
|
||||
|
||||
fn read_string(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
|
||||
let file_reader = self.reader.clone();
|
||||
let file_reader_ = self.reader.clone();
|
||||
let file = self.file.clone();
|
||||
Box::pin(async move {
|
||||
let (rx, tx) = oneshot::channel();
|
||||
let on_load: Closure<dyn FnMut()> = Closure::new({
|
||||
let mut rx = Some(rx);
|
||||
move || {
|
||||
let result = file_reader.result();
|
||||
let _ = rx
|
||||
.take()
|
||||
.expect("multiple files read without refreshing the channel")
|
||||
.send(result);
|
||||
}
|
||||
});
|
||||
|
||||
file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||
on_load.forget();
|
||||
file_reader_
|
||||
.read_as_text(&file)
|
||||
.ok()
|
||||
.context("Failed to read file")?;
|
||||
|
||||
let js_val = tx.await?.ok().context("Failed to read file")?;
|
||||
let as_string = js_val.as_string().context("Failed to read file")?;
|
||||
Ok(as_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// we'd like to use `blob` to readable stream here, but we cannot.
|
||||
///
|
||||
/// We just read the entire file into memory and return it as a single chunk.
|
||||
/// This is not super great, especially given the wasm <-> js boundary duplication cost.
|
||||
///
|
||||
/// For more efficient streaming of byte data, consider using the dedicated FileStream type which
|
||||
/// goes directly from `File` to fetch request body without going through Rust.
|
||||
///
|
||||
/// We should maybe update these APIs to use our own custom `ByteBuffer` type to avoid going through `Vec<u8>`?
|
||||
fn byte_stream(
|
||||
&self,
|
||||
) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>
|
||||
{
|
||||
let file = self.file.dyn_ref::<web_sys::Blob>().unwrap().clone();
|
||||
Box::pin(SendWrapper::new(futures_util::stream::once(async move {
|
||||
let array_buff = wasm_bindgen_futures::JsFuture::from(file.array_buffer())
|
||||
.await
|
||||
.unwrap();
|
||||
let as_uint_array = array_buff.dyn_into::<Uint8Array>().unwrap();
|
||||
Ok(as_uint_array.to_vec().into())
|
||||
})))
|
||||
}
|
||||
|
||||
fn inner(&self) -> &dyn std::any::Any {
|
||||
&self.file
|
||||
}
|
||||
|
||||
fn path(&self) -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(self.file.name())
|
||||
}
|
||||
|
||||
fn content_type(&self) -> Option<String> {
|
||||
let type_ = self.file.type_();
|
||||
if type_.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(type_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A file engine for the web platform
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WebFileEngine {
|
||||
file_list: FileList,
|
||||
}
|
||||
|
||||
impl WebFileEngine {
|
||||
/// Create a new file engine from a file list
|
||||
pub fn new(file_list: FileList) -> Self {
|
||||
Self { file_list }
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.file_list.length() as usize
|
||||
}
|
||||
|
||||
fn get(&self, index: usize) -> Option<File> {
|
||||
self.file_list.item(index as u32)
|
||||
}
|
||||
|
||||
pub fn to_files(&self) -> Vec<FileData> {
|
||||
(0..self.len())
|
||||
.filter_map(|i| self.get(i))
|
||||
.map(|file| {
|
||||
FileData::new(WebFileData {
|
||||
file,
|
||||
reader: FileReader::new().unwrap(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for extracting the underlying `web_sys::File` from a `FileData`
|
||||
pub trait WebFileExt {
|
||||
/// returns web_sys::File
|
||||
fn get_web_file(&self) -> Option<web_sys::File>;
|
||||
}
|
||||
|
||||
impl WebFileExt for FileData {
|
||||
fn get_web_file(&self) -> Option<web_sys::File> {
|
||||
self.inner().downcast_ref::<web_sys::File>().cloned()
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use dioxus_core::{
|
||||
AttributeValue, DynamicNode, ElementId, ScopeId, ScopeState, SuspenseBoundaryProps,
|
||||
SuspenseContext, TemplateNode, VNode, VirtualDom,
|
||||
};
|
||||
use dioxus_fullstack_protocol::HydrationContext;
|
||||
use dioxus_fullstack_core::HydrationContext;
|
||||
use futures_channel::mpsc::UnboundedReceiver;
|
||||
use std::fmt::Write;
|
||||
use RehydrationError::*;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
//! # Dioxus Web
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub use crate::cfg::Config;
|
||||
use crate::hydration::SuspenseMessage;
|
||||
use dioxus_core::VirtualDom;
|
||||
@@ -22,17 +20,16 @@ pub use events::*;
|
||||
|
||||
#[cfg(feature = "document")]
|
||||
mod document;
|
||||
#[cfg(feature = "file_engine")]
|
||||
mod file_engine;
|
||||
#[cfg(feature = "document")]
|
||||
mod history;
|
||||
#[cfg(feature = "document")]
|
||||
pub use document::WebDocument;
|
||||
#[cfg(feature = "file_engine")]
|
||||
pub use file_engine::*;
|
||||
#[cfg(feature = "document")]
|
||||
pub use history::{HashHistory, WebHistory};
|
||||
|
||||
mod files;
|
||||
pub use files::*;
|
||||
|
||||
#[cfg(all(feature = "devtools", debug_assertions))]
|
||||
mod devtools;
|
||||
|
||||
@@ -95,7 +92,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
use dioxus_fullstack_protocol::HydrationContext;
|
||||
use dioxus_fullstack_core::HydrationContext;
|
||||
|
||||
websys_dom.skip_mutations = true;
|
||||
// Get the initial hydration data from the client
|
||||
@@ -139,7 +136,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
|
||||
// Provide a hydration compatible create error boundary method
|
||||
dioxus_core::ScopeId::ROOT.in_runtime(|| {
|
||||
dioxus_core::provide_create_error_boundary(
|
||||
dioxus_fullstack_hooks::errors::init_error_boundary,
|
||||
dioxus_fullstack_core::init_error_boundary,
|
||||
);
|
||||
});
|
||||
#[cfg(feature = "document")]
|
||||
@@ -235,7 +232,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
|
||||
"Hot-patch success!",
|
||||
&format!("App successfully patched in {} ms", hr_msg.ms_elapsed),
|
||||
devtools::ToastLevel::Success,
|
||||
Duration::from_millis(2000),
|
||||
std::time::Duration::from_millis(2000),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user