瀏覽代碼

clean out router

Adrian Wannenmacher 2 年之前
父節點
當前提交
998f9b9ec9

+ 2 - 43
packages/router/Cargo.toml

@@ -11,50 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus = { path = "../dioxus" }
-futures-channel = "0.3.21"
-url = { version = "2.2.2", default-features = false }
+dioxus = { path="../dioxus" }
+dioxus-router-core = { path = "../router-core"}
 
 # for wasm
-web-sys = { version = "0.3", features = [
-    "Attr",
-    "Document",
-    "History",
-    "HtmlBaseElement",
-    "Event",
-    "NamedNodeMap",
-    "Url",
-    "UrlSearchParams",
-    "Window",
-    "Location",
-], optional = true }
-wasm-bindgen = { version = "0.2", optional = true }
-js-sys = { version = "0.3", optional = true }
-gloo-events = { version = "0.1.1", optional = true }
-log = "0.4.14"
-thiserror = "1.0.30"
-futures-util = "0.3.21"
-serde = { version = "1", optional = true }
-serde_urlencoded = { version = "0.7.1", optional = true }
 
 [features]
-default = ["query"]
-web = ["web-sys", "gloo-events", "js-sys", "wasm-bindgen"]
-query = ["serde", "serde_urlencoded"]
-wasm_test = []
-hot-reload = ["dioxus/hot-reload"]
-
-[dev-dependencies]
-console_error_panic_hook = "0.1.7"
-log = "0.4.14"
-wasm-logger = "0.2.0"
-wasm-bindgen-test = "0.3"
-gloo-utils = "0.1.2"
-dioxus-web = { path = "../web" }
-
-[target.wasm32-unknown-unknown.dev-dependencies]
-dioxus-router = { path = ".", features = ["web"] }
-
-[dev-dependencies.web-sys]
-version = "0.3"
-features = ["Document"]

+ 0 - 44
packages/router/examples/simple.rs

@@ -1,44 +0,0 @@
-#![allow(non_snake_case)]
-
-use dioxus::prelude::*;
-use dioxus_router::*;
-
-fn main() {
-    dioxus_web::launch(app);
-}
-
-fn app(cx: Scope) -> Element {
-    cx.render(rsx! {
-        Router {
-            h1 { "Your app here" }
-            ul {
-                Link { to: "/", li { "home" } }
-                Link { to: "/blog", li { "blog" } }
-                Link { to: "/blog/tim", li { "tims' blog" } }
-                Link { to: "/blog/bill", li { "bills' blog" } }
-                Link { to: "/apples", li { "go to apples" } }
-            }
-            Route { to: "/", Home {} }
-            Route { to: "/blog/", BlogList {} }
-            Route { to: "/blog/:id/", BlogPost {} }
-            Route { to: "/oranges", "Oranges are not apples!" }
-            Redirect { from: "/apples", to: "/oranges" }
-        }
-    })
-}
-
-fn Home(cx: Scope) -> Element {
-    cx.render(rsx! { h1 { "Home" } })
-}
-
-fn BlogList(cx: Scope) -> Element {
-    cx.render(rsx! { div { "Blog List" } })
-}
-
-fn BlogPost(cx: Scope) -> Element {
-    let id = use_route(&cx).segment("id")?;
-
-    log::trace!("rendering blog post {}", id);
-
-    cx.render(rsx! { div { "{id:?}" } })
-}

+ 0 - 6
packages/router/src/cfg.rs

@@ -1,6 +0,0 @@
-#[derive(Default)]
-pub struct RouterCfg {
-    pub base_url: Option<String>,
-    pub active_class: Option<String>,
-    pub initial_url: Option<String>,
-}

+ 0 - 139
packages/router/src/components/link.rs

