diff --git a/.github/workflows/cli_release.yml b/.github/workflows/cli_release.yml new file mode 100644 index 000000000..c6c2b81e8 --- /dev/null +++ b/.github/workflows/cli_release.yml @@ -0,0 +1,54 @@ +name: Build CLI for Release + +# Will run automatically on every new release +on: + release: + types: [published] + +jobs: + build-and-upload: + permissions: + contents: write + runs-on: ${{ matrix.platform.os }} + strategy: + matrix: + platform: + - { + target: x86_64-pc-windows-msvc, + os: windows-latest, + toolchain: "1.70.0", + } + - { + target: x86_64-apple-darwin, + os: macos-latest, + toolchain: "1.70.0", + } + - { + target: x86_64-unknown-linux-gnu, + os: ubuntu-latest, + toolchain: "1.70.0", + } + steps: + - uses: actions/checkout@v3 + - name: Install stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.platform.toolchain }} + targets: ${{ matrix.platform.target }} + + # Setup the Github Actions Cache for the CLI package + - name: Setup cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: packages/cli -> ../../target + + # This neat action can build and upload the binary in one go! + - name: Build and upload binary + uses: taiki-e/upload-rust-binary-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + target: ${{ matrix.platform.target }} + bin: dx + archive: dx-${{ matrix.platform.target }} + checksum: sha256 + manifest_path: packages/cli/Cargo.toml diff --git a/README.md b/README.md index 52e4408d8..5b6a99ad3 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ | Examples | - Guide + Guide | 中文 | diff --git a/docs/guide/examples/readme_expanded.rs b/docs/guide/examples/readme_expanded.rs index 896d365e5..1ec30e57b 100644 --- a/docs/guide/examples/readme_expanded.rs +++ b/docs/guide/examples/readme_expanded.rs @@ -89,7 +89,8 @@ fn app(cx: Scope) -> Element { key: None, // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled template: std::cell::Cell::new(TEMPLATE), - root_ids: Default::default(), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump()) + .into(), dynamic_nodes: __cx.bump().alloc([ // The dynamic count text node (dynamic node id 0) __cx.text_node(format_args!("High-Five counter: {0}", count)), diff --git a/docs/guide/src/en/interactivity/event_handlers.md b/docs/guide/src/en/interactivity/event_handlers.md index 4c77cef1e..4e332adf9 100644 --- a/docs/guide/src/en/interactivity/event_handlers.md +++ b/docs/guide/src/en/interactivity/event_handlers.md @@ -51,6 +51,8 @@ Any event handlers will still be called. > Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event. +> Note about forms: if an event handler is attached to the `onsubmit` event of a form, default behavior is to **not submit it**, meaning having `prevent_default: "onsubmit"` will submit it in this case. + ## Handler Props Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler: diff --git a/docs/guide/src/pt-br/interactivity/event_handlers.md b/docs/guide/src/pt-br/interactivity/event_handlers.md index 12aad321e..8b3d757ef 100644 --- a/docs/guide/src/pt-br/interactivity/event_handlers.md +++ b/docs/guide/src/pt-br/interactivity/event_handlers.md @@ -64,3 +64,5 @@ Então, você pode usá-lo como qualquer outro manipulador: > Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada. > > Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData` + +> Nota sobre formulários: se um manipulador de evento está anexado ao evento `onsubmit` em um formulário, o comportamento padrão é de **não submetê-lo**. Portanto, especificar `prevent_default: "onsubmit"` irá submetê-lo. \ No newline at end of file diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 3d38bfc01..fee52a4f0 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -102,3 +102,9 @@ name = "dx" [dev-dependencies] tempfile = "3.3" + +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }" + +[package.metadata.binstall.overrides.x86_64-pc-windows-msvc] +pkg-fmt = "zip" diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 9d264c366..581e69326 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -47,6 +47,32 @@ impl Build { Platform::Desktop => { crate::builder::build_desktop(&crate_config, false)?; } + Platform::Fullstack => { + { + let mut web_config = crate_config.clone(); + let web_feature = self.build.client_feature; + let features = &mut web_config.features; + match features { + Some(features) => { + features.push(web_feature); + } + None => web_config.features = Some(vec![web_feature]), + }; + crate::builder::build(&crate_config, false)?; + } + { + let mut desktop_config = crate_config.clone(); + let desktop_feature = self.build.server_feature; + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + crate::builder::build_desktop(&desktop_config, false)?; + } + } } let temp = gen_page(&crate_config, false); diff --git a/packages/cli/src/cli/cfg.rs b/packages/cli/src/cli/cfg.rs index 2a441a1c8..bbe3119d3 100644 --- a/packages/cli/src/cli/cfg.rs +++ b/packages/cli/src/cli/cfg.rs @@ -35,6 +35,30 @@ pub struct ConfigOptsBuild { /// Space separated list of features to activate #[clap(long)] pub features: Option>, + + /// The feature to use for the client in a fullstack app [default: "web"] + #[clap(long, default_value_t = { "web".to_string() })] + pub client_feature: String, + + /// The feature to use for the server in a fullstack app [default: "ssr"] + #[clap(long, default_value_t = { "ssr".to_string() })] + pub server_feature: String, +} + +impl From for ConfigOptsBuild { + fn from(serve: ConfigOptsServe) -> Self { + Self { + target: serve.target, + release: serve.release, + verbose: serve.verbose, + example: serve.example, + profile: serve.profile, + platform: serve.platform, + features: serve.features, + client_feature: serve.client_feature, + server_feature: serve.server_feature, + } + } } #[derive(Clone, Debug, Default, Deserialize, Parser)] @@ -89,6 +113,14 @@ pub struct ConfigOptsServe { /// Space separated list of features to activate #[clap(long)] pub features: Option>, + + /// The feature to use for the client in a fullstack app [default: "web"] + #[clap(long, default_value_t = { "web".to_string() })] + pub client_feature: String, + + /// The feature to use for the server in a fullstack app [default: "ssr"] + #[clap(long, default_value_t = { "ssr".to_string() })] + pub server_feature: String, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)] @@ -99,6 +131,9 @@ pub enum Platform { #[clap(name = "desktop")] #[serde(rename = "desktop")] Desktop, + #[clap(name = "fullstack")] + #[serde(rename = "fullstack")] + Fullstack, } /// Config options for the bundling system. diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 774241b0f..6de136167 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -12,6 +12,7 @@ pub struct Serve { impl Serve { pub async fn serve(self, bin: Option) -> Result<()> { let mut crate_config = crate::CrateConfig::new(bin)?; + let serve_cfg = self.serve.clone(); // change the relase state. crate_config.with_hot_reload(self.serve.hot_reload); @@ -49,7 +50,10 @@ impl Serve { .await?; } cfg::Platform::Desktop => { - server::desktop::startup(crate_config.clone()).await?; + server::desktop::startup(crate_config.clone(), &serve_cfg).await?; + } + cfg::Platform::Fullstack => { + server::fullstack::startup(crate_config.clone(), &serve_cfg).await?; } } Ok(()) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 0cd21d1b6..80df74fa4 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -1,4 +1,5 @@ use crate::{ + cfg::ConfigOptsServe, server::{ output::{print_console_info, PrettierOptions}, setup_file_watcher, setup_file_watcher_hot_reload, @@ -19,7 +20,16 @@ use tokio::sync::broadcast::{self}; #[cfg(feature = "plugin")] use plugin::PluginManager; -pub async fn startup(config: CrateConfig) -> Result<()> { +use super::Platform; + +pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> { + startup_with_platform::(config, serve).await +} + +pub(crate) async fn startup_with_platform( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { // ctrl-c shutdown checker let _crate_config = config.clone(); let _ = ctrlc::set_handler(move || { @@ -29,17 +39,19 @@ pub async fn startup(config: CrateConfig) -> Result<()> { }); match config.hot_reload { - true => serve_hot_reload(config).await?, - false => serve_default(config).await?, + true => serve_hot_reload::

