Skip to content
197 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,
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.2//! how it tells the person something arrived, its title bar, zoom and
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.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};
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.10use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.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,
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.39 /// The system's title bar, and on Windows and Linux its menu bar, in
40 /// place of g1t's own (frame.rs). Takes effect when g1t restarts.
41 pub system_title_bar: bool,
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.42 /// The site's zoom, 1 for as it is.
43 pub zoom: f64,
44 /// Updates download by themselves; a person can still check from the menu.
45 pub auto_update: bool,
46}
47
48impl Default for Settings {
49 fn default() -> Self {
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.50 Self { start_hidden: true, close_to_tray: true, notifications: true, flash: true, badge: true, system_title_bar: false, zoom: 1.0, auto_update: true }
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.51 }
52}
53
54impl Settings {
55 /// A zoom the app offers, nearest to the one asked for.
56 pub fn clamp_zoom(zoom: f64) -> f64 {
57 ZOOMS.iter().copied().min_by(|a, b| (a - zoom).abs().total_cmp(&(b - zoom).abs())).unwrap_or(1.0)
58 }
59}
60
61/// What the settings window shows: the settings, and what the app and the
62/// system know.
63#[derive(Serialize)]
64#[serde(rename_all = "camelCase")]
65pub struct View {
66 #[serde(flatten)]
67 pub settings: Settings,
68 pub open_at_login: bool,
69 pub version: String,
70 pub platform: String,
71 pub server: String,
72 pub update_ready: Option<String>,
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.73 /// When the app last asked for updates and heard back, in milliseconds since 1970.
74 pub checked_at: Option<u64>,
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.75 /// The title bar setting differs from the one the windows have: a restart applies it.
76 pub title_bar_changed: bool,
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.77}
78
79pub fn view(app: &AppHandle) -> View {
80 View {
81 settings: config::settings(app),
82 open_at_login: app.autolaunch().is_enabled().unwrap_or(false),
83 version: env!("CARGO_PKG_VERSION").into(),
84 platform: std::env::consts::OS.into(),
85 server: config::server(app).to_string(),
86 update_ready: crate::updates::ready(app),
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.87 checked_at: crate::updates::checked_at(app),
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.88 title_bar_changed: crate::frame::started(app) != crate::frame::custom(app),
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.89 }
90}
91
92/// Keeps new settings and puts them to work at once.
93pub fn apply(app: &AppHandle, mut settings: Settings) -> Result<(), String> {
94 settings.zoom = Settings::clamp_zoom(settings.zoom);
95 let before = config::settings(app);
96 config::set_settings(app, settings.clone())?;
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.97 if let Some(window) = app.get_webview_window(WINDOW).filter(|_| before.zoom != settings.zoom) {
98 let _ = window.set_zoom(settings.zoom);
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.99 }
100 if before.badge != settings.badge {
101 crate::badge::refresh(app);
102 }
103 Ok(())
104}
105
106/// Turns opening at sign-in on or off, with the system.
107pub fn open_at_login(app: &AppHandle, on: bool) -> Result<(), String> {
108 let autolaunch = app.autolaunch();
109 let result = if on { autolaunch.enable() } else { autolaunch.disable() };
110 result.map_err(|error| error.to_string())
111}
112
113/// Steps the zoom in (1), out (-1), or back to as it is (0).
114pub fn zoom(app: &AppHandle, step: i32) {
115 let mut settings = config::settings(app);
116 let at = ZOOMS.iter().position(|zoom| *zoom == Settings::clamp_zoom(settings.zoom)).unwrap_or(4);
117 settings.zoom = match step {
118 0 => 1.0,
119 1 => ZOOMS[(at + 1).min(ZOOMS.len() - 1)],
120 _ => ZOOMS[at.saturating_sub(1)],
121 };
122 let _ = apply(app, settings);
123}
124
125/// Whether this launch is the system's, at sign-in, and g1t should wait in the tray.
126pub fn starts_hidden(app: &AppHandle) -> bool {
127 std::env::args().any(|arg| arg == AT_LOGIN) && config::settings(app).start_hidden
128}
129
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.130/// What the settings window listens for: check for updates now.
131pub const CHECK_EVENT: &str = "g1t-check-updates";
132/// What it listens for: an update is downloaded, with its version.
133pub const READY_EVENT: &str = "g1t-update-ready";
134
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.135/// Opens the settings window, or brings it forward.
136pub fn open(app: &AppHandle) {
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.137 show(app, false);
138}
139
140/// "Check for updates…" in a menu: the settings window opens and checks,
141/// and says what it found, the download included, in its Updates section.
142pub fn open_and_check(app: &AppHandle) {
143 show(app, true);
144}
145
146fn show(app: &AppHandle, check: bool) {
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.147 if let Some(window) = app.get_webview_window(SETTINGS) {
148 let _ = window.unminimize();
149 let _ = window.show();
150 let _ = window.set_focus();
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.151 if check {
152 let _ = app.emit_to(SETTINGS, CHECK_EVENT, ());
153 }
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.154 return;
155 }
Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.156 // A window still loading has no listener yet: the address asks instead.
157 let page = if check { "settings.html?check" } else { "settings.html" };
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.158 let custom = crate::frame::started(app);
159 let builder = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App(page.into()))
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.160 .title("g1t settings")
The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again.161 .initialization_script(crate::bridge::script(SETTINGS, custom));
162 let built = crate::frame::style(builder, custom)
The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it.163 .inner_size(560.0, 680.0)
164 .min_inner_size(440.0, 420.0)
165 .maximizable(false)
166 .minimizable(false)
167 .center()
168 // The page is bundled with the app; its links go to the browser (settings.js).
169 .on_navigation(config::is_launcher)
170 .build();
171 match built {
172 Ok(window) => {
173 let _ = window.set_focus();
174 }
175 Err(error) => eprintln!("g1t: the settings window could not open: {error}"),
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::Settings;
182
183 #[test]
184 fn a_missing_setting_is_its_default() {
185 let settings: Settings = serde_json::from_str(r#"{"closeToTray":false}"#).unwrap();
186 assert!(!settings.close_to_tray);
187 assert!(settings.notifications);
188 assert_eq!(settings.zoom, 1.0);
189 }
190
191 #[test]
192 fn zoom_is_one_the_app_offers() {
193 assert_eq!(Settings::clamp_zoom(1.07), 1.1);
194 assert_eq!(Settings::clamp_zoom(9.0), 2.0);
195 assert_eq!(Settings::clamp_zoom(0.1), 0.67);
196 }
197}