@@ -1,139 +0,0 @@
-use std::sync::Arc;
-
-use crate::{use_route, RouterCore};
-use dioxus::prelude::*;
-
-/// Props for the [`Link`](struct.Link.html) component.
-#[derive(Props)]
-pub struct LinkProps<'a> {
-    /// The route to link to. This can be a relative path, or a full URL.
-    ///
-    /// ```rust, ignore
-    /// // Absolute path
-    /// Link { to: "/home", "Go Home" }
-    ///
-    /// // Relative path
-    /// Link { to: "../", "Go Up" }
-    /// ```
-    pub to: &'a str,
-
-    /// Set the class of the inner link ['a'](https://www.w3schools.com/tags/tag_a.asp) element.
-    ///
-    /// This can be useful when styling the inner link element.
-    #[props(default, strip_option)]
-    pub class: Option<&'a str>,
-
-    /// Set the class added to the inner link when the current route is the same as the "to" route.
-    ///
-    /// To set all of the active classes inside a Router at the same time use the `active_class`
-    /// prop on the Router component. If both the Router prop as well as this prop are provided then
-    /// this one has precedence. By default set to `"active"`.
-    #[props(default, strip_option)]
-    pub active_class: Option<&'a str>,
-
-    /// Set the ID of the inner link ['a'](https://www.w3schools.com/tags/tag_a.asp) element.
-    ///
-    /// This can be useful when styling the inner link element.
-    #[props(default, strip_option)]
-    pub id: Option<&'a str>,
-
-    /// Set the title of the window after the link is clicked..
-    #[props(default, strip_option)]
-    pub title: Option<&'a str>,
-
-    /// Autodetect if a link is external or not.
-    ///
-    /// This is automatically set to `true` and will use http/https detection
-    #[props(default = true)]
-    pub autodetect: bool,
-
-    /// Is this link an external link?
-    #[props(default = false)]
-    pub external: bool,
-
-    /// New tab?
-    #[props(default = false)]
-    pub new_tab: bool,
-
-    /// Pass children into the `<a>` element
-    pub children: Element<'a>,
-}
-
-/// A component that renders a link to a route.
-///
-/// `Link` components are just [`<a>`](https://www.w3schools.com/tags/tag_a.asp) elements
-/// that link to different pages *within* your single-page app.
-///
-/// If you need to link to a resource outside of your app, then just use a regular
-/// `<a>` element directly.
-///
-/// # Examples
-///
-/// ```rust, ignore
-/// fn Header(cx: Scope) -> Element {
-///     cx.render(rsx!{
-///         Link { to: "/home", "Go Home" }
-///     })
-/// }
-/// ```
-pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| cx.consume_context::<Arc<RouterCore>>());
-
-    let LinkProps {
-        to,
-        class,
-        id,
-        title,
-        autodetect,
-        external,
-        new_tab,
-        children,
-        active_class,
-        ..
-    } = cx.props;
-
-    let is_http = to.starts_with("http") || to.starts_with("https");
-    let outerlink = (*autodetect && is_http) || *external;
-    let prevent_default = if outerlink { "" } else { "onclick" };
-
-    let active_class_name = match active_class {
-        Some(c) => (*c).into(),
-        None => {
-            let active_from_router = match svc {
-                Some(service) => service.cfg.active_class.clone(),
-                None => None,
-            };
-            active_from_router.unwrap_or_else(|| "active".into())
-        }
-    };
-
-    let route = use_route(&cx);
-    let url = route.url();
-    let path = url.path();
-    let active = path == cx.props.to;
-    let active_class = if active { active_class_name } else { "".into() };
-
-    cx.render(rsx! {
-        a {
-            href: "{to}",
-            class: format_args!("{} {}", class.unwrap_or(""), active_class),
-            id: format_args!("{}", id.unwrap_or("")),
-            title: format_args!("{}", title.unwrap_or("")),
-            prevent_default: "{prevent_default}",
-            target: format_args!("{}", if * new_tab { "_blank" } else { "" }),
-            onclick: move |_| {
-                if !outerlink {
-                    if let Some(service) = svc {
-                        service.push_route(to, cx.props.title.map(|f| f.to_string()), None);
-                    } else {
-                        log::error!(
-                            "Attempted to create a Link to {} outside of a Router context", cx.props
-                            .to,
-                        );
-                    }
-                }
-            },
-            children
-        }
-    })
-}

+ 0 - 51
packages/router/src/components/redirect.rs

@@ -1,51 +0,0 @@
-use dioxus::prelude::*;
-
-use crate::use_router;
-
-/// The props for the [`Router`](fn.Router.html) component.
-#[derive(Props)]
-pub struct RedirectProps<'a> {
-    /// The route to link to. This can be a relative path, or a full URL.
-    ///
-    /// ```rust, ignore
-    /// // Absolute path
-    /// Redirect { from: "", to: "/home" }
-    ///
-    /// // Relative path
-    /// Redirect { from: "", to: "../" }
-    /// ```
-    pub to: &'a str,
-
-    /// The route to link from. This can be a relative path, or a full URL.
-    ///
-    /// ```rust, ignore
-    /// // Absolute path
-    /// Redirect { from: "", to: "/home" }
-    ///
-    /// // Relative path
-    /// Redirect { from: "", to: "../" }
-    /// ```
-    pub from: Option<&'a str>,
-}
-
-/// If this component is rendered, it will redirect the user to the given route.
-///
-/// It will replace the current route rather than pushing the current one to the stack.
-pub fn Redirect<'a>(cx: Scope<'a, RedirectProps<'a>>) -> Element {
-    let router = use_router(&cx);
-
-    let immediate_redirect = cx.use_hook(|| {
-        if let Some(from) = cx.props.from {
-            router.register_total_route(from.to_string(), cx.scope_id());
-            false
-        } else {
-            true
-        }
-    });
-
-    if *immediate_redirect || router.should_render(cx.scope_id()) {
-        router.replace_route(cx.props.to, None, None);
-    }
-
-    None
-}

+ 0 - 60
packages/router/src/components/route.rs

