feat: add check for updates button, close #766

This commit is contained in:
GyDi 2023-09-10 14:30:31 +08:00
parent 16d80718cb
commit f4c7b17a87
No known key found for this signature in database
GPG Key ID: 9C3AD40F1F99880A
10 changed files with 93 additions and 84 deletions

View File

@ -6,7 +6,7 @@ import {
HorizontalRuleRounded, HorizontalRuleRounded,
} from "@mui/icons-material"; } from "@mui/icons-material";
const LayoutControl = () => { export const LayoutControl = () => {
const minWidth = 40; const minWidth = 40;
return ( return (
@ -37,5 +37,3 @@ const LayoutControl = () => {
</> </>
); );
}; };
export default LayoutControl;

View File

@ -2,7 +2,7 @@ import { alpha, ListItem, ListItemButton, ListItemText } from "@mui/material";
import { useMatch, useResolvedPath, useNavigate } from "react-router-dom"; import { useMatch, useResolvedPath, useNavigate } from "react-router-dom";
import type { LinkProps } from "react-router-dom"; import type { LinkProps } from "react-router-dom";
const LayoutItem = (props: LinkProps) => { export const LayoutItem = (props: LinkProps) => {
const { to, children } = props; const { to, children } = props;
const resolved = useResolvedPath(to); const resolved = useResolvedPath(to);
@ -40,5 +40,3 @@ const LayoutItem = (props: LinkProps) => {
</ListItem> </ListItem>
); );
}; };
export default LayoutItem;

View File

@ -14,7 +14,7 @@ import { useWebsocket } from "@/hooks/use-websocket";
import parseTraffic from "@/utils/parse-traffic"; import parseTraffic from "@/utils/parse-traffic";
// setup the traffic // setup the traffic
const LayoutTraffic = () => { export const LayoutTraffic = () => {
const { clashInfo } = useClashInfo(); const { clashInfo } = useClashInfo();
const { verge } = useVerge(); const { verge } = useVerge();
@ -134,5 +134,3 @@ const LayoutTraffic = () => {
</Box> </Box>
); );
}; };
export default LayoutTraffic;

View File

@ -1,17 +1,19 @@
import useSWR from "swr"; import useSWR from "swr";
import { useState } from "react"; import { useRef } from "react";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import { checkUpdate } from "@tauri-apps/api/updater"; import { checkUpdate } from "@tauri-apps/api/updater";
import UpdateDialog from "./update-dialog"; import { UpdateViewer } from "../setting/mods/update-viewer";
import { DialogRef } from "../base";
interface Props { interface Props {
className?: string; className?: string;
} }
const UpdateButton = (props: Props) => { export const UpdateButton = (props: Props) => {
const { className } = props; const { className } = props;
const [dialogOpen, setDialogOpen] = useState(false); const viewerRef = useRef<DialogRef>(null);
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, { const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
errorRetryCount: 2, errorRetryCount: 2,
revalidateIfStale: false, revalidateIfStale: false,
@ -22,21 +24,17 @@ const UpdateButton = (props: Props) => {
return ( return (
<> <>
<UpdateViewer ref={viewerRef} />
<Button <Button
color="error" color="error"
variant="contained" variant="contained"
size="small" size="small"
className={className} className={className}
onClick={() => setDialogOpen(true)} onClick={() => viewerRef.current?.open()}
> >
New New
</Button> </Button>
{dialogOpen && (
<UpdateDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
)}
</> </>
); );
}; };
export default UpdateButton;

View File

@ -9,7 +9,7 @@ import { useVerge } from "@/hooks/use-verge";
/** /**
* custom theme * custom theme
*/ */
export default function useCustomTheme() { export const useCustomTheme = () => {
const { verge } = useVerge(); const { verge } = useVerge();
const { theme_mode, theme_setting } = verge ?? {}; const { theme_mode, theme_setting } = verge ?? {};
const [mode, setMode] = useRecoilState(atomThemeMode); const [mode, setMode] = useRecoilState(atomThemeMode);
@ -121,4 +121,4 @@ export default function useCustomTheme() {
}, [mode, theme_setting]); }, [mode, theme_setting]);
return { theme }; return { theme };
} };

View File

