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