@@ -1,60 +0,0 @@
-use dioxus::prelude::*;
-use std::sync::Arc;
-
-use crate::{RouteContext, RouterCore};
-
-/// Props for the [`Route`](struct.Route.html) component.
-#[derive(Props)]
-pub struct RouteProps<'a> {
-    /// The path to match.
-    pub to: &'a str,
-
-    /// The component to render when the path matches.
-    pub children: Element<'a>,
-}
-
-/// A component that conditionally renders children based on the current location.
-///
-/// # Example
-///
-///```rust, ignore
-/// rsx!(
-///     Router {
-///         Route { to: "/home", Home {} }
-///         Route { to: "/about", About {} }
-///         Route { to: "/Blog", Blog {} }
-///     }
-/// )
-/// ```
-pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
-    let router_root = cx
-        .use_hook(|| cx.consume_context::<Arc<RouterCore>>())
-        .as_ref()?;
-
-    cx.use_hook(|| {
-        // create a bigger, better, longer route if one above us exists
-        let total_route = match cx.consume_context::<RouteContext>() {
-            Some(ctx) => ctx.total_route,
-            None => cx.props.to.to_string(),
-        };
-
-        // provide our route context
-        let route_context = cx.provide_context(RouteContext {
-            declared_route: cx.props.to.to_string(),
-            total_route,
-        });
-
-        // submit our rout
-        router_root.register_total_route(route_context.total_route, cx.scope_id());
-    });
-
-    log::trace!("Checking Route: {:?}", cx.props.to);
-
-    if router_root.should_render(cx.scope_id()) {
-        log::trace!("Route should render: {:?}", cx.scope_id());
-        cx.render(rsx!(&cx.props.children))
-    } else {
-        log::trace!("Route should *not* render: {:?}", cx.scope_id());
-        None
-    }
-}

+ 0 - 60
packages/router/src/components/router.rs

@@ -1,60 +0,0 @@
-use crate::{cfg::RouterCfg, RouterCore};
-use dioxus::prelude::*;
-use std::sync::Arc;
-
-/// The props for the [`Router`](fn.Router.html) component.
-#[derive(Props)]
-pub struct RouterProps<'a> {
-    /// The routes and elements that should be rendered when the path matches.
-    ///
-    /// If elements are not contained within Routes, the will be rendered
-    /// regardless of the path.
-    pub children: Element<'a>,
-
-    /// The URL to point at
-    ///
-    /// This will be used to trim any latent segments from the URL when your app is
-    /// not deployed to the root of the domain.
-    pub base_url: Option<&'a str>,
-
-    /// Hook into the router when the route is changed.
-    ///
-    /// This lets you easily implement redirects
-    #[props(default)]
-    pub onchange: EventHandler<'a, Arc<RouterCore>>,
-
-    /// Set the active class of all Link components contained in this router.
-    ///
-    /// This is useful if you don't want to repeat the same `active_class` prop value in every Link.
-    /// By default set to `"active"`.
-    pub active_class: Option<&'a str>,
-
-    /// Set the initial url.
-    pub initial_url: Option<String>,
-}
-
-/// A component that conditionally renders children based on the current location of the app.
-///
-/// Uses BrowserRouter in the browser and HashRouter everywhere else.
-///
-/// Will fallback to HashRouter is BrowserRouter is not available, or through configuration.
-#[allow(non_snake_case)]
-pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| {
-        cx.provide_context(RouterCore::new(
-            &cx,
-            RouterCfg {
-                base_url: cx.props.base_url.map(|s| s.to_string()),
-                active_class: cx.props.active_class.map(|s| s.to_string()),
-                initial_url: cx.props.initial_url.clone(),
-            },
-        ))
-    });
-
-    // next time we run the rout_found will be filled
-    if svc.route_found.get().is_none() {
-        cx.props.onchange.call(svc.clone());
-    }
-
-    cx.render(rsx!(&cx.props.children))
-}

+ 0 - 0
packages/router/src/error.rs


+ 0 - 126
packages/router/src/hooks/use_route.rs

