From 688a013d73040305cdbcb699bbaa1e6e256c0ed2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 18 Feb 2025 21:21:29 +0800 Subject: [PATCH] feat(ui): version checker --- ui/src/components/Version.tsx | 18 +++++--- ui/src/hooks/index.ts | 3 +- ui/src/hooks/useVersionChecker.ts | 70 +++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 ui/src/hooks/useVersionChecker.ts diff --git a/ui/src/components/Version.tsx b/ui/src/components/Version.tsx index 4832cce8..fcdf1170 100644 --- a/ui/src/components/Version.tsx +++ b/ui/src/components/Version.tsx @@ -1,8 +1,10 @@ +import { memo, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { ReadOutlined as ReadOutlinedIcon } from "@ant-design/icons"; -import { Divider, Space, Typography } from "antd"; +import { Badge, Divider, Space, Typography } from "antd"; import { version } from "@/domain/version"; +import { useVersionChecker } from "@/hooks"; export type VersionProps = { className?: string; @@ -12,6 +14,8 @@ export type VersionProps = { const Version = ({ className, style }: VersionProps) => { const { t } = useTranslation(); + const { data: hasNewVersions } = useVersionChecker(); + return ( @@ -20,12 +24,16 @@ const Version = ({ className, style }: VersionProps) => { {t("common.menu.document")} + - - {version} - + + + + {version} + + ); }; -export default Version; +export default memo(Version); diff --git a/ui/src/hooks/index.ts b/ui/src/hooks/index.ts index 045c0aad..6f154701 100644 --- a/ui/src/hooks/index.ts +++ b/ui/src/hooks/index.ts @@ -2,6 +2,7 @@ import useAntdFormName from "./useAntdFormName"; import useBrowserTheme from "./useBrowserTheme"; import useTriggerElement from "./useTriggerElement"; +import useVersionChecker from "./useVersionChecker"; import useZustandShallowSelector from "./useZustandShallowSelector"; -export { useAntdForm, useAntdFormName, useBrowserTheme, useTriggerElement, useZustandShallowSelector }; +export { useAntdForm, useAntdFormName, useBrowserTheme, useTriggerElement, useVersionChecker, useZustandShallowSelector }; diff --git a/ui/src/hooks/useVersionChecker.ts b/ui/src/hooks/useVersionChecker.ts new file mode 100644 index 00000000..e77b69f9 --- /dev/null +++ b/ui/src/hooks/useVersionChecker.ts @@ -0,0 +1,70 @@ +import { useRequest } from "ahooks"; + +import { version } from "@/domain/version"; + +export type UseVersionCheckerReturns = { + data: boolean; + check: () => void; +}; + +const extractSemver = (vers: string) => { + let semver = String(vers ?? ""); + semver = semver.replace(/^v/i, ""); + semver = semver.split("-")[0]; + return semver; +}; + +const compareVersions = (a: string, b: string) => { + const aSemver = extractSemver(a); + const bSemver = extractSemver(b); + const aSemverParts = aSemver.split("."); + const bSemverParts = bSemver.split("."); + + const len = Math.max(aSemverParts.length, bSemverParts.length); + for (let i = 0; i < len; i++) { + const aPart = parseInt(aSemverParts[i] ?? "0"); + const bPart = parseInt(bSemverParts[i] ?? "0"); + if (aPart > bPart) return 1; + if (bPart > aPart) return -1; + } + + return 0; +}; + +/** + * 获取版本检查器。 + * @returns {UseVersionCheckerReturns} + */ +const useVersionChecker = () => { + const { data, refresh } = useRequest( + async () => { + const releases = await fetch("https://api.github.com/repos/usual2970/certimate/releases") + .then((res) => res.json()) + .then((res) => Array.from(res)); + + const cIdx = releases.findIndex((e: any) => e.name === version); + if (cIdx === 0) { + return false; + } + + const nIdx = releases.findIndex((e: any) => compareVersions(e.name, version) !== -1); + if (cIdx >= nIdx) { + return false; + } + + return !!releases[nIdx]; + }, + { + pollingInterval: 6 * 60 * 60 * 1000, + refreshOnWindowFocus: true, + focusTimespan: 15 * 60 * 1000, + } + ); + + return { + data: !!data, + check: refresh, + }; +}; + +export default useVersionChecker;