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