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}