feat: add edits back! and more webview support!
This commit adds a new type - the DomEdit - for serializing the changes made by the diffing machine. The architecture of how DomEdits fit into the cooperative scheduling is still TBD but it will allow us to build change lists without applying them immediately. This is more performant and allows us to only render parts of the page at a time. This commit also adds more infrastructure around webview. Dioxus can now run on the web, generate static pages, run in the desktop, and run on mobile, with a large part of thanks to webview.
This commit is contained in:
1 parent
e8f29a8f8a
commit
904b26f711
22 files changed
+1155
-735
No files matched your search
@@ -0,0 +1,14 @@
|
||||
# dioxus webview arch
|
||||
|
||||
|
||||
dioxus webview represents a usecase of dioxus to stream edits from one dom to another. In this particular case, we stream edits from a native process into the webview's runtime. WebView is really portable, so we can leverage the UI of iOS, Android, and all desktop platforms while also maintaining direct access to platform-specific APIs. It's the best of both worlds!
|
||||
|
||||
|
||||
For this to work properly, we need to ship a universal client that receives Dioxus edits from a remote process. To make our lives (as developers) a bit easier, the client code is currently implemented in JS (subject to change!).
|
||||
|
||||
The provider (the thing that talks to the client on the other side of the network) is still written in Rust, and is supplied through the dioxus-webview crate. The client is much more "dumb" than the provider - the provider must handle NodeID generation, flushing changes, priorities, managing events, and more.
|
||||
|
||||
|
||||
## update; wry!
|
||||
|
||||
Wry is an effort by the Tauri team to make fully native
|
||||
@@ -11,15 +11,15 @@ license = "MIT/Apache-2.0"
|
||||
[dependencies]
|
||||
# web-view = { git = "https://github.com/Boscop/web-view" }
|
||||
web-view = "0.7.3"
|
||||
dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] }
|
||||
dioxus-core = { path="../core", version="0.1.2", features=["serialize"] }
|
||||
anyhow = "1.0.38"
|
||||
argh = "0.1.4"
|
||||
serde = "1.0.120"
|
||||
serde_json = "1.0.61"
|
||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
||||
async-std = { version="1.9.0", features=["attributes"] }
|
||||
thiserror = "1.0.23"
|
||||
log = "0.4.13"
|
||||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
fern = { version="0.6.0", features=["colored"] }
|
||||
|
||||
# thiserror = "1.0.23"
|
||||
# log = "0.4.13"
|
||||
@@ -32,6 +32,8 @@ fern = { version = "0.6.0", features = ["colored"] }
|
||||
# async-std = { version = "1.9.0", features = ["attributes"] }
|
||||
tide = "0.15.0"
|
||||
tide-websockets = "0.3.0"
|
||||
html-escape = "0.2.9"
|
||||
# wry = "0.10.3"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
@@ -10,7 +10,7 @@ Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop
|
||||
async fn main() {
|
||||
dioxus_webview::new(|cx| {
|
||||
let (count, set_count) = use_state(&cx, || 0);
|
||||
html! {
|
||||
cx.render(html! {
|
||||
<div>
|
||||
<h1> "Dioxus Desktop Demo" </h1>
|
||||
<p> "Count is {count}"</p>
|
||||
@@ -18,7 +18,7 @@ async fn main() {
|
||||
"Click to increment"
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
})
|
||||
})
|
||||
.configure_webview(|view| {
|
||||
// custom webview config options
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
use dioxus::events::on::*;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core as dioxus;
|
||||
|
||||
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
|
||||
|
||||
fn main() {
|
||||
dioxus_webview::launch(
|
||||
|builder| {
|
||||
builder
|
||||
.title("Test Dioxus App")
|
||||
.size(340, 560)
|
||||
.resizable(false)
|
||||
.debug(true)
|
||||
},
|
||||
(),
|
||||
App,
|
||||
)
|
||||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let state = use_model(&cx, || Calculator::new());
|
||||
|
||||
let clear_display = state.display_value.eq("0");
|
||||
let clear_text = if clear_display { "C" } else { "AC" };
|
||||
let formatted = state.formatted_display();
|
||||
|
||||
cx.render(rsx! {
|
||||
div { id: "wrapper"
|
||||
div { class: "app", style { "{STYLE}" }
|
||||
div { class: "calculator", onkeypress: move |evt| state.get_mut().handle_keydown(evt),
|
||||
div { class: "calculator-display", "{formatted}"}
|
||||
div { class: "calculator-keypad"
|
||||
div { class: "input-keys"
|
||||
div { class: "function-keys"
|
||||
CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().clear_display(), "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: move |_| state.get_mut().toggle_sign(), "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: move |_| state.get_mut().toggle_percent(), "%"}
|
||||
}
|
||||
div { class: "digit-keys"
|
||||
CalculatorKey { name: "key-0", onclick: move |_| state.get_mut().input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| state.get_mut().input_dot(), "●" }
|
||||
{(1..10).map(move |k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.get_mut().input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name:"key-divide", onclick: move |_| state.get_mut().set_operator(Operator::Div), "÷" }
|
||||
CalculatorKey { name:"key-multiply", onclick: move |_| state.get_mut().set_operator(Operator::Mul), "×" }
|
||||
CalculatorKey { name:"key-subtract", onclick: move |_| state.get_mut().set_operator(Operator::Sub), "−" }
|
||||
CalculatorKey { name:"key-add", onclick: move |_| state.get_mut().set_operator(Operator::Add), "+" }
|
||||
CalculatorKey { name:"key-equals", onclick: move |_| state.get_mut().perform_operation(), "=" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps<'a> {
|
||||
name: &'static str,
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.name}"
|
||||
onclick: {cx.onclick}
|
||||
{cx.children()}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Calculator {
|
||||
display_value: String,
|
||||
operator: Option<Operator>,
|
||||
waiting_for_operand: bool,
|
||||
cur_val: f64,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum Operator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
fn new() -> Self {
|
||||
Calculator {
|
||||
display_value: "0".to_string(),
|
||||
operator: None,
|
||||
waiting_for_operand: false,
|
||||
cur_val: 0.0,
|
||||
}
|
||||
}
|
||||
fn formatted_display(&self) -> String {
|
||||
// use separator::Separatable;
|
||||
self.display_value.clone()
|
||||
// .parse::<f64>()
|
||||
// .unwrap()
|
||||
// .separated_string()
|
||||
}
|
||||
fn clear_display(&mut self) {
|
||||
self.display_value = "0".to_string();
|
||||
}
|
||||
fn input_digit(&mut self, digit: u8) {
|
||||
let content = digit.to_string();
|
||||
if self.waiting_for_operand || self.display_value == "0" {
|
||||
self.waiting_for_operand = false;
|
||||
self.display_value = content;
|
||||
} else {
|
||||
self.display_value.push_str(content.as_str());
|
||||
}
|
||||
}
|
||||
fn input_dot(&mut self) {
|
||||
if self.display_value.find(".").is_none() {
|
||||
self.display_value.push_str(".");
|
||||
}
|
||||
}
|
||||
fn perform_operation(&mut self) {
|
||||
if let Some(op) = &self.operator {
|
||||
let rhs = self.display_value.parse::<f64>().unwrap();
|
||||
let new_val = match op {
|
||||
Operator::Add => self.cur_val + rhs,
|
||||
Operator::Sub => self.cur_val - rhs,
|
||||
Operator::Mul => self.cur_val * rhs,
|
||||
Operator::Div => self.cur_val / rhs,
|
||||
};
|
||||
self.cur_val = new_val;
|
||||
self.display_value = new_val.to_string();
|
||||
self.operator = None;
|
||||
}
|
||||
}
|
||||
fn toggle_sign(&mut self) {
|
||||
if self.display_value.starts_with("-") {
|
||||
self.display_value = self.display_value.trim_start_matches("-").to_string();
|
||||
} else {
|
||||
self.display_value = format!("-{}", self.display_value);
|
||||
}
|
||||
}
|
||||
fn toggle_percent(&mut self) {
|
||||
self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
|
||||
}
|
||||
fn backspace(&mut self) {
|
||||
if !self.display_value.as_str().eq("0") {
|
||||
self.display_value.pop();
|
||||
}
|
||||
}
|
||||
fn set_operator(&mut self, operator: Operator) {
|
||||
self.operator = Some(operator);
|
||||
self.cur_val = self.display_value.parse::<f64>().unwrap();
|
||||
self.waiting_for_operand = true;
|
||||
}
|
||||
fn handle_keydown(&mut self, evt: KeyboardEvent) {
|
||||
match evt.key_code() {
|
||||
KeyCode::Backspace => self.backspace(),
|
||||
KeyCode::_0 => self.input_digit(0),
|
||||
KeyCode::_1 => self.input_digit(1),
|
||||
KeyCode::_2 => self.input_digit(2),
|
||||
KeyCode::_3 => self.input_digit(3),
|
||||
KeyCode::_4 => self.input_digit(4),
|
||||
KeyCode::_5 => self.input_digit(5),
|
||||
KeyCode::_6 => self.input_digit(6),
|
||||
KeyCode::_7 => self.input_digit(7),
|
||||
KeyCode::_8 => self.input_digit(8),
|
||||
KeyCode::_9 => self.input_digit(9),
|
||||
KeyCode::Add => self.operator = Some(Operator::Add),
|
||||
KeyCode::Subtract => self.operator = Some(Operator::Sub),
|
||||
KeyCode::Divide => self.operator = Some(Operator::Div),
|
||||
KeyCode::Multiply => self.operator = Some(Operator::Mul),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! An example where the dioxus vdom is running in a native thread, interacting with webview
|
||||
//! Content is passed from the native thread into the webview
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_webview::launch(
|
||||
|builder| {
|
||||
@@ -17,49 +17,55 @@ fn main() {
|
||||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static Example: FC<()> = |cx| {
|
||||
cx.render(html! {
|
||||
<div>
|
||||
<svg class="octicon octicon-star v-align-text-bottom"
|
||||
viewBox="0 0 14 16" version="1.1"
|
||||
width="14" height="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
<path
|
||||
d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
</path>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
})
|
||||
};
|
||||
// static Example: FC<()> = |cx| {
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// class: "flex items-center justify-center flex-col"
|
||||
// div {
|
||||
// class: "flex items-center justify-center"
|
||||
// div {
|
||||
// class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||
// div { class: "font-bold text-xl", "Example desktop app" }
|
||||
// div { class: "text-sm text-gray-500", "This is running natively" }
|
||||
// div {
|
||||
// class: "flex flex-row items-center justify-center mt-6"
|
||||
// div { class: "font-medium text-6xl", "100%" }
|
||||
// }
|
||||
// div {
|
||||
// class: "flex flex-row justify-between mt-6"
|
||||
// a {
|
||||
// href: "https://www.dioxuslabs.com"
|
||||
// class: "underline"
|
||||
// "Made with dioxus"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// cx.render(html! {
|
||||
// <div>
|
||||
// <svg class="octicon octicon-star v-align-text-bottom"
|
||||
// viewBox="0 0 14 16" version="1.1"
|
||||
// width="14" height="16"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
|
||||
// <path
|
||||
// d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
// </path>
|
||||
|
||||
// </svg>
|
||||
// </div>
|
||||
// })
|
||||
// };
|
||||
static Example: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "flex items-center justify-center flex-col"
|
||||
div {
|
||||
class: "flex items-center justify-center"
|
||||
div {
|
||||
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||
div { class: "font-bold text-xl", "Example desktop app" }
|
||||
div { class: "text-sm text-gray-500", "This is running natively" }
|
||||
div {
|
||||
class: "flex flex-row items-center justify-center mt-6"
|
||||
div { class: "font-medium text-6xl", "100%" }
|
||||
}
|
||||
div {
|
||||
class: "flex flex-row justify-between mt-6"
|
||||
a {
|
||||
href: "https://www.dioxuslabs.com"
|
||||
class: "underline"
|
||||
"Made with dioxus"
|
||||
}
|
||||
}
|
||||
ul {
|
||||
{(0..10).map(|f| rsx!(li {
|
||||
key: "{f}"
|
||||
"{f}"
|
||||
}))}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
//! An example where the dioxus vdom is running in a native thread, interacting with webview
|
||||
//! Content is passed from the native thread into the webview
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
fn main() {
|
||||
dioxus_webview::launch(
|
||||
|builder| {
|
||||
builder
|
||||
.title("Test Dioxus App")
|
||||
.size(320, 480)
|
||||
.resizable(false)
|
||||
.debug(true)
|
||||
},
|
||||
(),
|
||||
App,
|
||||
)
|
||||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let hifives = use_model(&cx, || 0);
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "Hi-fives collected: {hifives}" }
|
||||
button { "Hi five me!", onclick: move |_| *hifives.get_mut() += 1 }
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core::serialize::DomEdits;
|
||||
use dioxus_core::{
|
||||
diff::RealDom,
|
||||
prelude::ScopeIdx,
|
||||
virtual_dom::{RealDomNode, VirtualDom},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use DomEdits::*;
|
||||
|
||||
fn test() {
|
||||
const App: FC<()> = |cx| cx.render(rsx! { div {}});
|
||||
@@ -20,7 +22,7 @@ fn test() {
|
||||
}
|
||||
|
||||
pub struct WebviewDom<'bump> {
|
||||
pub edits: Vec<SerializedDom<'bump>>,
|
||||
pub edits: Vec<DomEdits<'bump>>,
|
||||
pub node_counter: u64,
|
||||
}
|
||||
impl WebviewDom<'_> {
|
||||
@@ -31,55 +33,6 @@ impl WebviewDom<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum SerializedDom<'bump> {
|
||||
PushRoot {
|
||||
root: u64,
|
||||
},
|
||||
AppendChild,
|
||||
ReplaceWith,
|
||||
Remove,
|
||||
RemoveAllChildren,
|
||||
CreateTextNode {
|
||||
text: &'bump str,
|
||||
id: u64,
|
||||
},
|
||||
CreateElement {
|
||||
tag: &'bump str,
|
||||
id: u64,
|
||||
},
|
||||
CreateElementNs {
|
||||
tag: &'bump str,
|
||||
id: u64,
|
||||
ns: &'bump str,
|
||||
},
|
||||
CreatePlaceholder {
|
||||
id: u64,
|
||||
},
|
||||
NewEventListener {
|
||||
event: &'bump str,
|
||||
scope: ScopeIdx,
|
||||
node: u64,
|
||||
idx: usize,
|
||||
},
|
||||
RemoveEventListener {
|
||||
event: &'bump str,
|
||||
},
|
||||
SetText {
|
||||
text: &'bump str,
|
||||
},
|
||||
SetAttribute {
|
||||
field: &'bump str,
|
||||
value: &'bump str,
|
||||
ns: Option<&'bump str>,
|
||||
},
|
||||
RemoveAttribute {
|
||||
name: &'bump str,
|
||||
},
|
||||
}
|
||||
|
||||
use SerializedDom::*;
|
||||
impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
|
||||
fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
|
||||
self.edits.push(PushRoot { root: root.0 });
|
||||
|
||||
@@ -1 +1,131 @@
|
||||
<!-- this is used to instantiate the module -->
|
||||
<!-- a js-only interpreter for the dioxus patch stream :) -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta charset="UTF-8" />
|
||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
<script>
|
||||
class Interpreter {
|
||||
constructor(root) {
|
||||
this.stack = [root];
|
||||
this.lastNodeWasText = false;
|
||||
this.nodes = {
|
||||
0: root
|
||||
};
|
||||
}
|
||||
|
||||
top() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
|
||||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
class OPTABLE {
|
||||
PushRoot(self, edit) {
|
||||
const id = edit.root;
|
||||
const node = self.nodes[id];
|
||||
self.stack.push(node);
|
||||
}
|
||||
AppendChild(self, edit) {
|
||||
// todo: prevent merging of text nodes
|
||||
const node = self.pop();
|
||||
self.top().appendChild(node);
|
||||
}
|
||||
ReplaceWith(self, edit) {
|
||||
const newNode = self.pop();
|
||||
const oldNode = self.pop();
|
||||
oldNode.replaceWith(newNode);
|
||||
self.stack.push(newNode);
|
||||
}
|
||||
Remove(self, edit) {
|
||||
const node = self.stack.pop();
|
||||
node.remove();
|
||||
}
|
||||
RemoveAllChildren(self, edit) {
|
||||
// todo - we never actually call this one
|
||||
}
|
||||
CreateTextNode(self, edit) {
|
||||
self.stack.push(document.createTextNode(edit.text));
|
||||
}
|
||||
CreateElement(self, edit) {
|
||||
const tagName = edit.tag;
|
||||
console.log(`creating element! ${edit}`);
|
||||
self.stack.push(document.createElement(tagName));
|
||||
}
|
||||
CreateElementNs(self, edit) {
|
||||
self.stack.push(document.createElementNS(edit.ns, edit.tag));
|
||||
}
|
||||
CreatePlaceholder(self, edit) {
|
||||
self.stack.push(document.createElement("pre"));
|
||||
}
|
||||
NewEventListener(self, edit) {
|
||||
// todo
|
||||
}
|
||||
RemoveEventListener(self, edit) {
|
||||
// todo
|
||||
}
|
||||
SetText(self, edit) {
|
||||
self.top().textContent = edit.text;
|
||||
}
|
||||
SetAttribute(self, edit) {
|
||||
const name = edit.field;
|
||||
const value = edit.value;
|
||||
const node = self.top(self.stack);
|
||||
node.setAttribute(name, value);
|
||||
|
||||
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||
if ((name === "value", self)) {
|
||||
node.value = value;
|
||||
}
|
||||
if ((name === "checked", self)) {
|
||||
node.checked = true;
|
||||
}
|
||||
if ((name === "selected", self)) {
|
||||
node.selected = true;
|
||||
}
|
||||
}
|
||||
RemoveAttribute(self, edit) {
|
||||
const name = edit.field;
|
||||
const node = self.top(self.stack);
|
||||
node.removeAttribute(name);
|
||||
|
||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||
if ((name === "value", self)) {
|
||||
node.value = null;
|
||||
}
|
||||
if ((name === "checked", self)) {
|
||||
node.checked = false;
|
||||
}
|
||||
if ((name === "selected", self)) {
|
||||
node.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const op_table = new OPTABLE();
|
||||
const interpreter = new Interpreter(window.document.body);
|
||||
|
||||
function EditListReceived(rawEditList) {
|
||||
let editList = JSON.parse(rawEditList);
|
||||
console.warn("hnelllo");
|
||||
editList.forEach(function (edit, index) {
|
||||
console.log(edit);
|
||||
op_table[edit.type](interpreter, edit);
|
||||
});
|
||||
}
|
||||
|
||||
external.invoke("initiate");
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -3,11 +3,11 @@ use std::sync::Arc;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core::virtual_dom::VirtualDom;
|
||||
use web_view::Handle;
|
||||
use web_view::{escape, Handle};
|
||||
use web_view::{WVResult, WebView, WebViewBuilder};
|
||||
mod dom;
|
||||
|
||||
static HTML_CONTENT: &'static str = include_str!("./../../liveview/index.html");
|
||||
static HTML_CONTENT: &'static str = include_str!("./index.html");
|
||||
|
||||
pub fn launch<T: Properties + 'static>(
|
||||
builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
||||
@@ -79,13 +79,15 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||
view.step()
|
||||
.expect("should not fail")
|
||||
.expect("should not fail");
|
||||
std::thread::sleep(std::time::Duration::from_millis(15));
|
||||
|
||||
if let Ok(event) = receiver.try_recv() {
|
||||
if let InnerEvent::Initiate(handle) = event {
|
||||
let editlist = ref_edits.clone();
|
||||
handle
|
||||
.dispatch(move |view| {
|
||||
view.eval(format!("EditListReceived(`{}`);", editlist).as_str())
|
||||
let escaped = escape(&editlist);
|
||||
view.eval(&format!("EditListReceived({});", escaped))
|
||||
})
|
||||
.expect("Dispatch failed");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user