tallyweb_frontend/
screen.rs1use 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}