it almost lives?

This commit is contained in:
Evan Almloff committed 2023-07-25 18:14:48 -07:00
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 => {
+78 -22
View File
@@ -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 {
+6 -6
View File
@@ -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)
}
+17 -21
View File
@@ -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> {}
}
}