@ -1,43 +1,45 @@
import useSWR from "swr"; import useSWR from "swr";
import snarkdown from "snarkdown"; import snarkdown from "snarkdown";
import { useMemo } from "react"; import { forwardRef, useImperativeHandle, useState, useMemo } from "react";
import { useLockFn } from "ahooks";
import { Box, styled } from "@mui/material";
import { useRecoilState } from "recoil"; import { useRecoilState } from "recoil";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
styled,
} from "@mui/material";
import { relaunch } from "@tauri-apps/api/process"; import { relaunch } from "@tauri-apps/api/process";
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater"; import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { atomUpdateState } from "@/services/states"; import { atomUpdateState } from "@/services/states";
import { Notice } from "@/components/base";
interface Props {
open: boolean;
onClose: () => void;
}
const UpdateLog = styled(Box)(() => ({ const UpdateLog = styled(Box)(() => ({
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" }, "h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
})); }));
const UpdateDialog = (props: Props) => { export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
const { open, onClose } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [updateState, setUpdateState] = useRecoilState(atomUpdateState);
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, { const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
errorRetryCount: 2, errorRetryCount: 2,
revalidateIfStale: false, revalidateIfStale: false,
focusThrottleInterval: 36e5, // 1 hour focusThrottleInterval: 36e5, // 1 hour
}); });
const [updateState, setUpdateState] = useRecoilState(atomUpdateState); useImperativeHandle(ref, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}));
const onUpdate = async () => { // markdown parser
const parseContent = useMemo(() => {
if (!updateInfo?.manifest?.body) {
return "New Version is available";
}
return snarkdown(updateInfo?.manifest?.body);
}, [updateInfo]);
const onUpdate = useLockFn(async () => {
if (updateState) return; if (updateState) return;
setUpdateState(true); setUpdateState(true);
@ -49,39 +51,20 @@ const UpdateDialog = (props: Props) => {
} finally { } finally {
setUpdateState(false); setUpdateState(false);
} }
}; });
// markdown parser
const parseContent = useMemo(() => {
if (!updateInfo?.manifest?.body) {
return "New Version is available";
}
return snarkdown(updateInfo?.manifest?.body);
}, [updateInfo]);
return ( return (
<Dialog open={open} onClose={onClose}> <BaseDialog
<DialogTitle>New Version v{updateInfo?.manifest?.version}</DialogTitle> open={open}
title={`New Version v${updateInfo?.manifest?.version}`}
<DialogContent sx={{ minWidth: 360, maxWidth: 400, maxHeight: "50vh" }}> contentSx={{ minWidth: 360, maxWidth: 400, maxHeight: "50vh" }}
<UpdateLog dangerouslySetInnerHTML={{ __html: parseContent }} /> okBtn={t("Update")}
</DialogContent> cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
<DialogActions> onCancel={() => setOpen(false)}
<Button variant="outlined" onClick={onClose}> onOk={onUpdate}
{t("Cancel")}
</Button>
<Button
autoFocus
variant="contained"
disabled={updateState}
onClick={onUpdate}
> >
{t("Update")} <UpdateLog dangerouslySetInnerHTML={{ __html: parseContent }} />
</Button> </BaseDialog>
</DialogActions>
</Dialog>
); );
}; });
export default UpdateDialog;

View File

