Merge branch 'upstream' into make-widgets-cross-framework

This commit is contained in:
Evan Almloff committed 2023-04-12 10:04:18 -05:00
1 parent 4bc5d457ae
commit e7fb03f900
10 files changed
+125 -24

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
//! Comparison example with leptos' counter example
//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let counters = use_state(cx, || vec![0, 0, 0]);
let sum: usize = counters.iter().copied().sum();
render! {
div {
button { onclick: move |_| counters.make_mut().push(0), "Add counter" }
button { onclick: move |_| { counters.make_mut().pop(); }, "Remove counter" }
p { "Total: {sum}" }
for (i, counter) in counters.iter().enumerate() {
li {
button { onclick: move |_| counters.make_mut()[i] -= 1, "-1" }
input {
value: "{counter}",
oninput: move |e| {
if let Ok(value) = e.value.parse::<usize>() {
counters.make_mut()[i] = value;
}
}
}
button { onclick: move |_| counters.make_mut()[i] += 1, "+1" }
button { onclick: move |_| { counters.make_mut().remove(i); }, "x" }
}
}
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ serde = "1.0.136"
serde_json = "1.0.79" serde_json = "1.0.79"
thiserror = "1.0.30" thiserror = "1.0.30"
log = "0.4.14" log = "0.4.14"
wry = { version = "0.23.4" } wry = { version = "0.27.2" }
futures-channel = "0.3.21" futures-channel = "0.3.21"
tokio = { version = "1.16.1", features = [ tokio = { version = "1.16.1", features = [
"sync", "sync",
+3 -2
View File
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::path::PathBuf; use std::path::PathBuf;
use wry::application::window::Icon; use wry::application::window::Icon;
@@ -28,7 +29,7 @@ type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
pub(crate) type WryProtocol = ( pub(crate) type WryProtocol = (
String, String,
Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static>, Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static>,
); );
impl Config { impl Config {
@@ -98,7 +99,7 @@ impl Config {
/// Set a custom protocol /// Set a custom protocol
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
where where
F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static, F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static,
{ {
self.protocols.push((name, Box::new(handler))); self.protocols.push((name, Box::new(handler)));
self self
+4 -2
View File
@@ -36,8 +36,8 @@ use tao::{
}; };
pub use wry; pub use wry;
pub use wry::application as tao; pub use wry::application as tao;
use wry::application::window::WindowId;
use wry::webview::WebView; use wry::webview::WebView;
use wry::{application::window::WindowId, webview::WebContext};
/// Launch the WebView and run the event loop. /// Launch the WebView and run the event loop.
/// ///
@@ -281,7 +281,7 @@ fn create_new_window(
event_handlers: &WindowEventHandlers, event_handlers: &WindowEventHandlers,
shortcut_manager: ShortcutRegistry, shortcut_manager: ShortcutRegistry,
) -> WebviewHandler { ) -> WebviewHandler {
let webview = webview::build(&mut cfg, event_loop, proxy.clone()); let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone());
dom.base_scope().provide_context(DesktopContext::new( dom.base_scope().provide_context(DesktopContext::new(
webview.clone(), webview.clone(),
@@ -299,6 +299,7 @@ fn create_new_window(
webview, webview,
dom, dom,
waker: waker::tao_waker(proxy, id), waker: waker::tao_waker(proxy, id),
web_context,
} }
} }
@@ -306,6 +307,7 @@ struct WebviewHandler {
dom: VirtualDom, dom: VirtualDom,
webview: Rc<wry::webview::WebView>, webview: Rc<wry::webview::WebView>,
waker: Waker, waker: Waker,
web_context: WebContext,
} }
/// Poll the virtualdom until it's pending /// Poll the virtualdom until it's pending
+8 -5
View File
@@ -1,5 +1,8 @@
use dioxus_interpreter_js::INTERPRETER_JS; use dioxus_interpreter_js::INTERPRETER_JS;
use std::path::{Path, PathBuf}; use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use wry::{ use wry::{
http::{status::StatusCode, Request, Response}, http::{status::StatusCode, Request, Response},
Result, Result,
@@ -27,7 +30,7 @@ pub(super) fn desktop_handler(
custom_head: Option<String>, custom_head: Option<String>,
custom_index: Option<String>, custom_index: Option<String>,
root_name: &str, root_name: &str,
) -> Result<Response<Vec<u8>>> { ) -> Result<Response<Cow<'static, [u8]>>> {
// If the request is for the root, we'll serve the index.html file. // If the request is for the root, we'll serve the index.html file.
if request.uri().path() == "/" { if request.uri().path() == "/" {
// If a custom index is provided, just defer to that, expecting the user to know what they're doing. // If a custom index is provided, just defer to that, expecting the user to know what they're doing.
@@ -53,7 +56,7 @@ pub(super) fn desktop_handler(
return Response::builder() return Response::builder()
.header("Content-Type", "text/html") .header("Content-Type", "text/html")
.body(body) .body(Cow::from(body))
.map_err(From::from); .map_err(From::from);
} }
@@ -72,13 +75,13 @@ pub(super) fn desktop_handler(
if asset.exists() { if asset.exists() {
return Response::builder() return Response::builder()
.header("Content-Type", get_mime_from_path(&asset)?) .header("Content-Type", get_mime_from_path(&asset)?)
.body(std::fs::read(asset)?) .body(Cow::from(std::fs::read(asset)?))
.map_err(From::from); .map_err(From::from);
} }
Response::builder() Response::builder()
.status(StatusCode::NOT_FOUND) .status(StatusCode::NOT_FOUND)
.body(String::from("Not Found").into_bytes()) .body(Cow::from(String::from("Not Found").into_bytes()))
.map_err(From::from) .map_err(From::from)
} }
+7 -8
View File
@@ -7,13 +7,13 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
pub use wry; pub use wry;
pub use wry::application as tao; pub use wry::application as tao;
use wry::application::window::Window; use wry::application::window::Window;
use wry::webview::{WebView, WebViewBuilder}; use wry::webview::{WebContext, WebView, WebViewBuilder};
pub fn build( pub fn build(
cfg: &mut Config, cfg: &mut Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>, event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: EventLoopProxy<UserWindowEvent>, proxy: EventLoopProxy<UserWindowEvent>,
) -> Rc<WebView> { ) -> (Rc<WebView>, WebContext) {
let builder = cfg.window.clone(); let builder = cfg.window.clone();
let window = builder.build(event_loop).unwrap(); let window = builder.build(event_loop).unwrap();
let file_handler = cfg.file_drop_handler.take(); let file_handler = cfg.file_drop_handler.take();
@@ -33,6 +33,8 @@ pub fn build(
)); ));
} }
let mut web_context = WebContext::new(cfg.data_dir.clone());
let mut webview = WebViewBuilder::new(window) let mut webview = WebViewBuilder::new(window)
.unwrap() .unwrap()
.with_transparent(cfg.window.window.transparent) .with_transparent(cfg.window.window.transparent)
@@ -52,11 +54,8 @@ pub fn build(
.as_ref() .as_ref()
.map(|handler| handler(window, evet)) .map(|handler| handler(window, evet))
.unwrap_or_default() .unwrap_or_default()
}); })
.with_web_context(&mut web_context);
// These are commented out because wry is currently broken in wry
// let mut web_context = WebContext::new(cfg.data_dir.clone());
// .with_web_context(&mut web_context);
for (name, handler) in cfg.protocols.drain(..) { for (name, handler) in cfg.protocols.drain(..) {
webview = webview.with_custom_protocol(name, handler) webview = webview.with_custom_protocol(name, handler)
@@ -82,5 +81,5 @@ pub fn build(
webview = webview.with_devtools(true); webview = webview.with_devtools(true);
} }
Rc::new(webview.build().unwrap()) (Rc::new(webview.build().unwrap()), web_context)
} }
+15 -1
View File
@@ -23,6 +23,8 @@ pub enum Segment {
// This will be true if there are static styles // This will be true if there are static styles
inside_style_tag: bool, inside_style_tag: bool,
}, },
/// A marker for where to insert a dynamic inner html
InnerHtmlMarker,
} }
impl std::fmt::Write for StringChain { impl std::fmt::Write for StringChain {
@@ -69,6 +71,9 @@ impl StringCache {
write!(chain, "<{tag}")?; write!(chain, "<{tag}")?;
// we need to collect the styles and write them at the end // we need to collect the styles and write them at the end
let mut styles = Vec::new(); let mut styles = Vec::new();
// we need to collect the inner html and write it at the end
let mut inner_html = None;
// we need to keep track of if we have dynamic attrs to know if we need to insert a style and inner_html marker
let mut has_dynamic_attrs = false; let mut has_dynamic_attrs = false;
for attr in *attrs { for attr in *attrs {
match attr { match attr {
@@ -77,7 +82,9 @@ impl StringCache {
value, value,
namespace, namespace,
} => { } => {
if let Some("style") = namespace { if *name == "dangerous_inner_html" {
inner_html = Some(value);
} else if let Some("style") = namespace {
styles.push((name, value)); styles.push((name, value));
} else { } else {
write!(chain, " {name}=\"{value}\"")?; write!(chain, " {name}=\"{value}\"")?;
@@ -110,6 +117,13 @@ impl StringCache {
write!(chain, "/>")?; write!(chain, "/>")?;
} else { } else {
write!(chain, ">")?; write!(chain, ">")?;
// Write the static inner html, or insert a marker if dynamic inner html is possible
if let Some(inner_html) = inner_html {
chain.write_str(inner_html)?;
} else if has_dynamic_attrs {
chain.segments.push(Segment::InnerHtmlMarker);
}
for child in *children { for child in *children {
Self::recurse(child, cur_path, root_idx, chain)?; Self::recurse(child, cur_path, root_idx, chain)?;
} }
+21 -2
View File
@@ -70,6 +70,8 @@ impl Renderer {
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
.clone(); .clone();
let mut inner_html = None;
// We need to keep track of the dynamic styles so we can insert them into the right place // We need to keep track of the dynamic styles so we can insert them into the right place
let mut accumulated_dynamic_styles = Vec::new(); let mut accumulated_dynamic_styles = Vec::new();
@@ -77,7 +79,9 @@ impl Renderer {
match segment { match segment {
Segment::Attr(idx) => { Segment::Attr(idx) => {
let attr = &template.dynamic_attrs[*idx]; let attr = &template.dynamic_attrs[*idx];
if attr.namespace == Some("style") { if attr.name == "dangerous_inner_html" {
inner_html = Some(attr);
} else if attr.namespace == Some("style") {
accumulated_dynamic_styles.push(attr); accumulated_dynamic_styles.push(attr);
} else { } else {
match attr.value { match attr.value {
@@ -165,6 +169,19 @@ impl Renderer {
accumulated_dynamic_styles.clear(); accumulated_dynamic_styles.clear();
} }
} }
Segment::InnerHtmlMarker => {
if let Some(inner_html) = inner_html.take() {
let inner_html = &inner_html.value;
match inner_html {
AttributeValue::Text(value) => write!(buf, "{}", value)?,
AttributeValue::Bool(value) => write!(buf, "{}", value)?,
AttributeValue::Float(f) => write!(buf, "{}", f)?,
AttributeValue::Int(i) => write!(buf, "{}", i)?,
_ => {}
}
}
}
} }
} }
@@ -208,7 +225,9 @@ fn to_string_works() {
StyleMarker { StyleMarker {
inside_style_tag: false, inside_style_tag: false,
}, },
PreRendered(">Hello world 1 --&gt;".into(),), PreRendered(">".into()),
InnerHtmlMarker,
PreRendered("Hello world 1 --&gt;".into(),),
Node(0,), Node(0,),
PreRendered( PreRendered(
"&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(), "&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),
+26
View File
@@ -0,0 +1,26 @@
use dioxus::prelude::*;
#[test]
fn static_inner_html() {
fn app(cx: Scope) -> Element {
render! { div { dangerous_inner_html: "<div>1234</div>" } }
}
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
assert_eq!(dioxus_ssr::render(&dom), r#"<div><div>1234</div></div>"#);
}
#[test]
fn dynamic_inner_html() {
fn app(cx: Scope) -> Element {
let inner_html = "<div>1234</div>";
render! { div { dangerous_inner_html: "{inner_html}" } }
}
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
assert_eq!(dioxus_ssr::render(&dom), r#"<div><div>1234</div></div>"#);
}
+4 -3
View File
@@ -49,15 +49,16 @@ impl Config {
/// Set the name of the element that Dioxus will use as the root. /// Set the name of the element that Dioxus will use as the root.
/// ///
/// This is akint to calling React.render() on the element with the specified name. /// This is akin to calling React.render() on the element with the specified name.
pub fn rootname(mut self, name: impl Into<String>) -> Self { pub fn rootname(mut self, name: impl Into<String>) -> Self {
self.rootname = name.into(); self.rootname = name.into();
self self
} }
/// Set the name of the element that Dioxus will use as the root. /// Sets a string cache for wasm bindgen to [intern](https://docs.rs/wasm-bindgen/0.2.84/wasm_bindgen/fn.intern.html). This can help reduce the time it takes for wasm bindgen to pass
/// strings from rust to javascript. This can significantly improve pefromance when passing strings to javascript, but can have a negative impact on startup time.
/// ///
/// This is akint to calling React.render() on the element with the specified name. /// > Currently this cache is only used when creating static elements and attributes.
pub fn with_string_cache(mut self, cache: Vec<String>) -> Self { pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
self.cached_strings = cache; self.cached_strings = cache;
self self