@@ -1,126 +0,0 @@
-use crate::{ParsedRoute, RouteContext, RouterCore, RouterService};
-use dioxus::core::{ScopeId, ScopeState};
-use std::{borrow::Cow, str::FromStr, sync::Arc};
-use url::Url;
-
-/// This hook provides access to information about the current location in the
-/// context of a [`Router`]. If this function is called outside of a `Router`
-/// component it will panic.
-pub fn use_route(cx: &ScopeState) -> &UseRoute {
-    let handle = cx.use_hook(|| {
-        let router = cx
-            .consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component");
-
-        let route_context = cx.consume_context::<RouteContext>();
-
-        router.subscribe_onchange(cx.scope_id());
-
-        UseRouteListener {
-            state: UseRoute {
-                route_context,
-                route: router.current_location(),
-            },
-            router,
-            scope: cx.scope_id(),
-        }
-    });
-
-    handle.state.route = handle.router.current_location();
-
-    &handle.state
-}
-
-/// A handle to the current location of the router.
-pub struct UseRoute {
-    pub(crate) route: Arc<ParsedRoute>,
-
-    /// If `use_route` is used inside a `Route` component this has some context otherwise `None`.
-    pub(crate) route_context: Option<RouteContext>,
-}
-
-impl UseRoute {
-    /// Get the underlying [`Url`] of the current location.
-    pub fn url(&self) -> &Url {
-        &self.route.url
-    }
-
-    /// Get the first query parameter given the parameter name.
-    ///
-    /// If you need to get more than one parameter, use [`query_pairs`] on the [`Url`] instead.
-    #[cfg(feature = "query")]
-    pub fn query<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
-        let query = self.url().query()?;
-        serde_urlencoded::from_str(query).ok()
-    }
-
-    /// Get the first query parameter given the parameter name.
-    ///
-    /// If you need to get more than one parameter, use [`query_pairs`] on the [`Url`] instead.
-    pub fn query_param(&self, param: &str) -> Option<Cow<str>> {
-        self.route
-            .url
-            .query_pairs()
-            .find(|(k, _)| k == param)
-            .map(|(_, v)| v)
-    }
-
-    /// Returns the nth segment in the path. Paths that end with a slash have
-    /// the slash removed before determining the segments. If the path has
-    /// fewer segments than `n` then this method returns `None`.
-    pub fn nth_segment(&self, n: usize) -> Option<&str> {
-        self.route.url.path_segments()?.nth(n)
-    }
-
-    /// Returns the last segment in the path. Paths that end with a slash have
-    /// the slash removed before determining the segments. The root path, `/`,
-    /// will return an empty string.
-    pub fn last_segment(&self) -> Option<&str> {
-        self.route.url.path_segments()?.last()
-    }
-
-    /// Get the named parameter from the path, as defined in your router. The
-    /// value will be parsed into the type specified by `T` by calling
-    /// `value.parse::<T>()`. This method returns `None` if the named
-    /// parameter does not exist in the current path.
-    pub fn segment(&self, name: &str) -> Option<&str> {
-        let total_route = match self.route_context {
-            None => self.route.url.path(),
-            Some(ref ctx) => &ctx.total_route,
-        };
-
-        let index = total_route
-            .trim_start_matches('/')
-            .split('/')
-            .position(|segment| segment.starts_with(':') && &segment[1..] == name)?;
-
-        self.route.url.path_segments()?.nth(index)
-    }
-
-    /// Get the named parameter from the path, as defined in your router. The
-    /// value will be parsed into the type specified by `T` by calling
-    /// `value.parse::<T>()`. This method returns `None` if the named
-    /// parameter does not exist in the current path.
-    pub fn parse_segment<T>(&self, name: &str) -> Option<Result<T, T::Err>>
-    where
-        T: FromStr,
-    {
-        self.segment(name).map(|value| value.parse::<T>())
-    }
-}
-
-// The entire purpose of this struct is to unubscribe this component when it is unmounted.
-// The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe.
-// Instead, we hide the drop implementation on this private type exclusive to the hook,
-// and reveal our cached version of UseRoute to the component.
-struct UseRouteListener {
-    state: UseRoute,
-    router: Arc<RouterCore>,
-    scope: ScopeId,
-}
-
-impl Drop for UseRouteListener {
-    fn drop(&mut self) {
-        self.router.unsubscribe_onchange(self.scope)
-    }
-}

+ 0 - 10
packages/router/src/hooks/use_router.rs

@@ -1,10 +0,0 @@
-use crate::RouterService;
-use dioxus::core::ScopeState;
-
-/// This hook provides access to the `RouterService` for the app.
-pub fn use_router(cx: &ScopeState) -> &RouterService {
-    cx.use_hook(|| {
-        cx.consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component")
-    })
-}

+ 2 - 31
packages/router/src/lib.rs

