Skip to content

Commit

Permalink
In-app toasts for electron-updater notifications (#3902)
Browse files Browse the repository at this point in the history
* Add custom updater model back after electron migration
Fixes #3872

* Enable release builds (temp)

* Lint & clean up

* Change approach to no user input, heads up with toast

* Re-enable prod builds

* Working toasts

* Only one toast

* Add missing type

* Clean up before review

* New toast design test

* Clean up

* Use theme colors, add link to changelog

---------

Co-authored-by: Frank Noirot <[email protected]>
  • Loading branch information
pierremtb and franknoirot authored Sep 24, 2024
1 parent 2263958 commit 8b0b5a0
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 10 deletions.
4 changes: 4 additions & 0 deletions interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export interface IElectronAPI {
kittycad: (access: string, args: any) => any
listMachines: () => Promise<MachinesListing>
getMachineApiIp: () => Promise<string | null>
onUpdateDownloaded: (
callback: (value: string) => void
) => Electron.IpcRenderer
appRestart: () => void
}

declare global {
Expand Down
64 changes: 64 additions & 0 deletions src/components/ToastUpdate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import toast from 'react-hot-toast'
import { ActionButton } from './ActionButton'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'

export function ToastUpdate({
version,
onRestart,
}: {
version: string
onRestart: () => void
}) {
return (
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<div className="my-4 flex items-baseline">
<span
className="px-3 py-1 text-xl rounded-full bg-primary text-chalkboard-10"
data-testid="update-version"
>
v{version}
</span>
<span className="ml-4 text-md text-bold">
A new update has downloaded and will be available next time you
start the app. You can view the release notes{' '}
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`}
target="_blank"
rel="noreferrer"
>
here on GitHub.
</a>
</span>
</div>
<div className="flex justify-between gap-8">
<ActionButton
Element="button"
iconStart={{
icon: 'arrowRotateRight',
}}
name="Restart app now"
onClick={onRestart}
>
Restart app now
</ActionButton>
<ActionButton
Element="button"
iconStart={{
icon: 'checkmark',
}}
name="Got it"
onClick={() => {
toast.dismiss()
}}
>
Got it
</ActionButton>
</div>
</div>
</div>
)
}
18 changes: 16 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import ReactDOM from 'react-dom/client'
import './index.css'
import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast'
import toast, { Toaster } from 'react-hot-toast'
import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook'
import ModalContainer from 'react-modal-promise'
import { isDesktop } from 'lib/isDesktop'
import { AppStreamProvider } from 'AppState'
import { ToastUpdate } from 'components/ToastUpdate'

// uncomment for xstate inspector
// import { DEV } from 'env'
Expand Down Expand Up @@ -52,4 +53,17 @@ root.render(
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()

isDesktop()
isDesktop() &&
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)
toast.custom(
ToastUpdate({
version,
onRestart: () => {
window.electron.appRestart()
},
}),
{ duration: 30000 }
)
})
17 changes: 9 additions & 8 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,18 +251,14 @@ export function getAutoUpdater(): AppUpdater {
return autoUpdater
}

export async function checkForUpdates(autoUpdater: AppUpdater) {
// TODO: figure out how to get the update modal back
const result = await autoUpdater.checkForUpdatesAndNotify()
console.log(result)
}

app.on('ready', () => {
const autoUpdater = getAutoUpdater()
checkForUpdates(autoUpdater).catch(reportRejection)
setTimeout(() => {
autoUpdater.checkForUpdates().catch(reportRejection)
}, 1000)
const fifteenMinutes = 15 * 60 * 1000
setInterval(() => {
checkForUpdates(autoUpdater).catch(reportRejection)
autoUpdater.checkForUpdates().catch(reportRejection)
}, fifteenMinutes)

autoUpdater.on('update-available', (info) => {
Expand All @@ -271,6 +267,11 @@ app.on('ready', () => {

autoUpdater.on('update-downloaded', (info) => {
console.log('update-downloaded', info)
mainWindow?.webContents.send('update-downloaded', info.version)
})

ipcMain.handle('app.restart', () => {
autoUpdater.quitAndInstall()
})
})

Expand Down
5 changes: 5 additions & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const startDeviceFlow = (host: string): Promise<string> =>
ipcRenderer.invoke('startDeviceFlow', host)
const loginWithDeviceFlow = (): Promise<string> =>
ipcRenderer.invoke('loginWithDeviceFlow')
const onUpdateDownloaded = (callback: (value: string) => void) =>
ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
const appRestart = () => ipcRenderer.invoke('app.restart')

const isMac = os.platform() === 'darwin'
const isWindows = os.platform() === 'win32'
Expand Down Expand Up @@ -123,4 +126,6 @@ contextBridge.exposeInMainWorld('electron', {
kittycad,
listMachines,
getMachineApiIp,
onUpdateDownloaded,
appRestart,
})

0 comments on commit 8b0b5a0

Please sign in to comment.