From 45ebcf6f0e9f1f86601e83d83a55797e21628a85 Mon Sep 17 00:00:00 2001 From: JtotheThree Date: Fri, 7 Jan 2022 20:01:52 -0600 Subject: [PATCH 01/34] form prevent_default --- packages/html/src/elements.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 1a2f36e57..4b53f91f0 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1047,7 +1047,7 @@ impl input { /// - `text` /// - `time` /// - `url` - /// - `week` + /// - `week` pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("type", val, None, false) } @@ -1106,6 +1106,12 @@ impl a { } } +impl form { + pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("dioxus-prevent-default", val, None, false) + } +} + builder_constructors! { // SVG components /// Build a From e69fbb4baa3de3902a746a0cb2b06338e33dcaa9 Mon Sep 17 00:00:00 2001 From: JtotheThree Date: Fri, 7 Jan 2022 20:20:52 -0600 Subject: [PATCH 02/34] login_form example --- examples/login_form.rs | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/login_form.rs diff --git a/examples/login_form.rs b/examples/login_form.rs new file mode 100644 index 000000000..fdd1843ce --- /dev/null +++ b/examples/login_form.rs @@ -0,0 +1,89 @@ +//! This example demonstrates the following: +//! Futures in a callback, Router, and Forms + +use dioxus::events::*; +use dioxus::prelude::*; +use dioxus::router::{Link, Router, Route, RouterService}; + +fn main() { + dioxus::desktop::launch(APP); +} + +static APP: Component = |cx| { + cx.render(rsx!{ + Router { + Route { to: "/", home() } + Route { to: "/login", login() } + } + }) +}; + +fn home(cx: Scope) -> Element { + cx.render(rsx! { + h1 { "Welcome Home" } + Link { to: "/login", "Login" } + }) +} + + +fn login(cx: Scope) -> Element { + let username = use_state(&cx, String::new); + let password = use_state(&cx, String::new); + + let service = cx.consume_context::()?; + + let onsubmit = move |_| { + cx.push_future({ + let (username, password) = (username.get().clone(), password.get().clone()); + let service = service.clone(); + + async move { + let params = [ + ("username", username.to_string()), + ("password", password.to_string()) + ]; + + let resp = reqwest::Client::new() + .post("http://localhost/login") + .form(¶ms) + .send() + .await; + + match resp { + Ok(data) => { + // Parse data from here, such as storing a response token + service.push_route("/"); + } + Err(err) => {} //Handle any errors from the fetch here + } + } + }); + }; + + cx.render(rsx!{ + h1 { "Login" } + form { + onsubmit: onsubmit, + // Prevent the default behavior of
to post + prevent_default: "onsubmit", + input { + oninput: move |evt| username.set(evt.value.clone()) + } + label { + "Username" + } + br {} + input { + oninput: move |evt| password.set(evt.value.clone()), + r#type: "password" + } + label { + "Password" + } + br {} + button { + "Login" + } + } + }) +} \ No newline at end of file From c5025357247465fc9ca2e85f97e81b2c35da8805 Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 18:59:15 +0100 Subject: [PATCH 03/34] Update dependencies Specifically set wry to 0.13; this has breaking changes (notably: RPC -> IPC). --- Cargo.toml | 12 ++++++------ packages/desktop/Cargo.toml | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e3a0a032..e1561cb59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,15 +57,15 @@ members = [ ] [dev-dependencies] -futures-util = "0.3.17" +futures-util = "0.3.21" log = "0.4.14" num-format = "0.4.0" separator = "0.4.1" -serde = { version = "1.0.131", features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"] } im-rc = "15.0.0" -anyhow = "1.0.51" -serde_json = "1.0.73" +anyhow = "1.0.53" +serde_json = "1.0.79" rand = { version = "0.8.4", features = ["small_rng"] } -tokio = { version = "1.14.0", features = ["full"] } -reqwest = { version = "0.11.8", features = ["json"] } +tokio = { version = "1.16.1", features = ["full"] } +reqwest = { version = "0.11.9", features = ["json"] } dioxus = { path = ".", features = ["desktop", "ssr", "router"] } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 0d7c13e3f..d1f8b8697 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -13,15 +13,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] } -argh = "0.1.4" -serde = "1.0.120" -serde_json = "1.0.61" -thiserror = "1.0.23" -log = "0.4.13" +argh = "0.1.7" +serde = "1.0.136" +serde_json = "1.0.79" +thiserror = "1.0.30" +log = "0.4.14" html-escape = "0.2.9" -wry = "0.12.2" -futures-channel = "0.3" -tokio = { version = "1.12.0", features = [ +wry = { version = "0.13.1" } +futures-channel = "0.3.21" +tokio = { version = "1.16.1", features = [ "sync", "rt-multi-thread", "rt", From ee2b869e9957312f1141065102f9fa00e3405ee7 Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:39:47 +0100 Subject: [PATCH 04/34] Add optional feature flags of wry Check wry's documentation for each. Some of them are platform dependent or have platform dependent effects. (mostly MacOS and Linux) --- Cargo.toml | 7 +++++++ packages/desktop/Cargo.toml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index e1561cb59..3f0d50187 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,13 @@ web = ["dioxus-web"] desktop = ["dioxus-desktop"] router = ["dioxus-router"] +devtool = ["dioxus-desktop/devtool"] +fullscreen = ["dioxus-desktop/fullscreen"] +transparent = ["dioxus-desktop/transparent"] + +tray = ["dioxus-desktop/tray"] +ayatana = ["dioxus-desktop/ayatana"] + # "dioxus-router/web" # "dioxus-router/desktop" # desktop = ["dioxus-desktop", "dioxus-router/desktop"] diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index d1f8b8697..2622355eb 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -38,6 +38,13 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } default = ["tokio_runtime"] tokio_runtime = ["tokio"] +devtool = ["wry/devtool"] +fullscreen = ["wry/fullscreen"] +transparent = ["wry/transparent"] + +tray = ["wry/tray"] +ayatana = ["wry/ayatana"] + [dev-dependencies] dioxus-hooks = { path = "../hooks" } From c40d225d7db2490f47967d1fea5c65e1a524954d Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:50:29 +0100 Subject: [PATCH 05/34] Fix typo --- packages/desktop/src/cfg.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 49a26ae78..210ec9637 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -12,9 +12,9 @@ use wry::{ pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView); pub struct DesktopConfig { - pub window: WindowBuilder, - pub file_drop_handler: Option bool>>, - pub protocols: Vec, + pub(crate) window: WindowBuilder, + pub(crate) file_drop_handler: Option bool>>, + pub(crate) protocols: Vec, pub(crate) pre_rendered: Option, pub(crate) event_handler: Option>, } From a5bf25ce18d97ad58fa10fd0e9be5cb69f632cbd Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:51:37 +0100 Subject: [PATCH 06/34] Adjust visibility --- packages/desktop/src/cfg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 210ec9637..90fd0f1ae 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -19,7 +19,7 @@ pub struct DesktopConfig { pub(crate) event_handler: Option>, } -pub type WryProtocol = ( +pub(crate) type WryProtocol = ( String, Box WryResult + 'static>, ); From afa5a301c7ddae707130f0f8fb27a9446f4a879d Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:52:05 +0100 Subject: [PATCH 07/34] Fix typo --- packages/interpreter/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index ebca3cbf4..076a89197 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1,4 +1,4 @@ -pub static INTERPRTER_JS: &str = include_str!("./interpreter.js"); +pub static INTERPRETER_JS: &str = include_str!("./interpreter.js"); #[cfg(feature = "web")] mod bindings; From 594a794f0538749f56f8fb56d011e46aead7ca1e Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:56:11 +0100 Subject: [PATCH 08/34] Switch from RPC to IPC --- packages/desktop/src/events.rs | 40 +++++++++++++++++------ packages/desktop/src/lib.rs | 42 ++++++++++++------------- packages/interpreter/src/interpreter.js | 12 ++++--- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 760bf7662..a991ec788 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -1,5 +1,4 @@ -//! Convert a serialized event to an event Trigger -//! +//! Convert a serialized event to an event trigger use std::any::Any; use std::sync::Arc; @@ -7,27 +6,50 @@ use std::sync::Arc; use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_html::on::*; +#[derive(serde::Serialize, serde::Deserialize)] +pub(crate) struct IpcMessage { + method: String, + params: serde_json::Value, +} + +impl IpcMessage { + pub(crate) fn method(&self) -> &str { + self.method.as_str() + } + + pub(crate) fn params(self) -> serde_json::Value { + self.params + } +} + +pub(crate) fn parse_ipc_message(payload: &str) -> Option { + let mm = serde_json::from_str(payload); + match mm { + Ok(message) => Some(message), + Err(e) => { + log::error!("could not parse IPC message, error: {e}"); + None + } + } +} + #[derive(serde::Serialize, serde::Deserialize)] struct ImEvent { event: String, mounted_dom_id: u64, - // scope: u64, contents: serde_json::Value, } pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { - let ims: Vec = serde_json::from_value(val).unwrap(); - let ImEvent { event, mounted_dom_id, contents, - } = ims.into_iter().next().unwrap(); + } = serde_json::from_value(val).unwrap(); - // let scope_id = ScopeId(scope as usize); let mounted_dom_id = Some(ElementId(mounted_dom_id as usize)); - let name = event_name_from_typ(&event); + let name = event_name_from_type(&event); let event = make_synthetic_event(&event, contents); UserEvent { @@ -105,7 +127,7 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc &'static str { +fn event_name_from_type(typ: &str) -> &'static str { match typ { "copy" => "copy", "cut" => "cut", diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index f4e28ca2a..90fb4a3b0 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -132,23 +132,24 @@ pub fn launch_with_props( .with_transparent(cfg.window.window.transparent) .with_url("dioxus://index.html/") .unwrap() - .with_rpc_handler(move |_window: &Window, req: RpcRequest| { - match req.method.as_str() { - "user_event" => { - let event = events::trigger_from_serialized(req.params.unwrap()); - log::trace!("User event: {:?}", event); - sender.unbounded_send(SchedulerMsg::Event(event)).unwrap(); - } - "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - let _ = proxy.send_event(UserWindowEvent::Update); - } - "browser_open" => { - println!("browser_open"); - let data = req.params.unwrap(); - log::trace!("Open browser: {:?}", data); - if let Some(arr) = data.as_array() { - if let Some(temp) = arr[0].as_object() { + .with_ipc_handler(move |_window: &Window, payload: String| { + parse_ipc_message(&payload) + .map(|message| match message.method() { + "user_event" => { + let event = trigger_from_serialized(message.params()); + log::trace!("User event: {:?}", event); + sender.unbounded_send(SchedulerMsg::Event(event)).unwrap(); + } + "initialize" => { + is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + let _ = proxy + .send_event(user_window_events::UserWindowEvent::Update); + } + "browser_open" => { + println!("browser_open"); + let data = message.params(); + log::trace!("Open browser: {:?}", data); + if let Some(temp) = data.as_object() { if temp.contains_key("href") { let url = temp.get("href").unwrap().as_str().unwrap(); if let Err(e) = webbrowser::open(url) { @@ -157,11 +158,8 @@ pub fn launch_with_props( } } } - } - _ => {} - } - None - }) + _ => (), + }) .with_custom_protocol(String::from("dioxus"), move |request| { // Any content that that uses the `dioxus://` scheme will be shuttled through this handler as a "special case" // For now, we only serve two pieces of content which get included as bytes into the final binary. diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index f0acd0802..f042b74c9 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -2,7 +2,7 @@ export function main() { let root = window.document.getElementById("main"); if (root != null) { window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); + window.ipc.postMessage(serializeIpcMessage("initialize")) } } export class Interpreter { @@ -207,7 +207,7 @@ export class Interpreter { event.preventDefault(); const href = target.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); + window.ipc.postMessage(serializeIpcMessage("browser_open", { href })) } } } @@ -261,11 +261,12 @@ export class Interpreter { if (realId == null) { return; } - window.rpc.call("user_event", { + window.ipc.postMessage(serializeIpcMessage( + "user_event", { event: edit.event_name, mounted_dom_id: parseInt(realId), contents: contents, - }); + })); } }; this.NewEventListener(edit.event_name, edit.root, handler); @@ -544,6 +545,9 @@ export function serialize_event(event) { } } } +function serializeIpcMessage(method, params = {}) { + return JSON.stringify({ method, params }); +} const bool_attrs = { allowfullscreen: true, allowpaymentrequest: true, From 73ce79bd2a31e409fc34d0673c8efbd08fc01d31 Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:57:46 +0100 Subject: [PATCH 09/34] Extract protocol hander into module --- packages/desktop/src/lib.rs | 47 +++----------------------------- packages/desktop/src/protocol.rs | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 packages/desktop/src/protocol.rs diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 90fb4a3b0..31812f680 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -160,50 +160,11 @@ pub fn launch_with_props( } _ => (), }) - .with_custom_protocol(String::from("dioxus"), move |request| { - // Any content that that uses the `dioxus://` scheme will be shuttled through this handler as a "special case" - // For now, we only serve two pieces of content which get included as bytes into the final binary. - let path = request.uri().replace("dioxus://", ""); - - // all assets shouldbe called from index.html - let trimmed = path.trim_start_matches("index.html/"); - - if trimmed.is_empty() { - wry::http::ResponseBuilder::new() - .mimetype("text/html") - .body(include_bytes!("./index.html").to_vec()) - } else if trimmed == "index.js" { - wry::http::ResponseBuilder::new() - .mimetype("text/javascript") - .body(dioxus_interpreter_js::INTERPRTER_JS.as_bytes().to_vec()) - } else { - // Read the file content from file path - use std::fs::read; - - let path_buf = std::path::Path::new(trimmed).canonicalize()?; - let cur_path = std::path::Path::new(".").canonicalize()?; - - if !path_buf.starts_with(cur_path) { - return wry::http::ResponseBuilder::new() - .status(wry::http::status::StatusCode::FORBIDDEN) - .body(String::from("Forbidden").into_bytes()); - } - - if !path_buf.exists() { - return wry::http::ResponseBuilder::new() - .status(wry::http::status::StatusCode::NOT_FOUND) - .body(String::from("Not Found").into_bytes()); - } - - let mime = mime_guess::from_path(&path_buf).first_or_octet_stream(); - - // do not let path searching to go two layers beyond the caller level - let data = read(path_buf)?; - let meta = format!("{}", mime); - - wry::http::ResponseBuilder::new().mimetype(&meta).body(data) - } + .unwrap_or_else(|| { + log::warn!("invalid IPC message received"); + }); }) + .with_custom_protocol(String::from("dioxus"), protocol::desktop_handler) .with_file_drop_handler(move |window, evet| { file_handler .as_ref() diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs new file mode 100644 index 000000000..c8f007c1d --- /dev/null +++ b/packages/desktop/src/protocol.rs @@ -0,0 +1,47 @@ +use std::path::Path; +use wry::{ + http::{status::StatusCode, Request, Response, ResponseBuilder}, + Result, +}; + +pub(super) fn desktop_handler(request: &Request) -> Result { + // Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case". + // For now, we only serve two pieces of content which get included as bytes into the final binary. + let path = request.uri().replace("dioxus://", ""); + + // all assets should be called from index.html + let trimmed = path.trim_start_matches("index.html/"); + + if trimmed.is_empty() { + ResponseBuilder::new() + .mimetype("text/html") + .body(include_bytes!("./index.html").to_vec()) + } else if trimmed == "index.js" { + ResponseBuilder::new() + .mimetype("text/javascript") + .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) + } else { + let path_buf = Path::new(trimmed).canonicalize()?; + let cur_path = Path::new(".").canonicalize()?; + + if !path_buf.starts_with(cur_path) { + return ResponseBuilder::new() + .status(StatusCode::FORBIDDEN) + .body(String::from("Forbidden").into_bytes()); + } + + if !path_buf.exists() { + return ResponseBuilder::new() + .status(StatusCode::NOT_FOUND) + .body(String::from("Not Found").into_bytes()); + } + + let mime = mime_guess::from_path(&path_buf).first_or_octet_stream(); + + // do not let path searching to go two layers beyond the caller level + let data = std::fs::read(path_buf)?; + let meta = format!("{}", mime); + + ResponseBuilder::new().mimetype(&meta).body(data) + } +} From cf543ab1dfe0a0af423c37d3850af04432cb075a Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:58:37 +0100 Subject: [PATCH 10/34] Extract controller into module --- packages/desktop/src/controller.rs | 106 +++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 packages/desktop/src/controller.rs diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs new file mode 100644 index 000000000..7d9a2f432 --- /dev/null +++ b/packages/desktop/src/controller.rs @@ -0,0 +1,106 @@ +use crate::desktop_context::DesktopContext; +use crate::user_window_events::UserWindowEvent; +use dioxus_core::*; +use std::{ + collections::{HashMap, VecDeque}, + sync::atomic::AtomicBool, + sync::{Arc, RwLock}, +}; +use wry::{ + self, + application::{event_loop::ControlFlow, event_loop::EventLoopProxy, window::WindowId}, + webview::WebView, +}; + +pub(super) struct DesktopController { + pub(super) webviews: HashMap, + pub(super) sender: futures_channel::mpsc::UnboundedSender, + pub(super) pending_edits: Arc>>, + pub(super) quit_app_on_close: bool, + pub(super) is_ready: Arc, +} + +impl DesktopController { + // Launch the virtualdom on its own thread managed by tokio + // returns the desktop state + pub(super) fn new_on_tokio( + root: Component

, + props: P, + proxy: EventLoopProxy, + ) -> Self { + let edit_queue = Arc::new(RwLock::new(VecDeque::new())); + let pending_edits = edit_queue.clone(); + + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + let return_sender = sender.clone(); + + let desktop_context_proxy = proxy.clone(); + std::thread::spawn(move || { + // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(async move { + let mut dom = + VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver)); + + let window_context = DesktopContext::new(desktop_context_proxy); + + dom.base_scope().provide_context(window_context); + + let edits = dom.rebuild(); + + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edits.edits).unwrap()); + + loop { + dom.wait_for_work().await; + let mut muts = dom.work_with_deadline(|| false); + + while let Some(edit) = muts.pop() { + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edit.edits).unwrap()); + } + + let _ = proxy.send_event(UserWindowEvent::Update); + } + }) + }); + + Self { + pending_edits, + sender: return_sender, + webviews: HashMap::new(), + is_ready: Arc::new(AtomicBool::new(false)), + quit_app_on_close: true, + } + } + + pub(super) fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) { + self.webviews.remove(&window_id); + + if self.webviews.is_empty() && self.quit_app_on_close { + *control_flow = ControlFlow::Exit; + } + } + + pub(super) fn try_load_ready_webviews(&mut self) { + if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) { + let mut queue = self.pending_edits.write().unwrap(); + let (_id, view) = self.webviews.iter_mut().next().unwrap(); + + while let Some(edit) = queue.pop_back() { + view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) + .unwrap(); + } + } else { + println!("waiting for ready"); + } + } +} From e7a0e5f1d9fb03e9aa1ef8aa9978898f4c6ba56f Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:58:50 +0100 Subject: [PATCH 11/34] Extract user window events into module --- packages/desktop/src/user_window_events.rs | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 packages/desktop/src/user_window_events.rs diff --git a/packages/desktop/src/user_window_events.rs b/packages/desktop/src/user_window_events.rs new file mode 100644 index 000000000..93eeaccf1 --- /dev/null +++ b/packages/desktop/src/user_window_events.rs @@ -0,0 +1,72 @@ +use wry::application::event_loop::ControlFlow; +use wry::application::window::Fullscreen as WryFullscreen; + +use crate::controller::DesktopController; + +pub(crate) enum UserWindowEvent { + Update, + + CloseWindow, + DragWindow, + FocusWindow, + + Visible(bool), + Minimize(bool), + Maximize(bool), + MaximizeToggle, + Resizable(bool), + AlwaysOnTop(bool), + Fullscreen(bool), + + CursorVisible(bool), + CursorGrab(bool), + + SetTitle(String), + SetDecorations(bool), + + DevTool, +} + +use UserWindowEvent::*; + +pub(super) fn handler( + user_event: UserWindowEvent, + desktop: &mut DesktopController, + control_flow: &mut ControlFlow, +) { + // currently dioxus-desktop supports a single window only, + // so we can grab the only webview from the map; + let webview = desktop.webviews.values().next().unwrap(); + let window = webview.window(); + + match user_event { + Update => desktop.try_load_ready_webviews(), + CloseWindow => *control_flow = ControlFlow::Exit, + DragWindow => { + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); + } + Visible(state) => window.set_visible(state), + Minimize(state) => window.set_minimized(state), + Maximize(state) => window.set_maximized(state), + MaximizeToggle => window.set_maximized(!window.is_maximized()), + Fullscreen(state) => { + window.current_monitor().map(|handle| { + window.set_fullscreen(state.then(|| WryFullscreen::Borderless(Some(handle)))); + }); + } + FocusWindow => window.set_focus(), + Resizable(state) => window.set_resizable(state), + AlwaysOnTop(state) => window.set_always_on_top(state), + + CursorVisible(state) => window.set_cursor_visible(state), + CursorGrab(state) => { + let _ = window.set_cursor_grab(state); + } + + SetTitle(content) => window.set_title(&content), + SetDecorations(state) => window.set_decorations(state), + + DevTool => webview.devtool(), + } +} From 2828f45e12e129a29006bdacbf0ccbec38fefbfe Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 19:59:25 +0100 Subject: [PATCH 12/34] Clean up desktop's lib.rs --- packages/desktop/src/lib.rs | 246 ++---------------------------------- 1 file changed, 11 insertions(+), 235 deletions(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 31812f680..371adced5 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -3,31 +3,28 @@ #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] pub mod cfg; +mod controller; pub mod desktop_context; pub mod escape; pub mod events; +mod protocol; +mod user_window_events; use cfg::DesktopConfig; +use controller::DesktopController; pub use desktop_context::use_window; -use desktop_context::DesktopContext; use dioxus_core::*; -use std::{ - collections::{HashMap, VecDeque}, - sync::atomic::AtomicBool, - sync::{Arc, RwLock}, -}; +use events::parse_ipc_message; use tao::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{Window, WindowId}, + window::Window, }; pub use wry; pub use wry::application as tao; -use wry::{ - application::{event_loop::EventLoopProxy, window::Fullscreen}, - webview::RpcRequest, - webview::{WebView, WebViewBuilder}, -}; +use wry::webview::WebViewBuilder; + +use crate::events::trigger_from_serialized; /// Launch the WebView and run the event loop. /// @@ -194,114 +191,8 @@ pub fn launch_with_props( _ => {} }, - Event::UserEvent(_evt) => { - // - match _evt { - UserWindowEvent::Update => desktop.try_load_ready_webviews(), - UserWindowEvent::DragWindow => { - // this loop just run once, because dioxus-desktop is unsupport multi-window. - for webview in desktop.webviews.values() { - let window = webview.window(); - // start to drag the window. - // if the drag_window have any err. we don't do anything. - - if window.fullscreen().is_some() { - return; - } - - let _ = window.drag_window(); - } - } - UserWindowEvent::CloseWindow => { - // close window - *control_flow = ControlFlow::Exit; - } - UserWindowEvent::Visible(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_visible(state); - } - } - UserWindowEvent::Minimize(state) => { - // this loop just run once, because dioxus-desktop is unsupport multi-window. - for webview in desktop.webviews.values() { - let window = webview.window(); - // change window minimized state. - window.set_minimized(state); - } - } - UserWindowEvent::Maximize(state) => { - // this loop just run once, because dioxus-desktop is unsupport multi-window. - for webview in desktop.webviews.values() { - let window = webview.window(); - // change window maximized state. - window.set_maximized(state); - } - } - UserWindowEvent::Fullscreen(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - - let current_monitor = window.current_monitor(); - - if current_monitor.is_none() { - return; - } - - let fullscreen = if state { - Some(Fullscreen::Borderless(current_monitor)) - } else { - None - }; - - window.set_fullscreen(fullscreen); - } - } - UserWindowEvent::FocusWindow => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_focus(); - } - } - UserWindowEvent::Resizable(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_resizable(state); - } - } - UserWindowEvent::AlwaysOnTop(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_always_on_top(state); - } - } - - UserWindowEvent::CursorVisible(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_cursor_visible(state); - } - } - UserWindowEvent::CursorGrab(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - let _ = window.set_cursor_grab(state); - } - } - - UserWindowEvent::SetTitle(content) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_title(&content); - } - } - UserWindowEvent::SetDecorations(state) => { - for webview in desktop.webviews.values() { - let window = webview.window(); - window.set_decorations(state); - } - } - } + Event::UserEvent(user_event) => { + user_window_events::handler(user_event, &mut desktop, control_flow) } Event::MainEventsCleared => {} Event::Resumed => {} @@ -312,118 +203,3 @@ pub fn launch_with_props( } }) } - -pub enum UserWindowEvent { - Update, - DragWindow, - CloseWindow, - FocusWindow, - Visible(bool), - Minimize(bool), - Maximize(bool), - Resizable(bool), - AlwaysOnTop(bool), - Fullscreen(bool), - - CursorVisible(bool), - CursorGrab(bool), - - SetTitle(String), - SetDecorations(bool), -} - -pub struct DesktopController { - pub proxy: EventLoopProxy, - pub webviews: HashMap, - pub sender: futures_channel::mpsc::UnboundedSender, - pub pending_edits: Arc>>, - pub quit_app_on_close: bool, - pub is_ready: Arc, -} - -impl DesktopController { - // Launch the virtualdom on its own thread managed by tokio - // returns the desktop state - pub fn new_on_tokio( - root: Component

, - props: P, - evt: EventLoopProxy, - ) -> Self { - let edit_queue = Arc::new(RwLock::new(VecDeque::new())); - let pending_edits = edit_queue.clone(); - - let (sender, receiver) = futures_channel::mpsc::unbounded::(); - let return_sender = sender.clone(); - let proxy = evt.clone(); - - let desktop_context_proxy = proxy.clone(); - std::thread::spawn(move || { - // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - runtime.block_on(async move { - let mut dom = - VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver)); - - let window_context = DesktopContext::new(desktop_context_proxy); - - dom.base_scope().provide_context(window_context); - - let edits = dom.rebuild(); - - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edits.edits).unwrap()); - - loop { - dom.wait_for_work().await; - let mut muts = dom.work_with_deadline(|| false); - - while let Some(edit) = muts.pop() { - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edit.edits).unwrap()); - } - - let _ = evt.send_event(UserWindowEvent::Update); - } - }) - }); - - Self { - pending_edits, - sender: return_sender, - proxy, - webviews: HashMap::new(), - is_ready: Arc::new(AtomicBool::new(false)), - quit_app_on_close: true, - } - } - - pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) { - self.webviews.remove(&window_id); - - if self.webviews.is_empty() && self.quit_app_on_close { - *control_flow = ControlFlow::Exit; - } - } - - pub fn try_load_ready_webviews(&mut self) { - if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) { - let mut queue = self.pending_edits.write().unwrap(); - let (_id, view) = self.webviews.iter_mut().next().unwrap(); - - while let Some(edit) = queue.pop_back() { - view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) - .unwrap(); - } - } else { - println!("waiting for ready"); - } - } -} From 934d5998dbc5bc8e14cce10d3b4f1332205f6ae8 Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 20:00:45 +0100 Subject: [PATCH 13/34] Support maximize toggle and devtool --- packages/desktop/src/desktop_context.rs | 47 +++++++++++++------------ 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index d180e1235..e1b726d73 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -3,7 +3,8 @@ use std::rc::Rc; use dioxus_core::ScopeState; use wry::application::event_loop::EventLoopProxy; -use crate::UserWindowEvent; +use crate::user_window_events::UserWindowEvent; +use UserWindowEvent::*; type ProxyType = EventLoopProxy; @@ -35,75 +36,77 @@ impl DesktopContext { /// onmousedown: move |_| { desktop.drag_window(); } /// ``` pub fn drag(&self) { - let _ = self.proxy.send_event(UserWindowEvent::DragWindow); + let _ = self.proxy.send_event(DragWindow); } /// set window minimize state pub fn set_minimized(&self, minimized: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized)); + let _ = self.proxy.send_event(Minimize(minimized)); } /// set window maximize state pub fn set_maximized(&self, maximized: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized)); + let _ = self.proxy.send_event(Maximize(maximized)); + } + + /// toggle window maximize state + pub fn toggle_maximized(&self) { + let _ = self.proxy.send_event(MaximizeToggle); } /// set window visible or not pub fn set_visible(&self, visible: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Visible(visible)); + let _ = self.proxy.send_event(Visible(visible)); } /// close window pub fn close(&self) { - let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); + let _ = self.proxy.send_event(CloseWindow); } /// set window to focus pub fn focus(&self) { - let _ = self.proxy.send_event(UserWindowEvent::FocusWindow); + let _ = self.proxy.send_event(FocusWindow); } /// change window to fullscreen pub fn set_fullscreen(&self, fullscreen: bool) { - let _ = self - .proxy - .send_event(UserWindowEvent::Fullscreen(fullscreen)); + let _ = self.proxy.send_event(Fullscreen(fullscreen)); } /// set resizable state pub fn set_resizable(&self, resizable: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Resizable(resizable)); + let _ = self.proxy.send_event(Resizable(resizable)); } /// set the window always on top pub fn set_always_on_top(&self, top: bool) { - let _ = self.proxy.send_event(UserWindowEvent::AlwaysOnTop(top)); + let _ = self.proxy.send_event(AlwaysOnTop(top)); } // set cursor visible or not pub fn set_cursor_visible(&self, visible: bool) { - let _ = self - .proxy - .send_event(UserWindowEvent::CursorVisible(visible)); + let _ = self.proxy.send_event(CursorVisible(visible)); } // set cursor grab pub fn set_cursor_grab(&self, grab: bool) { - let _ = self.proxy.send_event(UserWindowEvent::CursorGrab(grab)); + let _ = self.proxy.send_event(CursorGrab(grab)); } /// set window title pub fn set_title(&self, title: &str) { - let _ = self - .proxy - .send_event(UserWindowEvent::SetTitle(String::from(title))); + let _ = self.proxy.send_event(SetTitle(String::from(title))); } /// change window to borderless pub fn set_decorations(&self, decoration: bool) { - let _ = self - .proxy - .send_event(UserWindowEvent::SetDecorations(decoration)); + let _ = self.proxy.send_event(SetDecorations(decoration)); + } + + /// opens DevTool window + pub fn devtool(&self) { + let _ = self.proxy.send_event(DevTool); } } From 932ad0164454c65335f7c62078d3a7250a8608c3 Mon Sep 17 00:00:00 2001 From: Christoph Grabo Date: Sun, 13 Feb 2022 20:57:30 +0100 Subject: [PATCH 14/34] Make clippy happy --- packages/desktop/src/user_window_events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/user_window_events.rs b/packages/desktop/src/user_window_events.rs index 93eeaccf1..aab14561b 100644 --- a/packages/desktop/src/user_window_events.rs +++ b/packages/desktop/src/user_window_events.rs @@ -51,9 +51,9 @@ pub(super) fn handler( Maximize(state) => window.set_maximized(state), MaximizeToggle => window.set_maximized(!window.is_maximized()), Fullscreen(state) => { - window.current_monitor().map(|handle| { + if let Some(handle) = window.current_monitor() { window.set_fullscreen(state.then(|| WryFullscreen::Borderless(Some(handle)))); - }); + } } FocusWindow => window.set_focus(), Resizable(state) => window.set_resizable(state), From dd9f0f362ec8e7193a4920c2ee46246f1cbe67c9 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 14 Feb 2022 16:52:16 +0800 Subject: [PATCH 15/34] fix: statement problem --- packages/interpreter/src/interpreter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index f0acd0802..a631d0368 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -102,10 +102,11 @@ export class Interpreter { SetAttribute(root, field, value, ns) { const name = field; const node = this.nodes[root]; + console.log(ns); if (ns === "style") { // @ts-ignore node.style[name] = value; - } else if (ns != null || ns !== undefined) { + } else if (ns != null || ns != undefined) { node.setAttributeNS(ns, name, value); } else { switch (name) { From e3e5f22bc7605b7019d5f92eeff719abcbf512c5 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 14 Feb 2022 16:53:35 +0800 Subject: [PATCH 16/34] feat: move `default_icon` to assets --- packages/desktop/src/{ => assets}/default_icon.bin | Bin packages/desktop/src/{ => assets}/default_icon.png | Bin packages/desktop/src/cfg.rs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/desktop/src/{ => assets}/default_icon.bin (100%) rename packages/desktop/src/{ => assets}/default_icon.png (100%) diff --git a/packages/desktop/src/default_icon.bin b/packages/desktop/src/assets/default_icon.bin similarity index 100% rename from packages/desktop/src/default_icon.bin rename to packages/desktop/src/assets/default_icon.bin diff --git a/packages/desktop/src/default_icon.png b/packages/desktop/src/assets/default_icon.png similarity index 100% rename from packages/desktop/src/default_icon.png rename to packages/desktop/src/assets/default_icon.png diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 49a26ae78..2a1067ded 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -88,7 +88,7 @@ impl DesktopConfig { impl DesktopConfig { pub(crate) fn with_default_icon(mut self) -> Self { - let bin: &[u8] = include_bytes!("default_icon.bin"); + let bin: &[u8] = include_bytes!("./assets/default_icon.bin"); let rgba = Icon::from_rgba(bin.to_owned(), 460, 460).expect("image parse failed"); self.window.window.window_icon = Some(rgba); self From 3317b3aac51f2e1225ffc873cfa0ed25b3096f3d Mon Sep 17 00:00:00 2001 From: dazmaks Date: Mon, 14 Feb 2022 17:13:09 +0300 Subject: [PATCH 17/34] fix: typo --- docs/reference/src/mobile/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/src/mobile/index.md b/docs/reference/src/mobile/index.md index 2e8344ed1..aa3470761 100644 --- a/docs/reference/src/mobile/index.md +++ b/docs/reference/src/mobile/index.md @@ -21,7 +21,7 @@ $ cargo install --git https://github.com/BrainiumLLC/cargo-mobile And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile. ```shell -$ cargo moble init +$ cargo mobile init ``` We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`. From 8ca505b65beba1edf74eddf47d4da5a8e7be7033 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 14 Feb 2022 09:30:08 -0500 Subject: [PATCH 18/34] feat: better link --- packages/router/src/components/link.rs | 48 +++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 18e9fd92e..b2b69108f 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -34,6 +34,17 @@ pub struct LinkProps<'a> { #[props(default, strip_option)] title: Option<&'a str>, + #[props(default = true)] + autodetect: bool, + + /// Is this link an external link? + #[props(default = false)] + external: bool, + + /// New tab? + #[props(default = false)] + new_tab: bool, + children: Element<'a>, #[props(default)] @@ -41,17 +52,38 @@ pub struct LinkProps<'a> { } pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - // log::trace!("render Link to {}", cx.props.to); if let Some(service) = cx.consume_context::() { + let LinkProps { + to, + href, + class, + id, + title, + autodetect, + external, + new_tab, + children, + .. + } = cx.props; + + let is_http = to.starts_with("http") || to.starts_with("https"); + let outerlink = (*autodetect && is_http) || *external; + + let prevent_default = if outerlink { "" } else { "onclick" }; + return cx.render(rsx! { a { - href: "{cx.props.to}", - class: format_args!("{}", cx.props.class.unwrap_or("")), - id: format_args!("{}", cx.props.id.unwrap_or("")), - title: format_args!("{}", cx.props.title.unwrap_or("")), - - prevent_default: "onclick", - onclick: move |_| service.push_route(cx.props.to), + href: "{to}", + class: format_args!("{}", class.unwrap_or("")), + id: format_args!("{}", id.unwrap_or("")), + title: format_args!("{}", title.unwrap_or("")), + prevent_default: "{prevent_default}", + target: format_args!("{}", if *new_tab { "_blank" } else { "" }), + onclick: move |_| { + if !outerlink { + service.push_route(to); + } + }, &cx.props.children } From 5a908d1e8b92d712c35c7f05af5956926acec29e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 14 Feb 2022 12:23:30 -0500 Subject: [PATCH 19/34] fix: remove console log --- packages/interpreter/src/interpreter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index a631d0368..5b81d3a9f 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -102,7 +102,6 @@ export class Interpreter { SetAttribute(root, field, value, ns) { const name = field; const node = this.nodes[root]; - console.log(ns); if (ns === "style") { // @ts-ignore node.style[name] = value; From 1413631d306065b76a9d2dce1c6e709c0def0bd4 Mon Sep 17 00:00:00 2001 From: Miles Murgaw Date: Mon, 14 Feb 2022 19:08:10 -0500 Subject: [PATCH 20/34] update: match DioxusLabs/docs#3 --- README.md | 8 ++++---- docs/guide/src/README.md | 12 ++++++------ docs/guide/src/setup.md | 2 +- docs/reference/src/SUMMARY.md | 11 ++++++----- .../src/{desktop/index.md => platforms/desktop.md} | 2 +- docs/reference/src/platforms/index.md | 10 ++++++++++ .../src/{mobile/index.md => platforms/mobile.md} | 2 +- .../reference/src/{ssr/index.md => platforms/ssr.md} | 0 .../reference/src/{tui/index.md => platforms/tui.md} | 2 +- .../reference/src/{web/index.md => platforms/web.md} | 2 +- 10 files changed, 31 insertions(+), 20 deletions(-) rename docs/reference/src/{desktop/index.md => platforms/desktop.md} (98%) create mode 100644 docs/reference/src/platforms/index.md rename docs/reference/src/{mobile/index.md => platforms/mobile.md} (99%) rename docs/reference/src/{ssr/index.md => platforms/ssr.md} (100%) rename docs/reference/src/{tui/index.md => platforms/tui.md} (98%) rename docs/reference/src/{web/index.md => platforms/web.md} (98%) diff --git a/README.md b/README.md index 02857ed18..23cd9c516 100644 --- a/README.md +++ b/README.md @@ -101,10 +101,10 @@ cargo run --example EXAMPLE - - - - + + + +
TutorialWebDesktopSSRMobileWebDesktopSSRMobile State
diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md index bbbd9f85f..da6c0156f 100644 --- a/docs/guide/src/README.md +++ b/docs/guide/src/README.md @@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element { In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you! -> This is an introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead. +> This is an introduction book! For advanced topics, check out the [Reference](/reference) instead. ## Multiplatform @@ -37,7 +37,7 @@ The Web is the best-supported target platform for Dioxus. To run on the Web, you Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features. -[Jump to the getting started guide for the web.]() +[Jump to the getting started guide for the web.](/reference/platforms/web) Examples: - [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc) @@ -55,7 +55,7 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan let contents = dioxus::ssr::render_vdom(&dom); ``` -[Jump to the getting started guide for SSR.]() +[Jump to the getting started guide for SSR.](/reference/platforms/ssr) Examples: - [Example DocSite](https://github.com/dioxusLabs/docsite) @@ -68,13 +68,13 @@ The desktop is a powerful target for Dioxus, but is currently limited in capabil Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart. -[Jump to the getting started guide for Desktop.]() +[Jump to the getting started guide for Desktop.](/reference/platforms/desktop) Examples: - [File explorer](https://github.com/dioxusLabs/file-explorer/) - [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) -[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/) +[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer) ### Mobile Support --- @@ -82,7 +82,7 @@ Mobile is currently the least-supported renderer target for Dioxus. Mobile apps Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets. -[Jump to the getting started guide for Mobile.]() +[Jump to the getting started guide for Mobile.](/reference/platforms/mobile) Examples: - [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo) diff --git a/docs/guide/src/setup.md b/docs/guide/src/setup.md index 1dc9434c5..75cfc6606 100644 --- a/docs/guide/src/setup.md +++ b/docs/guide/src/setup.md @@ -8,7 +8,7 @@ We'll learn about: - Suggested cargo extensions -For platform-specific guides, check out the [Platform Specific Guides](../platforms/00-index.md). +For platform-specific guides, check out the [Platform Specific Guides](/reference/platforms/index.md). # Setting up Dioxus diff --git a/docs/reference/src/SUMMARY.md b/docs/reference/src/SUMMARY.md index 3feb9fe36..18f351bfa 100644 --- a/docs/reference/src/SUMMARY.md +++ b/docs/reference/src/SUMMARY.md @@ -2,11 +2,12 @@ - [Introduction](README.md) -- [Web](web/index.md) -- [Desktop](desktop/index.md) -- [Mobile](mobile/index.md) -- [SSR](ssr/index.md) -- [TUI](tui/index.md) +- [Platforms](platforms/index.md) + - [Web](platforms/web.md) + - [Server Side Rendering](platforms/ssr.md) + - [Desktop](platforms/desktop.md) + - [Mobile](platforms/mobile.md) + - [TUI](platforms/tui.md) - [Advanced Guides](guide/index.md) - [RSX in Depth](guide/rsx_in_depth.md) diff --git a/docs/reference/src/desktop/index.md b/docs/reference/src/platforms/desktop.md similarity index 98% rename from docs/reference/src/desktop/index.md rename to docs/reference/src/platforms/desktop.md index 8011f1eed..18393f132 100644 --- a/docs/reference/src/desktop/index.md +++ b/docs/reference/src/platforms/desktop.md @@ -1,4 +1,4 @@ -# Desktop +# Getting Started: Desktop One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. diff --git a/docs/reference/src/platforms/index.md b/docs/reference/src/platforms/index.md new file mode 100644 index 000000000..fbbabd35d --- /dev/null +++ b/docs/reference/src/platforms/index.md @@ -0,0 +1,10 @@ +# Platforms + +Dioxus supports many different platforms. Below are a list of guides that walk you through setting up Dioxus for a specific platform. + +### Setup Guides +- [Web](web.md) +- [Server Side Rendering](ssr.md) +- [Desktop](desktop.md) +- [Mobile](mobile.md) +- [TUI](tui.md) \ No newline at end of file diff --git a/docs/reference/src/mobile/index.md b/docs/reference/src/platforms/mobile.md similarity index 99% rename from docs/reference/src/mobile/index.md rename to docs/reference/src/platforms/mobile.md index 2e8344ed1..5a41ffe60 100644 --- a/docs/reference/src/mobile/index.md +++ b/docs/reference/src/platforms/mobile.md @@ -21,7 +21,7 @@ $ cargo install --git https://github.com/BrainiumLLC/cargo-mobile And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile. ```shell -$ cargo moble init +$ cargo mobile init ``` We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`. diff --git a/docs/reference/src/ssr/index.md b/docs/reference/src/platforms/ssr.md similarity index 100% rename from docs/reference/src/ssr/index.md rename to docs/reference/src/platforms/ssr.md diff --git a/docs/reference/src/tui/index.md b/docs/reference/src/platforms/tui.md similarity index 98% rename from docs/reference/src/tui/index.md rename to docs/reference/src/platforms/tui.md index 3947883d4..e646bd4ba 100644 --- a/docs/reference/src/tui/index.md +++ b/docs/reference/src/platforms/tui.md @@ -1,4 +1,4 @@ -# TUI +# Getting Started: TUI TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started. diff --git a/docs/reference/src/web/index.md b/docs/reference/src/platforms/web.md similarity index 98% rename from docs/reference/src/web/index.md rename to docs/reference/src/platforms/web.md index 062cba177..2ff5f621e 100644 --- a/docs/reference/src/web/index.md +++ b/docs/reference/src/platforms/web.md @@ -1,4 +1,4 @@ -# Getting Started: Dioxus for Web +# Getting Started: Web [*"Pack your things, we're going on an adventure!"*](https://trunkrs.dev) From 7c2d911fbf1abb23a216f3ef5c235bc75766ee4a Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Tue, 15 Feb 2022 09:54:32 -0600 Subject: [PATCH 21/34] Fix closure signature for use_ref example in interactivity docs The example showed the closure accepting a single argument, but it accepts none. --- docs/guide/src/interactivity/hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/src/interactivity/hooks.md b/docs/guide/src/interactivity/hooks.md index a90afe2dc..a84a0b129 100644 --- a/docs/guide/src/interactivity/hooks.md +++ b/docs/guide/src/interactivity/hooks.md @@ -140,7 +140,7 @@ fn Child(cx: Scope, name: String) -> Element { // ✅ Or, use a hashmap with use_ref ```rust -let ages = use_ref(&cx, |_| HashMap::new()); +let ages = use_ref(&cx, || HashMap::new()); names.iter().map(|name| { let age = ages.get(name).unwrap(); From e43a8a9b6a68bd2344bc86c6613ab046d89da40b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Feb 2022 11:03:13 -0500 Subject: [PATCH 22/34] feat: remove old async channel for new channel in ric raf web code --- packages/web/Cargo.toml | 2 +- packages/web/src/dom.rs | 7 ++----- packages/web/src/lib.rs | 2 +- packages/web/src/ric_raf.rs | 21 +++++++++++---------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 7a3baa955..31a7ff6be 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -23,13 +23,13 @@ wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } wasm-bindgen-test = "0.3.29" once_cell = "1.9.0" -async-channel = "1.6.1" anyhow = "1.0.53" gloo-timers = { version = "0.2.3", features = ["futures"] } futures-util = "0.3.19" smallstr = "0.2.0" dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] } serde-wasm-bindgen = "0.4.2" +futures-channel = "0.3.21" [dependencies.web-sys] version = "0.3.56" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 48fb25e6e..b2eb65d08 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -87,14 +87,11 @@ impl WebsysDom { } }); + // a match here in order to avoid some error during runtime browser test let document = load_document(); let root = match document.get_element_by_id(&cfg.rootname) { Some(root) => root, - // a match here in order to avoid some error during runtime browser test - None => { - let body = document.create_element("body").ok().unwrap(); - body - } + None => document.create_element("body").ok().unwrap(), }; Self { diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index cef1bc14c..90fe84d04 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -211,7 +211,7 @@ pub async fn run_with_props(root: Component, root_props: T websys_dom.apply_edits(edits.edits); } - let work_loop = ric_raf::RafLoop::new(); + let mut work_loop = ric_raf::RafLoop::new(); loop { log::trace!("waiting for work"); diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 2ac0668b9..89819fff0 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -7,6 +7,7 @@ //! Because RIC doesn't work on Safari, we polyfill using the "ricpolyfill.js" file and use some basic detection to see //! if RIC is available. +use futures_util::StreamExt; use gloo_timers::future::TimeoutFuture; use js_sys::Function; use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; @@ -14,21 +15,21 @@ use web_sys::{window, Window}; pub(crate) struct RafLoop { window: Window, - ric_receiver: async_channel::Receiver, - raf_receiver: async_channel::Receiver<()>, + ric_receiver: futures_channel::mpsc::UnboundedReceiver, + raf_receiver: futures_channel::mpsc::UnboundedReceiver<()>, ric_closure: Closure, raf_closure: Closure, } impl RafLoop { pub fn new() -> Self { - let (raf_sender, raf_receiver) = async_channel::unbounded(); + let (raf_sender, raf_receiver) = futures_channel::mpsc::unbounded(); let raf_closure: Closure = Closure::wrap(Box::new(move |_v: JsValue| { - raf_sender.try_send(()).unwrap() + raf_sender.unbounded_send(()).unwrap() })); - let (ric_sender, ric_receiver) = async_channel::unbounded(); + let (ric_sender, ric_receiver) = futures_channel::mpsc::unbounded(); let has_idle_callback = { let bo = window().unwrap().dyn_into::().unwrap(); @@ -45,7 +46,7 @@ impl RafLoop { 10 }; - ric_sender.try_send(time_remaining).unwrap() + ric_sender.unbounded_send(time_remaining).unwrap() })); // execute the polyfill for safari @@ -64,16 +65,16 @@ impl RafLoop { } } /// waits for some idle time and returns a timeout future that expires after the idle time has passed - pub async fn wait_for_idle_time(&self) -> TimeoutFuture { + pub async fn wait_for_idle_time(&mut self) -> TimeoutFuture { let ric_fn = self.ric_closure.as_ref().dyn_ref::().unwrap(); let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap(); - let deadline = self.ric_receiver.recv().await.unwrap(); + let deadline = self.ric_receiver.next().await.unwrap(); TimeoutFuture::new(deadline) } - pub async fn wait_for_raf(&self) { + pub async fn wait_for_raf(&mut self) { let raf_fn = self.raf_closure.as_ref().dyn_ref::().unwrap(); let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap(); - self.raf_receiver.recv().await.unwrap(); + self.raf_receiver.next().await.unwrap(); } } From 06ea624eecc132dc2a27fa8f6b2e2605126caeb4 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Tue, 15 Feb 2022 10:27:11 -0600 Subject: [PATCH 23/34] Make all packages which require futures-channel ask for the same version If they aren't the same then Cargo cannot resolve a working version for some reason. --- packages/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 03b378e88..4126eb47f 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -32,7 +32,7 @@ smallvec = "1.6" slab = "0.4" -futures-channel = "0.3" +futures-channel = "0.3.21" # used for noderefs once_cell = "1.8" From a6cbe233b7f40763faa8e247b4264fba07f09c08 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Feb 2022 13:38:06 -0500 Subject: [PATCH 24/34] fix: empty values on desktop --- packages/desktop/src/controller.rs | 2 -- packages/desktop/src/events.rs | 3 +-- packages/html/src/events.rs | 2 ++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 7d9a2f432..8497a42e9 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -99,8 +99,6 @@ impl DesktopController { view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) .unwrap(); } - } else { - println!("waiting for ready"); } } } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index a991ec788..7be4273e9 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -23,8 +23,7 @@ impl IpcMessage { } pub(crate) fn parse_ipc_message(payload: &str) -> Option { - let mm = serde_json::from_str(payload); - match mm { + match serde_json::from_str(payload) { Ok(message) => Some(message), Err(e) => { log::error!("could not parse IPC message, error: {e}"); diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 8067e6314..4336d2476 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -486,6 +486,8 @@ pub mod on { #[derive(Debug)] pub struct FormData { pub value: String, + + #[serde(default)] pub values: HashMap, /* DOMEvent: Send + SyncTarget relatedTarget */ } From ba17b57cdd244946ca780e3183cd7c9a43648380 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Feb 2022 13:44:05 -0500 Subject: [PATCH 25/34] fix: also include values in onchange --- packages/interpreter/src/interpreter.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index b1271b303..76da73752 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -2,7 +2,7 @@ export function main() { let root = window.document.getElementById("main"); if (root != null) { window.interpreter = new Interpreter(root); - window.ipc.postMessage(serializeIpcMessage("initialize")) + window.ipc.postMessage(serializeIpcMessage("initialize")); } } export class Interpreter { @@ -207,7 +207,9 @@ export class Interpreter { event.preventDefault(); const href = target.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { - window.ipc.postMessage(serializeIpcMessage("browser_open", { href })) + window.ipc.postMessage( + serializeIpcMessage("browser_open", { href }) + ); } } } @@ -261,12 +263,13 @@ export class Interpreter { if (realId == null) { return; } - window.ipc.postMessage(serializeIpcMessage( - "user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - })); + window.ipc.postMessage( + serializeIpcMessage("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }) + ); } }; this.NewEventListener(edit.event_name, edit.root, handler); @@ -342,6 +345,7 @@ export function serialize_event(event) { } return { value: value, + values: {}, }; } case "input": From 61f9b9dd83a3151b3ee4054792e7b7bc5eb2af95 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Feb 2022 13:46:14 -0500 Subject: [PATCH 26/34] fix: remove serde attr --- packages/html/src/events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 4336d2476..8067e6314 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -486,8 +486,6 @@ pub mod on { #[derive(Debug)] pub struct FormData { pub value: String, - - #[serde(default)] pub values: HashMap, /* DOMEvent: Send + SyncTarget relatedTarget */ } From ee2e986a307b385e88ad82ca604dd2d34e06ec29 Mon Sep 17 00:00:00 2001 From: Denis Richartz Date: Wed, 16 Feb 2022 15:34:43 +0100 Subject: [PATCH 27/34] fix unnecessary div --- packages/router/src/components/router.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index bf23645f5..df1daab78 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -33,6 +33,6 @@ pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { } cx.render(rsx!( - div { &cx.props.children } + &cx.props.children )) } From b71cf6ed4ad4367da264827b55fbec537f631d41 Mon Sep 17 00:00:00 2001 From: Denis Richartz Date: Wed, 16 Feb 2022 16:11:57 +0100 Subject: [PATCH 28/34] cargo fmt --- packages/router/src/components/router.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index df1daab78..c117c6650 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -32,7 +32,5 @@ pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { cx.props.onchange.call(path.to_string()); } - cx.render(rsx!( - &cx.props.children - )) + cx.render(rsx!(&cx.props.children)) } From aa60971c5c9209d42b3f39b636e757c910a6f61e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:03:53 -0500 Subject: [PATCH 29/34] fix: remove preventdefault on form --- packages/html/src/elements.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 3d160211e..c6b1cb7e0 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1101,12 +1101,6 @@ impl label { } } -impl form { - pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { - cx.attr("dioxus-prevent-default", val, None, false) - } -} - builder_constructors! { // SVG components /// Build a From c6bdb5db76629e7fae77c110a21509d07ece2e89 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:08:57 -0500 Subject: [PATCH 30/34] fix: login form --- examples/login_form.rs | 68 +++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/examples/login_form.rs b/examples/login_form.rs index fdd1843ce..62c6f982b 100644 --- a/examples/login_form.rs +++ b/examples/login_form.rs @@ -3,14 +3,14 @@ use dioxus::events::*; use dioxus::prelude::*; -use dioxus::router::{Link, Router, Route, RouterService}; +use dioxus::router::{Link, Route, Router, RouterService}; fn main() { dioxus::desktop::launch(APP); } static APP: Component = |cx| { - cx.render(rsx!{ + cx.render(rsx! { Router { Route { to: "/", home() } Route { to: "/login", login() } @@ -25,65 +25,45 @@ fn home(cx: Scope) -> Element { }) } - fn login(cx: Scope) -> Element { - let username = use_state(&cx, String::new); - let password = use_state(&cx, String::new); - let service = cx.consume_context::()?; - let onsubmit = move |_| { - cx.push_future({ - let (username, password) = (username.get().clone(), password.get().clone()); - let service = service.clone(); + let onsubmit = move |evt: FormEvent| { + to_owned![service]; + let username = evt.values["username"].clone(); + let password = evt.values["password"].clone(); - async move { - let params = [ - ("username", username.to_string()), - ("password", password.to_string()) - ]; + cx.spawn(async move { + let resp = reqwest::Client::new() + .post("http://localhost/login") + .form(&[("username", username), ("password", password)]) + .send() + .await; - let resp = reqwest::Client::new() - .post("http://localhost/login") - .form(¶ms) - .send() - .await; + match resp { + // Parse data from here, such as storing a response token + Ok(data) => service.push_route("/"), - match resp { - Ok(data) => { - // Parse data from here, such as storing a response token - service.push_route("/"); - } - Err(err) => {} //Handle any errors from the fetch here - } + //Handle any errors from the fetch here + Err(err) => {} } }); }; - cx.render(rsx!{ + cx.render(rsx! { h1 { "Login" } form { onsubmit: onsubmit, - // Prevent the default behavior of to post - prevent_default: "onsubmit", - input { - oninput: move |evt| username.set(evt.value.clone()) - } - label { - "Username" - } + prevent_default: "onsubmit", // Prevent the default behavior of to post + input { r#type: "text" } + label { "Username" } br {} - input { - oninput: move |evt| password.set(evt.value.clone()), - r#type: "password" - } + input { r#type: "password" } label { "Password" } br {} - button { - "Login" - } + button { "Login" } } }) -} \ No newline at end of file +} From babe8627394c47491348b7e3cec54ce02a31e3b2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:11:31 -0500 Subject: [PATCH 31/34] fix: login example to use proper methods --- examples/login_form.rs | 23 +++++++++++------------ packages/router/src/hooks/use_route.rs | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/login_form.rs b/examples/login_form.rs index 62c6f982b..cdf05d7c3 100644 --- a/examples/login_form.rs +++ b/examples/login_form.rs @@ -3,20 +3,20 @@ use dioxus::events::*; use dioxus::prelude::*; -use dioxus::router::{Link, Route, Router, RouterService}; +use dioxus::router::{use_router, Link, Route, Router}; fn main() { - dioxus::desktop::launch(APP); + dioxus::desktop::launch(app); } -static APP: Component = |cx| { +fn app(cx: Scope) -> Element { cx.render(rsx! { Router { Route { to: "/", home() } Route { to: "/login", login() } } }) -}; +} fn home(cx: Scope) -> Element { cx.render(rsx! { @@ -26,7 +26,7 @@ fn home(cx: Scope) -> Element { } fn login(cx: Scope) -> Element { - let service = cx.consume_context::()?; + let service = use_router(&cx); let onsubmit = move |evt: FormEvent| { to_owned![service]; @@ -42,10 +42,10 @@ fn login(cx: Scope) -> Element { match resp { // Parse data from here, such as storing a response token - Ok(data) => service.push_route("/"), + Ok(_data) => service.push_route("/"), //Handle any errors from the fetch here - Err(err) => {} + Err(_err) => {} } }); }; @@ -55,13 +55,12 @@ fn login(cx: Scope) -> Element { form { onsubmit: onsubmit, prevent_default: "onsubmit", // Prevent the default behavior of to post - input { r#type: "text" } + + input { "type": "text" } label { "Username" } br {} - input { r#type: "password" } - label { - "Password" - } + input { "type": "password" } + label { "Password" } br {} button { "Login" } } diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 31a157a8f..4de6dff12 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -106,7 +106,7 @@ impl Drop for UseRouteListener { } /// This hook provides access to the `RouterService` for the app. -pub fn use_router(cx: &ScopeState) -> &RouterService { +pub fn use_router(cx: &ScopeState) -> &Rc { cx.use_hook(|_| { cx.consume_context::() .expect("Cannot call use_route outside the scope of a Router component") From 4d965c8571774c59c5c6d6a3223a747301da4949 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:16:03 -0500 Subject: [PATCH 32/34] feat: simplify login example --- examples/login_form.rs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/examples/login_form.rs b/examples/login_form.rs index cdf05d7c3..1613cdc63 100644 --- a/examples/login_form.rs +++ b/examples/login_form.rs @@ -10,39 +10,22 @@ fn main() { } fn app(cx: Scope) -> Element { - cx.render(rsx! { - Router { - Route { to: "/", home() } - Route { to: "/login", login() } - } - }) -} - -fn home(cx: Scope) -> Element { - cx.render(rsx! { - h1 { "Welcome Home" } - Link { to: "/login", "Login" } - }) -} - -fn login(cx: Scope) -> Element { - let service = use_router(&cx); - let onsubmit = move |evt: FormEvent| { - to_owned![service]; - let username = evt.values["username"].clone(); - let password = evt.values["password"].clone(); - cx.spawn(async move { let resp = reqwest::Client::new() .post("http://localhost/login") - .form(&[("username", username), ("password", password)]) + .form(&[ + ("username", &evt.values["username"]), + ("password", &evt.values["password"]), + ]) .send() .await; match resp { // Parse data from here, such as storing a response token - Ok(_data) => service.push_route("/"), + Ok(_data) => { + println!("Login successful"); + } //Handle any errors from the fetch here Err(_err) => {} From b57987cfb1964d761eeb85f4019cabfcd7625f2c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:17:47 -0500 Subject: [PATCH 33/34] chore: clean up example a bit --- examples/login_form.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/login_form.rs b/examples/login_form.rs index 1613cdc63..7fd91262d 100644 --- a/examples/login_form.rs +++ b/examples/login_form.rs @@ -3,7 +3,6 @@ use dioxus::events::*; use dioxus::prelude::*; -use dioxus::router::{use_router, Link, Route, Router}; fn main() { dioxus::desktop::launch(app); @@ -23,12 +22,10 @@ fn app(cx: Scope) -> Element { match resp { // Parse data from here, such as storing a response token - Ok(_data) => { - println!("Login successful"); - } + Ok(_data) => println!("Login successful"), //Handle any errors from the fetch here - Err(_err) => {} + Err(_err) => println!("Login failed"), } }); }; From 3873cd1a60e6bc6371e73fe7c378cc2fb2c60407 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Feb 2022 14:40:45 -0500 Subject: [PATCH 34/34] fix: remove unused depds --- packages/core-macro/Cargo.toml | 1 - packages/desktop/Cargo.toml | 4 +--- packages/router/Cargo.toml | 2 +- packages/web/Cargo.toml | 5 ++--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index eb8210923..8026bcdaf 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -15,7 +15,6 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] proc-macro = true [dependencies] -once_cell = "1.8" proc-macro-error = "1.0.4" proc-macro2 = { version = "1.0.6" } quote = "1.0" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 2622355eb..6fd277ba4 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -13,12 +13,10 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] } -argh = "0.1.7" serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -html-escape = "0.2.9" wry = { version = "0.13.1" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ @@ -27,7 +25,6 @@ tokio = { version = "1.16.1", features = [ "rt", "time", ], optional = true, default-features = false } -dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" } webbrowser = "0.5.5" mime_guess = "2.0.3" @@ -47,5 +44,6 @@ ayatana = ["wry/ayatana"] [dev-dependencies] +dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } dioxus-hooks = { path = "../hooks" } # image = "0.24.0" # enable this when generating a new desktop image diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 239a3c5d8..221372b50 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -16,8 +16,8 @@ dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" -url = "2.2.2" serde_urlencoded = "0.7" +# url = "2.2.2" # for wasm web-sys = { version = "0.3", features = [ diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 31a7ff6be..333838398 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -15,13 +15,10 @@ dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-html = { path = "../html", version = "^0.1.6" } js-sys = "0.3.56" wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } -lazy_static = "1.4.0" wasm-bindgen-futures = "0.4.29" log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" -wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } -wasm-bindgen-test = "0.3.29" once_cell = "1.9.0" anyhow = "1.0.53" gloo-timers = { version = "0.2.3", features = ["futures"] } @@ -80,3 +77,5 @@ panic_hook = ["console_error_panic_hook"] dioxus-core-macro = { path = "../core-macro" } wasm-bindgen-test = "0.3.29" dioxus-ssr = { path = "../ssr" } +wasm-logger = "0.2.0" +