@@ -1,32 +1,3 @@
-#![doc = include_str!("../README.md")]
-#![warn(missing_docs)]
-
-mod hooks {
-    mod use_route;
-    mod use_router;
-    pub use use_route::*;
-    pub use use_router::*;
+pub mod prelude {
+    pub use dioxus_router_core::prelude::*;
 }
-pub use hooks::*;
-
-mod components {
-    #![allow(non_snake_case)]
-
-    mod link;
-    mod redirect;
-    mod route;
-    mod router;
-
-    pub use link::*;
-    pub use redirect::*;
-    pub use route::*;
-    pub use router::*;
-}
-pub use components::*;
-
-mod cfg;
-mod routecontext;
-mod service;
-
-pub use routecontext::*;
-pub use service::*;

+ 0 - 24
packages/router/src/routecontext.rs

@@ -1,24 +0,0 @@
-/// A `RouteContext` is a context that is provided by [`Route`](fn.Route.html) components.
-///
-/// This signals to all child [`Route`] and [`Link`] components that they are
-/// currently nested under this route.
-#[derive(Debug, Clone)]
-pub struct RouteContext {
-    /// The `declared_route` is the sub-piece of the route that matches this pattern.
-    ///
-    ///
-    /// It follows this pattern:
-    /// ```ignore
-    /// "name/:id"
-    /// ```
-    pub declared_route: String,
-
-    /// The `total_route` is the full route that matches this pattern.
-    ///
-    ///
-    /// It follows this pattern:
-    /// ```ignore
-    /// "/level0/level1/:id"
-    /// ```
-    pub total_route: String,
-}

+ 0 - 441
packages/router/src/service.rs

@@ -1,441 +0,0 @@
-// todo: how does router work in multi-window contexts?
-// does each window have its own router? probably, lol
-
-use crate::cfg::RouterCfg;
-use dioxus::core::{ScopeId, ScopeState, VirtualDom};
-use std::any::Any;
-use std::sync::Weak;
-use std::{
-    cell::{Cell, RefCell},
-    collections::{HashMap, HashSet},
-    rc::Rc,
-    str::FromStr,
-    sync::Arc,
-};
-use url::Url;
-
-/// An abstraction over the platform's history API.
-///
-/// The history is denoted using web-like semantics, with forward slashes delmitiing
-/// routes and question marks denoting optional parameters.
-///
-/// This RouterService is exposed so you can modify the history directly. It
-/// does not provide a high-level ergonomic API for your components. Instead,
-/// you should consider using the components and hooks instead.
-/// - [`Route`](struct.Route.html)
-/// - [`Link`](struct.Link.html)
-/// - [`UseRoute`](struct.UseRoute.html)
-/// - [`Router`](struct.Router.html)
-///
-///
-/// # Example
-///
-/// ```rust, ignore
-/// let router = Router::new();
-/// router.push_route("/home/custom");
-/// cx.provide_context(router);
-/// ```
-///
-/// # Platform Specific
-///
-/// - On the web, this is a [`BrowserHistory`](https://docs.rs/gloo/0.3.0/gloo/history/struct.BrowserHistory.html).
-/// - On desktop, mobile, and SSR, this is just a Vec of Strings. Currently on
-///   desktop, there is no way to tap into forward/back for the app unless explicitly set.
-pub struct RouterCore {
-    pub(crate) route_found: Cell<Option<ScopeId>>,
-
-    pub(crate) stack: RefCell<Vec<Arc<ParsedRoute>>>,
-
-    pub(crate) slots: Rc<RefCell<HashMap<ScopeId, String>>>,
-
-    pub(crate) ordering: Rc<RefCell<Vec<ScopeId>>>,
-
-    pub(crate) onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
-
-    pub(crate) history: Box<dyn RouterProvider>,
-
-    pub(crate) regen_any_route: Arc<dyn Fn(ScopeId)>,
-
-    pub(crate) router_id: ScopeId,
-
-    pub(crate) cfg: RouterCfg,
-}
-
-/// A shared type for the RouterCore.
-pub type RouterService = Arc<RouterCore>;
-
-/// A route is a combination of window title, saved state, and a URL.
-#[derive(Debug, Clone)]
-pub struct ParsedRoute {
-    /// The URL of the route.
-    pub url: Url,
-
-    /// The title of the route.
-    pub title: Option<String>,
-
-    /// The serialized state of the route.
-    pub serialized_state: Option<String>,
-}
-
-impl RouterCore {
-    pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc<Self> {
-        #[cfg(feature = "web")]
-        let history = Box::new(web::new());
-
-        #[cfg(not(feature = "web"))]
-        let history = Box::new(hash::new());
-
-        let route = match &cfg.initial_url {
-            Some(url) => Arc::new(ParsedRoute {
-                url: Url::from_str(url).unwrap_or_else(|_|
-                    panic!(
-                        "RouterCfg expects a valid initial_url, but got '{}'. Example: '{{scheme}}://{{?authority}}/{{?path}}'",
-                        &url
-                    )
-                ),
-                title: None,
-                serialized_state: None,
-            }),
-            None => Arc::new(history.init_location()),
-        };
-
-        let svc = Arc::new(Self {
-            cfg,
-            regen_any_route: cx.schedule_update_any(),
-            router_id: cx.scope_id(),
-            route_found: Cell::new(None),
-            stack: RefCell::new(vec![route]),
-            ordering: Default::default(),
-            slots: Default::default(),
-            onchange_listeners: Default::default(),
-            history,
-        });
-
-        svc.history.attach_listeners(Arc::downgrade(&svc));
-
-        svc
-    }
-
-    /// Push a new route with no custom title or serialized state.
-    ///
-    /// This is a convenience method for easily navigating.
-    pub fn navigate_to(&self, route: &str) {
-        self.push_route(route, None, None);
-    }
-
-    /// Push a new route to the history.
-    ///
-    /// This will trigger a route change event.
-    ///
-    /// This does not modify the current route
-    pub fn push_route(&self, route: &str, title: Option<String>, serialized_state: Option<String>) {
-        let new_route = Arc::new(ParsedRoute {
-            url: self.current_location().url.join(route).ok().unwrap(),
-            title,
-            serialized_state,
-        });
-
-        self.history.push(&new_route);
-        self.stack.borrow_mut().push(new_route);
-
-        self.regen_routes();
-    }
-
-    /// Instead of pushing a new route, replaces the current route.
-    pub fn replace_route(
-        &self,
-        route: &str,
-        title: Option<String>,
-        serialized_state: Option<String>,
-    ) {
-        let new_route = Arc::new(ParsedRoute {
-            url: self.current_location().url.join(route).ok().unwrap(),
-            title,
-            serialized_state,
-        });
-
-        self.history.replace(&new_route);
-        *self.stack.borrow_mut().last_mut().unwrap() = new_route;
-
-        self.regen_routes();
-    }
-
-    /// Pop the current route from the history.
-    pub fn pop_route(&self) {
-        let mut stack = self.stack.borrow_mut();
-
-        if stack.len() > 1 {
-            stack.pop();
-        }
-
-        self.regen_routes();
-    }
-
-    /// Regenerate any routes that need to be regenerated, discarding the currently found route
-    ///
-    /// You probably don't need this method
-    pub fn regen_routes(&self) {
-        self.route_found.set(None);
-
-        (self.regen_any_route)(self.router_id);
-
-        for listener in self.onchange_listeners.borrow().iter() {
-            (self.regen_any_route)(*listener);
-        }
-
-        for route in self.ordering.borrow().iter().rev() {
-            (self.regen_any_route)(*route);
-        }
-    }
-
-    /// Get the current location of the Router
-    pub fn current_location(&self) -> Arc<ParsedRoute> {
-        self.stack.borrow().last().unwrap().clone()
-    }
-
-    /// Get the current native location of the Router
-    pub fn native_location<T: 'static>(&self) -> Option<Box<T>> {
-        self.history.native_location().downcast::<T>().ok()
-    }
-
-    /// Registers a scope to regenerate on route change.
-    ///
-    /// This is useful if you've built some abstraction on top of the router service.
-    pub fn subscribe_onchange(&self, id: ScopeId) {
-        self.onchange_listeners.borrow_mut().insert(id);
-    }
-
-    /// Unregisters a scope to regenerate on route change.
-    ///
-    /// This is useful if you've built some abstraction on top of the router service.
-    pub fn unsubscribe_onchange(&self, id: ScopeId) {
-        self.onchange_listeners.borrow_mut().remove(&id);
-    }
-
-    pub(crate) fn register_total_route(&self, route: String, scope: ScopeId) {
-        let clean = clean_route(route);
-        self.slots.borrow_mut().insert(scope, clean);
-        self.ordering.borrow_mut().push(scope);
-    }
-
-    pub(crate) fn should_render(&self, scope: ScopeId) -> bool {
-        if let Some(root_id) = self.route_found.get() {
-            return root_id == scope;
-        }
-
-        let roots = self.slots.borrow();
-
-        if let Some(route) = roots.get(&scope) {
-            let cur = &self.current_location().url;
-            log::trace!("Checking if {} matches {}", cur, route);
-
-            if route_matches_path(cur, route, self.cfg.base_url.as_ref()) || route.is_empty() {
-                self.route_found.set(Some(scope));
-                true
-            } else {
-                false
-            }
-        } else {
-            false
-        }
-    }
-}
-
-/// Get the router service from an existing VirtualDom.
-///
-/// Takes an optional target_scope parameter to specify the scope to use if ScopeId is not the component
-/// that owns the router.
-///
-/// This might change in the future.
-pub fn get_router_from_vdom(
-    dom: &VirtualDom,
-    target_scope: Option<ScopeId>,
-) -> Option<Arc<RouterCore>> {
-    dom.get_scope(target_scope.unwrap_or(ScopeId(0)))
-        .and_then(|scope| scope.consume_context::<Arc<RouterCore>>())
-}
-
-fn clean_route(route: String) -> String {
-    if route.as_str() == "/" {
-        return route;
-    }
-    route.trim_end_matches('/').to_string()
-}
-
-fn clean_path(path: &str) -> &str {
-    if path == "/" {
-        return path;
-    }
-    let sub = path.trim_end_matches('/');
-
-    if sub.starts_with('/') {
-        &path[1..]
-    } else {
-        sub
-    }
-}
-
-fn route_matches_path(cur: &Url, attempt: &str, base_url: Option<&String>) -> bool {
-    let cur_piece_iter = cur.path_segments().unwrap();
-
-    let mut cur_pieces = match base_url {
-        // baseurl is naive right now and doesn't support multiple nesting levels
-        Some(_) => cur_piece_iter.skip(1).collect::<Vec<_>>(),
-        None => cur_piece_iter.collect::<Vec<_>>(),
-    };
-
-    if attempt == "/" && cur_pieces.len() == 1 && cur_pieces[0].is_empty() {
-        return true;
-    }
-
-    // allow slashes at the end of the path
-    if cur_pieces.last() == Some(&"") {
-        cur_pieces.pop();
-    }
-
-    let attempt_pieces = clean_path(attempt).split('/').collect::<Vec<_>>();
-
-    if attempt_pieces.len() != cur_pieces.len() {
-        return false;
-    }
-
-    for (i, r) in attempt_pieces.iter().enumerate() {
-        // If this is a parameter then it matches as long as there's
-        // _any_thing in that spot in the path.
-        if r.starts_with(':') {
-            continue;
-        }
-
-        if cur_pieces[i] != *r {
-            return false;
-        }
-    }
-
-    true
-}
-
-pub(crate) trait RouterProvider {
-    fn push(&self, route: &ParsedRoute);
-    fn replace(&self, route: &ParsedRoute);
-    fn native_location(&self) -> Box<dyn Any>;
-    fn init_location(&self) -> ParsedRoute;
-    fn attach_listeners(&self, svc: Weak<RouterCore>);
-}
-
-#[cfg(not(feature = "web"))]
-mod hash {
-    use super::*;
-
-    pub fn new() -> HashRouter {
-        HashRouter {}
-    }
-
-    /// a simple cross-platform hash-based router
-    pub struct HashRouter {}
-
-    impl RouterProvider for HashRouter {
-        fn push(&self, _route: &ParsedRoute) {}
-
-        fn native_location(&self) -> Box<dyn Any> {
-            Box::new(())
-        }
-
-        fn init_location(&self) -> ParsedRoute {
-            ParsedRoute {
-                url: Url::parse("app:///").unwrap(),
-                title: None,
-                serialized_state: None,
-            }
-        }
-
-        fn replace(&self, _route: &ParsedRoute) {}
-
-        fn attach_listeners(&self, _svc: Weak<RouterCore>) {}
-    }
-}
-
-#[cfg(feature = "web")]
-mod web {
-    use super::RouterProvider;
-    use crate::ParsedRoute;
-
-    use gloo_events::EventListener;
-    use std::{any::Any, cell::Cell};
-    use web_sys::History;
-
-    pub struct WebRouter {
-        // keep it around so it drops when the router is dropped
-        _listener: Cell<Option<gloo_events::EventListener>>,
-
-        window: web_sys::Window,
-        history: History,
-    }
-
-    impl RouterProvider for WebRouter {
-        fn push(&self, route: &ParsedRoute) {
-            let ParsedRoute {
-                url,
-                title,
-                serialized_state,
-            } = route;
-
-            let _ = self.history.push_state_with_url(
-                &wasm_bindgen::JsValue::from_str(serialized_state.as_deref().unwrap_or("")),
-                title.as_deref().unwrap_or(""),
-                Some(url.as_str()),
-            );
-        }
-
-        fn replace(&self, route: &ParsedRoute) {
-            let ParsedRoute {
-                url,
-                title,
-                serialized_state,
-            } = route;
-
-            let _ = self.history.replace_state_with_url(
-                &wasm_bindgen::JsValue::from_str(serialized_state.as_deref().unwrap_or("")),
-                title.as_deref().unwrap_or(""),
-                Some(url.as_str()),
-            );
-        }
-
-        fn native_location(&self) -> Box<dyn Any> {
-            Box::new(self.window.location())
-        }
-
-        fn init_location(&self) -> ParsedRoute {
-            ParsedRoute {
-                url: url::Url::parse(&web_sys::window().unwrap().location().href().unwrap())
-                    .unwrap(),
-                title: web_sys::window()
-                    .unwrap()
-                    .document()
-                    .unwrap()
-                    .title()
-                    .into(),
-                serialized_state: None,
-            }
-        }
-
-        fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterCore>) {
-            self._listener.set(Some(EventListener::new(
-                &web_sys::window().unwrap(),
-                "popstate",
-                move |_| {
-                    if let Some(svc) = svc.upgrade() {
-                        svc.pop_route();
-                    }
-                },
-            )));
-        }
-    }
-
-    pub(crate) fn new() -> WebRouter {
-        WebRouter {
-            history: web_sys::window().unwrap().history().unwrap(),
-            window: web_sys::window().unwrap(),
-            _listener: Cell::new(None),
-        }
-    }
-}

