| 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 | })(); |