logicaffeine_web/ui/
platform.rs1use crate::ui::pages::landing::content::Install;
5
6pub struct Seen<'a> {
8 pub ua_platform: Option<&'a str>,
10 pub platform: &'a str,
12 pub user_agent: &'a str,
13 pub max_touch_points: u32,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum System {
19 MacOs,
20 Linux,
21 Windows,
22 ChromeOs,
23 Phone,
25 Unknown,
26}
27
28impl System {
29 pub const ALL: &'static [System] = &[System::MacOs, System::Linux, System::Windows, System::ChromeOs, System::Phone, System::Unknown];
30
31 pub fn name(self) -> &'static str {
33 match self {
34 System::MacOs => "macos",
35 System::Linux => "linux",
36 System::Windows => "windows",
37 System::ChromeOs => "chromeos",
38 System::Phone => "phone",
39 System::Unknown => "unknown",
40 }
41 }
42}
43
44pub fn system_of(seen: &Seen) -> System {
48 let ua = seen.user_agent;
49 let platform = seen.platform;
50 let ua_platform = seen.ua_platform.unwrap_or("");
51 let says = |word: &str| ua.contains(word);
52 let starts = |prefix: &str| platform.starts_with(prefix);
53 if says("iPhone")
54 || says("iPad")
55 || says("iPod")
56 || says("Android")
57 || ua_platform == "Android"
58 || ua_platform == "iOS"
59 || starts("iPhone")
60 || starts("iPad")
61 || starts("iPod")
62 || (platform == "MacIntel" && seen.max_touch_points > 1)
63 {
64 System::Phone
65 } else if says("CrOS") || ua_platform == "Chrome OS" || ua_platform == "ChromeOS" {
66 System::ChromeOs
67 } else if ua_platform == "Windows" || starts("Win") || says("Windows") {
68 System::Windows
69 } else if ua_platform == "macOS" || starts("Mac") || says("Macintosh") {
70 System::MacOs
71 } else if ua_platform == "Linux" || starts("Linux") || says("X11") || says("Linux") {
72 System::Linux
73 } else {
74 System::Unknown
75 }
76}
77
78pub fn install_for(system: System) -> Install {
82 match system {
83 System::Windows => Install::Windows,
84 System::MacOs | System::Linux | System::ChromeOs | System::Phone | System::Unknown => Install::Unix,
85 }
86}
87
88pub const REMEMBERED: &str = "logos-install";
90
91pub const SYSTEM_OF_JS: &str = "function systemOf(ua, platform, uaPlatform, touch) { \
95var says = function (w) { return ua.indexOf(w) !== -1; }; \
96var starts = function (p) { return platform.indexOf(p) === 0; }; \
97if (says('iPhone') || says('iPad') || says('iPod') || says('Android') || uaPlatform === 'Android' || uaPlatform === 'iOS' || starts('iPhone') || starts('iPad') || starts('iPod') || (platform === 'MacIntel' && touch > 1)) return 'phone'; \
98if (says('CrOS') || uaPlatform === 'Chrome OS' || uaPlatform === 'ChromeOS') return 'chromeos'; \
99if (uaPlatform === 'Windows' || starts('Win') || says('Windows')) return 'windows'; \
100if (uaPlatform === 'macOS' || starts('Mac') || says('Macintosh')) return 'macos'; \
101if (uaPlatform === 'Linux' || starts('Linux') || says('X11') || says('Linux')) return 'linux'; \
102return 'unknown'; }";
103
104pub fn os_script() -> String {
108 format!(
109 "(function () {{ {SYSTEM_OF_JS} var n = navigator; \
110var system = systemOf(n.userAgent || '', n.platform || '', (n.userAgentData && n.userAgentData.platform) || '', n.maxTouchPoints || 0); \
111var chosen = null; try {{ chosen = localStorage.getItem('{REMEMBERED}'); }} catch (e) {{}} \
112var root = document.documentElement; root.setAttribute('data-system', system); \
113root.setAttribute('data-os', chosen === 'windows' || chosen === 'unix' ? chosen : (system === 'windows' ? 'windows' : 'unix')); }})();"
114 )
115}
116
117pub fn shown_install() -> Install {
121 #[cfg(target_arch = "wasm32")]
122 {
123 let window = web_sys::window();
124 let Some(root) = window.as_ref().and_then(|w| w.document()).and_then(|d| d.document_element()) else {
125 return Install::Unix;
126 };
127 if let Some(install) = root.get_attribute("data-os").as_deref().and_then(Install::from_name) {
128 return install;
129 }
130 let navigator = window.as_ref().map(|w| w.navigator());
131 let read = |of: &wasm_bindgen::JsValue, name: &str| js_sys::Reflect::get(of, &name.into()).ok().and_then(|v| v.as_string());
132 let user_agent = navigator.as_ref().and_then(|n| read(n, "userAgent")).unwrap_or_default();
133 let platform = navigator.as_ref().and_then(|n| read(n, "platform")).unwrap_or_default();
134 let ua_platform = navigator
135 .as_ref()
136 .and_then(|n| js_sys::Reflect::get(n, &"userAgentData".into()).ok())
137 .filter(|data| data.is_object())
138 .and_then(|data| read(&data, "platform"));
139 let max_touch_points = navigator.as_ref().map_or(0, |n| n.max_touch_points().max(0) as u32);
140 let system = system_of(&Seen { ua_platform: ua_platform.as_deref(), platform: &platform, user_agent: &user_agent, max_touch_points });
141 let chosen = window.as_ref().and_then(|w| w.local_storage().ok().flatten()).and_then(|s| s.get_item(REMEMBERED).ok().flatten());
142 let install = chosen.as_deref().and_then(Install::from_name).unwrap_or(install_for(system));
143 let _ = root.set_attribute("data-system", system.name());
144 let _ = root.set_attribute("data-os", install.name());
145 install
146 }
147 #[cfg(not(target_arch = "wasm32"))]
148 Install::Unix
149}
150
151pub fn choose_install(install: Install) {
153 #[cfg(target_arch = "wasm32")]
154 {
155 let window = web_sys::window();
156 if let Some(root) = window.as_ref().and_then(|w| w.document()).and_then(|d| d.document_element()) {
157 let _ = root.set_attribute("data-os", install.name());
158 }
159 if let Some(storage) = window.as_ref().and_then(|w| w.local_storage().ok().flatten()) {
160 let _ = storage.set_item(REMEMBERED, install.name());
161 }
162 }
163 #[cfg(not(target_arch = "wasm32"))]
164 let _ = install;
165}
166
167#[cfg(test)]
168#[path = "../../../../crates/logicaffeine_tests/unit/logicaffeine_web/ui/platform/tests.rs"]
169mod tests;