@@ -9,7 +9,7 @@ use crate::{
9
9
} ,
10
10
state:: { FrozenApp , GlobalState , PageStateStore , ThawPrefs } ,
11
11
templates:: { RouterLoadState , RouterState , TemplateNodeType } ,
12
- DomNode , ErrorPages ,
12
+ DomNode , ErrorPages , Html ,
13
13
} ;
14
14
use std:: cell:: RefCell ;
15
15
use std:: rc:: Rc ;
@@ -35,6 +35,106 @@ const ROUTE_ANNOUNCER_STYLES: &str = r#"
35
35
word-wrap: normal;
36
36
"# ;
37
37
38
+ /// The properties that `on_route_change` takes.
39
+ #[ derive( Debug , Clone ) ]
40
+ struct OnRouteChangeProps < G : Html > {
41
+ locales : Rc < Locales > ,
42
+ container_rx : NodeRef < G > ,
43
+ router_state : RouterState ,
44
+ pss : PageStateStore ,
45
+ global_state : GlobalState ,
46
+ frozen_app : Rc < RefCell < Option < ( FrozenApp , ThawPrefs ) > > > ,
47
+ translations_manager : Rc < RefCell < ClientTranslationsManager > > ,
48
+ error_pages : Rc < ErrorPages < DomNode > > ,
49
+ initial_container : Option < Element > ,
50
+ }
51
+
52
+ /// The function that runs when a route change takes place. This can also be run at any time to force the current page to reload.
53
+ fn on_route_change < G : Html > (
54
+ verdict : RouteVerdict < TemplateNodeType > ,
55
+ OnRouteChangeProps {
56
+ locales,
57
+ container_rx,
58
+ router_state,
59
+ pss,
60
+ global_state,
61
+ frozen_app,
62
+ translations_manager,
63
+ error_pages,
64
+ initial_container,
65
+ } : OnRouteChangeProps < G > ,
66
+ ) {
67
+ wasm_bindgen_futures:: spawn_local ( async move {
68
+ let container_rx_elem = container_rx
69
+ . get :: < DomNode > ( )
70
+ . unchecked_into :: < web_sys:: Element > ( ) ;
71
+ checkpoint ( "router_entry" ) ;
72
+ match & verdict {
73
+ // Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
74
+ // If a non-404 error occurred, it will be handled in the app shell
75
+ RouteVerdict :: Found ( RouteInfo {
76
+ path,
77
+ template,
78
+ locale,
79
+ was_incremental_match,
80
+ } ) => {
81
+ app_shell (
82
+ // TODO Make this not allocate so much
83
+ ShellProps {
84
+ path : path. clone ( ) ,
85
+ template : template. clone ( ) ,
86
+ was_incremental_match : * was_incremental_match,
87
+ locale : locale. clone ( ) ,
88
+ router_state,
89
+ translations_manager,
90
+ error_pages,
91
+ initial_container : initial_container. unwrap ( ) ,
92
+ container_rx_elem,
93
+ page_state_store : pss,
94
+ global_state,
95
+ frozen_app,
96
+ route_verdict : verdict,
97
+ } ,
98
+ )
99
+ . await
100
+ }
101
+ // If the user is using i18n, then they'll want to detect the locale on any paths missing a locale
102
+ // Those all go to the same system that redirects to the appropriate locale
103
+ // Note that `container` doesn't exist for this scenario
104
+ RouteVerdict :: LocaleDetection ( path) => detect_locale ( path. clone ( ) , & locales) ,
105
+ // To get a translator here, we'd have to go async and dangerously check the URL
106
+ // If this is an initial load, there'll already be an error message, so we should only proceed if the declaration is not `error`
107
+ // BUG If we have an error in a subsequent load, the error message appears below the current page...
108
+ RouteVerdict :: NotFound => {
109
+ checkpoint ( "not_found" ) ;
110
+ if let InitialState :: Error ( ErrorPageData { url, status, err } ) = get_initial_state ( )
111
+ {
112
+ let initial_container = initial_container. unwrap ( ) ;
113
+ // We need to move the server-rendered content from its current container to the reactive container (otherwise Sycamore can't work with it properly)
114
+ // If we're not hydrating, there's no point in moving anything over, we'll just fully re-render
115
+ #[ cfg( feature = "hydrate" ) ]
116
+ {
117
+ let initial_html = initial_container. inner_html ( ) ;
118
+ container_rx_elem. set_inner_html ( & initial_html) ;
119
+ }
120
+ initial_container. set_inner_html ( "" ) ;
121
+ // Make the initial container invisible
122
+ initial_container
123
+ . set_attribute ( "style" , "display: none;" )
124
+ . unwrap ( ) ;
125
+ // Hydrate the error pages
126
+ // Right now, we don't provide translators to any error pages that have come from the server
127
+ error_pages. render_page ( & url, status, & err, None , & container_rx_elem) ;
128
+ } else {
129
+ // This is an error from navigating within the app (probably the dev mistyped a link...), so we'll clear the page
130
+ container_rx_elem. set_inner_html ( "" ) ;
131
+ error_pages. render_page ( "" , 404 , "not found" , None , & container_rx_elem) ;
132
+ }
133
+ }
134
+ } ;
135
+ } ) ;
136
+ }
137
+
38
138
/// The properties that the router takes.
39
139
#[ derive( Debug ) ]
40
140
pub struct PerseusRouterProps {
@@ -133,73 +233,43 @@ pub fn perseus_router<AppRoute: PerseusRoute<TemplateNodeType> + 'static>(
133
233
} ) ,
134
234
) ;
135
235
236
+ // Set up the function we'll call on a route change
237
+ // Set up the properties for the function we'll call in a route change
238
+ let on_route_change_props = OnRouteChangeProps {
239
+ locales,
240
+ container_rx : container_rx. clone ( ) ,
241
+ router_state : router_state. clone ( ) ,
242
+ pss,
243
+ global_state,
244
+ frozen_app,
245
+ translations_manager,
246
+ error_pages,
247
+ initial_container,
248
+ } ;
249
+
250
+ // Listen for changes to the reload commander and reload as appropriate
251
+ let reload_commander = router_state. reload_commander . clone ( ) ;
252
+ create_effect (
253
+ cloned ! ( router_state, reload_commander, on_route_change_props => move || {
254
+ // This is just a flip-flop, but we need to add it to the effect's dependencies
255
+ let _ = reload_commander. get( ) ;
256
+ // Get the route verdict and re-run the function we use on route changes
257
+ let verdict = match router_state. get_last_verdict( ) {
258
+ Some ( verdict) => verdict,
259
+ // If the first page hasn't loaded yet, terminate now
260
+ None => return
261
+ } ;
262
+ on_route_change( verdict, on_route_change_props. clone( ) ) ;
263
+ } ) ,
264
+ ) ;
265
+
136
266
view ! {
137
- Router ( RouterProps :: new( HistoryIntegration :: new( ) , move |route: ReadSignal <AppRoute >| {
138
- create_effect( cloned!( ( container_rx) => move || {
139
- // Sycamore's reactivity is broken by a future, so we need to explicitly add the route to the reactive dependencies here
140
- // We do need the future though (otherwise `container_rx` doesn't link to anything until it's too late)
141
- let _ = route. get( ) ;
142
- wasm_bindgen_futures:: spawn_local( cloned!( ( locales, route, container_rx, router_state, pss, global_state, frozen_app, translations_manager, error_pages, initial_container) => async move {
143
- let container_rx_elem = container_rx. get:: <DomNode >( ) . unchecked_into:: <web_sys:: Element >( ) ;
144
- checkpoint( "router_entry" ) ;
145
- match & route. get( ) . as_ref( ) . get_verdict( ) {
146
- // Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
147
- // If a non-404 error occurred, it will be handled in the app shell
148
- RouteVerdict :: Found ( RouteInfo {
149
- path,
150
- template,
151
- locale,
152
- was_incremental_match
153
- } ) => app_shell(
154
- // TODO Make this not allocate so much...
155
- ShellProps {
156
- path: path. clone( ) ,
157
- template: template. clone( ) ,
158
- was_incremental_match: * was_incremental_match,
159
- locale: locale. clone( ) ,
160
- router_state: router_state. clone( ) ,
161
- translations_manager: translations_manager. clone( ) ,
162
- error_pages: error_pages. clone( ) ,
163
- initial_container: initial_container. unwrap( ) . clone( ) ,
164
- container_rx_elem: container_rx_elem. clone( ) ,
165
- page_state_store: pss. clone( ) ,
166
- global_state: global_state. clone( ) ,
167
- frozen_app
168
- }
169
- ) . await ,
170
- // If the user is using i18n, then they'll want to detect the locale on any paths missing a locale
171
- // Those all go to the same system that redirects to the appropriate locale
172
- // Note that `container` doesn't exist for this scenario
173
- RouteVerdict :: LocaleDetection ( path) => detect_locale( path. clone( ) , & locales) ,
174
- // To get a translator here, we'd have to go async and dangerously check the URL
175
- // If this is an initial load, there'll already be an error message, so we should only proceed if the declaration is not `error`
176
- // BUG If we have an error in a subsequent load, the error message appears below the current page...
177
- RouteVerdict :: NotFound => {
178
- checkpoint( "not_found" ) ;
179
- if let InitialState :: Error ( ErrorPageData { url, status, err } ) = get_initial_state( ) {
180
- let initial_container = initial_container. unwrap( ) ;
181
- // We need to move the server-rendered content from its current container to the reactive container (otherwise Sycamore can't work with it properly)
182
- // If we're not hydrating, there's no point in moving anything over, we'll just fully re-render
183
- #[ cfg( feature = "hydrate" ) ]
184
- {
185
- let initial_html = initial_container. inner_html( ) ;
186
- container_rx_elem. set_inner_html( & initial_html) ;
187
- }
188
- initial_container. set_inner_html( "" ) ;
189
- // Make the initial container invisible
190
- initial_container. set_attribute( "style" , "display: none;" ) . unwrap( ) ;
191
- // Hydrate the error pages
192
- // Right now, we don't provide translators to any error pages that have come from the server
193
- error_pages. render_page( & url, status, & err, None , & container_rx_elem) ;
194
- } else {
195
- // This is an error from navigating within the app (probably the dev mistyped a link...), so we'll clear the page
196
- container_rx_elem. set_inner_html( "" ) ;
197
- error_pages. render_page( "" , 404 , "not found" , None , & container_rx_elem) ;
198
- }
199
- } ,
200
- } ;
201
- } ) ) ;
202
- } ) ) ;
267
+ Router ( RouterProps :: new( HistoryIntegration :: new( ) , cloned!( on_route_change_props => move |route: ReadSignal <AppRoute >| {
268
+ // Sycamore's reactivity is broken by a future, so we need to explicitly add the route to the reactive dependencies here
269
+ // We do need the future though (otherwise `container_rx` doesn't link to anything until it's too late)
270
+ let verdict = route. get( ) . get_verdict( ) . clone( ) ;
271
+ on_route_change( verdict, on_route_change_props) ;
272
+
203
273
// This template is reactive, and will be updated as necessary
204
274
// However, the server has already rendered initial load content elsewhere, so we move that into here as well in the app shell
205
275
// The main reason for this is that the router only intercepts click events from its children
@@ -209,6 +279,6 @@ pub fn perseus_router<AppRoute: PerseusRoute<TemplateNodeType> + 'static>(
209
279
p( id = "__perseus_route_announcer" , aria_live = "assertive" , role = "alert" , style = ROUTE_ANNOUNCER_STYLES ) { ( route_announcement. get( ) ) }
210
280
}
211
281
}
212
- } ) )
282
+ } ) ) )
213
283
}
214
284
}
0 commit comments