feat: add check for updates button, close #766
This commit is contained in:
parent
16d80718cb
commit
f4c7b17a87
@ -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;
|
|
||||||
|
@ -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;
|
|
||||||
|
@ -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;
|
|
||||||
|
@ -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;
|
|
||||||
|
@ -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 };
|
||||||
}
|
};
|
||||||
|
@ -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;
|
|
@ -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>
|
||||||
|
@ -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",
|
||||||
|
@ -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": "深色",
|
||||||
|
@ -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";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user