1use std::rc::Rc;
3
4use yew::prelude::*;
5use yew::virtual_dom::AttrValue;
6
7use crate::history::{AnyHistory, BrowserHistory, HashHistory, History, Location};
8use crate::navigator::Navigator;
9use crate::utils::{base_url, strip_slash_suffix};
10
11#[derive(Properties, PartialEq, Clone)]
13pub struct RouterProps {
14 #[prop_or_default]
15 pub children: Html,
16 pub history: AnyHistory,
17 #[prop_or_default]
18 pub basename: Option<AttrValue>,
19}
20
21#[derive(Clone)]
22pub(crate) struct LocationContext {
23 location: Location,
24 ctr: u32,
26}
27
28impl LocationContext {
29 pub fn location(&self) -> Location {
30 self.location.clone()
31 }
32}
33
34impl PartialEq for LocationContext {
35 fn eq(&self, rhs: &Self) -> bool {
36 self.ctr == rhs.ctr
37 }
38}
39
40impl Reducible for LocationContext {
41 type Action = Location;
42
43 fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
44 Self {
45 location: action,
46 ctr: self.ctr + 1,
47 }
48 .into()
49 }
50}
51
52#[derive(Clone, PartialEq)]
53pub(crate) struct NavigatorContext {
54 navigator: Navigator,
55}
56
57impl NavigatorContext {
58 pub fn navigator(&self) -> Navigator {
59 self.navigator.clone()
60 }
61}
62
63#[function_component(BaseRouter)]
68fn base_router(props: &RouterProps) -> Html {
69 let RouterProps {
70 history,
71 children,
72 basename,
73 } = props.clone();
74
75 let loc_ctx = use_reducer(|| LocationContext {
76 location: history.location(),
77 ctr: 0,
78 });
79
80 let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
81 let navi_ctx = NavigatorContext {
82 navigator: Navigator::new(history.clone(), basename),
83 };
84
85 {
86 let loc_ctx_dispatcher = loc_ctx.dispatcher();
87
88 use_effect_with(history, move |history| {
89 let history = history.clone();
90 loc_ctx_dispatcher.dispatch(history.location());
92
93 let history_cb = {
94 let history = history.clone();
95 move || loc_ctx_dispatcher.dispatch(history.location())
96 };
97
98 let listener = history.listen(history_cb);
99
100 move || {
102 std::mem::drop(listener);
103 }
104 });
105 }
106
107 html! {
108 <ContextProvider<NavigatorContext> context={navi_ctx}>
109 <ContextProvider<LocationContext> context={(*loc_ctx).clone()}>
110 {children}
111 </ContextProvider<LocationContext>>
112 </ContextProvider<NavigatorContext>>
113 }
114}
115
116#[function_component(Router)]
124pub fn router(props: &RouterProps) -> Html {
125 html! {
126 <BaseRouter ..{props.clone()} />
127 }
128}
129
130#[derive(Properties, PartialEq, Clone)]
132pub struct ConcreteRouterProps {
133 pub children: Html,
134 #[prop_or_default]
135 pub basename: Option<AttrValue>,
136}
137
138#[function_component(BrowserRouter)]
148pub fn browser_router(props: &ConcreteRouterProps) -> Html {
149 let ConcreteRouterProps { children, basename } = props.clone();
150 let history = use_state(|| AnyHistory::from(BrowserHistory::new()));
151
152 let basename = basename.map(|m| m.to_string()).or_else(base_url);
154
155 html! {
156 <BaseRouter history={(*history).clone()} {basename}>
157 {children}
158 </BaseRouter>
159 }
160}
161
162#[function_component(HashRouter)]
171pub fn hash_router(props: &ConcreteRouterProps) -> Html {
172 let ConcreteRouterProps { children, basename } = props.clone();
173 let history = use_state(|| AnyHistory::from(HashHistory::new()));
174
175 html! {
176 <BaseRouter history={(*history).clone()} {basename}>
177 {children}
178 </BaseRouter>
179 }
180}