+ 0 - 62
packages/router/tests/web_router.rs

@@ -1,62 +0,0 @@
-#![cfg(target_arch = "wasm32")]
-#![allow(non_snake_case)]
-
-use dioxus::prelude::*;
-use dioxus_router::*;
-use gloo_utils::document;
-use wasm_bindgen_test::*;
-
-wasm_bindgen_test_configure!(run_in_browser);
-
-#[wasm_bindgen_test]
-fn simple_test() {
-    fn main() {
-        console_error_panic_hook::set_once();
-        wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
-        dioxus_web::launch(APP);
-    }
-
-    static APP: Component = |cx| {
-        cx.render(rsx! {
-            Router {
-                onchange: move |route: RouterService| log::trace!("route changed to {:?}", route.current_location()),
-                active_class: "is-active",
-                Route { to: "/", Home {} }
-                Route { to: "blog"
-                    Route { to: "/", BlogList {} }
-                    Route { to: ":id", BlogPost {} }
-                }
-            }
-        })
-    };
-
-    fn Home(cx: Scope) -> Element {
-        cx.render(rsx! {
-            div {
-                h1 { "Home" }
-            }
-        })
-    }
-
-    fn BlogList(cx: Scope) -> Element {
-        cx.render(rsx! {
-            div {
-
-            }
-        })
-    }
-
-    fn BlogPost(cx: Scope) -> Element {
-        let _id = use_route(&cx).parse_segment::<usize>("id")?;
-
-        cx.render(rsx! {
-            div {
-
-            }
-        })
-    }
-
-    main();
-
-    let _ = document();
-}

