it almost lives?
This commit is contained in:
1 parent
d637ef187c
commit
5f0dd3af3e
43 files changed
+506
-244
No files matched your search
@@ -1,10 +1,10 @@
|
||||
use crate::{hooks::use_generic_router, routable::Routable};
|
||||
use crate::hooks::use_router;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// The default component to render when an external navigation fails.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
||||
let router = use_generic_router::<R>(cx);
|
||||
pub fn FailureExternalNavigation(cx: Scope) -> Element {
|
||||
let router = use_router(cx);
|
||||
|
||||
render! {
|
||||
h1 { "External Navigation Failure!" }
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
use dioxus::prelude::*;
|
||||
use log::error;
|
||||
|
||||
use crate::{prelude::*, utils::use_router_internal::use_router_internal};
|
||||
use crate::utils::use_router_internal::use_router_internal;
|
||||
|
||||
/// The properties for a [`GenericGoBackButton`] or a [`GenericGoForwardButton`].
|
||||
/// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
|
||||
#[derive(Debug, Props)]
|
||||
pub struct GenericHistoryButtonProps<'a> {
|
||||
pub struct HistoryButtonProps<'a> {
|
||||
/// The children to render within the generated HTML button tag.
|
||||
pub children: Element<'a>,
|
||||
}
|
||||
|
||||
/// A button to go back through the navigation history. Similar to a browsers back button.
|
||||
///
|
||||
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
|
||||
/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
|
||||
///
|
||||
/// The button will disable itself if it is known that no prior history is available.
|
||||
///
|
||||
/// # Panic
|
||||
/// - When the [`GenericGoBackButton`] is not nested within a [`GenericRouter`] component
|
||||
/// - When the [`GoBackButton`] is not nested within a [`Link`] component
|
||||
/// hook, but only in debug builds.
|
||||
///
|
||||
/// # Example
|
||||
@@ -32,7 +32,7 @@ pub struct GenericHistoryButtonProps<'a> {
|
||||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -53,13 +53,11 @@ pub struct GenericHistoryButtonProps<'a> {
|
||||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericGoBackButton<'a, R: Routable>(
|
||||
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||
) -> Element {
|
||||
let GenericHistoryButtonProps { children } = cx.props;
|
||||
pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
||||
let HistoryButtonProps { children } = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
None => {
|
||||
@@ -85,12 +83,12 @@ pub fn GenericGoBackButton<'a, R: Routable>(
|
||||
|
||||
/// A button to go forward through the navigation history. Similar to a browsers forward button.
|
||||
///
|
||||
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
|
||||
/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
|
||||
///
|
||||
/// The button will disable itself if it is known that no later history is available.
|
||||
///
|
||||
/// # Panic
|
||||
/// - When the [`GenericGoForwardButton`] is not nested within a [`GenericRouter`] component
|
||||
/// - When the [`GoForwardButton`] is not nested within a [`Link`] component
|
||||
/// hook, but only in debug builds.
|
||||
///
|
||||
/// # Example
|
||||
@@ -105,7 +103,7 @@ pub fn GenericGoBackButton<'a, R: Routable>(
|
||||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -126,13 +124,11 @@ pub fn GenericGoBackButton<'a, R: Routable>(
|
||||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericGoForwardButton<'a, R: Routable>(
|
||||
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||
) -> Element {
|
||||
let GenericHistoryButtonProps { children } = cx.props;
|
||||
pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
||||
let HistoryButtonProps { children } = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
None => {
|
||||
|
||||
@@ -1,15 +1,57 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use log::error;
|
||||
|
||||
use crate::navigation::NavigationTarget;
|
||||
use crate::prelude::*;
|
||||
use crate::prelude::{AnyDisplay, Routable};
|
||||
use crate::utils::use_router_internal::use_router_internal;
|
||||
|
||||
/// The properties for a [`GenericLink`].
|
||||
/// Something that can be converted into a [`NavigationTarget`].
|
||||
pub enum IntoRoutable {
|
||||
/// A raw string target.
|
||||
FromStr(String),
|
||||
/// A internal target.
|
||||
Route(Box<dyn AnyDisplay>),
|
||||
}
|
||||
|
||||
impl<R: Routable> From<R> for IntoRoutable {
|
||||
fn from(value: R) -> Self {
|
||||
IntoRoutable::Route(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Routable> From<NavigationTarget<R>> for IntoRoutable {
|
||||
fn from(value: NavigationTarget<R>) -> Self {
|
||||
match value {
|
||||
NavigationTarget::Internal(route) => IntoRoutable::Route(Box::new(route)),
|
||||
NavigationTarget::External(url) => IntoRoutable::FromStr(url),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IntoRoutable {
|
||||
fn from(value: String) -> Self {
|
||||
IntoRoutable::FromStr(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for IntoRoutable {
|
||||
fn from(value: &String) -> Self {
|
||||
IntoRoutable::FromStr(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for IntoRoutable {
|
||||
fn from(value: &str) -> Self {
|
||||
IntoRoutable::FromStr(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// The properties for a [`Link`].
|
||||
#[derive(Props)]
|
||||
pub struct GenericLinkProps<'a, R: Routable> {
|
||||
pub struct LinkProps<'a> {
|
||||
/// A class to apply to the generate HTML anchor tag if the `target` route is active.
|
||||
pub active_class: Option<&'a str>,
|
||||
/// The children to render within the generated HTML anchor tag.
|
||||
@@ -23,7 +65,7 @@ pub struct GenericLinkProps<'a, R: Routable> {
|
||||
pub id: Option<&'a str>,
|
||||
/// When [`true`], the `target` route will be opened in a new tab.
|
||||
///
|
||||
/// This does not change whether the [`GenericLink`] is active or not.
|
||||
/// This does not change whether the [`Link`] is active or not.
|
||||
#[props(default)]
|
||||
pub new_tab: bool,
|
||||
/// The onclick event handler.
|
||||
@@ -42,10 +84,10 @@ pub struct GenericLinkProps<'a, R: Routable> {
|
||||
pub rel: Option<&'a str>,
|
||||
/// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
|
||||
#[props(into)]
|
||||
pub to: NavigationTarget<R>,
|
||||
pub to: IntoRoutable,
|
||||
}
|
||||
|
||||
impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
||||
impl Debug for LinkProps<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LinkProps")
|
||||
.field("active_class", &self.active_class)
|
||||
@@ -56,27 +98,26 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
||||
.field("onclick", &self.onclick.as_ref().map(|_| "onclick is set"))
|
||||
.field("onclick_only", &self.onclick_only)
|
||||
.field("rel", &self.rel)
|
||||
.field("to", &self.to.to_string())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A link to navigate to another route.
|
||||
///
|
||||
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
|
||||
/// Only works as descendant of a [`Router`] component, otherwise it will be inactive.
|
||||
///
|
||||
/// Unlike a regular HTML anchor, a [`GenericLink`] allows the router to handle the navigation and doesn't
|
||||
/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't
|
||||
/// cause the browser to load a new page.
|
||||
///
|
||||
/// However, in the background a [`GenericLink`] still generates an anchor, which you can use for styling
|
||||
/// However, in the background a [`Link`] still generates an anchor, which you can use for styling
|
||||
/// as normal.
|
||||
///
|
||||
/// # External targets
|
||||
/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
|
||||
/// means that a [`GenericLink`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
|
||||
/// When the [`Link`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
|
||||
/// means that a [`Link`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
|
||||
///
|
||||
/// # Panic
|
||||
/// - When the [`GenericLink`] is not nested within a [`GenericRouter`], but
|
||||
/// - When the [`Link`] is not nested within a [`Router`], but
|
||||
/// only in debug builds.
|
||||
///
|
||||
/// # Example
|
||||
@@ -92,7 +133,7 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
||||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -122,8 +163,8 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
||||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R>>) -> Element {
|
||||
let GenericLinkProps {
|
||||
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
||||
let LinkProps {
|
||||
active_class,
|
||||
children,
|
||||
class,
|
||||
@@ -133,10 +174,11 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
||||
onclick_only,
|
||||
rel,
|
||||
to,
|
||||
..
|
||||
} = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
None => {
|
||||
@@ -148,9 +190,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
||||
}
|
||||
};
|
||||
|
||||
let current_route = router.current();
|
||||
let current_url = current_route.to_string();
|
||||
let href = to.to_string();
|
||||
let current_url = router.current_route_string();
|
||||
let href = match to {
|
||||
IntoRoutable::FromStr(url) => url.to_string(),
|
||||
IntoRoutable::Route(route) => route.to_string(),
|
||||
};
|
||||
let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
|
||||
Ok(route) => NavigationTarget::Internal(route.into()),
|
||||
Err(err) => NavigationTarget::External(err),
|
||||
};
|
||||
let ac = active_class
|
||||
.and_then(|active_class| (href == current_url).then(|| format!(" {active_class}")))
|
||||
.unwrap_or_default();
|
||||
@@ -159,7 +207,7 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
||||
let class = format!("{}{ac}", class.unwrap_or_default());
|
||||
let tag_target = new_tab.then_some("_blank").unwrap_or_default();
|
||||
|
||||
let is_external = matches!(to, NavigationTarget::External(_));
|
||||
let is_external = matches!(parsed_route, NavigationTarget::External(_));
|
||||
let is_router_nav = !is_external && !new_tab;
|
||||
let prevent_default = is_router_nav.then_some("onclick").unwrap_or_default();
|
||||
let rel = rel
|
||||
@@ -169,7 +217,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
||||
let do_default = onclick.is_none() || !onclick_only;
|
||||
let action = move |event| {
|
||||
if do_default && is_router_nav {
|
||||
router.push(to.clone());
|
||||
let href = match to {
|
||||
IntoRoutable::FromStr(url) => url.to_string(),
|
||||
IntoRoutable::Route(route) => route.to_string(),
|
||||
};
|
||||
let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
|
||||
Ok(route) => NavigationTarget::Internal(route.into()),
|
||||
Err(err) => NavigationTarget::External(err),
|
||||
};
|
||||
router.push_any(parsed_route);
|
||||
}
|
||||
|
||||
if let Some(handler) = onclick {
|
||||
|
||||
@@ -3,13 +3,13 @@ use dioxus::prelude::*;
|
||||
|
||||
/// An outlet for the current content.
|
||||
///
|
||||
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
|
||||
/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
|
||||
///
|
||||
/// The [`GenericOutlet`] is aware of how many [`GenericOutlet`]s it is nested within. It will render the content
|
||||
/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content
|
||||
/// of the active route that is __exactly as deep__.
|
||||
///
|
||||
/// # Panic
|
||||
/// - When the [`GenericOutlet`] is not nested a [`GenericRouter`] component,
|
||||
/// - When the [`Outlet`] is not nested a [`Link`] component,
|
||||
/// but only in debug builds.
|
||||
///
|
||||
/// # Example
|
||||
@@ -42,7 +42,7 @@ use dioxus::prelude::*;
|
||||
/// fn Wrapper(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// h1 { "App" }
|
||||
/// Outlet {} // The content of child routes will be rendered here
|
||||
/// Outlet::<Route> {} // The content of child routes will be rendered here
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -57,7 +57,7 @@ use dioxus::prelude::*;
|
||||
///
|
||||
/// # fn App(cx: Scope) -> Element {
|
||||
/// # render! {
|
||||
/// # Router {
|
||||
/// # Router::<Route> {
|
||||
/// # config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {}))
|
||||
/// # }
|
||||
/// # }
|
||||
@@ -67,6 +67,6 @@ use dioxus::prelude::*;
|
||||
/// # let _ = vdom.rebuild();
|
||||
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
|
||||
/// ```
|
||||
pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
|
||||
pub fn Outlet<R: Routable + Clone>(cx: Scope) -> Element {
|
||||
OutletContext::<R>::render(cx)
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
use dioxus::prelude::*;
|
||||
use std::{cell::RefCell, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
prelude::{GenericOutlet, GenericRouterContext},
|
||||
routable::Routable,
|
||||
router_cfg::RouterConfig,
|
||||
};
|
||||
use crate::{prelude::Outlet, routable::Routable, router_cfg::RouterConfig};
|
||||
|
||||
/// The config for [`GenericRouter`].
|
||||
/// The config for [`Router`].
|
||||
pub struct RouterConfigFactory<R: Routable> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
config: RefCell<Option<Box<dyn FnOnce() -> RouterConfig<R>>>>,
|
||||
@@ -43,9 +39,9 @@ impl<R: Routable, F: FnOnce() -> RouterConfig<R> + 'static> From<F> for RouterCo
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
/// The props for [`GenericRouter`].
|
||||
/// The props for [`Router`].
|
||||
#[derive(Props)]
|
||||
pub struct GenericRouterProps<R: Routable>
|
||||
pub struct RouterProps<R: Routable>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
@@ -55,9 +51,9 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
/// The props for [`GenericRouter`].
|
||||
/// The props for [`Router`].
|
||||
#[derive(Props)]
|
||||
pub struct GenericRouterProps<R: Routable>
|
||||
pub struct RouterProps<R: Routable>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
@@ -66,7 +62,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
impl<R: Routable> Default for GenericRouterProps<R>
|
||||
impl<R: Routable> Default for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
@@ -78,7 +74,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<R: Routable> Default for GenericRouterProps<R>
|
||||
impl<R: Routable> Default for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
@@ -91,7 +87,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
||||
impl<R: Routable> PartialEq for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
@@ -102,7 +98,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
||||
impl<R: Routable> PartialEq for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
@@ -115,14 +111,14 @@ where
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
/// A component that renders the current route.
|
||||
pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
|
||||
pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
use crate::prelude::outlet::OutletContext;
|
||||
use crate::prelude::{outlet::OutletContext, RouterContext};
|
||||
|
||||
use_context_provider(cx, || {
|
||||
GenericRouterContext::new(
|
||||
RouterContext::new(
|
||||
(cx.props
|
||||
.config
|
||||
.config
|
||||
@@ -137,19 +133,19 @@ where
|
||||
});
|
||||
|
||||
render! {
|
||||
GenericOutlet::<R> {}
|
||||
Outlet::<R> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
/// A component that renders the current route.
|
||||
pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
|
||||
pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
{
|
||||
use_context_provider(cx, || {
|
||||
GenericRouterContext::new(
|
||||
RouterContext::new(
|
||||
(cx.props
|
||||
.config
|
||||
.config
|
||||
@@ -164,6 +160,6 @@ where
|
||||
});
|
||||
|
||||
render! {
|
||||
GenericOutlet::<R> {}
|
||||
Outlet::<R> {}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user