use crate::{
agent::{RouteAgentBridge, RouteRequest},
route::Route,
RouteState, Switch,
};
use std::{
fmt::{self, Debug, Error as FmtError, Formatter},
rc::Rc,
};
use yew::{html, virtual_dom::VNode, Component, Context, Html, Properties};
pub trait RouterState: RouteState + PartialEq {}
impl<STATE> RouterState for STATE where STATE: RouteState + PartialEq {}
#[derive(Debug)]
pub struct Router<SW: Switch + Clone + 'static, STATE: RouterState = ()> {
switch: Option<SW>,
router_agent: RouteAgentBridge<STATE>,
}
impl<SW, STATE> Router<SW, STATE>
where
STATE: RouterState,
SW: Switch + PartialEq + Clone + 'static,
{
pub fn render<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Render<SW, STATE> {
Render::new(f)
}
pub fn redirect<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Option<Redirect<SW, STATE>> {
Some(Redirect::new(f))
}
}
#[derive(Debug, Clone)]
pub enum Msg<STATE> {
UpdateRoute(Route<STATE>),
}
pub trait RenderFn<CTX: Component, SW>: Fn(SW) -> Html {}
impl<T, CTX: Component, SW> RenderFn<CTX, SW> for T where T: Fn(SW) -> Html {}
#[derive(Clone)]
pub struct Render<SW: Switch + Clone + 'static, STATE: RouterState = ()>(
pub(crate) Rc<dyn RenderFn<Router<SW, STATE>, SW>>,
);
impl<SW: Switch + Clone + 'static, STATE: RouterState> PartialEq for Render<SW, STATE> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Render<SW, STATE> {
fn new<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Self {
Render(Rc::new(f))
}
}
impl<STATE: RouterState, SW: Switch + Clone> Debug for Render<SW, STATE> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Render").finish()
}
}
pub trait RedirectFn<SW, STATE>: Fn(Route<STATE>) -> SW {}
impl<T, SW, STATE> RedirectFn<SW, STATE> for T where T: Fn(Route<STATE>) -> SW {}
#[derive(Clone)]
pub struct Redirect<SW: Switch + 'static, STATE: RouterState>(
pub(crate) Rc<dyn RedirectFn<SW, STATE>>,
);
impl<SW: Switch + 'static, STATE: RouterState> PartialEq for Redirect<SW, STATE> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl<STATE: RouterState, SW: Switch + 'static> Redirect<SW, STATE> {
fn new<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Self {
Redirect(Rc::new(f))
}
}
impl<STATE: RouterState, SW: Switch> Debug for Redirect<SW, STATE> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Redirect").finish()
}
}
#[derive(Properties, Clone, PartialEq)]
pub struct Props<STATE: RouterState, SW: Switch + PartialEq + PartialEq + Clone + 'static> {
pub render: Render<SW, STATE>,
#[prop_or_default]
pub redirect: Option<Redirect<SW, STATE>>,
}
impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Debug for Props<STATE, SW> {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
f.debug_struct("Props").finish()
}
}
impl<STATE, SW> Component for Router<SW, STATE>
where
STATE: RouterState,
SW: Switch + PartialEq + Clone + 'static,
{
type Message = Msg<STATE>;
type Properties = Props<STATE, SW>;
fn create(ctx: &Context<Self>) -> Self {
let callback = ctx.link().callback(Msg::UpdateRoute);
let mut router_agent = RouteAgentBridge::new(callback);
router_agent.send(RouteRequest::GetCurrentRoute);
Router {
switch: Default::default(),
router_agent,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::UpdateRoute(route) => {
let mut switch = SW::switch(route.clone());
if switch.is_none() {
if let Some(redirect) = &ctx.props().redirect {
let redirected: SW = (&redirect.0)(route);
log::trace!(
"Route failed to match, but redirecting route to a known switch."
);
self.router_agent
.send(RouteRequest::ReplaceRouteNoBroadcast(
redirected.clone().into(),
));
switch = Some(redirected)
}
}
self.switch = switch;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> VNode {
match self.switch.clone() {
Some(switch) => (&ctx.props().render.0)(switch),
None => {
log::warn!("No route matched, provide a redirect prop to the router to handle cases where no route can be matched");
html! {"No route matched"}
}
}
}
}