Merge branch 'main' into paultag/tabled

This commit is contained in:
Paul Tagliamonte
2024-09-24 14:14:34 -04:00
committed by GitHub
7 changed files with 144 additions and 14 deletions

4
interface.d.ts vendored
View File

@ -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 {

View File

@ -0,0 +1,42 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { ActionButton } from './ActionButton'
describe('ActionButton tests', () => {
it('ActionButton with no iconStart or iconEnd should have even left and right padding', () => {
render(<ActionButton Element="button">No icons</ActionButton>)
expect(screen.getByRole('button')).toHaveClass('px-2')
})
it('ActionButton with iconStart should have no padding on the left', () => {
render(
<ActionButton Element="button" iconStart={{ icon: 'trash' }}>
Start icon only
</ActionButton>
)
expect(screen.getByRole('button')).toHaveClass('pr-2')
})
it('ActionButton with iconEnd should have no padding on the right', () => {
render(
<ActionButton Element="button" iconEnd={{ icon: 'trash' }}>
End icon only
</ActionButton>
)
expect(screen.getByRole('button')).toHaveClass('pl-2')
})
it('ActionButton with both icons should have no padding on either side', () => {
render(
<ActionButton
Element="button"
iconStart={{ icon: 'trash' }}
iconEnd={{ icon: 'trash' }}
>
Both icons
</ActionButton>
)
expect(screen.getByRole('button')).not.toHaveClass('px-2')
expect(screen.getByRole('button')).toHaveClass('px-0')
})
})

View File

@ -44,11 +44,11 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
props.iconStart
? props.iconEnd
? 'px-0'
: 'pr-2'
? 'px-0' // No padding if both icons are present
: 'pr-2' // Padding on the right if only the start icon is present
: props.iconEnd
? 'px-2'
: 'pl-2'
? 'pl-2' // Padding on the left if only the end icon is present
: 'px-2' // Padding on both sides if no icons are present
} ${props.className ? props.className : ''}`
switch (props.Element) {

View File

@ -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>
)
}

View File

@ -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'
@ -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 }
)
})

View File

@ -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) => {
@ -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()
})
})

View File

@ -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'
@ -123,4 +126,6 @@ contextBridge.exposeInMainWorld('electron', {
kittycad,
listMachines,
getMachineApiIp,
onUpdateDownloaded,
appRestart,
})