Skip to main content

euv_ui/component/bridge/hook/
impl.rs

1use crate::*;
2
3/// Implementation of native bridge functionality.
4impl UseEuvNativeBridge {
5    /// Creates native bridge state for accessing platform-native features.
6    ///
7    /// Initializes all signals with default values and sets `available` to `false`.
8    /// The actual data is loaded asynchronously via `Self::load_data`.
9    ///
10    /// # Returns
11    ///
12    /// - `UseEuvNativeBridge`: The native bridge state.
13    pub fn use_bridge_state() -> UseEuvNativeBridge {
14        UseEuvNativeBridge::new(
15            App::use_signal(|| false),
16            App::use_signal(|| true),
17            App::use_signal(String::new),
18        )
19    }
20
21    /// Asynchronously loads native bridge data and updates the provided state signals.
22    ///
23    /// First checks platform availability via `BridgeConfig::is_available`. If unavailable,
24    /// sets `available` to `false` and returns. Otherwise, invokes the
25    /// `resolve_bridge_group_permissions` command, then populates the corresponding
26    /// signal from the result. If the invoke fails, sets `available` to `false`
27    /// so the card is hidden.
28    ///
29    /// # Arguments
30    ///
31    /// - `Option<BridgeConfig>`: Optional custom bridge configuration.
32    pub fn load_data(self, config: Option<BridgeConfig>) {
33        if !BridgeConfig::is_available(config.as_ref()) {
34            self.get_available().set(false);
35            self.get_loading().set(false);
36            return;
37        }
38        let permissions_state: UseEuvNativeBridge = self;
39        let cfg: Option<BridgeConfig> = config;
40        spawn_local(async move {
41            let args_obj: Object = Object::new();
42            Reflect::set(
43                &args_obj,
44                &JsValue::from_str(BRIDGE_GROUP_KEY),
45                &JsValue::from_str(BRIDGE_GROUP_ALL),
46            )
47            .unwrap_or(false);
48            let permissions_result: Result<JsValue, String> = match BridgeConfig::invoke(
49                INVOKE_RESOLVE_BRIDGE_GROUP_PERMISSIONS,
50                Some(&args_obj),
51                cfg.as_ref(),
52            ) {
53                Ok(promise) => {
54                    let future: JsFuture = JsFuture::from(promise);
55                    match future.await {
56                        Ok(value) => Ok(value),
57                        Err(error) => Err(format!("{error:?}")),
58                    }
59                }
60                Err(error) => Err(error),
61            };
62            match permissions_result {
63                Ok(value) => {
64                    let permissions_array: Vec<String> = value
65                        .dyn_into::<Array>()
66                        .map(|array: Array| {
67                            array
68                                .iter()
69                                .filter_map(|item: JsValue| item.as_string())
70                                .collect::<Vec<String>>()
71                        })
72                        .unwrap_or_default();
73                    permissions_state
74                        .get_permissions()
75                        .set(permissions_array.join(", "));
76                    permissions_state.get_available().set(true);
77                }
78                Err(_) => {
79                    permissions_state.get_available().set(false);
80                }
81            }
82            permissions_state.get_loading().set(false);
83        });
84    }
85}
86
87/// Implementation of cache update functionality.
88impl UseCacheUpdate {
89    /// Creates cache update state for tracking documentation build status.
90    ///
91    /// Initializes `doc_status` to `false`, `version` to an empty string,
92    /// and `updating` to `false`. The actual data is loaded asynchronously
93    /// via `Self::load`.
94    ///
95    /// # Returns
96    ///
97    /// - `UseCacheUpdate`: The cache update state.
98    pub fn use_cache_state() -> UseCacheUpdate {
99        UseCacheUpdate::new(
100            App::use_signal(|| false),
101            App::use_signal(String::new),
102            App::use_signal(|| false),
103        )
104    }
105
106    /// Runs the provided updater closure and applies its result to the
107    /// internal state signals.
108    ///
109    /// The closure is called asynchronously via `spawn_local`. It receives
110    /// no arguments and must return a `Future<Output = UpdateResult>`.
111    /// Once it resolves, the `doc_status`, `version`, and `updating`
112    /// signals are updated from the returned `UpdateResult`.
113    ///
114    /// The UI layer is completely unaware of how the update check is
115    /// performed (fetch, version comparison, bridge invocation, etc.) —
116    /// it only sees the result.
117    ///
118    /// # Arguments
119    ///
120    /// - `F`: An async closure that returns `UpdateResult`.
121    pub fn load<F, Fut>(self, updater: F)
122    where
123        F: FnOnce() -> Fut + 'static,
124        Fut: Future<Output = UpdateResult>,
125    {
126        spawn_local(async move {
127            let result: UpdateResult = updater().await;
128            self.get_doc_status().set(result.get_doc_status());
129            self.get_version().set(result.get_version().clone());
130            self.get_updating().set(result.get_updating());
131        });
132    }
133}
134
135/// Implementation of bridge configuration and invocation.
136impl BridgeConfig {
137    /// Checks whether the bridge native bridge is available on the current platform.
138    ///
139    /// Looks up `window.___.core` via `Reflect` to determine if the
140    /// bridge runtime is present. Returns `false` if the property chain does not exist
141    /// or if any reflection error occurs.
142    ///
143    /// # Arguments
144    ///
145    /// - `Option<&BridgeConfig>`: Optional custom bridge configuration.
146    ///
147    /// # Returns
148    ///
149    /// - `bool`: `true` if the bridge core module is available.
150    pub fn is_available(config: Option<&BridgeConfig>) -> bool {
151        let config: BridgeConfig = config
152            .map_or_else(BridgeConfig::default, |config: &BridgeConfig| {
153                config.clone()
154            });
155        let window_value: Window = window().expect("no global window exists");
156        let bridge_key: JsValue = JsValue::from_str(config.get_global_key());
157        let bridge_obj: JsValue = match Reflect::get(&window_value, &bridge_key) {
158            Ok(value) => value,
159            Err(_) => return false,
160        };
161        if bridge_obj.is_undefined() || bridge_obj.is_null() {
162            return false;
163        }
164        let core_key: JsValue = JsValue::from_str(config.get_core_key());
165        let core_obj: JsValue = match Reflect::get(&bridge_obj, &core_key) {
166            Ok(value) => value,
167            Err(_) => return false,
168        };
169        !core_obj.is_undefined() && !core_obj.is_null()
170    }
171
172    /// Invokes a bridge core command by name via `window.___.core.invoke`.
173    ///
174    /// Resolves the `___` → `core` → `invoke` property chain on the global
175    /// `window` object, then calls `invoke` with the given command name and
176    /// optional arguments object. Returns the resulting `Promise`, or an
177    /// error string if any step in the reflection chain fails.
178    ///
179    /// # Arguments
180    ///
181    /// - `&str`: The bridge command name to invoke.
182    /// - `Option<&JsValue>`: Optional arguments object to pass to the command.
183    /// - `Option<&BridgeConfig>`: Optional custom bridge configuration.
184    ///
185    /// # Returns
186    ///
187    /// - `Result<Promise, String>`: The promise returned by the invoke call, or an error message.
188    pub fn invoke(
189        command: &str,
190        args: Option<&JsValue>,
191        config: Option<&BridgeConfig>,
192    ) -> Result<Promise, String> {
193        let copnfig: BridgeConfig = config
194            .map_or_else(BridgeConfig::default, |copnfig: &BridgeConfig| {
195                copnfig.clone()
196            });
197        let window_value: Window = window().expect("no global window exists");
198        let bridge_key: JsValue = JsValue::from_str(copnfig.get_global_key());
199        let bridge_obj: JsValue = Reflect::get(&window_value, &bridge_key)
200            .map_err(|error: JsValue| format!("{error:?}"))?;
201        let core_key: JsValue = JsValue::from_str(copnfig.get_core_key());
202        let core_obj: JsValue =
203            Reflect::get(&bridge_obj, &core_key).map_err(|error: JsValue| format!("{error:?}"))?;
204        let invoke_key: JsValue = JsValue::from_str(copnfig.get_invoke_key());
205        let invoke_fn: JsValue =
206            Reflect::get(&core_obj, &invoke_key).map_err(|error: JsValue| format!("{error:?}"))?;
207        let invoke_function: Function = invoke_fn
208            .dyn_into::<Function>()
209            .map_err(|error: JsValue| format!("{error:?}"))?;
210        let command_value: JsValue = JsValue::from_str(command);
211        let result: JsValue = match args {
212            Some(arguments) => invoke_function
213                .call2(&core_obj, &command_value, arguments)
214                .map_err(|error: JsValue| format!("{error:?}"))?,
215            None => invoke_function
216                .call1(&core_obj, &command_value)
217                .map_err(|error: JsValue| format!("{error:?}"))?,
218        };
219        result
220            .dyn_into::<Promise>()
221            .map_err(|error: JsValue| format!("{error:?}"))
222    }
223}