examples: webview and async
This commit is contained in:
parent
99d94b69ab
commit
eb82051000
|
@ -61,3 +61,4 @@ wasm
|
|||
attr
|
||||
derefed
|
||||
Tokio
|
||||
asynchronicity
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
//! Example: README.md showcase
|
||||
//!
|
||||
//! The example from the README.md
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures::Future;
|
||||
fn main() {
|
||||
dioxus::web::launch(App)
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
// let mut count = use_state(cx, || 0);
|
||||
let mut fut = cx.use_hook(
|
||||
move || {
|
||||
Box::pin(async {
|
||||
//
|
||||
loop {
|
||||
// repeatadly get new doggos
|
||||
match surf::get(ENDPOINT).recv_json::<DogApi>().await {
|
||||
Ok(_) => (),
|
||||
Err(_) => (),
|
||||
// Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||
// Err(_) => rsx!(in cx, div { "No doggos for you :(" }),
|
||||
}
|
||||
// wait one seconds
|
||||
}
|
||||
}) as Pin<Box<dyn Future<Output = ()> + 'static>>
|
||||
},
|
||||
|h| h,
|
||||
|_| {},
|
||||
);
|
||||
|
||||
cx.submit_task(fut);
|
||||
|
||||
todo!()
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// h1 { "Hifive counter: {count}" }
|
||||
// button { onclick: move |_| count += 1, "Up high!" }
|
||||
// button { onclick: move |_| count -= 1, "Down low!" }
|
||||
// }
|
||||
// })
|
||||
};
|
||||
|
||||
// #[derive(serde::Deserialize)]
|
||||
// struct DogApi {
|
||||
// message: String,
|
||||
// }
|
||||
// const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
|
||||
|
||||
// pub static App: FC<()> = |cx| {
|
||||
// let doggo = use_future_effect(&cx, move || async move {
|
||||
// match surf::get(ENDPOINT).recv_json::<DogApi>().await {
|
||||
// Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||
// Err(_) => rsx!(in cx, div { "No doggos for you :(" }),
|
||||
// }
|
||||
// });
|
||||
|
||||
// cx.render(rsx!(
|
||||
// div {
|
||||
// h1 {"Waiting for a doggo..."}
|
||||
// {doggo}
|
||||
// }
|
||||
// ))
|
||||
// };
|
|
@ -4,15 +4,17 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
fn main() {
|
||||
dioxus::web::launch(Example)
|
||||
dioxus::web::launch(App)
|
||||
}
|
||||
|
||||
fn Example(cx: Context<()>) -> VNode {
|
||||
let name = use_state(cx, || "..?");
|
||||
static App: FC<()> = |cx| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Hello, {name}" }
|
||||
button { "?", onclick: move |_| name.set("world!")}
|
||||
button { "?", onclick: move |_| name.set("Dioxus 🎉")}
|
||||
div {
|
||||
h1 { "Hifive counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,9 +16,9 @@ fn main() {
|
|||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let slide_id = use_state(cx, || 0);
|
||||
let slides = use_state(cx, SlideController::new);
|
||||
|
||||
let slide = match *slide_id {
|
||||
let slide = match slides.slide_id {
|
||||
0 => cx.render(rsx!(Title {})),
|
||||
1 => cx.render(rsx!(Slide1 {})),
|
||||
2 => cx.render(rsx!(Slide2 {})),
|
||||
|
@ -28,12 +28,15 @@ static App: FC<()> = |cx| {
|
|||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
style: {
|
||||
background_color: "red"
|
||||
}
|
||||
div {
|
||||
div { h1 {"my awesome slideshow"} }
|
||||
div {
|
||||
button {"<-", onclick: move |_| if *slide_id != 0 { *slide_id.get_mut() -= 1}}
|
||||
h3 { "{slide_id}" }
|
||||
button {"->" onclick: move |_| if *slide_id != 4 { *slide_id.get_mut() += 1 }}
|
||||
button {"<-", onclick: move |_| slides.get_mut().go_forward()}
|
||||
h3 { "{slides.slide_id}" }
|
||||
button {"->" onclick: move |_| slides.get_mut().go_backward()}
|
||||
}
|
||||
}
|
||||
{slide}
|
||||
|
@ -41,38 +44,69 @@ static App: FC<()> = |cx| {
|
|||
})
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SlideController {
|
||||
slide_id: isize,
|
||||
}
|
||||
impl SlideController {
|
||||
fn new() -> Self {
|
||||
Self { slide_id: 0 }
|
||||
}
|
||||
fn can_go_forward(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn can_go_backward(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn go_forward(&mut self) {
|
||||
if self.can_go_forward() {
|
||||
self.slide_id += 1;
|
||||
}
|
||||
}
|
||||
fn go_backward(&mut self) {
|
||||
if self.can_go_backward() {
|
||||
self.slide_id -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Title: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
h1 { "Title" }
|
||||
p {}
|
||||
}
|
||||
})
|
||||
};
|
||||
const Slide1: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
h1 { "Slide1" }
|
||||
p {}
|
||||
}
|
||||
})
|
||||
};
|
||||
const Slide2: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
h1 { "Slide2" }
|
||||
p {}
|
||||
}
|
||||
})
|
||||
};
|
||||
const Slide3: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
h1 { "Slide3" }
|
||||
p {}
|
||||
}
|
||||
})
|
||||
};
|
||||
const End: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
h1 { "End" }
|
||||
p {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Suspense, Async, and More
|
||||
|
||||
This doc goes into the design of asynchronicity in Dioxus.
|
||||
|
||||
|
||||
## for UI elements
|
||||
|
||||
`suspend`-ing a future submits an &mut future to Dioxus. the future must return VNodes. the future is still waiting before the component renders, the `.await` is dropped and tried again. users will want to attach their future to a hook so the future doesn't really get dropped.
|
||||
|
||||
|
||||
## for tasks
|
||||
|
||||
for more general tasks, we need some way of submitting a future or task into some sort of task system.
|
||||
|
||||
|
||||
`use_task()` submits a future to Dioxus. the future is polled infinitely until it finishes. The caller of `use_task` may drop, pause, restart, or insert a new the task
|
||||
|
||||
```rust
|
||||
|
||||
let task = use_hook(|| {
|
||||
// create the future
|
||||
}, || {
|
||||
update the future if it needs to be updated
|
||||
}, || {
|
||||
|
||||
});
|
||||
cx.poll_future()
|
||||
// let recoil_event_loop = cx.use_task(move |_| async move {
|
||||
// loop {
|
||||
// let msg = receiver.await?;
|
||||
// }
|
||||
// });
|
||||
// where suspend wraps use_task
|
||||
```
|
|
@ -1,14 +1,14 @@
|
|||
- [] Move the builder API onto NodeFactory
|
||||
- [] Transition away from names and towards compile-time safe tags
|
||||
- [] Fix diffing of fragments
|
||||
- [] Properly integrate memoization to prevent safety issues with children
|
||||
- [] Understand and fix the issue with callbacks (outdated generations)
|
||||
- [] Fix examples for core, web, ssr, and general
|
||||
- [x] Move the builder API onto NodeFactory
|
||||
- [x] Transition away from names and towards compile-time safe tags
|
||||
- [x] Fix diffing of fragments
|
||||
- [x] Properly integrate memoization to prevent safety issues with children
|
||||
- [x] Understand and fix the issue with callbacks (outdated generations)
|
||||
- [x] Fix examples for core, web, ssr, and general
|
||||
- [] Finish up documentation
|
||||
- [] Polish the Recoil (Dirac?) API
|
||||
- [] Find better names for things
|
||||
- [x] Find better names for things
|
||||
- [] get the html macro up to parity with the rsx! macro
|
||||
- [] put warnings in for iterating w/o keys
|
||||
- [x] put warnings in for iterating w/o keys
|
||||
- [] finish up the crate for generating code blocks
|
||||
- [] make progress on the docusarus-style crate for generating the websites
|
||||
- [] build the docsite
|
||||
|
@ -17,4 +17,13 @@
|
|||
- [] Implement controlled inputs for select and textarea
|
||||
- [] ...somehow... noderefs....
|
||||
|
||||
use_state(cx, )
|
||||
- [x] soup of the use_state hook
|
||||
- [ ] wire up inline css
|
||||
- [ ] compile-time correct inline CSS
|
||||
- [ ] signals
|
||||
- [ ] cooperative scheduler
|
||||
- [ ] suspense
|
||||
- [ ] task system
|
||||
- [] wire up events for webview
|
||||
- [] JIT for class-based style engines
|
||||
- []
|
||||
|
|
|
@ -41,6 +41,8 @@ slotmap = "1.0.3"
|
|||
futures = "0.3.15"
|
||||
|
||||
|
||||
async-std = { version="1.9.0", features=["attributes"] }
|
||||
|
||||
[features]
|
||||
default = ["serialize"]
|
||||
# default = []
|
||||
|
|
|
@ -149,9 +149,9 @@ pub mod on {
|
|||
|
||||
$(
|
||||
$(#[$method_attr])*
|
||||
pub fn $name<'a>(
|
||||
pub fn $name<'a, F: FnMut($wrapper) + 'a>(
|
||||
c: NodeFactory<'a>,
|
||||
mut callback: impl FnMut($wrapper) + 'a,
|
||||
mut callback: F,
|
||||
) -> Listener<'a> {
|
||||
let bump = &c.bump();
|
||||
Listener {
|
||||
|
|
|
@ -401,7 +401,7 @@ pub struct Scope {
|
|||
// NEEDS TO BE PRIVATE
|
||||
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
|
||||
|
||||
pub(crate) suspended_tasks: Vec<Box<dyn Future<Output = VNode<'static>>>>,
|
||||
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
|
||||
// pub(crate) listeners: RefCell<nohash_hasher::IntMap<u32, *const dyn FnMut(VirtualEvent)>>,
|
||||
// pub(crate) listeners: RefCell<Vec<*const dyn FnMut(VirtualEvent)>>,
|
||||
// pub(crate) listeners: RefCell<Vec<*const dyn FnMut(VirtualEvent)>>,
|
||||
|
@ -871,8 +871,27 @@ Any function prefixed with "use" should not be called conditionally.
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `submit_task` will submit the future to be polled.
|
||||
/// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
|
||||
///
|
||||
/// Tasks can't return anything, but they can be controlled with the returned handle
|
||||
///
|
||||
/// Tasks will only run until the component renders again. If you want your task to last longer than one frame, you'll need
|
||||
/// to store it somehow.
|
||||
///
|
||||
pub fn submit_task(
|
||||
&self,
|
||||
task: &'src mut Pin<Box<dyn Future<Output = ()> + 'static>>,
|
||||
) -> TaskHandle {
|
||||
// the pointer to the task is stable - we guarantee stability of all &'src references
|
||||
let task_ptr = task as *mut _;
|
||||
|
||||
TaskHandle {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskHandle {}
|
||||
#[derive(Clone)]
|
||||
pub struct SuspendedContext {}
|
||||
|
||||
|
|
|
@ -9,17 +9,32 @@ use dioxus_core::{
|
|||
};
|
||||
use DomEdit::*;
|
||||
|
||||
pub struct WebviewRegistry {}
|
||||
|
||||
impl WebviewRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebviewDom<'bump> {
|
||||
pub edits: Vec<DomEdit<'bump>>,
|
||||
pub node_counter: u64,
|
||||
pub registry: WebviewRegistry,
|
||||
}
|
||||
impl WebviewDom<'_> {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(registry: WebviewRegistry) -> Self {
|
||||
Self {
|
||||
edits: Vec::new(),
|
||||
node_counter: 0,
|
||||
registry,
|
||||
}
|
||||
}
|
||||
|
||||
// Finish using the dom (for its edit list) and give back the node and event registry
|
||||
pub fn consume(self) -> WebviewRegistry {
|
||||
self.registry
|
||||
}
|
||||
}
|
||||
impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
|
||||
fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
use std::fmt::{self, Write};
|
||||
|
||||
/// Escape a string to pass it into JavaScript.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use web_view::WebView;
|
||||
/// # use std::mem;
|
||||
/// #
|
||||
/// # let mut view: WebView<()> = unsafe { mem::uninitialized() };
|
||||
/// #
|
||||
/// let string = "Hello, world!";
|
||||
///
|
||||
/// // Calls the function callback with "Hello, world!" as its parameter.
|
||||
///
|
||||
/// view.eval(&format!("callback({});", web_view::escape(string)));
|
||||
/// ```
|
||||
pub fn escape(string: &str) -> Escaper {
|
||||
Escaper(string)
|
||||
}
|
||||
|
||||
// "All code points may appear literally in a string literal except for the
|
||||
// closing quote code points, U+005C (REVERSE SOLIDUS), U+000D (CARRIAGE
|
||||
// RETURN), U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR), and U+000A
|
||||
// (LINE FEED)." - ES6 Specification
|
||||
|
||||
pub struct Escaper<'a>(&'a str);
|
||||
|
||||
const SPECIAL: &[char] = &[
|
||||
'\n', // U+000A (LINE FEED)
|
||||
'\r', // U+000D (CARRIAGE RETURN)
|
||||
'\'', // U+0027 (APOSTROPHE)
|
||||
'\\', // U+005C (REVERSE SOLIDUS)
|
||||
'\u{2028}', // U+2028 (LINE SEPARATOR)
|
||||
'\u{2029}', // U+2029 (PARAGRAPH SEPARATOR)
|
||||
];
|
||||
|
||||
impl<'a> fmt::Display for Escaper<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let &Escaper(mut string) = self;
|
||||
|
||||
f.write_char('\'')?;
|
||||
|
||||
while !string.is_empty() {
|
||||
if let Some(i) = string.find(SPECIAL) {
|
||||
if i > 0 {
|
||||
f.write_str(&string[..i])?;
|
||||
}
|
||||
|
||||
let mut chars = string[i..].chars();
|
||||
|
||||
f.write_str(match chars.next().unwrap() {
|
||||
'\n' => "\\n",
|
||||
'\r' => "\\r",
|
||||
'\'' => "\\'",
|
||||
'\\' => "\\\\",
|
||||
'\u{2028}' => "\\u2028",
|
||||
'\u{2029}' => "\\u2029",
|
||||
_ => unreachable!(),
|
||||
})?;
|
||||
|
||||
string = chars.as_str();
|
||||
} else {
|
||||
f.write_str(string)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
f.write_char('\'')?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let plain = "ABC \n\r' abc \\ \u{2028} \u{2029}123";
|
||||
let escaped = escape(plain).to_string();
|
||||
assert!(escaped == "'ABC \\n\\r\\' abc \\\\ \\u2028 \\u2029123'");
|
||||
}
|
|
@ -5,9 +5,6 @@
|
|||
<!-- <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" /> -->
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
|
@ -16,7 +13,9 @@
|
|||
class Interpreter {
|
||||
constructor(root) {
|
||||
this.stack = [root];
|
||||
this.listeners = {};
|
||||
this.listeners = {
|
||||
|
||||
};
|
||||
this.lastNodeWasText = false;
|
||||
this.nodes = {
|
||||
0: root
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::borrow::BorrowMut;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
|
@ -10,6 +11,7 @@ use wry::{
|
|||
};
|
||||
|
||||
mod dom;
|
||||
mod escape;
|
||||
|
||||
static HTML_CONTENT: &'static str = include_str!("./index.html");
|
||||
|
||||
|
@ -59,33 +61,57 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||
|
||||
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
|
||||
|
||||
let mut vdom = VirtualDom::new_with_props(root, props);
|
||||
let mut real_dom = dom::WebviewDom::new();
|
||||
vdom.rebuild(&mut real_dom)?;
|
||||
let vir = VirtualDom::new_with_props(root, props);
|
||||
|
||||
let edits = Arc::new(RwLock::new(Some(serde_json::to_value(real_dom.edits)?)));
|
||||
|
||||
// let ref_edits = Arc::new(serde_json::to_string(&real_dom.edits)?);
|
||||
|
||||
let handler = move |window: &Window, mut req: RpcRequest| {
|
||||
//
|
||||
let d = edits.clone();
|
||||
match req.method.as_str() {
|
||||
"initiate" => {
|
||||
let mut ed = d.write().unwrap();
|
||||
let edits = match ed.as_mut() {
|
||||
Some(ed) => Some(ed.take()),
|
||||
None => None,
|
||||
};
|
||||
Some(RpcResponse::new_result(req.id.take(), edits))
|
||||
}
|
||||
_ => todo!("this message failed"),
|
||||
}
|
||||
};
|
||||
// todo: combine these or something
|
||||
let vdom = Arc::new(RwLock::new(vir));
|
||||
let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
|
||||
|
||||
let webview = WebViewBuilder::new(window)?
|
||||
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
||||
.with_rpc_handler(handler)
|
||||
.with_rpc_handler(move |window: &Window, mut req: RpcRequest| {
|
||||
match req.method.as_str() {
|
||||
"initiate" => {
|
||||
let mut lock = vdom.write().unwrap();
|
||||
let mut reg_lock = registry.write().unwrap();
|
||||
|
||||
// Create the thin wrapper around the registry to collect the edits into
|
||||
let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
|
||||
|
||||
// Serialize the edit stream
|
||||
let edits = {
|
||||
lock.rebuild(&mut real).unwrap();
|
||||
serde_json::to_value(&real.edits).unwrap()
|
||||
};
|
||||
|
||||
// Give back the registry into its slot
|
||||
*reg_lock = Some(real.consume());
|
||||
|
||||
// Return the edits into the webview runtime
|
||||
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
|
||||
}
|
||||
"user_event" => {
|
||||
let mut lock = vdom.write().unwrap();
|
||||
let mut reg_lock = registry.write().unwrap();
|
||||
|
||||
// Create the thin wrapper around the registry to collect the edits into
|
||||
let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
|
||||
|
||||
// Serialize the edit stream
|
||||
let edits = {
|
||||
lock.rebuild(&mut real).unwrap();
|
||||
serde_json::to_value(&real.edits).unwrap()
|
||||
};
|
||||
|
||||
// Give back the registry into its slot
|
||||
*reg_lock = Some(real.consume());
|
||||
|
||||
// Return the edits into the webview runtime
|
||||
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
|
||||
}
|
||||
_ => todo!("this message failed"),
|
||||
}
|
||||
})
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
|
@ -173,6 +199,8 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::dom::WebviewRegistry;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MessageParameters {
|
||||
message: String,
|
||||
|
|
Loading…
Reference in New Issue