Merge branch 'master' into add-file-data-drag-event
This commit is contained in:
commit
847801908f
575 files changed
+12880
-17751
No files matched your search
+29
-48
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "dioxus-web"
|
||||
version = "0.3.2"
|
||||
version = { workspace = true }
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Web renderer for Dioxus using websys"
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/web.html"
|
||||
homepage = "https://dioxuslabs.com/learn/0.4/getting_started/wasm"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
@@ -20,73 +20,53 @@ dioxus-interpreter-js = { workspace = true, features = [
|
||||
js-sys = "0.3.56"
|
||||
wasm-bindgen = { workspace = true, features = ["enable-interning"] }
|
||||
wasm-bindgen-futures = "0.4.29"
|
||||
log = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
once_cell = "1.9.0"
|
||||
anyhow = "1.0.53"
|
||||
gloo-timers = { version = "0.2.3", features = ["futures"] }
|
||||
futures-util = { workspace = true, features = ["std", "async-await", "async-await-macro"] }
|
||||
smallstr = "0.2.0"
|
||||
futures-channel = { workspace = true }
|
||||
serde_json = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
serde-wasm-bindgen = "0.4.5"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
async-trait = "0.1.58"
|
||||
async-channel = "1.8.0"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
features = [
|
||||
"Comment",
|
||||
"Attr",
|
||||
"Document",
|
||||
"Element",
|
||||
"CssStyleDeclaration",
|
||||
"HtmlElement",
|
||||
"HtmlInputElement",
|
||||
"HtmlSelectElement",
|
||||
"HtmlTextAreaElement",
|
||||
"HtmlFormElement",
|
||||
"EventTarget",
|
||||
"HtmlCollection",
|
||||
"Node",
|
||||
"NodeList",
|
||||
"Text",
|
||||
"Window",
|
||||
"Event",
|
||||
"MouseEvent",
|
||||
"InputEvent",
|
||||
"ClipboardEvent",
|
||||
"NamedNodeMap",
|
||||
"KeyboardEvent",
|
||||
"TouchEvent",
|
||||
"WheelEvent",
|
||||
"AnimationEvent",
|
||||
"TransitionEvent",
|
||||
"PointerEvent",
|
||||
"FocusEvent",
|
||||
"CompositionEvent",
|
||||
"ClipboardEvent",
|
||||
"DocumentType",
|
||||
"CharacterData",
|
||||
"SvgElement",
|
||||
"SvgAnimatedString",
|
||||
"HtmlOptionElement",
|
||||
"IdleDeadline",
|
||||
"WebSocket",
|
||||
"Location",
|
||||
"MessageEvent",
|
||||
"console",
|
||||
"FileList",
|
||||
"File",
|
||||
"FileReader",
|
||||
"DataTransfer"
|
||||
"DataTransfer",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["panic_hook"]
|
||||
default = ["panic_hook", "mounted", "file_engine", "hot_reload", "eval"]
|
||||
panic_hook = ["console_error_panic_hook"]
|
||||
hydrate = []
|
||||
hydrate = [
|
||||
"web-sys/Comment",
|
||||
"web-sys/console",
|
||||
]
|
||||
mounted = [
|
||||
"web-sys/Element",
|
||||
"dioxus-html/mounted"
|
||||
]
|
||||
file_engine = [
|
||||
"web-sys/File",
|
||||
"web-sys/FileList",
|
||||
"web-sys/FileReader",
|
||||
]
|
||||
hot_reload = [
|
||||
"web-sys/MessageEvent",
|
||||
"web-sys/WebSocket",
|
||||
"web-sys/Location",
|
||||
]
|
||||
eval = []
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true }
|
||||
@@ -95,4 +75,5 @@ dioxus-ssr = { workspace = true}
|
||||
wasm-logger = "0.2.0"
|
||||
gloo-timers = "0.2.3"
|
||||
gloo-dialogs = "0.1.1"
|
||||
dioxus-web = { workspace = true, features = ["hydrate"] }
|
||||
dioxus-web = { path = ".", features = ["hydrate"] }
|
||||
tracing-wasm = "0.2.1"
|
||||
@@ -7,18 +7,15 @@
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-web.svg
|
||||
[crates-url]: https://crates.io/crates/dioxus-web
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||
|
||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
||||
[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
|
||||
|
||||
[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/XgGxMSkvUM
|
||||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) |
|
||||
[Guides](https://dioxuslabs.com/learn/0.4/) |
|
||||
[API Docs](https://docs.rs/dioxus-web/latest/dioxus_web) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
@@ -36,6 +33,7 @@ Run Dioxus in the browser using WebAssembly.
|
||||
- Join the discord and ask questions!
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
@@ -43,4 +41,3 @@ This project is licensed under the [MIT license].
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
||||
@@ -43,13 +43,13 @@ fn Bapp(cx: Scope) -> Element {
|
||||
|
||||
fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||
tracing_wasm::set_as_global_default();
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
|
||||
let pre = dioxus_ssr::pre_render(&dom);
|
||||
log::trace!("{}", pre);
|
||||
tracing::trace!("{}", pre);
|
||||
|
||||
// set the inner content of main to the pre-rendered content
|
||||
window()
|
||||
|
||||
+24
-8
@@ -7,19 +7,21 @@
|
||||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?>
|
||||
|
||||
use crate::file_engine::WebFileEngine;
|
||||
use dioxus_core::{
|
||||
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
|
||||
};
|
||||
use dioxus_html::{event_bubbles, CompositionData, FileEngine, FormData, MountedData};
|
||||
use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData};
|
||||
use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel};
|
||||
use futures_channel::mpsc;
|
||||
use js_sys::Array;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{any::Any, rc::Rc, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, rc::Rc};
|
||||
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
|
||||
use web_sys::{Document, Element, Event};
|
||||
|
||||
use crate::{file_engine::WebFileEngine, Config};
|
||||
use crate::Config;
|
||||
|
||||
pub struct WebsysDom {
|
||||
document: Document,
|
||||
@@ -56,17 +58,27 @@ impl WebsysDom {
|
||||
let element = walk_event_for_id(event);
|
||||
let bubbles = dioxus_html::event_bubbles(name.as_str());
|
||||
if let Some((element, target)) = element {
|
||||
let prevent_event;
|
||||
if let Some(prevent_requests) = target
|
||||
.get_attribute("dioxus-prevent-default")
|
||||
.as_deref()
|
||||
.map(|f| f.split_whitespace())
|
||||
{
|
||||
if prevent_requests
|
||||
prevent_event = prevent_requests
|
||||
.map(|f| f.trim_start_matches("on"))
|
||||
.any(|f| f == name)
|
||||
{
|
||||
.any(|f| f == name);
|
||||
} else {
|
||||
prevent_event = false;
|
||||
}
|
||||
|
||||
// Prevent forms from submitting and redirecting
|
||||
if name == "submit" {
|
||||
// On forms the default behavior is not to submit, if prevent default is set then we submit the form
|
||||
if !prevent_event {
|
||||
event.prevent_default();
|
||||
}
|
||||
} else if prevent_event {
|
||||
event.prevent_default();
|
||||
}
|
||||
|
||||
let data = virtual_event_from_websys_event(event.clone(), target);
|
||||
@@ -239,7 +251,6 @@ impl WebsysDom {
|
||||
for id in to_mount {
|
||||
let node = get_node(id.0 as u32);
|
||||
if let Some(element) = node.dyn_ref::<Element>() {
|
||||
log::info!("mounted event fired: {}", id.0);
|
||||
let data: MountedData = element.into();
|
||||
let data = Rc::new(data);
|
||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||
@@ -384,11 +395,16 @@ fn read_input_to_data(target: Element) -> Rc<FormData> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
let files = None;
|
||||
#[cfg(feature = "file_engine")]
|
||||
let files = target
|
||||
.dyn_ref()
|
||||
.and_then(|input: &web_sys::HtmlInputElement| {
|
||||
input.files().and_then(|files| {
|
||||
WebFileEngine::new(files).map(|f| Arc::new(f) as Arc<dyn FileEngine>)
|
||||
#[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>)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
export class Dioxus {
|
||||
constructor(sendCallback, returnCallback) {
|
||||
this.sendCallback = sendCallback;
|
||||
this.returnCallback = returnCallback;
|
||||
this.promiseResolve = null;
|
||||
this.received = [];
|
||||
}
|
||||
|
||||
// Receive message from Rust
|
||||
recv() {
|
||||
return new Promise((resolve, _reject) => {
|
||||
// If data already exists, resolve immediately
|
||||
let data = this.received.shift();
|
||||
if (data) {
|
||||
resolve(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise set a resolve callback
|
||||
this.promiseResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
// Send message to rust.
|
||||
send(data) {
|
||||
this.sendCallback(data);
|
||||
}
|
||||
|
||||
// Internal rust send
|
||||
rustSend(data) {
|
||||
// If a promise is waiting for data, resolve it, and clear the resolve callback
|
||||
if (this.promiseResolve) {
|
||||
this.promiseResolve(data);
|
||||
this.promiseResolve = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise add the data to a queue
|
||||
this.received.push(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use async_trait::async_trait;
|
||||
use dioxus_core::ScopeState;
|
||||
use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator};
|
||||
use js_sys::Function;
|
||||
use serde_json::Value;
|
||||
use std::{cell::RefCell, rc::Rc, str::FromStr};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Provides the WebEvalProvider through [`cx.provide_context`].
|
||||
pub fn init_eval(cx: &ScopeState) {
|
||||
let provider: Rc<dyn EvalProvider> = Rc::new(WebEvalProvider {});
|
||||
cx.provide_context(provider);
|
||||
}
|
||||
|
||||
/// Reprents the web-target's provider of evaluators.
|
||||
pub struct WebEvalProvider;
|
||||
impl EvalProvider for WebEvalProvider {
|
||||
fn new_evaluator(&self, js: String) -> Result<Rc<dyn Evaluator>, EvalError> {
|
||||
WebEvaluator::new(js).map(|eval| Rc::new(eval) as Rc<dyn Evaluator + 'static>)
|
||||
}
|
||||
}
|
||||
|
||||
/// Required to avoid blocking the Rust WASM thread.
|
||||
const PROMISE_WRAPPER: &str = r#"
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
{JS_CODE}
|
||||
resolve(null);
|
||||
});
|
||||
"#;
|
||||
|
||||
/// Reprents a web-target's JavaScript evaluator.
|
||||
pub struct WebEvaluator {
|
||||
dioxus: Dioxus,
|
||||
channel_receiver: async_channel::Receiver<serde_json::Value>,
|
||||
result: RefCell<Option<serde_json::Value>>,
|
||||
}
|
||||
|
||||
impl WebEvaluator {
|
||||
/// Creates a new evaluator for web-based targets.
|
||||
pub fn new(js: String) -> Result<Self, EvalError> {
|
||||
let (channel_sender, channel_receiver) = async_channel::unbounded();
|
||||
|
||||
// This Rc cloning mess hurts but it seems to work..
|
||||
let recv_value = Closure::<dyn FnMut(JsValue)>::new(move |data| {
|
||||
match serde_wasm_bindgen::from_value::<serde_json::Value>(data) {
|
||||
Ok(data) => _ = channel_sender.send_blocking(data),
|
||||
Err(e) => {
|
||||
// Can't really do much here.
|
||||
tracing::error!("failed to serialize JsValue to serde_json::Value (eval communication) - {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let dioxus = Dioxus::new(recv_value.as_ref().unchecked_ref());
|
||||
recv_value.forget();
|
||||
|
||||
// Wrap the evaluated JS in a promise so that wasm can continue running (send/receive data from js)
|
||||
let code = PROMISE_WRAPPER.replace("{JS_CODE}", &js);
|
||||
|
||||
let result = match Function::new_with_args("dioxus", &code).call1(&JsValue::NULL, &dioxus) {
|
||||
Ok(result) => {
|
||||
if let Ok(stringified) = js_sys::JSON::stringify(&result) {
|
||||
if !stringified.is_undefined() && stringified.is_valid_utf16() {
|
||||
let string: String = stringified.into();
|
||||
Value::from_str(&string).map_err(|e| {
|
||||
EvalError::Communication(format!("Failed to parse result - {}", e))
|
||||
})?
|
||||
} else {
|
||||
return Err(EvalError::Communication(
|
||||
"Failed to stringify result".into(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(EvalError::Communication(
|
||||
"Failed to stringify result".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(EvalError::InvalidJs(
|
||||
err.as_string().unwrap_or("unknown".to_string()),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
dioxus,
|
||||
channel_receiver,
|
||||
result: RefCell::new(Some(result)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Evaluator for WebEvaluator {
|
||||
/// Runs the evaluated JavaScript.
|
||||
async fn join(&self) -> Result<serde_json::Value, EvalError> {
|
||||
self.result.take().ok_or(EvalError::Finished)
|
||||
}
|
||||
|
||||
/// Sends a message to the evaluated JavaScript.
|
||||
fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
|
||||
let data = match serde_wasm_bindgen::to_value::<serde_json::Value>(&data) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return Err(EvalError::Communication(e.to_string())),
|
||||
};
|
||||
|
||||
self.dioxus.rustSend(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
|
||||
async fn recv(&self) -> Result<serde_json::Value, EvalError> {
|
||||
self.channel_receiver
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| EvalError::Communication("failed to receive data from js".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "/src/eval.js")]
|
||||
extern "C" {
|
||||
pub type Dioxus;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(recv_callback: &Function) -> Dioxus;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn rustSend(this: &Dioxus, data: JsValue);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::any::Any;
|
||||
|
||||
use dioxus_html::FileEngine;
|
||||
use futures_channel::oneshot;
|
||||
use js_sys::Uint8Array;
|
||||
@@ -100,4 +102,25 @@ impl FileEngine for WebFileEngine {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,12 @@
|
||||
use futures_channel::mpsc::UnboundedReceiver;
|
||||
|
||||
use dioxus_core::Template;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{MessageEvent, WebSocket};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
std::mem::forget(tx);
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{MessageEvent, WebSocket};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
|
||||
+38
-13
@@ -54,18 +54,24 @@
|
||||
// - Do DOM work in the next requestAnimationFrame callback
|
||||
|
||||
pub use crate::cfg::Config;
|
||||
pub use crate::util::{use_eval, EvalResult};
|
||||
pub use crate::file_engine::WebFileEngineExt;
|
||||
use dioxus_core::{Element, Scope, VirtualDom};
|
||||
use futures_util::{pin_mut, FutureExt, StreamExt};
|
||||
use futures_util::{
|
||||
future::{select, Either},
|
||||
pin_mut, FutureExt, StreamExt,
|
||||
};
|
||||
|
||||
mod cache;
|
||||
mod cfg;
|
||||
mod dom;
|
||||
#[cfg(feature = "eval")]
|
||||
mod eval;
|
||||
#[cfg(feature = "file_engine")]
|
||||
mod file_engine;
|
||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||
mod hot_reload;
|
||||
#[cfg(feature = "hydrate")]
|
||||
mod rehydrate;
|
||||
mod util;
|
||||
|
||||
// Currently disabled since it actually slows down immediate rendering
|
||||
// todo: only schedule non-immediate renders through ric/raf
|
||||
@@ -164,15 +170,23 @@ pub fn launch_with_props<T: 'static>(
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_props: T, cfg: Config) {
|
||||
log::info!("Starting up");
|
||||
tracing::info!("Starting up");
|
||||
|
||||
let mut dom = VirtualDom::new_with_props(root, root_props);
|
||||
|
||||
#[cfg(feature = "eval")]
|
||||
{
|
||||
// Eval
|
||||
let cx = dom.base_scope();
|
||||
eval::init_eval(cx);
|
||||
}
|
||||
|
||||
#[cfg(feature = "panic_hook")]
|
||||
if cfg.default_panic_hook {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||
let mut hotreload_rx = hot_reload::init();
|
||||
|
||||
for s in crate::cache::BUILTIN_INTERNED_STRINGS {
|
||||
@@ -191,7 +205,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
|
||||
let mut websys_dom = dom::WebsysDom::new(cfg, tx);
|
||||
|
||||
log::info!("rebuilding app");
|
||||
tracing::info!("rebuilding app");
|
||||
|
||||
if should_hydrate {
|
||||
#[cfg(feature = "hydrate")]
|
||||
@@ -203,7 +217,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
websys_dom.load_templates(&templates);
|
||||
|
||||
if let Err(err) = websys_dom.rehydrate(&dom) {
|
||||
log::error!(
|
||||
tracing::error!(
|
||||
"Rehydration failed {:?}. Rebuild DOM into element from scratch",
|
||||
&err
|
||||
);
|
||||
@@ -226,7 +240,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
websys_dom.mount();
|
||||
|
||||
loop {
|
||||
log::trace!("waiting for work");
|
||||
tracing::trace!("waiting for work");
|
||||
|
||||
// if virtualdom has nothing, wait for it to have something before requesting idle time
|
||||
// if there is work then this future resolves immediately.
|
||||
@@ -234,12 +248,23 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
let work = dom.wait_for_work().fuse();
|
||||
pin_mut!(work);
|
||||
|
||||
futures_util::select! {
|
||||
_ = work => (None, None),
|
||||
new_template = hotreload_rx.next() => {
|
||||
(None, new_template)
|
||||
}
|
||||
evt = rx.next() => (evt, None)
|
||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||
// futures_util::select! {
|
||||
// _ = work => (None, None),
|
||||
// new_template = hotreload_rx.next() => {
|
||||
// (None, new_template)
|
||||
// }
|
||||
// evt = rx.next() =>
|
||||
// }
|
||||
match select(work, select(hotreload_rx.next(), rx.next())).await {
|
||||
Either::Left((_, _)) => (None, None),
|
||||
Either::Right((Either::Left((new_template, _)), _)) => (None, new_template),
|
||||
Either::Right((Either::Right((evt, _)), _)) => (evt, None),
|
||||
}
|
||||
#[cfg(not(all(feature = "hot_reload", debug_assertions)))]
|
||||
match select(work, rx.next()).await {
|
||||
Either::Left((_, _)) => (None, None),
|
||||
Either::Right((evt, _)) => (evt, None),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::dom::WebsysDom;
|
||||
use dioxus_core::{
|
||||
AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VPlaceholder, VText,
|
||||
VirtualDom,
|
||||
AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom,
|
||||
};
|
||||
use dioxus_html::event_bubbles;
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -115,9 +114,9 @@ impl WebsysDom {
|
||||
node: &TemplateNode,
|
||||
last_node_was_static_text: &mut bool,
|
||||
) -> Result<(), RehydrationError> {
|
||||
log::trace!("rehydrate template node: {:?}", node);
|
||||
tracing::trace!("rehydrate template node: {:?}", node);
|
||||
if let Ok(current_child) = current_child {
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
if tracing::event_enabled!(tracing::Level::TRACE) {
|
||||
web_sys::console::log_1(¤t_child.clone().into());
|
||||
}
|
||||
}
|
||||
@@ -130,7 +129,7 @@ impl WebsysDom {
|
||||
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
|
||||
let attribute = &vnode.dynamic_attrs[*id];
|
||||
let value = &attribute.value;
|
||||
let id = attribute.mounted_element.get();
|
||||
let id = attribute.mounted_element();
|
||||
mounted_id = Some(id);
|
||||
let name = attribute.name;
|
||||
if let AttributeValue::Listener(_) = value {
|
||||
@@ -204,14 +203,15 @@ impl WebsysDom {
|
||||
dynamic: &DynamicNode,
|
||||
last_node_was_static_text: &mut bool,
|
||||
) -> Result<(), RehydrationError> {
|
||||
log::trace!("rehydrate dynamic node: {:?}", dynamic);
|
||||
tracing::trace!("rehydrate dynamic node: {:?}", dynamic);
|
||||
if let Ok(current_child) = current_child {
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
if tracing::event_enabled!(tracing::Level::TRACE) {
|
||||
web_sys::console::log_1(¤t_child.clone().into());
|
||||
}
|
||||
}
|
||||
match dynamic {
|
||||
dioxus_core::DynamicNode::Text(VText { id, .. }) => {
|
||||
dioxus_core::DynamicNode::Text(text) => {
|
||||
let id = text.mounted_element();
|
||||
// skip comment separator before node
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(current_child
|
||||
@@ -227,7 +227,7 @@ impl WebsysDom {
|
||||
|
||||
set_node(
|
||||
hydrated,
|
||||
id.get().ok_or(VNodeNotInitialized)?,
|
||||
id.ok_or(VNodeNotInitialized)?,
|
||||
current_child.clone()?,
|
||||
);
|
||||
*current_child = current_child
|
||||
@@ -251,10 +251,10 @@ impl WebsysDom {
|
||||
|
||||
*last_node_was_static_text = false;
|
||||
}
|
||||
dioxus_core::DynamicNode::Placeholder(VPlaceholder { id, .. }) => {
|
||||
dioxus_core::DynamicNode::Placeholder(placeholder) => {
|
||||
set_node(
|
||||
hydrated,
|
||||
id.get().ok_or(VNodeNotInitialized)?,
|
||||
placeholder.mounted_element().ok_or(VNodeNotInitialized)?,
|
||||
current_child.clone()?,
|
||||
);
|
||||
*current_child = current_child
|
||||
@@ -265,7 +265,7 @@ impl WebsysDom {
|
||||
*last_node_was_static_text = false;
|
||||
}
|
||||
dioxus_core::DynamicNode::Component(comp) => {
|
||||
let scope = comp.scope.get().ok_or(VNodeNotInitialized)?;
|
||||
let scope = comp.mounted_scope().ok_or(VNodeNotInitialized)?;
|
||||
self.rehydrate_scope(
|
||||
dom.get_scope(scope).unwrap(),
|
||||
current_child,
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Utilities specific to websys
|
||||
|
||||
use std::{
|
||||
future::{IntoFuture, Ready},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use dioxus_core::*;
|
||||
use serde::de::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
/// Get a closure that executes any JavaScript in the webpage.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Please be very careful with this function. A script with too many dynamic
|
||||
/// parts is practically asking for a hacker to find an XSS vulnerability in
|
||||
/// it. **This applies especially to web targets, where the JavaScript context
|
||||
/// has access to most, if not all of your application data.**
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The closure will panic if the provided script is not valid JavaScript code
|
||||
/// or if it returns an uncaught error.
|
||||
pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String) -> EvalResult> {
|
||||
cx.use_hook(|| {
|
||||
Rc::new(|script: String| EvalResult {
|
||||
value: if let Ok(value) =
|
||||
js_sys::Function::new_no_args(&script).call0(&wasm_bindgen::JsValue::NULL)
|
||||
{
|
||||
if let Ok(stringified) = js_sys::JSON::stringify(&value) {
|
||||
if !stringified.is_undefined() && stringified.is_valid_utf16() {
|
||||
let string: String = stringified.into();
|
||||
Value::from_str(&string)
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to stringify result"))
|
||||
}
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to stringify result"))
|
||||
}
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to execute script"))
|
||||
},
|
||||
}) as Rc<dyn Fn(String) -> EvalResult>
|
||||
})
|
||||
}
|
||||
|
||||
/// A wrapper around the result of a JavaScript evaluation.
|
||||
/// This implements IntoFuture to be compatible with the desktop renderer's EvalResult.
|
||||
pub struct EvalResult {
|
||||
value: Result<Value, serde_json::Error>,
|
||||
}
|
||||
|
||||
impl EvalResult {
|
||||
/// Get the result of the Javascript execution.
|
||||
pub fn get(self) -> Result<Value, serde_json::Error> {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for EvalResult {
|
||||
type Output = Result<Value, serde_json::Error>;
|
||||
|
||||
type IntoFuture = Ready<Result<Value, serde_json::Error>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
std::future::ready(self.value)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user