Merge branch 'main' into paultag/tabled
This commit is contained in:
4
interface.d.ts
vendored
4
interface.d.ts
vendored
@ -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 {
|
||||
|
||||
42
src/components/ActionButton.test.tsx
Normal file
42
src/components/ActionButton.test.tsx
Normal 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')
|
||||
})
|
||||
})
|
||||
@ -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) {
|
||||
|
||||
64
src/components/ToastUpdate.tsx
Normal file
64
src/components/ToastUpdate.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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 }
|
||||
)
|
||||
})
|
||||
|
||||
17
src/main.ts
17
src/main.ts
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user