Skip to main content

euv_ui/component/browser/hook/
impl.rs

1use crate::*;
2
3/// Implementation of browser API functionality.
4impl UseEuvBrowser {
5    /// Creates browser API state for localStorage, sessionStorage, clipboard, and navigator access.
6    ///
7    /// # Returns
8    ///
9    /// - `UseEuvBrowser`: The browser API state.
10    pub fn use_browser_state() -> UseEuvBrowser {
11        UseEuvBrowser::default()
12    }
13
14    /// Reads a value from the browser localStorage.
15    ///
16    /// # Arguments
17    ///
18    /// - `&str`: The key to look up.
19    ///
20    /// # Returns
21    ///
22    /// - `Option<String>`: The stored value if found, or None.
23    pub fn local_storage_get(key: &str) -> Option<String> {
24        let window: Window = window().expect("no global window exists");
25        let storage: Storage = window.local_storage().ok()??;
26        storage.get_item(key).ok()?
27    }
28
29    /// Writes a key-value pair to the browser localStorage.
30    ///
31    /// # Arguments
32    ///
33    /// - `&str`: The key to store.
34    /// - `&str`: The value to store.
35    pub fn local_storage_set(key: &str, value: &str) {
36        let window: Window = window().expect("no global window exists");
37        let storage: Storage = match window.local_storage() {
38            Ok(Some(local_storage)) => local_storage,
39            _ => return,
40        };
41        let _ = storage.set_item(key, value);
42    }
43
44    /// Removes a key from the browser localStorage.
45    ///
46    /// # Arguments
47    ///
48    /// - `&str`: The key to remove.
49    pub(crate) fn local_storage_remove(key: &str) {
50        let window: Window = window().expect("no global window exists");
51        let storage: Storage = match window.local_storage() {
52            Ok(Some(local_storage)) => local_storage,
53            _ => return,
54        };
55        let _ = storage.remove_item(key);
56    }
57
58    /// Reads a value from the browser sessionStorage.
59    ///
60    /// # Arguments
61    ///
62    /// - `&str`: The key to look up.
63    ///
64    /// # Returns
65    ///
66    /// - `Option<String>`: The stored value if found, or None.
67    pub(crate) fn session_storage_get(key: &str) -> Option<String> {
68        let window: Window = window().expect("no global window exists");
69        let storage: Storage = window.session_storage().ok()??;
70        storage.get_item(key).ok()?
71    }
72
73    /// Writes a key-value pair to the browser sessionStorage.
74    ///
75    /// # Arguments
76    ///
77    /// - `&str`: The key to store.
78    /// - `&str`: The value to store.
79    pub(crate) fn session_storage_set(key: &str, value: &str) {
80        let window: Window = window().expect("no global window exists");
81        let storage: Storage = match window.session_storage() {
82            Ok(Some(session_storage)) => session_storage,
83            _ => return,
84        };
85        let _ = storage.set_item(key, value);
86    }
87
88    /// Removes a key from the browser sessionStorage.
89    ///
90    /// # Arguments
91    ///
92    /// - `&str`: The key to remove.
93    pub(crate) fn session_storage_remove(key: &str) {
94        let window: Window = window().expect("no global window exists");
95        let storage: Storage = match window.session_storage() {
96            Ok(Some(session_storage)) => session_storage,
97            _ => return,
98        };
99        let _ = storage.remove_item(key);
100    }
101
102    /// Reads text from the system clipboard asynchronously.
103    ///
104    /// # Returns
105    ///
106    /// - `String`: The clipboard text content, or an error message.
107    pub(crate) async fn clipboard_read_text() -> String {
108        let window: Window = window().expect("no global window exists");
109        let navigator: Navigator = window.navigator();
110        match Reflect::get(&navigator, &JsValue::from_str("clipboard")) {
111            Ok(clipboard_obj) if !clipboard_obj.is_undefined() => {
112                let clipboard: Clipboard = navigator.clipboard();
113                let promise: Promise = clipboard.read_text();
114                let future: JsFuture = JsFuture::from(promise);
115                match future.await {
116                    Ok(value) => value
117                        .as_string()
118                        .unwrap_or_else(|| "No text content".to_string()),
119                    Err(_) => "Failed to read clipboard".to_string(),
120                }
121            }
122            _ => "Clipboard API not available (requires secure context)".to_string(),
123        }
124    }
125
126    /// Writes text to the system clipboard asynchronously.
127    ///
128    /// # Arguments
129    ///
130    /// - `&str`: The text to write.
131    ///
132    /// # Returns
133    ///
134    /// - `bool`: Whether the write succeeded.
135    pub(crate) async fn clipboard_write_text(text: &str) -> bool {
136        let window: Window = window().expect("no global window exists");
137        let navigator: Navigator = window.navigator();
138        match js_sys::Reflect::get(&navigator, &JsValue::from_str("clipboard")) {
139            Ok(clipboard_obj) if !clipboard_obj.is_undefined() => {
140                let clipboard: Clipboard = navigator.clipboard();
141                let promise: Promise = clipboard.write_text(text);
142                let future: JsFuture = JsFuture::from(promise);
143                future.await.is_ok()
144            }
145            _ => false,
146        }
147    }
148
149    /// Reads the browser window inner dimensions.
150    ///
151    /// # Returns
152    ///
153    /// - `(i32, i32)`: A tuple of (inner_width, inner_height).
154    pub(crate) fn window_inner_size() -> (i32, i32) {
155        let window: Window = window().expect("no global window exists");
156        let width: i32 = window
157            .inner_width()
158            .ok()
159            .map(|value: JsValue| Number::from(value).value_of() as i32)
160            .unwrap_or_default();
161        let height: i32 = window
162            .inner_height()
163            .ok()
164            .map(|value: JsValue| Number::from(value).value_of() as i32)
165            .unwrap_or_default();
166        (width, height)
167    }
168
169    /// Reads the browser navigator user agent string.
170    ///
171    /// # Returns
172    ///
173    /// - `String`: The user agent string.
174    pub(crate) fn navigator_user_agent() -> String {
175        let window: Window = window().expect("no global window exists");
176        window
177            .navigator()
178            .user_agent()
179            .unwrap_or_else(|_: JsValue| "Unknown".to_string())
180    }
181
182    /// Reads the browser navigator language.
183    ///
184    /// # Returns
185    ///
186    /// - `String`: The preferred language string.
187    pub(crate) fn navigator_language() -> String {
188        let window: Window = window().expect("no global window exists");
189        window
190            .navigator()
191            .language()
192            .unwrap_or_else(|| "Unknown".to_string())
193    }
194
195    /// Reads the current browser location href.
196    ///
197    /// # Returns
198    ///
199    /// - `String`: The current full URL.
200    pub(crate) fn location_href() -> String {
201        let window: Window = window().expect("no global window exists");
202        window
203            .location()
204            .href()
205            .unwrap_or_else(|_error: JsValue| "Unknown".to_string())
206    }
207
208    /// Reads the current browser location origin.
209    ///
210    /// # Returns
211    ///
212    /// - `String`: The origin portion of the URL.
213    pub(crate) fn location_origin() -> String {
214        let window: Window = window().expect("no global window exists");
215        window
216            .location()
217            .origin()
218            .unwrap_or_else(|_error: JsValue| "Unknown".to_string())
219    }
220
221    /// Reads the current browser location pathname.
222    ///
223    /// # Returns
224    ///
225    /// - `String`: The pathname portion of the URL.
226    pub(crate) fn location_pathname() -> String {
227        let window: Window = window().expect("no global window exists");
228        window
229            .location()
230            .pathname()
231            .unwrap_or_else(|_error: JsValue| "Unknown".to_string())
232    }
233
234    /// Creates a click event handler that sets a localStorage item.
235    ///
236    /// # Arguments
237    ///
238    /// - `UseEuvBrowser`: The browser API state.
239    ///
240    /// # Returns
241    ///
242    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to set the localStorage item.
243    pub fn on_local_storage_set(self) -> Option<Rc<dyn Fn(Event)>> {
244        Some(Rc::new(move |_: Event| {
245            let key: String = self.get_local_key().get();
246            let value: String = self.get_local_value().get();
247            if !key.is_empty() {
248                Self::local_storage_set(&key, &value);
249                self.get_local_result()
250                    .set(format!("Set: {} = {}", key, value));
251            }
252        }))
253    }
254
255    /// Creates a click event handler that gets a localStorage item.
256    ///
257    /// # Arguments
258    ///
259    /// - `UseEuvBrowser`: The browser API state.
260    ///
261    /// # Returns
262    ///
263    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to get the localStorage item.
264    pub fn on_local_storage_get(self) -> Option<Rc<dyn Fn(Event)>> {
265        Some(Rc::new(move |_: Event| {
266            let key: String = self.get_local_key().get();
267            let value: Option<String> = Self::local_storage_get(&key);
268            match value {
269                Some(v) => self.get_local_result().set(format!("Get: {} = {}", key, v)),
270                None => self
271                    .get_local_result()
272                    .set(format!("Key '{}' not found", key)),
273            }
274        }))
275    }
276
277    /// Creates a click event handler that removes a localStorage item.
278    ///
279    /// # Arguments
280    ///
281    /// - `UseEuvBrowser`: The browser API state.
282    ///
283    /// # Returns
284    ///
285    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to remove the localStorage item.
286    pub fn on_local_storage_remove(self) -> Option<Rc<dyn Fn(Event)>> {
287        Some(Rc::new(move |_: Event| {
288            let key: String = self.get_local_key().get();
289            Self::local_storage_remove(&key);
290            self.get_local_result().set(format!("Removed key: {}", key));
291        }))
292    }
293
294    /// Creates a click event handler that sets a sessionStorage item.
295    ///
296    /// # Arguments
297    ///
298    /// - `UseEuvBrowser`: The browser API state.
299    ///
300    /// # Returns
301    ///
302    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to set the sessionStorage item.
303    pub fn on_session_storage_set(self) -> Option<Rc<dyn Fn(Event)>> {
304        Some(Rc::new(move |_: Event| {
305            let key: String = self.get_session_key().get();
306            let value: String = self.get_session_value().get();
307            if !key.is_empty() {
308                Self::session_storage_set(&key, &value);
309                self.get_session_result()
310                    .set(format!("Set: {} = {}", key, value));
311            }
312        }))
313    }
314
315    /// Creates a click event handler that gets a sessionStorage item.
316    ///
317    /// # Arguments
318    ///
319    /// - `UseEuvBrowser`: The browser API state.
320    ///
321    /// # Returns
322    ///
323    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to get the sessionStorage item.
324    pub fn on_session_storage_get(self) -> Option<Rc<dyn Fn(Event)>> {
325        Some(Rc::new(move |_: Event| {
326            let key: String = self.get_session_key().get();
327            let value: Option<String> = Self::session_storage_get(&key);
328            match value {
329                Some(v) => self
330                    .get_session_result()
331                    .set(format!("Get: {} = {}", key, v)),
332                None => self
333                    .get_session_result()
334                    .set(format!("Key '{}' not found", key)),
335            }
336        }))
337    }
338
339    /// Creates a click event handler that removes a sessionStorage item.
340    ///
341    /// # Arguments
342    ///
343    /// - `UseEuvBrowser`: The browser API state.
344    ///
345    /// # Returns
346    ///
347    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to remove the sessionStorage item.
348    pub fn on_session_storage_remove(self) -> Option<Rc<dyn Fn(Event)>> {
349        Some(Rc::new(move |_: Event| {
350            let key: String = self.get_session_key().get();
351            Self::session_storage_remove(&key);
352            self.get_session_result()
353                .set(format!("Removed key: {}", key));
354        }))
355    }
356
357    /// Creates a click event handler that copies text to clipboard.
358    ///
359    /// # Arguments
360    ///
361    /// - `UseEuvBrowser`: The browser API state.
362    ///
363    /// # Returns
364    ///
365    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to copy text to clipboard.
366    pub fn on_clipboard_copy(self) -> Option<Rc<dyn Fn(Event)>> {
367        Some(Rc::new(move |_: Event| {
368            let text: String = self.get_clipboard_text().get();
369            let text_clone: String = text.clone();
370            let result: Signal<String> = self.get_clipboard_result();
371            if text.is_empty() {
372                result.set("Please enter text to copy".to_string());
373                return;
374            }
375            let window: Window = window().expect("no global window exists");
376            let navigator: Navigator = window.navigator();
377            match js_sys::Reflect::get(&navigator, &JsValue::from_str("clipboard")) {
378                Ok(clipboard_obj) if !clipboard_obj.is_undefined() => {
379                    spawn_local(async move {
380                        let success: bool = Self::clipboard_write_text(&text_clone).await;
381                        if success {
382                            result.set("Copied to clipboard!".to_string());
383                        } else {
384                            result.set("Failed to copy".to_string());
385                        }
386                    });
387                }
388                _ => {
389                    result.set("Clipboard API not available (requires secure context)".to_string());
390                }
391            }
392        }))
393    }
394
395    /// Creates a click event handler that reads text from clipboard.
396    ///
397    /// # Arguments
398    ///
399    /// - `UseEuvBrowser`: The browser API state.
400    ///
401    /// # Returns
402    ///
403    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to read text from clipboard.
404    pub fn on_clipboard_paste(self) -> Option<Rc<dyn Fn(Event)>> {
405        Some(Rc::new(move |_: Event| {
406            let result: Signal<String> = self.get_clipboard_result();
407            let window: Window = window().expect("no global window exists");
408            let navigator: Navigator = window.navigator();
409            match js_sys::Reflect::get(&navigator, &JsValue::from_str("clipboard")) {
410                Ok(clipboard_obj) if !clipboard_obj.is_undefined() => {
411                    spawn_local(async move {
412                        let text: String = Self::clipboard_read_text().await;
413                        result.set(format!("Pasted: {}", text));
414                    });
415                }
416                _ => {
417                    result.set("Clipboard API not available (requires secure context)".to_string());
418                }
419            }
420        }))
421    }
422
423    /// Creates a click event handler that refreshes the window size display.
424    ///
425    /// # Arguments
426    ///
427    /// - `UseEuvBrowser`: The browser API state.
428    ///
429    /// # Returns
430    ///
431    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler to refresh the window size.
432    pub fn on_window_refresh_size(self) -> Option<Rc<dyn Fn(Event)>> {
433        Some(Rc::new(move |_: Event| {
434            let (width, height): (i32, i32) = Self::window_inner_size();
435            self.get_window_size()
436                .set(format!("{} x {}", width, height));
437        }))
438    }
439
440    /// Creates a click event handler that logs a console message.
441    ///
442    /// # Arguments
443    ///
444    /// - `Signal<String>`: The console input signal.
445    ///
446    /// # Returns
447    ///
448    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler for console.log.
449    pub fn on_console_log(console_input: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
450        Some(Rc::new(move |_: Event| {
451            let raw: String = console_input.get();
452            let message: &str = if raw.is_empty() {
453                CONSOLE_LOG_DEFAULT_MESSAGE
454            } else {
455                &raw
456            };
457            Console::log(message);
458        }))
459    }
460
461    /// Creates a click event handler that warns a console message.
462    ///
463    /// # Arguments
464    ///
465    /// - `Signal<String>`: The console input signal.
466    ///
467    /// # Returns
468    ///
469    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler for console.warn.
470    pub fn on_console_warn(console_input: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
471        Some(Rc::new(move |_: Event| {
472            let raw: String = console_input.get();
473            let message: &str = if raw.is_empty() {
474                CONSOLE_WARN_DEFAULT_MESSAGE
475            } else {
476                &raw
477            };
478            Console::warn(message);
479        }))
480    }
481
482    /// Creates a click event handler that errors a console message.
483    ///
484    /// # Arguments
485    ///
486    /// - `Signal<String>`: The console input signal.
487    ///
488    /// # Returns
489    ///
490    /// - `Option<Rc<dyn Fn(Event)>>`: A click handler for console.error.
491    pub fn on_console_error(console_input: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
492        Some(Rc::new(move |_: Event| {
493            let raw: String = console_input.get();
494            let message: &str = if raw.is_empty() {
495                CONSOLE_ERROR_DEFAULT_MESSAGE
496            } else {
497                &raw
498            };
499            Console::error(message);
500        }))
501    }
502}
503
504/// Default implementation for `UseEuvBrowser`.
505impl Default for UseEuvBrowser {
506    fn default() -> Self {
507        let window_size_val: String = {
508            let (width, height): (i32, i32) = UseEuvBrowser::window_inner_size();
509            format!("{} x {}", width, height)
510        };
511        UseEuvBrowser {
512            local_key: App::use_signal(String::new),
513            local_value: App::use_signal(String::new),
514            local_result: App::use_signal(String::new),
515            session_key: App::use_signal(String::new),
516            session_value: App::use_signal(String::new),
517            session_result: App::use_signal(String::new),
518            clipboard_text: App::use_signal(String::new),
519            clipboard_result: App::use_signal(String::new),
520            window_size: App::use_signal(move || window_size_val.clone()),
521            user_agent: App::use_signal(UseEuvBrowser::navigator_user_agent),
522            language: App::use_signal(UseEuvBrowser::navigator_language),
523            location_url: App::use_signal(UseEuvBrowser::location_href),
524            location_origin_val: App::use_signal(UseEuvBrowser::location_origin),
525            location_pathname_val: App::use_signal(UseEuvBrowser::location_pathname),
526            console_input: App::use_signal(String::new),
527        }
528    }
529}