freya_router/contexts/
router.rs1use std::{
2 cell::RefCell,
3 error::Error,
4 fmt::Display,
5 rc::Rc,
6};
7
8use freya_core::{
9 integration::FxHashSet,
10 prelude::*,
11};
12
13use crate::{
14 components::child_router::consume_child_route_mapping,
15 memory::MemoryHistory,
16 navigation::NavigationTarget,
17 prelude::SiteMapSegment,
18 routable::Routable,
19 router_cfg::RouterConfig,
20};
21
22#[derive(Debug, Clone)]
24pub struct ParseRouteError {
25 message: String,
26}
27
28impl Error for ParseRouteError {}
29impl Display for ParseRouteError {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 self.message.fmt(f)
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct ExternalNavigationFailure(pub String);
38
39struct RouterContextInner {
40 subscribers: Rc<RefCell<FxHashSet<ReactiveContext>>>,
41
42 internal_route: fn(&str) -> bool,
43
44 site_map: &'static [SiteMapSegment],
45
46 history: MemoryHistory,
47}
48
49impl RouterContextInner {
50 fn update_subscribers(&self) {
51 for id in self.subscribers.borrow().iter() {
52 id.notify();
53 }
54 }
55
56 fn subscribe_to_current_context(&self) {
57 if let Some(mut rc) = ReactiveContext::try_current() {
58 rc.subscribe(&self.subscribers);
59 }
60 }
61
62 fn external(&mut self, external: String) -> Option<ExternalNavigationFailure> {
63 let failure = ExternalNavigationFailure(external);
64
65 self.update_subscribers();
66
67 Some(failure)
68 }
69}
70
71#[derive(Clone, Copy)]
73pub struct RouterContext {
74 inner: State<RouterContextInner>,
75}
76
77impl RouterContext {
78 pub(crate) fn create<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {
79 let subscribers = Rc::new(RefCell::new(FxHashSet::default()));
80
81 let history = if let Some(initial_path) = cfg.initial_path {
82 MemoryHistory::with_initial_path(initial_path)
83 } else {
84 MemoryHistory::default()
85 };
86
87 Self {
88 inner: State::create(RouterContextInner {
89 subscribers: subscribers.clone(),
90
91 internal_route: |route| R::from_str(route).is_ok(),
92
93 site_map: R::SITE_MAP,
94
95 history,
96 }),
97 }
98 }
99
100 pub fn create_global<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {
101 let subscribers = Rc::new(RefCell::new(FxHashSet::default()));
102
103 let history = if let Some(initial_path) = cfg.initial_path {
104 MemoryHistory::with_initial_path(initial_path)
105 } else {
106 MemoryHistory::default()
107 };
108
109 Self {
110 inner: State::create_global(RouterContextInner {
111 subscribers: subscribers.clone(),
112
113 internal_route: |route| R::from_str(route).is_ok(),
114
115 site_map: R::SITE_MAP,
116
117 history,
118 }),
119 }
120 }
121
122 pub fn try_get() -> Option<Self> {
123 try_consume_context()
124 }
125
126 pub fn get() -> Self {
127 consume_context()
128 }
129
130 #[must_use]
132 pub fn can_go_back(&self) -> bool {
133 self.inner.peek().history.can_go_back()
134 }
135
136 #[must_use]
138 pub fn can_go_forward(&self) -> bool {
139 self.inner.peek().history.can_go_forward()
140 }
141
142 pub fn go_back(&self) {
146 self.inner.peek().history.go_back();
147 self.change_route();
148 }
149
150 pub fn go_forward(&self) {
154 self.inner.peek().history.go_forward();
155 self.change_route();
156 }
157
158 pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {
162 let target = target.into();
163 {
164 let mut write = self.inner.write_unchecked();
165 match target {
166 NavigationTarget::Internal(p) => write.history.push(p),
167 NavigationTarget::External(e) => return write.external(e),
168 }
169 }
170
171 self.change_route();
172 None
173 }
174
175 pub fn replace(
179 &self,
180 target: impl Into<NavigationTarget>,
181 ) -> Option<ExternalNavigationFailure> {
182 let target = target.into();
183 {
184 let mut write = self.inner.write_unchecked();
185 match target {
186 NavigationTarget::Internal(p) => write.history.replace(p),
187 NavigationTarget::External(e) => return write.external(e),
188 }
189 }
190
191 self.change_route();
192 None
193 }
194
195 pub fn current<R: Routable>(&self) -> R {
197 let absolute_route = self.full_route_string();
198 let mapping = consume_child_route_mapping::<R>();
200 let route = match mapping.as_ref() {
201 Some(mapping) => mapping
202 .parse_route_from_root_route(&absolute_route)
203 .ok_or_else(|| "Failed to parse route".to_string()),
204 None => {
205 R::from_str(&absolute_route).map_err(|err| format!("Failed to parse route {err}"))
206 }
207 };
208
209 match route {
210 Ok(route) => route,
211 Err(_err) => "/".parse().unwrap_or_else(|err| panic!("{err}")),
212 }
213 }
214
215 pub fn full_route_string(&self) -> String {
217 let inner = self.inner.read();
218 inner.subscribe_to_current_context();
219
220 self.inner.peek().history.current_route()
221 }
222
223 pub fn site_map(&self) -> &'static [SiteMapSegment] {
225 self.inner.read().site_map
226 }
227
228 fn change_route(&self) {
229 self.inner.read().update_subscribers();
230 }
231
232 pub(crate) fn internal_route(&self, route: &str) -> bool {
233 (self.inner.read().internal_route)(route)
234 }
235}
236
237pub struct GenericRouterContext<R> {
239 inner: RouterContext,
240 _marker: std::marker::PhantomData<R>,
241}
242
243impl<R> GenericRouterContext<R>
244where
245 R: Routable,
246{
247 #[must_use]
249 pub fn can_go_back(&self) -> bool {
250 self.inner.can_go_back()
251 }
252
253 #[must_use]
255 pub fn can_go_forward(&self) -> bool {
256 self.inner.can_go_forward()
257 }
258
259 pub fn go_back(&self) {
263 self.inner.go_back();
264 }
265
266 pub fn go_forward(&self) {
270 self.inner.go_forward();
271 }
272
273 pub fn push(
277 &self,
278 target: impl Into<NavigationTarget<R>>,
279 ) -> Option<ExternalNavigationFailure> {
280 self.inner.push(target.into())
281 }
282
283 pub fn replace(
287 &self,
288 target: impl Into<NavigationTarget<R>>,
289 ) -> Option<ExternalNavigationFailure> {
290 self.inner.replace(target.into())
291 }
292
293 pub fn current(&self) -> R
295 where
296 R: Clone,
297 {
298 self.inner.current()
299 }
300}