diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-win32.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-win32.png index 8cca06272..60d5ffd49 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-win32.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-win32.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-win32.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-win32.png index f6ea762d5..49764cce2 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-win32.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-win32.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png index f94bd2630..ad78a77e5 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png differ diff --git a/interface.d.ts b/interface.d.ts index 7319c69c1..66938f10d 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -69,9 +69,13 @@ export interface IElectronAPI { kittycad: (access: string, args: any) => any listMachines: () => Promise getMachineApiIp: () => Promise + onUpdateDownloadStart: ( + callback: (value: { version: string }) => void + ) => Electron.IpcRenderer onUpdateDownloaded: ( callback: (value: string) => void ) => Electron.IpcRenderer + onUpdateError: (callback: (value: { error: Error }) => void) => Electron appRestart: () => void } diff --git a/src/index.tsx b/src/index.tsx index 6e9b4f4a7..4ce2fff5a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,6 +8,7 @@ import ModalContainer from 'react-modal-promise' import { isDesktop } from 'lib/isDesktop' import { AppStreamProvider } from 'AppState' import { ToastUpdate } from 'components/ToastUpdate' +import { AUTO_UPDATER_TOAST_ID } from 'lib/constants' // uncomment for xstate inspector // import { DEV } from 'env' @@ -53,7 +54,22 @@ root.render( // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals() -isDesktop() && +if (isDesktop()) { + // Listen for update download progress to begin + // to show a loading toast. + window.electron.onUpdateDownloadStart(() => { + const message = `Downloading app update...` + console.log(message) + toast.loading(message, { id: AUTO_UPDATER_TOAST_ID }) + }) + // Listen for update download errors to show + // an error toast and clear the loading toast. + window.electron.onUpdateError(({ error }) => { + console.error(error) + toast.error('An error occurred while downloading the update.', { + id: AUTO_UPDATER_TOAST_ID, + }) + }) window.electron.onUpdateDownloaded((version: string) => { const message = `A new update (${version}) was downloaded and will be available next time you open the app.` console.log(message) @@ -64,6 +80,7 @@ isDesktop() && window.electron.appRestart() }, }), - { duration: 30000 } + { duration: 30000, id: AUTO_UPDATER_TOAST_ID } ) }) +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4c906dc9d..ae6973df7 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -102,3 +102,6 @@ export const KCL_SAMPLES_MANIFEST_URLS = { 'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json', localFallback: '/kcl-samples-manifest-fallback.json', } as const + +/** Toast id for the app auto-updater toast */ +export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast' diff --git a/src/main.ts b/src/main.ts index c561910b0..7f8c9d614 100644 --- a/src/main.ts +++ b/src/main.ts @@ -261,10 +261,30 @@ app.on('ready', () => { autoUpdater.checkForUpdates().catch(reportRejection) }, fifteenMinutes) + autoUpdater.on('error', (error) => { + console.error('updater-error', error) + mainWindow?.webContents.send('updater-error', error) + }) + autoUpdater.on('update-available', (info) => { console.log('update-available', info) }) + autoUpdater.prependOnceListener('download-progress', (progress) => { + // For now, we'll send nothing and just start a loading spinner. + // See below for a TODO to send progress data to the renderer. + console.log('update-download-start', { + version: '', + }) + mainWindow?.webContents.send('update-download-start', progress) + }) + + autoUpdater.on('download-progress', (progress) => { + // TODO: in a future PR (https://github.com/KittyCAD/modeling-app/issues/3994) + // send this data to mainWindow to show a progress bar for the download. + console.log('download-progress', progress) + }) + autoUpdater.on('update-downloaded', (info) => { console.log('update-downloaded', info) mainWindow?.webContents.send('update-downloaded', info.version) diff --git a/src/preload.ts b/src/preload.ts index 1f458a424..52e37c1d3 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -16,8 +16,13 @@ const startDeviceFlow = (host: string): Promise => ipcRenderer.invoke('startDeviceFlow', host) const loginWithDeviceFlow = (): Promise => ipcRenderer.invoke('loginWithDeviceFlow') +const onUpdateDownloadStart = ( + callback: (value: { version: string }) => void +) => ipcRenderer.on('update-download-start', (_event, value) => callback(value)) const onUpdateDownloaded = (callback: (value: string) => void) => ipcRenderer.on('update-downloaded', (_event, value) => callback(value)) +const onUpdateError = (callback: (value: Error) => void) => + ipcRenderer.on('update-error', (_event, value) => callback(value)) const appRestart = () => ipcRenderer.invoke('app.restart') const isMac = os.platform() === 'darwin' @@ -144,6 +149,8 @@ contextBridge.exposeInMainWorld('electron', { kittycad, listMachines, getMachineApiIp, + onUpdateDownloadStart, onUpdateDownloaded, + onUpdateError, appRestart, })