Skip to content
172 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.1//! The app's own settings: how it starts, what closing the window does,
2//! how it tells the person something arrived, the menu bar, zoom and
3//! updates. They live in their own window (`launcher/settings.html`),
4//! opened from the menu, the tray, or the site's account menu, and are
5//! kept with the server in `desktop.json` (config.rs). Whether g1t opens
6//! when someone signs in to their computer is kept by the system itself,
7//! through the autostart plugin, and read back from it.
8
9use serde::{Deserialize, Serialize};
10use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
11use tauri_plugin_autostart::ManagerExt;
12
13use crate::{WINDOW, config};
14
15/// The settings window's label.
16pub const SETTINGS: &str = "settings";
17
18/// The argument the system starts g1t with at sign-in, so it can tell a
19/// start of its own from someone opening it.
20pub const AT_LOGIN: &str = "--at-login";
21
22/// The zoom levels the View menu steps through.
23pub const ZOOMS: &[f64] = &[0.67, 0.75, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0];
24
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26#[serde(default, rename_all = "camelCase")]
27pub struct Settings {
28 /// Opened at sign-in, g1t waits in the tray instead of opening its window.
29 pub start_hidden: bool,
30 /// Closing the window leaves g1t running in the tray, for notifications;
31 /// otherwise closing it quits.
32 pub close_to_tray: bool,
33 /// Notifications from the system, for what the site says needs the person.
34 pub notifications: bool,
35 /// The taskbar icon flashes, or the dock icon bounces, with a notification.
36 pub flash: bool,
37 /// What needs the person, counted on the icon.
38 pub badge: bool,
39 /// The menu bar under the title (Windows and Linux; macOS has its own at the top).
40 pub menu_bar: bool,
41 /// The site's zoom, 1 for as it is.
42 pub zoom: f64,
43 /// Updates download by themselves; a person can still check from the menu.
44 pub auto_update: bool,
45}
46
47impl Default for Settings {
48 fn default() -> Self {
49 Self { start_hidden: true, close_to_tray: true, notifications: true, flash: true, badge: true, menu_bar: true, zoom: 1.0, auto_update: true }
50 }
51}
52
53impl Settings {
54 /// A zoom the app offers, nearest to the one asked for.
55 pub fn clamp_zoom(zoom: f64) -> f64 {
56 ZOOMS.iter().copied().min_by(|a, b| (a - zoom).abs().total_cmp(&(b - zoom).abs())).unwrap_or(1.0)
57 }
58}
59
60/// What the settings window shows: the settings, and what the app and the
61/// system know.
62#[derive(Serialize)]
63#[serde(rename_all = "camelCase")]
64pub struct View {
65 #[serde(flatten)]
66 pub settings: Settings,
67 pub open_at_login: bool,
68 pub version: String,
69 pub platform: String,
70 pub server: String,
71 pub update_ready: Option<String>,
72}
73
74pub fn view(app: &AppHandle) -> View {
75 View {
76 settings: config::settings(app),
77 open_at_login: app.autolaunch().is_enabled().unwrap_or(false),
78 version: env!("CARGO_PKG_VERSION").into(),
79 platform: std::env::consts::OS.into(),
80 server: config::server(app).to_string(),
81 update_ready: crate::updates::ready(app),
82 }
83}
84
85/// Keeps new settings and puts them to work at once.
86pub fn apply(app: &AppHandle, mut settings: Settings) -> Result<(), String> {
87 settings.zoom = Settings::clamp_zoom(settings.zoom);
88 let before = config::settings(app);
89 config::set_settings(app, settings.clone())?;
90 if let Some(window) = app.get_webview_window(WINDOW) {
91 if before.menu_bar != settings.menu_bar {
92 let _ = if settings.menu_bar { window.show_menu() } else { window.hide_menu() };
93 }
94 if before.zoom != settings.zoom {
95 let _ = window.set_zoom(settings.zoom);
96 }
97 }
98 if before.badge != settings.badge {
99 crate::badge::refresh(app);
100 }
101 Ok(())
102}
103
104/// Turns opening at sign-in on or off, with the system.
105pub fn open_at_login(app: &AppHandle, on: bool) -> Result<(), String> {
106 let autolaunch = app.autolaunch();
107 let result = if on { autolaunch.enable() } else { autolaunch.disable() };
108 result.map_err(|error| error.to_string())
109}
110
111/// Steps the zoom in (1), out (-1), or back to as it is (0).
112pub fn zoom(app: &AppHandle, step: i32) {
113 let mut settings = config::settings(app);
114 let at = ZOOMS.iter().position(|zoom| *zoom == Settings::clamp_zoom(settings.zoom)).unwrap_or(4);
115 settings.zoom = match step {
116 0 => 1.0,
117 1 => ZOOMS[(at + 1).min(ZOOMS.len() - 1)],
118 _ => ZOOMS[at.saturating_sub(1)],
119 };
120 let _ = apply(app, settings);
121}
122
123/// Whether this launch is the system's, at sign-in, and g1t should wait in the tray.
124pub fn starts_hidden(app: &AppHandle) -> bool {
125 std::env::args().any(|arg| arg == AT_LOGIN) && config::settings(app).start_hidden
126}
127
128/// Opens the settings window, or brings it forward.
129pub fn open(app: &AppHandle) {
130 if let Some(window) = app.get_webview_window(SETTINGS) {
131 let _ = window.unminimize();
132 let _ = window.show();
133 let _ = window.set_focus();
134 return;
135 }
136 let built = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App("settings.html".into()))
137 .title("g1t settings")
138 .inner_size(560.0, 680.0)
139 .min_inner_size(440.0, 420.0)
140 .maximizable(false)
141 .minimizable(false)
142 .center()
143 // The page is bundled with the app; its links go to the browser (settings.js).
144 .on_navigation(config::is_launcher)
145 .build();
146 match built {
147 Ok(window) => {
148 let _ = window.set_focus();
149 }
150 Err(error) => eprintln!("g1t: the settings window could not open: {error}"),
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::Settings;
157
158 #[test]
159 fn a_missing_setting_is_its_default() {
160 let settings: Settings = serde_json::from_str(r#"{"closeToTray":false}"#).unwrap();
161 assert!(!settings.close_to_tray);
162 assert!(settings.notifications);
163 assert_eq!(settings.zoom, 1.0);
164 }
165
166 #[test]
167 fn zoom_is_one_the_app_offers() {
168 assert_eq!(Settings::clamp_zoom(1.07), 1.1);
169 assert_eq!(Settings::clamp_zoom(9.0), 2.0);
170 assert_eq!(Settings::clamp_zoom(0.1), 0.67);
171 }
172}