1use std::borrow::Cow;
3use std::rc::Rc;
4
5use gloo::history::query::Raw;
6use yew::prelude::*;
7use yew::virtual_dom::AttrValue;
8
9use crate::history::{AnyHistory, BrowserHistory, HashHistory, History, Location};
10use crate::navigator::Navigator;
11use crate::utils::{base_url, strip_slash_suffix};
12
13#[derive(Properties, PartialEq, Clone)]
15pub struct RouterProps {
16 #[prop_or_default]
17 pub children: Html,
18 pub history: AnyHistory,
19 #[prop_or_default]
20 pub basename: Option<AttrValue>,
21}
22
23#[derive(Clone)]
24pub(crate) struct LocationContext {
25 location: Location,
26 ctr: u32,
28}
29
30impl LocationContext {
31 pub fn location(&self) -> Location {
32 self.location.clone()
33 }
34}
35
36impl PartialEq for LocationContext {
37 fn eq(&self, rhs: &Self) -> bool {
38 self.ctr == rhs.ctr
39 }
40}
41
42impl Reducible for LocationContext {
43 type Action = Location;
44
45 fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
46 Self {
47 location: action,
48 ctr: self.ctr + 1,
49 }
50 .into()
51 }
52}
53
54#[derive(Clone, PartialEq)]
55pub(crate) struct NavigatorContext {
56 navigator: Navigator,
57}
58
59impl NavigatorContext {
60 pub fn navigator(&self) -> Navigator {
61 self.navigator.clone()
62 }
63}
64
65#[function_component(BaseRouter)]
70fn base_router(props: &RouterProps) -> Html {
71 let RouterProps {
72 history,
73 children,
74 basename,
75 } = props.clone();
76
77 let basename = basename.map(|m| strip_slash_suffix(&m).to_owned());
78 let navigator = Navigator::new(history.clone(), basename.clone());
79
80 let old_basename = use_mut_ref(|| Option::<String>::None);
81 let mut old_basename = old_basename.borrow_mut();
82 if basename != *old_basename {
83 let old_navigator = Navigator::new(
87 history.clone(),
88 old_basename.as_ref().or(basename.as_ref()).cloned(),
89 );
90 *old_basename = basename.clone();
91 let location = history.location();
92 let stripped = old_navigator.strip_basename(Cow::from(location.path()));
93 let prefixed = navigator.prefix_basename(&stripped);
94
95 if prefixed != location.path() {
96 history
97 .replace_with_query(prefixed, Raw(location.query_str()))
98 .unwrap_or_else(|never| match never {});
99 } else {
100 }
105 }
106
107 let navi_ctx = NavigatorContext { navigator };
108
109 let loc_ctx = use_reducer(|| LocationContext {
110 location: history.location(),
111 ctr: 0,
112 });
113
114 {
115 let loc_ctx_dispatcher = loc_ctx.dispatcher();
116
117 use_effect_with(history, move |history| {
118 let history = history.clone();
119 loc_ctx_dispatcher.dispatch(history.location());
121
122 let history_cb = {
123 let history = history.clone();
124 move || loc_ctx_dispatcher.dispatch(history.location())
125 };
126
127 let listener = history.listen(history_cb);
128
129 move || {
131 std::mem::drop(listener);
132 }
133 });
134 }
135
136 html! {
137 <ContextProvider<NavigatorContext> context={navi_ctx}>
138 <ContextProvider<LocationContext> context={(*loc_ctx).clone()}>
139 {children}
140 </ContextProvider<LocationContext>>
141 </ContextProvider<NavigatorContext>>
142 }
143}
144
145#[function_component(Router)]
153pub fn router(props: &RouterProps) -> Html {
154 html! {
155 <BaseRouter ..{props.clone()} />
156 }
157}
158
159#[derive(Properties, PartialEq, Clone)]
161pub struct ConcreteRouterProps {
162 pub children: Html,
163 #[prop_or_default]
164 pub basename: Option<AttrValue>,
165}
166
167#[function_component(BrowserRouter)]
177pub fn browser_router(props: &ConcreteRouterProps) -> Html {
178 let ConcreteRouterProps { children, basename } = props.clone();
179 let history = use_state(|| AnyHistory::from(BrowserHistory::new()));
180
181 let basename = basename.map(|m| m.to_string()).or_else(base_url);
183
184 html! {
185 <BaseRouter history={(*history).clone()} {basename}>
186 {children}
187 </BaseRouter>
188 }
189}
190
191#[function_component(HashRouter)]
200pub fn hash_router(props: &ConcreteRouterProps) -> Html {
201 let ConcreteRouterProps { children, basename } = props.clone();
202 let history = use_state(|| AnyHistory::from(HashHistory::new()));
203
204 html! {
205 <BaseRouter history={(*history).clone()} {basename}>
206 {children}
207 </BaseRouter>
208 }
209}