feat: refactor app data provider and context for improved data management and performance
This commit is contained in:
@@ -58,6 +58,32 @@ export default defineConfig([
|
|||||||
|
|
||||||
"@eslint-react/no-forward-ref": "off",
|
"@eslint-react/no-forward-ref": "off",
|
||||||
|
|
||||||
|
// React performance and production quality rules
|
||||||
|
"@eslint-react/no-array-index-key": "warn",
|
||||||
|
"@eslint-react/no-children-count": "error",
|
||||||
|
"@eslint-react/no-children-for-each": "error",
|
||||||
|
"@eslint-react/no-children-map": "error",
|
||||||
|
"@eslint-react/no-children-only": "error",
|
||||||
|
"@eslint-react/no-children-prop": "error",
|
||||||
|
"@eslint-react/no-children-to-array": "error",
|
||||||
|
"@eslint-react/no-class-component": "error",
|
||||||
|
"@eslint-react/no-clone-element": "error",
|
||||||
|
"@eslint-react/no-create-ref": "error",
|
||||||
|
"@eslint-react/no-default-props": "error",
|
||||||
|
"@eslint-react/no-direct-mutation-state": "error",
|
||||||
|
"@eslint-react/no-implicit-key": "error",
|
||||||
|
"@eslint-react/no-prop-types": "error",
|
||||||
|
"@eslint-react/no-set-state-in-component-did-mount": "error",
|
||||||
|
"@eslint-react/no-set-state-in-component-did-update": "error",
|
||||||
|
"@eslint-react/no-set-state-in-component-will-update": "error",
|
||||||
|
"@eslint-react/no-string-refs": "error",
|
||||||
|
"@eslint-react/no-unstable-context-value": "warn",
|
||||||
|
"@eslint-react/no-unstable-default-props": "warn",
|
||||||
|
"@eslint-react/no-unused-class-component-members": "error",
|
||||||
|
"@eslint-react/no-unused-state": "error",
|
||||||
|
"@eslint-react/no-useless-fragment": "warn",
|
||||||
|
"@eslint-react/prefer-destructuring-assignment": "warn",
|
||||||
|
|
||||||
// TypeScript
|
// TypeScript
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react/prop-types */
|
|
||||||
import { LoadingButton } from "@mui/lab";
|
import { LoadingButton } from "@mui/lab";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { DeveloperBoardOutlined } from "@mui/icons-material";
|
import { DeveloperBoardOutlined } from "@mui/icons-material";
|
||||||
import { Typography, Stack, Divider } from "@mui/material";
|
import { Divider, Stack, Typography } from "@mui/material";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
|
|
||||||
import { EnhancedCard } from "./enhanced-card";
|
import { EnhancedCard } from "./enhanced-card";
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
|
DirectionsRounded,
|
||||||
LanguageRounded,
|
LanguageRounded,
|
||||||
MultipleStopRounded,
|
MultipleStopRounded,
|
||||||
DirectionsRounded,
|
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { Box, Typography, Paper, Stack } from "@mui/material";
|
import { Box, Paper, Stack, Typography } from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { closeAllConnections } from "@/services/cmds";
|
import { closeAllConnections, patchClashMode } from "@/services/cmds";
|
||||||
import { patchClashMode } from "@/services/cmds";
|
|
||||||
|
|
||||||
export const ClashModeCard = () => {
|
export const ClashModeCard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
import {
|
import {
|
||||||
SignalWifi4Bar as SignalStrong,
|
AccessTimeRounded,
|
||||||
|
ChevronRight,
|
||||||
|
WifiOff as SignalError,
|
||||||
SignalWifi3Bar as SignalGood,
|
SignalWifi3Bar as SignalGood,
|
||||||
SignalWifi2Bar as SignalMedium,
|
SignalWifi2Bar as SignalMedium,
|
||||||
SignalWifi1Bar as SignalWeak,
|
|
||||||
SignalWifi0Bar as SignalNone,
|
SignalWifi0Bar as SignalNone,
|
||||||
WifiOff as SignalError,
|
SignalWifi4Bar as SignalStrong,
|
||||||
ChevronRight,
|
SignalWifi1Bar as SignalWeak,
|
||||||
SortRounded,
|
|
||||||
AccessTimeRounded,
|
|
||||||
SortByAlphaRounded,
|
SortByAlphaRounded,
|
||||||
|
SortRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
|
||||||
Chip,
|
|
||||||
Button,
|
Button,
|
||||||
alpha,
|
Chip,
|
||||||
useTheme,
|
|
||||||
Select,
|
|
||||||
MenuItem,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
|
IconButton,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
SelectChangeEvent,
|
SelectChangeEvent,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton,
|
Typography,
|
||||||
|
alpha,
|
||||||
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { EnhancedCard } from "@/components/home/enhanced-card";
|
import { EnhancedCard } from "@/components/home/enhanced-card";
|
||||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
// 本地存储的键名
|
// 本地存储的键名
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
ArrowUpwardRounded,
|
|
||||||
ArrowDownwardRounded,
|
ArrowDownwardRounded,
|
||||||
MemoryRounded,
|
ArrowUpwardRounded,
|
||||||
LinkRounded,
|
|
||||||
CloudUploadRounded,
|
|
||||||
CloudDownloadRounded,
|
CloudDownloadRounded,
|
||||||
|
CloudUploadRounded,
|
||||||
|
LinkRounded,
|
||||||
|
MemoryRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Typography,
|
Box,
|
||||||
|
Grid,
|
||||||
|
PaletteColor,
|
||||||
Paper,
|
Paper,
|
||||||
|
Typography,
|
||||||
alpha,
|
alpha,
|
||||||
useTheme,
|
useTheme,
|
||||||
PaletteColor,
|
|
||||||
Grid,
|
|
||||||
Box,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useRef, useCallback, memo, useMemo } from "react";
|
import { ReactNode, memo, useCallback, useMemo, useRef } from "react";
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@@ -24,8 +23,8 @@ import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary
|
|||||||
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
|
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useVisibility } from "@/hooks/use-visibility";
|
import { useVisibility } from "@/hooks/use-visibility";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { isDebugEnabled, gc } from "@/services/cmds";
|
import { gc, isDebugEnabled } from "@/services/cmds";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import {
|
import {
|
||||||
CloudUploadOutlined,
|
CloudUploadOutlined,
|
||||||
StorageOutlined,
|
|
||||||
UpdateOutlined,
|
|
||||||
DnsOutlined,
|
DnsOutlined,
|
||||||
SpeedOutlined,
|
|
||||||
EventOutlined,
|
EventOutlined,
|
||||||
LaunchOutlined,
|
LaunchOutlined,
|
||||||
|
SpeedOutlined,
|
||||||
|
StorageOutlined,
|
||||||
|
UpdateOutlined,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
|
||||||
Button,
|
Button,
|
||||||
Stack,
|
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
alpha,
|
|
||||||
useTheme,
|
|
||||||
Link,
|
Link,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
alpha,
|
||||||
keyframes,
|
keyframes,
|
||||||
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useMemo, useCallback, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
|
import { RefreshRounded, StorageOutlined } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
LinearProgress,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Typography,
|
Typography,
|
||||||
Divider,
|
|
||||||
LinearProgress,
|
|
||||||
alpha,
|
alpha,
|
||||||
styled,
|
styled,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
@@ -21,7 +21,7 @@ import dayjs from "dayjs";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { proxyProviderUpdate } from "@/services/cmds";
|
import { proxyProviderUpdate } from "@/services/cmds";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import {
|
import {
|
||||||
closeAllConnections,
|
closeAllConnections,
|
||||||
getProxies,
|
getProxies,
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import { ExpandMoreRounded } from "@mui/icons-material";
|
import { ExpandMoreRounded } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Snackbar,
|
|
||||||
Alert,
|
Alert,
|
||||||
|
Box,
|
||||||
Chip,
|
Chip,
|
||||||
Typography,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Snackbar,
|
||||||
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import {
|
import {
|
||||||
providerHealthCheck,
|
|
||||||
getGroupProxyDelays,
|
getGroupProxyDelays,
|
||||||
updateProxyChainConfigInRuntime,
|
|
||||||
getRuntimeConfig,
|
getRuntimeConfig,
|
||||||
|
providerHealthCheck,
|
||||||
|
updateProxyChainConfigInRuntime,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { useEffect, useMemo } from "react";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { getRuntimeConfig } from "@/services/cmds";
|
import { getRuntimeConfig } from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
import { filterSort } from "./use-filter-sort";
|
import { filterSort } from "./use-filter-sort";
|
||||||
import {
|
import {
|
||||||
useHeadStateNew,
|
|
||||||
DEFAULT_STATE,
|
DEFAULT_STATE,
|
||||||
|
useHeadStateNew,
|
||||||
type HeadState,
|
type HeadState,
|
||||||
} from "./use-head-state";
|
} from "./use-head-state";
|
||||||
import { useWindowWidth } from "./use-window-width";
|
import { useWindowWidth } from "./use-window-width";
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
|
import { RefreshRounded, StorageOutlined } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Typography,
|
Typography,
|
||||||
Divider,
|
|
||||||
alpha,
|
alpha,
|
||||||
styled,
|
styled,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
@@ -20,7 +20,7 @@ import dayjs from "dayjs";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { ruleProviderUpdate } from "@/services/cmds";
|
import { ruleProviderUpdate } from "@/services/cmds";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ import { BaseFieldset } from "@/components/base/base-fieldset";
|
|||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { getClashConfig } from "@/services/cmds";
|
|
||||||
import {
|
import {
|
||||||
getAutotemProxy,
|
getAutotemProxy,
|
||||||
|
getClashConfig,
|
||||||
getNetworkInterfacesInfo,
|
getNetworkInterfacesInfo,
|
||||||
getSystemHostname,
|
getSystemHostname,
|
||||||
getSystemProxy,
|
getSystemProxy,
|
||||||
@@ -440,14 +440,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
{!value.pac && (
|
{!value.pac && (
|
||||||
<>
|
<FlexBox>
|
||||||
<FlexBox>
|
<Typography className="label">{t("Server Addr")}</Typography>
|
||||||
<Typography className="label">{t("Server Addr")}</Typography>
|
<Typography className="value">{getSystemProxyAddress}</Typography>
|
||||||
<Typography className="value">
|
</FlexBox>
|
||||||
{getSystemProxyAddress}
|
|
||||||
</Typography>
|
|
||||||
</FlexBox>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{value.pac && (
|
{value.pac && (
|
||||||
<FlexBox>
|
<FlexBox>
|
||||||
@@ -582,39 +578,37 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{value.pac && (
|
{value.pac && (
|
||||||
<>
|
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
|
||||||
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
|
<ListItemText
|
||||||
<ListItemText
|
primary={t("PAC Script Content")}
|
||||||
primary={t("PAC Script Content")}
|
sx={{ padding: "3px 0" }}
|
||||||
sx={{ padding: "3px 0" }}
|
/>
|
||||||
/>
|
<Button
|
||||||
<Button
|
startIcon={<EditRounded />}
|
||||||
startIcon={<EditRounded />}
|
variant="outlined"
|
||||||
variant="outlined"
|
onClick={() => {
|
||||||
onClick={() => {
|
setEditorOpen(true);
|
||||||
setEditorOpen(true);
|
}}
|
||||||
|
>
|
||||||
|
{t("Edit")} PAC
|
||||||
|
</Button>
|
||||||
|
{editorOpen && (
|
||||||
|
<EditorViewer
|
||||||
|
open={true}
|
||||||
|
title={`${t("Edit")} PAC`}
|
||||||
|
initialData={Promise.resolve(value.pac_content ?? "")}
|
||||||
|
language="javascript"
|
||||||
|
onSave={(_prev, curr) => {
|
||||||
|
let pac = DEFAULT_PAC;
|
||||||
|
if (curr && curr.trim().length > 0) {
|
||||||
|
pac = curr;
|
||||||
|
}
|
||||||
|
setValue((v) => ({ ...v, pac_content: pac }));
|
||||||
}}
|
}}
|
||||||
>
|
onClose={() => setEditorOpen(false)}
|
||||||
{t("Edit")} PAC
|
/>
|
||||||
</Button>
|
)}
|
||||||
{editorOpen && (
|
</ListItem>
|
||||||
<EditorViewer
|
|
||||||
open={true}
|
|
||||||
title={`${t("Edit")} PAC`}
|
|
||||||
initialData={Promise.resolve(value.pac_content ?? "")}
|
|
||||||
language="javascript"
|
|
||||||
onSave={(_prev, curr) => {
|
|
||||||
let pac = DEFAULT_PAC;
|
|
||||||
if (curr && curr.trim().length > 0) {
|
|
||||||
pac = curr;
|
|
||||||
}
|
|
||||||
setValue((v) => ({ ...v, pac_content: pac }));
|
|
||||||
}}
|
|
||||||
onClose={() => setEditorOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
|
|
||||||
// 定义代理组类型
|
// 定义代理组类型
|
||||||
interface ProxyGroup {
|
interface ProxyGroup {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { getAutotemProxy } from "@/services/cmds";
|
import { closeAllConnections, getAutotemProxy } from "@/services/cmds";
|
||||||
import { closeAllConnections } from "@/services/cmds";
|
|
||||||
|
|
||||||
// 系统代理状态检测统一逻辑
|
// 系统代理状态检测统一逻辑
|
||||||
export const useSystemProxyState = () => {
|
export const useSystemProxyState = () => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { ConnectionItem } from "@/components/connection/connection-item";
|
import { ConnectionItem } from "@/components/connection/connection-item";
|
||||||
import { ConnectionTable } from "@/components/connection/connection-table";
|
import { ConnectionTable } from "@/components/connection/connection-table";
|
||||||
import { useVisibility } from "@/hooks/use-visibility";
|
import { useVisibility } from "@/hooks/use-visibility";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
import { closeAllConnections } from "@/services/cmds";
|
import { closeAllConnections } from "@/services/cmds";
|
||||||
import { useConnectionSetting } from "@/services/states";
|
import { useConnectionSetting } from "@/services/states";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useState, useMemo, useRef, useEffect } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
|
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import { ScrollTopButton } from "@/components/layout/scroll-top-button";
|
|||||||
import { ProviderButton } from "@/components/rule/provider-button";
|
import { ProviderButton } from "@/components/rule/provider-button";
|
||||||
import RuleItem from "@/components/rule/rule-item";
|
import RuleItem from "@/components/rule/rule-item";
|
||||||
import { useVisibility } from "@/hooks/use-visibility";
|
import { useVisibility } from "@/hooks/use-visibility";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-context";
|
||||||
|
|
||||||
const RulesPage = () => {
|
const RulesPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
53
src/providers/app-data-context.ts
Normal file
53
src/providers/app-data-context.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { createContext, use } from "react";
|
||||||
|
|
||||||
|
export interface AppDataContextType {
|
||||||
|
proxies: any;
|
||||||
|
clashConfig: any;
|
||||||
|
rules: any[];
|
||||||
|
sysproxy: any;
|
||||||
|
runningMode?: string;
|
||||||
|
uptime: number;
|
||||||
|
proxyProviders: any;
|
||||||
|
ruleProviders: any;
|
||||||
|
connections: {
|
||||||
|
data: ConnectionWithSpeed[];
|
||||||
|
count: number;
|
||||||
|
uploadTotal: number;
|
||||||
|
downloadTotal: number;
|
||||||
|
};
|
||||||
|
traffic: { up: number; down: number };
|
||||||
|
memory: { inuse: number };
|
||||||
|
systemProxyAddress: string;
|
||||||
|
|
||||||
|
refreshProxy: () => Promise<any>;
|
||||||
|
refreshClashConfig: () => Promise<any>;
|
||||||
|
refreshRules: () => Promise<any>;
|
||||||
|
refreshSysproxy: () => Promise<any>;
|
||||||
|
refreshProxyProviders: () => Promise<any>;
|
||||||
|
refreshRuleProviders: () => Promise<any>;
|
||||||
|
refreshAll: () => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConnectionWithSpeed extends IConnectionsItem {
|
||||||
|
curUpload: number;
|
||||||
|
curDownload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConnectionSpeedData {
|
||||||
|
id: string;
|
||||||
|
upload: number;
|
||||||
|
download: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppDataContext = createContext<AppDataContextType | null>(null);
|
||||||
|
|
||||||
|
export const useAppData = () => {
|
||||||
|
const context = use(AppDataContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAppData必须在AppDataProvider内使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import React, { createContext, use, useEffect, useMemo, useRef } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { useClashInfo } from "@/hooks/use-clash";
|
import { useClashInfo } from "@/hooks/use-clash";
|
||||||
@@ -20,50 +20,11 @@ import {
|
|||||||
getTrafficData,
|
getTrafficData,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
|
|
||||||
// 连接速度计算接口
|
import {
|
||||||
interface ConnectionSpeedData {
|
AppDataContext,
|
||||||
id: string;
|
type ConnectionSpeedData,
|
||||||
upload: number;
|
type ConnectionWithSpeed,
|
||||||
download: number;
|
} from "./app-data-context";
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConnectionWithSpeed extends IConnectionsItem {
|
|
||||||
curUpload: number;
|
|
||||||
curDownload: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义AppDataContext类型 - 使用宽松类型
|
|
||||||
interface AppDataContextType {
|
|
||||||
proxies: any;
|
|
||||||
clashConfig: any;
|
|
||||||
rules: any[];
|
|
||||||
sysproxy: any;
|
|
||||||
runningMode?: string;
|
|
||||||
uptime: number;
|
|
||||||
proxyProviders: any;
|
|
||||||
ruleProviders: any;
|
|
||||||
connections: {
|
|
||||||
data: ConnectionWithSpeed[];
|
|
||||||
count: number;
|
|
||||||
uploadTotal: number;
|
|
||||||
downloadTotal: number;
|
|
||||||
};
|
|
||||||
traffic: { up: number; down: number };
|
|
||||||
memory: { inuse: number };
|
|
||||||
systemProxyAddress: string;
|
|
||||||
|
|
||||||
refreshProxy: () => Promise<any>;
|
|
||||||
refreshClashConfig: () => Promise<any>;
|
|
||||||
refreshRules: () => Promise<any>;
|
|
||||||
refreshSysproxy: () => Promise<any>;
|
|
||||||
refreshProxyProviders: () => Promise<any>;
|
|
||||||
refreshRuleProviders: () => Promise<any>;
|
|
||||||
refreshAll: () => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建上下文
|
|
||||||
const AppDataContext = createContext<AppDataContextType | null>(null);
|
|
||||||
|
|
||||||
// 全局数据提供者组件
|
// 全局数据提供者组件
|
||||||
export const AppDataProvider = ({
|
export const AppDataProvider = ({
|
||||||
@@ -135,196 +96,227 @@ export const AppDataProvider = ({
|
|||||||
|
|
||||||
// 监听profile和clash配置变更事件
|
// 监听profile和clash配置变更事件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let profileUnlisten: Promise<() => void> | undefined;
|
|
||||||
let lastProfileId: string | null = null;
|
let lastProfileId: string | null = null;
|
||||||
let lastUpdateTime = 0;
|
let lastUpdateTime = 0;
|
||||||
const refreshThrottle = 500;
|
const refreshThrottle = 500;
|
||||||
|
|
||||||
const setupEventListeners = async () => {
|
let isUnmounted = false;
|
||||||
try {
|
const scheduledTimeouts = new Set<ReturnType<typeof setTimeout>>();
|
||||||
// 监听profile切换事件
|
const cleanupFns: Array<() => void> = [];
|
||||||
profileUnlisten = listen<string>("profile-changed", (event) => {
|
const fallbackWindowListeners: Array<[string, EventListener]> = [];
|
||||||
const newProfileId = event.payload;
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
console.log(`[AppDataProvider] Profile切换事件: ${newProfileId}`);
|
const registerCleanup = (fn: () => void) => {
|
||||||
|
if (isUnmounted) {
|
||||||
if (
|
fn();
|
||||||
lastProfileId === newProfileId &&
|
} else {
|
||||||
now - lastUpdateTime < refreshThrottle
|
cleanupFns.push(fn);
|
||||||
) {
|
|
||||||
console.log("[AppDataProvider] 重复事件被防抖,跳过");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastProfileId = newProfileId;
|
|
||||||
lastUpdateTime = now;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// 先执行 forceRefreshProxies,完成后稍延迟再刷新前端数据,避免页面一直 loading
|
|
||||||
forceRefreshProxies()
|
|
||||||
.catch((e) =>
|
|
||||||
console.warn("[AppDataProvider] forceRefreshProxies 失败:", e),
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
refreshProxy().catch((e) =>
|
|
||||||
console.warn("[AppDataProvider] 普通刷新也失败:", e),
|
|
||||||
);
|
|
||||||
}, 200); // 200ms 延迟,保证后端缓存已清理
|
|
||||||
});
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听Clash配置刷新事件(enhance操作等)
|
|
||||||
const handleRefreshClash = () => {
|
|
||||||
const now = Date.now();
|
|
||||||
console.log("[AppDataProvider] Clash配置刷新事件");
|
|
||||||
|
|
||||||
if (now - lastUpdateTime > refreshThrottle) {
|
|
||||||
lastUpdateTime = now;
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
console.log("[AppDataProvider] Clash刷新 - 强制刷新代理缓存");
|
|
||||||
|
|
||||||
// 添加超时保护
|
|
||||||
const refreshPromise = Promise.race([
|
|
||||||
forceRefreshProxies(),
|
|
||||||
new Promise((_, reject) =>
|
|
||||||
setTimeout(
|
|
||||||
() => reject(new Error("forceRefreshProxies timeout")),
|
|
||||||
8000,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await refreshPromise;
|
|
||||||
await refreshProxy();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"[AppDataProvider] Clash刷新时强制刷新代理缓存失败:",
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
refreshProxy().catch((e) =>
|
|
||||||
console.warn("[AppDataProvider] Clash刷新普通刷新也失败:", e),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听代理配置刷新事件(托盘代理切换等)
|
|
||||||
const handleRefreshProxy = () => {
|
|
||||||
const now = Date.now();
|
|
||||||
console.log("[AppDataProvider] 代理配置刷新事件");
|
|
||||||
|
|
||||||
if (now - lastUpdateTime > refreshThrottle) {
|
|
||||||
lastUpdateTime = now;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
refreshProxy().catch((e) =>
|
|
||||||
console.warn("[AppDataProvider] 代理刷新失败:", e),
|
|
||||||
);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听强制代理刷新事件(托盘代理切换立即刷新)
|
|
||||||
const handleForceRefreshProxies = () => {
|
|
||||||
console.log("[AppDataProvider] 强制代理刷新事件");
|
|
||||||
|
|
||||||
// 立即刷新,无延迟,无防抖
|
|
||||||
forceRefreshProxies()
|
|
||||||
.then(() => {
|
|
||||||
console.log("[AppDataProvider] 强制刷新代理缓存完成");
|
|
||||||
// 强制刷新完成后,立即刷新前端显示
|
|
||||||
return refreshProxy();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log("[AppDataProvider] 前端代理数据刷新完成");
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn("[AppDataProvider] 强制代理刷新失败:", e);
|
|
||||||
// 如果强制刷新失败,尝试普通刷新
|
|
||||||
refreshProxy().catch((e2) =>
|
|
||||||
console.warn("[AppDataProvider] 普通代理刷新也失败:", e2),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用 Tauri 事件监听器替代 window 事件监听器
|
|
||||||
const setupTauriListeners = async () => {
|
|
||||||
try {
|
|
||||||
const unlistenClash = await listen(
|
|
||||||
"verge://refresh-clash-config",
|
|
||||||
handleRefreshClash,
|
|
||||||
);
|
|
||||||
const unlistenProxy = await listen(
|
|
||||||
"verge://refresh-proxy-config",
|
|
||||||
handleRefreshProxy,
|
|
||||||
);
|
|
||||||
const unlistenForceRefresh = await listen(
|
|
||||||
"verge://force-refresh-proxies",
|
|
||||||
handleForceRefreshProxies,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlistenClash();
|
|
||||||
unlistenProxy();
|
|
||||||
unlistenForceRefresh();
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("[AppDataProvider] 设置 Tauri 事件监听器失败:", error);
|
|
||||||
|
|
||||||
// 降级到 window 事件监听器
|
|
||||||
window.addEventListener(
|
|
||||||
"verge://refresh-clash-config",
|
|
||||||
handleRefreshClash,
|
|
||||||
);
|
|
||||||
window.addEventListener(
|
|
||||||
"verge://refresh-proxy-config",
|
|
||||||
handleRefreshProxy,
|
|
||||||
);
|
|
||||||
window.addEventListener(
|
|
||||||
"verge://force-refresh-proxies",
|
|
||||||
handleForceRefreshProxies,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener(
|
|
||||||
"verge://refresh-clash-config",
|
|
||||||
handleRefreshClash,
|
|
||||||
);
|
|
||||||
window.removeEventListener(
|
|
||||||
"verge://refresh-proxy-config",
|
|
||||||
handleRefreshProxy,
|
|
||||||
);
|
|
||||||
window.removeEventListener(
|
|
||||||
"verge://force-refresh-proxies",
|
|
||||||
handleForceRefreshProxies,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanupTauriListeners = setupTauriListeners();
|
|
||||||
|
|
||||||
return async () => {
|
|
||||||
const cleanup = await cleanupTauriListeners;
|
|
||||||
cleanup();
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[AppDataProvider] 事件监听器设置失败:", error);
|
|
||||||
return () => {};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanupPromise = setupEventListeners();
|
const scheduleTimeout = (
|
||||||
|
callback: () => void | Promise<void>,
|
||||||
|
delay: number,
|
||||||
|
) => {
|
||||||
|
const timeoutId = window.setTimeout(() => {
|
||||||
|
scheduledTimeouts.delete(timeoutId);
|
||||||
|
void callback();
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
scheduledTimeouts.add(timeoutId);
|
||||||
|
return timeoutId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearScheduledTimeout = (
|
||||||
|
timeoutId: ReturnType<typeof setTimeout>,
|
||||||
|
) => {
|
||||||
|
if (scheduledTimeouts.has(timeoutId)) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
scheduledTimeouts.delete(timeoutId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllTimeouts = () => {
|
||||||
|
scheduledTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
|
||||||
|
scheduledTimeouts.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const withTimeout = async <T,>(
|
||||||
|
promise: Promise<T>,
|
||||||
|
timeoutMs: number,
|
||||||
|
label: string,
|
||||||
|
): Promise<T> => {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
|
timeoutId = scheduleTimeout(() => reject(new Error(label)), timeoutMs);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await Promise.race([promise, timeoutPromise]);
|
||||||
|
} finally {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearScheduledTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProfileChanged = (event: { payload: string }) => {
|
||||||
|
const newProfileId = event.payload;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
console.log(`[AppDataProvider] Profile切换事件: ${newProfileId}`);
|
||||||
|
|
||||||
|
if (
|
||||||
|
lastProfileId === newProfileId &&
|
||||||
|
now - lastUpdateTime < refreshThrottle
|
||||||
|
) {
|
||||||
|
console.log("[AppDataProvider] 重复事件被防抖,跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastProfileId = newProfileId;
|
||||||
|
lastUpdateTime = now;
|
||||||
|
|
||||||
|
scheduleTimeout(() => {
|
||||||
|
void forceRefreshProxies()
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("[AppDataProvider] forceRefreshProxies 失败:", error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
scheduleTimeout(() => {
|
||||||
|
refreshProxy().catch((error) => {
|
||||||
|
console.warn("[AppDataProvider] 普通刷新也失败:", error);
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefreshClash = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
console.log("[AppDataProvider] Clash配置刷新事件");
|
||||||
|
|
||||||
|
if (now - lastUpdateTime <= refreshThrottle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdateTime = now;
|
||||||
|
|
||||||
|
scheduleTimeout(async () => {
|
||||||
|
try {
|
||||||
|
console.log("[AppDataProvider] Clash刷新 - 强制刷新代理缓存");
|
||||||
|
await withTimeout(
|
||||||
|
forceRefreshProxies(),
|
||||||
|
8000,
|
||||||
|
"forceRefreshProxies timeout",
|
||||||
|
);
|
||||||
|
await refreshProxy();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[AppDataProvider] Clash刷新时强制刷新代理缓存失败:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
refreshProxy().catch((e) =>
|
||||||
|
console.warn("[AppDataProvider] Clash刷新普通刷新也失败:", e),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefreshProxy = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
console.log("[AppDataProvider] 代理配置刷新事件");
|
||||||
|
|
||||||
|
if (now - lastUpdateTime <= refreshThrottle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdateTime = now;
|
||||||
|
|
||||||
|
scheduleTimeout(() => {
|
||||||
|
refreshProxy().catch((error) =>
|
||||||
|
console.warn("[AppDataProvider] 代理刷新失败:", error),
|
||||||
|
);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForceRefreshProxies = () => {
|
||||||
|
console.log("[AppDataProvider] 强制代理刷新事件");
|
||||||
|
|
||||||
|
void forceRefreshProxies()
|
||||||
|
.then(() => {
|
||||||
|
console.log("[AppDataProvider] 强制刷新代理缓存完成");
|
||||||
|
return refreshProxy();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log("[AppDataProvider] 前端代理数据刷新完成");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("[AppDataProvider] 强制代理刷新失败:", error);
|
||||||
|
refreshProxy().catch((fallbackError) => {
|
||||||
|
console.warn(
|
||||||
|
"[AppDataProvider] 普通代理刷新也失败:",
|
||||||
|
fallbackError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeListeners = async () => {
|
||||||
|
try {
|
||||||
|
const unlistenProfile = await listen<string>(
|
||||||
|
"profile-changed",
|
||||||
|
handleProfileChanged,
|
||||||
|
);
|
||||||
|
registerCleanup(unlistenProfile);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[AppDataProvider] 监听 Profile 事件失败:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const unlistenClash = await listen(
|
||||||
|
"verge://refresh-clash-config",
|
||||||
|
handleRefreshClash,
|
||||||
|
);
|
||||||
|
const unlistenProxy = await listen(
|
||||||
|
"verge://refresh-proxy-config",
|
||||||
|
handleRefreshProxy,
|
||||||
|
);
|
||||||
|
const unlistenForceRefresh = await listen(
|
||||||
|
"verge://force-refresh-proxies",
|
||||||
|
handleForceRefreshProxies,
|
||||||
|
);
|
||||||
|
|
||||||
|
registerCleanup(() => {
|
||||||
|
unlistenClash();
|
||||||
|
unlistenProxy();
|
||||||
|
unlistenForceRefresh();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[AppDataProvider] 设置 Tauri 事件监听器失败:", error);
|
||||||
|
|
||||||
|
const fallbackHandlers: Array<[string, EventListener]> = [
|
||||||
|
["verge://refresh-clash-config", handleRefreshClash],
|
||||||
|
["verge://refresh-proxy-config", handleRefreshProxy],
|
||||||
|
["verge://force-refresh-proxies", handleForceRefreshProxies],
|
||||||
|
];
|
||||||
|
|
||||||
|
fallbackHandlers.forEach(([eventName, handler]) => {
|
||||||
|
window.addEventListener(eventName, handler);
|
||||||
|
fallbackWindowListeners.push([eventName, handler]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void initializeListeners();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
profileUnlisten?.then((unlisten) => unlisten()).catch(console.error);
|
isUnmounted = true;
|
||||||
cleanupPromise.then((cleanup) => cleanup());
|
clearAllTimeouts();
|
||||||
|
fallbackWindowListeners.splice(0).forEach(([eventName, handler]) => {
|
||||||
|
window.removeEventListener(eventName, handler);
|
||||||
|
});
|
||||||
|
cleanupFns.splice(0).forEach((fn) => fn());
|
||||||
};
|
};
|
||||||
}, [refreshProxy]);
|
}, [refreshProxy]);
|
||||||
|
|
||||||
@@ -474,7 +466,7 @@ export const AppDataProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 提供统一的刷新方法
|
// 提供统一的刷新方法
|
||||||
const refreshAll = React.useCallback(async () => {
|
const refreshAll = useCallback(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
refreshProxy(),
|
refreshProxy(),
|
||||||
refreshClashConfig(),
|
refreshClashConfig(),
|
||||||
@@ -585,14 +577,3 @@ export const AppDataProvider = ({
|
|||||||
|
|
||||||
return <AppDataContext value={value}>{children}</AppDataContext>;
|
return <AppDataContext value={value}>{children}</AppDataContext>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 自定义Hook访问全局数据
|
|
||||||
export const useAppData = () => {
|
|
||||||
const context = use(AppDataContext);
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useAppData必须在AppDataProvider内使用");
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user