+ 0 - 221
packages/router/usage.md

@@ -1,221 +0,0 @@
-
-## Usage
-
-Using Dioxus Router is pretty simple. Add a top-level Router to your app (not necessary but good practice) and then start adding routes, specifying the "to" field:
-
-```rust
-fn app() {
-    cx.render(rsx! {
-        Router {
-            Route { to: "/", Component {} },
-            Route { to: "/blog", Blog {} },
-            Route { to: "/about", Blog {} },
-            Route { to: "/contact", Blog {} },
-            Route { to: "/shop", Blog {} },
-        }
-    })
-}
-```
-
-All Routes must start with a forward slash.
-
-To have dynamic route segments, use the `:id` syntax. If concrete paths come *before* the dynamic syntax, then those will be chosen first.
-
-```rust
-cx.render(rsx! {
-    Router {
-        Route { to: "/", Component {} },
-        Route { to: "/blog", BlogList {} },
-        Route { to: "/blog/welcome", BlogWelcome {} },
-        Route { to: "/blog/:post", BlogPost {} },
-    }
-})
-```
-
-### Nested `Routes`
-
-Routes can be composed at various levels, so you don't just need top-level routes. To do this, simple add Routes inside other Routes
-
-```rust
-cx.render(rsx! {
-    Router {
-        Route { to: "/", Component {} },
-        Route { to: "/blog",
-            BlogContainer {
-                h1 { "blog" } // always renders as long as we're on the "blog" subroute
-                Route { to: "/", BlogList {} }
-                Route { to: "welcome", BlogWelcome {} }
-                Route { to: ":post", BlogPost {} }
-            }
-        },
-    }
-})
-```
-
-### Navigating with `Links`
-
-To navigate your app, regular, old, `a` tags are not going to work. We provide the `Link` component that wraps an `a` tag with the appropriate `href` attribute that generates semantic HTML. You can pass any children into this component and they will become clickable to the appropriate route.
-
-```rust
-Link { to: "/blog/welcome",
-    h1 { "Welcome to my blog!" }
-}
-```
-
-#### Active `Links`
-
-When your app has been navigated to a route that matches the route of a `Link`, this `Link` becomes 'active'.
-Active links have a special class attached to them. By default it is simply called `"active"` but it can be
-modified on the `Link` level or on the `Router` level. Both is done through the prop `active_class`.
-If the active class is given on both, the `Router` and the `Link`, the one on the `Link` has precedence.
-
-```rust
-Router {
-    active_class: "custom-active",  // All active links in this router get this class.
-    Link { to: "/", "Home" },
-    Link {
-        to: "/blog",
-        active_class: "is-active",  // Only for this Link. Overwrites "custom-active" from Router.
-        "Blog"
-    },
-}
-```
-
-#### Initial url
-
-When working with ssr specially is crucial to have the ability to set an initial url. This is an optional property and defaults to `app:///` for desktop, mobile and ssr, and for web retrieves the url from the window current location.
-
-You must provide a valid URL `{scheme}://{?authority}/{?path}`.
-
-```rust
-Router {
-    initial_url: "https://dioxuslab.com/blog",  // Set the initial url. 
-    Link { to: "/", "Home" },
-    Link { to: "/blog", "Blog" }, // The router will render this route.
-}
-```
-
-### Segments
-
-Each route in your app is comprised of segments and queries. Segments are the portions of the route delimited by forward slashes.
-
-For the route `/dogs/breeds/yorkie/hugo` our "segment list" would be:
-
-```rust
-vec!["dogs", "breeds", "yorkie", "hugo"]
-```
-
-For any route, you can get a handle the current route with the `use_route` hook.
-
-```rust
-fn Title(cx: Scope) -> Element {
-    let route = use_route(&cx);
-
-    assert_eq!(route.segments(), &["dogs", "breeds", "yorkie", "hugo"]);
-
-    assert_eq!(route.nth_segment(1), "breeds");
-
-    assert_eq!(route.last_segment(), "hugo");
-}
-```
-
-As we've shown above, segments can also be named. We can get these named segments out by the last match at that route level:
-
-```rust
-// For this router:
-Router {
-    Route { to: "/", Component {} },
-    Route { to: "/blog", BlogList {} },
-    Route { to: "/blog/:post", BlogPost {} },
-}
-
-fn BlogPost(cx: Scope) -> Element {
-    let route = use_route(&cx);
-
-    match route.segment("post").and_then(parse) {
-        Some(post) => cx.render(rsx!{ div { "Post {post}" } })
-        None => cx.render(rsx!{ div { "Could not find that blog post" } }),
-    }
-}
-```
-
-### Queries
-
-### Listeners
-
-It's possible to connect to route change events from the router by attaching a listener to the Router's `onchange` parameter. This listener is guaranteed to run before any of your routes are matched, so you can perform redirects, add some logging, fetch some data, or do anything that you might want to be synchronous with clicks on Links.
-
-```rust
-fn app() {
-    cx.render(rsx! {
-        Router {
-            onchange: move |router| {
-                let current = router.current_route();
-                log::debug!("App has navigated to {:?}", current);
-
-                // perform a redirect
-                if current == "404" {
-                    router.navigate_to("/");
-                }
-            },
-            Route { to: "/", Component {} },
-        }
-    })
-}
-```
-
-Listeners can also be attached downstream in your app with the `RouteListener` handler component:
-
-```rust
-fn TitleCard(cx: Scope) -> Element {
-    let (title, set_title) = use_state(&cx, || "First");
-
-    cx.render(rsx!{
-        h1 { "render {title}" }
-
-        RouteListener { onchange: move |_| set_title("Last") }
-    })
-}
-```
-
-### Working with Github Pages and other static hosts
-
-Most "static" hosts will have issues with single-page-app (SPA) routers. To get around this, you can either generate an index.html for each route or hijack the 404 page.
-
-For generating a static index.html, see `Generating a Route List`.
-
-To hijack the 404 page, we can simply make a copy of our index.html page and call it 404.html. When Github Pages serves this 404 page, your app will be served instead and the router will render the right corresponding route.
-
-<https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-custom-404-page-for-your-github-pages-site>
-
-### Generating a SiteMap or Route List
-
-If you want to statically generate and rehydrate all your pages, lean on Dioxus Router to do the heavy lifting.
-
-For this feature to work properly, each route (and nested) route will need to be probed, but this can be done automatically.
-
-```rust
-let mut dom = VirtualDom::new(app);
-dom.inject_root_context(RouterContext::new());
-
-// populate the router
-let _ = dom.rebuild();
-
-// load the router context from the dom, generate a sitemap, and then pre-render each page
-let mut prerendered_pages = dom
-    .consume_root_context::<RouterContext>()
-    .unwrap()
-    .sitemap()
-    .into_iter()
-    .map(|route| {
-        // update the root context
-        router.navigate_to(route);
-
-        // force our app to update
-        let _ = dom.rebuild();
-
-        // render the page and insert it into our map
-        (route, dioxus_ssr::render_vdom(&dom))
-    })
-    .collect::<HashMap<_, _>>();
-```