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 new<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 try_get() -> Option<Self> {
101 try_consume_context()
102 }
103
104 pub fn get() -> Self {
105 consume_context()
106 }
107
108 #[must_use]
110 pub fn can_go_back(&self) -> bool {
111 self.inner.peek().history.can_go_back()
112 }
113
114 #[must_use]
116 pub fn can_go_forward(&self) -> bool {
117 self.inner.peek().history.can_go_forward()
118 }
119
120 pub fn go_back(&self) {
124 self.inner.peek().history.go_back();
125 self.change_route();
126 }
127
128 pub fn go_forward(&self) {
132 self.inner.peek().history.go_forward();
133 self.change_route();
134 }
135
136 pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {
140 let target = target.into();
141 {
142 let mut write = self.inner.write_unchecked();
143 match target {
144 NavigationTarget::Internal(p) => write.history.push(p),
145 NavigationTarget::External(e) => return write.external(e),
146 }
147 }
148
149 self.change_route();
150 None
151 }
152
153 pub fn replace(
157 &self,
158 target: impl Into<NavigationTarget>,
159 ) -> Option<ExternalNavigationFailure> {
160 let target = target.into();
161 {
162 let mut write = self.inner.write_unchecked();
163 match target {
164 NavigationTarget::Internal(p) => write.history.replace(p),
165 NavigationTarget::External(e) => return write.external(e),
166 }
167 }
168
169 self.change_route();
170 None
171 }
172
173 pub fn current<R: Routable>(&self) -> R {
175 let absolute_route = self.full_route_string();
176 let mapping = consume_child_route_mapping::<R>();
178 let route = match mapping.as_ref() {
179 Some(mapping) => mapping
180 .parse_route_from_root_route(&absolute_route)
181 .ok_or_else(|| "Failed to parse route".to_string()),
182 None => {
183 R::from_str(&absolute_route).map_err(|err| format!("Failed to parse route {err}"))
184 }
185 };
186
187 match route {
188 Ok(route) => route,
189 Err(_err) => "/".parse().unwrap_or_else(|err| panic!("{err}")),
190 }
191 }
192
193 pub fn full_route_string(&self) -> String {
195 let inner = self.inner.read();
196 inner.subscribe_to_current_context();
197
198 self.inner.peek().history.current_route()
199 }
200
201 pub fn site_map(&self) -> &'static [SiteMapSegment] {
203 self.inner.read().site_map
204 }
205
206 fn change_route(&self) {
207 self.inner.read().update_subscribers();
208 }
209
210 pub(crate) fn internal_route(&self, route: &str) -> bool {
211 (self.inner.read().internal_route)(route)
212 }
213}
214
215pub struct GenericRouterContext<R> {
217 inner: RouterContext,
218 _marker: std::marker::PhantomData<R>,
219}
220
221impl<R> GenericRouterContext<R>
222where
223 R: Routable,
224{
225 #[must_use]
227 pub fn can_go_back(&self) -> bool {
228 self.inner.can_go_back()
229 }
230
231 #[must_use]
233 pub fn can_go_forward(&self) -> bool {
234 self.inner.can_go_forward()
235 }
236
237 pub fn go_back(&self) {
241 self.inner.go_back();
242 }
243
244 pub fn go_forward(&self) {
248 self.inner.go_forward();
249 }
250
251 pub fn push(
255 &self,
256 target: impl Into<NavigationTarget<R>>,
257 ) -> Option<ExternalNavigationFailure> {
258 self.inner.push(target.into())
259 }
260
261 pub fn replace(
265 &self,
266 target: impl Into<NavigationTarget<R>>,
267 ) -> Option<ExternalNavigationFailure> {
268 self.inner.replace(target.into())
269 }
270
271 pub fn current(&self) -> R
273 where
274 R: Clone,
275 {
276 self.inner.current()
277 }
278}