tallyweb_frontend/
screen.rs

1use super::{connect_on_window_resize, AppError};
2use components::{MessageJar, SidebarLayout};
3use leptos::*;
4use wasm_bindgen::JsCast;
5
6#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
7pub enum ScreenStyle {
8    Portrait,
9    Small,
10    Big,
11}
12
13impl From<ScreenStyle> for SidebarLayout {
14    fn from(val: ScreenStyle) -> Self {
15        match val {
16            ScreenStyle::Portrait => SidebarLayout::Portrait,
17            ScreenStyle::Small => SidebarLayout::Hover,
18            ScreenStyle::Big => SidebarLayout::Landscape,
19        }
20    }
21}
22
23#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
24pub struct Screen {
25    pub style: RwSignal<ScreenStyle>,
26    pub size: RwSignal<(usize, usize)>,
27}
28
29impl Screen {
30    pub fn new(size: (usize, usize)) -> Result<Self, AppError> {
31        let style = match size {
32            (w, h) if w < 600 && h > w => ScreenStyle::Portrait,
33            (w, _) if w < 1200 => ScreenStyle::Small,
34            _ => ScreenStyle::Big,
35        };
36
37        Ok(Self {
38            style: create_rw_signal(style),
39            size: create_rw_signal(size),
40        })
41    }
42
43    pub fn update(&self) -> Result<(), AppError> {
44        let width = leptos_dom::window()
45            .inner_width()
46            .map_err(|val| AppError::WindowSize(val.as_string().unwrap_or_default()))?
47            .as_f64()
48            .ok_or(AppError::WindowSize(
49                "Unable to convert JsValue to f64".to_string(),
50            ))? as usize;
51
52        let height = leptos_dom::window()
53            .inner_height()
54            .map_err(|val| AppError::WindowSize(val.as_string().unwrap_or_default()))?
55            .as_f64()
56            .ok_or(AppError::WindowSize(
57                "Unable to convert JsValue to f64".to_string(),
58            ))? as usize;
59
60        let style = match (width, height) {
61            _ if width < 600 && height > width => ScreenStyle::Portrait,
62            _ if width < 1200 => ScreenStyle::Small,
63            _ => ScreenStyle::Big,
64        };
65
66        self.style.set(style);
67        self.size.set((width, height));
68
69        let document = document();
70        let document: &web_sys::HtmlDocument = document.unchecked_ref();
71
72        let size_json = serde_json::to_string(&(width, height)).unwrap();
73        let size_cookie = cookie::Cookie::build(("screen_size", &size_json))
74            .path("/")
75            .same_site(cookie::SameSite::Strict)
76            .build();
77        let size_cookie_encoded = cookie::Cookie::encoded(&size_cookie);
78
79        let mut cookie_str = size_cookie_encoded.to_string();
80        let age_str = format!("; Max-Age={}", 30 * 24 * 60 * 60);
81        cookie_str.push_str(&age_str);
82
83        document.set_cookie(&cookie_str).ok();
84
85        Ok(())
86    }
87}
88
89impl Default for Screen {
90    fn default() -> Self {
91        Self {
92            style: create_rw_signal(ScreenStyle::Big),
93            size: create_rw_signal((1920, 1080)),
94        }
95    }
96}
97
98#[server]
99pub async fn get_screen_cookie() -> Result<Screen, ServerFnError> {
100    use leptos_actix::extract;
101
102    let header = extract::<actix_web::HttpRequest>().await?;
103    let cookie = match header.cookie("screen_size") {
104        Some(s) => s.value().to_string(),
105        None => return Ok(Screen::default()),
106    };
107
108    let size: (usize, usize) = serde_json::from_str(&cookie)?;
109
110    return Ok(Screen::new(size)?);
111}
112
113async fn get_screen() -> Screen {
114    get_screen_cookie().await.unwrap_or_default()
115}
116
117#[component(transparent)]
118pub fn ProvideScreenSignal(children: ChildrenFn) -> impl IntoView {
119    view! {
120        <Await future=get_screen let:screen>
121
122            {
123                let s = *screen;
124                create_effect(move |_| {
125                    let _ = s.update();
126                    connect_on_window_resize(
127                        Box::new(move || {
128                            if let Err(err) = s.update() {
129                                if let Some(msg) = use_context::<MessageJar>() {
130                                    msg.set_err(err.clone())
131                                }
132                                logging::warn!("{}", err)
133                            }
134                        }),
135                    )
136                });
137                provide_context(s);
138                children()
139            }
140
141        </Await>
142    }
143}