@ -1,11 +1,13 @@
import { useRef } from "react"; import { useRef } from "react";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { IconButton, MenuItem, Select, Typography } from "@mui/material"; import { IconButton, MenuItem, Select, Typography } from "@mui/material";
import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds"; import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
import { ArrowForward } from "@mui/icons-material"; import { ArrowForward } from "@mui/icons-material";
import { checkUpdate } from "@tauri-apps/api/updater";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { version } from "@root/package.json"; import { version } from "@root/package.json";
import { DialogRef } from "@/components/base"; import { DialogRef, Notice } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp"; import { SettingList, SettingItem } from "./mods/setting-comp";
import { ThemeModeSwitch } from "./mods/theme-mode-switch"; import { ThemeModeSwitch } from "./mods/theme-mode-switch";
import { ConfigViewer } from "./mods/config-viewer"; import { ConfigViewer } from "./mods/config-viewer";
@ -14,29 +16,45 @@ import { MiscViewer } from "./mods/misc-viewer";
import { ThemeViewer } from "./mods/theme-viewer"; import { ThemeViewer } from "./mods/theme-viewer";
import { GuardState } from "./mods/guard-state"; import { GuardState } from "./mods/guard-state";
import { LayoutViewer } from "./mods/layout-viewer"; import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import getSystem from "@/utils/get-system";
interface Props { interface Props {
onError?: (err: Error) => void; onError?: (err: Error) => void;
} }
const OS = getSystem();
const SettingVerge = ({ onError }: Props) => { const SettingVerge = ({ onError }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge(); const { verge, patchVerge, mutateVerge } = useVerge();
const { theme_mode, language } = verge ?? {};
const { theme_mode, theme_blur, traffic_graph, language } = verge ?? {};
const configRef = useRef<DialogRef>(null); const configRef = useRef<DialogRef>(null);
const hotkeyRef = useRef<DialogRef>(null); const hotkeyRef = useRef<DialogRef>(null);
const miscRef = useRef<DialogRef>(null); const miscRef = useRef<DialogRef>(null);
const themeRef = useRef<DialogRef>(null); const themeRef = useRef<DialogRef>(null);
const layoutRef = useRef<DialogRef>(null); const layoutRef = useRef<DialogRef>(null);
const updateRef = useRef<DialogRef>(null);
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IVergeConfig>) => { const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false); mutateVerge({ ...verge, ...patch }, false);
}; };
const onCheckUpdate = useLockFn(async () => {
try {
const info = await checkUpdate();
if (!info?.shouldUpdate) {
Notice.success("No Updates Available");
} else {
updateRef.current?.open();
}
} catch (err: any) {
Notice.error(err.message || err.toString());
}
});
return ( return (
<SettingList title={t("Verge Setting")}> <SettingList title={t("Verge Setting")}>
<ThemeViewer ref={themeRef} /> <ThemeViewer ref={themeRef} />
@ -44,6 +62,7 @@ const SettingVerge = ({ onError }: Props) => {
<HotkeyViewer ref={hotkeyRef} /> <HotkeyViewer ref={hotkeyRef} />
<MiscViewer ref={miscRef} /> <MiscViewer ref={miscRef} />
<LayoutViewer ref={layoutRef} /> <LayoutViewer ref={layoutRef} />
<UpdateViewer ref={updateRef} />
<SettingItem label={t("Language")}> <SettingItem label={t("Language")}>
<GuardState <GuardState
@ -160,6 +179,19 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton> </IconButton>
</SettingItem> </SettingItem>
{!(OS === "windows" && WIN_PORTABLE) && (
<SettingItem label={t("Check for Updates")}>
<IconButton
color="inherit"
size="small"
sx={{ my: "2px" }}
onClick={onCheckUpdate}
>
<ArrowForward />
</IconButton>
</SettingItem>
)}
<SettingItem label={t("Verge Version")}> <SettingItem label={t("Verge Version")}>
<Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography> <Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography>
</SettingItem> </SettingItem>

View File

@ -90,6 +90,7 @@
"Open App Dir": "Open App Dir", "Open App Dir": "Open App Dir",
"Open Core Dir": "Open Core Dir", "Open Core Dir": "Open Core Dir",
"Open Logs Dir": "Open Logs Dir", "Open Logs Dir": "Open Logs Dir",
"Check for Updates": "Check for Updates",
"Verge Version": "Verge Version", "Verge Version": "Verge Version",
"theme.light": "Light", "theme.light": "Light",
"theme.dark": "Dark", "theme.dark": "Dark",

View File

@ -90,6 +90,7 @@
"Open App Dir": "应用目录", "Open App Dir": "应用目录",
"Open Core Dir": "内核目录", "Open Core Dir": "内核目录",
"Open Logs Dir": "日志目录", "Open Logs Dir": "日志目录",
"Check for Updates": "检查更新",
"Verge Version": "应用版本", "Verge Version": "应用版本",
"theme.light": "浅色", "theme.light": "浅色",
"theme.dark": "深色", "theme.dark": "深色",

View File

@ -13,11 +13,11 @@ import { getAxios } from "@/services/api";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { ReactComponent as LogoSvg } from "@/assets/image/logo.svg"; import { ReactComponent as LogoSvg } from "@/assets/image/logo.svg";
import { BaseErrorBoundary, Notice } from "@/components/base"; import { BaseErrorBoundary, Notice } from "@/components/base";
import LayoutItem from "@/components/layout/layout-item"; import { LayoutItem } from "@/components/layout/layout-item";
import LayoutControl from "@/components/layout/layout-control"; import { LayoutControl } from "@/components/layout/layout-control";
import LayoutTraffic from "@/components/layout/layout-traffic"; import { LayoutTraffic } from "@/components/layout/layout-traffic";
import UpdateButton from "@/components/layout/update-button"; import { UpdateButton } from "@/components/layout/update-button";
import useCustomTheme from "@/components/layout/use-custom-theme"; import { useCustomTheme } from "@/components/layout/use-custom-theme";
import getSystem from "@/utils/get-system"; import getSystem from "@/utils/get-system";
import "dayjs/locale/ru"; import "dayjs/locale/ru";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";