From 4b2e42695898d9adf6108a9ef4c8ce8571041fdf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Mar 2024 22:38:38 -0800 Subject: [PATCH] clippy, and don't hash invisible files for ts generation --- Cargo.lock | 1 - examples/assets/file_upload.css | 9 +- examples/file_upload.rs | 57 ++- examples/form.rs | 4 +- packages/desktop/Cargo.toml | 2 - packages/desktop/headless_tests/events.rs | 4 +- packages/desktop/headless_tests/rendering.rs | 2 +- packages/desktop/headless_tests/utils.rs | 2 +- packages/desktop/src/app.rs | 70 +-- packages/desktop/src/config.rs | 12 +- packages/desktop/src/desktop_context.rs | 16 +- packages/desktop/src/file_upload.rs | 14 +- packages/desktop/src/ipc.rs | 18 +- packages/desktop/src/launch.rs | 20 +- packages/desktop/src/mobile_shortcut.rs | 6 - packages/desktop/src/protocol.rs | 1 + packages/desktop/src/query.rs | 9 +- packages/desktop/src/waker.rs | 4 +- packages/desktop/src/webview.rs | 49 +-- packages/interpreter/build.rs | 23 +- packages/interpreter/src/js/common.js | 100 +---- packages/interpreter/src/js/core.js | 208 +-------- packages/interpreter/src/js/hash.txt | 2 +- packages/interpreter/src/js/native.js | 423 +------------------ packages/interpreter/src/ts/native.ts | 41 -- packages/interpreter/src/unified_bindings.rs | 2 +- packages/web/src/lib.rs | 10 +- 27 files changed, 173 insertions(+), 936 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ffbb194b..3d1ebad8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2384,7 +2384,6 @@ version = "0.5.0-alpha.0" dependencies = [ "async-trait", "core-foundation", - "crossbeam-channel", "dioxus", "dioxus-cli-config", "dioxus-core", diff --git a/examples/assets/file_upload.css b/examples/assets/file_upload.css index 81aba2638..58d1bb3dc 100644 --- a/examples/assets/file_upload.css +++ b/examples/assets/file_upload.css @@ -1,14 +1,11 @@ body { font-family: Arial, sans-serif; - margin: 0; - padding: 0; + margin: 10px; + padding: 10px; background-color: #f4f4f4; - display: flex; justify-content: center; align-items: center; - height: 100vh; - flex-direction: column; - gap: 20px; + min-height: 100vh; } #drop-zone { diff --git a/examples/file_upload.rs b/examples/file_upload.rs index f26b45477..3a433250e 100644 --- a/examples/file_upload.rs +++ b/examples/file_upload.rs @@ -12,15 +12,23 @@ fn main() { LaunchBuilder::desktop().launch(app); } +struct UploadedFile { + name: String, + contents: String, +} + fn app() -> Element { let mut enable_directory_upload = use_signal(|| false); - let mut files_uploaded = use_signal(|| Vec::new() as Vec); + let mut files_uploaded = use_signal(|| Vec::new() as Vec); let read_files = move |file_engine: Arc| async move { let files = file_engine.files(); for file_name in &files { - if let Some(file) = file_engine.read_file_to_string(file_name).await { - files_uploaded.write().push(file); + if let Some(contents) = file_engine.read_file_to_string(file_name).await { + files_uploaded.write().push(UploadedFile { + name: file_name.clone(), + contents, + }); } } }; @@ -40,25 +48,31 @@ fn app() -> Element { rsx! { style { {include_str!("./assets/file_upload.css")} } - input { - r#type: "checkbox", - id: "directory-upload", - checked: enable_directory_upload, - oninput: move |evt| enable_directory_upload.set(evt.checked()), - }, + h1 { "File Upload Example" } + p { "Drop a .txt, .rs, or .js file here to read it" } - label { r#for: "directory-upload", "Enable directory upload" } - input { - r#type: "file", - accept: ".txt,.rs,.js", - multiple: true, - name: "textreader", - directory: enable_directory_upload, - onchange: upload_files, + div { + label { r#for: "directory-upload", "Enable directory upload" } + input { + r#type: "checkbox", + id: "directory-upload", + checked: enable_directory_upload, + oninput: move |evt| enable_directory_upload.set(evt.checked()), + }, } - label { r#for: "textreader", "Upload text/rust files and read them" } + div { + label { r#for: "textreader", "Upload text/rust files and read them" } + input { + r#type: "file", + accept: ".txt,.rs,.js", + multiple: true, + name: "textreader", + directory: enable_directory_upload, + onchange: upload_files, + } + } div { // cheating with a little bit of JS... @@ -71,8 +85,11 @@ fn app() -> Element { } ul { - for file in files_uploaded.read().iter() { - li { "{file}" } + for file in files_uploaded.read().iter().rev() { + li { + span { "{file.name}" } + pre { "{file.contents}" } + } } } } diff --git a/examples/form.rs b/examples/form.rs index b49072292..9f6215226 100644 --- a/examples/form.rs +++ b/examples/form.rs @@ -11,8 +11,8 @@ fn main() { } fn app() -> Element { - let mut values = use_signal(|| HashMap::new()); - let mut submitted_values = use_signal(|| HashMap::new()); + let mut values = use_signal(HashMap::new); + let mut submitted_values = use_signal(HashMap::new); rsx! { div { style: "display: flex", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index fdb68d734..dfca27c58 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -46,11 +46,9 @@ dunce = "1.0.2" slab = { workspace = true } rustc-hash = { workspace = true } dioxus-hooks = { workspace = true } - futures-util = { workspace = true } urlencoding = "2.1.2" async-trait = "0.1.68" -crossbeam-channel = "0.5.8" tao = { version = "0.24.0", features = ["rwh_05"] } [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies] diff --git a/packages/desktop/headless_tests/events.rs b/packages/desktop/headless_tests/events.rs index 870e43057..c981144d5 100644 --- a/packages/desktop/headless_tests/events.rs +++ b/packages/desktop/headless_tests/events.rs @@ -437,7 +437,7 @@ fn test_focus_out_div() -> Element { } fn test_form_input() -> Element { - let mut values = use_signal(|| HashMap::new()); + let mut values = use_signal(HashMap::new); utils::mock_event_with_extra( "form-username", @@ -489,7 +489,7 @@ fn test_form_input() -> Element { } fn test_form_submit() -> Element { - let mut values = use_signal(|| HashMap::new()); + let mut values = use_signal(HashMap::new); utils::mock_event_with_extra( "form-submitter", diff --git a/packages/desktop/headless_tests/rendering.rs b/packages/desktop/headless_tests/rendering.rs index 15e2b9f59..df050ed10 100644 --- a/packages/desktop/headless_tests/rendering.rs +++ b/packages/desktop/headless_tests/rendering.rs @@ -14,7 +14,7 @@ fn use_inner_html(id: &'static str) -> Option { use_effect(move || { spawn(async move { - tokio::time::sleep(std::time::Duration::from_millis(2000)).await; + tokio::time::sleep(std::time::Duration::from_millis(500)).await; let res = eval(&format!( r#"let element = document.getElementById('{}'); diff --git a/packages/desktop/headless_tests/utils.rs b/packages/desktop/headless_tests/utils.rs index 343e76467..419f5c8cd 100644 --- a/packages/desktop/headless_tests/utils.rs +++ b/packages/desktop/headless_tests/utils.rs @@ -36,7 +36,7 @@ pub fn mock_event_with_extra(id: &'static str, value: &'static str, extra: &'sta EXPECTED_EVENTS.with_mut(|x| *x += 1); spawn(async move { - tokio::time::sleep(std::time::Duration::from_millis(5000)).await; + tokio::time::sleep(std::time::Duration::from_millis(500)).await; let js = format!( r#" diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index ed3528508..d70788bd3 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -3,12 +3,11 @@ use crate::{ element::DesktopElement, event_handlers::WindowEventHandlers, file_upload::{DesktopFileDragEvent, DesktopFileUploadForm, FileDialogRequest}, - ipc::{EventData, IpcMessage, UserWindowEvent}, + ipc::{IpcMessage, UserWindowEvent}, query::QueryResult, shortcut::{GlobalHotKeyEvent, ShortcutRegistry}, webview::WebviewInstance, }; -use crossbeam_channel::Receiver; use dioxus_core::ElementId; use dioxus_core::VirtualDom; use dioxus_html::{native_bind::NativeFileEngine, HasFileData, HtmlEvent, PlatformEventData}; @@ -50,7 +49,6 @@ pub(crate) struct SharedContext { pub(crate) event_handlers: WindowEventHandlers, pub(crate) pending_webviews: RefCell>, pub(crate) shortcut_manager: ShortcutRegistry, - pub(crate) global_hotkey_channel: Receiver, pub(crate) proxy: EventLoopProxy, pub(crate) target: EventLoopWindowTarget, } @@ -70,7 +68,6 @@ impl App { event_handlers: WindowEventHandlers::default(), pending_webviews: Default::default(), shortcut_manager: ShortcutRegistry::new(), - global_hotkey_channel: GlobalHotKeyEvent::receiver().clone(), proxy: event_loop.create_proxy(), target: event_loop.clone(), }), @@ -79,6 +76,9 @@ impl App { // Set the event converter dioxus_html::set_event_converter(Box::new(crate::events::SerializedHtmlEventConverter)); + // Wire up the global hotkey handler + app.set_global_hotkey_handler(); + // Allow hotreloading to work - but only in debug mode #[cfg(all(feature = "hot-reload", debug_assertions))] app.connect_hotreload(); @@ -88,14 +88,13 @@ impl App { pub fn tick(&mut self, window_event: &Event<'_, UserWindowEvent>) { self.control_flow = ControlFlow::Wait; - self.shared .event_handlers .apply_event(window_event, &self.shared.target); + } - if let Ok(event) = self.shared.global_hotkey_channel.try_recv() { - self.shared.shortcut_manager.call_handlers(event); - } + pub fn handle_global_hotkey(&self, event: GlobalHotKeyEvent) { + self.shared.shortcut_manager.call_handlers(event); } #[cfg(all(feature = "hot-reload", debug_assertions))] @@ -103,10 +102,7 @@ impl App { dioxus_hot_reload::connect({ let proxy = self.shared.proxy.clone(); move |template| { - let _ = proxy.send_event(UserWindowEvent( - EventData::HotReloadEvent(template), - unsafe { WindowId::dummy() }, - )); + let _ = proxy.send_event(UserWindowEvent::HotReloadEvent(template)); } }); } @@ -115,10 +111,7 @@ impl App { for handler in self.shared.pending_webviews.borrow_mut().drain(..) { let id = handler.desktop_context.window.id(); self.webviews.insert(id, handler); - _ = self - .shared - .proxy - .send_event(UserWindowEvent(EventData::Poll, id)); + _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id)); } } @@ -168,11 +161,6 @@ impl App { let id = webview.desktop_context.window.id(); self.webviews.insert(id, webview); - - _ = self - .shared - .proxy - .send_event(UserWindowEvent(EventData::Poll, id)); } pub fn handle_browser_open(&mut self, msg: IpcMessage) { @@ -186,19 +174,29 @@ impl App { } } + /// The webview is finally loaded + /// + /// Let's rebuild it and then start polling it pub fn handle_initialize_msg(&mut self, id: WindowId) { let view = self.webviews.get_mut(&id).unwrap(); + view.dom .rebuild(&mut *view.desktop_context.mutation_state.borrow_mut()); + view.desktop_context.send_edits(); + view.desktop_context .window .set_visible(self.is_visible_before_start); + + _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id)); } + /// Todo: maybe we should poll the virtualdom asking if it has any final actions to apply before closing the webview + /// + /// Technically you can handle this with the use_window_event hook pub fn handle_close_msg(&mut self, id: WindowId) { self.webviews.remove(&id); - if self.webviews.is_empty() { self.control_flow = ControlFlow::Exit } @@ -244,16 +242,14 @@ impl App { if drag.files().is_some() { let file_event = recent_file.current().unwrap(); let paths = match file_event { - wry::FileDropEvent::Hovered { paths, position } => paths, - wry::FileDropEvent::Dropped { paths, position } => paths, + wry::FileDropEvent::Hovered { paths, .. } => paths, + wry::FileDropEvent::Dropped { paths, .. } => paths, _ => vec![], }; - let files = Arc::new(NativeFileEngine::new(paths)); - let event = DesktopFileDragEvent { + Rc::new(PlatformEventData::new(Box::new(DesktopFileDragEvent { mouse: drag.mouse.clone(), - files, - }; - Rc::new(PlatformEventData::new(Box::new(event))) + files: Arc::new(NativeFileEngine::new(paths)), + }))) } else { data.into_any() } @@ -282,8 +278,6 @@ impl App { } } - pub fn handle_file_drag(&mut self, msg: IpcMessage, window: WindowId) {} - pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) { let Ok(file_dialog) = serde_json::from_value::(msg.params()) else { return; @@ -327,6 +321,19 @@ impl App { view.poll_vdom(); } + + fn set_global_hotkey_handler(&self) { + let receiver = self.shared.proxy.clone(); + + // The event loop becomes the hotkey receiver + // This means we don't need to poll the receiver on every tick - we just get the events as they come in + // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the + // receiver will become inert. + global_hotkey::GlobalHotKeyEvent::set_event_handler(Some(move |t| { + // todo: should we unset the event handler when the app shuts down? + _ = receiver.send_event(UserWindowEvent::GlobalHotKeyEvent(t)); + })); + } } /// Different hide implementations per platform @@ -336,7 +343,6 @@ pub fn hide_app_window(window: &wry::WebView) { { use tao::platform::windows::WindowExtWindows; window.set_visible(false); - // window.set_skip_taskbar(true); } #[cfg(target_os = "linux")] diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index feedf00da..6178dbf93 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -1,11 +1,7 @@ use std::borrow::Cow; use std::path::PathBuf; - -use tao::window::{Icon, WindowBuilder, WindowId}; -use wry::{ - http::{Request as HttpRequest, Response as HttpResponse}, - FileDropEvent, -}; +use tao::window::{Icon, WindowBuilder}; +use wry::http::{Request as HttpRequest, Response as HttpResponse}; /// The behaviour of the application when the last window is closed. #[derive(Copy, Clone, Eq, PartialEq)] @@ -21,7 +17,6 @@ pub enum WindowCloseBehaviour { /// The configuration for the desktop application. pub struct Config { pub(crate) window: WindowBuilder, - pub(crate) file_drop_handler: Option, pub(crate) protocols: Vec, pub(crate) pre_rendered: Option, pub(crate) disable_context_menu: bool, @@ -35,8 +30,6 @@ pub struct Config { pub(crate) enable_default_menu_bar: bool, } -type DropHandler = Box bool>; - pub(crate) type WryProtocol = ( String, Box>) -> HttpResponse> + 'static>, @@ -56,7 +49,6 @@ impl Config { Self { window, protocols: Vec::new(), - file_drop_handler: None, pre_rendered: None, disable_context_menu: !cfg!(debug_assertions), resource_dir: None, diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 55f3c6417..a32436f0e 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -3,7 +3,7 @@ use crate::{ assets::AssetHandlerRegistry, edits::EditQueue, file_upload::NativeFileHover, - ipc::{EventData, UserWindowEvent}, + ipc::UserWindowEvent, query::QueryEngine, shortcut::{HotKey, ShortcutHandle, ShortcutRegistryError}, webview::WebviewInstance, @@ -17,14 +17,13 @@ use dioxus_interpreter_js::MutationState; use std::{ cell::RefCell, rc::{Rc, Weak}, - sync::Arc, }; use tao::{ event::Event, event_loop::EventLoopWindowTarget, window::{Fullscreen as WryFullscreen, Window, WindowId}, }; -use wry::{FileDropEvent, RequestAsyncResponder, WebView}; +use wry::{RequestAsyncResponder, WebView}; #[cfg(target_os = "ios")] use tao::platform::ios::WindowExtIOS; @@ -130,12 +129,7 @@ impl DesktopService { self.shared .proxy - .send_event(UserWindowEvent(EventData::NewWindow, cx.id())) - .unwrap(); - - self.shared - .proxy - .send_event(UserWindowEvent(EventData::Poll, cx.id())) + .send_event(UserWindowEvent::NewWindow) .unwrap(); self.shared.pending_webviews.borrow_mut().push(window); @@ -167,7 +161,7 @@ impl DesktopService { let _ = self .shared .proxy - .send_event(UserWindowEvent(EventData::CloseWindow, self.id())); + .send_event(UserWindowEvent::CloseWindow(self.id())); } /// Close a particular window, given its ID @@ -175,7 +169,7 @@ impl DesktopService { let _ = self .shared .proxy - .send_event(UserWindowEvent(EventData::CloseWindow, id)); + .send_event(UserWindowEvent::CloseWindow(id)); } /// change window to fullscreen diff --git a/packages/desktop/src/file_upload.rs b/packages/desktop/src/file_upload.rs index a7edef30d..e31352814 100644 --- a/packages/desktop/src/file_upload.rs +++ b/packages/desktop/src/file_upload.rs @@ -12,7 +12,13 @@ use dioxus_html::{ }; use muda::accelerator::Modifiers; use serde::Deserialize; -use std::{cell::Cell, path::PathBuf, rc::Rc, str::FromStr, sync::Arc}; +use std::{ + cell::{Cell, RefCell}, + path::PathBuf, + rc::Rc, + str::FromStr, + sync::Arc, +}; use wry::FileDropEvent; #[derive(Debug, Deserialize)] @@ -156,15 +162,15 @@ impl HasFormData for DesktopFileUploadForm { #[derive(Default, Clone)] pub struct NativeFileHover { - event: Rc>>, + event: Rc>>, } impl NativeFileHover { pub fn set(&self, event: FileDropEvent) { - self.event.set(Some(event)); + self.event.borrow_mut().replace(event); } pub fn current(&self) -> Option { - self.event.as_ref().clone().take() + self.event.borrow_mut().clone() } } diff --git a/packages/desktop/src/ipc.rs b/packages/desktop/src/ipc.rs index 69d8eb5a3..785f0a4e7 100644 --- a/packages/desktop/src/ipc.rs +++ b/packages/desktop/src/ipc.rs @@ -1,18 +1,17 @@ +use global_hotkey::GlobalHotKeyEvent; use serde::{Deserialize, Serialize}; use tao::window::WindowId; -/// A pair of data #[derive(Debug, Clone)] -pub struct UserWindowEvent(pub EventData, pub WindowId); +pub enum UserWindowEvent { + /// A global hotkey event + GlobalHotKeyEvent(GlobalHotKeyEvent), -/// The data that might eminate from any window/webview -#[derive(Debug, Clone)] -pub enum EventData { /// Poll the virtualdom - Poll, + Poll(WindowId), /// Handle an ipc message eminating from the window.postMessage of a given webview - Ipc(IpcMessage), + Ipc { id: WindowId, msg: IpcMessage }, /// Handle a hotreload event, basically telling us to update our templates #[cfg(all(feature = "hot-reload", debug_assertions))] @@ -22,7 +21,7 @@ pub enum EventData { NewWindow, /// Close a given window (could be any window!) - CloseWindow, + CloseWindow(WindowId), } /// A message struct that manages the communication between the webview and the eventloop code @@ -42,16 +41,13 @@ pub enum IpcMethod<'a> { Query, BrowserOpen, Initialize, - FileDrag, Other(&'a str), } impl IpcMessage { pub(crate) fn method(&self) -> IpcMethod { match self.method.as_str() { - // todo: this is a misspelling, needs to be fixed "file_dialog" => IpcMethod::FileDialog, - "file_drag" => IpcMethod::FileDrag, "user_event" => IpcMethod::UserEvent, "query" => IpcMethod::Query, "browser_open" => IpcMethod::BrowserOpen, diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 40029d87d..ed75163fa 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -1,7 +1,7 @@ pub use crate::Config; use crate::{ app::App, - ipc::{EventData, IpcMethod, UserWindowEvent}, + ipc::{IpcMethod, UserWindowEvent}, }; use dioxus_core::*; use std::any::Any; @@ -15,6 +15,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf let (event_loop, mut app) = App::new(desktop_config, virtual_dom); event_loop.run(move |window_event, _, control_flow| { + // Set the control flow and check if any events need to be handled in the app itself app.tick(&window_event); match window_event { @@ -26,19 +27,20 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf WindowEvent::Destroyed { .. } => app.window_destroyed(window_id), _ => {} }, - Event::UserEvent(UserWindowEvent(event, id)) => match event { - EventData::Poll => app.poll_vdom(id), - EventData::NewWindow => app.handle_new_window(), - EventData::CloseWindow => app.handle_close_msg(id), + + Event::UserEvent(event) => match event { + UserWindowEvent::Poll(id) => app.poll_vdom(id), + UserWindowEvent::NewWindow => app.handle_new_window(), + UserWindowEvent::CloseWindow(id) => app.handle_close_msg(id), + UserWindowEvent::GlobalHotKeyEvent(evnt) => app.handle_global_hotkey(evnt), #[cfg(all(feature = "hot-reload", debug_assertions))] - EventData::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg), - EventData::Ipc(msg) => match msg.method() { + UserWindowEvent::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg), + UserWindowEvent::Ipc { id, msg } => match msg.method() { + IpcMethod::Initialize => app.handle_initialize_msg(id), IpcMethod::FileDialog => app.handle_file_dialog_msg(msg, id), - IpcMethod::FileDrag => app.handle_file_drag(msg, id), IpcMethod::UserEvent => app.handle_user_event_msg(msg, id), IpcMethod::Query => app.handle_query_msg(msg, id), IpcMethod::BrowserOpen => app.handle_browser_open(msg), - IpcMethod::Initialize => app.handle_initialize_msg(id), IpcMethod::Other(_) => {} }, }, diff --git a/packages/desktop/src/mobile_shortcut.rs b/packages/desktop/src/mobile_shortcut.rs index 03566bf36..5ff0683a1 100644 --- a/packages/desktop/src/mobile_shortcut.rs +++ b/packages/desktop/src/mobile_shortcut.rs @@ -80,10 +80,4 @@ pub struct GlobalHotKeyEvent { pub id: u32, } -impl GlobalHotKeyEvent { - pub fn receiver() -> crossbeam_channel::Receiver { - crossbeam_channel::unbounded().1 - } -} - pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code; diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 870c6cabb..1e615aa6e 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -50,6 +50,7 @@ pub(super) fn index_request( } None => assets_head(), }; + if let Some(head) = head { index.insert_str(index.find("").expect("Head element to exist"), &head); } diff --git a/packages/desktop/src/query.rs b/packages/desktop/src/query.rs index 0e5ecbe98..d6ab3f07b 100644 --- a/packages/desktop/src/query.rs +++ b/packages/desktop/src/query.rs @@ -1,13 +1,17 @@ -use std::{cell::RefCell, rc::Rc}; - use crate::DesktopContext; use futures_util::{FutureExt, StreamExt}; use generational_box::Owner; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use slab::Slab; +use std::{cell::RefCell, rc::Rc}; use thiserror::Error; +/* +todo: +- write this in the interpreter itself rather than in blobs of inline javascript... +- it could also be simpler, probably? +*/ const DIOXUS_CODE: &str = r#" let dioxus = { recv: function () { @@ -43,7 +47,6 @@ let dioxus = { }"#; /// Tracks what query ids are currently active - pub(crate) struct SharedSlab { pub slab: Rc>>, } diff --git a/packages/desktop/src/waker.rs b/packages/desktop/src/waker.rs index 5c3e915e3..fd031afb3 100644 --- a/packages/desktop/src/waker.rs +++ b/packages/desktop/src/waker.rs @@ -1,4 +1,4 @@ -use crate::ipc::{EventData, UserWindowEvent}; +use crate::ipc::UserWindowEvent; use futures_util::task::ArcWake; use std::sync::Arc; use tao::{event_loop::EventLoopProxy, window::WindowId}; @@ -24,7 +24,7 @@ pub fn tao_waker(proxy: EventLoopProxy, id: WindowId) -> std::t fn wake_by_ref(arc_self: &Arc) { _ = arc_self .proxy - .send_event(UserWindowEvent(EventData::Poll, arc_self.id)); + .send_event(UserWindowEvent::Poll(arc_self.id)); } } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 276327da4..ae3d48260 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,13 +1,7 @@ use crate::{ - app::SharedContext, - assets::AssetHandlerRegistry, - edits::EditQueue, - eval::DesktopEvalProvider, - file_upload::NativeFileHover, - ipc::{EventData, UserWindowEvent}, - protocol, - waker::tao_waker, - Config, DesktopContext, DesktopService, + app::SharedContext, assets::AssetHandlerRegistry, edits::EditQueue, eval::DesktopEvalProvider, + file_upload::NativeFileHover, ipc::UserWindowEvent, protocol, waker::tao_waker, Config, + DesktopContext, DesktopService, }; use dioxus_core::{ScopeId, VirtualDom}; use dioxus_html::prelude::EvalProvider; @@ -61,6 +55,7 @@ impl WebviewInstance { let mut web_context = WebContext::new(cfg.data_dir.clone()); let edit_queue = EditQueue::default(); + let file_hover = NativeFileHover::default(); let asset_handlers = AssetHandlerRegistry::new(dom.runtime()); let headless = !cfg.window.window.visible; @@ -72,6 +67,7 @@ impl WebviewInstance { let asset_handlers_ = asset_handlers.clone(); let edit_queue_ = edit_queue.clone(); let proxy_ = shared.proxy.clone(); + let file_hover_ = file_hover.clone(); let request_handler = move |request, responder: RequestAsyncResponder| { // Try to serve the index file first @@ -97,12 +93,17 @@ impl WebviewInstance { let ipc_handler = move |payload: String| { // defer the event to the main thread - if let Ok(message) = serde_json::from_str(&payload) { - _ = proxy_.send_event(UserWindowEvent(EventData::Ipc(message), window_id)); + if let Ok(msg) = serde_json::from_str(&payload) { + _ = proxy_.send_event(UserWindowEvent::Ipc { id: window_id, msg }); } }; - let file_hover = NativeFileHover::default(); + let file_drop_handler = move |evt| { + // Update the most recent file drop event - when the event comes in from the webview we can use the + // most recent event to build a new event with the files in it. + file_hover_.set(evt); + false + }; #[cfg(any( target_os = "windows", @@ -133,15 +134,7 @@ impl WebviewInstance { .with_navigation_handler(|var| var.contains("dioxus")) // prevent all navigations .with_asynchronous_custom_protocol(String::from("dioxus"), request_handler) .with_web_context(&mut web_context) - .with_file_drop_handler({ - let file_hover = file_hover.clone(); - move |evt| { - println!("file drop: {:?}", evt); - // Update the most recent file hover status - file_hover.set(evt); - false - } - }); + .with_file_drop_handler(file_drop_handler); if let Some(color) = cfg.background_color { webview = webview.with_background_color(color); @@ -153,15 +146,15 @@ impl WebviewInstance { const INITIALIZATION_SCRIPT: &str = r#" if (document.addEventListener) { - document.addEventListener('contextmenu', function(e) { - e.preventDefault(); - }, false); + document.addEventListener('contextmenu', function(e) { + e.preventDefault(); + }, false); } else { - document.attachEvent('oncontextmenu', function() { - window.event.returnValue = false; - }); + document.attachEvent('oncontextmenu', function() { + window.event.returnValue = false; + }); } - "#; + "#; if cfg.disable_context_menu { // in release mode, we don't want to show the dev tool or reload menus diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs index 90e7823a6..f3cf0b7de 100644 --- a/packages/interpreter/build.rs +++ b/packages/interpreter/build.rs @@ -6,13 +6,13 @@ use std::{ fn main() { // If any TS changes, re-run the build script - println!("cargo:rerun-if-changed=src/ts/*.ts,*.json"); + println!("cargo:rerun-if-changed=src/ts/*.ts"); // Compute the hash of the ts files - let hash = hash_dir("src/ts"); + let hash = hash_dir("./src/ts"); // If the hash matches the one on disk, we're good and don't need to update bindings - if let Ok(contents) = read_to_string("src/js/hash.txt") { + if let Ok(contents) = read_to_string("./src/js/hash.txt") { if contents.trim() == hash.to_string() { return; } @@ -34,11 +34,17 @@ fn hash_dir(dir: &str) -> u64 { for entry in std::fs::read_dir(dir).unwrap() { let entry = entry.unwrap(); let path = entry.path(); - let metadata = std::fs::metadata(&path).unwrap(); - if metadata.is_file() { - let contents = std::fs::read(&path).unwrap(); - contents.hash(&mut hasher); + + let Some(ext) = path.extension() else { + continue; + }; + + if ext != "ts" { + continue; } + + let contents = std::fs::read(&path).unwrap(); + contents.hash(&mut hasher); } hasher.finish() @@ -58,7 +64,8 @@ fn gen_bindings(input_name: &str, output_name: &str) { .arg(format!("src/ts/{input_name}.ts")) .arg("--outfile") .arg(format!("src/js/{output_name}.js")) - // .arg("--minify") + .arg("--minify-whitespace") + .arg("--minify-syntax") .status() .unwrap(); diff --git a/packages/interpreter/src/js/common.js b/packages/interpreter/src/js/common.js index 25d0ee691..9e4964594 100644 --- a/packages/interpreter/src/js/common.js +++ b/packages/interpreter/src/js/common.js @@ -1,99 +1 @@ -// src/ts/set_attribute.ts -function setAttributeInner(node, field, value, ns) { - if (ns === "style") { - node.style.setProperty(field, value); - return; - } - if (!!ns) { - node.setAttributeNS(ns, field, value); - return; - } - switch (field) { - case "value": - if (node.value !== value) { - node.value = value; - } - break; - case "initial_value": - node.defaultValue = value; - break; - case "checked": - node.checked = truthy(value); - break; - case "initial_checked": - node.defaultChecked = truthy(value); - break; - case "selected": - node.selected = truthy(value); - break; - case "initial_selected": - node.defaultSelected = truthy(value); - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - if (!truthy(value) && isBoolAttr(field)) { - node.removeAttribute(field); - } else { - node.setAttribute(field, value); - } - } -} -var truthy = function(val) { - return val === "true" || val === true; -}; -var isBoolAttr = function(field) { - switch (field) { - case "allowfullscreen": - case "allowpaymentrequest": - case "async": - case "autofocus": - case "autoplay": - case "checked": - case "controls": - case "default": - case "defer": - case "disabled": - case "formnovalidate": - case "hidden": - case "ismap": - case "itemscope": - case "loop": - case "multiple": - case "muted": - case "nomodule": - case "novalidate": - case "open": - case "playsinline": - case "readonly": - case "required": - case "reversed": - case "selected": - case "truespeed": - case "webkitdirectory": - return true; - default: - return false; - } -}; -// src/ts/form.ts -function retrieveFormValues(form) { - const formData = new FormData(form); - const contents = {}; - formData.forEach((value, key) => { - if (contents[key]) { - contents[key] += "," + value; - } else { - contents[key] = value; - } - }); - return { - valid: form.checkValidity(), - values: contents - }; -} -export { - setAttributeInner, - retrieveFormValues -}; +function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key]+=","+value;else contents[key]=value}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues}; diff --git a/packages/interpreter/src/js/core.js b/packages/interpreter/src/js/core.js index 48cc3315d..1940d5195 100644 --- a/packages/interpreter/src/js/core.js +++ b/packages/interpreter/src/js/core.js @@ -1,207 +1 @@ -// src/ts/set_attribute.ts -function setAttributeInner(node, field, value, ns) { - if (ns === "style") { - node.style.setProperty(field, value); - return; - } - if (!!ns) { - node.setAttributeNS(ns, field, value); - return; - } - switch (field) { - case "value": - if (node.value !== value) { - node.value = value; - } - break; - case "initial_value": - node.defaultValue = value; - break; - case "checked": - node.checked = truthy(value); - break; - case "initial_checked": - node.defaultChecked = truthy(value); - break; - case "selected": - node.selected = truthy(value); - break; - case "initial_selected": - node.defaultSelected = truthy(value); - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - if (!truthy(value) && isBoolAttr(field)) { - node.removeAttribute(field); - } else { - node.setAttribute(field, value); - } - } -} -var truthy = function(val) { - return val === "true" || val === true; -}; -var isBoolAttr = function(field) { - switch (field) { - case "allowfullscreen": - case "allowpaymentrequest": - case "async": - case "autofocus": - case "autoplay": - case "checked": - case "controls": - case "default": - case "defer": - case "disabled": - case "formnovalidate": - case "hidden": - case "ismap": - case "itemscope": - case "loop": - case "multiple": - case "muted": - case "nomodule": - case "novalidate": - case "open": - case "playsinline": - case "readonly": - case "required": - case "reversed": - case "selected": - case "truespeed": - case "webkitdirectory": - return true; - default: - return false; - } -}; - -// src/ts/core.ts -class BaseInterpreter { - global; - local; - root; - handler; - nodes; - stack; - templates; - m; - constructor() { - } - initialize(root, handler = null) { - this.global = {}; - this.local = {}; - this.root = root; - this.nodes = [root]; - this.stack = [root]; - this.templates = {}; - if (handler) { - this.handler = handler; - } - } - createListener(event_name, element, bubbles) { - if (bubbles) { - if (this.global[event_name] === undefined) { - this.global[event_name] = { active: 1, callback: this.handler }; - this.root.addEventListener(event_name, this.handler); - } else { - this.global[event_name].active++; - } - } else { - const id = element.getAttribute("data-dioxus-id"); - if (!this.local[id]) { - this.local[id] = {}; - } - element.addEventListener(event_name, this.handler); - } - } - removeListener(element, event_name, bubbles) { - if (bubbles) { - this.removeBubblingListener(event_name); - } else { - this.removeNonBubblingListener(element, event_name); - } - } - removeBubblingListener(event_name) { - this.global[event_name].active--; - if (this.global[event_name].active === 0) { - this.root.removeEventListener(event_name, this.global[event_name].callback); - delete this.global[event_name]; - } - } - removeNonBubblingListener(element, event_name) { - const id = element.getAttribute("data-dioxus-id"); - delete this.local[id][event_name]; - if (Object.keys(this.local[id]).length === 0) { - delete this.local[id]; - } - element.removeEventListener(event_name, this.handler); - } - removeAllNonBubblingListeners(element) { - const id = element.getAttribute("data-dioxus-id"); - delete this.local[id]; - } - getNode(id) { - return this.nodes[id]; - } - appendChildren(id, many) { - const root = this.nodes[id]; - const els = this.stack.splice(this.stack.length - many); - for (let k = 0;k < many; k++) { - root.appendChild(els[k]); - } - } - loadChild(ptr, len) { - let node = this.stack[this.stack.length - 1]; - let ptr_end = ptr + len; - for (;ptr < ptr_end; ptr++) { - let end = this.m.getUint8(ptr); - for (node = node.firstChild;end > 0; end--) { - node = node.nextSibling; - } - } - return node; - } - saveTemplate(nodes, tmpl_id) { - this.templates[tmpl_id] = nodes; - } - hydrateRoot(ids) { - const hydrateNodes = document.querySelectorAll("[data-node-hydration]"); - for (let i = 0;i < hydrateNodes.length; i++) { - const hydrateNode = hydrateNodes[i]; - const hydration = hydrateNode.getAttribute("data-node-hydration"); - const split = hydration.split(","); - const id = ids[parseInt(split[0])]; - this.nodes[id] = hydrateNode; - if (split.length > 1) { - hydrateNode.listening = split.length - 1; - hydrateNode.setAttribute("data-dioxus-id", id.toString()); - for (let j = 1;j < split.length; j++) { - const listener = split[j]; - const split2 = listener.split(":"); - const event_name = split2[0]; - const bubbles = split2[1] === "1"; - this.createListener(event_name, hydrateNode, bubbles); - } - } - } - const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT); - let currentNode = treeWalker.nextNode(); - while (currentNode) { - const id = currentNode.textContent; - const split = id.split("node-id"); - if (split.length > 1) { - this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling; - } - currentNode = treeWalker.nextNode(); - } - } - setAttributeInner(node, field, value, ns) { - setAttributeInner(node, field, value, ns); - } -} -export { - BaseInterpreter -}; +function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){if(this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},handler)this.handler=handler}createListener(event_name,element,bubbles){if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrateRoot(ids){const hydrateNodes=document.querySelectorAll("[data-node-hydration]");for(let i=0;i1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j1)this.nodes[ids[parseInt(split[1])]]=currentNode.nextSibling;currentNode=treeWalker.nextNode()}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter}; diff --git a/packages/interpreter/src/js/hash.txt b/packages/interpreter/src/js/hash.txt index bfd4a2268..4ab3240ae 100644 --- a/packages/interpreter/src/js/hash.txt +++ b/packages/interpreter/src/js/hash.txt @@ -1 +1 @@ -13140749608285001344 \ No newline at end of file +6653358969285642234 \ No newline at end of file diff --git a/packages/interpreter/src/js/native.js b/packages/interpreter/src/js/native.js index 0bd145a16..a281625d9 100644 --- a/packages/interpreter/src/js/native.js +++ b/packages/interpreter/src/js/native.js @@ -1,422 +1 @@ -// src/ts/form.ts -function retriveValues(event, target) { - let contents = { - values: {} - }; - let form = target.closest("form"); - if (form) { - if (event.type === "input" || event.type === "change" || event.type === "submit" || event.type === "reset" || event.type === "click") { - contents = retrieveFormValues(form); - } - } - return contents; -} -function retrieveFormValues(form) { - const formData = new FormData(form); - const contents = {}; - formData.forEach((value, key) => { - if (contents[key]) { - contents[key] += "," + value; - } else { - contents[key] = value; - } - }); - return { - valid: form.checkValidity(), - values: contents - }; -} -function retriveSelectValue(target) { - let options = target.selectedOptions; - let values = []; - for (let i = 0;i < options.length; i++) { - values.push(options[i].value); - } - return values; -} - -// src/ts/serialize.ts -function serializeEvent(event, target) { - let contents = {}; - let extend = (obj) => contents = { ...contents, ...obj }; - if (event instanceof WheelEvent) { - extend(serializeWheelEvent(event)); - } - if (event instanceof MouseEvent) { - extend(serializeMouseEvent(event)); - } - if (event instanceof KeyboardEvent) { - extend(serializeKeyboardEvent(event)); - } - if (event instanceof InputEvent) { - extend(serializeInputEvent(event, target)); - } - if (event instanceof PointerEvent) { - extend(serializePointerEvent(event)); - } - if (event instanceof AnimationEvent) { - extend(serializeAnimationEvent(event)); - } - if (event instanceof TransitionEvent) { - extend({ property_name: event.propertyName, elapsed_time: event.elapsedTime, pseudo_element: event.pseudoElement }); - } - if (event instanceof CompositionEvent) { - extend({ data: event.data }); - } - if (event instanceof DragEvent) { - extend(serializeDragEvent(event)); - } - if (event instanceof FocusEvent) { - extend({}); - } - if (event instanceof ClipboardEvent) { - extend({}); - } - if (typeof TouchEvent !== "undefined" && event instanceof TouchEvent) { - extend(serializeTouchEvent(event)); - } - if (event.type === "submit" || event.type === "reset" || event.type === "click" || event.type === "change" || event.type === "input") { - extend(serializeInputEvent(event, target)); - } - if (event instanceof DragEvent) { - } - return contents; -} -var serializeInputEvent = function(event, target) { - let contents = {}; - if (target instanceof HTMLElement) { - let values = retriveValues(event, target); - contents.values = values.values; - contents.valid = values.valid; - } - if (event.target instanceof HTMLInputElement) { - let target2 = event.target; - let value = target2.value ?? target2.textContent ?? ""; - if (target2.type === "checkbox") { - value = target2.checked ? "true" : "false"; - } else if (target2.type === "radio") { - value = target2.value; - } - contents.value = value; - } - if (event.target instanceof HTMLTextAreaElement) { - contents.value = event.target.value; - } - if (event.target instanceof HTMLSelectElement) { - contents.value = retriveSelectValue(event.target).join(","); - } - if (contents.value === undefined) { - contents.value = ""; - } - return contents; -}; -var serializeWheelEvent = function(event) { - return { - delta_x: event.deltaX, - delta_y: event.deltaY, - delta_z: event.deltaZ, - delta_mode: event.deltaMode - }; -}; -var serializeTouchEvent = function(event) { - return { - alt_key: event.altKey, - ctrl_key: event.ctrlKey, - meta_key: event.metaKey, - shift_key: event.shiftKey, - changed_touches: event.changedTouches, - target_touches: event.targetTouches, - touches: event.touches - }; -}; -var serializePointerEvent = function(event) { - return { - alt_key: event.altKey, - button: event.button, - buttons: event.buttons, - client_x: event.clientX, - client_y: event.clientY, - ctrl_key: event.ctrlKey, - meta_key: event.metaKey, - page_x: event.pageX, - page_y: event.pageY, - screen_x: event.screenX, - screen_y: event.screenY, - shift_key: event.shiftKey, - pointer_id: event.pointerId, - width: event.width, - height: event.height, - pressure: event.pressure, - tangential_pressure: event.tangentialPressure, - tilt_x: event.tiltX, - tilt_y: event.tiltY, - twist: event.twist, - pointer_type: event.pointerType, - is_primary: event.isPrimary - }; -}; -var serializeMouseEvent = function(event) { - return { - alt_key: event.altKey, - button: event.button, - buttons: event.buttons, - client_x: event.clientX, - client_y: event.clientY, - ctrl_key: event.ctrlKey, - meta_key: event.metaKey, - offset_x: event.offsetX, - offset_y: event.offsetY, - page_x: event.pageX, - page_y: event.pageY, - screen_x: event.screenX, - screen_y: event.screenY, - shift_key: event.shiftKey - }; -}; -var serializeKeyboardEvent = function(event) { - return { - char_code: event.charCode, - is_composing: event.isComposing, - key: event.key, - alt_key: event.altKey, - ctrl_key: event.ctrlKey, - meta_key: event.metaKey, - key_code: event.keyCode, - shift_key: event.shiftKey, - location: event.location, - repeat: event.repeat, - which: event.which, - code: event.code - }; -}; -var serializeAnimationEvent = function(event) { - return { - animation_name: event.animationName, - elapsed_time: event.elapsedTime, - pseudo_element: event.pseudoElement - }; -}; -var serializeDragEvent = function(event) { - return { - mouse: { - alt_key: event.altKey, - ctrl_key: event.ctrlKey, - meta_key: event.metaKey, - shift_key: event.shiftKey, - ...serializeMouseEvent(event) - }, - files: { - files: { - a: [1, 2, 3] - } - } - }; -}; - -// src/ts/native.ts -var getTargetId = function(target) { - if (!(target instanceof Node)) { - return null; - } - let ourTarget = target; - let realId = null; - while (realId == null) { - if (ourTarget === null) { - return null; - } - if (ourTarget instanceof Element) { - realId = ourTarget.getAttribute(`data-dioxus-id`); - } - ourTarget = ourTarget.parentNode; - } - return parseInt(realId); -}; -var JSChannel_; -if (RawInterpreter) { - JSChannel_ = RawInterpreter; -} - -class NativeInterpreter extends JSChannel_ { - intercept_link_redirects; - ipc; - editsPath; - liveview; - constructor(editsPath) { - super(); - this.editsPath = editsPath; - } - initialize(root) { - this.intercept_link_redirects = true; - this.liveview = false; - const dragEventHandler = (e) => { - }; - window.addEventListener("dragover", function(e) { - if (e.target instanceof Element && e.target.tagName != "INPUT") { - e.preventDefault(); - } - }, false); - window.addEventListener("drop", function(e) { - let target = e.target; - if (!(target instanceof Element)) { - return; - } - e.preventDefault(); - }, false); - window.addEventListener("click", (event) => { - const target = event.target; - if (target instanceof HTMLInputElement && target.getAttribute("type") === "file") { - let target_id = getTargetId(target); - if (target_id !== null) { - const message = this.serializeIpcMessage("file_dialog", { - event: "change&input", - accept: target.getAttribute("accept"), - directory: target.getAttribute("webkitdirectory") === "true", - multiple: target.hasAttribute("multiple"), - target: target_id, - bubbles: event.bubbles - }); - this.ipc.postMessage(message); - } - event.preventDefault(); - } - }); - this.ipc = window.ipc; - const handler = (event) => this.handleEvent(event, event.type, true); - super.initialize(root, handler); - } - serializeIpcMessage(method, params = {}) { - return JSON.stringify({ method, params }); - } - scrollTo(id, behavior) { - const node = this.nodes[id]; - if (node instanceof HTMLElement) { - node.scrollIntoView({ behavior }); - } - } - getClientRect(id) { - const node = this.nodes[id]; - if (node instanceof HTMLElement) { - const rect = node.getBoundingClientRect(); - return { - type: "GetClientRect", - origin: [rect.x, rect.y], - size: [rect.width, rect.height] - }; - } - } - setFocus(id, focus) { - const node = this.nodes[id]; - if (node instanceof HTMLElement) { - if (focus) { - node.focus(); - } else { - node.blur(); - } - } - } - loadChild(array) { - let node = this.stack[this.stack.length - 1]; - for (let i = 0;i < array.length; i++) { - let end = array[i]; - for (node = node.firstChild;end > 0; end--) { - node = node.nextSibling; - } - } - return node; - } - appendChildren(id, many) { - const root = this.nodes[id]; - const els = this.stack.splice(this.stack.length - many); - for (let k = 0;k < many; k++) { - root.appendChild(els[k]); - } - } - handleEvent(event, name, bubbles) { - const target = event.target; - const realId = getTargetId(target); - const contents = serializeEvent(event, target); - let body = { - name, - data: contents, - element: realId, - bubbles - }; - this.preventDefaults(event, target); - if (this.liveview) { - if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) { - if (target.getAttribute("type") === "file") { - this.readFiles(target, contents, bubbles, realId, name); - } - } - } else { - const message = this.serializeIpcMessage("user_event", body); - this.ipc.postMessage(message); - } - } - preventDefaults(event, target) { - let preventDefaultRequests = null; - if (target instanceof Element) { - preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`); - } - if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) { - event.preventDefault(); - } - if (event.type === "submit") { - event.preventDefault(); - } - if (target instanceof Element && event.type === "click") { - this.handleClickNavigate(event, target, preventDefaultRequests); - } - } - handleClickNavigate(event, target, preventDefaultRequests) { - if (!this.intercept_link_redirects) { - return; - } - if (target.tagName === "BUTTON" && event.type == "submit") { - event.preventDefault(); - } - let a_element = target.closest("a"); - if (a_element == null) { - return; - } - event.preventDefault(); - let elementShouldPreventDefault = preventDefaultRequests && preventDefaultRequests.includes(`onclick`); - let aElementShouldPreventDefault = a_element.getAttribute(`dioxus-prevent-default`); - let linkShouldPreventDefault = aElementShouldPreventDefault && aElementShouldPreventDefault.includes(`onclick`); - if (!elementShouldPreventDefault && !linkShouldPreventDefault) { - const href = a_element.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - this.ipc.postMessage(this.serializeIpcMessage("browser_open", { href })); - } - } - } - waitForRequest(headless) { - fetch(new Request(this.editsPath)).then((response) => response.arrayBuffer()).then((bytes) => { - if (headless) { - this.run_from_bytes(bytes); - } else { - requestAnimationFrame(() => this.run_from_bytes(bytes)); - } - this.waitForRequest(headless); - }); - } - async readFiles(target, contents, bubbles, realId, name) { - let files = target.files; - let file_contents = {}; - for (let i = 0;i < files.length; i++) { - const file = files[i]; - file_contents[file.name] = Array.from(new Uint8Array(await file.arrayBuffer())); - } - contents.files = { files: file_contents }; - const message = this.serializeIpcMessage("user_event", { - name, - element: realId, - data: contents, - bubbles - }); - this.ipc.postMessage(message); - } -} -export { - NativeInterpreter -}; +function retriveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key]+=","+value;else contents[key]=value}),{valid:form.checkValidity(),values:contents}}function retriveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;icontents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retriveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retriveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files:{files:{a:[1,2,3]}}}};var getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;editsPath;liveview;constructor(editsPath){super();this.editsPath=editsPath}initialize(root){this.intercept_link_redirects=!0,this.liveview=!1,window.addEventListener("dragover",function(e){if(e.target instanceof Element&&e.target.tagName!="INPUT")e.preventDefault()},!1),window.addEventListener("drop",function(e){if(!(e.target instanceof Element))return;e.preventDefault()},!1),window.addEventListener("click",(event)=>{const target=event.target;if(target instanceof HTMLInputElement&&target.getAttribute("type")==="file"){let target_id=getTargetId(target);if(target_id!==null){const message=this.serializeIpcMessage("file_dialog",{event:"change&input",accept:target.getAttribute("accept"),directory:target.getAttribute("webkitdirectory")==="true",multiple:target.hasAttribute("multiple"),target:target_id,bubbles:event.bubbles});this.ipc.postMessage(message)}event.preventDefault()}}),this.ipc=window.ipc;const handler=(event)=>this.handleEvent(event,event.type,!0);super.initialize(root,handler)}serializeIpcMessage(method,params={}){return JSON.stringify({method,params})}scrollTo(id,behavior){const node=this.nodes[id];if(node instanceof HTMLElement)node.scrollIntoView({behavior})}getClientRect(id){const node=this.nodes[id];if(node instanceof HTMLElement){const rect=node.getBoundingClientRect();return{type:"GetClientRect",origin:[rect.x,rect.y],size:[rect.width,rect.height]}}}setFocus(id,focus){const node=this.nodes[id];if(node instanceof HTMLElement)if(focus)node.focus();else node.blur()}loadChild(array){let node=this.stack[this.stack.length-1];for(let i=0;i0;end--)node=node.nextSibling}return node}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;kresponse.arrayBuffer()).then((bytes)=>{if(headless)this.run_from_bytes(bytes);else requestAnimationFrame(()=>this.run_from_bytes(bytes));this.waitForRequest(headless)})}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i { - // e.preventDefault(); - // e.dataTransfer.effectAllowed = 'none'; - // e.dataTransfer.dropEffect = 'none'; - - // - - // we need to signal to the host to provide a native file path, - // not the one coming from here - - // We can't get native - // if (e.type === "drop") { - // let target = e.target; - - // if (target instanceof Element) { - // let target_id = getTargetId(target); - // if (target_id !== null) { - // const message = this.serializeIpcMessage("file_drop", { - // event: "drop", - // target: target_id, - // data: Array.from(e.dataTransfer.files).map(file => { - // return { - // name: file.name, - // type: file.type, - // size: file.size, - // }; - // }), - // }); - - // this.ipc.postMessage(message); - // } - // } - // } - }; - - - // ["dragenter", "dragover", "drop"].forEach((event) => { - // window.addEventListener(event, dragEventHandler, false) - // }); - // attach an event listener on the body that prevents file drops from navigating // this is because the browser will try to navigate to the file if it's dropped on the window window.addEventListener("dragover", function (e) { diff --git a/packages/interpreter/src/unified_bindings.rs b/packages/interpreter/src/unified_bindings.rs index 9ab7cfd00..f4d4acf3f 100644 --- a/packages/interpreter/src/unified_bindings.rs +++ b/packages/interpreter/src/unified_bindings.rs @@ -28,7 +28,7 @@ impl Interpreter { /// Convert the interpreter to its baseclass, giving pub fn base(&self) -> &BaseInterpreter { use wasm_bindgen::prelude::JsCast; - &self.js_channel().unchecked_ref() + self.js_channel().unchecked_ref() } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index d949263f5..e47634505 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -54,11 +54,9 @@ mod rehydrate; /// /// # Example /// -/// ```rust, ignore -/// fn main() { -/// let app_fut = dioxus_web::run_with_props(App, RootProps { name: String::from("joe") }); -/// wasm_bindgen_futures::spawn_local(app_fut); -/// } +/// ```ignore, rust +/// let app_fut = dioxus_web::run_with_props(App, RootProps { name: String::from("foo") }); +/// wasm_bindgen_futures::spawn_local(app_fut); /// ``` pub async fn run(virtual_dom: VirtualDom, web_config: Config) { tracing::info!("Starting up"); @@ -66,7 +64,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) { let mut dom = virtual_dom; #[cfg(feature = "eval")] - dom.in_runtime(|| eval::init_eval()); + dom.in_runtime(eval::init_eval); #[cfg(feature = "panic_hook")] if web_config.default_panic_hook {