feat: app updater with changelog (#4631)
* feat: ui modal app updater with changelog * chore: update action when click update now * chore: update handler actions * chore: fix linter
This commit is contained in:
parent
476c7f723f
commit
b0c6779015
@ -31,6 +31,8 @@ export enum NativeRoute {
|
||||
|
||||
startServer = 'startServer',
|
||||
stopServer = 'stopServer',
|
||||
|
||||
appUpdateDownload = 'appUpdateDownload',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +52,8 @@ export enum AppRoute {
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
onAppUpdateNotAvailable = 'onAppUpdateNotAvailable',
|
||||
onAppUpdateAvailable = 'onAppUpdateAvailable',
|
||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { app, ipcMain, dialog, shell, nativeTheme } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { join } from 'path'
|
||||
import { windowManager } from '../managers/window'
|
||||
import {
|
||||
@ -28,6 +29,10 @@ export function handleAppIPCs() {
|
||||
shell.openPath(getJanDataFolderPath())
|
||||
})
|
||||
|
||||
ipcMain.handle(NativeRoute.appUpdateDownload, async (_event) => {
|
||||
autoUpdater.downloadUpdate()
|
||||
})
|
||||
|
||||
/**
|
||||
* Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light".
|
||||
* This will change the appearance of the app to the light theme.
|
||||
|
||||
@ -16,15 +16,21 @@ export function handleAppUpdates() {
|
||||
if (!app.isPackaged) {
|
||||
return
|
||||
}
|
||||
|
||||
/* New Update Available */
|
||||
autoUpdater.on('update-available', async (_info: UpdateInfo) => {
|
||||
const action = await dialog.showMessageBox({
|
||||
title: 'Update Available',
|
||||
message: 'Would you like to download and install it now?',
|
||||
buttons: ['Download', 'Later'],
|
||||
})
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onAppUpdateAvailable,
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
if (action.response === 0) await autoUpdater.downloadUpdate()
|
||||
/* New Update Not Available */
|
||||
autoUpdater.on('update-not-available', async (_info: UpdateInfo) => {
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onAppUpdateNotAvailable,
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
/* App Update Completion Message */
|
||||
|
||||
@ -509,61 +509,61 @@ __metadata:
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5eb526&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=720675&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
||||
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@ -29,6 +29,10 @@ import LoadingModal from '../LoadingModal'
|
||||
|
||||
import MainViewContainer from '../MainViewContainer'
|
||||
|
||||
import ModalAppUpdaterChangelog from '../ModalAppUpdaterChangelog'
|
||||
|
||||
import ModalAppUpdaterNotAvailable from '../ModalAppUpdaterNotAvailable'
|
||||
|
||||
import InstallingExtensionModal from './BottomPanel/InstallingExtension/InstallingExtensionModal'
|
||||
|
||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||
@ -222,6 +226,8 @@ const BaseLayout = () => {
|
||||
)}
|
||||
</div>
|
||||
<BottomPanel />
|
||||
<ModalAppUpdaterChangelog />
|
||||
<ModalAppUpdaterNotAvailable />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
91
web/containers/ModalAppUpdaterChangelog/index.tsx
Normal file
91
web/containers/ModalAppUpdaterChangelog/index.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { Button, Modal } from '@janhq/joi'
|
||||
|
||||
import { useAtom } from 'jotai'
|
||||
|
||||
import { useGetLatestRelease } from '@/hooks/useGetLatestRelease'
|
||||
|
||||
import { MarkdownTextMessage } from '@/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage'
|
||||
|
||||
import LogoMark from '../Brand/Logo/Mark'
|
||||
|
||||
import { appUpdateAvailableAtom } from '@/helpers/atoms/App.atom'
|
||||
|
||||
const ModalAppUpdaterChangelog = () => {
|
||||
const [appUpdateAvailable, setAppUpdateAvailable] = useAtom(
|
||||
appUpdateAvailableAtom
|
||||
)
|
||||
|
||||
const [open, setOpen] = useState(appUpdateAvailable)
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(appUpdateAvailable)
|
||||
}, [appUpdateAvailable])
|
||||
|
||||
const beta = VERSION.includes('beta')
|
||||
const nightly = VERSION.includes('-')
|
||||
|
||||
const { release } = useGetLatestRelease(beta ? true : false)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
hideClose={true}
|
||||
title={
|
||||
<>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<LogoMark width={40} height={40} />
|
||||
<h6>App Update</h6>
|
||||
</div>
|
||||
{!nightly && (
|
||||
<p className="mt-2 text-sm font-normal">
|
||||
Version <b>{release?.name}</b> is available and ready to install.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
open={open}
|
||||
onOpenChange={() => setOpen(!open)}
|
||||
content={
|
||||
<div className="mt-3">
|
||||
{nightly ? (
|
||||
<p className="mt-2 text-sm font-normal">
|
||||
You are using a nightly build. This version is built from the
|
||||
latest development branch and may not have release notes.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="markdown-content max-h-[400px] overflow-y-auto rounded-lg border border-[hsla(var(--app-border))] px-2 pb-4 pt-0">
|
||||
<MarkdownTextMessage text={release?.body} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-4 flex items-center justify-end gap-x-2">
|
||||
<Button
|
||||
theme="ghost"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setAppUpdateAvailable(false)
|
||||
}}
|
||||
>
|
||||
Later
|
||||
</Button>
|
||||
<Button
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
window.core?.api?.appUpdateDownload()
|
||||
setOpen(false)
|
||||
setAppUpdateAvailable(false)
|
||||
}}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalAppUpdaterChangelog
|
||||
57
web/containers/ModalAppUpdaterNotAvailable/index.tsx
Normal file
57
web/containers/ModalAppUpdaterNotAvailable/index.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { Button, Modal } from '@janhq/joi'
|
||||
|
||||
import { useAtom } from 'jotai'
|
||||
|
||||
import LogoMark from '../Brand/Logo/Mark'
|
||||
|
||||
import { appUpdateNotAvailableAtom } from '@/helpers/atoms/App.atom'
|
||||
|
||||
const ModalAppUpdaterNotAvailable = () => {
|
||||
const [appUpdateNotAvailable, setAppUpdateNotAvailable] = useAtom(
|
||||
appUpdateNotAvailableAtom
|
||||
)
|
||||
|
||||
const [open, setOpen] = useState(appUpdateNotAvailable)
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(appUpdateNotAvailable)
|
||||
}, [appUpdateNotAvailable])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
hideClose={true}
|
||||
title={
|
||||
<>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<LogoMark width={40} height={40} />
|
||||
<h6>App Update</h6>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
open={open}
|
||||
onOpenChange={() => setOpen(!open)}
|
||||
content={
|
||||
<div className="mt-3">
|
||||
<p className="mt-2 text-sm font-normal">
|
||||
You’re up to date! No new updates available
|
||||
</p>
|
||||
<div className="mt-4 flex items-center justify-end gap-x-2">
|
||||
<Button
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setAppUpdateNotAvailable(false)
|
||||
}}
|
||||
>
|
||||
Check back later
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalAppUpdaterNotAvailable
|
||||
@ -5,12 +5,16 @@ import { useSetAtom } from 'jotai'
|
||||
|
||||
import {
|
||||
appDownloadProgressAtom,
|
||||
appUpdateAvailableAtom,
|
||||
updateVersionErrorAtom,
|
||||
appUpdateNotAvailableAtom,
|
||||
} from '@/helpers/atoms/App.atom'
|
||||
|
||||
const AppUpdateListener = () => {
|
||||
const setProgress = useSetAtom(appDownloadProgressAtom)
|
||||
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
||||
const setAppUpdateAvailable = useSetAtom(appUpdateAvailableAtom)
|
||||
const setAppUpdateNotvailable = useSetAtom(appUpdateNotAvailableAtom)
|
||||
|
||||
useEffect(() => {
|
||||
if (window && window.electronAPI) {
|
||||
@ -36,8 +40,17 @@ const AppUpdateListener = () => {
|
||||
window.electronAPI.onAppUpdateDownloadSuccess(() => {
|
||||
setProgress(-1)
|
||||
})
|
||||
|
||||
window.electronAPI.onAppUpdateAvailable(() => {
|
||||
setAppUpdateAvailable(true)
|
||||
})
|
||||
|
||||
window.electronAPI.onAppUpdateNotAvailable(() => {
|
||||
setAppUpdateAvailable(false)
|
||||
setAppUpdateNotvailable(true)
|
||||
})
|
||||
}
|
||||
}, [setProgress, setUpdateVersionError])
|
||||
}, [setProgress, setUpdateVersionError, setAppUpdateAvailable])
|
||||
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
@ -23,6 +23,8 @@ export const showRightPanelAtom = atomWithStorage<boolean>(
|
||||
export const showSystemMonitorPanelAtom = atom<boolean>(false)
|
||||
export const appDownloadProgressAtom = atom<number>(-1)
|
||||
export const updateVersionErrorAtom = atom<string | undefined>(undefined)
|
||||
export const appUpdateAvailableAtom = atom<boolean>(false)
|
||||
export const appUpdateNotAvailableAtom = atom<boolean>(false)
|
||||
|
||||
const COPY_OVER_INSTRUCTION_ENABLED = 'copy_over_instruction_enabled'
|
||||
|
||||
|
||||
33
web/hooks/useGetLatestRelease.ts
Normal file
33
web/hooks/useGetLatestRelease.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import useSWR from 'swr'
|
||||
|
||||
const fetchLatestRelease = async (includeBeta: boolean) => {
|
||||
const res = await fetch('https://api.github.com/repos/janhq/jan/releases')
|
||||
if (!res.ok) throw new Error('Failed to fetch releases')
|
||||
|
||||
const releases = await res.json()
|
||||
|
||||
// Filter stable and beta releases
|
||||
const stableRelease = releases.find(
|
||||
(release: { prerelease: any; draft: any }) =>
|
||||
!release.prerelease && !release.draft
|
||||
)
|
||||
const betaRelease = releases.find(
|
||||
(release: { prerelease: any }) => release.prerelease
|
||||
)
|
||||
|
||||
return includeBeta ? (betaRelease ?? stableRelease) : stableRelease
|
||||
}
|
||||
|
||||
export function useGetLatestRelease(includeBeta = false) {
|
||||
const { data, error, mutate } = useSWR(
|
||||
['latestRelease', includeBeta],
|
||||
() => fetchLatestRelease(includeBeta),
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
}
|
||||
)
|
||||
|
||||
return { release: data, error, mutate }
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
.markdown-content h6 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: medium;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: hsla(var(--text-primary));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user