(
+ 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();
+ }
+ }
+ }
+}
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);
}
}
diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs
index 760bf7662..7be4273e9 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,49 @@ 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 {
+ match serde_json::from_str(payload) {
+ 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 +126,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..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.
///
@@ -132,23 +129,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,55 +155,13 @@ 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.
- 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()
@@ -235,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 => {}
@@ -353,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");
- }
- }
-}
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)
+ }
+}
diff --git a/packages/desktop/src/user_window_events.rs b/packages/desktop/src/user_window_events.rs
new file mode 100644
index 000000000..aab14561b
--- /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) => {
+ 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),
+ 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(),
+ }
+}
diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js
index f0acd0802..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.rpc.call("initialize");
+ window.ipc.postMessage(serializeIpcMessage("initialize"));
}
}
export class Interpreter {
@@ -105,7 +105,7 @@ export class Interpreter {
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) {
@@ -207,7 +207,9 @@ 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 +263,13 @@ export class Interpreter {
if (realId == null) {
return;
}
- window.rpc.call("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);
@@ -341,6 +345,7 @@ export function serialize_event(event) {
}
return {
value: value,
+ values: {},
};
}
case "input":
@@ -544,6 +549,9 @@ export function serialize_event(event) {
}
}
}
+function serializeIpcMessage(method, params = {}) {
+ return JSON.stringify({ method, params });
+}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
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;
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/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
}
diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs
index bf23645f5..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!(
- div { &cx.props.children }
- ))
+ cx.render(rsx!(&cx.props.children))
}
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")
diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml
index 7a3baa955..333838398 100644
--- a/packages/web/Cargo.toml
+++ b/packages/web/Cargo.toml
@@ -15,21 +15,18 @@ 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"
-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"
@@ -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"
+
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();
}
}