Skip to main content

max / goingson

fix: restore shared-updater.js symlink (sed -i had clobbered it to a regular file) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-13 01:52 UTC
Commit: 303d1227847ec0c230d3d4ca5f2c2d2dbd015468
Parent: 582f7a2
2 files changed, +1 insertion, -212 deletions
@@ -1,212 +0,0 @@
1 - /**
2 - * Shared Tauri OTA Update UI
3 - *
4 - * Listens for update-available events from the Rust backend and shows
5 - * a notification banner with Install / Dismiss buttons. The user must
6 - * explicitly consent before any download or installation happens.
7 - *
8 - * Usage:
9 - * initUpdater({ ui, escapeHtml, namespace })
10 - *
11 - * - ui: object with showToast(msg, level?) method
12 - * - escapeHtml: function(str) -> safe string (optional, falls back to identity)
13 - * - namespace: object to attach { showUpdateBanner } onto (e.g. GoingsOn.updater)
14 - * - bannerClass: CSS class name for the banner (optional, uses inline styles if omitted)
15 - * - cssVars: override CSS variable names (optional)
16 - * - renderMarkdown: function(body) -> safe HTML string (optional). When supplied,
17 - * release notes render as HTML and the 120-char preview truncation is dropped.
18 - * The caller is responsible for sanitizing the output.
19 - */
20 -
21 - // eslint-disable-next-line no-unused-vars
22 - function initUpdater(opts) {
23 - 'use strict';
24 -
25 - const ui = opts.ui;
26 - const esc = opts.escapeHtml || ((s) => s);
27 - const vars = Object.assign({
28 - accent: 'var(--action)',
29 - border: 'var(--border)',
30 - textSecondary: 'var(--content-secondary)',
31 - bgSecondary: 'var(--surface-overlay)',
32 - fontBody: 'var(--font-body, sans-serif)',
33 - }, opts.cssVars || {});
34 -
35 - let updateBannerShown = false;
36 - let pendingUpdate = null;
37 -
38 - const BTN_STYLE = [
39 - 'padding: 0.25rem 0.5rem',
40 - 'border-radius: 4px',
41 - 'cursor: pointer',
42 - 'font-size: 0.8rem',
43 - ].join(';');
44 -
45 - function showUpdateBanner(version, body, update) {
46 - if (updateBannerShown) return;
47 - updateBannerShown = true;
48 - pendingUpdate = update || null;
49 -
50 - const banner = document.createElement('div');
51 - banner.id = 'update-banner';
52 -
53 - if (opts.bannerClass) {
54 - banner.className = opts.bannerClass;
55 - } else {
56 - banner.style.cssText = [
57 - 'position: fixed',
58 - 'bottom: 1rem',
59 - 'right: 1rem',
60 - 'background: ' + vars.bgSecondary,
61 - 'border: 1px solid ' + vars.border,
62 - 'border-radius: 8px',
63 - 'padding: 0.75rem 1rem',
64 - 'z-index: 9999',
65 - 'max-width: 320px',
66 - 'box-shadow: 0 4px 12px rgba(0,0,0,0.3)',
67 - 'font-family: ' + vars.fontBody,
68 - 'font-size: 0.875rem',
69 - ].join(';');
70 - }
71 -
72 - const title = document.createElement('div');
73 - title.style.cssText = 'font-weight: 600; margin-bottom: 0.25rem;';
74 - title.textContent = 'Update Available: v' + esc(version);
75 - banner.appendChild(title);
76 -
77 - if (body) {
78 - const notes = document.createElement('div');
79 - notes.style.cssText = 'color: ' + vars.textSecondary + '; margin-bottom: 0.5rem; max-height: 8rem; overflow-y: auto;';
80 - if (typeof opts.renderMarkdown === 'function') {
81 - notes.innerHTML = opts.renderMarkdown(body);
82 - } else {
83 - notes.textContent = body.length > 120 ? body.substring(0, 120) + '...' : body;
84 - }
85 - banner.appendChild(notes);
86 - }
87 -
88 - const buttons = document.createElement('div');
89 - buttons.style.cssText = 'display: flex; gap: 0.5rem; margin-top: 0.5rem;';
90 -
91 - const install = document.createElement('button');
92 - install.textContent = 'Install & Restart';
93 - install.style.cssText = BTN_STYLE + ';background: ' + vars.accent + '; border: none; color: #fff; font-weight: 600;';
94 - install.onclick = () => installUpdate(banner);
95 - buttons.appendChild(install);
96 -
97 - const dismiss = document.createElement('button');
98 - dismiss.textContent = 'Not Now';
99 - dismiss.style.cssText = BTN_STYLE + ';background: none; border: 1px solid ' + vars.border + '; color: ' + vars.textSecondary + ';';
100 - dismiss.onclick = () => {
101 - banner.remove();
102 - updateBannerShown = false;
103 - pendingUpdate = null;
104 - };
105 - buttons.appendChild(dismiss);
106 -
107 - banner.appendChild(buttons);
108 - document.body.appendChild(banner);
109 - }
110 -
111 - async function installUpdate(banner) {
112 - if (!pendingUpdate) {
113 - try {
114 - const { check } = window.__TAURI_PLUGIN_UPDATER__;
115 - pendingUpdate = await check();
116 - } catch (err) {
117 - ui.showToast('Update check failed: ' + err, 'error');
118 - return;
119 - }
120 - }
121 -
122 - if (!pendingUpdate) {
123 - ui.showToast('No update available.', 'error');
124 - return;
125 - }
126 -
127 - banner.innerHTML = '';
128 - const status = document.createElement('div');
129 - status.textContent = 'Starting download...';
130 - status.style.cssText = 'margin-bottom: 0.5rem;';
131 - banner.appendChild(status);
132 -
133 - const progressWrap = document.createElement('div');
134 - progressWrap.style.cssText = 'height: 4px; background: ' + vars.border + '; border-radius: 2px; overflow: hidden;';
135 - const progressBar = document.createElement('div');
136 - progressBar.style.cssText = 'height: 100%; width: 0%; background: ' + vars.accent + '; transition: width 120ms linear;';
137 - progressWrap.appendChild(progressBar);
138 - banner.appendChild(progressWrap);
139 -
140 - let total = 0;
141 - let received = 0;
142 - const onEvent = (e) => {
143 - if (!e || !e.event) return;
144 - if (e.event === 'Started') {
145 - total = (e.data && e.data.contentLength) || 0;
146 - received = 0;
147 - status.textContent = total > 0
148 - ? 'Downloading update (0%)...'
149 - : 'Downloading update...';
150 - } else if (e.event === 'Progress') {
151 - received += (e.data && e.data.chunkLength) || 0;
152 - if (total > 0) {
153 - const pct = Math.min(100, Math.round((received / total) * 100));
154 - progressBar.style.width = pct + '%';
155 - status.textContent = 'Downloading update (' + pct + '%)...';
156 - }
157 - } else if (e.event === 'Finished') {
158 - progressBar.style.width = '100%';
159 - status.textContent = 'Installing...';
160 - }
161 - };
162 -
163 - try {
164 - await pendingUpdate.downloadAndInstall(onEvent);
165 - status.textContent = 'Update installed. Restarting...';
166 - // Relaunch into the new version. Best-effort: requires the host app
167 - // to register the process plugin; if it's absent the install still
168 - // succeeded and the user can restart manually.
169 - try {
170 - await window.__TAURI__.process.relaunch();
171 - } catch (e) {
172 - status.textContent = 'Update installed. Please quit and reopen the app.';
173 - }
174 - } catch (err) {
175 - status.textContent = 'Update failed: ' + err;
176 - ui.showToast('Update failed: ' + err, 'error');
177 - updateBannerShown = false;
178 - }
179 - }
180 -
181 - // Listen for automatic update check results from Rust backend
182 - if (window.__TAURI__) {
183 - const { listen } = window.__TAURI__.event;
184 -
185 - listen('update-available', (event) => {
186 - const { version, body } = event.payload;
187 - showUpdateBanner(version, body, null);
188 - });
189 -
190 - listen('menu:check_updates', async () => {
191 - ui.showToast('Checking for updates...');
192 - try {
193 - const { check } = window.__TAURI_PLUGIN_UPDATER__;
194 - const update = await check();
195 - if (update) {
196 - showUpdateBanner(update.version, update.body || '', update);
197 - } else {
198 - ui.showToast('You are running the latest version.');
199 - }
200 - } catch (err) {
201 - ui.showToast('Update check failed: ' + err, 'error');
202 - }
203 - });
204 - }
205 -
206 - // Attach to caller's namespace
207 - if (opts.namespace) {
208 - opts.namespace.showUpdateBanner = showUpdateBanner;
209 - }
210 -
211 - return { showUpdateBanner };
212 - }
@@ -0,0 +1 @@
1 + ../../../../../MNW/shared/tauri-updater-ui/updater.js
1 < \ No newline at end of file