1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::ParsedRoute;
use crate::{cfg::RouterCfg, RouteEvent, RouterCore};
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use futures_util::stream::StreamExt;
use std::sync::Arc;

/// The props for the [`Router`](fn.Router.html) component.
#[derive(Props)]
pub struct RouterProps<'a> {
    /// The routes and elements that should be rendered when the path matches.
    ///
    /// If elements are not contained within Routes, the will be rendered
    /// regardless of the path.
    pub children: Element<'a>,

    /// The URL to point at
    ///
    /// This will be used to trim any latent segments from the URL when your app is
    /// not deployed to the root of the domain.
    #[props(optional)]
    pub base_url: Option<&'a str>,

    /// Hook into the router when the route is changed.
    ///
    /// This lets you easily implement redirects
    #[props(default)]
    pub onchange: EventHandler<'a, Arc<RouterCore>>,
}

/// A component that conditionally renders children based on the current location of the app.
///
/// Uses BrowserRouter in the browser and HashRouter everywhere else.
///
/// Will fallback to HashRouter is BrowserRouter is not available, or through configuration.
#[allow(non_snake_case)]
pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
    let svc = cx.use_hook(|_| {
        let (tx, mut rx) = futures_channel::mpsc::unbounded::<RouteEvent>();

        let base_url = cx.props.base_url.map(|s| s.to_string());

        let svc = RouterCore::new(tx, RouterCfg { base_url });

        cx.spawn({
            let svc = svc.clone();
            let regen_route = cx.schedule_update_any();
            let router_id = cx.scope_id();

            async move {
                while let Some(msg) = rx.next().await {
                    match msg {
                        RouteEvent::Push {
                            route,
                            serialized_state,
                            title,
                        } => {
                            let new_route = Arc::new(ParsedRoute {
                                url: svc.current_location().url.join(&route).ok().unwrap(),
                                title,
                                serialized_state,
                            });

                            svc.history.push(&new_route);
                            svc.stack.borrow_mut().push(new_route);
                        }

                        RouteEvent::Replace {
                            route,
                            title,
                            serialized_state,
                        } => {
                            let new_route = Arc::new(ParsedRoute {
                                url: svc.current_location().url.join(&route).ok().unwrap(),
                                title,
                                serialized_state,
                            });

                            svc.history.replace(&new_route);
                            *svc.stack.borrow_mut().last_mut().unwrap() = new_route;
                        }

                        RouteEvent::Pop => {
                            let mut stack = svc.stack.borrow_mut();

                            if stack.len() == 1 {
                                continue;
                            }

                            stack.pop();
                        }
                    }

                    svc.route_found.set(None);

                    regen_route(router_id);

                    for listener in svc.onchange_listeners.borrow().iter() {
                        regen_route(*listener);
                    }

                    for route in svc.slots.borrow().keys() {
                        regen_route(*route);
                    }
                }
            }
        });

        cx.provide_context(svc)
    });

    // next time we run the rout_found will be filled
    if svc.route_found.get().is_none() {
        cx.props.onchange.call(svc.clone());
    }

    cx.render(rsx!(&cx.props.children))
}