util.rs
⎇
Raw
1//! Formatting + small browser helpers.
2
3use leptos::prelude::*;
4use wasm_bindgen::JsValue;
5
6/// A window event listener that is removed when the current owner is disposed.
7///
8/// Leptos' `window_event_listener` returns a `WindowListenerHandle` with no
9/// `Drop` impl — if the owning component is unmounted without an explicit
10/// `remove()`, the listener keeps firing on `window` and panics by reading
11/// the component's now-disposed signals.
12pub fn owned_window_listener<E: leptos::ev::EventDescriptor + 'static>(
13 event: E,
14 cb: impl Fn(E::EventType) + 'static,
15) where
16 E::EventType: wasm_bindgen::JsCast,
17{
18 let handle = window_event_listener(event, cb);
19 on_cleanup(move || handle.remove());
20}
21
22pub fn format_size(bytes: u64) -> String {
23 const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
24 let mut v = bytes as f64;
25 let mut i = 0;
26 while v >= 1024.0 && i < UNITS.len() - 1 {
27 v /= 1024.0;
28 i += 1;
29 }
30 if i == 0 {
31 format!("{bytes} B")
32 } else {
33 format!("{v:.1} {}", UNITS[i])
34 }
35}
36
37/// Format an RFC 3339 timestamp as "YYYY-MM-DD HH:MM" (UTC) for display.
38/// Falls back to the raw string when it cannot be parsed.
39pub fn format_date(rfc3339: &str) -> String {
40 let d = js_sys::Date::new(&JsValue::from_str(rfc3339));
41 if d.get_time().is_nan() {
42 return rfc3339.to_string();
43 }
44 fn pad(n: u32) -> String {
45 if n < 10 {
46 format!("0{n}")
47 } else {
48 n.to_string()
49 }
50 }
51 format!(
52 "{:04}-{}-{} {} {}",
53 d.get_utc_full_year(),
54 pad(d.get_utc_month() + 1),
55 pad(d.get_utc_date()),
56 pad(d.get_utc_hours()),
57 pad(d.get_utc_minutes()),
58 )
59}
60
61pub fn storage_get(key: &str) -> Option<String> {
62 let w = web_sys::window()?;
63 let storage_opt = w.local_storage().ok()?;
64 let storage = storage_opt.as_ref()?;
65 storage.get_item(key).ok().flatten()
66}
67
68pub fn storage_set(key: &str, value: &str) {
69 if let Some(s) = web_sys::window().and_then(|w| w.local_storage().ok().flatten()) {
70 let _ = s.set_item(key, value);
71 }
72}
73
74#[derive(Clone, Copy, PartialEq, Debug)]
75pub enum ViewMode {
76 Grid,
77 List,
78}
79
80impl ViewMode {
81 const KEY: &'static str = "fbng.view";
82
83 pub fn load() -> Self {
84 match storage_get(Self::KEY).as_deref() {
85 Some("list") => ViewMode::List,
86 _ => ViewMode::Grid,
87 }
88 }
89
90 pub fn save(self) {
91 storage_set(
92 Self::KEY,
93 if self == ViewMode::List {
94 "list"
95 } else {
96 "grid"
97 },
98 );
99 }
100}
101