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
39impl Error for ExternalNavigationFailure {}
40impl Display for ExternalNavigationFailure {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 write!(f, "External navigation failed: {}", self.0)
43 }
44}
45
46struct RouterContextInner {
47 subscribers: Rc<RefCell<FxHashSet<ReactiveContext>>>,
48
49 internal_route: fn(&str) -> bool,
50
51 site_map: &'static [SiteMapSegment],
52
53 history: MemoryHistory,
54}
55
56impl RouterContextInner {
57 fn update_subscribers(&self) {
58 for id in self.subscribers.borrow().iter() {
59 id.notify();
60 }
61 }
62
63 fn subscribe_to_current_context(&self) {
64 if let Some(mut rc) = ReactiveContext::try_current() {
65 rc.subscribe(&self.subscribers);
66 }
67 }
68
69 fn external(&mut self, external: String) -> Result<(), ExternalNavigationFailure> {
70 let failure = ExternalNavigationFailure(external);
71
72 self.update_subscribers();
73
74 Err(failure)
75 }
76}
77
78#[derive(Clone, Copy)]
80pub struct RouterContext {
81 inner: State<RouterContextInner>,
82}
83
84impl RouterContext {
85 pub(crate) fn create<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {
86 let subscribers = Rc::new(RefCell::new(FxHashSet::default()));
87
88 let history = if let Some(initial_path) = cfg.initial_path {
89 MemoryHistory::with_initial_path(initial_path)
90 } else {
91 MemoryHistory::default()
92 };
93
94 Self {
95 inner: State::create(RouterContextInner {
96 subscribers,
97
98 internal_route: |route| R::from_str(route).is_ok(),
99
100 site_map: R::SITE_MAP,
101
102 history,
103 }),
104 }
105 }
106
107 pub fn create_global<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {
129 let subscribers = Rc::new(RefCell::new(FxHashSet::default()));
130
131 let history = if let Some(initial_path) = cfg.initial_path {
132 MemoryHistory::with_initial_path(initial_path)
133 } else {
134 MemoryHistory::default()
135 };
136
137 Self {
138 inner: State::create_global(RouterContextInner {
139 subscribers,
140
141 internal_route: |route| R::from_str(route).is_ok(),
142
143 site_map: R::SITE_MAP,
144
145 history,
146 }),
147 }
148 }
149
150 pub fn try_get() -> Option<Self> {
151 try_consume_context()
152 }
153
154 pub fn get() -> Self {
155 consume_context()
156 }
157
158 #[must_use]
160 pub fn can_go_back(&self) -> bool {
161 self.inner.peek().history.can_go_back()
162 }
163
164 #[must_use]
166 pub fn can_go_forward(&self) -> bool {
167 self.inner.peek().history.can_go_forward()
168 }
169
170 pub fn go_back(&self) {
174 self.inner.peek().history.go_back();
175 self.change_route();
176 }
177
178 pub fn go_forward(&self) {
182 self.inner.peek().history.go_forward();
183 self.change_route();
184 }
185
186 pub fn push(
190 &self,
191 target: impl Into<NavigationTarget>,
192 ) -> Result<(), ExternalNavigationFailure> {
193 let target = target.into();
194 {
195 let mut write = self.inner.write_unchecked();
196 match target {
197 NavigationTarget::Internal(p) => write.history.push(p),
198 NavigationTarget::External(e) => return write.external(e),
199 }
200 }
201
202 self.change_route();
203 Ok(())
204 }
205
206 pub fn replace(
210 &self,
211 target: impl Into<NavigationTarget>,
212 ) -> Result<(), ExternalNavigationFailure> {
213 let target = target.into();
214 {
215 let mut write = self.inner.write_unchecked();
216 match target {
217 NavigationTarget::Internal(p) => write.history.replace(p),
218 NavigationTarget::External(e) => return write.external(e),
219 }
220 }
221
222 self.change_route();
223 Ok(())
224 }
225
226 pub fn current<R: Routable>(&self) -> R {
228 let absolute_route = self.full_route_string();
229 let mapping = consume_child_route_mapping::<R>();
231 let route = match mapping.as_ref() {
232 Some(mapping) => mapping
233 .parse_route_from_root_route(&absolute_route)
234 .ok_or_else(|| "Failed to parse route".to_string()),
235 None => {
236 R::from_str(&absolute_route).map_err(|err| format!("Failed to parse route {err}"))
237 }
238 };
239
240 match route {
241 Ok(route) => route,
242 Err(_err) => "/".parse().unwrap_or_else(|err| panic!("{err}")),
243 }
244 }
245
246 pub fn full_route_string(&self) -> String {
248 let inner = self.inner.read();
249 inner.subscribe_to_current_context();
250
251 self.inner.peek().history.current_route()
252 }
253
254 pub fn site_map(&self) -> &'static [SiteMapSegment] {
256 self.inner.read().site_map
257 }
258
259 fn change_route(&self) {
260 self.inner.read().update_subscribers();
261 }
262
263 pub(crate) fn internal_route(&self, route: &str) -> bool {
264 (self.inner.read().internal_route)(route)
265 }
266}
267
268pub struct GenericRouterContext<R> {
270 inner: RouterContext,
271 _marker: std::marker::PhantomData<R>,
272}
273
274impl<R> GenericRouterContext<R>
275where
276 R: Routable,
277{
278 #[must_use]
280 pub fn can_go_back(&self) -> bool {
281 self.inner.can_go_back()
282 }
283
284 #[must_use]
286 pub fn can_go_forward(&self) -> bool {
287 self.inner.can_go_forward()
288 }
289
290 pub fn go_back(&self) {
294 self.inner.go_back();
295 }
296
297 pub fn go_forward(&self) {
301 self.inner.go_forward();
302 }
303
304 pub fn push(
308 &self,
309 target: impl Into<NavigationTarget<R>>,
310 ) -> Result<(), ExternalNavigationFailure> {
311 self.inner.push(target.into())
312 }
313
314 pub fn replace(
318 &self,
319 target: impl Into<NavigationTarget<R>>,
320 ) -> Result<(), ExternalNavigationFailure> {
321 self.inner.replace(target.into())
322 }
323
324 pub fn current(&self) -> R
326 where
327 R: Clone,
328 {
329 self.inner.current()
330 }
331}