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,
} from "@mui/icons-material";
const LayoutControl = () => {
export const LayoutControl = () => {
const minWidth = 40;
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 type { LinkProps } from "react-router-dom";
const LayoutItem = (props: LinkProps) => {
export const LayoutItem = (props: LinkProps) => {
const { to, children } = props;
const resolved = useResolvedPath(to);
@ -40,5 +40,3 @@ const LayoutItem = (props: LinkProps) => {
</ListItem>
);
};
export default LayoutItem;

View File

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

View File

@ -1,17 +1,19 @@
import useSWR from "swr";
import { useState } from "react";
import { useRef } from "react";
import { Button } from "@mui/material";
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 {
className?: string;
}
const UpdateButton = (props: Props) => {
export const UpdateButton = (props: Props) => {
const { className } = props;
const [dialogOpen, setDialogOpen] = useState(false);
const viewerRef = useRef<DialogRef>(null);
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
errorRetryCount: 2,
revalidateIfStale: false,
@ -22,21 +24,17 @@ const UpdateButton = (props: Props) => {
return (
<>
<UpdateViewer ref={viewerRef} />
<Button
color="error"
variant="contained"
size="small"
className={className}
onClick={() => setDialogOpen(true)}
onClick={() => viewerRef.current?.open()}
>
New
</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
*/
export default function useCustomTheme() {
export const useCustomTheme = () => {
const { verge } = useVerge();
const { theme_mode, theme_setting } = verge ?? {};
const [mode, setMode] = useRecoilState(atomThemeMode);
@ -121,4 +121,4 @@ export default function useCustomTheme() {
}, [mode, theme_setting]);
return { theme };
}
};

View File

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

View File

@ -1,11 +1,13 @@
import { useRef } from "react";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import { IconButton, MenuItem, Select, Typography } from "@mui/material";
import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
import { ArrowForward } from "@mui/icons-material";
import { checkUpdate } from "@tauri-apps/api/updater";
import { useVerge } from "@/hooks/use-verge";
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 { ThemeModeSwitch } from "./mods/theme-mode-switch";
import { ConfigViewer } from "./mods/config-viewer";
@ -14,29 +16,45 @@ import { MiscViewer } from "./mods/misc-viewer";
import { ThemeViewer } from "./mods/theme-viewer";
import { GuardState } from "./mods/guard-state";
import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import getSystem from "@/utils/get-system";
interface Props {
onError?: (err: Error) => void;
}
const OS = getSystem();
const SettingVerge = ({ onError }: Props) => {
const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge();
const { theme_mode, theme_blur, traffic_graph, language } = verge ?? {};
const { theme_mode, language } = verge ?? {};
const configRef = useRef<DialogRef>(null);
const hotkeyRef = useRef<DialogRef>(null);
const miscRef = useRef<DialogRef>(null);
const themeRef = 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>) => {
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 (
<SettingList title={t("Verge Setting")}>
<ThemeViewer ref={themeRef} />
@ -44,6 +62,7 @@ const SettingVerge = ({ onError }: Props) => {
<HotkeyViewer ref={hotkeyRef} />
<MiscViewer ref={miscRef} />
<LayoutViewer ref={layoutRef} />
<UpdateViewer ref={updateRef} />
<SettingItem label={t("Language")}>
<GuardState
@ -160,6 +179,19 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton>
</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")}>
<Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography>
</SettingItem>

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": "Check for Updates",
"Verge Version": "Verge Version",
"theme.light": "Light",
"theme.dark": "Dark",

View File

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

View File

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