dioxus_router/contexts/
router.rs1use std::{
2 collections::HashSet,
3 sync::{Arc, Mutex},
4};
5
6use dioxus_history::history;
7use dioxus_lib::prelude::*;
8
9use crate::{
10 components::child_router::consume_child_route_mapping, navigation::NavigationTarget,
11 prelude::SiteMapSegment, routable::Routable, router_cfg::RouterConfig,
12};
13
14#[derive(Clone, Copy)]
16struct RootRouterContext(Signal<Option<RouterContext>>);
17
18pub fn root_router() -> Option<RouterContext> {
22 if let Some(ctx) = ScopeId::ROOT.consume_context::<RootRouterContext>() {
23 ctx.0.cloned()
24 } else {
25 ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(None, ScopeId::ROOT)));
26 None
27 }
28}
29
30pub(crate) fn provide_router_context(ctx: RouterContext) {
31 if root_router().is_none() {
32 ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(
33 Some(ctx),
34 ScopeId::ROOT,
35 )));
36 }
37 provide_context(ctx);
38}
39
40#[derive(Debug, Clone)]
42pub struct ExternalNavigationFailure(pub String);
43
44pub(crate) type RoutingCallback<R> =
46 Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
47pub(crate) type AnyRoutingCallback = Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>;
48
49struct RouterContextInner {
50 unresolved_error: Option<ExternalNavigationFailure>,
51
52 subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,
53 routing_callback: Option<AnyRoutingCallback>,
54
55 failure_external_navigation: fn() -> Element,
56
57 internal_route: fn(&str) -> bool,
58
59 site_map: &'static [SiteMapSegment],
60}
61
62impl RouterContextInner {
63 fn update_subscribers(&self) {
64 for &id in self.subscribers.lock().unwrap().iter() {
65 id.mark_dirty();
66 }
67 }
68
69 fn subscribe_to_current_context(&self) {
70 if let Some(rc) = ReactiveContext::current() {
71 rc.subscribe(self.subscribers.clone());
72 }
73 }
74
75 fn external(&mut self, external: String) -> Option<ExternalNavigationFailure> {
76 match history().external(external.clone()) {
77 true => None,
78 false => {
79 let failure = ExternalNavigationFailure(external);
80 self.unresolved_error = Some(failure.clone());
81
82 self.update_subscribers();
83
84 Some(failure)
85 }
86 }
87 }
88}
89
90#[derive(Clone, Copy)]
92pub struct RouterContext {
93 inner: CopyValue<RouterContextInner>,
94}
95
96impl RouterContext {
97 pub(crate) fn new<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self
98 where
99 <R as std::str::FromStr>::Err: std::fmt::Display,
100 {
101 let subscribers = Arc::new(Mutex::new(HashSet::new()));
102 let mapping = consume_child_route_mapping();
103
104 let myself = RouterContextInner {
105 unresolved_error: None,
106 subscribers: subscribers.clone(),
107 routing_callback: cfg.on_update.map(|update| {
108 Arc::new(move |ctx| {
109 let ctx = GenericRouterContext {
110 inner: ctx,
111 _marker: std::marker::PhantomData,
112 };
113 update(ctx).map(|t| match t {
114 NavigationTarget::Internal(r) => match mapping.as_ref() {
115 Some(mapping) => {
116 NavigationTarget::Internal(mapping.format_route_as_root_route(r))
117 }
118 None => NavigationTarget::Internal(r.to_string()),
119 },
120 NavigationTarget::External(s) => NavigationTarget::External(s),
121 })
122 }) as Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>
123 }),
124
125 failure_external_navigation: cfg.failure_external_navigation,
126
127 internal_route: |route| R::from_str(route).is_ok(),
128
129 site_map: R::SITE_MAP,
130 };
131
132 history().updater(Arc::new(move || {
134 for &rc in subscribers.lock().unwrap().iter() {
135 rc.mark_dirty();
136 }
137 }));
138
139 Self {
140 inner: CopyValue::new_in_scope(myself, ScopeId::ROOT),
141 }
142 }
143
144 pub(crate) fn include_prevent_default(&self) -> bool {
147 history().include_prevent_default()
148 }
149
150 #[must_use]
152 pub fn can_go_back(&self) -> bool {
153 history().can_go_back()
154 }
155
156 #[must_use]
158 pub fn can_go_forward(&self) -> bool {
159 history().can_go_forward()
160 }
161
162 pub fn go_back(&self) {
166 history().go_back();
167 self.change_route();
168 }
169
170 pub fn go_forward(&self) {
174 history().go_forward();
175 self.change_route();
176 }
177
178 pub(crate) fn push_any(&self, target: NavigationTarget) -> Option<ExternalNavigationFailure> {
179 {
180 let mut write = self.inner.write_unchecked();
181 match target {
182 NavigationTarget::Internal(p) => history().push(p),
183 NavigationTarget::External(e) => return write.external(e),
184 }
185 }
186
187 self.change_route()
188 }
189
190 pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {
194 let target = target.into();
195 {
196 let mut write = self.inner.write_unchecked();
197 match target {
198 NavigationTarget::Internal(p) => {
199 let history = history();
200 history.push(p)
201 }
202 NavigationTarget::External(e) => return write.external(e),
203 }
204 }
205
206 self.change_route()
207 }
208
209 pub fn replace(
213 &self,
214 target: impl Into<NavigationTarget>,
215 ) -> Option<ExternalNavigationFailure> {
216 let target = target.into();
217 {
218 let mut state = self.inner.write_unchecked();
219 match target {
220 NavigationTarget::Internal(p) => {
221 let history = history();
222 history.replace(p)
223 }
224 NavigationTarget::External(e) => return state.external(e),
225 }
226 }
227
228 self.change_route()
229 }
230
231 pub fn current<R: Routable>(&self) -> R {
233 let absolute_route = self.full_route_string();
234 let mapping = consume_child_route_mapping::<R>();
236 match mapping.as_ref() {
237 Some(mapping) => mapping
238 .parse_route_from_root_route(&absolute_route)
239 .unwrap_or_else(|| {
240 panic!("route's display implementation must be parsable by FromStr")
241 }),
242 None => R::from_str(&absolute_route).unwrap_or_else(|_| {
243 panic!("route's display implementation must be parsable by FromStr")
244 }),
245 }
246 }
247
248 pub fn full_route_string(&self) -> String {
250 let inner = self.inner.read();
251 inner.subscribe_to_current_context();
252 let history = history();
253 history.current_route()
254 }
255
256 pub fn prefix(&self) -> Option<String> {
258 let history = history();
259 history.current_prefix()
260 }
261
262 pub fn clear_error(&self) {
264 let mut write_inner = self.inner.write_unchecked();
265 write_inner.unresolved_error = None;
266
267 write_inner.update_subscribers();
268 }
269
270 pub fn site_map(&self) -> &'static [SiteMapSegment] {
272 self.inner.read().site_map
273 }
274
275 pub(crate) fn render_error(&self) -> Option<Element> {
276 let inner_write = self.inner.write_unchecked();
277 inner_write.subscribe_to_current_context();
278 inner_write
279 .unresolved_error
280 .as_ref()
281 .map(|_| (inner_write.failure_external_navigation)())
282 }
283
284 fn change_route(&self) -> Option<ExternalNavigationFailure> {
285 let self_read = self.inner.read();
286 if let Some(callback) = &self_read.routing_callback {
287 let myself = *self;
288 let callback = callback.clone();
289 drop(self_read);
290 if let Some(new) = callback(myself) {
291 let mut self_write = self.inner.write_unchecked();
292 match new {
293 NavigationTarget::Internal(p) => {
294 let history = history();
295 history.replace(p)
296 }
297 NavigationTarget::External(e) => return self_write.external(e),
298 }
299 }
300 }
301
302 self.inner.read().update_subscribers();
303
304 None
305 }
306
307 pub(crate) fn internal_route(&self, route: &str) -> bool {
308 (self.inner.read().internal_route)(route)
309 }
310}
311
312pub struct GenericRouterContext<R> {
313 inner: RouterContext,
314 _marker: std::marker::PhantomData<R>,
315}
316
317impl<R> GenericRouterContext<R>
318where
319 R: Routable,
320{
321 #[must_use]
323 pub fn can_go_back(&self) -> bool {
324 self.inner.can_go_back()
325 }
326
327 #[must_use]
329 pub fn can_go_forward(&self) -> bool {
330 self.inner.can_go_forward()
331 }
332
333 pub fn go_back(&self) {
337 self.inner.go_back();
338 }
339
340 pub fn go_forward(&self) {
344 self.inner.go_forward();
345 }
346
347 pub fn push(
351 &self,
352 target: impl Into<NavigationTarget<R>>,
353 ) -> Option<ExternalNavigationFailure> {
354 self.inner.push(target.into())
355 }
356
357 pub fn replace(
361 &self,
362 target: impl Into<NavigationTarget<R>>,
363 ) -> Option<ExternalNavigationFailure> {
364 self.inner.replace(target.into())
365 }
366
367 pub fn current(&self) -> R
369 where
370 R: Clone,
371 {
372 self.inner.current()
373 }
374
375 pub fn prefix(&self) -> Option<String> {
377 self.inner.prefix()
378 }
379
380 pub fn clear_error(&self) {
382 self.inner.clear_error()
383 }
384}