1use crate::{
4 agent::{RouteAgentBridge, RouteRequest},
5 route::Route,
6 RouteState, Switch,
7};
8use std::{
9 fmt::{self, Debug, Error as FmtError, Formatter},
10 rc::Rc,
11};
12use yew::{html, virtual_dom::VNode, Component, Context, Html, Properties};
13
14pub trait RouterState: RouteState + PartialEq {}
16impl<STATE> RouterState for STATE where STATE: RouteState + PartialEq {}
17
18#[derive(Debug)]
56pub struct Router<SW: Switch + Clone + 'static, STATE: RouterState = ()> {
57 switch: Option<SW>,
58 router_agent: RouteAgentBridge<STATE>,
59}
60
61impl<SW, STATE> Router<SW, STATE>
62where
63 STATE: RouterState,
64 SW: Switch + PartialEq + Clone + 'static,
65{
66 pub fn render<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Render<SW, STATE> {
88 Render::new(f)
89 }
90
91 pub fn redirect<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Option<Redirect<SW, STATE>> {
93 Some(Redirect::new(f))
94 }
95}
96
97#[derive(Debug, Clone)]
99pub enum Msg<STATE> {
100 UpdateRoute(Route<STATE>),
102}
103
104pub trait RenderFn<CTX: Component, SW>: Fn(SW) -> Html {}
106impl<T, CTX: Component, SW> RenderFn<CTX, SW> for T where T: Fn(SW) -> Html {}
107#[derive(Clone)]
109pub struct Render<SW: Switch + Clone + 'static, STATE: RouterState = ()>(
110 pub(crate) Rc<dyn RenderFn<Router<SW, STATE>, SW>>,
111);
112
113impl<SW: Switch + Clone + 'static, STATE: RouterState> PartialEq for Render<SW, STATE> {
114 fn eq(&self, other: &Self) -> bool {
115 Rc::ptr_eq(&self.0, &other.0)
116 }
117}
118
119impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Render<SW, STATE> {
120 fn new<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Self {
122 Render(Rc::new(f))
123 }
124}
125impl<STATE: RouterState, SW: Switch + Clone> Debug for Render<SW, STATE> {
126 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
127 f.debug_struct("Render").finish()
128 }
129}
130
131pub trait RedirectFn<SW, STATE>: Fn(Route<STATE>) -> SW {}
134impl<T, SW, STATE> RedirectFn<SW, STATE> for T where T: Fn(Route<STATE>) -> SW {}
135#[derive(Clone)]
137pub struct Redirect<SW: Switch + 'static, STATE: RouterState>(
138 pub(crate) Rc<dyn RedirectFn<SW, STATE>>,
139);
140impl<SW: Switch + 'static, STATE: RouterState> PartialEq for Redirect<SW, STATE> {
141 fn eq(&self, other: &Self) -> bool {
142 Rc::ptr_eq(&self.0, &other.0)
143 }
144}
145impl<STATE: RouterState, SW: Switch + 'static> Redirect<SW, STATE> {
146 fn new<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Self {
147 Redirect(Rc::new(f))
148 }
149}
150impl<STATE: RouterState, SW: Switch> Debug for Redirect<SW, STATE> {
151 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
152 f.debug_struct("Redirect").finish()
153 }
154}
155
156#[derive(Properties, Clone, PartialEq)]
158pub struct Props<STATE: RouterState, SW: Switch + PartialEq + PartialEq + Clone + 'static> {
159 pub render: Render<SW, STATE>,
161 #[prop_or_default]
165 pub redirect: Option<Redirect<SW, STATE>>,
166}
167
168impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Debug for Props<STATE, SW> {
169 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
170 f.debug_struct("Props").finish()
171 }
172}
173
174impl<STATE, SW> Component for Router<SW, STATE>
175where
176 STATE: RouterState,
177 SW: Switch + PartialEq + Clone + 'static,
178{
179 type Message = Msg<STATE>;
180 type Properties = Props<STATE, SW>;
181
182 fn create(ctx: &Context<Self>) -> Self {
183 let callback = ctx.link().callback(Msg::UpdateRoute);
184 let mut router_agent = RouteAgentBridge::new(callback);
185 router_agent.send(RouteRequest::GetCurrentRoute);
186
187 Router {
188 switch: Default::default(), router_agent,
191 }
192 }
193
194 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
195 match msg {
196 Msg::UpdateRoute(route) => {
197 let mut switch = SW::switch(route.clone());
198
199 if switch.is_none() {
200 if let Some(redirect) = &ctx.props().redirect {
201 let redirected: SW = (&redirect.0)(route);
202
203 log::trace!(
204 "Route failed to match, but redirecting route to a known switch."
205 );
206 self.router_agent
208 .send(RouteRequest::ReplaceRouteNoBroadcast(
209 redirected.clone().into(),
210 ));
211 switch = Some(redirected)
212 }
213 }
214
215 self.switch = switch;
216 true
217 }
218 }
219 }
220
221 fn view(&self, ctx: &Context<Self>) -> VNode {
222 match self.switch.clone() {
223 Some(switch) => (&ctx.props().render.0)(switch),
224 None => {
225 log::warn!("No route matched, provide a redirect prop to the router to handle cases where no route can be matched");
226 html! {"No route matched"}
227 }
228 }
229 }
230}