(config, serve).await?, + false => serve_default::

(config, serve).await?, } Ok(()) } /// Start the server without hot reload -pub async fn serve_default(config: CrateConfig) -> Result<()> { - let (child, first_build_result) = start_desktop(&config)?; - let currently_running_child: RwLock = RwLock::new(child); +async fn serve_default( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { + let platform = RwLock::new(P::start(&config, serve)?); log::info!("🚀 Starting development server..."); @@ -49,49 +61,29 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { { let config = config.clone(); - move || { - let mut current_child = currently_running_child.write().unwrap(); - current_child.kill()?; - let (child, result) = start_desktop(&config)?; - *current_child = child; - Ok(result) - } + move || platform.write().unwrap().rebuild(&config) }, &config, None, ) .await?; - // Print serve info - print_console_info( - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - None, - ); - std::future::pending::<()>().await; Ok(()) } -/// Start the server without hot reload - /// Start dx serve with hot reload -pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { - let (_, first_build_result) = start_desktop(&config)?; - - println!("🚀 Starting development server..."); +async fn serve_hot_reload( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { + let platform = RwLock::new(P::start(&config, serve)?); // Setup hot reload let FileMapBuildResult { map, errors } = FileMap::::create(config.crate_dir.clone()).unwrap(); - println!("🚀 Starting development server..."); - for err in errors { log::error!("{}", err); } @@ -119,24 +111,13 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { for channel in &mut *channels.lock().unwrap() { send_msg(HotReloadMsg::Shutdown, channel); } - Ok(start_desktop(&config)?.1) + platform.write().unwrap().rebuild(&config) } }, None, ) .await?; - // Print serve info - print_console_info( - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - None, - ); - clear_paths(); match LocalSocketListener::bind("@dioxusin") { @@ -228,7 +209,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { } } -pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { +fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { // Run the desktop application let result = crate::builder::build_desktop(config, true)?; @@ -249,3 +230,37 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { } } } + +pub(crate) struct DesktopPlatform { + currently_running_child: Child, +} + +impl Platform for DesktopPlatform { + fn start(config: &CrateConfig, _serve: &ConfigOptsServe) -> Result { + let (child, first_build_result) = start_desktop(config)?; + + log::info!("🚀 Starting development server..."); + + // Print serve info + print_console_info( + config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + None, + ); + + Ok(Self { + currently_running_child: child, + }) + } + + fn rebuild(&mut self, config: &CrateConfig) -> Result { + self.currently_running_child.kill()?; + let (child, result) = start_desktop(config)?; + self.currently_running_child = child; + Ok(result) + } +} diff --git a/packages/cli/src/server/fullstack/mod.rs b/packages/cli/src/server/fullstack/mod.rs new file mode 100644 index 000000000..16ab31c44 --- /dev/null +++ b/packages/cli/src/server/fullstack/mod.rs @@ -0,0 +1,72 @@ +use crate::{ + cfg::{ConfigOptsBuild, ConfigOptsServe}, + CrateConfig, Result, +}; + +use super::{desktop, Platform}; + +pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> { + desktop::startup_with_platform::(config, serve).await +} + +struct FullstackPlatform { + serve: ConfigOptsServe, + desktop: desktop::DesktopPlatform, +} + +impl Platform for FullstackPlatform { + fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result + where + Self: Sized, + { + { + build_web(serve.clone())?; + } + + let mut desktop_config = config.clone(); + let desktop_feature = serve.server_feature.clone(); + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + let desktop = desktop::DesktopPlatform::start(&desktop_config, serve)?; + + Ok(Self { + desktop, + serve: serve.clone(), + }) + } + + fn rebuild(&mut self, crate_config: &CrateConfig) -> Result { + build_web(self.serve.clone())?; + { + let mut desktop_config = crate_config.clone(); + let desktop_feature = self.serve.server_feature.clone(); + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + self.desktop.rebuild(&desktop_config) + } + } +} + +fn build_web(serve: ConfigOptsServe) -> Result<()> { + let mut web_config: ConfigOptsBuild = serve.into(); + let web_feature = web_config.client_feature.clone(); + let features = &mut web_config.features; + match features { + Some(features) => { + features.push(web_feature); + } + None => web_config.features = Some(vec![web_feature]), + }; + web_config.platform = Some(crate::cfg::Platform::Web); + crate::cli::build::Build { build: web_config }.build(None) +} diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 19dc44b75..1571a198e 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -1,4 +1,4 @@ -use crate::{BuildResult, CrateConfig, Result}; +use crate::{cfg::ConfigOptsServe, BuildResult, CrateConfig, Result}; use cargo_metadata::diagnostic::Diagnostic; use dioxus_core::Template; @@ -14,6 +14,7 @@ use tokio::sync::broadcast::Sender; mod output; use output::*; pub mod desktop; +pub mod fullstack; pub mod web; /// Sets up a file watcher @@ -180,3 +181,10 @@ async fn setup_file_watcher_hot_reload Result + Send + ' Ok(watcher) } + +pub(crate) trait Platform { + fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result + where + Self: Sized; + fn rebuild(&mut self, config: &CrateConfig) -> Result; +} diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index e08d2297a..7f09dca6c 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -5,7 +5,7 @@ use crate::{ output::{print_console_info, PrettierOptions, WebServerInfo}, setup_file_watcher, setup_file_watcher_hot_reload, }, - BuildResult, CrateConfig, Result, + BuildResult, CrateConfig, Result, WebHttpsConfig, }; use axum::{ body::{Full, HttpBody}, @@ -67,7 +67,7 @@ pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Res } /// Start the server without hot reload -pub async fn serve_default( +async fn serve_default( ip: String, port: u16, config: CrateConfig, @@ -128,7 +128,7 @@ pub async fn serve_default( } /// Start dx serve with hot reload -pub async fn serve_hot_reload( +async fn serve_hot_reload( ip: String, port: u16, config: CrateConfig, @@ -218,67 +218,12 @@ async fn get_rustls(config: &CrateConfig) -> Result> { return Ok(None); } - let (cert_path, key_path) = match web_config.mkcert { + let (cert_path, key_path) = if let Some(true) = web_config.mkcert { // mkcert, use it - Some(true) => { - // Get paths to store certs, otherwise use ssl/item.pem - let key_path = web_config - .key_path - .clone() - .unwrap_or(DEFAULT_KEY_PATH.to_string()); - - let cert_path = web_config - .cert_path - .clone() - .unwrap_or(DEFAULT_CERT_PATH.to_string()); - - // Create ssl directory if using defaults - if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH { - _ = fs::create_dir("ssl"); - } - - let cmd = Command::new("mkcert") - .args([ - "-install", - "-key-file", - &key_path, - "-cert-file", - &cert_path, - "localhost", - "::1", - "127.0.0.1", - ]) - .spawn(); - - match cmd { - Err(e) => { - match e.kind() { - io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."), - e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()), - }; - return Err("failed to generate mkcert certificates".into()); - } - Ok(mut cmd) => { - cmd.wait()?; - } - } - - (cert_path, key_path) - } - // not mkcert - Some(false) => { - // get paths to cert & key - if let (Some(key), Some(cert)) = - (web_config.key_path.clone(), web_config.cert_path.clone()) - { - (cert, key) - } else { - // missing cert or key - return Err("https is enabled but cert or key path is missing".into()); - } - } - // other - _ => return Ok(None), + get_rustls_with_mkcert(web_config)? + } else { + // if mkcert not specified or false, don't use it + get_rustls_without_mkcert(web_config)? }; Ok(Some( @@ -286,6 +231,62 @@ async fn get_rustls(config: &CrateConfig) -> Result> { )) } +fn get_rustls_with_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> { + // Get paths to store certs, otherwise use ssl/item.pem + let key_path = web_config + .key_path + .clone() + .unwrap_or(DEFAULT_KEY_PATH.to_string()); + + let cert_path = web_config + .cert_path + .clone() + .unwrap_or(DEFAULT_CERT_PATH.to_string()); + + // Create ssl directory if using defaults + if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH { + _ = fs::create_dir("ssl"); + } + + let cmd = Command::new("mkcert") + .args([ + "-install", + "-key-file", + &key_path, + "-cert-file", + &cert_path, + "localhost", + "::1", + "127.0.0.1", + ]) + .spawn(); + + match cmd { + Err(e) => { + match e.kind() { + io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."), + e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()), + }; + return Err("failed to generate mkcert certificates".into()); + } + Ok(mut cmd) => { + cmd.wait()?; + } + } + + Ok((cert_path, key_path)) +} + +fn get_rustls_without_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> { + // get paths to cert & key + if let (Some(key), Some(cert)) = (web_config.key_path.clone(), web_config.cert_path.clone()) { + Ok((cert, key)) + } else { + // missing cert or key + Err("https is enabled but cert or key path is missing".into()) + } +} + /// Sets up and returns a router async fn setup_router( config: CrateConfig, diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 7fbc8a9c8..a61a8cace 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -87,8 +87,12 @@ impl<'b> VirtualDom { } } - // Intialize the root nodes slice - *node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()]; + // Initialize the root nodes slice + { + let mut nodes_mut = node.root_ids.borrow_mut(); + let len = node.template.get().roots.len(); + nodes_mut.resize(len, ElementId::default()); + }; // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 270c81730..dd4cf8147 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -15,6 +15,7 @@ use DynamicNode::*; impl<'b> VirtualDom { pub(super) fn diff_scope(&mut self, scope: ScopeId) { + self.runtime.scope_stack.borrow_mut().push(scope); let scope_state = &mut self.get_scope(scope).unwrap(); unsafe { // Load the old and new bump arenas @@ -45,6 +46,7 @@ impl<'b> VirtualDom { (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]), }; } + self.runtime.scope_stack.borrow_mut().pop(); } fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) { @@ -126,7 +128,13 @@ impl<'b> VirtualDom { }); // Make sure the roots get transferred over while we're here - *right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone(); + { + let mut right = right_template.root_ids.borrow_mut(); + right.clear(); + for &element in left_template.root_ids.borrow().iter() { + right.push(element); + } + } let root_ids = right_template.root_ids.borrow(); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a108f3577..2399c16b8 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -54,7 +54,7 @@ pub struct VNode<'a> { /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom - pub root_ids: RefCell>, + pub root_ids: RefCell>, /// The dynamic parts of the template pub dynamic_nodes: &'a [DynamicNode<'a>], @@ -65,11 +65,11 @@ pub struct VNode<'a> { impl<'a> VNode<'a> { /// Create a template with no nodes that will be skipped over during diffing - pub fn empty() -> Element<'a> { + pub fn empty(cx: &'a ScopeState) -> Element<'a> { Some(VNode { key: None, parent: None, - root_ids: Default::default(), + root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), dynamic_nodes: &[], dynamic_attrs: &[], template: Cell::new(Template { @@ -698,7 +698,7 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { impl<'a, 'b> IntoDynNode<'b> for &'a str { fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { DynamicNode::Text(VText { - value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(), + value: cx.bump().alloc_str(self), id: Default::default(), }) } @@ -741,10 +741,10 @@ impl<'a> IntoTemplate<'a> for VNode<'a> { } } impl<'a> IntoTemplate<'a> for Element<'a> { - fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> { + fn into_template(self, cx: &'a ScopeState) -> VNode<'a> { match self { - Some(val) => val.into_template(_cx), - _ => VNode::empty().unwrap(), + Some(val) => val.into_template(cx), + _ => VNode::empty(cx).unwrap(), } } } diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 40bb5dcbf..1523c4e22 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -179,7 +179,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { node_paths: &[&[0]], attr_paths: &[], }), - root_ids: Default::default(), + root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(), dynamic_nodes: cx.bump().alloc([cx.component( create_random_element, DepthProps { depth, root: false }, @@ -276,7 +276,7 @@ fn create_random_element(cx: Scope) -> Element { key: None, parent: None, template: Cell::new(template), - root_ids: Default::default(), + root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(), dynamic_nodes: { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 019d55942..b900b9dc6 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -282,6 +282,20 @@ impl UseSharedState { ), } } + + /// Take a reference to the inner value temporarily and produce a new value + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] + pub fn with(&self, immutable_callback: impl FnOnce(&T) -> O) -> O { + immutable_callback(&*self.read()) + } + + /// Take a mutable reference to the inner value temporarily and produce a new value + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] + pub fn with_mut(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O { + mutable_callback(&mut *self.write()) + } } impl Clone for UseSharedState { diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 1fe59a640..fab13482b 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Debug}; +use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; @@ -45,6 +45,12 @@ impl FileEngine for SerializedFileEngine { .await .map(|bytes| String::from_utf8_lossy(&bytes).to_string()) } + + async fn get_native_file(&self, file: &str) -> Option> { + self.read_file(file) + .await + .map(|val| Box::new(val) as Box) + } } #[cfg(feature = "serialize")] @@ -89,6 +95,9 @@ pub trait FileEngine { // read a file to string async fn read_file_to_string(&self, file: &str) -> Option; + + // returns a file in platform's native representation + async fn get_native_file(&self, file: &str) -> Option>; } impl_event! { diff --git a/packages/html/src/native_bind/native_file_engine.rs b/packages/html/src/native_bind/native_file_engine.rs index e3291fa36..88b996a4d 100644 --- a/packages/html/src/native_bind/native_file_engine.rs +++ b/packages/html/src/native_bind/native_file_engine.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::path::PathBuf; use crate::FileEngine; @@ -40,4 +41,9 @@ impl FileEngine for NativeFileEngine { Some(contents) } + + async fn get_native_file(&self, file: &str) -> Option> { + let file = File::open(file).await.ok()?; + Some(Box::new(file)) + } } diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 1d1bd881c..4f0a34b70 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -187,7 +187,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { node_paths: &[&[0]], attr_paths: &[], }), - root_ids: Default::default(), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(), dynamic_nodes: cx.bump().alloc([cx.component( create_random_element, DepthProps { depth, root: false }, @@ -257,7 +257,8 @@ fn create_random_element(cx: Scope) -> Element { key: None, parent: None, template: Cell::new(template), - root_ids: Default::default(), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()) + .into(), dynamic_nodes: { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 783dd3322..f52ecad0e 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -231,6 +231,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { // Render and release the mutable borrow on context let roots = quote! { #( #root_printer ),* }; + let root_count = self.roots.len(); let node_printer = &context.dynamic_nodes; let dyn_attr_printer = &context.dynamic_attributes; let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*])); @@ -247,7 +248,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { parent: None, key: #key_tokens, template: std::cell::Cell::new(TEMPLATE), - root_ids: Default::default(), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(), dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]), dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]), } diff --git a/packages/web/src/file_engine.rs b/packages/web/src/file_engine.rs index 6552be084..f98f726e2 100644 --- a/packages/web/src/file_engine.rs +++ b/packages/web/src/file_engine.rs @@ -1,3 +1,5 @@ +use std::any::Any; + use dioxus_html::FileEngine; use futures_channel::oneshot; use js_sys::Uint8Array; @@ -100,4 +102,25 @@ impl FileEngine for WebFileEngine { None } } + + async fn get_native_file(&self, file: &str) -> Option> { + let file = self.find(file)?; + Some(Box::new(file)) + } +} + +/// Helper trait for WebFileEngine +#[async_trait::async_trait(?Send)] +pub trait WebFileEngineExt { + /// returns web_sys::File + async fn get_web_file(&self, file: &str) -> Option; +} + +#[async_trait::async_trait(?Send)] +impl WebFileEngineExt for std::sync::Arc { + async fn get_web_file(&self, file: &str) -> Option { + let native_file = self.get_native_file(file).await?; + let ret = native_file.downcast::().ok()?; + Some(*ret) + } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 8bea4a460..261232607 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -54,6 +54,7 @@ // - Do DOM work in the next requestAnimationFrame callback pub use crate::cfg::Config; +pub use crate::file_engine::WebFileEngineExt; use dioxus_core::{Element, Scope, VirtualDom}; use futures_util::{ future::{select, Either},