diff options
| author | haoyuren <13851610112@163.com> | 2026-08-18 20:30:17 +0800 |
|---|---|---|
| committer | haoyuren <13851610112@163.com> | 2026-08-18 20:30:17 +0800 |
| commit | 1251d9208caadce626fd0706dfd34ba3e022fb2a (patch) | |
| tree | f8833a14bf299684778cce8e91a16347223bc559 /src/renderer | |
| parent | ec8214a4d111d92ce7c44de29d1706f241aa9514 (diff) | |
Add in-app update check with one-click installer downloadv0.6.1
On launch the home page checks the latest GitHub release; when a newer
version exists a bottom-right card offers Download (streams the right
installer for the platform into ~/Downloads and opens it), Later, or
Skip this version (remembered). Unsigned app rules out silent
auto-update on macOS, so installation stays a manual replace.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'src/renderer')
| -rw-r--r-- | src/renderer/src/App.css | 46 | ||||
| -rw-r--r-- | src/renderer/src/App.tsx | 89 |
2 files changed, 135 insertions, 0 deletions
diff --git a/src/renderer/src/App.css b/src/renderer/src/App.css index d790a14..40867c4 100644 --- a/src/renderer/src/App.css +++ b/src/renderer/src/App.css @@ -1090,6 +1090,52 @@ html, body, #root { color: var(--text-primary); } +/* ── Update toast (bottom-right card on the home page) ─────────── */ + +.update-toast { + position: fixed; + bottom: 16px; + right: 16px; + z-index: 1000; + width: 320px; + padding: 14px 16px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-md); + font-size: 13px; + color: var(--text-primary); +} + +.update-toast-title { + font-weight: 600; + margin-bottom: 10px; + display: flex; + align-items: baseline; + gap: 8px; +} + +.update-toast-link { + border: none; + background: none; + padding: 0; + font-size: 12px; + color: var(--accent-blue); + cursor: pointer; + text-decoration: underline; +} + +.update-toast-body { + color: var(--text-secondary); + margin-bottom: 10px; +} + +.update-toast-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + /* ── Projects Page ──────────────────────────────────────────── */ .projects-page { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 3aa68e2..f3f9f79 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -39,6 +39,94 @@ export const remoteCursors = new Map<string, RemoteCursor & { docId: string }>() // the list window opens each project in its own window via ?projectId=) const initialProjectId = new URLSearchParams(window.location.search).get('projectId') +/** Update-available card (home renderer only). Checks GitHub Releases once + * per launch; "Skip this version" is remembered in localStorage. */ +function UpdateToast() { + const [info, setInfo] = useState<{ + version: string + releaseUrl: string + assetName?: string + assetUrl?: string + assetSize?: number + } | null>(null) + const [phase, setPhase] = useState<'idle' | 'downloading' | 'done' | 'error'>('idle') + + useEffect(() => { + window.api.updateCheck().then((r) => { + if (!r.available || !r.version) return + if (localStorage.getItem('lattex-skip-version') === r.version) return + setInfo({ + version: r.version, + releaseUrl: r.releaseUrl || 'https://github.com/YurenHao0426/lattex/releases', + assetName: r.assetName, + assetUrl: r.assetUrl, + assetSize: r.assetSize + }) + }).catch(() => {}) + }, []) + + if (!info) return null + + const sizeMb = info.assetSize ? ` (${(info.assetSize / 1024 / 1024).toFixed(0)} MB)` : '' + + const download = async () => { + if (!info.assetUrl || !info.assetName) { + // No installer for this platform in the release — open the page + window.api.openExternal(info.releaseUrl) + return + } + setPhase('downloading') + const r = await window.api.updateDownload(info.assetUrl, info.assetName) + setPhase(r.success ? 'done' : 'error') + } + + return ( + <div className="update-toast"> + <div className="update-toast-title"> + Update available: v{info.version} + <button className="update-toast-link" onClick={() => window.api.openExternal(info.releaseUrl)}> + release notes + </button> + </div> + {phase === 'done' ? ( + <div className="update-toast-body"> + Installer opened — quit LatteX and replace the app to finish updating. + </div> + ) : phase === 'error' ? ( + <div className="update-toast-body"> + Download failed — you can get it from the releases page instead. + </div> + ) : ( + <div className="update-toast-actions"> + <button className="btn btn-primary btn-sm" onClick={download} disabled={phase === 'downloading'}> + {phase === 'downloading' ? 'Downloading…' : `Download${sizeMb}`} + </button> + <button className="btn btn-sm" onClick={() => setInfo(null)}>Later</button> + <button + className="btn btn-sm" + onClick={() => { + localStorage.setItem('lattex-skip-version', info.version) + setInfo(null) + }} + > + Skip this version + </button> + </div> + )} + {(phase === 'done' || phase === 'error') && ( + <div className="update-toast-actions"> + <button className="btn btn-sm" onClick={() => setInfo(null)}>Dismiss</button> + {phase === 'error' && ( + <button className="btn btn-sm" onClick={() => window.api.openExternal(info.releaseUrl)}> + Open releases page + </button> + )} + </div> + )} + </div> + ) +} + /** Browser-style tab strip: persistent home tab + one tab per open project. * Rendered by the home renderer only, in the top TAB_BAR_HEIGHT (38px) strip * that project views never cover. Hidden entirely when no project is open. */ @@ -657,6 +745,7 @@ export default function App() { <div className="home-content"> <ProjectList /> </div> + <UpdateToast /> </div> </> ) |
