Custom updater modal (#1738)

* WIP: Custom updater modal
Fixes #1663

* First working example with data

* Clean up, moved code to index.tsx

* Clean up

* Nicer dialog

* Add relaunch dialog (macOS)

* max-height in case of a long text

* Clean up

* Add component tests and fix name consistency

* Update styling, re-add md parser

* Clean up

* Quick typo

* Clean up

* Rebase on tauri v2

* Clean up

* Add updater permissions

* Remove dialog from config

* Fix restart after install
This commit is contained in:
Pierre Jacquier
2024-04-17 10:30:23 -04:00
committed by GitHub
parent 624b1fc07d
commit b13c1339aa
13 changed files with 297 additions and 10 deletions

View File

@ -0,0 +1,42 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { vi } from 'vitest'
import { UpdaterModal } from './UpdaterModal'
describe('UpdaterModal tests', () => {
test('Renders the modal', () => {
const callback = vi.fn()
const data = {
version: '1.2.3',
date: '2021-22-23T21:22:23Z',
body: 'This is the body.',
}
render(
<UpdaterModal
isOpen={true}
onReject={() => {}}
onResolve={callback}
instanceId=""
open={false}
close={(res) => {}}
version={data.version}
date={data.date}
body={data.body}
/>
)
expect(screen.getByTestId('update-version')).toHaveTextContent(data.version)
const updateButton = screen.getByTestId('update-button-update')
expect(updateButton).toBeEnabled()
fireEvent.click(updateButton)
expect(callback.mock.calls).toHaveLength(1)
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: true })
const cancelButton = screen.getByTestId('update-button-cancel')
expect(cancelButton).toBeEnabled()
fireEvent.click(cancelButton)
expect(callback.mock.calls).toHaveLength(2)
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: false })
})
})

View File

@ -0,0 +1,84 @@
import { create, InstanceProps } from 'react-modal-promise'
import { ActionButton } from './ActionButton'
import { Logo } from './Logo'
import { Marked } from '@ts-stack/markdown'
type ModalResolve = {
wantUpdate: boolean
}
type ModalReject = boolean
type UpdaterModalProps = InstanceProps<ModalResolve, ModalReject> & {
version: string
date?: string
body?: string
}
export const createUpdaterModal = create<
UpdaterModalProps,
ModalResolve,
ModalReject
>
export const UpdaterModal = ({
onResolve,
version,
date,
body,
}: UpdaterModalProps) => (
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl min-w-[45rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<div className="flex items-center">
<h1 className="flex-grow text-3xl font-bold">New version available!</h1>
<Logo className="h-9" />
</div>
<div className="my-4 flex items-baseline">
<span
className="px-3 py-1 text-xl rounded-full bg-energy-10 text-energy-80"
data-testid="update-version"
>
v{version}
</span>
<span className="ml-4 text-sm text-gray-400">Published on {date}</span>
</div>
{/* TODO: fix list bullets */}
{body && (
<div
className="my-4 max-h-60 overflow-y-auto"
dangerouslySetInnerHTML={{
__html: Marked.parse(body, {
gfm: true,
breaks: true,
sanitize: true,
}),
}}
></div>
)}
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => onResolve({ wantUpdate: false })}
icon={{
icon: 'close',
bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
}}
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
data-testid="update-button-cancel"
>
Not now
</ActionButton>
<ActionButton
Element="button"
onClick={() => onResolve({ wantUpdate: true })}
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
className="dark:hover:bg-chalkboard-80/50"
data-testid="update-button-update"
>
Update
</ActionButton>
</div>
</div>
</div>
)

View File

@ -0,0 +1,40 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { vi } from 'vitest'
import { UpdaterRestartModal } from './UpdaterRestartModal'
describe('UpdaterRestartModal tests', () => {
test('Renders the modal', () => {
const callback = vi.fn()
const data = {
version: '1.2.3',
}
render(
<UpdaterRestartModal
isOpen={true}
onReject={() => {}}
onResolve={callback}
instanceId=""
open={false}
close={(res) => {}}
version={data.version}
/>
)
expect(screen.getByTestId('update-restart-version')).toHaveTextContent(
data.version
)
const updateButton = screen.getByTestId('update-restrart-button-update')
expect(updateButton).toBeEnabled()
fireEvent.click(updateButton)
expect(callback.mock.calls).toHaveLength(1)
expect(callback.mock.lastCall[0]).toEqual({ wantRestart: true })
const cancelButton = screen.getByTestId('update-restrart-button-cancel')
expect(cancelButton).toBeEnabled()
fireEvent.click(cancelButton)
expect(callback.mock.calls).toHaveLength(2)
expect(callback.mock.lastCall[0]).toEqual({ wantRestart: false })
})
})

View File

@ -0,0 +1,56 @@
import { create, InstanceProps } from 'react-modal-promise'
import { ActionButton } from './ActionButton'
type ModalResolve = {
wantRestart: boolean
}
type ModalReject = boolean
type UpdaterRestartModalProps = InstanceProps<ModalResolve, ModalReject> & {
version: string
}
export const createUpdaterRestartModal = create<
UpdaterRestartModalProps,
ModalResolve,
ModalReject
>
export const UpdaterRestartModal = ({
onResolve,
version,
}: UpdaterRestartModalProps) => (
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<h1 className="text-3xl font-bold">Ready to restart?</h1>
<p className="my-4" data-testid="update-restart-version">
v{version} is now installed. Restart the app to use the new features.
</p>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => onResolve({ wantRestart: false })}
icon={{
icon: 'close',
bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
}}
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
data-testid="update-restrart-button-cancel"
>
Not now
</ActionButton>
<ActionButton
Element="button"
onClick={() => onResolve({ wantRestart: true })}
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
className="dark:hover:bg-chalkboard-80/50"
data-testid="update-restrart-button-update"
>
Restart
</ActionButton>
</div>
</div>
</div>
)