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

+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 {