yggdrasil/bridges/
navigation.rs1use serde::{de::DeserializeOwned, Serialize};
8
9pub fn push(route: &str) -> bool {
11 #[cfg(target_arch = "wasm32")]
12 if let Some(module) = wasm::get_module() {
13 module.push(route);
14 return true;
15 }
16 let _ = route;
17 false
18}
19
20pub fn replace(route: &str) -> bool {
22 #[cfg(target_arch = "wasm32")]
23 if let Some(module) = wasm::get_module() {
24 module.replace(route);
25 return true;
26 }
27 let _ = route;
28 false
29}
30
31pub fn back() -> bool {
33 #[cfg(target_arch = "wasm32")]
34 if let Some(module) = wasm::get_module() {
35 module.back();
36 return true;
37 }
38 false
39}
40
41pub fn forward() -> bool {
43 #[cfg(target_arch = "wasm32")]
44 if let Some(module) = wasm::get_module() {
45 module.forward();
46 return true;
47 }
48 false
49}
50
51pub fn external(url: &str) -> bool {
53 #[cfg(target_arch = "wasm32")]
54 if let Some(module) = wasm::get_module() {
55 return module.external(url);
56 }
57 let _ = url;
58 false
59}
60
61#[cfg(target_arch = "wasm32")]
66pub fn connect(
67 notify: &wasm_bindgen::prelude::Closure<dyn FnMut()>,
68 prefetch: &wasm_bindgen::prelude::Closure<dyn FnMut(String, bool)>,
69) -> bool {
70 let Some(module) = wasm::get_module() else {
71 return false;
72 };
73 if module.connect(notify, None, prefetch).is_err() {
74 let _ = module.disconnect();
75 return false;
76 }
77 true
78}
79
80#[cfg(target_arch = "wasm32")]
82pub fn disconnect() {
83 if let Some(module) = wasm::get_module() {
84 let _ = module.disconnect();
85 }
86}
87
88pub fn read_state<T: DeserializeOwned>(key: &str) -> Option<T> {
90 #[cfg(target_arch = "wasm32")]
91 {
92 let value = wasm::get_module()?.read_state(key)?;
93 serde_json::from_slice(value.as_bytes()).ok()
94 }
95 #[cfg(not(target_arch = "wasm32"))]
96 {
97 let _ = key;
98 None
99 }
100}
101
102pub fn write_state<T: Serialize>(entry_id: &str, key: &str, value: &T) {
104 #[cfg(target_arch = "wasm32")]
105 if let (Some(module), Ok(value)) = (wasm::get_module(), serde_json::to_string(value)) {
106 module.write_state(entry_id, key, &value);
107 }
108 #[cfg(not(target_arch = "wasm32"))]
109 let _ = (entry_id, key, value);
110}
111
112pub fn entry_id() -> String {
114 #[cfg(target_arch = "wasm32")]
115 if let Some(module) = wasm::get_module() {
116 return module.entry_id();
117 }
118 String::new()
119}
120
121pub fn clear_admin_state() {
123 #[cfg(target_arch = "wasm32")]
124 if let Some(module) = wasm::get_module() {
125 module.clear_admin_state();
126 }
127}
128
129pub fn navigation_id() -> u32 {
131 #[cfg(target_arch = "wasm32")]
132 if let Some(module) = wasm::get_module() {
133 return module.navigation_id();
134 }
135 0
136}
137
138pub fn rendered(id: u32, route: &str) {
140 #[cfg(target_arch = "wasm32")]
141 if let Some(module) = wasm::get_module() {
142 module.rendered(id, route);
143 }
144 #[cfg(not(target_arch = "wasm32"))]
145 let _ = (id, route);
146}
147
148pub fn route_pending() -> bool {
153 #[cfg(target_arch = "wasm32")]
154 {
155 leptos::prelude::document()
156 .document_element()
157 .is_some_and(|root| root.class_list().contains("is-route-pending"))
158 }
159 #[cfg(not(target_arch = "wasm32"))]
160 {
161 false
162 }
163}
164
165#[cfg(target_arch = "wasm32")]
166pub mod wasm {
167 use wasm_bindgen::{prelude::*, JsCast};
168
169 #[wasm_bindgen]
170 extern "C" {
171 pub type NavigationModule;
173
174 #[wasm_bindgen(method, catch)]
175 pub fn connect(
176 this: &NavigationModule,
177 notify: &Closure<dyn FnMut()>,
178 prefix: Option<&str>,
179 prefetch: &Closure<dyn FnMut(String, bool)>,
180 ) -> Result<(), JsValue>;
181
182 #[wasm_bindgen(method, catch)]
183 pub fn disconnect(this: &NavigationModule) -> Result<(), JsValue>;
184
185 #[wasm_bindgen(method, js_name = currentRoute)]
186 pub fn current_route(this: &NavigationModule) -> String;
187
188 #[wasm_bindgen(method, js_name = currentPrefix)]
189 pub fn current_prefix(this: &NavigationModule) -> Option<String>;
190
191 #[wasm_bindgen(method, js_name = navigationId)]
192 pub fn navigation_id(this: &NavigationModule) -> u32;
193
194 #[wasm_bindgen(method)]
195 pub fn push(this: &NavigationModule, route: &str);
196
197 #[wasm_bindgen(method)]
198 pub fn replace(this: &NavigationModule, route: &str);
199
200 #[wasm_bindgen(method)]
201 pub fn back(this: &NavigationModule);
202
203 #[wasm_bindgen(method)]
204 pub fn forward(this: &NavigationModule);
205
206 #[wasm_bindgen(method)]
207 pub fn external(this: &NavigationModule, url: &str) -> bool;
208
209 #[wasm_bindgen(method)]
210 pub fn rendered(this: &NavigationModule, id: u32, route: &str);
211
212 #[wasm_bindgen(method, js_name = readState)]
213 pub fn read_state(this: &NavigationModule, key: &str) -> Option<String>;
214
215 #[wasm_bindgen(method, js_name = writeState)]
216 pub fn write_state(this: &NavigationModule, entry_id: &str, key: &str, value: &str);
217
218 #[wasm_bindgen(method, js_name = entryId)]
219 pub fn entry_id(this: &NavigationModule) -> String;
220
221 #[wasm_bindgen(method, js_name = clearAdminState)]
222 pub fn clear_admin_state(this: &NavigationModule);
223
224 #[wasm_bindgen(method, js_name = setLeaveGuard)]
225 pub fn set_leave_guard(
226 this: &NavigationModule,
227 check: &Closure<dyn FnMut() -> bool>,
228 ) -> u32;
229
230 #[wasm_bindgen(method, js_name = clearLeaveGuard)]
231 pub fn clear_leave_guard(this: &NavigationModule, id: u32);
232 }
233
234 pub fn get_module() -> Option<NavigationModule> {
235 let window = web_sys::window()?;
236 let value = js_sys::Reflect::get(&window, &"__routeTransitions".into()).ok()?;
237 if value.is_null() || value.is_undefined() {
238 return None;
239 }
240 let connect = js_sys::Reflect::get(&value, &"connect".into()).ok()?;
241 connect
242 .is_function()
243 .then(|| value.unchecked_into::<NavigationModule>())
244 }
245}