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',
|
startServer = 'startServer',
|
||||||
stopServer = 'stopServer',
|
stopServer = 'stopServer',
|
||||||
|
|
||||||
|
appUpdateDownload = 'appUpdateDownload',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,6 +52,8 @@ export enum AppRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AppEvent {
|
export enum AppEvent {
|
||||||
|
onAppUpdateNotAvailable = 'onAppUpdateNotAvailable',
|
||||||
|
onAppUpdateAvailable = 'onAppUpdateAvailable',
|
||||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { app, ipcMain, dialog, shell, nativeTheme } from 'electron'
|
import { app, ipcMain, dialog, shell, nativeTheme } from 'electron'
|
||||||
|
import { autoUpdater } from 'electron-updater'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { windowManager } from '../managers/window'
|
import { windowManager } from '../managers/window'
|
||||||
import {
|
import {
|
||||||
@ -28,6 +29,10 @@ export function handleAppIPCs() {
|
|||||||
shell.openPath(getJanDataFolderPath())
|
shell.openPath(getJanDataFolderPath())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.appUpdateDownload, async (_event) => {
|
||||||
|
autoUpdater.downloadUpdate()
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light".
|
* 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.
|
* This will change the appearance of the app to the light theme.
|
||||||
|
|||||||
@ -16,15 +16,21 @@ export function handleAppUpdates() {
|
|||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* New Update Available */
|
/* New Update Available */
|
||||||
autoUpdater.on('update-available', async (_info: UpdateInfo) => {
|
autoUpdater.on('update-available', async (_info: UpdateInfo) => {
|
||||||
const action = await dialog.showMessageBox({
|
windowManager.mainWindow?.webContents.send(
|
||||||
title: 'Update Available',
|
AppEvent.onAppUpdateAvailable,
|
||||||
message: 'Would you like to download and install it now?',
|
{}
|
||||||
buttons: ['Download', 'Later'],
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
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 */
|
/* App Update Completion Message */
|
||||||
|
|||||||
@ -509,61 +509,61 @@ __metadata:
|
|||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension":
|
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension":
|
||||||
version: 0.1.10
|
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:
|
dependencies:
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ulidx: "npm:^2.3.0"
|
ulidx: "npm:^2.3.0"
|
||||||
checksum: 10c0/e53df943c345a1496d45d86e65bf40cf0fe0dd716ac1c1753453bad6877f36035a4fb305cb5e1690c18d426609ba125d1370304c7399fd4abac760e09fef2c52
|
checksum: 10c0/6d870700c86244fafdb7b799232655fa2708b84103441e994a31ca3a0892866193e90771f09b41436400e251eca5891fd3278b13543fa0b90f3f1480199e0931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,10 @@ import LoadingModal from '../LoadingModal'
|
|||||||
|
|
||||||
import MainViewContainer from '../MainViewContainer'
|
import MainViewContainer from '../MainViewContainer'
|
||||||
|
|
||||||
|
import ModalAppUpdaterChangelog from '../ModalAppUpdaterChangelog'
|
||||||
|
|
||||||
|
import ModalAppUpdaterNotAvailable from '../ModalAppUpdaterNotAvailable'
|
||||||
|
|
||||||
import InstallingExtensionModal from './BottomPanel/InstallingExtension/InstallingExtensionModal'
|
import InstallingExtensionModal from './BottomPanel/InstallingExtension/InstallingExtensionModal'
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
@ -222,6 +226,8 @@ const BaseLayout = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<BottomPanel />
|
<BottomPanel />
|
||||||
|
<ModalAppUpdaterChangelog />
|
||||||
|
<ModalAppUpdaterNotAvailable />
|
||||||
</div>
|
</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 {
|
import {
|
||||||
appDownloadProgressAtom,
|
appDownloadProgressAtom,
|
||||||
|
appUpdateAvailableAtom,
|
||||||
updateVersionErrorAtom,
|
updateVersionErrorAtom,
|
||||||
|
appUpdateNotAvailableAtom,
|
||||||
} from '@/helpers/atoms/App.atom'
|
} from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
const AppUpdateListener = () => {
|
const AppUpdateListener = () => {
|
||||||
const setProgress = useSetAtom(appDownloadProgressAtom)
|
const setProgress = useSetAtom(appDownloadProgressAtom)
|
||||||
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
||||||
|
const setAppUpdateAvailable = useSetAtom(appUpdateAvailableAtom)
|
||||||
|
const setAppUpdateNotvailable = useSetAtom(appUpdateNotAvailableAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window && window.electronAPI) {
|
if (window && window.electronAPI) {
|
||||||
@ -36,8 +40,17 @@ const AppUpdateListener = () => {
|
|||||||
window.electronAPI.onAppUpdateDownloadSuccess(() => {
|
window.electronAPI.onAppUpdateDownloadSuccess(() => {
|
||||||
setProgress(-1)
|
setProgress(-1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.electronAPI.onAppUpdateAvailable(() => {
|
||||||
|
setAppUpdateAvailable(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
window.electronAPI.onAppUpdateNotAvailable(() => {
|
||||||
|
setAppUpdateAvailable(false)
|
||||||
|
setAppUpdateNotvailable(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, [setProgress, setUpdateVersionError])
|
}, [setProgress, setUpdateVersionError, setAppUpdateAvailable])
|
||||||
|
|
||||||
return <Fragment></Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ export const showRightPanelAtom = atomWithStorage<boolean>(
|
|||||||
export const showSystemMonitorPanelAtom = atom<boolean>(false)
|
export const showSystemMonitorPanelAtom = atom<boolean>(false)
|
||||||
export const appDownloadProgressAtom = atom<number>(-1)
|
export const appDownloadProgressAtom = atom<number>(-1)
|
||||||
export const updateVersionErrorAtom = atom<string | undefined>(undefined)
|
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'
|
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 {
|
.markdown-content h6 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-weight: medium;
|
font-weight: 500;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
color: hsla(var(--text-primary));
|
color: hsla(var(--text-primary));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user