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,
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 {
122 let subscribers = Rc::new(RefCell::new(FxHashSet::default()));
123
124 let history = if let Some(initial_path) = cfg.initial_path {
125 MemoryHistory::with_initial_path(initial_path)
126 } else {
127 MemoryHistory::default()
128 };
129
130 Self {
131 inner: State::create_global(RouterContextInner {
132 subscribers,
133
134 internal_route: |route| R::from_str(route).is_ok(),
135
136 site_map: R::SITE_MAP,
137
138 history,
139 }),
140 }
141 }
142
143 pub fn try_get() -> Option<Self> {
144 try_consume_context()
145 }
146
147 pub fn get() -> Self {
148 consume_context()
149 }
150
151 #[must_use]
153 pub fn can_go_back(&self) -> bool {
154 self.inner.peek().history.can_go_back()
155 }
156
157 #[must_use]
159 pub fn can_go_forward(&self) -> bool {
160 self.inner.peek().history.can_go_forward()
161 }
162
163 pub fn go_back(&self) {
167 self.inner.peek().history.go_back();
168 self.change_route();
169 }
170
171 pub fn go_forward(&self) {
175 self.inner.peek().history.go_forward();
176 self.change_route();
177 }
178
179 pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {
183 let target = target.into();
184 {
185 let mut write = self.inner.write_unchecked();
186 match target {
187 NavigationTarget::Internal(p) => write.history.push(p),
188 NavigationTarget::External(e) => return write.external(e),
189 }
190 }
191
192 self.change_route();
193 None
194 }
195
196 pub fn replace(
200 &self,
201 target: impl Into<NavigationTarget>,
202 ) -> Option<ExternalNavigationFailure> {
203 let target = target.into();
204 {
205 let mut write = self.inner.write_unchecked();
206 match target {
207 NavigationTarget::Internal(p) => write.history.replace(p),
208 NavigationTarget::External(e) => return write.external(e),
209 }
210 }
211
212 self.change_route();
213 None
214 }
215
216 pub fn current<R: Routable>(&self) -> R {
218 let absolute_route = self.full_route_string();
219 let mapping = consume_child_route_mapping::<R>();
221 let route = match mapping.as_ref() {
222 Some(mapping) => mapping
223 .parse_route_from_root_route(&absolute_route)
224 .ok_or_else(|| "Failed to parse route".to_string()),
225 None => {
226 R::from_str(&absolute_route).map_err(|err| format!("Failed to parse route {err}"))
227 }
228 };
229
230 match route {
231 Ok(route) => route,
232 Err(_err) => "/".parse().unwrap_or_else(|err| panic!("{err}")),
233 }
234 }
235
236 pub fn full_route_string(&self) -> String {
238 let inner = self.inner.read();
239 inner.subscribe_to_current_context();
240
241 self.inner.peek().history.current_route()
242 }
243
244 pub fn site_map(&self) -> &'static [SiteMapSegment] {
246 self.inner.read().site_map
247 }
248
249 fn change_route(&self) {
250 self.inner.read().update_subscribers();
251 }
252
253 pub(crate) fn internal_route(&self, route: &str) -> bool {
254 (self.inner.read().internal_route)(route)
255 }
256}
257
258pub struct GenericRouterContext<R> {
260 inner: RouterContext,
261 _marker: std::marker::PhantomData<R>,
262}
263
264impl<R> GenericRouterContext<R>
265where
266 R: Routable,
267{
268 #[must_use]
270 pub fn can_go_back(&self) -> bool {
271 self.inner.can_go_back()
272 }
273
274 #[must_use]
276 pub fn can_go_forward(&self) -> bool {
277 self.inner.can_go_forward()
278 }
279
280 pub fn go_back(&self) {
284 self.inner.go_back();
285 }
286
287 pub fn go_forward(&self) {
291 self.inner.go_forward();
292 }
293
294 pub fn push(
298 &self,
299 target: impl Into<NavigationTarget<R>>,
300 ) -> Option<ExternalNavigationFailure> {
301 self.inner.push(target.into())
302 }
303
304 pub fn replace(
308 &self,
309 target: impl Into<NavigationTarget<R>>,
310 ) -> Option<ExternalNavigationFailure> {
311 self.inner.replace(target.into())
312 }
313
314 pub fn current(&self) -> R
316 where
317 R: Clone,
318 {
319 self.inner.current()
320 }
321}