Skip to content
248 linesCodeBlameRaw
1// The settings window's work: show what the app has, and keep each change
2// the moment it is made. Everything it asks is a command in
3// src/bridge.rs. The settings usually open over the site, which draws
4// them (apps/web components/desktop-settings.tsx); this window is for when
5// the window shows no page of the site's. The runner has a window of its
6// own (runner.html); here it is a line that opens it. A button that asks
7// the app for something is busy until it is done (busy.js).
8(function () {
9 var SWITCHES = ["startHidden", "closeToTray", "notifications", "flash", "badge", "systemTitleBar", "autoUpdate"];
10 // The settings the app keeps (src/settings.rs Settings); a save sends them all.
11 var KEPT = SWITCHES.concat(["zoom", "developerTools", "runner", "runnerBackend", "runnerWorkspace", "runnerCapabilities"]);
12 var current = null;
13
14 function byId(id) {
15 return document.getElementById(id);
16 }
17
18 function invoke(command, args) {
19 var core = window.__TAURI__ && window.__TAURI__.core;
20 if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
21 return Promise.reject(new Error("This page belongs inside the g1t desktop app."));
22 }
23
24 function fail(error) {
25 var box = byId("error");
26 box.textContent = typeof error === "string" ? error : (error && error.message) || "That could not be changed.";
27 box.hidden = false;
28 }
29
30 function hostOf(server) {
31 try {
32 return new URL(server).host;
33 } catch (error) {
34 return server;
35 }
36 }
37
38 // What changes between Windows, macOS and Linux.
39 function platform(name) {
40 byId("flash-label").textContent =
41 name === "macos" ? "Bounce the dock icon" : name === "windows" ? "Flash the taskbar icon" : "Ask for attention";
42 // macOS keeps its menu bar at the top of the screen, whichever title bar the window has.
43 if (name === "macos") byId("system-title-bar-note").textContent = "In place of g1t's own, which sits under the window's buttons.";
44 if (name === "macos") byId("zoom-note").textContent = "The View menu changes it too: ⌘+, ⌘− and ⌘0.";
45 else byId("zoom-note").textContent = "The View menu changes it too: Ctrl+=, Ctrl+− and Ctrl+0.";
46 }
47
48 function render(view) {
49 current = view;
50 byId("error").hidden = true;
51 byId("openAtLogin").checked = !!view.openAtLogin;
52 SWITCHES.forEach(function (key) {
53 byId(key).checked = !!view[key];
54 });
55 // Starting in the tray only means something when g1t starts at sign-in.
56 byId("startHidden").disabled = !view.openAtLogin;
57 byId("startHidden").closest(".setting").classList.toggle("off", !view.openAtLogin);
58 // The title bar changes when g1t restarts.
59 byId("restart-row").hidden = !view.titleBarChanged;
60 var zoom = byId("zoom");
61 zoom.value = String(view.zoom);
62 if (zoom.value === "") zoom.value = "1";
63 byId("version").textContent = "g1t " + view.version;
64 if (!checking) updateStatus(view);
65 byId("server").textContent = hostOf(view.server);
66 renderRunner(view.runnerView);
67 }
68
69 // The runner, in a line: what it is doing, and the window that has the rest.
70 function renderRunner(runner) {
71 if (!runner) return;
72 byId("runner-dot").className = "dot " + runner.chip.tone;
73 byId("runner-title").textContent = runner.chip.label;
74 byId("runner-summary").textContent = runner.diagnosis.title + (runner.version ? " · g1t-runner " + runner.version : "");
75 }
76
77 // Every setting, as the app has it now (the Runner window changes the
78 // runner's), with this window's own as they are on the page.
79 function settingsOf(fresh) {
80 var settings = {};
81 KEPT.forEach(function (key) {
82 if (key in fresh) settings[key] = fresh[key];
83 });
84 SWITCHES.forEach(function (key) {
85 settings[key] = byId(key).checked;
86 });
87 settings.zoom = Number(byId("zoom").value) || 1;
88 return settings;
89 }
90
91
92 function save() {
93 invoke("get_settings")
94 .then(function (fresh) {
95 return invoke("save_settings", { settings: settingsOf(fresh) });
96 })
97 .then(render, function (error) {
98 fail(error);
99 if (current) render(current);
100 });
101 }
102
103 byId("open-runner").addEventListener("click", function () {
104 window.g1tBusy.run(byId("open-runner"), "open-runner", "", false, function () {
105 return invoke("runner_control", { action: "open_runner" });
106 }, fail);
107 });
108
109 SWITCHES.forEach(function (key) {
110 byId(key).addEventListener("change", save);
111 });
112 byId("zoom").addEventListener("change", save);
113 byId("openAtLogin").addEventListener("change", function (event) {
114 invoke("set_open_at_login", { on: event.target.checked }).then(render, function (error) {
115 fail(error);
116 if (current) render(current);
117 });
118 });
119
120 // Updates: where this copy stands, said in the window, so a check from
121 // the menu or the tray is answered here and cannot be missed.
122 var checking = false;
123
124 function when(ms) {
125 var date = new Date(ms);
126 var today = new Date().toDateString() === date.toDateString();
127 var time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
128 return today ? "today at " + time : date.toLocaleDateString([], { month: "short", day: "numeric" }) + " at " + time;
129 }
130
131 function ready(version) {
132 byId("update-status").textContent = "Version " + version + " is downloaded and installs when g1t restarts.";
133 byId("restart").hidden = false;
134 }
135
136 function updateStatus(view) {
137 if (view.updateReady) return ready(view.updateReady);
138 byId("restart").hidden = true;
139 byId("update-status").textContent = view.checkedAt
140 ? "Up to date. Last checked " + when(view.checkedAt) + "."
141 : view.autoUpdate
142 ? "Not checked yet since g1t started; it checks on its own shortly."
143 : "Checking on its own is off.";
144 }
145
146 function check() {
147 if (checking) return;
148 checking = true;
149 var button = byId("check");
150 var status = byId("update-status");
151 window.g1tBusy.start(button);
152 status.textContent = "Checking, and downloading any new version…";
153 invoke("check_for_updates")
154 .then(function (answer) {
155 if (answer.indexOf("ready:") === 0) ready(answer.slice(6));
156 else status.textContent = "Up to date. Last checked " + when(Date.now()) + ".";
157 })
158 .catch(function (error) {
159 var text = typeof error === "string" ? error : "";
160 status.textContent = /sending request|connect|dns|timed out/i.test(text) || !text
161 ? "Could not reach g1t.sh to check. Check your connection and try again."
162 : "Could not check: " + text;
163 })
164 .then(function () {
165 checking = false;
166 window.g1tBusy.stop(button);
167 });
168 }
169
170 byId("check").addEventListener("click", check);
171 byId("restart").addEventListener("click", function () {
172 window.g1tBusy.start(byId("restart"));
173 byId("update-status").textContent = "Installing…";
174 invoke("restart_to_update").catch(function (error) {
175 window.g1tBusy.stop(byId("restart"));
176 fail(error);
177 });
178 });
179
180 // The menu's or tray's "Check for updates…" with this window open, and a
181 // download finishing in the background (src/settings.rs).
182 var events = window.__TAURI__ && window.__TAURI__.event;
183 if (events && typeof events.listen === "function") {
184 events.listen("g1t-check-updates", check).catch(function () {});
185 events
186 .listen("g1t-runner-status", function (event) {
187 if (current) current.runnerView = event.payload;
188 renderRunner(event.payload);
189 })
190 .catch(function () {});
191 events
192 .listen("g1t-update-ready", function (event) {
193 if (!checking && typeof event.payload === "string") ready(event.payload);
194 })
195 .catch(function () {});
196 }
197
198 byId("restart-app").addEventListener("click", function () {
199 window.g1tBusy.start(byId("restart-app"));
200 invoke("restart").catch(function (error) {
201 window.g1tBusy.stop(byId("restart-app"));
202 fail(error);
203 });
204 });
205
206 byId("change-server").addEventListener("click", function () {
207 window.g1tBusy.run(byId("change-server"), "change-server", "", false, function () {
208 return invoke("change_server");
209 }, fail);
210 });
211
212 // Links out, in the system's browser (src/bridge.js does the same on the site).
213 document.addEventListener("click", function (event) {
214 var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
215 if (!anchor) return;
216 event.preventDefault();
217 invoke("open_url", { url: anchor.href }).catch(function () {});
218 });
219
220 // g1t's own title bar, when the window has no system frame.
221 function frame(view) {
222 if (!view.customFrame) return;
223 if (view.platform === "macos") document.documentElement.classList.add("mac");
224 var bar = byId("titlebar");
225 bar.hidden = false;
226 document.body.classList.add("framed");
227 bar.addEventListener("mousedown", function (event) {
228 if (event.button !== 0 || event.target.closest("button")) return;
229 invoke("window_control", { action: "drag" }).catch(function () {});
230 });
231 byId("close-window").addEventListener("click", function () {
232 invoke("window_control", { action: "close" }).catch(function () {});
233 });
234 }
235
236 invoke("get_settings")
237 .then(function (view) {
238 frame(view);
239 platform(view.platform);
240 render(view);
241 // Opened by "Check for updates…" in a menu.
242 if (new URLSearchParams(location.search).has("check")) {
243 byId("updates-row").scrollIntoView({ block: "center" });
244 check();
245 }
246 })
247 .catch(fail);
248})();