Kaynağa Gözat

Less clumsy configuration for desktop and mobile (#553)

* chore: dont use prebuilt builder pattern for configuring desktop

* chore: use regular config pattern for web

* Chore: update docs too

* chore: clean up some warnings
Jon Kelley 2 yıl önce
ebeveyn
işleme
540e785d8b

+ 0 - 5
.vscode/settings.json

@@ -1,9 +1,4 @@
 {
-  "rust-analyzer.cargo.allFeatures": true,
-  "rust-analyzer.cargo.features": [
-    "desktop",
-    "router"
-  ],
   "editor.formatOnSave": true,
   "[toml]": {
     "editor.formatOnSave": false

+ 8 - 12
docs/guide/examples/component_props_options.rs

@@ -60,10 +60,10 @@ struct OptionalProps<'a> {
 }
 
 fn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {
-    return cx.render(rsx!(h1{
+    cx.render(rsx!(h1{
         "{cx.props.title}: ",
-        [cx.props.subtitle.unwrap_or("No subtitle provided")],
-    }));
+        cx.props.subtitle.unwrap_or("No subtitle provided"),
+    }))
 }
 // ANCHOR_END: OptionalProps
 
@@ -76,10 +76,10 @@ struct ExplicitOptionProps<'a> {
 }
 
 fn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {
-    return cx.render(rsx!(h1{
+    cx.render(rsx!(h1 {
         "{cx.props.title}: ",
-        [cx.props.subtitle.unwrap_or("No subtitle provided")],
-    }));
+        cx.props.subtitle.unwrap_or("No subtitle provided"),
+    }))
 }
 // ANCHOR_END: ExplicitOption
 
@@ -92,9 +92,7 @@ struct DefaultProps {
 }
 
 fn DefaultComponent(cx: Scope<DefaultProps>) -> Element {
-    return cx.render(rsx!(h1{
-        "{cx.props.number}",
-    }));
+    cx.render(rsx!(h1 { "{cx.props.number}" }))
 }
 // ANCHOR_END: DefaultComponent
 
@@ -106,8 +104,6 @@ struct IntoProps {
 }
 
 fn IntoComponent(cx: Scope<IntoProps>) -> Element {
-    return cx.render(rsx!(h1{
-        "{cx.props.string}",
-    }));
+    cx.render(rsx!(h1 { "{cx.props.string}" }))
 }
 // ANCHOR_END: IntoComponent

+ 1 - 1
docs/guide/examples/hooks_bad.rs

@@ -55,7 +55,7 @@ fn App(cx: Scope) -> Element {
     }
 
     // ✅ Instead, use a hashmap with use_ref
-    let selection_map = use_ref(&cx, || HashMap::<&str, bool>::new());
+    let selection_map = use_ref(&cx, HashMap::<&str, bool>::new);
 
     for name in &names {
         let is_selected = selection_map.read()[name];

+ 1 - 1
docs/guide/examples/hooks_composed.rs

@@ -8,6 +8,6 @@ struct AppSettings {}
 
 // ANCHOR: wrap_context
 fn use_settings(cx: &ScopeState) -> UseSharedState<AppSettings> {
-    use_context::<AppSettings>(&cx).expect("App settings not provided")
+    use_context::<AppSettings>(cx).expect("App settings not provided")
 }
 // ANCHOR_END: wrap_context

+ 1 - 1
docs/guide/examples/hooks_use_ref.rs

@@ -7,7 +7,7 @@ fn main() {
 
 // ANCHOR: component
 fn App(cx: Scope) -> Element {
-    let list = use_ref(&cx, || Vec::new());
+    let list = use_ref(&cx, Vec::new);
     let list_formatted = format!("{:?}", *list.read());
 
     cx.render(rsx!(

+ 1 - 1
docs/guide/examples/meme_editor_dark_mode.rs

@@ -37,7 +37,7 @@ pub fn App(cx: Scope) -> Element {
 
 pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
     // ANCHOR: use_context
-    let dark_mode_context = use_context::<DarkMode>(&cx);
+    let dark_mode_context = use_context::<DarkMode>(cx);
     // ANCHOR_END: use_context
 
     dark_mode_context

+ 2 - 2
docs/guide/examples/rendering_lists.rs

@@ -14,9 +14,9 @@ struct Comment {
 
 pub fn App(cx: Scope) -> Element {
     // ANCHOR: render_list
-    let comment_field = use_state(&cx, || String::new());
+    let comment_field = use_state(&cx, String::new);
     let mut next_id = use_state(&cx, || 0);
-    let comments = use_ref(&cx, || Vec::<Comment>::new());
+    let comments = use_ref(&cx, Vec::<Comment>::new);
 
     let comments_lock = comments.read();
     let comments_rendered = comments_lock.iter().map(|comment| {

+ 8 - 8
examples/calculator.rs

@@ -6,16 +6,16 @@ This calculator version uses React-style state management. All state is held as
 use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;
 use dioxus::prelude::*;
+use dioxus_desktop::{Config, WindowBuilder};
 
 fn main() {
-    use dioxus_desktop::tao::dpi::LogicalSize;
-    dioxus_desktop::launch_cfg(app, |cfg| {
-        cfg.with_window(|w| {
-            w.with_title("Calculator Demo")
-                .with_resizable(false)
-                .with_inner_size(LogicalSize::new(320.0, 530.0))
-        })
-    });
+    let config = Config::new().with_window(
+        WindowBuilder::default()
+            .with_title("Calculator")
+            .with_inner_size(dioxus_desktop::LogicalSize::new(300.0, 500.0)),
+    );
+
+    dioxus_desktop::launch_cfg(app, config);
 }
 
 fn app(cx: Scope) -> Element {

+ 10 - 7
examples/custom_html.rs

@@ -2,14 +2,17 @@
 //! to add things like stylesheets, scripts, and third-party JS libraries.
 
 use dioxus::prelude::*;
+use dioxus_desktop::Config;
 
 fn main() {
-    dioxus_desktop::launch_cfg(app, |c| {
-        c.with_custom_head("<style>body { background-color: red; }</style>".into())
-    });
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()),
+    );
 
-    dioxus_desktop::launch_cfg(app, |c| {
-        c.with_custom_index(
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new().with_custom_index(
             r#"
 <!DOCTYPE html>
 <html>
@@ -24,8 +27,8 @@ fn main() {
 </html>
         "#
             .into(),
-        )
-    });
+        ),
+    );
 }
 
 fn app(cx: Scope) -> Element {

+ 5 - 1
examples/file_explorer.rs

@@ -9,9 +9,13 @@
 //! we dont need to clutter our code with `read` commands.
 
 use dioxus::prelude::*;
+use dioxus_desktop::{Config, WindowBuilder};
 
 fn main() {
-    dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_resizable(true)));
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new().with_window(WindowBuilder::new().with_resizable(true)),
+    );
 }
 
 fn app(cx: Scope) -> Element {

+ 6 - 5
examples/filedragdrop.rs

@@ -1,12 +1,13 @@
 use dioxus::prelude::*;
+use dioxus_desktop::Config;
 
 fn main() {
-    dioxus_desktop::launch_with_props(app, (), |c| {
-        c.with_file_drop_handler(|_w, e| {
-            println!("{:?}", e);
-            true
-        })
+    let cfg = Config::new().with_file_drop_handler(|_w, e| {
+        println!("{:?}", e);
+        true
     });
+
+    dioxus_desktop::launch_with_props(app, (), cfg);
 }
 
 fn app(cx: Scope) -> Element {

+ 9 - 8
examples/flat_router.rs

@@ -1,17 +1,18 @@
 use dioxus::prelude::*;
-use dioxus_desktop::tao::dpi::LogicalSize;
+use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder};
 use dioxus_router::{Link, Route, Router};
 
 fn main() {
     env_logger::init();
 
-    dioxus_desktop::launch_cfg(app, |c| {
-        c.with_window(|c| {
-            c.with_title("Spinsense Client")
-                .with_inner_size(LogicalSize::new(600, 1000))
-                .with_resizable(false)
-        })
-    })
+    let cfg = Config::new().with_window(
+        WindowBuilder::new()
+            .with_title("Spinsense Client")
+            .with_inner_size(LogicalSize::new(600, 1000))
+            .with_resizable(false),
+    );
+
+    dioxus_desktop::launch_cfg(app, cfg)
 }
 
 fn app(cx: Scope) -> Element {

+ 2 - 1
examples/hydration.rs

@@ -10,12 +10,13 @@
 //! proof-of-concept for the hydration feature, but you'll probably only want to use hydration for the web.
 
 use dioxus::prelude::*;
+use dioxus_desktop::Config;
 
 fn main() {
     let vdom = VirtualDom::new(app);
     let content = dioxus_ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
 
-    dioxus_desktop::launch_cfg(app, |c| c.with_prerendered(content));
+    dioxus_desktop::launch_cfg(app, Config::new().with_prerendered(content));
 }
 
 fn app(cx: Scope) -> Element {

+ 9 - 7
examples/pattern_model.rs

@@ -21,15 +21,17 @@ use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;
 use dioxus::prelude::*;
 use dioxus_desktop::wry::application::dpi::LogicalSize;
+use dioxus_desktop::{Config, WindowBuilder};
 
 fn main() {
-    dioxus_desktop::launch_cfg(app, |cfg| {
-        cfg.with_window(|w| {
-            w.with_title("Calculator Demo")
-                .with_resizable(false)
-                .with_inner_size(LogicalSize::new(320.0, 530.0))
-        })
-    });
+    let cfg = Config::new().with_window(
+        WindowBuilder::new()
+            .with_title("Calculator Demo")
+            .with_resizable(false)
+            .with_inner_size(LogicalSize::new(320.0, 530.0)),
+    );
+
+    dioxus_desktop::launch_cfg(app, cfg);
 }
 
 fn app(cx: Scope) -> Element {

+ 8 - 7
examples/suspense.rs

@@ -14,15 +14,16 @@
 //! primitives in our own custom components.
 
 use dioxus::prelude::*;
+use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
 
 fn main() {
-    use dioxus_desktop::tao::dpi::LogicalSize;
-    dioxus_desktop::launch_cfg(app, |cfg| {
-        cfg.with_window(|w| {
-            w.with_title("Doggo Fetcher")
-                .with_inner_size(LogicalSize::new(600.0, 800.0))
-        })
-    });
+    let cfg = Config::new().with_window(
+        WindowBuilder::new()
+            .with_title("Doggo Fetcher")
+            .with_inner_size(LogicalSize::new(600.0, 800.0)),
+    );
+
+    dioxus_desktop::launch_cfg(app, cfg);
 }
 
 #[derive(serde::Deserialize)]

+ 6 - 3
examples/tailwind.rs

@@ -9,11 +9,14 @@
 //!     https://dev.to/arctic_hen7/how-to-set-up-tailwind-css-with-yew-and-trunk-il9
 
 use dioxus::prelude::*;
+use dioxus_desktop::Config;
 
 fn main() {
-    dioxus_desktop::launch_cfg(app, |c| {
-        c.with_custom_head("<script src=\"https://cdn.tailwindcss.com\"></script>".to_string())
-    });
+    dioxus_desktop::launch_cfg(
+        app,
+        Config::new()
+            .with_custom_head("<script src=\"https://cdn.tailwindcss.com\"></script>".to_string()),
+    );
 }
 
 pub fn app(cx: Scope) -> Element {

+ 2 - 2
examples/todomvc.rs

@@ -7,14 +7,14 @@ fn main() {
     dioxus_desktop::launch(app);
 }
 
-#[derive(Eq, PartialEq)]
+#[derive(PartialEq, Eq)]
 pub enum FilterState {
     All,
     Active,
     Completed,
 }
 
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 pub struct TodoItem {
     pub id: u32,
     pub checked: bool,

+ 8 - 3
examples/window_event.rs

@@ -1,9 +1,14 @@
 use dioxus::prelude::*;
+use dioxus_desktop::{Config, WindowBuilder};
 
 fn main() {
-    dioxus_desktop::launch_cfg(app, |cfg| {
-        cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false))
-    });
+    let cfg = Config::new().with_window(
+        WindowBuilder::new()
+            .with_title("Borderless Window")
+            .with_decorations(false),
+    );
+
+    dioxus_desktop::launch_cfg(app, cfg);
 }
 
 fn app(cx: Scope) -> Element {

+ 19 - 32
packages/desktop/src/cfg.rs

@@ -11,9 +11,9 @@ use wry::{
 // pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
 
 /// The configuration for the desktop application.
-pub struct DesktopConfig {
+pub struct Config {
     pub(crate) window: WindowBuilder,
-    pub(crate) file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
+    pub(crate) file_drop_handler: Option<DropHandler>,
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) pre_rendered: Option<String>,
     // pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
@@ -23,12 +23,14 @@ pub struct DesktopConfig {
     pub(crate) custom_index: Option<String>,
 }
 
+type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
+
 pub(crate) type WryProtocol = (
     String,
     Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>,
 );
 
-impl DesktopConfig {
+impl Config {
     /// Initializes a new `WindowBuilder` with default values.
     #[inline]
     pub fn new() -> Self {
@@ -48,57 +50,51 @@ impl DesktopConfig {
     }
 
     /// set the directory from which assets will be searched in release mode
-    pub fn with_resource_directory(&mut self, path: impl Into<PathBuf>) -> &mut Self {
+    pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {
         self.resource_dir = Some(path.into());
         self
     }
 
     /// Set whether or not the right-click context menu should be disabled.
-    pub fn with_disable_context_menu(&mut self, disable: bool) -> &mut Self {
+    pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
         self.disable_context_menu = disable;
         self
     }
 
     /// Set the pre-rendered HTML content
-    pub fn with_prerendered(&mut self, content: String) -> &mut Self {
+    pub fn with_prerendered(mut self, content: String) -> Self {
         self.pre_rendered = Some(content);
         self
     }
 
     /// Set the configuration for the window.
-    pub fn with_window(
-        &mut self,
-        configure: impl FnOnce(WindowBuilder) -> WindowBuilder,
-    ) -> &mut Self {
+    pub fn with_window(mut self, window: WindowBuilder) -> Self {
         // gots to do a swap because the window builder only takes itself as muy self
         // I wish more people knew about returning &mut Self
-        let mut builder = WindowBuilder::default().with_title("Dioxus App");
-        std::mem::swap(&mut self.window, &mut builder);
-        builder = configure(builder);
-        std::mem::swap(&mut self.window, &mut builder);
+        self.window = window;
         self
     }
 
     // /// Set a custom event handler
     // pub fn with_event_handler(
-    //     &mut self,
+    //     mut self,
     //     handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static,
-    // ) -> &mut Self {
+    // ) -> Self {
     //     self.event_handler = Some(Box::new(handler));
     //     self
     // }
 
     /// Set a file drop handler
     pub fn with_file_drop_handler(
-        &mut self,
+        mut self,
         handler: impl Fn(&Window, FileDropEvent) -> bool + 'static,
-    ) -> &mut Self {
+    ) -> Self {
         self.file_drop_handler = Some(Box::new(handler));
         self
     }
 
     /// Set a custom protocol
-    pub fn with_custom_protocol<F>(&mut self, name: String, handler: F) -> &mut Self
+    pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
     where
         F: Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static,
     {
@@ -107,7 +103,7 @@ impl DesktopConfig {
     }
 
     /// Set a custom icon for this application
-    pub fn with_icon(&mut self, icon: Icon) -> &mut Self {
+    pub fn with_icon(mut self, icon: Icon) -> Self {
         self.window.window.window_icon = Some(icon);
         self
     }
@@ -115,7 +111,7 @@ impl DesktopConfig {
     /// Inject additional content into the document's HEAD.
     ///
     /// This is useful for loading CSS libraries, JS libraries, etc.
-    pub fn with_custom_head(&mut self, head: String) -> &mut Self {
+    pub fn with_custom_head(mut self, head: String) -> Self {
         self.custom_head = Some(head);
         self
     }
@@ -126,22 +122,13 @@ impl DesktopConfig {
     ///
     /// Dioxus injects some loader code into the closing body tag. Your document
     /// must include a body element!
-    pub fn with_custom_index(&mut self, index: String) -> &mut Self {
+    pub fn with_custom_index(mut self, index: String) -> Self {
         self.custom_index = Some(index);
         self
     }
 }
 
-impl DesktopConfig {
-    pub(crate) fn with_default_icon(mut self) -> Self {
-        let bin: &[u8] = include_bytes!("./assets/default_icon.bin");
-        let rgba = Icon::from_rgba(bin.to_owned(), 460, 460).expect("image parse failed");
-        self.window.window.window_icon = Some(rgba);
-        self
-    }
-}
-
-impl Default for DesktopConfig {
+impl Default for Config {
     fn default() -> Self {
         Self::new()
     }

+ 17 - 14
packages/desktop/src/lib.rs

@@ -18,10 +18,11 @@ pub use wry;
 pub use wry::application as tao;
 
 use crate::events::trigger_from_serialized;
-pub use cfg::DesktopConfig;
+pub use cfg::Config;
 use controller::DesktopController;
 use dioxus_core::*;
 use events::parse_ipc_message;
+pub use tao::dpi::{LogicalSize, PhysicalSize};
 pub use tao::window::WindowBuilder;
 use tao::{
     event::{Event, StartCause, WindowEvent},
@@ -48,7 +49,7 @@ use wry::webview::WebViewBuilder;
 /// }
 /// ```
 pub fn launch(root: Component) {
-    launch_with_props(root, (), |c| c)
+    launch_with_props(root, (), Config::default())
 }
 
 /// Launch the WebView and run the event loop, with configuration.
@@ -70,10 +71,7 @@ pub fn launch(root: Component) {
 ///     })
 /// }
 /// ```
-pub fn launch_cfg(
-    root: Component,
-    config_builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
-) {
+pub fn launch_cfg(root: Component, config_builder: Config) {
     launch_with_props(root, (), config_builder)
 }
 
@@ -100,19 +98,24 @@ pub fn launch_cfg(
 ///     })
 /// }
 /// ```
-pub fn launch_with_props<P: 'static + Send>(
-    root: Component<P>,
-    props: P,
-    builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
-) {
-    let mut cfg = DesktopConfig::default().with_default_icon();
-    builder(&mut cfg);
-
+pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
     let event_loop = EventLoop::with_user_event();
 
     let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
     let proxy = event_loop.create_proxy();
 
+    // We assume that if the icon is None, then the user just didnt set it
+    if cfg.window.window.window_icon.is_none() {
+        cfg.window.window.window_icon = Some(
+            tao::window::Icon::from_rgba(
+                include_bytes!("./assets/default_icon.bin").to_vec(),
+                460,
+                460,
+            )
+            .expect("image parse failed"),
+        );
+    }
+
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 

+ 3 - 3
packages/router/src/service.rs

@@ -87,11 +87,11 @@ impl RouterCore {
 
         let route = match &cfg.initial_url {
             Some(url) => Arc::new(ParsedRoute {
-                url: Url::from_str(&url).expect(
-                    format!(
+                url: Url::from_str(url).unwrap_or_else(|_|
+                    panic!(
                         "RouterCfg expects a valid initial_url, but got '{}'. Example: '{{scheme}}://{{?authority}}/{{?path}}'",
                         &url
-                    ).as_str()
+                    )
                 ),
                 title: None,
                 serialized_state: None,

+ 1 - 1
packages/tui/src/style_attributes.rs

@@ -143,7 +143,7 @@ impl Default for BorderEdge {
     }
 }
 
-#[derive(Clone, Copy, PartialEq, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub enum BorderStyle {
     Dotted,
     Dashed,

+ 2 - 1
packages/web/examples/hydrate.rs

@@ -1,4 +1,5 @@
 use dioxus::prelude::*;
+use dioxus_web::Config;
 use web_sys::window;
 
 fn app(cx: Scope) -> Element {
@@ -59,5 +60,5 @@ fn main() {
         .set_inner_html(&pre);
 
     // now rehydtrate
-    dioxus_web::launch_with_props(app, (), |c| c.hydrate(true));
+    dioxus_web::launch_with_props(app, (), Config::new().hydrate(true));
 }

+ 24 - 7
packages/web/src/cfg.rs

@@ -5,32 +5,41 @@
 /// # Example
 ///
 /// ```rust, ignore
-/// dioxus_web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot"))
+/// dioxus_web::launch(App, Config::new().hydrate(true).root_name("myroot"))
 /// ```
-pub struct WebConfig {
+pub struct Config {
     pub(crate) hydrate: bool,
     pub(crate) rootname: String,
     pub(crate) cached_strings: Vec<String>,
+    pub(crate) default_panic_hook: bool,
 }
 
-impl Default for WebConfig {
+impl Default for Config {
     fn default() -> Self {
         Self {
             hydrate: false,
             rootname: "main".to_string(),
             cached_strings: Vec::new(),
+            default_panic_hook: true,
         }
     }
 }
 
-impl WebConfig {
+impl Config {
+    /// Create a new Default instance of the Config.
+    ///
+    /// This is no different than calling `Config::default()`
+    pub fn new() -> Self {
+        Self::default()
+    }
+
     /// Enable SSR hydration
     ///
     /// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async
     /// work and suspended nodes.
     ///
     /// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.
-    pub fn hydrate(&mut self, f: bool) -> &mut Self {
+    pub fn hydrate(mut self, f: bool) -> Self {
         self.hydrate = f;
         self
     }
@@ -38,7 +47,7 @@ impl WebConfig {
     /// Set the name of the element that Dioxus will use as the root.
     ///
     /// This is akint to calling React.render() on the element with the specified name.
-    pub fn rootname(&mut self, name: impl Into<String>) -> &mut Self {
+    pub fn rootname(mut self, name: impl Into<String>) -> Self {
         self.rootname = name.into();
         self
     }
@@ -46,8 +55,16 @@ impl WebConfig {
     /// Set the name of the element that Dioxus will use as the root.
     ///
     /// This is akint to calling React.render() on the element with the specified name.
-    pub fn with_string_cache(&mut self, cache: Vec<String>) -> &mut Self {
+    pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
         self.cached_strings = cache;
         self
     }
+
+    /// Set whether or not Dioxus should use the built-in panic hook or defer to your own.
+    ///
+    /// The panic hook is set to true normally so even the simplest apps have helpful error messages.
+    pub fn with_default_panic_hook(mut self, f: bool) -> Self {
+        self.default_panic_hook = f;
+        self
+    }
 }

+ 2 - 2
packages/web/src/dom.rs

@@ -15,7 +15,7 @@ use std::{any::Any, rc::Rc, sync::Arc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{Document, Element, Event, HtmlElement};
 
-use crate::WebConfig;
+use crate::Config;
 
 pub struct WebsysDom {
     pub interpreter: Interpreter,
@@ -26,7 +26,7 @@ pub struct WebsysDom {
 }
 
 impl WebsysDom {
-    pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
+    pub fn new(cfg: Config, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
         // eventually, we just want to let the interpreter do all the work of decoding events into our event type
         let callback: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
             let mut target = event

+ 13 - 18
packages/web/src/lib.rs

@@ -56,7 +56,7 @@
 
 use std::rc::Rc;
 
-pub use crate::cfg::WebConfig;
+pub use crate::cfg::Config;
 pub use crate::util::use_eval;
 use dioxus_core::prelude::Component;
 use dioxus_core::SchedulerMsg;
@@ -93,7 +93,7 @@ mod util;
 /// }
 /// ```
 pub fn launch(root_component: Component) {
-    launch_with_props(root_component, (), |c| c);
+    launch_with_props(root_component, (), Config::default());
 }
 
 /// Launch your app and run the event loop, with configuration.
@@ -106,7 +106,7 @@ pub fn launch(root_component: Component) {
 /// use dioxus::prelude::*;
 ///
 /// fn main() {
-///     dioxus_web::launch_with_props(App, |config| config.pre_render(true));
+///     dioxus_web::launch_with_props(App, Config::new().pre_render(true));
 /// }
 ///
 /// fn app(cx: Scope) -> Element {
@@ -115,8 +115,8 @@ pub fn launch(root_component: Component) {
 ///     })
 /// }
 /// ```
-pub fn launch_cfg(root: Component, config_builder: impl FnOnce(&mut WebConfig) -> &mut WebConfig) {
-    launch_with_props(root, (), config_builder)
+pub fn launch_cfg(root: Component, config: Config) {
+    launch_with_props(root, (), config)
 }
 
 /// Launches the VirtualDOM from the specified component function and props.
@@ -130,7 +130,7 @@ pub fn launch_cfg(root: Component, config_builder: impl FnOnce(&mut WebConfig) -
 ///     dioxus_web::launch_with_props(
 ///         App,
 ///         RootProps { name: String::from("joe") },
-///         |config| config
+///         Config::new()
 ///     );
 /// }
 ///
@@ -143,19 +143,10 @@ pub fn launch_cfg(root: Component, config_builder: impl FnOnce(&mut WebConfig) -
 ///     rsx!(cx, div {"hello {cx.props.name}"})
 /// }
 /// ```
-pub fn launch_with_props<T>(
-    root_component: Component<T>,
-    root_properties: T,
-    configuration_builder: impl FnOnce(&mut WebConfig) -> &mut WebConfig,
-) where
+pub fn launch_with_props<T>(root_component: Component<T>, root_properties: T, config: Config)
+where
     T: Send + 'static,
 {
-    if cfg!(feature = "panic_hook") {
-        console_error_panic_hook::set_once();
-    }
-
-    let mut config = WebConfig::default();
-    configuration_builder(&mut config);
     wasm_bindgen_futures::spawn_local(run_with_props(root_component, root_properties, config));
 }
 
@@ -171,9 +162,13 @@ pub fn launch_with_props<T>(
 ///     wasm_bindgen_futures::spawn_local(app_fut);
 /// }
 /// ```
-pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
+pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: Config) {
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
+    if cfg!(feature = "panic_hook") && cfg.default_panic_hook {
+        console_error_panic_hook::set_once();
+    }
+
     #[cfg(feature = "hot-reload")]
     hot_reload::init(&dom);
 

+ 2 - 2
packages/web/src/olddom.rs

@@ -16,7 +16,7 @@ use web_sys::{
     HtmlOptionElement, HtmlTextAreaElement, Node,
 };
 
-use crate::{nodeslab::NodeSlab, WebConfig};
+use crate::{nodeslab::NodeSlab, Config};
 
 pub struct WebsysDom {
     stack: Stack,
@@ -39,7 +39,7 @@ pub struct WebsysDom {
 type ListenerEntry = (usize, Closure<dyn FnMut(&Event)>);
 
 impl WebsysDom {
-    pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
+    pub fn new(cfg: Config, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
         let document = load_document();
 
         let nodes = NodeSlab::new(2000);

+ 2 - 1
packages/web/tests/hydrate.rs

@@ -1,4 +1,5 @@
 use dioxus::prelude::*;
+use dioxus_web::Config;
 use wasm_bindgen_test::wasm_bindgen_test;
 use web_sys::window;
 
@@ -59,5 +60,5 @@ fn rehydrates() {
         .unwrap()
         .set_inner_html(&format!("<div id='main'>{}</div>", out));
 
-    dioxus_web::launch_cfg(app, |c| c.hydrate(true));
+    dioxus_web::launch_cfg(app, Config::new().hydrate(true));
 }