Intigrate macro with router hooks and components

This commit is contained in:
Evan Almloff committed 2023-05-22 16:42:32 -05:00
1 parent ef6551a6cd
commit e4b8fbfafe
37 files changed
+1442 -3009

No files matched your search

@@ -1,17 +1,12 @@
use crate::{components::Link, hooks::use_route};
use crate::{components::Link, hooks::use_route, navigation::NavigationTarget, routable::Routable};
use dioxus::prelude::*;
use dioxus_router_core::prelude::{named, FailureExternalNavigation as FENName, RootIndex};
#[allow(non_snake_case)]
pub fn FailureExternalNavigation(cx: Scope) -> Element {
let state = use_route(cx).expect(
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
let href = use_route::<R>(cx).expect(
"`FailureExternalNavigation` can only be mounted by the router itself, \
since it is not exposed",
);
let href = state.parameter::<FENName>().expect(
"`FailureExternalNavigation` cannot be mounted without receiving its parameter, \
since it is not exposed",
);
render! {
h1 { "External Navigation Failure!" }
@@ -28,7 +23,7 @@ pub fn FailureExternalNavigation(cx: Scope) -> Element {
}
#[allow(non_snake_case)]
pub fn FailureNamedNavigation(cx: Scope) -> Element {
pub fn FailureNamedNavigation<R: Routable + Clone>(cx: Scope) -> Element {
render! {
h1 { "Named Navigation Failure!" }
p {
@@ -40,15 +35,15 @@ pub fn FailureNamedNavigation(cx: Scope) -> Element {
"We are sorry for the inconvenience. The link below may help to fix the problem, but "
"there is no guarantee."
}
Link {
target: named::<RootIndex>(),
Link::<R> {
target: NavigationTarget::External("https://google.com".into()),
"Click here to try to fix the failure."
}
}
}
#[allow(non_snake_case)]
pub fn FailureRedirectionLimit(cx: Scope) -> Element {
pub fn FailureRedirectionLimit<R: Routable + Clone>(cx: Scope) -> Element {
render! {
h1 { "Redirection Limit Failure!" }
p {
@@ -60,8 +55,8 @@ pub fn FailureRedirectionLimit(cx: Scope) -> Element {
"We are sorry for the inconvenience. The link below may help to fix the problem, but "
"there is no guarantee."
}
Link {
target: named::<RootIndex>(),
Link::<R> {
target: NavigationTarget::External("https://google.com".into()),
"Click here to try to fix the failure."
}
}
@@ -1,8 +1,7 @@
use dioxus::prelude::*;
use dioxus_router_core::RouterMessage;
use log::error;
use crate::utils::use_router_internal::use_router_internal;
use crate::{routable::Routable, utils::use_router_internal::use_router_internal};
/// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
#[derive(Debug, Props)]
@@ -52,11 +51,11 @@ pub struct HistoryButtonProps<'a> {
/// # );
/// ```
#[allow(non_snake_case)]
pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
let HistoryButtonProps { children } = cx.props;
// hook up to router
let router = match use_router_internal(cx) {
let router = match use_router_internal::<R>(cx) {
Some(r) => r,
#[allow(unreachable_code)]
None => {
@@ -67,20 +66,14 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
return None;
}
};
let state = loop {
if let Some(state) = router.state.try_read() {
break state;
}
};
let sender = router.sender.clone();
let disabled = !state.can_go_back;
let disabled = !router.can_go_back();
render! {
button {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| { let _ = sender.unbounded_send(RouterMessage::GoBack); },
onclick: move |_| router.go_back(),
children
}
}
@@ -127,11 +120,11 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
/// # );
/// ```
#[allow(non_snake_case)]
pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
pub fn GoForwardButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
let HistoryButtonProps { children } = cx.props;
// hook up to router
let router = match use_router_internal(cx) {
let router = match use_router_internal::<R>(cx) {
Some(r) => r,
#[allow(unreachable_code)]
None => {
@@ -142,20 +135,14 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
return None;
}
};
let state = loop {
if let Some(state) = router.state.try_read() {
break state;
}
};
let sender = router.sender.clone();
let disabled = !state.can_go_back;
let disabled = !router.can_go_back();
render! {
button {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| { let _ = sender.unbounded_send(RouterMessage::GoForward); },
onclick: move |_| router.go_forward(),
children
}
}
+20 -26
View File
@@ -1,14 +1,15 @@
use std::fmt::Debug;
use dioxus::prelude::*;
use dioxus_router_core::{navigation::NavigationTarget, RouterMessage};
use log::error;
use crate::navigation::NavigationTarget;
use crate::routable::Routable;
use crate::utils::use_router_internal::use_router_internal;
/// The properties for a [`Link`].
#[derive(Props)]
pub struct LinkProps<'a> {
pub struct LinkProps<'a, R: Routable> {
/// 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.
@@ -18,11 +19,6 @@ pub struct LinkProps<'a> {
/// If `active_class` is [`Some`] and the `target` route is active, `active_class` will be
/// appended at the end of `class`.
pub class: Option<&'a str>,
/// Require the __exact__ `target` to be active, for the [`Link`] to be active.
///
/// See [`RouterState::is_at`](dioxus_router_core::RouterState::is_at) for more details.
#[props(default)]
pub exact: bool,
/// The id attribute for the generated HTML anchor tag.
pub id: Option<&'a str>,
/// When [`true`], the `target` route will be opened in a new tab.
@@ -46,22 +42,21 @@ pub struct LinkProps<'a> {
pub rel: Option<&'a str>,
/// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
#[props(into)]
pub target: NavigationTarget,
pub target: NavigationTarget<R>,
}
impl Debug for LinkProps<'_> {
impl<R: Routable> Debug for LinkProps<'_, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LinkProps")
.field("active_class", &self.active_class)
.field("children", &self.children)
.field("class", &self.class)
.field("exact", &self.exact)
.field("id", &self.id)
.field("new_tab", &self.new_tab)
.field("onclick", &self.onclick.as_ref().map(|_| "onclick is set"))
.field("onclick_only", &self.onclick_only)
.field("rel", &self.rel)
.field("target", &self.target)
.field("target", &self.target.to_string())
.finish()
}
}
@@ -130,12 +125,11 @@ impl Debug for LinkProps<'_> {
/// # );
/// ```
#[allow(non_snake_case)]
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
pub fn Link<'a, R: Routable + Clone>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
let LinkProps {
active_class,
children,
class,
exact,
id,
new_tab,
onclick,
@@ -145,7 +139,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
} = cx.props;
// hook up to router
let router = match use_router_internal(cx) {
let router = match use_router_internal::<R>(cx) {
Some(r) => r,
#[allow(unreachable_code)]
None => {
@@ -156,19 +150,19 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
return None;
}
};
let state = loop {
if let Some(state) = router.state.try_read() {
break state;
}
};
let sender = router.sender.clone();
let href = state.href(target);
let current_route = router.current();
let href = current_route.to_string();
let ac = active_class
.and_then(|active_class| {
state
.is_at(target, *exact)
.then(|| format!(" {active_class}"))
.and_then(|active_class| match target {
NavigationTarget::Internal(target) => {
if href == target.to_string() {
Some(format!(" {active_class}"))
} else {
None
}
}
_ => None,
})
.unwrap_or_default();
@@ -186,7 +180,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
let do_default = onclick.is_none() || !onclick_only;
let action = move |event| {
if do_default && is_router_nav {
let _ = sender.unbounded_send(RouterMessage::Push(target.clone()));
router.push(target.clone());
}
if let Some(handler) = onclick {
+49
View File
@@ -0,0 +1,49 @@
use crate::{contexts::outlet::OutletContext, routable::Routable};
use dioxus::prelude::*;
/// An outlet for the current content.
///
/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
///
/// 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__.
///
/// [`use_router`]: crate::hooks::use_router
///
/// # Panic
/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
/// but only in debug builds.
///
/// # Example
/// ```rust
/// # use dioxus::prelude::*;
/// # use dioxus_router::prelude::*;
/// fn App(cx: Scope) -> Element {
/// use_router(
/// &cx,
/// &|| RouterConfiguration {
/// synchronous: true, // asynchronicity not needed for doc test
/// ..Default::default()
/// },
/// &|| Segment::content(comp(Content))
/// );
///
/// render! {
/// h1 { "App" }
/// Outlet { } // The content component will be rendered here
/// }
/// }
///
/// fn Content(cx: Scope) -> Element {
/// render! {
/// p { "Content" }
/// }
/// }
/// #
/// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild();
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Content</p>");
/// ```
pub fn Outlet<R: Routable + Clone>(cx: Scope) -> Element {
OutletContext::render::<R>(cx)
}
+60
View File
@@ -0,0 +1,60 @@
use dioxus::prelude::*;
use log::error;
use std::{cell::RefCell, str::FromStr};
use crate::{
history::HistoryProvider,
prelude::{outlet::OutletContext, RouterContext},
routable::Routable,
router_cfg::RouterConfiguration,
};
/// The props for [`Router`].
#[derive(Props)]
pub struct RouterProps<R: Routable> {
#[props(into)]
initial_url: Option<String>,
#[props(default, into)]
config: RefCell<Option<RouterConfiguration<R>>>,
}
impl<R: Routable> PartialEq for RouterProps<R> {
fn eq(&self, _: &Self) -> bool {
// prevent the router from re-rendering when the initial url or config changes
true
}
}
/// A component that renders the current route.
pub fn Router<R: Routable + Clone, H: HistoryProvider<R> + Default + 'static>(
cx: Scope<RouterProps<R>>,
) -> Element
where
<R as FromStr>::Err: std::fmt::Display,
{
let router = use_context_provider(cx, || {
#[allow(unreachable_code, unused_variables)]
if let Some(outer) = cx.consume_context::<RouterContext<R>>() {
let msg = "Router components should not be nested within each other";
error!("{msg}, inner will be inactive and transparent");
#[cfg(debug_assertions)]
panic!("{}", msg);
}
let router = RouterContext::new(
cx.props.config.take().unwrap_or_default(),
cx.schedule_update_any(),
);
if let Some(initial) = cx.props.initial_url.as_ref() {
router.replace(
initial
.parse()
.unwrap_or_else(|_| panic!("failed to parse initial url")),
);
}
router
});
use_context_provider(cx, || OutletContext { current_level: 1 });
router.current().render(cx, 0)
}