diff --git a/src/App.tsx b/src/App.tsx
deleted file mode 100644
index c1fe73a7d..000000000
--- a/src/App.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Layout from "./pages/_layout";
-import { AppDataProvider } from "./providers/app-data-provider";
-
-function App() {
- return (
-
-
-
- );
-}
-
-export default App;
diff --git a/src/components/center.tsx b/src/components/center.tsx
deleted file mode 100644
index 8204c3275..000000000
--- a/src/components/center.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Box, BoxProps } from "@mui/material";
-import React from "react";
-
-interface CenterProps extends BoxProps {
- children: React.ReactNode;
-}
-
-export const Center: React.FC = ({ children, ...props }) => {
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/home/clash-info-card.tsx b/src/components/home/clash-info-card.tsx
index bb89c7d31..7a4bff47f 100644
--- a/src/components/home/clash-info-card.tsx
+++ b/src/components/home/clash-info-card.tsx
@@ -3,14 +3,14 @@ import { Divider, Stack, Typography } from "@mui/material";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
+import { useClash } from "@/hooks/use-clash";
import {
useAppUptime,
useClashConfig,
useRulesData,
useSystemProxyAddress,
useSystemProxyData,
-} from "@/hooks/app-data";
-import { useClash } from "@/hooks/use-clash";
+} from "@/hooks/use-clash-data";
import { EnhancedCard } from "./enhanced-card";
diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx
index 2624ce2dd..e6fb20841 100644
--- a/src/components/home/clash-mode-card.tsx
+++ b/src/components/home/clash-mode-card.tsx
@@ -9,7 +9,7 @@ import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
-import { useClashConfig } from "@/hooks/app-data";
+import { useClashConfig } from "@/hooks/use-clash-data";
import { useVerge } from "@/hooks/use-verge";
import { patchClashMode } from "@/services/cmds";
import type { TranslationKey } from "@/types/generated/i18n-keys";
diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx
index 2aabba29b..6032152e9 100644
--- a/src/components/home/current-proxy-card.tsx
+++ b/src/components/home/current-proxy-card.tsx
@@ -34,7 +34,11 @@ import { useNavigate } from "react-router";
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
import { EnhancedCard } from "@/components/home/enhanced-card";
-import { useClashConfig, useProxiesData, useRulesData } from "@/hooks/app-data";
+import {
+ useClashConfig,
+ useProxiesData,
+ useRulesData,
+} from "@/hooks/use-clash-data";
import { useProfiles } from "@/hooks/use-profiles";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
diff --git a/src/components/home/home-profile-card.tsx b/src/components/home/home-profile-card.tsx
index 8e40287f4..21937ff3a 100644
--- a/src/components/home/home-profile-card.tsx
+++ b/src/components/home/home-profile-card.tsx
@@ -24,7 +24,7 @@ import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
-import { useRefreshAll } from "@/hooks/app-data";
+import { useRefreshAll } from "@/hooks/use-clash-data";
import { openWebUrl, updateProfile } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import parseTraffic from "@/utils/parse-traffic";
diff --git a/src/components/home/system-info-card.tsx b/src/components/home/system-info-card.tsx
index 8697291f5..c7b8cbfc4 100644
--- a/src/components/home/system-info-card.tsx
+++ b/src/components/home/system-info-card.tsx
@@ -20,9 +20,9 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import useSWR from "swr";
+import { useServiceInstaller } from "@/hooks/use-service-installer";
import { useSystemState } from "@/hooks/use-system-state";
import { useVerge } from "@/hooks/use-verge";
-import { useServiceInstaller } from "@/hooks/useServiceInstaller";
import { getSystemInfo } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import { checkUpdateSafe as checkUpdate } from "@/services/update";
diff --git a/src/components/proxy/provider-button.tsx b/src/components/proxy/provider-button.tsx
index 68217775a..861a57aec 100644
--- a/src/components/proxy/provider-button.tsx
+++ b/src/components/proxy/provider-button.tsx
@@ -22,7 +22,7 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
-import { useProxiesData, useProxyProvidersData } from "@/hooks/app-data";
+import { useProxiesData, useProxyProvidersData } from "@/hooks/use-clash-data";
import { showNotice } from "@/services/noticeService";
import parseTraffic from "@/utils/parse-traffic";
diff --git a/src/components/proxy/proxy-chain.tsx b/src/components/proxy/proxy-chain.tsx
index b10676ce1..9e47a289e 100644
--- a/src/components/proxy/proxy-chain.tsx
+++ b/src/components/proxy/proxy-chain.tsx
@@ -39,7 +39,7 @@ import {
selectNodeForGroup,
} from "tauri-plugin-mihomo-api";
-import { useProxiesData } from "@/hooks/app-data";
+import { useProxiesData } from "@/hooks/use-clash-data";
import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds";
import { debugLog } from "@/utils/debug";
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
index ab4814262..d103e06dd 100644
--- a/src/components/proxy/proxy-groups.tsx
+++ b/src/components/proxy/proxy-groups.tsx
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
-import { useProxiesData } from "@/hooks/app-data";
+import { useProxiesData } from "@/hooks/use-clash-data";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
import { updateProxyChainConfigInRuntime } from "@/services/cmds";
diff --git a/src/components/proxy/use-render-list.ts b/src/components/proxy/use-render-list.ts
index 9e25c58ec..dd7e96d6e 100644
--- a/src/components/proxy/use-render-list.ts
+++ b/src/components/proxy/use-render-list.ts
@@ -1,7 +1,7 @@
import { useEffect, useMemo } from "react";
import useSWR from "swr";
-import { useProxiesData } from "@/hooks/app-data";
+import { useProxiesData } from "@/hooks/use-clash-data";
import { useVerge } from "@/hooks/use-verge";
import { getRuntimeConfig } from "@/services/cmds";
import delayManager from "@/services/delay";
diff --git a/src/components/rule/provider-button.tsx b/src/components/rule/provider-button.tsx
index 875be01b3..59d6f651f 100644
--- a/src/components/rule/provider-button.tsx
+++ b/src/components/rule/provider-button.tsx
@@ -21,7 +21,10 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { updateRuleProvider } from "tauri-plugin-mihomo-api";
-import type { useRuleProvidersData, useRulesData } from "@/hooks/app-data";
+import type {
+ useRuleProvidersData,
+ useRulesData,
+} from "@/hooks/use-clash-data";
import { showNotice } from "@/services/noticeService";
// 辅助组件 - 类型框
diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx
index 4b7a4bd02..7a3f43b93 100644
--- a/src/components/setting/mods/sysproxy-viewer.tsx
+++ b/src/components/setting/mods/sysproxy-viewer.tsx
@@ -30,7 +30,7 @@ import {
useClashConfig,
useSystemProxyAddress,
useSystemProxyData,
-} from "@/hooks/app-data";
+} from "@/hooks/use-clash-data";
import { useVerge } from "@/hooks/use-verge";
import {
getAutotemProxy,
diff --git a/src/components/shared/ProxyControlSwitches.tsx b/src/components/shared/ProxyControlSwitches.tsx
index 79466dfb6..4eba4bc3f 100644
--- a/src/components/shared/ProxyControlSwitches.tsx
+++ b/src/components/shared/ProxyControlSwitches.tsx
@@ -16,11 +16,11 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { GuardState } from "@/components/setting/mods/guard-state";
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
import { TunViewer } from "@/components/setting/mods/tun-viewer";
+import { useServiceInstaller } from "@/hooks/use-service-installer";
+import { useServiceUninstaller } from "@/hooks/use-service-uninstaller";
import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
import { useSystemState } from "@/hooks/use-system-state";
import { useVerge } from "@/hooks/use-verge";
-import { useServiceInstaller } from "@/hooks/useServiceInstaller";
-import { useServiceUninstaller } from "@/hooks/useServiceUninstaller";
import { showNotice } from "@/services/noticeService";
interface ProxySwitchProps {
diff --git a/src/hooks/app-data.ts b/src/hooks/use-clash-data.ts
similarity index 100%
rename from src/hooks/app-data.ts
rename to src/hooks/use-clash-data.ts
diff --git a/src/hooks/use-cleanup.ts b/src/hooks/use-cleanup.ts
deleted file mode 100644
index 3766cec8c..000000000
--- a/src/hooks/use-cleanup.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useEffect, useRef } from "react";
-
-/**
- * 资源清理 Hook
- * 用于在组件卸载或窗口关闭时统一清理资源
- */
-export const useCleanup = () => {
- const cleanupFnsRef = useRef void>>(new Set());
-
- const registerCleanup = (fn: () => void) => {
- cleanupFnsRef.current.add(fn);
- return () => {
- cleanupFnsRef.current.delete(fn);
- };
- };
-
- const cleanup = () => {
- cleanupFnsRef.current.forEach((fn) => {
- try {
- fn();
- } catch (error) {
- console.error("[资源清理] 清理失败:", error);
- }
- });
- cleanupFnsRef.current.clear();
- };
-
- useEffect(() => {
- return () => {
- cleanup();
- };
- }, []);
-
- return { registerCleanup, cleanup };
-};
diff --git a/src/hooks/use-current-proxy.ts b/src/hooks/use-current-proxy.ts
deleted file mode 100644
index e1d1ab1ba..000000000
--- a/src/hooks/use-current-proxy.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useMemo } from "react";
-
-import { useClashConfig, useProxiesData } from "@/hooks/app-data";
-
-// 获取当前代理节点信息的自定义Hook
-export const useCurrentProxy = () => {
- const { proxies, refreshProxy } = useProxiesData();
- const { clashConfig } = useClashConfig();
-
- // 获取当前模式
- const currentMode = clashConfig?.mode?.toLowerCase() || "rule";
-
- // 获取当前代理节点信息
- const currentProxyInfo = useMemo(() => {
- if (!proxies) return { currentProxy: null, primaryGroupName: null };
-
- const globalGroup = proxies.global as IProxyGroupItem | undefined;
- const groups: IProxyGroupItem[] = Array.isArray(proxies.groups)
- ? (proxies.groups as IProxyGroupItem[])
- : [];
- const records = (proxies.records || {}) as Record;
-
- // 默认信息
- let primaryGroupName = "GLOBAL";
- let currentName = globalGroup?.now;
-
- // 在规则模式下,寻找主要代理组(通常是第一个或者名字包含特定关键词的组)
- if (currentMode === "rule" && groups.length > 0) {
- // 查找主要的代理组(优先级:包含关键词 > 第一个非GLOBAL组)
- const primaryKeywords = [
- "auto",
- "select",
- "proxy",
- "节点选择",
- "自动选择",
- ];
- const primaryGroup =
- groups.find((group) =>
- primaryKeywords.some((keyword) =>
- group.name.toLowerCase().includes(keyword.toLowerCase()),
- ),
- ) || groups.filter((g) => g.name !== "GLOBAL")[0];
-
- if (primaryGroup) {
- primaryGroupName = primaryGroup.name;
- currentName = primaryGroup.now;
- }
- }
-
- // 如果找不到当前节点,返回null
- if (!currentName) return { currentProxy: null, primaryGroupName };
-
- // 获取完整的节点信息
- const currentProxy = records[currentName] || {
- name: currentName,
- type: "Unknown",
- udp: false,
- xudp: false,
- tfo: false,
- mptcp: false,
- smux: false,
- history: [],
- };
-
- return { currentProxy, primaryGroupName };
- }, [proxies, currentMode]);
-
- return {
- currentProxy: currentProxyInfo.currentProxy,
- primaryGroupName: currentProxyInfo.primaryGroupName,
- mode: currentMode,
- refreshProxy,
- };
-};
diff --git a/src/hooks/useServiceInstaller.ts b/src/hooks/use-service-installer.ts
similarity index 100%
rename from src/hooks/useServiceInstaller.ts
rename to src/hooks/use-service-installer.ts
diff --git a/src/hooks/useServiceUninstaller.ts b/src/hooks/use-service-uninstaller.ts
similarity index 100%
rename from src/hooks/useServiceUninstaller.ts
rename to src/hooks/use-service-uninstaller.ts
diff --git a/src/hooks/use-system-proxy-state.ts b/src/hooks/use-system-proxy-state.ts
index ef25274b1..b6dc94752 100644
--- a/src/hooks/use-system-proxy-state.ts
+++ b/src/hooks/use-system-proxy-state.ts
@@ -2,7 +2,7 @@ import { useLockFn } from "ahooks";
import useSWR, { mutate } from "swr";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
-import { useSystemProxyData } from "@/hooks/app-data";
+import { useSystemProxyData } from "@/hooks/use-clash-data";
import { useVerge } from "@/hooks/use-verge";
import { getAutotemProxy } from "@/services/cmds";
diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx
index 73ff968a4..1d8e5309b 100644
--- a/src/pages/_layout.tsx
+++ b/src/pages/_layout.tsx
@@ -54,10 +54,12 @@ import { useWindowDecorations } from "@/hooks/use-window";
import { useThemeMode } from "@/services/states";
import getSystem from "@/utils/get-system";
-import { handleNoticeMessage } from "./_layout/notificationHandlers";
-import { useAppInitialization } from "./_layout/useAppInitialization";
-import { useLayoutEvents } from "./_layout/useLayoutEvents";
-import { useLoadingOverlay } from "./_layout/useLoadingOverlay";
+import {
+ useAppInitialization,
+ useLayoutEvents,
+ useLoadingOverlay,
+} from "./_layout/hooks";
+import { handleNoticeMessage } from "./_layout/utils";
import { navItems } from "./_routers";
import "dayjs/locale/ru";
diff --git a/src/pages/_layout/hooks/index.ts b/src/pages/_layout/hooks/index.ts
new file mode 100644
index 000000000..11de552f2
--- /dev/null
+++ b/src/pages/_layout/hooks/index.ts
@@ -0,0 +1,3 @@
+export { useAppInitialization } from "./use-app-initialization";
+export { useLayoutEvents } from "./use-layout-events";
+export { useLoadingOverlay } from "./use-loading-overlay";
diff --git a/src/pages/_layout/useAppInitialization.ts b/src/pages/_layout/hooks/use-app-initialization.ts
similarity index 81%
rename from src/pages/_layout/useAppInitialization.ts
rename to src/pages/_layout/hooks/use-app-initialization.ts
index 34abf9f7e..d791ecb5b 100644
--- a/src/pages/_layout/useAppInitialization.ts
+++ b/src/pages/_layout/hooks/use-app-initialization.ts
@@ -1,6 +1,8 @@
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useRef } from "react";
+import { hideInitialOverlay } from "../utils";
+
export const useAppInitialization = () => {
const initRef = useRef(false);
@@ -25,6 +27,8 @@ export const useAppInitialization = () => {
};
const notifyBackend = async (stage?: string) => {
+ if (isCancelled) return;
+
try {
if (stage) {
await invoke("update_ui_stage", { stage });
@@ -32,20 +36,16 @@ export const useAppInitialization = () => {
await invoke("notify_ui_ready");
}
} catch (err) {
- console.error(`[初始化] 通知后端失败:`, err);
+ console.error(`[Initialization] Failed to notify backend:`, err);
}
};
const removeLoadingOverlay = () => {
- const overlay = document.getElementById("initial-loading-overlay");
- if (overlay) {
- overlay.style.opacity = "0";
- scheduleTimeout(() => overlay.remove(), 300);
- }
+ hideInitialOverlay({ schedule: scheduleTimeout });
};
const performInitialization = async () => {
- if (isInitialized) return;
+ if (isCancelled || isInitialized) return;
isInitialized = true;
try {
@@ -70,14 +70,18 @@ export const useAppInitialization = () => {
await notifyBackend("ResourcesLoaded");
await notifyBackend();
} catch (error) {
- console.error("[初始化] 失败:", error);
- removeLoadingOverlay();
- notifyBackend().catch(console.error);
+ if (!isCancelled) {
+ console.error("[Initialization] Failed:", error);
+ removeLoadingOverlay();
+ notifyBackend().catch(console.error);
+ }
}
};
const checkBackendReady = async () => {
try {
+ if (isCancelled) return;
+
await invoke("update_ui_stage", { stage: "Loading" });
performInitialization();
} catch {
@@ -99,7 +103,7 @@ export const useAppInitialization = () => {
try {
window.clearTimeout(id);
} catch (error) {
- console.warn("[初始化] 清理定时器失败:", error);
+ console.warn("[Initialization] Failed to clear timer:", error);
}
});
timers.clear();
diff --git a/src/pages/_layout/useLayoutEvents.ts b/src/pages/_layout/hooks/use-layout-events.ts
similarity index 75%
rename from src/pages/_layout/useLayoutEvents.ts
rename to src/pages/_layout/hooks/use-layout-events.ts
index 0b7955b30..3f71ab34f 100644
--- a/src/pages/_layout/useLayoutEvents.ts
+++ b/src/pages/_layout/hooks/use-layout-events.ts
@@ -13,6 +13,10 @@ export const useLayoutEvents = (
useEffect(() => {
const unlisteners: Array<() => void> = [];
let disposed = false;
+ const revalidateKeys = (keys: readonly string[]) => {
+ const keySet = new Set(keys);
+ mutate((key) => typeof key === "string" && keySet.has(key));
+ };
const register = (
maybeUnlisten: void | (() => void) | Promise void)>,
@@ -33,25 +37,31 @@ export const useLayoutEvents = (
unlisteners.push(unlisten);
}
})
- .catch((error) => console.error("[事件监听] 注册失败", error));
+ .catch((error) =>
+ console.error("[Event Listener] Registration failed:", error),
+ );
};
register(
addListener("verge://refresh-clash-config", async () => {
- mutate("getProxies");
- mutate("getVersion");
- mutate("getClashConfig");
- mutate("getProxyProviders");
+ revalidateKeys([
+ "getProxies",
+ "getVersion",
+ "getClashConfig",
+ "getProxyProviders",
+ ]);
}),
);
register(
addListener("verge://refresh-verge-config", () => {
- mutate("getVergeConfig");
- mutate("getSystemProxy");
- mutate("getAutotemProxy");
- mutate("getRunningMode");
- mutate("isServiceAvailable");
+ revalidateKeys([
+ "getVergeConfig",
+ "getSystemProxy",
+ "getAutotemProxy",
+ "getRunningMode",
+ "isServiceAvailable",
+ ]);
}),
);
@@ -91,7 +101,7 @@ export const useLayoutEvents = (
if (errors.length > 0) {
console.error(
- `[事件监听] 清理过程中发生 ${errors.length} 个错误:`,
+ `[Event Listener] Encountered ${errors.length} errors during cleanup:`,
errors,
);
}
diff --git a/src/pages/_layout/useLoadingOverlay.ts b/src/pages/_layout/hooks/use-loading-overlay.ts
similarity index 52%
rename from src/pages/_layout/useLoadingOverlay.ts
rename to src/pages/_layout/hooks/use-loading-overlay.ts
index d26e138db..641baf15b 100644
--- a/src/pages/_layout/useLoadingOverlay.ts
+++ b/src/pages/_layout/hooks/use-loading-overlay.ts
@@ -1,13 +1,15 @@
import { useEffect, useRef } from "react";
+import { hideInitialOverlay } from "../utils";
+
export const useLoadingOverlay = (themeReady: boolean) => {
const overlayRemovedRef = useRef(false);
useEffect(() => {
if (!themeReady || overlayRemovedRef.current) return;
- let fadeTimer: number | null = null;
- let retryTimer: number | null = null;
+ let removalTimer: number | undefined;
+ let retryTimer: number | undefined;
let attempts = 0;
const maxAttempts = 50;
let stopped = false;
@@ -15,19 +17,15 @@ export const useLoadingOverlay = (themeReady: boolean) => {
const tryRemoveOverlay = () => {
if (stopped || overlayRemovedRef.current) return;
- const overlay = document.getElementById("initial-loading-overlay");
- if (overlay) {
- overlayRemovedRef.current = true;
- overlay.style.opacity = "0";
- overlay.style.pointerEvents = "none";
+ const { removed, removalTimer: timerId } = hideInitialOverlay({
+ assumeMissingAsRemoved: true,
+ });
+ if (typeof timerId === "number") {
+ removalTimer = timerId;
+ }
- fadeTimer = window.setTimeout(() => {
- try {
- overlay.remove();
- } catch (error) {
- console.warn("[加载遮罩] 移除失败:", error);
- }
- }, 300);
+ if (removed) {
+ overlayRemovedRef.current = true;
return;
}
@@ -35,7 +33,7 @@ export const useLoadingOverlay = (themeReady: boolean) => {
attempts += 1;
retryTimer = window.setTimeout(tryRemoveOverlay, 100);
} else {
- console.warn("[加载遮罩] 未找到元素");
+ console.warn("[Loading Overlay] Element not found");
}
};
@@ -43,8 +41,8 @@ export const useLoadingOverlay = (themeReady: boolean) => {
return () => {
stopped = true;
- if (fadeTimer) window.clearTimeout(fadeTimer);
- if (retryTimer) window.clearTimeout(retryTimer);
+ if (typeof removalTimer === "number") window.clearTimeout(removalTimer);
+ if (typeof retryTimer === "number") window.clearTimeout(retryTimer);
};
}, [themeReady]);
};
diff --git a/src/pages/_layout/useLazyDataLoad.ts b/src/pages/_layout/useLazyDataLoad.ts
deleted file mode 100644
index 3eb0434cf..000000000
--- a/src/pages/_layout/useLazyDataLoad.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useEffect, useRef } from "react";
-
-export const useLazyDataLoad = (
- callbacks: Array<() => void>,
- delay: number = 1000,
-) => {
- const hasLoadedRef = useRef(false);
-
- useEffect(() => {
- if (hasLoadedRef.current) return;
-
- const timer = window.setTimeout(() => {
- hasLoadedRef.current = true;
- callbacks.forEach((callback) => {
- try {
- callback();
- } catch (error) {
- console.error("[延迟加载] 执行失败:", error);
- }
- });
- }, delay);
-
- return () => window.clearTimeout(timer);
- }, [callbacks, delay]);
-};
diff --git a/src/pages/_layout/utils/index.ts b/src/pages/_layout/utils/index.ts
new file mode 100644
index 000000000..76dde298c
--- /dev/null
+++ b/src/pages/_layout/utils/index.ts
@@ -0,0 +1,2 @@
+export { hideInitialOverlay } from "./initial-loading-overlay";
+export { handleNoticeMessage } from "./notification-handlers";
diff --git a/src/pages/_layout/utils/initial-loading-overlay.ts b/src/pages/_layout/utils/initial-loading-overlay.ts
new file mode 100644
index 000000000..e57a14593
--- /dev/null
+++ b/src/pages/_layout/utils/initial-loading-overlay.ts
@@ -0,0 +1,45 @@
+const OVERLAY_ID = "initial-loading-overlay";
+const REMOVE_DELAY = 300;
+
+let overlayRemoved = false;
+
+type HideOverlayOptions = {
+ schedule?: (handler: () => void, delay: number) => number;
+ assumeMissingAsRemoved?: boolean;
+};
+
+type HideOverlayResult = {
+ removed: boolean;
+ removalTimer?: number;
+};
+
+export const hideInitialOverlay = (
+ options: HideOverlayOptions = {},
+): HideOverlayResult => {
+ if (overlayRemoved) {
+ return { removed: true };
+ }
+
+ const overlay = document.getElementById(OVERLAY_ID);
+ if (!overlay) {
+ if (options.assumeMissingAsRemoved) {
+ overlayRemoved = true;
+ return { removed: true };
+ }
+ return { removed: false };
+ }
+
+ overlayRemoved = true;
+ overlay.dataset.hidden = "true";
+
+ const schedule = options.schedule ?? window.setTimeout;
+ const removalTimer = schedule(() => {
+ try {
+ overlay.remove();
+ } catch (error) {
+ console.warn("[Loading Overlay] Removal failed:", error);
+ }
+ }, REMOVE_DELAY);
+
+ return { removed: true, removalTimer };
+};
diff --git a/src/pages/_layout/notificationHandlers.ts b/src/pages/_layout/utils/notification-handlers.ts
similarity index 100%
rename from src/pages/_layout/notificationHandlers.ts
rename to src/pages/_layout/utils/notification-handlers.ts
diff --git a/src/pages/rules.tsx b/src/pages/rules.tsx
index 266db15d7..b418e54ad 100644
--- a/src/pages/rules.tsx
+++ b/src/pages/rules.tsx
@@ -8,7 +8,7 @@ import { BaseSearchBox } from "@/components/base/base-search-box";
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
import { ProviderButton } from "@/components/rule/provider-button";
import RuleItem from "@/components/rule/rule-item";
-import { useRuleProvidersData, useRulesData } from "@/hooks/app-data";
+import { useRuleProvidersData, useRulesData } from "@/hooks/use-clash-data";
import { useVisibility } from "@/hooks/use-visibility";
const RulesPage = () => {