Skip to main content

yggdrasil/bridges/
navigation.rs

1//! 浏览器导航协调器桥接;历史记录中的页面状态只保存在当前文档内存中。
2
3use serde::{de::DeserializeOwned, Serialize};
4
5/// 读取当前历史记录保存的页面状态。SSR 和资源加载失败时没有客户端状态。
6pub fn read_state<T: DeserializeOwned>(key: &str) -> Option<T> {
7    #[cfg(target_arch = "wasm32")]
8    {
9        let value = wasm::get_module()?.read_state(key)?;
10        serde_json::from_str(&value).ok()
11    }
12    #[cfg(not(target_arch = "wasm32"))]
13    {
14        let _ = key;
15        None
16    }
17}
18
19/// 写入挂载时捕获的历史记录,避免卸载后的异步回调覆盖新页面状态。
20pub fn write_state<T: Serialize>(entry_id: &str, key: &str, value: &T) {
21    #[cfg(target_arch = "wasm32")]
22    if let (Some(module), Ok(value)) = (wasm::get_module(), serde_json::to_string(value)) {
23        module.write_state(entry_id, key, &value);
24    }
25    #[cfg(not(target_arch = "wasm32"))]
26    let _ = (entry_id, key, value);
27}
28
29/// 页面挂载时捕获的历史记录标识。服务端返回空字符串。
30pub fn entry_id() -> String {
31    #[cfg(target_arch = "wasm32")]
32    if let Some(module) = wasm::get_module() {
33        return module.entry_id();
34    }
35    String::new()
36}
37
38/// 退出登录时清理后台历史记录中的页面状态。
39pub fn clear_admin_state() {
40    #[cfg(target_arch = "wasm32")]
41    if let Some(module) = wasm::get_module() {
42        module.clear_admin_state();
43    }
44}
45
46/// 与本轮路由 DOM 一起渲染的导航编号。
47pub fn navigation_id() -> u32 {
48    #[cfg(target_arch = "wasm32")]
49    if let Some(module) = wasm::get_module() {
50        return module.navigation_id();
51    }
52    0
53}
54
55/// 布局 effect 在实际 DOM 提交后通知 JS;JS 再核对编号和 DOM 标记。
56pub fn rendered(id: u32, route: &str) {
57    #[cfg(target_arch = "wasm32")]
58    if let Some(module) = wasm::get_module() {
59        module.rendered(id, route);
60    }
61    #[cfg(not(target_arch = "wasm32"))]
62    let _ = (id, route);
63}
64
65#[cfg(target_arch = "wasm32")]
66pub mod wasm {
67    use wasm_bindgen::{prelude::*, JsCast};
68
69    #[wasm_bindgen]
70    extern "C" {
71        /// `window.__routeTransitions` 是对象字面量,通过 Reflect 读取。
72        pub type NavigationModule;
73
74        #[wasm_bindgen(method, catch)]
75        pub fn connect(
76            this: &NavigationModule,
77            notify: &Closure<dyn FnMut()>,
78            prefix: Option<&str>,
79        ) -> Result<(), JsValue>;
80
81        #[wasm_bindgen(method, catch)]
82        pub fn disconnect(this: &NavigationModule) -> Result<(), JsValue>;
83
84        #[wasm_bindgen(method, js_name = currentRoute)]
85        pub fn current_route(this: &NavigationModule) -> String;
86
87        #[wasm_bindgen(method, js_name = currentPrefix)]
88        pub fn current_prefix(this: &NavigationModule) -> Option<String>;
89
90        #[wasm_bindgen(method, js_name = navigationId)]
91        pub fn navigation_id(this: &NavigationModule) -> u32;
92
93        #[wasm_bindgen(method)]
94        pub fn push(this: &NavigationModule, route: &str);
95
96        #[wasm_bindgen(method)]
97        pub fn replace(this: &NavigationModule, route: &str);
98
99        #[wasm_bindgen(method)]
100        pub fn back(this: &NavigationModule);
101
102        #[wasm_bindgen(method)]
103        pub fn forward(this: &NavigationModule);
104
105        #[wasm_bindgen(method)]
106        pub fn external(this: &NavigationModule, url: &str) -> bool;
107
108        #[wasm_bindgen(method)]
109        pub fn rendered(this: &NavigationModule, id: u32, route: &str);
110
111        #[wasm_bindgen(method, js_name = readState)]
112        pub fn read_state(this: &NavigationModule, key: &str) -> Option<String>;
113
114        #[wasm_bindgen(method, js_name = writeState)]
115        pub fn write_state(this: &NavigationModule, entry_id: &str, key: &str, value: &str);
116
117        #[wasm_bindgen(method, js_name = entryId)]
118        pub fn entry_id(this: &NavigationModule) -> String;
119
120        #[wasm_bindgen(method, js_name = clearAdminState)]
121        pub fn clear_admin_state(this: &NavigationModule);
122    }
123
124    pub fn get_module() -> Option<NavigationModule> {
125        let window = web_sys::window()?;
126        let value = js_sys::Reflect::get(&window, &"__routeTransitions".into()).ok()?;
127        if value.is_null() || value.is_undefined() {
128            return None;
129        }
130        let connect = js_sys::Reflect::get(&value, &"connect".into()).ok()?;
131        connect
132            .is_function()
133            .then(|| value.unchecked_into::<NavigationModule>())
134    }
135}